diff --git a/Development/Scripts/Windows/CallBuildTool.bat b/Development/Scripts/Windows/CallBuildTool.bat index 9f60368c5..22c2d1dd7 100644 --- a/Development/Scripts/Windows/CallBuildTool.bat +++ b/Development/Scripts/Windows/CallBuildTool.bat @@ -11,16 +11,6 @@ for %%I in (Source\Logo.png) do if %%~zI LSS 2000 ( call "Development\Scripts\Windows\GetMSBuildPath.bat" if errorlevel 1 goto Error_NoVisualStudioEnvironment -if not exist "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" goto Compile -for /f "delims=" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -latest -products * -requires Microsoft.Component.MSBuild -property installationPath') do ( - for %%j in (15.0, Current) do ( - if exist "%%i\MSBuild\%%j\Bin\MSBuild.exe" ( - set MSBUILD_PATH="%%i\MSBuild\%%j\Bin\MSBuild.exe" - goto Compile - ) - ) -) - :Compile md Cache\Intermediate >nul 2>nul dir /s /b Source\Tools\Flax.Build\*.cs >Cache\Intermediate\Flax.Build.Files.txt @@ -44,7 +34,7 @@ goto Exit echo CallBuildTool ERROR: The script is in invalid directory. goto Exit :Error_NoVisualStudioEnvironment -echo CallBuildTool ERROR: Missing Visual Studio 2015 or newer. +echo CallBuildTool ERROR: Missing Visual Studio 2022 or newer. goto Exit :Error_CompilationFailed echo CallBuildTool ERROR: Failed to compile Flax.Build project. diff --git a/Development/Scripts/Windows/GetMSBuildPath.bat b/Development/Scripts/Windows/GetMSBuildPath.bat index b20230118..f44155272 100644 --- a/Development/Scripts/Windows/GetMSBuildPath.bat +++ b/Development/Scripts/Windows/GetMSBuildPath.bat @@ -4,66 +4,26 @@ rem Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. set MSBUILD_PATH= +rem Look for MSBuild version 17.0 or later if not exist "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" goto VsWhereNotFound -for /f "delims=" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -latest -products * -requires Microsoft.Component.MSBuild -property installationPath') do ( - if exist "%%i\MSBuild\15.0\Bin\MSBuild.exe" ( - set MSBUILD_PATH="%%i\MSBuild\15.0\Bin\MSBuild.exe" - goto End - ) -) -for /f "delims=" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -latest -prerelease -products * -requires Microsoft.Component.MSBuild -property installationPath') do ( - if exist "%%i\MSBuild\15.0\Bin\MSBuild.exe" ( - set MSBUILD_PATH="%%i\MSBuild\15.0\Bin\MSBuild.exe" - goto End - ) +for /f "delims=" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -version 17.0 -latest -products * -requires Microsoft.Component.MSBuild -property installationPath') do ( if exist "%%i\MSBuild\Current\Bin\MSBuild.exe" ( set MSBUILD_PATH="%%i\MSBuild\Current\Bin\MSBuild.exe" goto End ) ) -:VsWhereNotFound -if exist "%ProgramFiles(x86)%\MSBuild\14.0\bin\MSBuild.exe" ( - set MSBUILD_PATH="%ProgramFiles(x86)%\MSBuild\14.0\bin\MSBuild.exe" - goto End +rem Look for MSBuild version 17.0 or later in pre-release versions +for /f "delims=" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -version 17.0 -latest -prerelease -products * -requires Microsoft.Component.MSBuild -property installationPath') do ( + if exist "%%i\MSBuild\Current\Bin\MSBuild.exe" ( + set MSBUILD_PATH="%%i\MSBuild\Current\Bin\MSBuild.exe" + goto End + ) ) - -call :GetInstallPath Microsoft\VisualStudio\SxS\VS7 15.0 MSBuild\15.0\bin\MSBuild.exe -if not errorlevel 1 goto End -call :GetInstallPath Microsoft\MSBuild\ToolsVersions\14.0 MSBuildToolsPath MSBuild.exe -if not errorlevel 1 goto End -call :GetInstallPath Microsoft\MSBuild\ToolsVersions\12.0 MSBuildToolsPath MSBuild.exe -if not errorlevel 1 goto End -call :GetInstallPath Microsoft\MSBuild\ToolsVersions\4.0 MSBuildToolsPath MSBuild.exe -if not errorlevel 1 goto End - +echo GetMSBuildPath ERROR: Could not find MSBuild version 17.0 or later. +exit /B 1 +:VsWhereNotFound +echo GetMSBuildPath ERROR: vswhere.exe was not found. exit /B 1 :End -exit /B 0 - -:GetInstallPath -for /f "tokens=2,*" %%A in ('REG.exe query HKCU\SOFTWARE\%1 /v %2 2^>Nul') do ( - if exist "%%B%%3" ( - set MSBUILD_PATH="%%B%3" - exit /B 0 - ) -) -for /f "tokens=2,*" %%A in ('REG.exe query HKLM\SOFTWARE\%1 /v %2 2^>Nul') do ( - if exist "%%B%3" ( - set MSBUILD_PATH="%%B%3" - exit /B 0 - ) -) -for /f "tokens=2,*" %%A in ('REG.exe query HKCU\SOFTWARE\Wow6432Node\%1 /v %2 2^>Nul') do ( - if exist "%%B%%3" ( - set MSBUILD_PATH="%%B%3" - exit /B 0 - ) -) -for /f "tokens=2,*" %%A in ('REG.exe query HKLM\SOFTWARE\Wow6432Node\%1 /v %2 2^>Nul') do ( - if exist "%%B%3" ( - set MSBUILD_PATH="%%B%3" - exit /B 0 - ) -) -exit /B 1 +exit /B 0 \ No newline at end of file diff --git a/Flax.flaxproj b/Flax.flaxproj index 50fcaeda3..fbf48cc2b 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -3,7 +3,7 @@ "Version": { "Major": 1, "Minor": 6, - "Build": 6344 + "Build": 6345 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.", diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings index e8af1461c..6a22f211b 100644 --- a/Flax.sln.DotSettings +++ b/Flax.sln.DotSettings @@ -291,6 +291,8 @@ True True True + True + True True True True diff --git a/GenerateProjectFiles.bat b/GenerateProjectFiles.bat index 1b766457f..622939c34 100644 --- a/GenerateProjectFiles.bat +++ b/GenerateProjectFiles.bat @@ -1,15 +1,22 @@ @echo off - -rem Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +:: Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. setlocal pushd + echo Generating Flax Engine project files... -rem Run Flax.Build to generate Visual Studio solution and project files (also pass the arguments) -call "Development\Scripts\Windows\CallBuildTool.bat" -genproject %* +:: Change the path to the script root +cd /D "%~dp0" + +:: Run Flax.Build to generate Visual Studio solution and project files (also pass the arguments) +call "Development\Scripts\Windows\CallBuildTool.bat" -genproject %* 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 + popd echo Done! exit /B 0 diff --git a/GenerateProjectFiles.command b/GenerateProjectFiles.command index 6554886bc..5ee5c0783 100755 --- a/GenerateProjectFiles.command +++ b/GenerateProjectFiles.command @@ -10,3 +10,8 @@ cd "`dirname "$0"`" # Run Flax.Build to generate project files (also pass the arguments) 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 diff --git a/GenerateProjectFiles.sh b/GenerateProjectFiles.sh index fcda1acc1..dceb8abe8 100755 --- a/GenerateProjectFiles.sh +++ b/GenerateProjectFiles.sh @@ -10,3 +10,8 @@ cd "`dirname "$0"`" # Run Flax.Build to generate project files (also pass the arguments) 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 diff --git a/README.md b/README.md index dc5abb84f..fac631a6a 100644 --- a/README.md +++ b/README.md @@ -31,19 +31,20 @@ Follow the instructions below to compile and run the engine from source. * Install Visual Studio 2022 or newer * Install Windows 8.1 SDK or newer (via Visual Studio Installer) * Install Microsoft Visual C++ 2015 v140 toolset or newer (via Visual Studio Installer) -* Install .Net 7 SDK (via Visual Studio Installer or [from web](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)) +* Install .NET 7 SDK for **Windows x64** (via Visual Studio Installer or [from web](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)) * Install Git with LFS * Clone repo (with LFS) * Run **GenerateProjectFiles.bat** * Open `Flax.sln` and set solution configuration to **Editor.Development** and solution platform to **Win64** * Set Flax (C++) or FlaxEngine (C#) as startup project * Compile Flax project (hit F7 or CTRL+Shift+B) +* Optionally set Debug Type to **Managed Only (.NET Core)** to debug C#-only, or **Mixed (.NET Core)** to debug both C++ and C# * Run Flax (hit F5 key) ## Linux * Install Visual Studio Code -* Install .Net 7 SDK ([https://dotnet.microsoft.com/en-us/download/dotnet/7.0](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)) +* Install .NET 7 SDK ([https://dotnet.microsoft.com/en-us/download/dotnet/7.0](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)) * Ubuntu: `sudo apt install dotnet-sdk-7.0` * Install Vulkan SDK ([https://vulkan.lunarg.com/](https://vulkan.lunarg.com/)) * Ubuntu: `sudo apt install vulkan-sdk` @@ -66,7 +67,7 @@ Follow the instructions below to compile and run the engine from source. ## Mac * Install XCode -* Install .Net 7 SDK ([https://dotnet.microsoft.com/en-us/download/dotnet/7.0](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)) +* Install .NET 7 SDK ([https://dotnet.microsoft.com/en-us/download/dotnet/7.0](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)) * Install Vulkan SDK ([https://vulkan.lunarg.com/](https://vulkan.lunarg.com/)) * Clone repo (with LFS) * Run `GenerateProjectFiles.command` diff --git a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp index c059acba9..b66a96a9b 100644 --- a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp @@ -5,6 +5,7 @@ #include "MacPlatformTools.h" #include "Engine/Platform/File.h" #include "Engine/Platform/FileSystem.h" +#include "Engine/Platform/CreateProcessSettings.h" #include "Engine/Platform/Mac/MacPlatformSettings.h" #include "Engine/Core/Config/GameSettings.h" #include "Engine/Core/Config/BuildSettings.h" @@ -124,17 +125,35 @@ bool MacPlatformTools::OnPostProcess(CookingData& data) LOG(Error, "Failed to export application icon."); return true; } - bool failed = Platform::RunProcess(TEXT("sips -z 16 16 icon_1024x1024.png --out icon_16x16.png"), tmpFolderPath); - failed |= Platform::RunProcess(TEXT("sips -z 32 32 icon_1024x1024.png --out icon_16x16@2x.png"), tmpFolderPath); - failed |= Platform::RunProcess(TEXT("sips -z 32 32 icon_1024x1024.png --out icon_32x32.png"), tmpFolderPath); - failed |= Platform::RunProcess(TEXT("sips -z 64 64 icon_1024x1024.png --out icon_32x32@2x.png"), tmpFolderPath); - failed |= Platform::RunProcess(TEXT("sips -z 128 128 icon_1024x1024.png --out icon_128x128.png"), tmpFolderPath); - failed |= Platform::RunProcess(TEXT("sips -z 256 256 icon_1024x1024.png --out icon_128x128@2x.png"), tmpFolderPath); - failed |= Platform::RunProcess(TEXT("sips -z 256 256 icon_1024x1024.png --out icon_256x256.png"), tmpFolderPath); - failed |= Platform::RunProcess(TEXT("sips -z 512 512 icon_1024x1024.png --out icon_256x256@2x.png"), tmpFolderPath); - failed |= Platform::RunProcess(TEXT("sips -z 512 512 icon_1024x1024.png --out icon_512x512.png"), tmpFolderPath); - failed |= Platform::RunProcess(TEXT("sips -z 1024 1024 icon_1024x1024.png --out icon_512x512@2x.png"), tmpFolderPath); - failed |= Platform::RunProcess(TEXT("iconutil -c icns icon.iconset"), iconFolderPath); + CreateProcessSettings procSettings; + procSettings.HiddenWindow = true; + procSettings.FileName = TEXT("/usr/bin/sips"); + procSettings.WorkingDirectory = tmpFolderPath; + procSettings.Arguments = TEXT("-z 16 16 icon_1024x1024.png --out icon_16x16.png"); + bool failed = false; + failed |= Platform::CreateProcess(procSettings); + procSettings.Arguments = TEXT("-z 32 32 icon_1024x1024.png --out icon_16x16@2x.png"); + failed |= Platform::CreateProcess(procSettings); + procSettings.Arguments = TEXT("-z 32 32 icon_1024x1024.png --out icon_32x32.png"); + failed |= Platform::CreateProcess(procSettings); + procSettings.Arguments = TEXT("-z 64 64 icon_1024x1024.png --out icon_32x32@2x.png"); + failed |= Platform::CreateProcess(procSettings); + procSettings.Arguments = TEXT("-z 128 128 icon_1024x1024.png --out icon_128x128.png"); + failed |= Platform::CreateProcess(procSettings); + procSettings.Arguments = TEXT("-z 256 256 icon_1024x1024.png --out icon_128x128@2x.png"); + failed |= Platform::CreateProcess(procSettings); + procSettings.Arguments = TEXT("-z 256 256 icon_1024x1024.png --out icon_256x256.png"); + failed |= Platform::CreateProcess(procSettings); + procSettings.Arguments = TEXT("-z 512 512 icon_1024x1024.png --out icon_256x256@2x.png"); + failed |= Platform::CreateProcess(procSettings); + procSettings.Arguments = TEXT("-z 512 512 icon_1024x1024.png --out icon_512x512.png"); + failed |= Platform::CreateProcess(procSettings); + procSettings.Arguments = TEXT("-z 1024 1024 icon_1024x1024.png --out icon_512x512@2x.png"); + failed |= Platform::CreateProcess(procSettings); + procSettings.FileName = TEXT("/usr/bin/iconutil"); + procSettings.Arguments = TEXT("-c icns icon.iconset"); + procSettings.WorkingDirectory = iconFolderPath; + failed |= Platform::CreateProcess(procSettings); if (failed) { LOG(Error, "Failed to export application icon."); @@ -210,16 +229,22 @@ bool MacPlatformTools::OnPostProcess(CookingData& data) return false; GameCooker::PackageFiles(); LOG(Info, "Building app package..."); - const String dmgPath = data.OriginalOutputPath / appName + TEXT(".dmg"); - const String dmgCommand = String::Format(TEXT("hdiutil create {0}.dmg -volname {0} -fs HFS+ -srcfolder {0}.app"), appName); - const int32 result = Platform::RunProcess(dmgCommand, data.OriginalOutputPath); - if (result != 0) { - data.Error(String::Format(TEXT("Failed to package app (result code: {0}). See log for more info."), result)); - return true; + const String dmgPath = data.OriginalOutputPath / appName + TEXT(".dmg"); + CreateProcessSettings procSettings; + procSettings.HiddenWindow = true; + procSettings.WorkingDirectory = data.OriginalOutputPath; + procSettings.FileName = TEXT("/usr/bin/hdiutil"); + procSettings.Arguments = String::Format(TEXT("create {0}.dmg -volname {0} -fs HFS+ -srcfolder {0}.app"), appName); + const int32 result = Platform::CreateProcess(procSettings); + if (result != 0) + { + data.Error(String::Format(TEXT("Failed to package app (result code: {0}). See log for more info."), result)); + return true; + } + // TODO: sign dmg + LOG(Info, "Output application package: {0} (size: {1} MB)", dmgPath, FileSystem::GetFileSize(dmgPath) / 1024 / 1024); } - // TODO: sign dmg - LOG(Info, "Output application package: {0} (size: {1} MB)", dmgPath, FileSystem::GetFileSize(dmgPath) / 1024 / 1024); return false; } diff --git a/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp b/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp index 8b0cf06a8..85a7f99fa 100644 --- a/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp @@ -260,15 +260,20 @@ bool iOSPlatformTools::OnPostProcess(CookingData& data) { LOG(Info, "Building app package..."); const Char* configuration = data.Configuration == BuildConfiguration::Release ? TEXT("Release") : TEXT("Debug"); - String command = String::Format(TEXT("xcodebuild -project FlaxGame.xcodeproj -configuration {} -scheme FlaxGame -archivePath FlaxGame.xcarchive archive"), configuration); - int32 result = Platform::RunProcess(command, data.OriginalOutputPath); + CreateProcessSettings procSettings; + procSettings.HiddenWindow = true; + procSettings.WorkingDirectory = data.OriginalOutputPath; + procSettings.FileName = TEXT("/usr/bin/xcodebuild"); + procSettings.Arguments = String::Format(TEXT("-project FlaxGame.xcodeproj -configuration {} -scheme FlaxGame -archivePath FlaxGame.xcarchive archive"), configuration); + int32 result = Platform::CreateProcess(procSettings); if (result != 0) { data.Error(String::Format(TEXT("Failed to package app (result code: {0}). See log for more info."), result)); return true; } - command = TEXT("xcodebuild -exportArchive -archivePath FlaxGame.xcarchive -allowProvisioningUpdates -exportPath . -exportOptionsPlist ExportOptions.plist"); - result = Platform::RunProcess(command, data.OriginalOutputPath); + procSettings.FileName = TEXT("/usr/bin/xcodebuild"); + procSettings.Arguments = TEXT("-exportArchive -archivePath FlaxGame.xcarchive -allowProvisioningUpdates -exportPath . -exportOptionsPlist ExportOptions.plist"); + result = Platform::CreateProcess(procSettings); if (result != 0) { data.Error(String::Format(TEXT("Failed to package app (result code: {0}). See log for more info."), result)); diff --git a/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs b/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs index ec71348cf..5b2cfc144 100644 --- a/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs @@ -25,9 +25,20 @@ namespace FlaxEditor.CustomEditors.Dedicated get { // All selected particle effects use the same system - var effect = (ParticleEffect)Values[0]; - var system = effect.ParticleSystem; - return system != null && Values.TrueForAll(x => (x as ParticleEffect)?.ParticleSystem == system); + var effect = Values[0] as ParticleEffect; + var system = effect ? effect.ParticleSystem : null; + if (system && Values.TrueForAll(x => x is ParticleEffect fx && fx && fx.ParticleSystem == system)) + { + // All parameters can be accessed + var parameters = effect.Parameters; + foreach (var parameter in parameters) + { + if (!parameter) + return false; + } + return true; + } + return false; } } diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index bf82e19da..aaf45bc52 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -25,6 +25,7 @@ namespace FlaxEditor.CustomEditors.Dedicated private DragHandlers _dragHandlers; private DragScriptItems _dragScripts; private DragAssets _dragAssets; + private Button _addScriptsButton; /// /// The parent scripts editor. @@ -40,16 +41,19 @@ namespace FlaxEditor.CustomEditors.Dedicated AutoFocus = false; // Add script button - float addScriptButtonWidth = 60.0f; - var addScriptButton = new Button + var buttonText = "Add script"; + var textSize = Style.Current.FontMedium.MeasureText(buttonText); + float addScriptButtonWidth = (textSize.X < 60.0f) ? 60.0f : textSize.X + 4; + var buttonHeight = (textSize.Y < 18) ? 18 : textSize.Y + 4; + _addScriptsButton = new Button { TooltipText = "Add new scripts to the actor", AnchorPreset = AnchorPresets.MiddleCenter, - Text = "Add script", + Text = buttonText, Parent = this, - Bounds = new Rectangle((Width - addScriptButtonWidth) / 2, 1, addScriptButtonWidth, 18), + Bounds = new Rectangle((Width - addScriptButtonWidth) / 2, 1, addScriptButtonWidth, buttonHeight), }; - addScriptButton.ButtonClicked += OnAddScriptButtonClicked; + _addScriptsButton.ButtonClicked += OnAddScriptButtonClicked; } private void OnAddScriptButtonClicked(Button button) @@ -82,7 +86,7 @@ namespace FlaxEditor.CustomEditors.Dedicated var size = Size; // Info - Render2D.DrawText(style.FontSmall, "Drag scripts here", new Rectangle(2, 22, size.X - 4, size.Y - 4 - 20), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontSmall, "Drag scripts here", new Rectangle(2, _addScriptsButton.Height + 4, size.X - 4, size.Y - 4 - 20), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); // Check if drag is over if (IsDragOver && _dragHandlers != null && _dragHandlers.HasValidDrag) @@ -576,8 +580,8 @@ namespace FlaxEditor.CustomEditors.Dedicated return; for (int j = 0; j < e.Length; j++) { - var t1 = scripts[j]?.TypeName; - var t2 = e[j]?.TypeName; + var t1 = scripts[j] != null ? scripts[j].TypeName : null; + var t2 = e[j] != null ? e[j].TypeName : null; if (t1 != t2) return; } diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs index fde4967e8..5215e23a4 100644 --- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs @@ -422,12 +422,14 @@ namespace FlaxEditor.CustomEditors.Dedicated // Set control type button var space = layout.Space(20); - float setTypeButtonWidth = 60.0f; + var buttonText = "Set Type"; + var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(buttonText); + float setTypeButtonWidth = (textSize.X < 60.0f) ? 60.0f : textSize.X + 4; var setTypeButton = new Button { TooltipText = "Sets the control to the given type", AnchorPreset = AnchorPresets.MiddleCenter, - Text = "Set Type", + Text = buttonText, Parent = space.Spacer, Bounds = new Rectangle((space.Spacer.Width - setTypeButtonWidth) / 2, 1, setTypeButtonWidth, 18), }; diff --git a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs index cb4e7b9c1..4aa02ac78 100644 --- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs @@ -88,20 +88,20 @@ namespace FlaxEditor.CustomEditors.Editors LinkValues = Editor.Instance.Windows.PropertiesWin.ScaleLinked; // Add button with the link icon + _linkButton = new Button { BackgroundBrush = new SpriteBrush(Editor.Instance.Icons.Link32), Parent = LinkedLabel, Width = 18, Height = 18, - AnchorPreset = AnchorPresets.TopLeft, + AnchorPreset = AnchorPresets.MiddleLeft, }; _linkButton.Clicked += ToggleLink; ToggleEnabled(); SetLinkStyle(); - var x = LinkedLabel.Text.Value.Length * 7 + 5; - _linkButton.LocalX += x; - _linkButton.LocalY += 1; + var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(LinkedLabel.Text.Value); + _linkButton.LocalX += textSize.X + 10; LinkedLabel.SetupContextMenu += (label, menu, editor) => { menu.AddSeparator(); diff --git a/Source/Editor/CustomEditors/Editors/BooleanEditor.cs b/Source/Editor/CustomEditors/Editors/BooleanEditor.cs index f5edafed8..803b7b4dd 100644 --- a/Source/Editor/CustomEditors/Editors/BooleanEditor.cs +++ b/Source/Editor/CustomEditors/Editors/BooleanEditor.cs @@ -34,7 +34,9 @@ namespace FlaxEditor.CustomEditors.Editors } else { - element.CheckBox.Checked = (bool)Values[0]; + var value = (bool?)Values[0]; + if (value != null) + element.CheckBox.Checked = value.Value; } } } diff --git a/Source/Editor/CustomEditors/Editors/InputEditor.cs b/Source/Editor/CustomEditors/Editors/InputEditor.cs index 416626e81..7b2682a84 100644 --- a/Source/Editor/CustomEditors/Editors/InputEditor.cs +++ b/Source/Editor/CustomEditors/Editors/InputEditor.cs @@ -1,8 +1,10 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System.Collections.Generic; +using FlaxEditor.CustomEditors.GUI; +using FlaxEditor.GUI; +using FlaxEditor.GUI.ContextMenu; using FlaxEngine; -using FlaxEngine.GUI; namespace FlaxEditor.CustomEditors.Editors { @@ -12,7 +14,7 @@ namespace FlaxEditor.CustomEditors.Editors [CustomEditor(typeof(InputEvent)), DefaultEditor] public class InputEventEditor : CustomEditor { - private Dropdown _dropdown; + private ComboBox _comboBox; /// public override DisplayStyle Style => DisplayStyle.Inline; @@ -20,23 +22,30 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { - var dropdownElement = layout.Custom(); - _dropdown = dropdownElement.CustomControl; - var names = new List(); + LinkedLabel.SetupContextMenu += OnSetupContextMenu; + var comboBoxElement = layout.ComboBox(); + _comboBox = comboBoxElement.ComboBox; + var names = new List(); foreach (var mapping in Input.ActionMappings) { if (!names.Contains(mapping.Name)) names.Add(mapping.Name); } - _dropdown.Items = names; + _comboBox.Items = names; if (Values[0] is InputEvent inputEvent && names.Contains(inputEvent.Name)) - _dropdown.SelectedItem = inputEvent.Name; - _dropdown.SelectedIndexChanged += OnSelectedIndexChanged; + _comboBox.SelectedItem = inputEvent.Name; + _comboBox.SelectedIndexChanged += OnSelectedIndexChanged; } - private void OnSelectedIndexChanged(Dropdown dropdown) + private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkededitor) { - SetValue(new InputEvent(dropdown.SelectedItem)); + var button = menu.AddButton("Set to null"); + button.Clicked += () => _comboBox.SelectedItem = null; + } + + private void OnSelectedIndexChanged(ComboBox comboBox) + { + SetValue(comboBox.SelectedItem == null ? null : new InputEvent(comboBox.SelectedItem)); } /// @@ -49,17 +58,21 @@ namespace FlaxEditor.CustomEditors.Editors } else { - if (Values[0] is InputEvent inputEvent && _dropdown.Items.Contains(inputEvent.Name)) - _dropdown.SelectedItem = inputEvent.Name; + if (Values[0] is InputEvent inputEvent && _comboBox.Items.Contains(inputEvent.Name)) + _comboBox.SelectedItem = inputEvent.Name; + else + _comboBox.SelectedItem = null; } } /// protected override void Deinitialize() { - if (_dropdown != null) - _dropdown.SelectedIndexChanged -= OnSelectedIndexChanged; - _dropdown = null; + if (LinkedLabel != null) + LinkedLabel.SetupContextMenu -= OnSetupContextMenu; + if (_comboBox != null) + _comboBox.SelectedIndexChanged -= OnSelectedIndexChanged; + _comboBox = null; } } @@ -69,7 +82,7 @@ namespace FlaxEditor.CustomEditors.Editors [CustomEditor(typeof(InputAxis)), DefaultEditor] public class InputAxisEditor : CustomEditor { - private Dropdown _dropdown; + private ComboBox _comboBox; /// public override DisplayStyle Style => DisplayStyle.Inline; @@ -77,23 +90,30 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { - var dropdownElement = layout.Custom(); - _dropdown = dropdownElement.CustomControl; - var names = new List(); + LinkedLabel.SetupContextMenu += OnSetupContextMenu; + var comboBoxElement = layout.ComboBox(); + _comboBox = comboBoxElement.ComboBox; + var names = new List(); foreach (var mapping in Input.AxisMappings) { if (!names.Contains(mapping.Name)) names.Add(mapping.Name); } - _dropdown.Items = names; + _comboBox.Items = names; if (Values[0] is InputAxis inputAxis && names.Contains(inputAxis.Name)) - _dropdown.SelectedItem = inputAxis.Name; - _dropdown.SelectedIndexChanged += OnSelectedIndexChanged; + _comboBox.SelectedItem = inputAxis.Name; + _comboBox.SelectedIndexChanged += OnSelectedIndexChanged; } - private void OnSelectedIndexChanged(Dropdown dropdown) + private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkededitor) { - SetValue(new InputAxis(dropdown.SelectedItem)); + var button = menu.AddButton("Set to null"); + button.Clicked += () => _comboBox.SelectedItem = null; + } + + private void OnSelectedIndexChanged(ComboBox comboBox) + { + SetValue(comboBox.SelectedItem == null ? null : new InputAxis(comboBox.SelectedItem)); } /// @@ -106,17 +126,21 @@ namespace FlaxEditor.CustomEditors.Editors } else { - if (Values[0] is InputAxis inputAxis && _dropdown.Items.Contains(inputAxis.Name)) - _dropdown.SelectedItem = inputAxis.Name; + if (Values[0] is InputAxis inputAxis && _comboBox.Items.Contains(inputAxis.Name)) + _comboBox.SelectedItem = inputAxis.Name; + else + _comboBox.SelectedItem = null; } } /// protected override void Deinitialize() { - if (_dropdown != null) - _dropdown.SelectedIndexChanged -= OnSelectedIndexChanged; - _dropdown = null; + if (LinkedLabel != null) + LinkedLabel.SetupContextMenu -= OnSetupContextMenu; + if (_comboBox != null) + _comboBox.SelectedIndexChanged -= OnSelectedIndexChanged; + _comboBox = null; } } } diff --git a/Source/Editor/CustomEditors/Editors/TagEditor.cs b/Source/Editor/CustomEditors/Editors/TagEditor.cs index 3d2dd86aa..911397785 100644 --- a/Source/Editor/CustomEditors/Editors/TagEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TagEditor.cs @@ -623,13 +623,18 @@ namespace FlaxEditor.CustomEditors.Editors { _label = layout.ClickableLabel(GetText(out _)).CustomControl; _label.RightClick += ShowPicker; + var buttonText = "..."; var button = new Button { Size = new Float2(16.0f), - Text = "...", + Text = buttonText, TooltipText = "Edit...", Parent = _label, }; + var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(buttonText); + if (textSize.Y > button.Width) + button.Width = textSize.Y + 2; + button.SetAnchorPreset(AnchorPresets.MiddleRight, false, true); button.Clicked += ShowPicker; } @@ -674,9 +679,9 @@ namespace FlaxEditor.CustomEditors.Editors } set { - if (Values[0] is Tag[]) + if (Values[0] is Tag[] || Values.Type.Type == typeof(Tag[])) SetValue(value); - if (Values[0] is List) + else if (Values[0] is List || Values.Type.Type == typeof(List)) SetValue(new List(value)); } } diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 7f0359331..06a6f0979 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; @@ -13,6 +14,7 @@ using FlaxEditor.Content.Thumbnails; using FlaxEditor.Modules; using FlaxEditor.Modules.SourceCodeEditing; using FlaxEditor.Options; +using FlaxEditor.SceneGraph.Actors; using FlaxEditor.States; using FlaxEditor.Windows; using FlaxEditor.Windows.Assets; @@ -1274,6 +1276,69 @@ namespace FlaxEditor Scene.MarkSceneEdited(scenes); } + /// + /// Bakes all environmental probes in the scene. + /// + public void BakeAllEnvProbes() + { + Scene.ExecuteOnGraph(node => + { + if (node is EnvironmentProbeNode envProbeNode && envProbeNode.IsActive) + { + ((EnvironmentProbe)envProbeNode.Actor).Bake(); + node.ParentScene.IsEdited = true; + } + else if (node is SkyLightNode skyLightNode && skyLightNode.IsActive && skyLightNode.Actor is SkyLight skyLight && skyLight.Mode == SkyLight.Modes.CaptureScene) + { + skyLight.Bake(); + node.ParentScene.IsEdited = true; + } + + return node.IsActive; + }); + } + + /// + /// Builds CSG for all open scenes. + /// + public void BuildCSG() + { + var scenes = Level.Scenes; + scenes.ToList().ForEach(x => x.BuildCSG(0)); + Scene.MarkSceneEdited(scenes); + } + + /// + /// Builds Nav mesh for all open scenes. + /// + public void BuildNavMesh() + { + var scenes = Level.Scenes; + scenes.ToList().ForEach(x => Navigation.BuildNavMesh(x, 0)); + Scene.MarkSceneEdited(scenes); + } + + /// + /// Builds SDF for all static models in the scene. + /// + public void BuildAllMeshesSDF() + { + // TODO: async maybe with progress reporting? + Scene.ExecuteOnGraph(node => + { + if (node is StaticModelNode staticModelNode && staticModelNode.Actor is StaticModel staticModel) + { + if (staticModel.DrawModes.HasFlag(DrawPass.GlobalSDF) && staticModel.Model != null && !staticModel.Model.IsVirtual && staticModel.Model.SDF.Texture == null) + { + Log("Generating SDF for " + staticModel.Model); + if (!staticModel.Model.GenerateSDF()) + staticModel.Model.Save(); + } + } + return true; + }); + } + #endregion #region Internal Calls diff --git a/Source/Editor/EditorAssets.cs b/Source/Editor/EditorAssets.cs index 7fa920ca6..eb2f21356 100644 --- a/Source/Editor/EditorAssets.cs +++ b/Source/Editor/EditorAssets.cs @@ -36,7 +36,9 @@ namespace FlaxEditor public static void OnEditorOptionsChanged(Options.EditorOptions options) { - var param = _highlightMaterial?.GetParameter("Color"); + if (!_highlightMaterial) + return; + var param = _highlightMaterial.GetParameter("Color"); if (param != null) param.Value = options.Visual.HighlightColor; } diff --git a/Source/Editor/GUI/ComboBox.cs b/Source/Editor/GUI/ComboBox.cs index da2106450..0417cc7e3 100644 --- a/Source/Editor/GUI/ComboBox.cs +++ b/Source/Editor/GUI/ComboBox.cs @@ -545,7 +545,7 @@ namespace FlaxEditor.GUI Render2D.DrawRectangle(clientRect.MakeExpanded(-2.0f), borderColor); // Check if has selected item - if (_selectedIndices.Count > 0) + if (_selectedIndices != null && _selectedIndices.Count > 0) { string text = _selectedIndices.Count == 1 ? _items[_selectedIndices[0]] : "Multiple Values"; diff --git a/Source/Editor/GUI/ContextMenu/ContextMenu.cs b/Source/Editor/GUI/ContextMenu/ContextMenu.cs index f2e5d30e3..25f45a1f8 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenu.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenu.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using FlaxEditor.Options; using FlaxEngine; using FlaxEngine.GUI; @@ -269,6 +270,24 @@ namespace FlaxEditor.GUI.ContextMenu return item; } + /// + /// Adds the button. + /// + /// The text. + /// The input binding. + /// On button clicked event. + /// Created context menu item control. + public ContextMenuButton AddButton(string text, InputBinding binding, Action clicked) + { + var item = new ContextMenuButton(this, text, binding.ToString()) + { + Parent = _panel + }; + item.Clicked += clicked; + SortButtons(); + return item; + } + /// /// Gets the child menu (with that name). /// diff --git a/Source/Editor/GUI/Dialogs/Dialog.cs b/Source/Editor/GUI/Dialogs/Dialog.cs index 8910cfe49..07fc3ff0d 100644 --- a/Source/Editor/GUI/Dialogs/Dialog.cs +++ b/Source/Editor/GUI/Dialogs/Dialog.cs @@ -290,7 +290,11 @@ namespace FlaxEditor.GUI.Dialogs OnCancel(); return true; case KeyboardKeys.Tab: - Root?.Navigate(NavDirection.Next); + if (Root != null) + { + bool shiftDown = Root.GetKey(KeyboardKeys.Shift); + Root.Navigate(shiftDown ? NavDirection.Previous : NavDirection.Next); + } return true; } return false; diff --git a/Source/Editor/GUI/Docking/DockHintWindow.cs b/Source/Editor/GUI/Docking/DockHintWindow.cs index 6dc700731..52c5dcd3c 100644 --- a/Source/Editor/GUI/Docking/DockHintWindow.cs +++ b/Source/Editor/GUI/Docking/DockHintWindow.cs @@ -476,6 +476,7 @@ namespace FlaxEditor.GUI.Docking settings.ShowInTaskbar = false; settings.ActivateWhenFirstShown = false; settings.IsTopmost = true; + settings.ShowAfterFirstPaint = false; win = Platform.CreateWindow(ref settings); diff --git a/Source/Editor/GUI/MainMenu.cs b/Source/Editor/GUI/MainMenu.cs index 370ad056b..b313fed9f 100644 --- a/Source/Editor/GUI/MainMenu.cs +++ b/Source/Editor/GUI/MainMenu.cs @@ -292,7 +292,8 @@ namespace FlaxEditor.GUI return true; #if PLATFORM_WINDOWS - if (_useCustomWindowSystem) + var child = GetChildAtRecursive(location); + if (_useCustomWindowSystem && child is not Button && child is not MainMenuButton) { if (_window.IsMaximized) _window.Restore(); diff --git a/Source/Editor/GUI/Row.cs b/Source/Editor/GUI/Row.cs index 8dad8b20d..b07d693e5 100644 --- a/Source/Editor/GUI/Row.cs +++ b/Source/Editor/GUI/Row.cs @@ -37,6 +37,9 @@ namespace FlaxEditor.GUI : base(0, 0, 100, height) { Depth = -1; + + if (Height < Style.Current.FontMedium.Height) + Height = Style.Current.FontMedium.Height + 4; } /// diff --git a/Source/Editor/GUI/Timeline/SceneAnimationTimeline.cs b/Source/Editor/GUI/Timeline/SceneAnimationTimeline.cs index c5989cca5..76b6ca246 100644 --- a/Source/Editor/GUI/Timeline/SceneAnimationTimeline.cs +++ b/Source/Editor/GUI/Timeline/SceneAnimationTimeline.cs @@ -33,7 +33,7 @@ namespace FlaxEditor.GUI.Timeline /// public SceneAnimationPlayer Player { - get => _player; + get => _player != null ? _player : null; set { if (_player == value) @@ -268,7 +268,7 @@ namespace FlaxEditor.GUI.Timeline /// public override void OnSeek(int frame) { - if (_player?.Animation) + if (_player != null && _player.Animation) { _player.Animation.WaitForLoaded(); _player.Time = frame / _player.Animation.FramesPerSecond; diff --git a/Source/Editor/Modules/ContentDatabaseModule.cs b/Source/Editor/Modules/ContentDatabaseModule.cs index 29d6b2c00..143352421 100644 --- a/Source/Editor/Modules/ContentDatabaseModule.cs +++ b/Source/Editor/Modules/ContentDatabaseModule.cs @@ -643,7 +643,8 @@ namespace FlaxEditor.Modules /// Deletes the specified item. /// /// The item. - public void Delete(ContentItem item) + /// If the file was deleted by the user and not outside the editor. + public void Delete(ContentItem item, bool deletedByUser = false) { if (item == null) throw new ArgumentNullException(); @@ -667,12 +668,12 @@ namespace FlaxEditor.Modules var children = folder.Children.ToArray(); for (int i = 0; i < children.Length; i++) { - Delete(children[i]); + Delete(children[i], deletedByUser); } } // Remove directory - if (Directory.Exists(path)) + if (deletedByUser && Directory.Exists(path)) { try { @@ -701,7 +702,7 @@ namespace FlaxEditor.Modules // Delete asset by using content pool FlaxEngine.Content.DeleteAsset(path); } - else + else if (deletedByUser) { // Delete file if (File.Exists(path)) @@ -847,7 +848,7 @@ namespace FlaxEditor.Modules Editor.Log(string.Format($"Content item \'{child.Path}\' has been removed")); // Destroy it - Delete(child); + Delete(child, false); i--; } diff --git a/Source/Editor/Modules/ContentEditingModule.cs b/Source/Editor/Modules/ContentEditingModule.cs index 3db27af40..a6d8132f0 100644 --- a/Source/Editor/Modules/ContentEditingModule.cs +++ b/Source/Editor/Modules/ContentEditingModule.cs @@ -90,6 +90,12 @@ namespace FlaxEditor.Modules hint = "Too long name."; return false; } + + if (item.IsFolder && shortName.EndsWith(".")) + { + hint = "Name cannot end with '.'"; + return false; + } // Find invalid characters if (Utilities.Utils.HasInvalidPathChar(shortName)) @@ -120,7 +126,7 @@ namespace FlaxEditor.Modules // Cache data string sourcePath = item.Path; string sourceFolder = System.IO.Path.GetDirectoryName(sourcePath); - string extension = System.IO.Path.GetExtension(sourcePath); + string extension = item.IsFolder ? "" : System.IO.Path.GetExtension(sourcePath); string destinationPath = StringUtils.CombinePaths(sourceFolder, shortName + extension); if (item.IsFolder) diff --git a/Source/Editor/Modules/ProgressReportingModule.cs b/Source/Editor/Modules/ProgressReportingModule.cs index 16acd7f8b..22ae5ea98 100644 --- a/Source/Editor/Modules/ProgressReportingModule.cs +++ b/Source/Editor/Modules/ProgressReportingModule.cs @@ -129,6 +129,7 @@ namespace FlaxEditor.Modules else { Editor.UI.UpdateProgress(string.Empty, 0); + Editor.UI.UpdateStatusBar(); } } diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 08ec09de8..299865dea 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -42,8 +42,10 @@ namespace FlaxEditor.Modules private ContextMenuButton _menuFileSaveScenes; private ContextMenuButton _menuFileCloseScenes; + private ContextMenuButton _menuFileOpenScriptsProject; private ContextMenuButton _menuFileGenerateScriptsProjectFiles; - private ContextMenuButton _menuSaveAll; + private ContextMenuButton _menuFileRecompileScripts; + private ContextMenuButton _menuFileSaveAll; private ContextMenuButton _menuEditUndo; private ContextMenuButton _menuEditRedo; private ContextMenuButton _menuEditCut; @@ -62,15 +64,19 @@ namespace FlaxEditor.Modules private ContextMenuButton _menuGamePlayCurrentScenes; private ContextMenuButton _menuGameStop; private ContextMenuButton _menuGamePause; + private ContextMenuButton _menuGameCookAndRun; + private ContextMenuButton _menuGameRunCookedGame; private ContextMenuButton _menuToolsBuildScenes; private ContextMenuButton _menuToolsBakeLightmaps; private ContextMenuButton _menuToolsClearLightmaps; private ContextMenuButton _menuToolsBakeAllEnvProbes; private ContextMenuButton _menuToolsBuildCSGMesh; private ContextMenuButton _menuToolsBuildNavMesh; - private ContextMenuButton _menuToolsBuildAllMesgesSDF; + private ContextMenuButton _menuToolsBuildAllMeshesSDF; private ContextMenuButton _menuToolsCancelBuilding; + private ContextMenuButton _menuToolsProfilerWindow; private ContextMenuButton _menuToolsSetTheCurrentSceneViewAsDefault; + private ContextMenuButton _menuToolsTakeScreenshot; private ContextMenuChildMenu _menuWindowApplyWindowLayout; private ToolStripButton _toolStripSaveAll; @@ -279,7 +285,7 @@ namespace FlaxEditor.Modules Color color; if (Editor.StateMachine.IsPlayMode) - color = Color.OrangeRed; + color = Style.Current.Statusbar.PlayMode; else color = Style.Current.BackgroundSelected; @@ -293,6 +299,11 @@ namespace FlaxEditor.Modules else text = "Ready"; + if(ProgressVisible) + { + color = Style.Current.Statusbar.Loading; + } + StatusBar.Text = text; StatusBar.StatusColor = color; _contentStats = contentStats; @@ -338,7 +349,7 @@ namespace FlaxEditor.Modules internal void ProgressFailed(string message) { _progressFailed = true; - StatusBar.StatusColor = Color.Red; + StatusBar.StatusColor = Style.Current.Statusbar.Failed; StatusBar.Text = message; _outputLogButton.Visible = true; } @@ -391,6 +402,10 @@ namespace FlaxEditor.Modules { UpdateStatusBar(); } + else if(ProgressVisible) + { + UpdateStatusBar(); + } } private class CustomWindowBorderControl : Control @@ -510,13 +525,13 @@ namespace FlaxEditor.Modules MenuFile = MainMenu.AddButton("File"); var cm = MenuFile.ContextMenu; cm.VisibleChanged += OnMenuFileShowHide; - _menuSaveAll = cm.AddButton("Save All", inputOptions.Save.ToString(), Editor.SaveAll); - _menuFileSaveScenes = cm.AddButton("Save scenes", Editor.Scene.SaveScenes); - _menuFileCloseScenes = cm.AddButton("Close scenes", Editor.Scene.CloseAllScenes); + _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); cm.AddSeparator(); - cm.AddButton("Open scripts project", Editor.CodeEditing.OpenSolution); - _menuFileGenerateScriptsProjectFiles = cm.AddButton("Generate scripts project files", Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync); - cm.AddButton("Recompile scripts", ScriptsBuilder.Compile); + _menuFileOpenScriptsProject = cm.AddButton("Open scripts project", inputOptions.OpenScriptsProject, Editor.CodeEditing.OpenSolution); + _menuFileGenerateScriptsProjectFiles = cm.AddButton("Generate scripts project files", inputOptions.GenerateScriptsProject, Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync); + _menuFileRecompileScripts = cm.AddButton("Recompile scripts", inputOptions.RecompileScripts, ScriptsBuilder.Compile); cm.AddSeparator(); cm.AddButton("Open project...", OpenProject); cm.AddSeparator(); @@ -526,27 +541,27 @@ namespace FlaxEditor.Modules MenuEdit = MainMenu.AddButton("Edit"); cm = MenuEdit.ContextMenu; cm.VisibleChanged += OnMenuEditShowHide; - _menuEditUndo = cm.AddButton(string.Empty, inputOptions.Undo.ToString(), Editor.PerformUndo); - _menuEditRedo = cm.AddButton(string.Empty, inputOptions.Redo.ToString(), Editor.PerformRedo); + _menuEditUndo = cm.AddButton(string.Empty, inputOptions.Undo, Editor.PerformUndo); + _menuEditRedo = cm.AddButton(string.Empty, inputOptions.Redo, Editor.PerformRedo); cm.AddSeparator(); - _menuEditCut = cm.AddButton("Cut", inputOptions.Cut.ToString(), Editor.SceneEditing.Cut); - _menuEditCopy = cm.AddButton("Copy", inputOptions.Copy.ToString(), Editor.SceneEditing.Copy); - _menuEditPaste = cm.AddButton("Paste", inputOptions.Paste.ToString(), Editor.SceneEditing.Paste); + _menuEditCut = cm.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut); + _menuEditCopy = cm.AddButton("Copy", inputOptions.Copy, Editor.SceneEditing.Copy); + _menuEditPaste = cm.AddButton("Paste", inputOptions.Paste, Editor.SceneEditing.Paste); cm.AddSeparator(); - _menuEditDelete = cm.AddButton("Delete", inputOptions.Delete.ToString(), Editor.SceneEditing.Delete); - _menuEditDuplicate = cm.AddButton("Duplicate", inputOptions.Duplicate.ToString(), Editor.SceneEditing.Duplicate); + _menuEditDelete = cm.AddButton("Delete", inputOptions.Delete, Editor.SceneEditing.Delete); + _menuEditDuplicate = cm.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate); cm.AddSeparator(); - _menuEditSelectAll = cm.AddButton("Select all", inputOptions.SelectAll.ToString(), Editor.SceneEditing.SelectAllScenes); - _menuEditFind = cm.AddButton("Find", inputOptions.Search.ToString(), Editor.Windows.SceneWin.Search); + _menuEditSelectAll = cm.AddButton("Select all", inputOptions.SelectAll, Editor.SceneEditing.SelectAllScenes); + _menuEditFind = cm.AddButton("Find", inputOptions.Search, Editor.Windows.SceneWin.Search); // Scene MenuScene = MainMenu.AddButton("Scene"); cm = MenuScene.ContextMenu; cm.VisibleChanged += OnMenuSceneShowHide; - _menuSceneMoveActorToViewport = cm.AddButton("Move actor to viewport", MoveActorToViewport); - _menuSceneAlignActorWithViewport = cm.AddButton("Align actor with viewport", AlignActorWithViewport); - _menuSceneAlignViewportWithActor = cm.AddButton("Align viewport with actor", AlignViewportWithActor); - _menuScenePilotActor = cm.AddButton("Pilot actor", PilotActor); + _menuSceneMoveActorToViewport = cm.AddButton("Move actor to viewport", inputOptions.MoveActorToViewport, MoveActorToViewport); + _menuSceneAlignActorWithViewport = cm.AddButton("Align actor with viewport", inputOptions.AlignActorWithViewport, AlignActorWithViewport); + _menuSceneAlignViewportWithActor = cm.AddButton("Align viewport with actor", inputOptions.AlignViewportWithActor, AlignViewportWithActor); + _menuScenePilotActor = cm.AddButton("Pilot actor", inputOptions.PilotActor, PilotActor); cm.AddSeparator(); _menuSceneCreateTerrain = cm.AddButton("Create terrain", CreateTerrain); @@ -555,39 +570,41 @@ namespace FlaxEditor.Modules cm = MenuGame.ContextMenu; cm.VisibleChanged += OnMenuGameShowHide; - _menuGamePlayGame = cm.AddButton("Play Game", Editor.Simulation.RequestPlayGameOrStopPlay); - _menuGamePlayCurrentScenes = cm.AddButton("Play Current Scenes", Editor.Simulation.RequestPlayScenesOrStopPlay); - _menuGameStop = cm.AddButton("Stop Game", Editor.Simulation.RequestStopPlay); - _menuGamePause = cm.AddButton("Pause", inputOptions.Pause.ToString(), Editor.Simulation.RequestPausePlay); + _menuGamePlayGame = cm.AddButton("Play Game", inputOptions.Play, Editor.Simulation.RequestPlayGameOrStopPlay); + _menuGamePlayCurrentScenes = cm.AddButton("Play Current Scenes", inputOptions.PlayCurrentScenes, Editor.Simulation.RequestPlayScenesOrStopPlay); + _menuGameStop = cm.AddButton("Stop Game", inputOptions.Play, Editor.Simulation.RequestStopPlay); + _menuGamePause = cm.AddButton("Pause", inputOptions.Pause, Editor.Simulation.RequestPausePlay); cm.AddSeparator(); var numberOfClientsMenu = cm.AddChildMenu("Number of game clients"); _numberOfClientsGroup.AddItemsToContextMenu(numberOfClientsMenu.ContextMenu); cm.AddSeparator(); - cm.AddButton("Cook & Run", Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip("Runs Game Cooker to build the game for this platform and runs the game after."); - cm.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked).LinkTooltip("Runs the game build from the last cooking output. Use Cook&Play or Game Cooker first."); + _menuGameCookAndRun = cm.AddButton("Cook & Run", inputOptions.CookAndRun, Editor.Windows.GameCookerWin.BuildAndRun); + _menuGameCookAndRun.LinkTooltip("Runs Game Cooker to build the game for this platform and runs the game after."); + _menuGameRunCookedGame = cm.AddButton("Run cooked game", inputOptions.RunCookedGame, Editor.Windows.GameCookerWin.RunCooked); + _menuGameRunCookedGame.LinkTooltip("Runs the game build from the last cooking output. Use 'Cook & Run' or Game Cooker first."); // Tools MenuTools = MainMenu.AddButton("Tools"); cm = MenuTools.ContextMenu; cm.VisibleChanged += OnMenuToolsShowHide; - _menuToolsBuildScenes = cm.AddButton("Build scenes data", "Ctrl+F10", Editor.BuildScenesOrCancel); + _menuToolsBuildScenes = cm.AddButton("Build scenes data", inputOptions.BuildScenesData, Editor.BuildScenesOrCancel); cm.AddSeparator(); - _menuToolsBakeLightmaps = cm.AddButton("Bake lightmaps", Editor.BakeLightmapsOrCancel); - _menuToolsClearLightmaps = cm.AddButton("Clear lightmaps data", Editor.ClearLightmaps); - _menuToolsBakeAllEnvProbes = cm.AddButton("Bake all env probes", BakeAllEnvProbes); - _menuToolsBuildCSGMesh = cm.AddButton("Build CSG mesh", BuildCSG); - _menuToolsBuildNavMesh = cm.AddButton("Build Nav Mesh", BuildNavMesh); - _menuToolsBuildAllMesgesSDF = cm.AddButton("Build all meshes SDF", BuildAllMeshesSDF); + _menuToolsBakeLightmaps = cm.AddButton("Bake lightmaps", inputOptions.BakeLightmaps, Editor.BakeLightmapsOrCancel); + _menuToolsClearLightmaps = cm.AddButton("Clear lightmaps data", inputOptions.ClearLightmaps, Editor.ClearLightmaps); + _menuToolsBakeAllEnvProbes = cm.AddButton("Bake all env probes", inputOptions.BakeEnvProbes, Editor.BakeAllEnvProbes); + _menuToolsBuildCSGMesh = cm.AddButton("Build CSG mesh", inputOptions.BuildCSG, Editor.BuildCSG); + _menuToolsBuildNavMesh = cm.AddButton("Build Nav Mesh", inputOptions.BuildNav, Editor.BuildNavMesh); + _menuToolsBuildAllMeshesSDF = cm.AddButton("Build all meshes SDF", inputOptions.BuildSDF, Editor.BuildAllMeshesSDF); cm.AddSeparator(); cm.AddButton("Game Cooker", Editor.Windows.GameCookerWin.FocusOrShow); _menuToolsCancelBuilding = cm.AddButton("Cancel building game", () => GameCooker.Cancel()); cm.AddSeparator(); - cm.AddButton("Profiler", Editor.Windows.ProfilerWin.FocusOrShow); + _menuToolsProfilerWindow = cm.AddButton("Profiler", inputOptions.ProfilerWindow, () => Editor.Windows.ProfilerWin.FocusOrShow()); cm.AddSeparator(); _menuToolsSetTheCurrentSceneViewAsDefault = cm.AddButton("Set current scene view as project default", SetTheCurrentSceneViewAsDefault); - cm.AddButton("Take screenshot", "F12", Editor.Windows.TakeScreenshot); + _menuToolsTakeScreenshot = cm.AddButton("Take screenshot", inputOptions.TakeScreenshot, Editor.Windows.TakeScreenshot); cm.AddSeparator(); cm.AddButton("Plugins", () => Editor.Windows.PluginsWin.Show()); cm.AddButton("Options", () => Editor.Windows.EditorOptionsWin.Show()); @@ -606,7 +623,7 @@ namespace FlaxEditor.Modules cm.AddButton("Output Log", Editor.Windows.OutputLogWin.FocusOrShow); cm.AddButton("Graphics Quality", Editor.Windows.GraphicsQualityWin.FocusOrShow); cm.AddButton("Game Cooker", Editor.Windows.GameCookerWin.FocusOrShow); - cm.AddButton("Profiler", Editor.Windows.ProfilerWin.FocusOrShow); + cm.AddButton("Profiler", inputOptions.ProfilerWindow, Editor.Windows.ProfilerWin.FocusOrShow); cm.AddButton("Content Search", Editor.ContentFinding.ShowSearch); cm.AddButton("Visual Script Debugger", Editor.Windows.VisualScriptDebuggerWin.FocusOrShow); cm.AddSeparator(); @@ -633,7 +650,12 @@ namespace FlaxEditor.Modules { var inputOptions = options.Input; - _menuSaveAll.ShortKeys = inputOptions.Save.ToString(); + _menuFileSaveAll.ShortKeys = inputOptions.Save.ToString(); + _menuFileSaveScenes.ShortKeys = inputOptions.SaveScenes.ToString(); + _menuFileCloseScenes.ShortKeys = inputOptions.CloseScenes.ToString(); + _menuFileOpenScriptsProject.ShortKeys = inputOptions.OpenScriptsProject.ToString(); + _menuFileGenerateScriptsProjectFiles.ShortKeys = inputOptions.GenerateScriptsProject.ToString(); + _menuFileRecompileScripts.ShortKeys = inputOptions.RecompileScripts.ToString(); _menuEditUndo.ShortKeys = inputOptions.Undo.ToString(); _menuEditRedo.ShortKeys = inputOptions.Redo.ToString(); _menuEditCut.ShortKeys = inputOptions.Cut.ToString(); @@ -642,8 +664,21 @@ namespace FlaxEditor.Modules _menuEditDuplicate.ShortKeys = inputOptions.Duplicate.ToString(); _menuEditSelectAll.ShortKeys = inputOptions.SelectAll.ToString(); _menuEditFind.ShortKeys = inputOptions.Search.ToString(); - _menuGamePlayCurrentScenes.ShortKeys = inputOptions.Play.ToString(); + _menuGamePlayGame.ShortKeys = inputOptions.Play.ToString(); + _menuGamePlayCurrentScenes.ShortKeys = inputOptions.PlayCurrentScenes.ToString(); _menuGamePause.ShortKeys = inputOptions.Pause.ToString(); + _menuGameStop.ShortKeys = inputOptions.Play.ToString(); + _menuGameCookAndRun.ShortKeys = inputOptions.CookAndRun.ToString(); + _menuGameRunCookedGame.ShortKeys = inputOptions.RunCookedGame.ToString(); + _menuToolsBuildScenes.ShortKeys = inputOptions.BuildScenesData.ToString(); + _menuToolsBakeLightmaps.ShortKeys = inputOptions.BakeLightmaps.ToString(); + _menuToolsClearLightmaps.ShortKeys = inputOptions.ClearLightmaps.ToString(); + _menuToolsBakeAllEnvProbes.ShortKeys = inputOptions.BakeEnvProbes.ToString(); + _menuToolsBuildCSGMesh.ShortKeys = inputOptions.BuildCSG.ToString(); + _menuToolsBuildNavMesh.ShortKeys = inputOptions.BuildNav.ToString(); + _menuToolsBuildAllMeshesSDF.ShortKeys = inputOptions.BuildSDF.ToString(); + _menuToolsProfilerWindow.ShortKeys = inputOptions.ProfilerWindow.ToString(); + _menuToolsTakeScreenshot.ShortKeys = inputOptions.TakeScreenshot.ToString(); MainMenuShortcutKeysUpdated?.Invoke(); } @@ -668,10 +703,10 @@ namespace FlaxEditor.Modules ToolStrip.AddSeparator(); // Cook scenes - _toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build64, Editor.BuildScenesOrCancel).LinkTooltip("Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options (Ctrl+F10)"); + _toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build64, Editor.BuildScenesOrCancel).LinkTooltip($"Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options ({inputOptions.BuildScenesData})"); // Cook and run - _toolStripCook = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip("Cook & Run - build game for the current platform and run it locally"); + _toolStripCook = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip($"Cook & Run - build game for the current platform and run it locally ({inputOptions.Play})"); _toolStripCook.ContextMenu = new ContextMenu(); _toolStripCook.ContextMenu.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked); _toolStripCook.ContextMenu.AddSeparator(); @@ -692,7 +727,7 @@ namespace FlaxEditor.Modules playActionGroup.SelectedChanged = SetPlayAction; Editor.Options.OptionsChanged += options => { playActionGroup.Selected = options.Interface.PlayButtonAction; }; - _toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause64, Editor.Simulation.RequestResumeOrPause).LinkTooltip($"Pause/Resume game({inputOptions.Pause})"); + _toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause64, Editor.Simulation.RequestResumeOrPause).LinkTooltip($"Pause/Resume game ({inputOptions.Pause})"); _toolStripStep = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Skip64, Editor.Simulation.RequestPlayOneFrame).LinkTooltip("Step one frame in game"); UpdateToolstrip(); @@ -752,7 +787,7 @@ namespace FlaxEditor.Modules HorizontalAlignment = TextAlignment.Far, AnchorPreset = AnchorPresets.HorizontalStretchMiddle, Parent = progressPanel, - Offsets = new Margin(progressBarRightMargin, progressBarWidth + progressBarLeftMargin + progressBarRightMargin, 0, 0), + Offsets = new Margin(progressBarRightMargin, progressBarWidth + progressBarLeftMargin + progressBarRightMargin, 0, 0) }; UpdateStatusBar(); @@ -872,7 +907,7 @@ namespace FlaxEditor.Modules _menuToolsBakeLightmaps.Text = isBakingLightmaps ? "Cancel baking lightmaps" : "Bake lightmaps"; _menuToolsClearLightmaps.Enabled = canEdit; _menuToolsBakeAllEnvProbes.Enabled = canEdit; - _menuToolsBuildAllMesgesSDF.Enabled = canEdit && !isBakingLightmaps; + _menuToolsBuildAllMeshesSDF.Enabled = canEdit && !isBakingLightmaps; _menuToolsBuildCSGMesh.Enabled = canEdit; _menuToolsBuildNavMesh.Enabled = canEdit; _menuToolsCancelBuilding.Enabled = GameCooker.IsRunning; @@ -911,7 +946,7 @@ namespace FlaxEditor.Modules Editor.Windows.LoadLayout((string)button.Tag); } - private void AlignViewportWithActor() + internal void AlignViewportWithActor() { var selection = Editor.SceneEditing; if (selection.HasSthSelected && selection.Selection[0] is ActorNode node) @@ -922,7 +957,7 @@ namespace FlaxEditor.Modules } } - private void MoveActorToViewport() + internal void MoveActorToViewport() { var selection = Editor.SceneEditing; if (selection.HasSthSelected && selection.Selection[0] is ActorNode node) @@ -936,7 +971,7 @@ namespace FlaxEditor.Modules } } - private void AlignActorWithViewport() + internal void AlignActorWithViewport() { var selection = Editor.SceneEditing; if (selection.HasSthSelected && selection.Selection[0] is ActorNode node) @@ -972,57 +1007,6 @@ namespace FlaxEditor.Modules new Tools.Terrain.CreateTerrainDialog().Show(Editor.Windows.MainWindow); } - private void BakeAllEnvProbes() - { - Editor.Scene.ExecuteOnGraph(node => - { - if (node is EnvironmentProbeNode envProbeNode && envProbeNode.IsActive) - { - ((EnvironmentProbe)envProbeNode.Actor).Bake(); - node.ParentScene.IsEdited = true; - } - else if (node is SkyLightNode skyLightNode && skyLightNode.IsActive && skyLightNode.Actor is SkyLight skyLight && skyLight.Mode == SkyLight.Modes.CaptureScene) - { - skyLight.Bake(); - node.ParentScene.IsEdited = true; - } - - return node.IsActive; - }); - } - - private void BuildCSG() - { - var scenes = Level.Scenes; - scenes.ToList().ForEach(x => x.BuildCSG(0)); - Editor.Scene.MarkSceneEdited(scenes); - } - - private void BuildNavMesh() - { - var scenes = Level.Scenes; - scenes.ToList().ForEach(x => Navigation.BuildNavMesh(x, 0)); - Editor.Scene.MarkSceneEdited(scenes); - } - - private void BuildAllMeshesSDF() - { - // TODO: async maybe with progress reporting? - Editor.Scene.ExecuteOnGraph(node => - { - if (node is StaticModelNode staticModelNode && staticModelNode.Actor is StaticModel staticModel) - { - if (staticModel.DrawModes.HasFlag(DrawPass.GlobalSDF) && staticModel.Model != null && !staticModel.Model.IsVirtual && staticModel.Model.SDF.Texture == null) - { - Editor.Log("Generating SDF for " + staticModel.Model); - if (!staticModel.Model.GenerateSDF()) - staticModel.Model.Save(); - } - } - return true; - }); - } - private void SetTheCurrentSceneViewAsDefault() { var projectInfo = Editor.GameProject; diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs index 61220f7d5..d0c8b9747 100644 --- a/Source/Editor/Modules/WindowsModule.cs +++ b/Source/Editor/Modules/WindowsModule.cs @@ -721,7 +721,7 @@ namespace FlaxEditor.Modules // Create main window var settings = CreateWindowSettings.Default; settings.Title = "Flax Editor"; - settings.Size = Platform.DesktopSize; + settings.Size = Platform.DesktopSize * 0.75f; settings.StartPosition = WindowStartPosition.CenterScreen; settings.ShowAfterFirstPaint = true; diff --git a/Source/Editor/Options/InputBinding.cs b/Source/Editor/Options/InputBinding.cs index 2e18dab9e..eb61c0f68 100644 --- a/Source/Editor/Options/InputBinding.cs +++ b/Source/Editor/Options/InputBinding.cs @@ -134,6 +134,66 @@ namespace FlaxEditor.Options return false; } + private bool ProcessModifiers(Control control) + { + return ProcessModifiers(control.Root.GetKey); + } + + private bool ProcessModifiers(Window window) + { + return ProcessModifiers(window.GetKey); + } + + private bool ProcessModifiers(Func getKeyFunc) + { + bool ctrlPressed = getKeyFunc(KeyboardKeys.Control); + bool shiftPressed = getKeyFunc(KeyboardKeys.Shift); + bool altPressed = getKeyFunc(KeyboardKeys.Alt); + + bool mod1 = false; + if (Modifier1 == KeyboardKeys.None) + mod1 = true; + else if (Modifier1 == KeyboardKeys.Control) + { + mod1 = ctrlPressed; + ctrlPressed = false; + } + else if (Modifier1 == KeyboardKeys.Shift) + { + mod1 = shiftPressed; + shiftPressed = false; + } + else if (Modifier1 == KeyboardKeys.Alt) + { + mod1 = altPressed; + altPressed = false; + } + + bool mod2 = false; + if (Modifier2 == KeyboardKeys.None) + mod2 = true; + else if (Modifier2 == KeyboardKeys.Control) + { + mod2 = ctrlPressed; + ctrlPressed = false; + } + else if (Modifier2 == KeyboardKeys.Shift) + { + mod2 = shiftPressed; + shiftPressed = false; + } + else if (Modifier2 == KeyboardKeys.Alt) + { + mod2 = altPressed; + altPressed = false; + } + + // Check if any unhandled modifiers are not pressed + if (mod1 && mod2) + return !ctrlPressed && !shiftPressed && !altPressed; + return false; + } + /// /// Processes this input binding to check if state matches. /// @@ -142,19 +202,7 @@ namespace FlaxEditor.Options public bool Process(Control control) { var root = control.Root; - - if (root.GetKey(Key)) - { - if (Modifier1 == KeyboardKeys.None || root.GetKey(Modifier1)) - { - if (Modifier2 == KeyboardKeys.None || root.GetKey(Modifier2)) - { - return true; - } - } - } - - return false; + return root.GetKey(Key) && ProcessModifiers(control); } /// @@ -165,20 +213,20 @@ namespace FlaxEditor.Options /// True if input has been processed, otherwise false. public bool Process(Control control, KeyboardKeys key) { - var root = control.Root; + if (key != Key) + return false; - if (key == Key) - { - if (Modifier1 == KeyboardKeys.None || root.GetKey(Modifier1)) - { - if (Modifier2 == KeyboardKeys.None || root.GetKey(Modifier2)) - { - return true; - } - } - } + return ProcessModifiers(control); + } - return false; + /// + /// Processes this input binding to check if state matches. + /// + /// The input providing window. + /// True if input has been processed, otherwise false. + public bool Process(Window window) + { + return window.GetKey(Key) && ProcessModifiers(window); } /// diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index e0a51622b..90d098bb6 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -78,6 +78,30 @@ namespace FlaxEditor.Options #endregion + #region File + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("File"), EditorOrder(300)] + public InputBinding SaveScenes = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("File"), EditorOrder(310)] + public InputBinding CloseScenes = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("File"), EditorOrder(320)] + public InputBinding OpenScriptsProject = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("File"), EditorOrder(330)] + public InputBinding GenerateScriptsProject = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("File"), EditorOrder(340)] + public InputBinding RecompileScripts = new InputBinding(KeyboardKeys.None); + + #endregion + #region Scene [DefaultValue(typeof(InputBinding), "End")] @@ -85,35 +109,115 @@ namespace FlaxEditor.Options public InputBinding SnapToGround = new InputBinding(KeyboardKeys.End); [DefaultValue(typeof(InputBinding), "F5")] - [EditorDisplay("Scene"), EditorOrder(510)] + [EditorDisplay("Scene", "Play/Stop"), EditorOrder(510)] public InputBinding Play = new InputBinding(KeyboardKeys.F5); + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Scene", "Play Current Scenes/Stop"), EditorOrder(520)] + public InputBinding PlayCurrentScenes = new InputBinding(KeyboardKeys.None); + [DefaultValue(typeof(InputBinding), "F6")] - [EditorDisplay("Scene"), EditorOrder(520)] + [EditorDisplay("Scene"), EditorOrder(530)] public InputBinding Pause = new InputBinding(KeyboardKeys.F6); [DefaultValue(typeof(InputBinding), "F11")] - [EditorDisplay("Scene"), EditorOrder(530)] + [EditorDisplay("Scene"), EditorOrder(540)] public InputBinding StepFrame = new InputBinding(KeyboardKeys.F11); + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Scene", "Cook & Run"), EditorOrder(550)] + public InputBinding CookAndRun = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Scene", "Run cooked game"), EditorOrder(560)] + public InputBinding RunCookedGame = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Scene", "Move actor to viewport"), EditorOrder(570)] + public InputBinding MoveActorToViewport = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Scene", "Align actor with viewport"), EditorOrder(571)] + public InputBinding AlignActorWithViewport = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Scene", "Align viewport with actor"), EditorOrder(572)] + public InputBinding AlignViewportWithActor = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Scene"), EditorOrder(573)] + public InputBinding PilotActor = new InputBinding(KeyboardKeys.None); + + #endregion + + #region Tools + + [DefaultValue(typeof(InputBinding), "Ctrl+F10")] + [EditorDisplay("Tools", "Build scenes data"), EditorOrder(600)] + public InputBinding BuildScenesData = new InputBinding(KeyboardKeys.F10, KeyboardKeys.Control); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Tools", "Bake lightmaps"), EditorOrder(601)] + public InputBinding BakeLightmaps = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Tools", "Clear lightmaps data"), EditorOrder(602)] + public InputBinding ClearLightmaps = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Tools", "Bake all env probes"), EditorOrder(603)] + public InputBinding BakeEnvProbes = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Tools", "Build CSG mesh"), EditorOrder(604)] + public InputBinding BuildCSG = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Tools", "Build Nav Mesh"), EditorOrder(605)] + public InputBinding BuildNav = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Tools", "Build all meshes SDF"), EditorOrder(606)] + public InputBinding BuildSDF = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "F12")] + [EditorDisplay("Tools", "Take screenshot"), EditorOrder(607)] + public InputBinding TakeScreenshot = new InputBinding(KeyboardKeys.F12); + + #endregion + + #region Profiler + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Profiler", "Open Profiler Window"), EditorOrder(630)] + public InputBinding ProfilerWindow = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Profiler", "Start/Stop Profiler"), EditorOrder(631)] + public InputBinding ProfilerStartStop = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Profiler", "Clear Profiler data"), EditorOrder(632)] + public InputBinding ProfilerClear = new InputBinding(KeyboardKeys.None); + #endregion #region Debugger [DefaultValue(typeof(InputBinding), "F5")] - [EditorDisplay("Debugger", "Continue"), EditorOrder(610)] + [EditorDisplay("Debugger", "Continue"), EditorOrder(810)] public InputBinding DebuggerContinue = new InputBinding(KeyboardKeys.F5); [DefaultValue(typeof(InputBinding), "F10")] - [EditorDisplay("Debugger", "Step Over"), EditorOrder(620)] + [EditorDisplay("Debugger", "Step Over"), EditorOrder(820)] public InputBinding DebuggerStepOver = new InputBinding(KeyboardKeys.F10); [DefaultValue(typeof(InputBinding), "F11")] - [EditorDisplay("Debugger", "Step Into"), EditorOrder(630)] + [EditorDisplay("Debugger", "Step Into"), EditorOrder(830)] public InputBinding DebuggerStepInto = new InputBinding(KeyboardKeys.F11); [DefaultValue(typeof(InputBinding), "Shift+F11")] - [EditorDisplay("Debugger", "Step Out"), EditorOrder(640)] + [EditorDisplay("Debugger", "Step Out"), EditorOrder(840)] public InputBinding DebuggerStepOut = new InputBinding(KeyboardKeys.F11, KeyboardKeys.Shift); #endregion @@ -132,6 +236,10 @@ namespace FlaxEditor.Options [EditorDisplay("Gizmo"), EditorOrder(1020)] public InputBinding ScaleMode = new InputBinding(KeyboardKeys.Alpha3); + [DefaultValue(typeof(InputBinding), "Alpha4")] + [EditorDisplay("Gizmo"), EditorOrder(1030)] + public InputBinding ToggleTransformSpace = new InputBinding(KeyboardKeys.Alpha4); + #endregion #region Viewport @@ -160,28 +268,40 @@ namespace FlaxEditor.Options [EditorDisplay("Viewport"), EditorOrder(1550)] public InputBinding Down = new InputBinding(KeyboardKeys.Q); + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Viewport", "Toggle Camera Rotation"), EditorOrder(1560)] + public InputBinding CameraToggleRotation = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Viewport", "Increase Camera Move Speed"), EditorOrder(1570)] + public InputBinding CameraIncreaseMoveSpeed = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Viewport", "Decrease Camera Move Speed"), EditorOrder(1571)] + public InputBinding CameraDecreaseMoveSpeed = new InputBinding(KeyboardKeys.None); + [DefaultValue(typeof(InputBinding), "Numpad0")] - [EditorDisplay("Viewport"), EditorOrder(1600)] + [EditorDisplay("Viewport"), EditorOrder(1700)] public InputBinding ViewpointFront = new InputBinding(KeyboardKeys.Numpad0); [DefaultValue(typeof(InputBinding), "Numpad5")] - [EditorDisplay("Viewport"), EditorOrder(1610)] + [EditorDisplay("Viewport"), EditorOrder(1710)] public InputBinding ViewpointBack = new InputBinding(KeyboardKeys.Numpad5); [DefaultValue(typeof(InputBinding), "Numpad4")] - [EditorDisplay("Viewport"), EditorOrder(1620)] + [EditorDisplay("Viewport"), EditorOrder(1720)] public InputBinding ViewpointLeft = new InputBinding(KeyboardKeys.Numpad4); [DefaultValue(typeof(InputBinding), "Numpad6")] - [EditorDisplay("Viewport"), EditorOrder(1630)] + [EditorDisplay("Viewport"), EditorOrder(1730)] public InputBinding ViewpointRight = new InputBinding(KeyboardKeys.Numpad6); [DefaultValue(typeof(InputBinding), "Numpad8")] - [EditorDisplay("Viewport"), EditorOrder(1640)] + [EditorDisplay("Viewport"), EditorOrder(1740)] public InputBinding ViewpointTop = new InputBinding(KeyboardKeys.Numpad8); [DefaultValue(typeof(InputBinding), "Numpad2")] - [EditorDisplay("Viewport"), EditorOrder(1650)] + [EditorDisplay("Viewport"), EditorOrder(1750)] public InputBinding ViewpointBottom = new InputBinding(KeyboardKeys.Numpad2); #endregion diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs index 68aa11626..c2d744239 100644 --- a/Source/Editor/Options/OptionsModule.cs +++ b/Source/Editor/Options/OptionsModule.cs @@ -244,6 +244,13 @@ namespace FlaxEditor.Options CollectionBackgroundColor = Color.FromBgra(0x14CCCCCC), ProgressNormal = Color.FromBgra(0xFF0ad328), + Statusbar = new Style.StatusbarStyle() + { + PlayMode = Color.FromBgra(0xFF2F9135), + Failed = Color.FromBgra(0xFF9C2424), + Loading = Color.FromBgra(0xFF2D2D30) + }, + // Fonts FontTitle = options.Interface.TitleFont.GetFont(), FontLarge = options.Interface.LargeFont.GetFont(), diff --git a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp index e28d1e381..7f5ca6f17 100644 --- a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp +++ b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp @@ -24,7 +24,8 @@ namespace String version; RiderInstallation(const String& path_, const String& version_) - : path(path_), version(version_) + : path(path_) + , version(version_) { } }; @@ -44,6 +45,10 @@ namespace if (document.HasParseError()) return; + // Check if this is actually rider and not another jetbrains product + if (document.FindMember("name")->value != "JetBrains Rider") + return; + // Find version auto versionMember = document.FindMember("version"); if (versionMember == document.MemberEnd()) @@ -141,14 +146,14 @@ bool sortInstallations(RiderInstallation* const& i1, RiderInstallation* const& i int32 version2[3] = { 0 }; StringUtils::Parse(values1[0].Get(), &version1[0]); StringUtils::Parse(values1[1].Get(), &version1[1]); - - if(values1.Count() > 2) + + if (values1.Count() > 2) StringUtils::Parse(values1[2].Get(), &version1[2]); - + StringUtils::Parse(values2[0].Get(), &version2[0]); StringUtils::Parse(values2[1].Get(), &version2[1]); - - if(values2.Count() > 2) + + if (values2.Count() > 2) StringUtils::Parse(values2[2].Get(), &version2[2]); // Compare by MAJOR.MINOR.BUILD @@ -174,7 +179,7 @@ void RiderCodeEditor::FindEditors(Array* output) String localAppDataPath; FileSystem::GetSpecialFolderPath(SpecialFolder::LocalAppData, localAppDataPath); - + #if PLATFORM_WINDOWS // Lookup from all known registry locations SearchRegistry(&installations, HKEY_CURRENT_USER, TEXT("SOFTWARE\\WOW6432Node\\JetBrains\\Rider for Unreal Engine")); @@ -187,6 +192,7 @@ void RiderCodeEditor::FindEditors(Array* output) SearchRegistry(&installations, HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\WOW6432Node\\JetBrains\\JetBrains Rider")); // Versions installed via JetBrains Toolbox + FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT("Programs")); FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT("JetBrains\\Toolbox\\apps\\Rider\\ch-0\\")); FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT("JetBrains\\Toolbox\\apps\\Rider\\ch-1\\")); // Beta versions #endif @@ -201,6 +207,7 @@ 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 @@ -210,7 +217,24 @@ void RiderCodeEditor::FindEditors(Array* output) TEXT("flatpak run com.jetbrains.Rider")); #endif - for (auto directory : subDirectories) +#if PLATFORM_MAC + String applicationSupportFolder; + FileSystem::GetSpecialFolderPath(SpecialFolder::ProgramData, applicationSupportFolder); + + Array subMacDirectories; + FileSystem::GetChildDirectories(subMacDirectories, applicationSupportFolder / TEXT("JetBrains/Toolbox/apps/Rider/ch-0/")); + FileSystem::GetChildDirectories(subMacDirectories, applicationSupportFolder / TEXT("JetBrains/Toolbox/apps/Rider/ch-1/")); + for (const String& directory : subMacDirectories) + { + String riderAppDirectory = directory / TEXT("Rider.app/Contents/Resources"); + SearchDirectory(&installations, riderAppDirectory); + } + + // Check the local installer version + SearchDirectory(&installations, TEXT("/Applications/Rider.app/Contents/Resources")); +#endif + + for (const String& directory : subDirectories) SearchDirectory(&installations, directory); // Sort found installations by version number @@ -244,8 +268,16 @@ void RiderCodeEditor::OpenFile(const String& path, int32 line) // Open file line = line > 0 ? line : 1; CreateProcessSettings procSettings; + +#if !PLATFORM_MAC procSettings.FileName = _execPath; procSettings.Arguments = String::Format(TEXT("\"{0}\" --line {2} \"{1}\""), _solutionPath, path, line); +#else + // This follows pretty much how all the other engines open rider which deals with cross architecture issues + procSettings.FileName = "/usr/bin/open"; + procSettings.Arguments = String::Format(TEXT("-n -a \"{0}\" --args \"{1}\" --line {3} \"{2}\""), _execPath, _solutionPath, path, line); +#endif + procSettings.HiddenWindow = false; procSettings.WaitForEnd = false; procSettings.LogOutput = false; @@ -263,8 +295,14 @@ void RiderCodeEditor::OpenSolution() // Open solution CreateProcessSettings procSettings; +#if !PLATFORM_MAC procSettings.FileName = _execPath; procSettings.Arguments = String::Format(TEXT("\"{0}\""), _solutionPath); +#else + // This follows pretty much how all the other engines open rider which deals with cross architecture issues + procSettings.FileName = "/usr/bin/open"; + procSettings.Arguments = String::Format(TEXT("-n -a \"{0}\" \"{1}\""), _execPath, _solutionPath); +#endif procSettings.HiddenWindow = false; procSettings.WaitForEnd = false; procSettings.LogOutput = false; diff --git a/Source/Editor/Scripting/ScriptsBuilder.cpp b/Source/Editor/Scripting/ScriptsBuilder.cpp index 3082a531e..8807379cf 100644 --- a/Source/Editor/Scripting/ScriptsBuilder.cpp +++ b/Source/Editor/Scripting/ScriptsBuilder.cpp @@ -246,20 +246,17 @@ bool ScriptsBuilder::RunBuildTool(const StringView& args, const StringView& work Log::FileNotFoundException(monoPath).SetLevel(LogType::Fatal); return true; } - //const String monoPath = TEXT("mono"); - cmdLine.Append(TEXT("\"")); + const String monoPath = TEXT("mono"); cmdLine.Append(monoPath); - cmdLine.Append(TEXT("\" ")); + cmdLine.Append(TEXT(" ")); // TODO: Set env var for the mono MONO_GC_PARAMS=nursery-size64m to boost build performance -> profile it #endif - cmdLine.Append(TEXT("\"")); cmdLine.Append(buildToolPath); - cmdLine.Append(TEXT("\" ")); - cmdLine.Append(args.Get(), args.Length()); // Call build tool CreateProcessSettings procSettings; procSettings.FileName = StringView(*cmdLine, cmdLine.Length()); + procSettings.Arguments = args.Get(); procSettings.WorkingDirectory = workingDir; const int32 result = Platform::CreateProcess(procSettings); if (result != 0) diff --git a/Source/Editor/States/LoadingState.cs b/Source/Editor/States/LoadingState.cs index 746c8d92b..698dc192f 100644 --- a/Source/Editor/States/LoadingState.cs +++ b/Source/Editor/States/LoadingState.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.IO; using FlaxEngine; using FlaxEngine.Utilities; @@ -54,6 +55,13 @@ 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 customArgs = Editor.Instance.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/Surface/Archetypes/Math.cs b/Source/Editor/Surface/Archetypes/Math.cs index 1b8f62e62..fe2f1e044 100644 --- a/Source/Editor/Surface/Archetypes/Math.cs +++ b/Source/Editor/Surface/Archetypes/Math.cs @@ -126,7 +126,7 @@ namespace FlaxEditor.Surface.Archetypes Title = "Clamp", Description = "Clamps value to the specified range", Flags = NodeFlags.AllGraphs, - Size = new Float2(110, 60), + Size = new Float2(140, 60), ConnectionsHints = ConnectionsHint.Numeric, IndependentBoxes = new[] { 0 }, DependentBoxes = new[] { 1, 2, 3 }, diff --git a/Source/Editor/Surface/Elements/OutputBox.cs b/Source/Editor/Surface/Elements/OutputBox.cs index 67b6e8409..cf78b559c 100644 --- a/Source/Editor/Surface/Elements/OutputBox.cs +++ b/Source/Editor/Surface/Elements/OutputBox.cs @@ -34,10 +34,8 @@ namespace FlaxEditor.Surface.Elements public static void DrawConnection(ref Float2 start, ref Float2 end, ref Color color, float thickness = 1) { // Calculate control points - var dst = (end - start) * new Float2(0.5f, 0.05f); - var control1 = new Float2(start.X + dst.X, start.Y + dst.Y); - var control2 = new Float2(end.X - dst.X, end.Y + dst.Y); - + CalculateBezierControlPoints(start, end, out var control1, out var control2); + // Draw line Render2D.DrawBezier(start, control1, control2, end, color, thickness); @@ -49,6 +47,23 @@ namespace FlaxEditor.Surface.Elements */ } + private static void CalculateBezierControlPoints(Float2 start, Float2 end, out Float2 control1, out Float2 control2) + { + // Control points parameters + const float minControlLength = 100f; + const float maxControlLength = 150f; + var dst = (end - start).Length; + var yDst = Mathf.Abs(start.Y - end.Y); + + // Calculate control points + var minControlDst = dst * 0.5f; + var maxControlDst = Mathf.Max(Mathf.Min(maxControlLength, dst), minControlLength); + var controlDst = Mathf.Lerp(minControlDst, maxControlDst, Mathf.Clamp(yDst / minControlLength, 0f, 1f)); + + control1 = new Float2(start.X + controlDst, start.Y); + control2 = new Float2(end.X - controlDst, end.Y); + } + /// /// Checks if a point intersects a connection /// @@ -75,13 +90,9 @@ namespace FlaxEditor.Surface.Elements float offset = Mathf.Sign(end.Y - start.Y) * distance; if ((point.Y - (start.Y - offset)) * ((end.Y + offset) - point.Y) < 0) return false; - - // Taken from the Render2D.DrawBezier code + float squaredDistance = distance; - - var dst = (end - start) * new Float2(0.5f, 0.05f); - var control1 = new Float2(start.X + dst.X, start.Y + dst.Y); - var control2 = new Float2(end.X - dst.X, end.Y + dst.Y); + CalculateBezierControlPoints(start, end, out var control1, out var control2); var d1 = control1 - start; var d2 = control2 - control1; diff --git a/Source/Editor/Surface/VisjectSurface.Input.cs b/Source/Editor/Surface/VisjectSurface.Input.cs index be64019b6..0b664768d 100644 --- a/Source/Editor/Surface/VisjectSurface.Input.cs +++ b/Source/Editor/Surface/VisjectSurface.Input.cs @@ -238,6 +238,23 @@ namespace FlaxEditor.Surface return; } + if (_middleMouseDown) + { + // Calculate delta + var delta = location - _middleMouseDownPos; + if (delta.LengthSquared > 0.01f) + { + // Move view + _mouseMoveAmount += delta.Length; + _rootControl.Location += delta; + _middleMouseDownPos = location; + Cursor = CursorType.SizeAll; + } + + // Handled + return; + } + // Check if user is selecting or moving node(s) if (_leftMouseDown) { @@ -316,6 +333,11 @@ namespace FlaxEditor.Surface _rightMouseDown = false; Cursor = CursorType.Default; } + if (_middleMouseDown) + { + _middleMouseDown = false; + Cursor = CursorType.Default; + } _isMovingSelection = false; ConnectingEnd(null); @@ -338,7 +360,7 @@ namespace FlaxEditor.Surface if (IsMouseOver && !_leftMouseDown && !IsPrimaryMenuOpened) { var nextViewScale = ViewScale + delta * 0.1f; - + if (delta > 0 && !_rightMouseDown) { // Scale towards mouse when zooming in @@ -353,7 +375,7 @@ namespace FlaxEditor.Surface ViewScale = nextViewScale; ViewCenterPosition = viewCenter; } - + return true; } @@ -427,6 +449,7 @@ namespace FlaxEditor.Surface _isMovingSelection = false; _rightMouseDown = false; _leftMouseDown = false; + _middleMouseDown = false; return true; } @@ -446,6 +469,11 @@ namespace FlaxEditor.Surface _rightMouseDown = true; _rightMouseDownPos = location; } + if (button == MouseButton.Middle) + { + _middleMouseDown = true; + _middleMouseDownPos = location; + } // Check if any node is under the mouse SurfaceControl controlUnderMouse = GetControlUnderMouse(); @@ -491,7 +519,7 @@ namespace FlaxEditor.Surface Focus(); return true; } - if (_rightMouseDown) + if (_rightMouseDown || _middleMouseDown) { // Start navigating StartMouseCapture(); @@ -560,6 +588,13 @@ namespace FlaxEditor.Surface } _mouseMoveAmount = 0; } + if (_middleMouseDown && button == MouseButton.Middle) + { + _middleMouseDown = false; + EndMouseCapture(); + Cursor = CursorType.Default; + _mouseMoveAmount = 0; + } // Base bool handled = base.OnMouseUp(location, button); @@ -570,6 +605,7 @@ namespace FlaxEditor.Surface // Clear flags _rightMouseDown = false; _leftMouseDown = false; + _middleMouseDown = false; return true; } @@ -753,6 +789,8 @@ namespace FlaxEditor.Surface { if (_inputBrackets.Count == 0) { + if (currentInputText.StartsWith(' ')) + currentInputText = ""; ResetInput(); ShowPrimaryMenu(_mousePos, false, currentInputText); } diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs index 50c112418..9a125f004 100644 --- a/Source/Editor/Surface/VisjectSurface.cs +++ b/Source/Editor/Surface/VisjectSurface.cs @@ -69,6 +69,11 @@ namespace FlaxEditor.Surface /// protected bool _rightMouseDown; + /// + /// The middle mouse down flag. + /// + protected bool _middleMouseDown; + /// /// The left mouse down position. /// @@ -79,6 +84,11 @@ namespace FlaxEditor.Surface /// protected Float2 _rightMouseDownPos = Float2.Minimum; + /// + /// The middle mouse down position. + /// + protected Float2 _middleMouseDownPos = Float2.Minimum; + /// /// The mouse position. /// @@ -912,7 +922,7 @@ namespace FlaxEditor.Surface { return _context.FindNode(id); } - + /// /// Adds the undo action to be batched (eg. if multiple undo actions is performed in a sequence during single update). /// diff --git a/Source/Editor/Tools/Foliage/FoliageTab.cs b/Source/Editor/Tools/Foliage/FoliageTab.cs index 814376c75..10358a8c0 100644 --- a/Source/Editor/Tools/Foliage/FoliageTab.cs +++ b/Source/Editor/Tools/Foliage/FoliageTab.cs @@ -137,14 +137,23 @@ namespace FlaxEditor.Tools.Foliage Offsets = Margin.Zero, Parent = _noFoliagePanel }; + + var buttonText = "Create new foliage"; _createNewFoliage = new Button { - Text = "Create new foliage", + Text = buttonText, AnchorPreset = AnchorPresets.MiddleCenter, Offsets = new Margin(-60, 120, -12, 24), Parent = _noFoliagePanel, Enabled = false }; + var textSize = Style.Current.FontMedium.MeasureText(buttonText); + if (_createNewFoliage.Width < textSize.X) + { + _createNewFoliage.LocalX -= (textSize.X - _createNewFoliage.Width) / 2; + _createNewFoliage.Width = textSize.X + 6; + } + _createNewFoliage.Clicked += OnCreateNewFoliageClicked; } diff --git a/Source/Editor/Tools/Foliage/FoliageTypesTab.cs b/Source/Editor/Tools/Foliage/FoliageTypesTab.cs index 2a51d972c..8934f5058 100644 --- a/Source/Editor/Tools/Foliage/FoliageTypesTab.cs +++ b/Source/Editor/Tools/Foliage/FoliageTypesTab.cs @@ -375,7 +375,7 @@ namespace FlaxEditor.Tools.Foliage private void OnModified() { - Editor.Instance.Scene.MarkSceneEdited(_proxy.Foliage?.Scene); + Editor.Instance.Scene.MarkSceneEdited(_proxy.Foliage != null ? _proxy.Foliage.Scene : null); } private void OnSelectedFoliageChanged() diff --git a/Source/Editor/Tools/Foliage/PaintTab.cs b/Source/Editor/Tools/Foliage/PaintTab.cs index e4f01661b..ba31ca18b 100644 --- a/Source/Editor/Tools/Foliage/PaintTab.cs +++ b/Source/Editor/Tools/Foliage/PaintTab.cs @@ -218,7 +218,7 @@ namespace FlaxEditor.Tools.Foliage private void OnModified() { - Editor.Instance.Scene.MarkSceneEdited(_proxy.Foliage?.Scene); + Editor.Instance.Scene.MarkSceneEdited(_proxy.Foliage != null ? _proxy.Foliage.Scene : null); } private void OnSelectedFoliageChanged() diff --git a/Source/Editor/Tools/Terrain/CarveTab.cs b/Source/Editor/Tools/Terrain/CarveTab.cs index 6bee0bd92..000f90817 100644 --- a/Source/Editor/Tools/Terrain/CarveTab.cs +++ b/Source/Editor/Tools/Terrain/CarveTab.cs @@ -95,14 +95,23 @@ namespace FlaxEditor.Tools.Terrain Offsets = Margin.Zero, Parent = _noTerrainPanel }; + + var buttonText = "Create new terrain"; _createTerrainButton = new Button { - Text = "Create new terrain", + Text = buttonText, AnchorPreset = AnchorPresets.MiddleCenter, Offsets = new Margin(-60, 120, -12, 24), Parent = _noTerrainPanel, Enabled = false }; + var textSize = Style.Current.FontMedium.MeasureText(buttonText); + if (_createTerrainButton.Width < textSize.X) + { + _createTerrainButton.LocalX -= (textSize.X - _createTerrainButton.Width) / 2; + _createTerrainButton.Width = textSize.X + 6; + } + _createTerrainButton.Clicked += OnCreateNewTerrainClicked; } diff --git a/Source/Editor/Tools/VertexPainting.cs b/Source/Editor/Tools/VertexPainting.cs index 0863569c2..6934a754b 100644 --- a/Source/Editor/Tools/VertexPainting.cs +++ b/Source/Editor/Tools/VertexPainting.cs @@ -544,7 +544,7 @@ namespace FlaxEditor.Tools public override bool IsControllingMouse => IsPainting; /// - public override BoundingSphere FocusBounds => _selectedModel?.Sphere ?? base.FocusBounds; + public override BoundingSphere FocusBounds => _selectedModel != null ? _selectedModel.Sphere : base.FocusBounds; /// public override void Update(float dt) diff --git a/Source/Editor/Undo/Actions/PasteActorsAction.cs b/Source/Editor/Undo/Actions/PasteActorsAction.cs index 8b0d040d8..7aee40878 100644 --- a/Source/Editor/Undo/Actions/PasteActorsAction.cs +++ b/Source/Editor/Undo/Actions/PasteActorsAction.cs @@ -144,7 +144,7 @@ namespace FlaxEditor.Actions { var node = nodeParents[i]; var actor = node.Actor; - var parent = actor?.Parent; + var parent = actor != null ? actor.Parent : null; if (parent != null) { bool IsNameValid(string name) diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index fe801bbaf..863bdbedb 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -18,10 +18,10 @@ using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Input; using FlaxEditor.GUI.Tree; using FlaxEditor.SceneGraph; -using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Utilities; +using FlaxEditor.Windows; namespace FlaxEngine { @@ -1235,5 +1235,60 @@ namespace FlaxEditor.Utilities } return s; } + + /// + /// Binds global input actions for the window. + /// + /// The editor window. + public static void SetupCommonInputActions(EditorWindow window) + { + var inputActions = window.InputActions; + + // Setup input actions + inputActions.Add(options => options.Save, Editor.Instance.SaveAll); + inputActions.Add(options => options.Undo, () => + { + Editor.Instance.PerformUndo(); + window.Focus(); + }); + inputActions.Add(options => options.Redo, () => + { + Editor.Instance.PerformRedo(); + window.Focus(); + }); + inputActions.Add(options => options.Cut, Editor.Instance.SceneEditing.Cut); + inputActions.Add(options => options.Copy, Editor.Instance.SceneEditing.Copy); + inputActions.Add(options => options.Paste, Editor.Instance.SceneEditing.Paste); + inputActions.Add(options => options.Duplicate, Editor.Instance.SceneEditing.Duplicate); + inputActions.Add(options => options.SelectAll, Editor.Instance.SceneEditing.SelectAllScenes); + inputActions.Add(options => options.Delete, Editor.Instance.SceneEditing.Delete); + inputActions.Add(options => options.Search, () => Editor.Instance.Windows.SceneWin.Search()); + inputActions.Add(options => options.MoveActorToViewport, Editor.Instance.UI.MoveActorToViewport); + inputActions.Add(options => options.AlignActorWithViewport, Editor.Instance.UI.AlignActorWithViewport); + inputActions.Add(options => options.AlignViewportWithActor, Editor.Instance.UI.AlignViewportWithActor); + inputActions.Add(options => options.PilotActor, Editor.Instance.UI.PilotActor); + inputActions.Add(options => options.Play, Editor.Instance.Simulation.DelegatePlayOrStopPlayInEditor); + inputActions.Add(options => options.PlayCurrentScenes, Editor.Instance.Simulation.RequestPlayScenesOrStopPlay); + inputActions.Add(options => options.Pause, Editor.Instance.Simulation.RequestResumeOrPause); + inputActions.Add(options => options.StepFrame, Editor.Instance.Simulation.RequestPlayOneFrame); + inputActions.Add(options => options.CookAndRun, () => Editor.Instance.Windows.GameCookerWin.BuildAndRun()); + inputActions.Add(options => options.RunCookedGame, () => Editor.Instance.Windows.GameCookerWin.RunCooked()); + inputActions.Add(options => options.BuildScenesData, Editor.Instance.BuildScenesOrCancel); + inputActions.Add(options => options.BakeLightmaps, Editor.Instance.BakeLightmapsOrCancel); + inputActions.Add(options => options.ClearLightmaps, Editor.Instance.ClearLightmaps); + inputActions.Add(options => options.BakeEnvProbes, Editor.Instance.BakeAllEnvProbes); + inputActions.Add(options => options.BuildCSG, Editor.Instance.BuildCSG); + inputActions.Add(options => options.BuildNav, Editor.Instance.BuildNavMesh); + inputActions.Add(options => options.BuildSDF, Editor.Instance.BuildAllMeshesSDF); + inputActions.Add(options => options.TakeScreenshot, Editor.Instance.Windows.TakeScreenshot); + inputActions.Add(options => options.ProfilerWindow, () => Editor.Instance.Windows.ProfilerWin.FocusOrShow()); + inputActions.Add(options => options.ProfilerStartStop, () => { Editor.Instance.Windows.ProfilerWin.LiveRecording = !Editor.Instance.Windows.ProfilerWin.LiveRecording; Editor.Instance.UI.AddStatusMessage($"Profiling {(Editor.Instance.Windows.ProfilerWin.LiveRecording ? "started" : "stopped")}."); }); + inputActions.Add(options => options.ProfilerClear, () => { Editor.Instance.Windows.ProfilerWin.Clear(); Editor.Instance.UI.AddStatusMessage($"Profiling results cleared."); }); + inputActions.Add(options => options.SaveScenes, () => Editor.Instance.Scene.SaveScenes()); + inputActions.Add(options => options.CloseScenes, () => Editor.Instance.Scene.CloseAllScenes()); + inputActions.Add(options => options.OpenScriptsProject, () => Editor.Instance.CodeEditing.OpenSolution()); + inputActions.Add(options => options.GenerateScriptsProject, () => Editor.Instance.ProgressReporting.GenerateScriptsProjectFiles.RunAsync()); + inputActions.Add(options => options.RecompileScripts, ScriptsBuilder.Compile); + } } } diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index ad7e06ba5..8c71a8b02 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -137,7 +137,7 @@ namespace FlaxEditor.Viewport // Input - private bool _isControllingMouse, _isViewportControllingMouse; + private bool _isControllingMouse, _isViewportControllingMouse, _wasVirtualMouseRightDown, _isVirtualMouseRightDown; private int _deltaFilteringStep; private Float2 _startPos; private Float2 _mouseDeltaLast; @@ -441,6 +441,9 @@ namespace FlaxEditor.Viewport if (useWidgets) { + var largestText = "Invert 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(); @@ -541,7 +544,7 @@ namespace FlaxEditor.Viewport { var ortho = ViewWidgetButtonMenu.AddButton("Orthographic"); ortho.CloseMenuOnClick = false; - var orthoValue = new CheckBox(90, 2, _isOrtho) + var orthoValue = new CheckBox(xLocationForExtras, 2, _isOrtho) { Parent = ortho }; @@ -581,7 +584,7 @@ namespace FlaxEditor.Viewport { var fov = ViewWidgetButtonMenu.AddButton("Field Of View"); fov.CloseMenuOnClick = false; - var fovValue = new FloatValueBox(1, 90, 2, 70.0f, 35.0f, 160.0f, 0.1f) + var fovValue = new FloatValueBox(1, xLocationForExtras, 2, 70.0f, 35.0f, 160.0f, 0.1f) { Parent = fov }; @@ -598,7 +601,7 @@ namespace FlaxEditor.Viewport { var orthoSize = ViewWidgetButtonMenu.AddButton("Ortho Scale"); orthoSize.CloseMenuOnClick = false; - var orthoSizeValue = new FloatValueBox(_orthoSize, 90, 2, 70.0f, 0.001f, 100000.0f, 0.01f) + var orthoSizeValue = new FloatValueBox(_orthoSize, xLocationForExtras, 2, 70.0f, 0.001f, 100000.0f, 0.01f) { Parent = orthoSize }; @@ -615,7 +618,7 @@ namespace FlaxEditor.Viewport { var nearPlane = ViewWidgetButtonMenu.AddButton("Near Plane"); nearPlane.CloseMenuOnClick = false; - var nearPlaneValue = new FloatValueBox(2.0f, 90, 2, 70.0f, 0.001f, 1000.0f) + var nearPlaneValue = new FloatValueBox(2.0f, xLocationForExtras, 2, 70.0f, 0.001f, 1000.0f) { Parent = nearPlane }; @@ -627,7 +630,7 @@ namespace FlaxEditor.Viewport { var farPlane = ViewWidgetButtonMenu.AddButton("Far Plane"); farPlane.CloseMenuOnClick = false; - var farPlaneValue = new FloatValueBox(1000, 90, 2, 70.0f, 10.0f) + var farPlaneValue = new FloatValueBox(1000, xLocationForExtras, 2, 70.0f, 10.0f) { Parent = farPlane }; @@ -639,7 +642,7 @@ namespace FlaxEditor.Viewport { var brightness = ViewWidgetButtonMenu.AddButton("Brightness"); brightness.CloseMenuOnClick = false; - var brightnessValue = new FloatValueBox(1.0f, 90, 2, 70.0f, 0.001f, 10.0f, 0.001f) + var brightnessValue = new FloatValueBox(1.0f, xLocationForExtras, 2, 70.0f, 0.001f, 10.0f, 0.001f) { Parent = brightness }; @@ -651,7 +654,7 @@ namespace FlaxEditor.Viewport { var resolution = ViewWidgetButtonMenu.AddButton("Resolution"); resolution.CloseMenuOnClick = false; - var resolutionValue = new FloatValueBox(1.0f, 90, 2, 70.0f, 0.1f, 4.0f, 0.001f) + var resolutionValue = new FloatValueBox(1.0f, xLocationForExtras, 2, 70.0f, 0.1f, 4.0f, 0.001f) { Parent = resolution }; @@ -663,7 +666,7 @@ namespace FlaxEditor.Viewport { var invert = ViewWidgetButtonMenu.AddButton("Invert Panning"); invert.CloseMenuOnClick = false; - var invertValue = new CheckBox(90, 2, _invertPanning) + var invertValue = new CheckBox(xLocationForExtras, 2, _invertPanning) { Parent = invert }; @@ -685,6 +688,9 @@ namespace FlaxEditor.Viewport InputActions.Add(options => options.ViewpointBack, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Back").Orientation))); InputActions.Add(options => options.ViewpointRight, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Right").Orientation))); InputActions.Add(options => options.ViewpointLeft, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Left").Orientation))); + InputActions.Add(options => options.CameraToggleRotation, () => _isVirtualMouseRightDown = !_isVirtualMouseRightDown); + InputActions.Add(options => options.CameraIncreaseMoveSpeed, () => AdjustCameraMoveSpeed(1)); + InputActions.Add(options => options.CameraDecreaseMoveSpeed, () => AdjustCameraMoveSpeed(-1)); // Link for task event task.Begin += OnRenderBegin; @@ -722,6 +728,30 @@ namespace FlaxEditor.Viewport } } + /// + /// 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; + + if (step > 0) + MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Min(camValueIndex + 1, EditorViewportCameraSpeedValues.Length - 1)]; + else if (step < 0) + MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Max(camValueIndex - 1, 0)]; + } + private void OnEditorOptionsChanged(EditorOptions options) { _mouseSensitivity = options.Viewport.MouseSensitivity; @@ -1048,6 +1078,15 @@ namespace FlaxEditor.Viewport // Track controlling mouse state change bool wasControllingMouse = _prevInput.IsControllingMouse; _isControllingMouse = _input.IsControllingMouse; + + // Simulate holding mouse right down for trackpad users + if ((_prevInput.IsMouseRightDown && !_input.IsMouseRightDown) || win.GetKeyDown(KeyboardKeys.Escape)) + _isVirtualMouseRightDown = false; // Cancel when mouse right or escape is pressed + if (_wasVirtualMouseRightDown) + wasControllingMouse = true; + if (_isVirtualMouseRightDown) + _isControllingMouse = _isVirtualMouseRightDown; + if (wasControllingMouse != _isControllingMouse) { if (_isControllingMouse) @@ -1061,16 +1100,18 @@ namespace FlaxEditor.Viewport OnLeftMouseButtonDown(); else if (_prevInput.IsMouseLeftDown && !_input.IsMouseLeftDown) OnLeftMouseButtonUp(); - // - if (!_prevInput.IsMouseRightDown && _input.IsMouseRightDown) + + if ((!_prevInput.IsMouseRightDown && _input.IsMouseRightDown) || (!_wasVirtualMouseRightDown && _isVirtualMouseRightDown)) OnRightMouseButtonDown(); - else if (_prevInput.IsMouseRightDown && !_input.IsMouseRightDown) + else if ((_prevInput.IsMouseRightDown && !_input.IsMouseRightDown) || (_wasVirtualMouseRightDown && !_isVirtualMouseRightDown)) OnRightMouseButtonUp(); - // + if (!_prevInput.IsMouseMiddleDown && _input.IsMouseMiddleDown) OnMiddleMouseButtonDown(); else if (_prevInput.IsMouseMiddleDown && !_input.IsMouseMiddleDown) OnMiddleMouseButtonUp(); + + _wasVirtualMouseRightDown = _isVirtualMouseRightDown; } // Get clamped delta time (more stable during lags) @@ -1088,7 +1129,7 @@ namespace FlaxEditor.Viewport bool isAltDown = _input.IsAltDown; bool lbDown = _input.IsMouseLeftDown; bool mbDown = _input.IsMouseMiddleDown; - bool rbDown = _input.IsMouseRightDown; + bool rbDown = _input.IsMouseRightDown || _isVirtualMouseRightDown; bool wheelInUse = Math.Abs(_input.MouseWheelDelta) > Mathf.Epsilon; _input.IsPanning = !isAltDown && mbDown && !rbDown; @@ -1098,32 +1139,20 @@ namespace FlaxEditor.Viewport _input.IsOrbiting = isAltDown && lbDown && !mbDown && !rbDown; // Control move speed with RMB+Wheel - rmbWheel = useMovementSpeed && _input.IsMouseRightDown && wheelInUse; + rmbWheel = useMovementSpeed && (_input.IsMouseRightDown || _isVirtualMouseRightDown) && wheelInUse; if (rmbWheel) { - float step = 4.0f; + const float step = 4.0f; _wheelMovementChangeDeltaSum += _input.MouseWheelDelta * options.Viewport.MouseWheelSensitivity; - int camValueIndex = -1; - for (int i = 0; i < EditorViewportCameraSpeedValues.Length; i++) + if (_wheelMovementChangeDeltaSum >= step) { - if (Mathf.NearEqual(EditorViewportCameraSpeedValues[i], _movementSpeed)) - { - camValueIndex = i; - break; - } + _wheelMovementChangeDeltaSum -= step; + AdjustCameraMoveSpeed(1); } - if (camValueIndex != -1) + else if (_wheelMovementChangeDeltaSum <= -step) { - if (_wheelMovementChangeDeltaSum >= step) - { - _wheelMovementChangeDeltaSum -= step; - MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Min(camValueIndex + 1, EditorViewportCameraSpeedValues.Length - 1)]; - } - else if (_wheelMovementChangeDeltaSum <= -step) - { - _wheelMovementChangeDeltaSum += step; - MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Max(camValueIndex - 1, 0)]; - } + _wheelMovementChangeDeltaSum += step; + AdjustCameraMoveSpeed(-1); } } } @@ -1165,7 +1194,7 @@ namespace FlaxEditor.Viewport // Calculate smooth mouse delta not dependant on viewport size var offset = _viewMousePos - _startPos; - if (_input.IsZooming && !_input.IsMouseRightDown && !_input.IsMouseLeftDown && !_input.IsMouseMiddleDown && !_isOrtho && !rmbWheel) + if (_input.IsZooming && !_input.IsMouseRightDown && !_input.IsMouseLeftDown && !_input.IsMouseMiddleDown && !_isOrtho && !rmbWheel && !_isVirtualMouseRightDown) { offset = Float2.Zero; } @@ -1213,7 +1242,7 @@ namespace FlaxEditor.Viewport UpdateView(dt, ref moveDelta, ref mouseDelta, out var centerMouse); // Move mouse back to the root position - if (centerMouse && (_input.IsMouseRightDown || _input.IsMouseLeftDown || _input.IsMouseMiddleDown)) + if (centerMouse && (_input.IsMouseRightDown || _input.IsMouseLeftDown || _input.IsMouseMiddleDown || _isVirtualMouseRightDown)) { var center = PointToWindow(_startPos); win.MousePosition = center; @@ -1229,7 +1258,7 @@ namespace FlaxEditor.Viewport } else { - if (_input.IsMouseLeftDown || _input.IsMouseRightDown) + if (_input.IsMouseLeftDown || _input.IsMouseRightDown || _isVirtualMouseRightDown) { // Calculate smooth mouse delta not dependant on viewport size var offset = _viewMousePos - _startPos; @@ -1359,6 +1388,7 @@ namespace FlaxEditor.Viewport { OnControlMouseEnd(RootWindow.Window); _isControllingMouse = false; + _isVirtualMouseRightDown = false; } } diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 9cbfce562..6c52ce46b 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -194,6 +194,7 @@ namespace FlaxEditor.Viewport { _editor = editor; _dragAssets = new DragAssets(ValidateDragItem); + var inputOptions = editor.Options.Options.Input; // Prepare rendering task Task.ActorsSource = ActorsSources.Scenes; @@ -250,7 +251,7 @@ namespace FlaxEditor.Viewport var transformSpaceToggle = new ViewportWidgetButton(string.Empty, editor.Icons.Globe32, null, true) { Checked = TransformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World, - TooltipText = "Gizmo transform space (world or local)", + TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})", Parent = transformSpaceWidget }; transformSpaceToggle.Toggled += OnTransformSpaceToggle; @@ -347,7 +348,7 @@ namespace FlaxEditor.Viewport _gizmoModeTranslate = new ViewportWidgetButton(string.Empty, editor.Icons.Translate32, null, true) { Tag = TransformGizmoBase.Mode.Translate, - TooltipText = "Translate gizmo mode", + TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})", Checked = true, Parent = gizmoMode }; @@ -355,14 +356,14 @@ namespace FlaxEditor.Viewport _gizmoModeRotate = new ViewportWidgetButton(string.Empty, editor.Icons.Rotate32, null, true) { Tag = TransformGizmoBase.Mode.Rotate, - TooltipText = "Rotate gizmo mode", + TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})", Parent = gizmoMode }; _gizmoModeRotate.Toggled += OnGizmoModeToggle; _gizmoModeScale = new ViewportWidgetButton(string.Empty, editor.Icons.Scale32, null, true) { Tag = TransformGizmoBase.Mode.Scale, - TooltipText = "Scale gizmo mode", + TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})", Parent = gizmoMode }; _gizmoModeScale.Toggled += OnGizmoModeToggle; @@ -390,6 +391,7 @@ 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.LockFocusSelection, LockFocusSelection); InputActions.Add(options => options.FocusSelection, FocusSelection); InputActions.Add(options => options.RotateSelection, RotateSelection); diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index ec6a983ce..c02d6b6e8 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -84,6 +84,7 @@ namespace FlaxEditor.Viewport _dragAssets = new DragAssets(ValidateDragItem); ShowDebugDraw = true; ShowEditorPrimitives = true; + var inputOptions = window.Editor.Options.Options.Input; // Prepare rendering task Task.ActorsSource = ActorsSources.CustomActors; @@ -113,7 +114,7 @@ namespace FlaxEditor.Viewport var transformSpaceToggle = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Globe32, null, true) { Checked = TransformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World, - TooltipText = "Gizmo transform space (world or local)", + TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})", Parent = transformSpaceWidget }; transformSpaceToggle.Toggled += OnTransformSpaceToggle; @@ -205,7 +206,7 @@ namespace FlaxEditor.Viewport _gizmoModeTranslate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Translate32, null, true) { Tag = TransformGizmoBase.Mode.Translate, - TooltipText = "Translate gizmo mode", + TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})", Checked = true, Parent = gizmoMode }; @@ -213,14 +214,14 @@ namespace FlaxEditor.Viewport _gizmoModeRotate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Rotate32, null, true) { Tag = TransformGizmoBase.Mode.Rotate, - TooltipText = "Rotate gizmo mode", + TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})", Parent = gizmoMode }; _gizmoModeRotate.Toggled += OnGizmoModeToggle; _gizmoModeScale = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Scale32, null, true) { Tag = TransformGizmoBase.Mode.Scale, - TooltipText = "Scale gizmo mode", + TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})", Parent = gizmoMode }; _gizmoModeScale.Toggled += OnGizmoModeToggle; @@ -233,6 +234,7 @@ 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.FocusSelection, ShowSelectedActors); SetUpdate(ref _update, OnUpdate); diff --git a/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs b/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs index fc7fcdbe1..c6ce5a7b5 100644 --- a/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs +++ b/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs @@ -68,7 +68,7 @@ namespace FlaxEditor.Viewport.Previews /// public bool ShowBounds { - get => _boundsModel?.IsActive ?? false; + get => _boundsModel != null ? _boundsModel.IsActive : false; set { if (value == ShowBounds) @@ -110,7 +110,7 @@ namespace FlaxEditor.Viewport.Previews /// public bool ShowOrigin { - get => _originModel?.IsActive ?? false; + get => _originModel != null ? _originModel.IsActive : false; set { if (value == ShowOrigin) diff --git a/Source/Editor/Viewport/Previews/TexturePreview.cs b/Source/Editor/Viewport/Previews/TexturePreview.cs index a33a61f86..ec10bb019 100644 --- a/Source/Editor/Viewport/Previews/TexturePreview.cs +++ b/Source/Editor/Viewport/Previews/TexturePreview.cs @@ -500,7 +500,7 @@ namespace FlaxEditor.Viewport.Previews /// protected override void CalculateTextureRect(out Rectangle rect) { - CalculateTextureRect(_asset?.Size ?? new Float2(100), Size, out rect); + CalculateTextureRect(_asset != null ? _asset.Size : new Float2(100), Size, out rect); } /// @@ -549,7 +549,7 @@ namespace FlaxEditor.Viewport.Previews /// protected override void CalculateTextureRect(out Rectangle rect) { - CalculateTextureRect(_asset?.Size ?? new Float2(100), Size, out rect); + CalculateTextureRect(_asset != null ? _asset.Size : new Float2(100), Size, out rect); } /// @@ -604,7 +604,7 @@ namespace FlaxEditor.Viewport.Previews /// protected override void CalculateTextureRect(out Rectangle rect) { - CalculateTextureRect(_asset?.Size ?? new Float2(100), Size, out rect); + CalculateTextureRect(_asset != null ? _asset.Size : new Float2(100), Size, out rect); } /// @@ -659,7 +659,7 @@ namespace FlaxEditor.Viewport.Previews /// protected override void CalculateTextureRect(out Rectangle rect) { - CalculateTextureRect(_asset?.Size ?? new Float2(100), Size, out rect); + CalculateTextureRect(_asset != null ? _asset.Size : new Float2(100), Size, out rect); } /// diff --git a/Source/Editor/Windows/AboutDialog.cs b/Source/Editor/Windows/AboutDialog.cs index 4b9595445..b059dadcb 100644 --- a/Source/Editor/Windows/AboutDialog.cs +++ b/Source/Editor/Windows/AboutDialog.cs @@ -52,9 +52,11 @@ namespace FlaxEditor.Windows VerticalAlignment = TextAlignment.Near, Parent = this }; - var copyVersionButton = new Button(Width - 104, 6, 100, 20) + var buttonText = "Copy version info"; + var fontSize = Style.Current.FontMedium.MeasureText(buttonText); + var copyVersionButton = new Button(Width - fontSize.X - 8, 6, fontSize.X + 4, 20) { - Text = "Copy version info", + Text = buttonText, TooltipText = "Copies the current engine version information to system clipboard.", Parent = this }; diff --git a/Source/Editor/Windows/Assets/CollisionDataWindow.cs b/Source/Editor/Windows/Assets/CollisionDataWindow.cs index cc1daa45b..1b6ed914c 100644 --- a/Source/Editor/Windows/Assets/CollisionDataWindow.cs +++ b/Source/Editor/Windows/Assets/CollisionDataWindow.cs @@ -200,6 +200,7 @@ namespace FlaxEditor.Windows.Assets ViewportCamera = new FPSCamera(), Parent = _split.Panel1 }; + _preview.Task.ViewFlags &= ~ViewFlags.Sky & ~ViewFlags.Bloom; // Asset properties _propertiesPresenter = new CustomEditorPresenter(null); diff --git a/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs b/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs index d5ab2ae8a..5f1273999 100644 --- a/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs +++ b/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs @@ -69,7 +69,7 @@ namespace FlaxEditor.Windows.Assets [EditorDisplay("General"), Tooltip("The base material used to override it's properties")] public MaterialBase BaseMaterial { - get => Window?.Asset?.BaseMaterial; + get => Window?.Asset != null ? Window?.Asset.BaseMaterial : null; set { var asset = Window?.Asset; @@ -101,10 +101,12 @@ namespace FlaxEditor.Windows.Assets [HideInEditor] public object[] Values { - get => Window?.Asset?.Parameters.Select(x => x.Value).ToArray(); + get => Window?.Asset != null ? Window?.Asset.Parameters.Select(x => x.Value).ToArray() : null; set { - var parameters = Window?.Asset?.Parameters; + if (Window?.Asset == null) + return; + var parameters = Window?.Asset.Parameters; if (value != null && parameters != null) { if (value.Length != parameters.Length) @@ -131,9 +133,11 @@ namespace FlaxEditor.Windows.Assets [HideInEditor] public FlaxEngine.Object[] ValuesRef { - get => Window?.Asset?.Parameters.Select(x => x.Value as FlaxEngine.Object).ToArray(); + get => Window?.Asset != null ? Window?.Asset.Parameters.Select(x => x.Value as FlaxEngine.Object).ToArray() : null; set { + if (Window?.Asset == null) + return; var parameters = Window?.Asset?.Parameters; if (value != null && parameters != null) { @@ -293,7 +297,7 @@ namespace FlaxEditor.Windows.Assets var p = (MaterialParameter)e.Tag; // Try to get default value (from the base material) - var pBase = baseMaterial?.GetParameter(p.Name); + var pBase = baseMaterial != null ? baseMaterial.GetParameter(p.Name) : null; if (pBase != null && pBase.ParameterType == p.ParameterType) { valueContainer.SetDefaultValue(pBase.Value); diff --git a/Source/Editor/Windows/Assets/ModelWindow.cs b/Source/Editor/Windows/Assets/ModelWindow.cs index f70f6a3b5..c2764a5a6 100644 --- a/Source/Editor/Windows/Assets/ModelWindow.cs +++ b/Source/Editor/Windows/Assets/ModelWindow.cs @@ -134,7 +134,7 @@ namespace FlaxEditor.Windows.Assets if (Window._skipEffectsGuiEvents) return; - Window._isolateIndex = mesh?.MaterialSlotIndex ?? -1; + Window._isolateIndex = mesh != null ? mesh.MaterialSlotIndex : -1; Window.UpdateEffectsOnAsset(); UpdateEffectsOnUI(); } @@ -144,7 +144,7 @@ namespace FlaxEditor.Windows.Assets if (Window._skipEffectsGuiEvents) return; - Window._highlightIndex = mesh?.MaterialSlotIndex ?? -1; + Window._highlightIndex = mesh != null ? mesh.MaterialSlotIndex : -1; Window.UpdateEffectsOnAsset(); UpdateEffectsOnUI(); } @@ -326,7 +326,7 @@ namespace FlaxEditor.Windows.Assets [EditorOrder(10), EditorDisplay("Materials", EditorDisplayAttribute.InlineStyle)] public MaterialSlot[] MaterialSlots { - get => Asset?.MaterialSlots; + get => Asset != null ? Asset.MaterialSlots : null; set { if (Asset != null) diff --git a/Source/Editor/Windows/Assets/ParticleSystemWindow.cs b/Source/Editor/Windows/Assets/ParticleSystemWindow.cs index 328369680..17eda1358 100644 --- a/Source/Editor/Windows/Assets/ParticleSystemWindow.cs +++ b/Source/Editor/Windows/Assets/ParticleSystemWindow.cs @@ -187,7 +187,7 @@ namespace FlaxEditor.Windows.Assets base.Initialize(layout); var emitterTrack = Values[0] as EmitterTrackProxy; - if (emitterTrack?._effect?.Parameters == null) + if (emitterTrack?._effect == null || emitterTrack?._effect.Parameters == null) return; var group = layout.Group("Parameters"); diff --git a/Source/Editor/Windows/Assets/SceneAnimationWindow.cs b/Source/Editor/Windows/Assets/SceneAnimationWindow.cs index 824928743..162944144 100644 --- a/Source/Editor/Windows/Assets/SceneAnimationWindow.cs +++ b/Source/Editor/Windows/Assets/SceneAnimationWindow.cs @@ -792,7 +792,7 @@ namespace FlaxEditor.Windows.Assets { if (_previewButton.Checked) return; - _previewPlayerPicker.Value = _timeline.Player; + _previewPlayerPicker.Value = _timeline.Player != null ? _timeline.Player : null; _cachedPlayerId = _timeline.Player?.ID ?? Guid.Empty; } @@ -821,7 +821,7 @@ namespace FlaxEditor.Windows.Assets if (_timeline.IsModified) { var time = _timeline.CurrentTime; - var isPlaying = _previewPlayer?.IsPlaying ?? false; + var isPlaying = _previewPlayer != null ? _previewPlayer.IsPlaying : false; _timeline.Save(_asset); if (_previewButton.Checked && _previewPlayer != null) { diff --git a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs index a7ee6e767..d360e5570 100644 --- a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs +++ b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs @@ -151,7 +151,7 @@ namespace FlaxEditor.Windows.Assets if (Window._skipEffectsGuiEvents) return; - Window._isolateIndex = mesh?.MaterialSlotIndex ?? -1; + Window._isolateIndex = mesh != null ? mesh.MaterialSlotIndex : -1; Window.UpdateEffectsOnAsset(); UpdateEffectsOnUI(); } @@ -165,7 +165,7 @@ namespace FlaxEditor.Windows.Assets if (Window._skipEffectsGuiEvents) return; - Window._highlightIndex = mesh?.MaterialSlotIndex ?? -1; + Window._highlightIndex = mesh != null ? mesh.MaterialSlotIndex : -1; Window.UpdateEffectsOnAsset(); UpdateEffectsOnUI(); } @@ -415,7 +415,7 @@ namespace FlaxEditor.Windows.Assets [EditorOrder(10), EditorDisplay("Materials", EditorDisplayAttribute.InlineStyle)] public MaterialSlot[] MaterialSlots { - get => Asset?.MaterialSlots; + get => Asset != null ? Asset.MaterialSlots : null; set { if (Asset != null) diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs index 283133688..6923d634d 100644 --- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs +++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs @@ -145,10 +145,9 @@ namespace FlaxEditor.Windows cm.AddButton("Refresh all thumbnails", RefreshViewItemsThumbnails); } - cm.AddSeparator(); - - if (!isRootFolder) + if (!isRootFolder && !(item is ContentFolder projectFolder && projectFolder.Node is ProjectTreeNode)) { + cm.AddSeparator(); cm.AddButton("New folder", NewFolder); } diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 0f3c12286..c25316e9a 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -46,7 +46,7 @@ namespace FlaxEditor.Windows private TextBox _itemsSearchBox; private ViewDropdown _viewDropdown; private SortType _sortType; - private bool _showEngineFiles = true, _showPluginsFiles = true, _showAllFiles = true; + private bool _showEngineFiles = true, _showPluginsFiles = true, _showAllFiles = true, _showGeneratedFiles = false; private RootContentTreeNode _root; @@ -106,6 +106,19 @@ namespace FlaxEditor.Windows } } + internal bool ShowGeneratedFiles + { + get => _showGeneratedFiles; + set + { + if (_showGeneratedFiles != value) + { + _showGeneratedFiles = value; + RefreshView(); + } + } + } + internal bool ShowAllFiles { get => _showAllFiles; @@ -129,6 +142,8 @@ namespace FlaxEditor.Windows Title = "Content"; Icon = editor.Icons.Folder32; + FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); + // Content database events editor.ContentDatabase.WorkspaceModified += () => _isWorkspaceDirty = true; editor.ContentDatabase.ItemRemoved += OnContentDatabaseItemRemoved; @@ -314,6 +329,12 @@ namespace FlaxEditor.Windows b.Checked = ShowPluginsFiles; b.CloseMenuOnClick = false; b.AutoCheck = true; + + b = show.ContextMenu.AddButton("Generated files", () => ShowGeneratedFiles = !ShowGeneratedFiles); + b.TooltipText = "Shows generated files"; + b.Checked = ShowGeneratedFiles; + b.CloseMenuOnClick = false; + b.AutoCheck = true; b = show.ContextMenu.AddButton("All files", () => ShowAllFiles = !ShowAllFiles); b.TooltipText = "Shows all files including other than assets and source code"; @@ -520,7 +541,7 @@ namespace FlaxEditor.Windows } // Cache data - string extension = Path.GetExtension(item.Path); + string extension = item.IsFolder ? "" : Path.GetExtension(item.Path); var newPath = StringUtils.CombinePaths(item.ParentFolder.Path, newShortName + extension); // Check if was renaming mock element @@ -626,7 +647,7 @@ namespace FlaxEditor.Windows // Delete items for (int i = 0; i < toDelete.Count; i++) - Editor.ContentDatabase.Delete(toDelete[i]); + Editor.ContentDatabase.Delete(toDelete[i], true); RefreshView(); } @@ -722,7 +743,12 @@ namespace FlaxEditor.Windows { var item = Editor.ContentDatabase.Find(sourcePath); if (item != null) - Editor.ContentDatabase.Copy(item, Path.Combine(CurrentViewFolder.Path, item.FileName)); + { + var newPath = StringUtils.NormalizePath(Path.Combine(CurrentViewFolder.Path, item.FileName)); + if (sourcePath.Equals(newPath)) + newPath = GetClonedAssetPath(item); + Editor.ContentDatabase.Copy(item, newPath); + } else importFiles.Add(sourcePath); } @@ -969,6 +995,8 @@ namespace FlaxEditor.Windows var items = target.Folder.Children; if (!_showAllFiles) items = items.Where(x => !(x is FileItem)).ToList(); + if (!_showGeneratedFiles) + items = items.Where(x => !(x.Path.EndsWith(".Gen.cs", StringComparison.Ordinal) || x.Path.EndsWith(".Gen.h", StringComparison.Ordinal) || x.Path.EndsWith(".Gen.cpp", StringComparison.Ordinal) || x.Path.EndsWith(".csproj", StringComparison.Ordinal) || x.Path.Contains(".CSharp"))).ToList(); _view.ShowItems(items, _sortType, false, true); } } @@ -1145,6 +1173,7 @@ namespace FlaxEditor.Windows writer.WriteAttributeString("ShowEngineFiles", ShowEngineFiles.ToString()); writer.WriteAttributeString("ShowPluginsFiles", ShowPluginsFiles.ToString()); writer.WriteAttributeString("ShowAllFiles", ShowAllFiles.ToString()); + writer.WriteAttributeString("ShowGeneratedFiles", ShowGeneratedFiles.ToString()); writer.WriteAttributeString("ViewType", _view.ViewType.ToString()); } @@ -1162,6 +1191,8 @@ namespace FlaxEditor.Windows ShowPluginsFiles = value2; if (bool.TryParse(node.GetAttribute("ShowAllFiles"), out value2)) ShowAllFiles = value2; + if (bool.TryParse(node.GetAttribute("ShowGeneratedFiles"), out value2)) + ShowGeneratedFiles = value2; if (Enum.TryParse(node.GetAttribute("ViewType"), out ContentViewType viewType)) _view.ViewType = viewType; } diff --git a/Source/Editor/Windows/DebugLogWindow.cs b/Source/Editor/Windows/DebugLogWindow.cs index 49b03ede1..ab0c07ec5 100644 --- a/Source/Editor/Windows/DebugLogWindow.cs +++ b/Source/Editor/Windows/DebugLogWindow.cs @@ -317,6 +317,7 @@ namespace FlaxEditor.Windows Title = "Debug Log"; Icon = IconInfo; OnEditorOptionsChanged(Editor.Options.Options); + FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); // Toolstrip var toolstrip = new ToolStrip(22.0f) @@ -544,7 +545,7 @@ namespace FlaxEditor.Windows if (noLocation) { desc.LocationFile = match.Groups[2].Value; - int.TryParse(match.Groups[5].Value, out desc.LocationLine); + int.TryParse(match.Groups[4].Value, out desc.LocationLine); noLocation = false; } fineStackTrace.AppendLine(match.Groups[0].Value); @@ -573,7 +574,7 @@ namespace FlaxEditor.Windows if (match.Success) { desc.LocationFile = match.Groups[2].Value; - int.TryParse(match.Groups[3].Value, out desc.LocationLine); + int.TryParse(match.Groups[4].Value, out desc.LocationLine); } } diff --git a/Source/Editor/Windows/EditorOptionsWindow.cs b/Source/Editor/Windows/EditorOptionsWindow.cs index 2942f98c0..d53d4f632 100644 --- a/Source/Editor/Windows/EditorOptionsWindow.cs +++ b/Source/Editor/Windows/EditorOptionsWindow.cs @@ -216,5 +216,36 @@ namespace FlaxEditor.Windows base.OnDestroy(); } + + /// + protected override bool OnClosing(ClosingReason reason) + { + // Block closing only on user events + if (reason == ClosingReason.User) + { + // Check if asset has been edited and not saved (and still has linked item) + if (_isDataDirty && _options != null) + { + // Ask user for further action + var result = MessageBox.Show( + "Editor options have been edited. Save before closing?", + "Save before closing?", + MessageBoxButtons.YesNoCancel + ); + if (result == DialogResult.OK || result == DialogResult.Yes) + { + // Save and close + SaveData(); + } + else if (result == DialogResult.Cancel || result == DialogResult.Abort) + { + // Cancel closing + return true; + } + } + } + + return base.OnClosing(reason); + } } } diff --git a/Source/Editor/Windows/EditorWindow.cs b/Source/Editor/Windows/EditorWindow.cs index 1ff0363f1..ff0673c11 100644 --- a/Source/Editor/Windows/EditorWindow.cs +++ b/Source/Editor/Windows/EditorWindow.cs @@ -192,6 +192,13 @@ namespace FlaxEditor.Windows /// public override bool OnKeyDown(KeyboardKeys key) { + // Prevent closing the editor window when using RMB + Ctrl + W to slow down the camera flight + if (Editor.Options.Options.Input.CloseTab.Process(this, key)) + { + if (Root.GetMouseButton(MouseButton.Right)) + return true; + } + if (base.OnKeyDown(key)) return true; @@ -207,7 +214,8 @@ namespace FlaxEditor.Windows case KeyboardKeys.Tab: if (CanUseNavigation && Root != null) { - Root.Navigate(NavDirection.Next); + bool shiftDown = Root.GetKey(KeyboardKeys.Shift); + Root.Navigate(shiftDown ? NavDirection.Previous : NavDirection.Next); return true; } break; diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index 513542fdc..bbdf763fb 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -271,6 +271,8 @@ namespace FlaxEditor.Windows Title = "Game"; AutoFocus = true; + FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); + var task = MainRenderTask.Instance; // Setup viewport @@ -302,10 +304,6 @@ namespace FlaxEditor.Windows // Link editor options Editor.Options.OptionsChanged += OnOptionsChanged; OnOptionsChanged(Editor.Options.Options); - - InputActions.Add(options => options.Play, Editor.Simulation.DelegatePlayOrStopPlayInEditor); - InputActions.Add(options => options.Pause, Editor.Simulation.RequestResumeOrPause); - InputActions.Add(options => options.StepFrame, Editor.Simulation.RequestPlayOneFrame); } private void ChangeViewportRatio(ViewportScaleOptions v) diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs index 0b2249a33..1d188a32c 100644 --- a/Source/Editor/Windows/OutputLogWindow.cs +++ b/Source/Editor/Windows/OutputLogWindow.cs @@ -150,6 +150,7 @@ namespace FlaxEditor.Windows { Title = "Output Log"; ClipChildren = false; + FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); // Setup UI _viewDropdown = new Button(2, 2, 40.0f, TextBoxBase.DefaultHeight) @@ -159,7 +160,7 @@ namespace FlaxEditor.Windows Parent = this, }; _viewDropdown.Clicked += OnViewButtonClicked; - _searchBox = new SearchBox(false, _viewDropdown.Right + 2, 2, Width - _viewDropdown.Right - 2 - _scrollSize) + _searchBox = new SearchBox(false, _viewDropdown.Right + 2, 2, Width - _viewDropdown.Right - 4) { Parent = this, }; @@ -170,11 +171,12 @@ namespace FlaxEditor.Windows Maximum = 0, }; _hScroll.ValueChanged += OnHScrollValueChanged; - _vScroll = new VScrollBar(this, Width - _scrollSize, Height, _scrollSize) + _vScroll = new VScrollBar(this, Width - _scrollSize, Height - _viewDropdown.Height - 2, _scrollSize) { ThumbThickness = 10, Maximum = 0, }; + _vScroll.Y += _viewDropdown.Height + 2; _vScroll.ValueChanged += OnVScrollValueChanged; _output = new OutputTextBox { @@ -408,7 +410,7 @@ namespace FlaxEditor.Windows if (_output != null) { - _searchBox.Width = Width - _viewDropdown.Right - 2 - _scrollSize; + _searchBox.Width = Width - _viewDropdown.Right - 4; _output.Size = new Float2(_vScroll.X - 2, _hScroll.Y - 4 - _viewDropdown.Bottom); } } diff --git a/Source/Editor/Windows/Profiler/CPU.cs b/Source/Editor/Windows/Profiler/CPU.cs index 36fbf21f4..fd4061276 100644 --- a/Source/Editor/Windows/Profiler/CPU.cs +++ b/Source/Editor/Windows/Profiler/CPU.cs @@ -429,21 +429,8 @@ namespace FlaxEditor.Windows.Profiler private void UpdateTable(ref ViewRange viewRange) { _table.IsLayoutLocked = true; - int idx = 0; - while (_table.Children.Count > idx) - { - var child = _table.Children[idx]; - if (child is Row row) - { - _tableRowsCache.Add(row); - child.Parent = null; - } - else - { - idx++; - } - } + RecycleTableRows(_table, _tableRowsCache); UpdateTableInner(ref viewRange); _table.UnlockChildrenRecursive(); diff --git a/Source/Editor/Windows/Profiler/GPU.cs b/Source/Editor/Windows/Profiler/GPU.cs index 2cf75a9aa..4ed18691a 100644 --- a/Source/Editor/Windows/Profiler/GPU.cs +++ b/Source/Editor/Windows/Profiler/GPU.cs @@ -298,21 +298,7 @@ namespace FlaxEditor.Windows.Profiler private void UpdateTable() { _table.IsLayoutLocked = true; - int idx = 0; - while (_table.Children.Count > idx) - { - var child = _table.Children[idx]; - if (child is Row row) - { - _tableRowsCache.Add(row); - child.Parent = null; - } - else - { - idx++; - } - } - _table.LockChildrenRecursive(); + RecycleTableRows(_table, _tableRowsCache); UpdateTableInner(); diff --git a/Source/Editor/Windows/Profiler/Network.cs b/Source/Editor/Windows/Profiler/Network.cs index f0b93a03c..dbee0e8e7 100644 --- a/Source/Editor/Windows/Profiler/Network.cs +++ b/Source/Editor/Windows/Profiler/Network.cs @@ -1,8 +1,32 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using System; +using System.Collections.Generic; +using FlaxEditor.GUI; using FlaxEngine; using FlaxEngine.GUI; +namespace FlaxEngine +{ + partial class ProfilingTools + { + partial struct NetworkEventStat + { + /// + /// Gets the event name. + /// + public unsafe string Name + { + get + { + fixed (byte* name = Name0) + return System.Runtime.InteropServices.Marshal.PtrToStringAnsi(new IntPtr(name)); + } + } + } + } +} + namespace FlaxEditor.Windows.Profiler { /// @@ -13,6 +37,10 @@ namespace FlaxEditor.Windows.Profiler { private readonly SingleChart _dataSentChart; private readonly SingleChart _dataReceivedChart; + private readonly Table _tableRpc; + private readonly Table _tableRep; + private SamplesBuffer _events; + private List _tableRowsCache; private FlaxEngine.Networking.NetworkDriverStats _prevStats; public Network() @@ -48,11 +76,10 @@ namespace FlaxEditor.Windows.Profiler Parent = layout, }; _dataReceivedChart.SelectedSampleChanged += OnSelectedSampleChanged; - } - private static string FormatSampleBytes(float v) - { - return Utilities.Utils.FormatBytesCount((ulong)v); + // Tables + _tableRpc = InitTable(layout, "RPC Name"); + _tableRep = InitTable(layout, "Replication Name"); } /// @@ -60,21 +87,30 @@ namespace FlaxEditor.Windows.Profiler { _dataSentChart.Clear(); _dataReceivedChart.Clear(); + _events?.Clear(); } /// public override void Update(ref SharedUpdateData sharedData) { - var peer = FlaxEngine.Networking.NetworkManager.Peer; - if (peer == null) + // Gather peer stats + var peers = FlaxEngine.Networking.NetworkPeer.Peers; + var stats = new FlaxEngine.Networking.NetworkDriverStats(); + foreach (var peer in peers) { - _prevStats = new FlaxEngine.Networking.NetworkDriverStats(); - return; + var peerStats = peer.NetworkDriver.GetStats(); + stats.TotalDataSent += peerStats.TotalDataSent; + stats.TotalDataReceived += peerStats.TotalDataReceived; } - var stats = peer.NetworkDriver.GetStats(); _dataSentChart.AddSample(Mathf.Max((long)stats.TotalDataSent - (long)_prevStats.TotalDataSent, 0)); _dataReceivedChart.AddSample(Mathf.Max((long)stats.TotalDataReceived - (long)_prevStats.TotalDataReceived, 0)); _prevStats = stats; + + // Gather network events + var events = ProfilingTools.EventsNetwork; + if (_events == null) + _events = new SamplesBuffer(); + _events.Add(events); } /// @@ -82,6 +118,159 @@ namespace FlaxEditor.Windows.Profiler { _dataSentChart.SelectedSampleIndex = selectedFrame; _dataReceivedChart.SelectedSampleIndex = selectedFrame; + + // Update events tables + if (_events != null) + { + if (_tableRowsCache == null) + _tableRowsCache = new List(); + _tableRpc.IsLayoutLocked = true; + _tableRep.IsLayoutLocked = true; + RecycleTableRows(_tableRpc, _tableRowsCache); + RecycleTableRows(_tableRep, _tableRowsCache); + + var events = _events.Get(selectedFrame); + var rowCount = Int2.Zero; + if (events != null && events.Length != 0) + { + var rowColor2 = Style.Current.Background * 1.4f; + for (int i = 0; i < events.Length; i++) + { + var e = events[i]; + var name = e.Name; + var isRpc = name.Contains("::", StringComparison.Ordinal); + + Row row; + if (_tableRowsCache.Count != 0) + { + var last = _tableRowsCache.Count - 1; + row = _tableRowsCache[last]; + _tableRowsCache.RemoveAt(last); + } + else + { + row = new Row + { + Values = new object[5], + }; + } + { + // Name + row.Values[0] = name; + + // Count + row.Values[1] = (int)e.Count; + + // Data Size + row.Values[2] = (int)e.DataSize; + + // Message Size + row.Values[3] = (int)e.MessageSize; + + // Receivers + row.Values[4] = (float)e.Receivers / (float)e.Count; + } + + var table = isRpc ? _tableRpc : _tableRep; + row.Width = table.Width; + row.BackgroundColor = rowCount[isRpc ? 0 : 1] % 2 == 0 ? rowColor2 : Color.Transparent; + row.Parent = table; + if (isRpc) + rowCount.X++; + else + rowCount.Y++; + } + } + + _tableRpc.Visible = rowCount.X != 0; + _tableRep.Visible = rowCount.Y != 0; + _tableRpc.Children.Sort(SortRows); + _tableRep.Children.Sort(SortRows); + + _tableRpc.UnlockChildrenRecursive(); + _tableRpc.PerformLayout(); + _tableRep.UnlockChildrenRecursive(); + _tableRep.PerformLayout(); + } + } + + /// + public override void OnDestroy() + { + _tableRowsCache?.Clear(); + + base.OnDestroy(); + } + + private static Table InitTable(ContainerControl parent, string name) + { + var headerColor = Style.Current.LightBackground; + var table = new Table + { + Columns = new[] + { + new ColumnDefinition + { + UseExpandCollapseMode = true, + CellAlignment = TextAlignment.Near, + Title = name, + TitleBackgroundColor = headerColor, + }, + new ColumnDefinition + { + Title = "Count", + TitleBackgroundColor = headerColor, + }, + new ColumnDefinition + { + Title = "Data Size", + TitleBackgroundColor = headerColor, + FormatValue = FormatCellBytes, + }, + new ColumnDefinition + { + Title = "Message Size", + TitleBackgroundColor = headerColor, + FormatValue = FormatCellBytes, + }, + new ColumnDefinition + { + Title = "Receivers", + TitleBackgroundColor = headerColor, + }, + }, + Splits = new[] + { + 0.40f, + 0.15f, + 0.15f, + 0.15f, + 0.15f, + }, + Parent = parent, + }; + return table; + } + + private static string FormatSampleBytes(float v) + { + return Utilities.Utils.FormatBytesCount((ulong)v); + } + + private static string FormatCellBytes(object x) + { + return Utilities.Utils.FormatBytesCount((int)x); + } + + private static int SortRows(Control x, Control y) + { + if (x is Row xRow && y is Row yRow) + { + var xDataSize = (int)xRow.Values[2]; + var yDataSize = (int)yRow.Values[2]; + return yDataSize - xDataSize; + } + return 0; } } } diff --git a/Source/Editor/Windows/Profiler/ProfilerMode.cs b/Source/Editor/Windows/Profiler/ProfilerMode.cs index 0cc1a39ee..b8d2f0c4c 100644 --- a/Source/Editor/Windows/Profiler/ProfilerMode.cs +++ b/Source/Editor/Windows/Profiler/ProfilerMode.cs @@ -1,6 +1,8 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Collections.Generic; +using FlaxEditor.GUI; using FlaxEditor.GUI.Tabs; using FlaxEngine; @@ -135,5 +137,28 @@ namespace FlaxEditor.Windows.Profiler { SelectedSampleChanged?.Invoke(frameIndex); } + + /// + /// Recycles all table rows to be reused. + /// + /// The table. + /// The output cache. + protected static void RecycleTableRows(Table table, List rowsCache) + { + int idx = 0; + while (table.Children.Count > idx) + { + var child = table.Children[idx]; + if (child is Row row) + { + rowsCache.Add(row); + child.Parent = null; + } + else + { + idx++; + } + } + } } } diff --git a/Source/Editor/Windows/Profiler/ProfilerWindow.cs b/Source/Editor/Windows/Profiler/ProfilerWindow.cs index f5a5c6f86..f97e943ad 100644 --- a/Source/Editor/Windows/Profiler/ProfilerWindow.cs +++ b/Source/Editor/Windows/Profiler/ProfilerWindow.cs @@ -93,7 +93,7 @@ namespace FlaxEditor.Windows.Profiler _liveRecordingButton = toolstrip.AddButton(editor.Icons.Play64); _liveRecordingButton.LinkTooltip("Live profiling events recording"); _liveRecordingButton.AutoCheck = true; - _liveRecordingButton.Clicked += () => _liveRecordingButton.Icon = LiveRecording ? editor.Icons.Stop64 : editor.Icons.Play64; + _liveRecordingButton.Clicked += OnLiveRecordingChanged; _clearButton = toolstrip.AddButton(editor.Icons.Rotate32, Clear); _clearButton.LinkTooltip("Clear data"); toolstrip.AddSeparator(); @@ -116,6 +116,16 @@ namespace FlaxEditor.Windows.Profiler Parent = this }; _tabs.SelectedTabChanged += OnSelectedTabChanged; + + FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); + InputActions.Bindings.RemoveAll(x => x.Callback == this.FocusOrShow); + InputActions.Add(options => options.ProfilerWindow, Hide); + } + + private void OnLiveRecordingChanged() + { + _liveRecordingButton.Icon = LiveRecording ? Editor.Icons.Stop64 : Editor.Icons.Play64; + ProfilingTools.Enabled = LiveRecording; } /// diff --git a/Source/Editor/Windows/Profiler/SamplesBuffer.cs b/Source/Editor/Windows/Profiler/SamplesBuffer.cs index abc99cfd5..999156dca 100644 --- a/Source/Editor/Windows/Profiler/SamplesBuffer.cs +++ b/Source/Editor/Windows/Profiler/SamplesBuffer.cs @@ -49,6 +49,8 @@ namespace FlaxEditor.Windows.Profiler /// The sample value public T Get(int index) { + if (index >= _data.Length || _data.Length == 0) + return default; return index == -1 ? _data[_count - 1] : _data[index]; } diff --git a/Source/Editor/Windows/SceneEditorWindow.cs b/Source/Editor/Windows/SceneEditorWindow.cs index 72ff3be33..04cce47e8 100644 --- a/Source/Editor/Windows/SceneEditorWindow.cs +++ b/Source/Editor/Windows/SceneEditorWindow.cs @@ -19,28 +19,7 @@ namespace FlaxEditor.Windows protected SceneEditorWindow(Editor editor, bool hideOnClose, ScrollBars scrollBars) : base(editor, hideOnClose, scrollBars) { - // Setup input actions - InputActions.Add(options => options.Save, Editor.SaveAll); - InputActions.Add(options => options.Undo, () => - { - Editor.PerformUndo(); - Focus(); - }); - InputActions.Add(options => options.Redo, () => - { - Editor.PerformRedo(); - Focus(); - }); - InputActions.Add(options => options.Cut, Editor.SceneEditing.Cut); - InputActions.Add(options => options.Copy, Editor.SceneEditing.Copy); - InputActions.Add(options => options.Paste, Editor.SceneEditing.Paste); - InputActions.Add(options => options.Duplicate, Editor.SceneEditing.Duplicate); - InputActions.Add(options => options.SelectAll, Editor.SceneEditing.SelectAllScenes); - InputActions.Add(options => options.Delete, Editor.SceneEditing.Delete); - InputActions.Add(options => options.Search, () => Editor.Windows.SceneWin.Search()); - InputActions.Add(options => options.Play, Editor.Simulation.DelegatePlayOrStopPlayInEditor); - InputActions.Add(options => options.Pause, Editor.Simulation.RequestResumeOrPause); - InputActions.Add(options => options.StepFrame, Editor.Simulation.RequestPlayOneFrame); + FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); } } } diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index e48e59f80..63d110368 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -26,6 +26,7 @@ namespace FlaxEditor.Windows bool hasSthSelected = Editor.SceneEditing.HasSthSelected; bool isSingleActorSelected = Editor.SceneEditing.SelectionCount == 1 && Editor.SceneEditing.Selection[0] is ActorNode; bool canEditScene = Editor.StateMachine.CurrentState.CanEditScene && Level.IsAnySceneLoaded; + var inputOptions = Editor.Options.Options.Input; // Create popup @@ -44,17 +45,17 @@ namespace FlaxEditor.Windows if (hasSthSelected) { - contextMenu.AddButton(Editor.Windows.EditWin.IsPilotActorActive ? "Stop piloting actor" : "Pilot actor", Editor.UI.PilotActor); + contextMenu.AddButton(Editor.Windows.EditWin.IsPilotActorActive ? "Stop piloting actor" : "Pilot actor", inputOptions.PilotActor, Editor.UI.PilotActor); } contextMenu.AddSeparator(); // Basic editing options - b = contextMenu.AddButton("Rename", Rename); + b = contextMenu.AddButton("Rename", inputOptions.Rename, Rename); b.Enabled = isSingleActorSelected; - b = contextMenu.AddButton("Duplicate", Editor.SceneEditing.Duplicate); + b = contextMenu.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate); b.Enabled = hasSthSelected; if (isSingleActorSelected) @@ -116,17 +117,17 @@ namespace FlaxEditor.Windows } } } - b = contextMenu.AddButton("Delete", Editor.SceneEditing.Delete); + b = contextMenu.AddButton("Delete", inputOptions.Delete, Editor.SceneEditing.Delete); b.Enabled = hasSthSelected; contextMenu.AddSeparator(); - b = contextMenu.AddButton("Copy", Editor.SceneEditing.Copy); + b = contextMenu.AddButton("Copy", inputOptions.Copy, Editor.SceneEditing.Copy); b.Enabled = hasSthSelected; - contextMenu.AddButton("Paste", Editor.SceneEditing.Paste); + contextMenu.AddButton("Paste", inputOptions.Paste, Editor.SceneEditing.Paste); - b = contextMenu.AddButton("Cut", Editor.SceneEditing.Cut); + b = contextMenu.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut); b.Enabled = canEditScene; // Prefab options diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs index 609f98f83..fba052269 100644 --- a/Source/Editor/Windows/SceneTreeWindow.cs +++ b/Source/Editor/Windows/SceneTreeWindow.cs @@ -173,6 +173,9 @@ namespace FlaxEditor.Windows // Spawn it Editor.SceneEditing.Spawn(actor, parentActor); + + Editor.SceneEditing.Select(actor); + Rename(); } /// diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp index 7cf7a7f78..d2371a540 100644 --- a/Source/Editor/Windows/SplashScreen.cpp +++ b/Source/Editor/Windows/SplashScreen.cpp @@ -18,21 +18,21 @@ const Char* SplashScreenQuotes[] = TEXT("Loading"), TEXT("Unloading"), TEXT("Reloading"), - TEXT("Reloading gun"), + TEXT("Downloading more RAM"), TEXT("Consuming your RAM"), TEXT("Burning your CPU"), - TEXT("#BetterThanUnity"), TEXT("Rendering buttons"), TEXT("Collecting crash data"), - TEXT("Downloading porn"), #if PLATFORM_WINDOWS - TEXT("Removing 'C:\\Windows\\'"), + TEXT("We're getting everything ready for you."), #elif PLATFORM_LINUX - TEXT("Time to switch to Windows?"), - TEXT("Installing Windows 10"), + TEXT("Try it on a Raspberry"), + TEXT("Trying to exit vim"), + TEXT("Sudo flax --loadproject"), #elif PLATFORM_MAC - TEXT("Hacking your iPhone"), + TEXT("don't compare Macbooks to oranges."), TEXT("Why does macbook heat up?\nBecause it doesn't have windows"), + TEXT("Starting Direc... um, Vulkan renderer."), #endif TEXT("Kappa!"), TEXT("How you doin'?"), @@ -40,12 +40,10 @@ const Char* SplashScreenQuotes[] = TEXT("Bond. James Bond."), TEXT("To infinity and beyond!"), TEXT("Houston, we have a problem"), - TEXT("NotImplementedEngineException"), TEXT("Made in Poland"), TEXT("We like you"), TEXT("Compiling the compiler"), TEXT("Flax it up!"), - TEXT("Fun fact: Fortnite runs on Flax"), TEXT("Toss a coin to your Witcher!!!"), TEXT("Holy Moly!"), TEXT("Just Read the Instructions"), @@ -60,7 +58,6 @@ const Char* SplashScreenQuotes[] = TEXT("They see me loadin'"), TEXT("Loadin' loadin' and loadin' loadin'"), TEXT("Procedurally generating buttons"), - TEXT("Out of Memory Exception!"), TEXT("Running Big Bang simulation"), TEXT("Calculating infinity"), TEXT("Dividing infinity by zero"), @@ -70,10 +67,7 @@ const Char* SplashScreenQuotes[] = TEXT("Whatever you do, do it well.\n~Walt Disney"), TEXT("Here's Johnny!"), TEXT("Did you see that? No... I don't think so"), - TEXT("Collecting unreal power"), TEXT("Stay safe, friend"), - TEXT("trolololololololololololo"), - TEXT("xD"), TEXT("Come to the dark side"), TEXT("Flax Facts: This is a loading screen"), TEXT("Don't Stop Me Now"), @@ -81,61 +75,58 @@ const Char* SplashScreenQuotes[] = TEXT("Made with Flax"), TEXT("This is the way"), TEXT("The quick brown fox jumps over the lazy dog"), - TEXT("Hit The Road Jack"), TEXT("You have 7 lives left"), TEXT("May the Force be with you"), TEXT("A martini. Shaken, not stirred"), TEXT("Hasta la vista, baby"), TEXT("Winter is coming"), - TEXT("You know nothing, Jon Snow"), TEXT("Create something awesome!"), TEXT("Well Polished Engine"), TEXT("Error 404: Joke Not Found"), TEXT("Rushing B"), TEXT("Putting pineapple on pizza"), - TEXT("Loading Simulation"), + TEXT("Entering the Matrix"), TEXT("Get ready for a surprise!"), TEXT("Coffee is my fuel"), - TEXT("Installing a free copy of Cyberpunk 2077"), TEXT("With great power comes great electricity bill"), TEXT("Flax was made in the same city as Witcher 3"), TEXT("So JavaScript is a scripting version of Java"), TEXT("Good things take time.\n~Someone"), - TEXT("Get shit done"), TEXT("Hold Tight! Loading Flax"), TEXT("That's one small step for a man,\none giant leap for mankind"), TEXT("Remember to save your work frequently"), TEXT("In case of fire:\ngit commit, git push, leave building"), TEXT("Keep calm and make games"), TEXT("You're breathtaking!!!"), - TEXT("Do regular dogs see police dogs & think,\nOh no it's a cop?"), - TEXT("Dear Santa,\nDefine naughty."), TEXT("Blah, blah"), TEXT("My PRECIOUS!!!!"), TEXT("YOU SHALL NOT PASS!"), TEXT("You have my bow.\nAnd my axe!"), TEXT("To the bridge of Khazad-dum."), TEXT("One ring to rule them all.\nOne ring to find them."), - TEXT("Ladies and gentelman, we got him"), - TEXT("Cyberpunk of game engines"), TEXT("That's what she said"), - TEXT("Compiling Shaders (93,788)"), + TEXT("We could be compiling shaders here"), TEXT("Hello There"), TEXT("BAGUETTE"), - TEXT("All we had to do was follow the damn train, CJ"), TEXT("Here we go again"), TEXT("@everyone"), TEXT("Potato"), TEXT("Python is a programming snek"), TEXT("Flax will start when pigs will fly"), - TEXT("I'm the android sent by CyberLife"), - TEXT("Fancy-ass ray tracing, rtx on, lighting"), TEXT("ZOINKS"), TEXT("Scooby dooby doo"), TEXT("You shall not load!"), TEXT("The roof, the roof, the roof is on fire!"), - TEXT("I've seen better documentation...\nFrom ransomware gangs!"), TEXT("Slava Ukraini!"), + TEXT("RTX off... for now!"), + TEXT("Increasing Fiber count"), + TEXT("Now this is podracing!"), + TEXT("Weird flax, but ok"), + TEXT("Reticulating Splines"), + TEXT("Discombobulating"), + TEXT("Who is signing all these integers?!"), + TEXT("Flax fact: Flax was called Celelej once."), + TEXT("Changing text overflow setti-"), }; SplashScreen::~SplashScreen() diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index 3df4d3527..b747149c5 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -353,6 +353,8 @@ namespace FlaxEditor.Windows : base(editor, true, ScrollBars.None) { Title = "Toolbox"; + + FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); } /// diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp index fa7999ce0..8fef8e6d3 100644 --- a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp +++ b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp @@ -905,7 +905,7 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3 MException ex(exception); ex.Log(LogType::Error, TEXT("Property")); } - else if (!MCore::Type::IsPointer(valueType)) + else if (!MCore::Type::IsPointer(valueType) && !MCore::Type::IsReference(valueType)) { if (boxed) Platform::MemoryCopy(value, MCore::Object::Unbox(boxed), valueSize); diff --git a/Source/Engine/Content/Assets/MaterialInstance.cpp b/Source/Engine/Content/Assets/MaterialInstance.cpp index 7581692e1..3bf0c54cd 100644 --- a/Source/Engine/Content/Assets/MaterialInstance.cpp +++ b/Source/Engine/Content/Assets/MaterialInstance.cpp @@ -205,8 +205,6 @@ void MaterialInstance::Bind(BindParameters& params) Asset::LoadResult MaterialInstance::load() { - ASSERT(_baseMaterial == nullptr); - // Get main chunk auto chunk0 = GetChunk(0); if (chunk0 == nullptr || chunk0->IsMissing()) @@ -229,6 +227,7 @@ Asset::LoadResult MaterialInstance::load() else { // Clear parameters if has no material loaded + _baseMaterial = nullptr; Params.Dispose(); ParamsChanged(); } diff --git a/Source/Engine/ContentImporters/ImportShader.cpp b/Source/Engine/ContentImporters/ImportShader.cpp index 9cfb1242c..bf5b5581d 100644 --- a/Source/Engine/ContentImporters/ImportShader.cpp +++ b/Source/Engine/ContentImporters/ImportShader.cpp @@ -32,14 +32,21 @@ CreateAssetResult ImportShader::Import(CreateAssetContext& context) LOG(Warning, "Empty shader source file."); return CreateAssetResult::Error; } + + // Ensure the source code has an empty line at the end (expected by glslang) + auto sourceCodeChunkSize = sourceCodeSize + 1; + if (sourceCodeText[sourceCodeSize - 1] != '\n') + sourceCodeChunkSize++; + const auto& sourceCodeChunk = context.Data.Header.Chunks[SourceCodeChunk]; - sourceCodeChunk->Data.Allocate(sourceCodeSize + 1); + sourceCodeChunk->Data.Allocate(sourceCodeChunkSize); const auto sourceCode = sourceCodeChunk->Get(); Platform::MemoryCopy(sourceCode, sourceCodeText.Get(), sourceCodeSize); + sourceCode[sourceCodeChunkSize - 2] = '\n'; // Encrypt source code - Encryption::EncryptBytes(sourceCode, sourceCodeSize); - sourceCode[sourceCodeSize] = 0; + Encryption::EncryptBytes(sourceCode, sourceCodeChunkSize - 1); + sourceCode[sourceCodeChunkSize - 1] = 0; // Set Custom Data with Header ShaderStorage::Header20 shaderHeader; diff --git a/Source/Engine/Core/Log.cpp b/Source/Engine/Core/Log.cpp index fea72bfe8..e3d97e149 100644 --- a/Source/Engine/Core/Log.cpp +++ b/Source/Engine/Core/Log.cpp @@ -223,10 +223,10 @@ void Log::Logger::ProcessLogMessage(LogType type, const StringView& msg, fmt_fla else { //w.append(msg.Get(), msg.Get() + msg.Length()); - fmt_flax::format(w, TEXT("{}"), (const Char*)msg.Get()); + fmt_flax::format(w, TEXT("{}"), msg); } #else - fmt_flax::format(w, TEXT("{}"), (const Char*)msg.Get()); + fmt_flax::format(w, TEXT("{}"), msg); #endif } @@ -251,19 +251,13 @@ void Log::Logger::Write(LogType type, const StringView& msg) OnError(type, msg); } - // Check if need to show message box with that log message - if (type == LogType::Fatal) - { + // Ensure the error gets written to the disk + if (type == LogType::Fatal || type == LogType::Error) Flush(); - // Process message further - if (type == LogType::Fatal) - Platform::Fatal(msg); - else if (type == LogType::Error) - Platform::Error(msg); - else - Platform::Info(msg); - } + // Check if need to show message box with that log message + if (type == LogType::Fatal) + Platform::Fatal(msg); } const Char* ToString(LogType e) diff --git a/Source/Engine/Core/Math/Vector2.cs b/Source/Engine/Core/Math/Vector2.cs index 2af365638..568c51764 100644 --- a/Source/Engine/Core/Math/Vector2.cs +++ b/Source/Engine/Core/Math/Vector2.cs @@ -58,6 +58,7 @@ using Mathr = FlaxEngine.Mathf; */ using System; using System.Globalization; +using System.ComponentModel; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -953,6 +954,91 @@ namespace FlaxEngine return result; } + /// + /// Performs a gradual change of a vector towards a specified target over time + /// + /// Current vector. + /// Target vector. + /// Used to store the current velocity. + /// Determines the approximate time it should take to reach the target vector. + /// Defines the upper limit on the speed of the Smooth Damp. + public static Vector2 SmoothDamp(Vector2 current, Vector2 target, ref Vector2 currentVelocity, float smoothTime, float maxSpeed) + { + return SmoothDamp(current, target, ref currentVelocity, smoothTime, maxSpeed, Time.DeltaTime); + } + + /// + /// Performs a gradual change of a vector towards a specified target over time + /// + /// Current vector. + /// Target vector. + /// Used to store the current velocity. + /// Determines the approximate time it should take to reach the target vector. + public static Vector2 SmoothDamp(Vector2 current, Vector2 target, ref Vector2 currentVelocity, float smoothTime) + { + return SmoothDamp(current, target, ref currentVelocity, smoothTime, float.PositiveInfinity, Time.DeltaTime); + } + + /// + /// Performs a gradual change of a vector towards a specified target over time + /// + /// Current vector. + /// Target vector. + /// Used to store the current velocity. + /// Determines the approximate time it should take to reach the target vector. + /// Defines the upper limit on the speed of the Smooth Damp. + /// Delta Time, represents the time elapsed since last frame. + public static Vector2 SmoothDamp(Vector2 current, Vector2 target, ref Vector2 currentVelocity, float smoothTime, [DefaultValue("float.PositiveInfinity")] float maxSpeed, [DefaultValue("Time.DeltaTime")] float deltaTime) + { + smoothTime = Mathf.Max(0.0001f, smoothTime); + Real a = 2f / smoothTime; + Real b = a * deltaTime; + Real e = 1f / (1f + b + 0.48f * b * b + 0.235f * b * b * b); + + Real change_x = current.X - target.X; + Real change_y = current.Y - target.Y; + Vector2 originalTo = target; + + Real maxChangeSpeed = maxSpeed * smoothTime; + Real changeSq = maxChangeSpeed * maxChangeSpeed; + Real sqrDist = change_x * change_x + change_y * change_y; + if (sqrDist > changeSq) + { + var dist = (Real)Math.Sqrt(sqrDist); + change_x = change_x / dist * maxChangeSpeed; + change_y = change_y / dist * maxChangeSpeed; + } + + target.X = current.X - change_x; + target.Y = current.Y - change_y; + + Real temp_x = (currentVelocity.X + a * change_x) * deltaTime; + Real temp_y = (currentVelocity.Y + a * change_y) * deltaTime; + + currentVelocity.X = (currentVelocity.X - a * temp_x) * e; + currentVelocity.Y = (currentVelocity.Y - a * temp_y) * e; + + Real output_x = target.X + (change_x + temp_x) * e; + Real output_y = target.Y + (change_y + temp_y) * e; + + Real x1 = originalTo.X - current.X; + Real y1 = originalTo.Y - current.Y; + + Real x2 = output_x - originalTo.X; + Real y2 = output_y - originalTo.Y; + + if (x1 * x2 + y1 * y2 > 0) + { + output_x = originalTo.X; + output_y = originalTo.Y; + + currentVelocity.X = (output_x - originalTo.X) / deltaTime; + currentVelocity.Y = (output_y - originalTo.Y) / deltaTime; + } + + return new Vector2(output_x, output_y); + } + /// /// Performs a cubic interpolation between two vectors. /// diff --git a/Source/Engine/Core/Math/Vector3.cs b/Source/Engine/Core/Math/Vector3.cs index 4442d3334..59d1f63db 100644 --- a/Source/Engine/Core/Math/Vector3.cs +++ b/Source/Engine/Core/Math/Vector3.cs @@ -58,6 +58,7 @@ using Mathr = FlaxEngine.Mathf; */ using System; using System.Globalization; +using System.ComponentModel; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -1042,6 +1043,103 @@ namespace FlaxEngine return result; } + /// + /// Performs a gradual change of a vector towards a specified target over time + /// + /// Current vector. + /// Target vector. + /// Used to store the current velocity. + /// Determines the approximate time it should take to reach the target vector. + /// Defines the upper limit on the speed of the Smooth Damp. + public static Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 currentVelocity, float smoothTime, float maxSpeed) + { + return SmoothDamp(current, target, ref currentVelocity, smoothTime, maxSpeed, Time.DeltaTime); + } + + /// + /// Performs a gradual change of a vector towards a specified target over time + /// + /// Current vector. + /// Target vector. + /// Used to store the current velocity. + /// Determines the approximate time it should take to reach the target vector. + public static Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 currentVelocity, float smoothTime) + { + return SmoothDamp(current, target, ref currentVelocity, smoothTime, float.PositiveInfinity, Time.DeltaTime); + } + + /// + /// Performs a gradual change of a vector towards a specified target over time + /// + /// Current vector. + /// Target vector. + /// Used to store the current velocity. + /// Determines the approximate time it should take to reach the target vector. + /// Defines the upper limit on the speed of the Smooth Damp. + /// Delta Time, represents the time elapsed since last frame. + public static Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 currentVelocity, float smoothTime, [DefaultValue("float.PositiveInfinity")] float maxSpeed, [DefaultValue("Time.DeltaTime")] float deltaTime) + { + smoothTime = Mathf.Max(0.0001f, smoothTime); + Real a = 2f / smoothTime; + Real b = a * deltaTime; + Real e = 1f / (1f + b + 0.48f * b * b + 0.235f * b * b * b); + + Real change_x = current.X - target.X; + Real change_y = current.Y - target.Y; + Real change_z = current.Z - target.Z; + + Vector3 originalTo = target; + + Real maxChangeSpeed = maxSpeed * smoothTime; + Real changeSq = maxChangeSpeed * maxChangeSpeed; + Real sqrLen = change_x * change_x + change_y * change_y + change_z * change_z; + if (sqrLen > changeSq) + { + var len = (Real)Math.Sqrt(sqrLen); + change_x = change_x / len * maxChangeSpeed; + change_y = change_y / len * maxChangeSpeed; + change_z = change_z / len * maxChangeSpeed; + } + + target.X = current.X - change_x; + target.Y = current.Y - change_y; + target.Z = current.Z - change_z; + + Real temp_x = (currentVelocity.X + a * change_x) * deltaTime; + Real temp_y = (currentVelocity.Y + a * change_y) * deltaTime; + Real temp_z = (currentVelocity.Z + a * change_z) * deltaTime; + + currentVelocity.X = (currentVelocity.X - a * temp_x) * e; + currentVelocity.Y = (currentVelocity.Y - a * temp_y) * e; + currentVelocity.Z = (currentVelocity.Z - a * temp_z) * e; + + Real output_x = target.X + (change_x + temp_x) * e; + Real output_y = target.Y + (change_y + temp_y) * e; + Real output_z = target.Z + (change_z + temp_z) * e; + + Real x1 = originalTo.X - current.X; + Real y1 = originalTo.Y - current.Y; + Real z1 = originalTo.Z - current.Z; + + Real x2 = output_x - originalTo.X; + Real y2 = output_y - originalTo.Y; + Real z2 = output_z - originalTo.Z; + + if (x1 * x2 + y1 * y2 + z1 * z2 > 0) + { + output_x = originalTo.X; + output_y = originalTo.Y; + output_z = originalTo.Z; + + currentVelocity.X = (output_x - originalTo.X) / deltaTime; + currentVelocity.Y = (output_y - originalTo.Y) / deltaTime; + currentVelocity.Z = (output_z - originalTo.Z) / deltaTime; + } + + return new Vector3(output_x, output_y, output_z); + } + + /// /// Performs a cubic interpolation between two vectors. /// diff --git a/Source/Engine/Core/Types/String.cpp b/Source/Engine/Core/Types/String.cpp index e53f04af7..dab000873 100644 --- a/Source/Engine/Core/Types/String.cpp +++ b/Source/Engine/Core/Types/String.cpp @@ -72,7 +72,7 @@ void String::Set(const char* chars, int32 length) } _length = length; } - if (chars) + if (chars && length) StringUtils::ConvertANSI2UTF16(chars, _data, length, _length); } @@ -298,8 +298,10 @@ String String::TrimTrailing() const end--; } - ASSERT_LOW_LAYER(end >= start); - return Substring(start, end - start + 1); + const int32 count = end - start + 1; + if (start >= 0 && start + count <= Length() && count >= 0) + return String(_data + start, count); + return Empty; } String& String::operator/=(const Char* str) diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index bafcd30b3..ec56861ba 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -102,9 +102,6 @@ int32 Engine::Main(const Char* cmdLine) Platform::SetHighDpiAwarenessEnabled(!CommandLine::Options.LowDPI.IsTrue()); Time::StartupTime = DateTime::Now(); -#if COMPILE_WITH_PROFILER - ProfilerCPU::Enabled = true; -#endif Globals::StartupFolder = Globals::BinariesFolder = Platform::GetMainDirectory(); #if USE_EDITOR Globals::StartupFolder /= TEXT("../../../.."); diff --git a/Source/Engine/Engine/NativeInterop.Invoker.cs b/Source/Engine/Engine/NativeInterop.Invoker.cs index 381e6dac0..36d7f2e03 100644 --- a/Source/Engine/Engine/NativeInterop.Invoker.cs +++ b/Source/Engine/Engine/NativeInterop.Invoker.cs @@ -84,7 +84,7 @@ namespace FlaxEngine.Interop internal static IntPtr MarshalReturnValueType(ref Type returnValue) { - return returnValue != null ? ManagedHandle.ToIntPtr(GetTypeGCHandle(returnValue)) : IntPtr.Zero; + return returnValue != null ? ManagedHandle.ToIntPtr(GetTypeManagedHandle(returnValue)) : IntPtr.Zero; } internal static IntPtr MarshalReturnValueArray(ref TRet returnValue) @@ -162,8 +162,8 @@ namespace FlaxEngine.Interop return ManagedHandle.ToIntPtr((ManagedHandle)(object)returnObject); if (returnType == typeof(bool)) return (bool)returnObject ? boolTruePtr : boolFalsePtr; - if (returnType == typeof(Type)) - return ManagedHandle.ToIntPtr(GetTypeGCHandle(Unsafe.As(returnObject))); + if (returnType == typeof(Type) || returnType == typeof(TypeHolder)) + return ManagedHandle.ToIntPtr(GetTypeManagedHandle(Unsafe.As(returnObject))); if (returnType.IsArray && ArrayFactory.GetMarshalledType(returnType.GetElementType()) == returnType.GetElementType()) return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(Unsafe.As(returnObject)), GCHandleType.Weak); if (returnType.IsArray) @@ -186,8 +186,8 @@ namespace FlaxEngine.Interop return (IntPtr)(object)returnObject; if (returnType == typeof(ManagedHandle)) return ManagedHandle.ToIntPtr((ManagedHandle)(object)returnObject); - if (returnType == typeof(Type)) - return returnObject != null ? ManagedHandle.ToIntPtr(GetTypeGCHandle(Unsafe.As(returnObject))) : IntPtr.Zero; + if (returnType == typeof(Type) || returnType == typeof(TypeHolder)) + return returnObject != null ? ManagedHandle.ToIntPtr(GetTypeManagedHandle(Unsafe.As(returnObject))) : IntPtr.Zero; if (returnType.IsArray) { var elementType = returnType.GetElementType(); diff --git a/Source/Engine/Engine/NativeInterop.Marshallers.cs b/Source/Engine/Engine/NativeInterop.Marshallers.cs index 74d6e6bad..58aa95a2f 100644 --- a/Source/Engine/Engine/NativeInterop.Marshallers.cs +++ b/Source/Engine/Engine/NativeInterop.Marshallers.cs @@ -32,6 +32,7 @@ namespace FlaxEngine.Interop public static class NativeToManaged { public static object ConvertToManaged(IntPtr unmanaged) => unmanaged == IntPtr.Zero ? null : ManagedHandle.FromIntPtr(unmanaged).Target; + public static IntPtr ConvertToUnmanaged(object managed) => managed != null ? ManagedHandle.ToIntPtr(managed, GCHandleType.Weak) : IntPtr.Zero; public static void Free(IntPtr unmanaged) { @@ -44,6 +45,7 @@ namespace FlaxEngine.Interop #endif public static class ManagedToNative { + public static object ConvertToManaged(IntPtr unmanaged) => unmanaged == IntPtr.Zero ? null : ManagedHandle.FromIntPtr(unmanaged).Target; public static IntPtr ConvertToUnmanaged(object managed) => managed != null ? ManagedHandle.ToIntPtr(managed, GCHandleType.Weak) : IntPtr.Zero; public static void Free(IntPtr unmanaged) @@ -117,13 +119,13 @@ namespace FlaxEngine.Interop [CustomMarshaller(typeof(Type), MarshalMode.Default, typeof(SystemTypeMarshaller))] public static class SystemTypeMarshaller { - public static Type ConvertToManaged(IntPtr unmanaged) => Unsafe.As(ManagedHandleMarshaller.ConvertToManaged(unmanaged)); + public static Type ConvertToManaged(IntPtr unmanaged) => unmanaged != IntPtr.Zero ? Unsafe.As(ManagedHandleMarshaller.ConvertToManaged(unmanaged)).type : null; public static IntPtr ConvertToUnmanaged(Type managed) { if (managed == null) return IntPtr.Zero; - ManagedHandle handle = NativeInterop.GetTypeGCHandle(managed); + ManagedHandle handle = NativeInterop.GetTypeManagedHandle(managed); return ManagedHandle.ToIntPtr(handle); } @@ -147,29 +149,16 @@ namespace FlaxEngine.Interop #if FLAX_EDITOR [HideInEditor] #endif - [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.ManagedToUnmanagedIn, typeof(ObjectMarshaller.ManagedToNative))] - [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.UnmanagedToManagedOut, typeof(ObjectMarshaller.ManagedToNative))] - [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.ElementIn, typeof(ObjectMarshaller.ManagedToNative))] - [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.ManagedToUnmanagedOut, typeof(ObjectMarshaller.NativeToManaged))] - [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.UnmanagedToManagedIn, typeof(ObjectMarshaller.NativeToManaged))] - [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.ElementOut, typeof(ObjectMarshaller.NativeToManaged))] + [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.ManagedToUnmanagedIn, typeof(ObjectMarshaller))] + [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.UnmanagedToManagedOut, typeof(ObjectMarshaller))] + [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.ElementIn, typeof(ObjectMarshaller))] + [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.ManagedToUnmanagedOut, typeof(ObjectMarshaller))] + [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.UnmanagedToManagedIn, typeof(ObjectMarshaller))] + [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.ElementOut, typeof(ObjectMarshaller))] public static class ObjectMarshaller { -#if FLAX_EDITOR - [HideInEditor] -#endif - public static class NativeToManaged - { - public static FlaxEngine.Object ConvertToManaged(IntPtr unmanaged) => unmanaged != IntPtr.Zero ? Unsafe.As(ManagedHandle.FromIntPtr(unmanaged).Target) : null; - } - -#if FLAX_EDITOR - [HideInEditor] -#endif - public static class ManagedToNative - { - public static IntPtr ConvertToUnmanaged(FlaxEngine.Object managed) => Unsafe.As(managed) != null ? ManagedHandle.ToIntPtr(managed) : IntPtr.Zero; - } + public static FlaxEngine.Object ConvertToManaged(IntPtr unmanaged) => unmanaged != IntPtr.Zero ? Unsafe.As(ManagedHandle.FromIntPtr(unmanaged).Target) : null; + public static IntPtr ConvertToUnmanaged(FlaxEngine.Object managed) => Unsafe.As(managed) != null ? ManagedHandle.ToIntPtr(managed) : IntPtr.Zero; } #if FLAX_EDITOR @@ -342,6 +331,7 @@ namespace FlaxEngine.Interop public static class NativeToManaged { public static Dictionary ConvertToManaged(IntPtr unmanaged) => DictionaryMarshaller.ToManaged(unmanaged); + public static IntPtr ConvertToUnmanaged(Dictionary managed) => DictionaryMarshaller.ToNative(managed, GCHandleType.Weak); public static void Free(IntPtr unmanaged) => DictionaryMarshaller.Free(unmanaged); } @@ -350,8 +340,8 @@ namespace FlaxEngine.Interop #endif public static class ManagedToNative { + public static Dictionary ConvertToManaged(IntPtr unmanaged) => DictionaryMarshaller.ToManaged(unmanaged); public static IntPtr ConvertToUnmanaged(Dictionary managed) => DictionaryMarshaller.ToNative(managed, GCHandleType.Weak); - public static void Free(IntPtr unmanaged) { //DictionaryMarshaller.Free(unmanaged); // No need to free weak handles @@ -425,6 +415,28 @@ namespace FlaxEngine.Interop return new T[numElements]; } + public static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[] managed, out int numElements) + { + if (managed is null) + { + numElements = 0; + return null; + } + numElements = managed.Length; + (ManagedHandle managedArrayHandle, _) = ManagedArray.AllocatePooledArray(managed.Length); + return (TUnmanagedElement*)ManagedHandle.ToIntPtr(managedArrayHandle); + } + + public static ReadOnlySpan GetManagedValuesSource(T[] managed) => managed; + + public static Span GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged) + { + if (unmanaged == null) + return Span.Empty; + ManagedArray managedArray = Unsafe.As(ManagedHandle.FromIntPtr(new IntPtr(unmanaged)).Target); + return managedArray.ToSpan(); + } + public static Span GetManagedValuesDestination(T[] managed) => managed; public static ReadOnlySpan GetUnmanagedValuesSource(TUnmanagedElement* unmanaged, int numElements) @@ -591,6 +603,7 @@ namespace FlaxEngine.Interop public static class NativeToManaged { public static string ConvertToManaged(IntPtr unmanaged) => ManagedString.ToManaged(unmanaged); + public static unsafe IntPtr ConvertToUnmanaged(string managed) => managed == null ? IntPtr.Zero : ManagedHandle.ToIntPtr(managed, GCHandleType.Weak); public static void Free(IntPtr unmanaged) => ManagedString.Free(unmanaged); } @@ -599,11 +612,8 @@ namespace FlaxEngine.Interop #endif public static class ManagedToNative { - public static unsafe IntPtr ConvertToUnmanaged(string managed) - { - return managed == null ? IntPtr.Zero : ManagedHandle.ToIntPtr(managed, GCHandleType.Weak); - } - + public static string ConvertToManaged(IntPtr unmanaged) => ManagedString.ToManaged(unmanaged); + public static unsafe IntPtr ConvertToUnmanaged(string managed) => managed == null ? IntPtr.Zero : ManagedHandle.ToIntPtr(managed, GCHandleType.Weak); public static void Free(IntPtr unmanaged) { //ManagedString.Free(unmanaged); // No need to free weak handles diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index 2c1bab744..b40fa740a 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -19,6 +19,7 @@ namespace FlaxEngine.Interop internal struct NativeClassDefinitions { internal ManagedHandle typeHandle; + internal IntPtr nativePointer; internal IntPtr name; internal IntPtr fullname; internal IntPtr @namespace; @@ -40,6 +41,7 @@ namespace FlaxEngine.Interop internal IntPtr name; internal ManagedHandle fieldHandle; internal ManagedHandle fieldTypeHandle; + internal int fieldOffset; internal uint fieldAttributes; } @@ -139,6 +141,9 @@ namespace FlaxEngine.Interop unsafe partial class NativeInterop { + [LibraryImport("FlaxEngine", EntryPoint = "NativeInterop_CreateClass", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))] + internal static partial void NativeInterop_CreateClass(ref NativeClassDefinitions managedClass, ManagedHandle assemblyHandle); + internal enum MTypes : uint { End = 0x00, @@ -184,7 +189,7 @@ namespace FlaxEngine.Interop { string moduleName = Marshal.PtrToStringAnsi(moduleNamePtr); string modulePath = Marshal.PtrToStringAnsi(modulePathPtr); - nativeLibraryPaths[moduleName] = modulePath; + libraryPaths[moduleName] = modulePath; } [UnmanagedCallersOnly] @@ -205,46 +210,8 @@ namespace FlaxEngine.Interop NativeMemory.AlignedFree(ptr); } - [UnmanagedCallersOnly] - internal static void GetManagedClasses(ManagedHandle assemblyHandle, NativeClassDefinitions** managedClasses, int* managedClassCount) + private static Assembly GetOwningAssembly(Type type) { - Assembly assembly = Unsafe.As(assemblyHandle.Target); - var assemblyTypes = GetAssemblyTypes(assembly); - - NativeClassDefinitions* arr = (NativeClassDefinitions*)NativeAlloc(assemblyTypes.Length, Unsafe.SizeOf()); - - for (int i = 0; i < assemblyTypes.Length; i++) - { - var type = assemblyTypes[i]; - IntPtr ptr = IntPtr.Add(new IntPtr(arr), Unsafe.SizeOf() * i); - var managedClass = new NativeClassDefinitions - { - typeHandle = GetTypeGCHandle(type), - name = NativeAllocStringAnsi(type.Name), - fullname = NativeAllocStringAnsi(type.GetTypeName()), - @namespace = NativeAllocStringAnsi(type.Namespace ?? ""), - typeAttributes = (uint)type.Attributes, - }; - Unsafe.Write(ptr.ToPointer(), managedClass); - } - - *managedClasses = arr; - *managedClassCount = assemblyTypes.Length; - } - - [UnmanagedCallersOnly] - internal static void GetManagedClassFromType(ManagedHandle typeHandle, NativeClassDefinitions* managedClass, ManagedHandle* assemblyHandle) - { - Type type = Unsafe.As(typeHandle.Target); - *managedClass = new NativeClassDefinitions - { - typeHandle = GetTypeGCHandle(type), - name = NativeAllocStringAnsi(type.Name), - fullname = NativeAllocStringAnsi(type.GetTypeName()), - @namespace = NativeAllocStringAnsi(type.Namespace ?? ""), - typeAttributes = (uint)type.Attributes, - }; - Assembly assembly = null; if (type.IsGenericType && !type.Assembly.IsCollectible) { @@ -261,14 +228,87 @@ namespace FlaxEngine.Interop } if (assembly == null) assembly = type.Assembly; + return assembly; + } - *assemblyHandle = GetAssemblyHandle(assembly); + private static NativeClassDefinitions CreateNativeClassDefinitions(Type type, out ManagedHandle assemblyHandle) + { + assemblyHandle = GetAssemblyHandle(GetOwningAssembly(type)); + return CreateNativeClassDefinitions(type); + } + + private static NativeClassDefinitions CreateNativeClassDefinitions(Type type) + { + return new NativeClassDefinitions() + { + typeHandle = RegisterType(type).handle, + name = NativeAllocStringAnsi(type.Name), + fullname = NativeAllocStringAnsi(type.GetTypeName()), + @namespace = NativeAllocStringAnsi(type.Namespace ?? ""), + typeAttributes = (uint)type.Attributes, + }; + } + + private static NativeClassDefinitions CreateNativeClassDefinitions(Type type, ManagedHandle typeHandle, out ManagedHandle assemblyHandle) + { + assemblyHandle = GetAssemblyHandle(GetOwningAssembly(type)); + return new NativeClassDefinitions() + { + typeHandle = typeHandle, + name = NativeAllocStringAnsi(type.Name), + fullname = NativeAllocStringAnsi(type.GetTypeName()), + @namespace = NativeAllocStringAnsi(type.Namespace ?? ""), + typeAttributes = (uint)type.Attributes, + }; + } + + [UnmanagedCallersOnly] + internal static void GetManagedClasses(ManagedHandle assemblyHandle, NativeClassDefinitions** managedClasses, int* managedClassCount) + { + Assembly assembly = Unsafe.As(assemblyHandle.Target); + Type[] assemblyTypes = GetAssemblyTypes(assembly); + + *managedClasses = (NativeClassDefinitions*)NativeAlloc(assemblyTypes.Length, Unsafe.SizeOf()); + *managedClassCount = assemblyTypes.Length; + Span span = new Span(*managedClasses, assemblyTypes.Length); + for (int i = 0; i < assemblyTypes.Length; i++) + { + Type type = assemblyTypes[i]; + ref var managedClass = ref span[i]; + managedClass = CreateNativeClassDefinitions(type); + } + } + + [UnmanagedCallersOnly] + internal static void RegisterManagedClassNativePointers(NativeClassDefinitions** managedClasses, int managedClassCount) + { + Span span = new Span(Unsafe.Read(managedClasses).ToPointer(), managedClassCount); + foreach (ref NativeClassDefinitions managedClass in span) + { + TypeHolder typeHolder = Unsafe.As(managedClass.typeHandle.Target); + typeHolder.managedClassPointer = managedClass.nativePointer; + } + } + + [UnmanagedCallersOnly] + internal static void GetManagedClassFromType(ManagedHandle typeHandle, NativeClassDefinitions* managedClass, ManagedHandle* assemblyHandle) + { + Type type = Unsafe.As(typeHandle.Target); + *managedClass = CreateNativeClassDefinitions(type, out ManagedHandle handle); + *assemblyHandle = handle; + } + + private static void RegisterNativeClassFromType(TypeHolder typeHolder, ManagedHandle typeHandle) + { + NativeClassDefinitions managedClass = CreateNativeClassDefinitions(typeHolder.type, typeHandle, out ManagedHandle assemblyHandle); + NativeInterop_CreateClass(ref managedClass, assemblyHandle); + typeHolder.managedClassPointer = managedClass.nativePointer; } [UnmanagedCallersOnly] internal static void GetClassMethods(ManagedHandle typeHandle, NativeMethodDefinitions** classMethods, int* classMethodsCount) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); var methods = new List(); var staticMethods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly); @@ -296,8 +336,8 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static void GetClassFields(ManagedHandle typeHandle, NativeFieldDefinitions** classFields, int* classFieldsCount) { - Type type = Unsafe.As(typeHandle.Target); - var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + Type type = Unsafe.As(typeHandle.Target); + var fields = type.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); NativeFieldDefinitions* arr = (NativeFieldDefinitions*)NativeAlloc(fields.Length, Unsafe.SizeOf()); for (int i = 0; i < fields.Length; i++) @@ -318,7 +358,8 @@ namespace FlaxEngine.Interop { name = NativeAllocStringAnsi(fieldHolder.field.Name), fieldHandle = fieldHandle, - fieldTypeHandle = GetTypeGCHandle(fieldHolder.field.FieldType), + fieldTypeHandle = GetTypeManagedHandle(fieldHolder.field.FieldType), + fieldOffset = fieldHolder.fieldOffset, fieldAttributes = (uint)fieldHolder.field.Attributes, }; Unsafe.Write(IntPtr.Add(new IntPtr(arr), Unsafe.SizeOf() * i).ToPointer(), classField); @@ -330,8 +371,8 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static void GetClassProperties(ManagedHandle typeHandle, NativePropertyDefinitions** classProperties, int* classPropertiesCount) { - Type type = Unsafe.As(typeHandle.Target); - var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + Type type = Unsafe.As(typeHandle.Target); + var properties = type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); var arr = (NativePropertyDefinitions*)NativeAlloc(properties.Length, Unsafe.SizeOf()); for (int i = 0; i < properties.Length; i++) @@ -364,7 +405,7 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static void GetClassAttributes(ManagedHandle typeHandle, ManagedHandle** classAttributes, int* classAttributesCount) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); object[] attributeValues = type.GetCustomAttributes(false); ManagedHandle* arr = (ManagedHandle*)NativeAlloc(attributeValues.Length, Unsafe.SizeOf()); @@ -384,13 +425,13 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static ManagedHandle GetCustomAttribute(ManagedHandle typeHandle, ManagedHandle attributeHandle) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); var attributes = type.GetCustomAttributes(false); object attrib; if (attributeHandle.IsAllocated) { // Check for certain attribute type - Type attributeType = Unsafe.As(attributeHandle.Target); + Type attributeType = Unsafe.As(attributeHandle.Target); attrib = attributes.FirstOrDefault(x => x.GetType() == attributeType); } else @@ -413,7 +454,7 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static void GetClassInterfaces(ManagedHandle typeHandle, IntPtr* classInterfaces, int* classInterfacesCount) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); Type[] interfaces = type.GetInterfaces(); // Match mono_class_get_interfaces which doesn't return interfaces from base class @@ -465,7 +506,7 @@ namespace FlaxEngine.Interop IntPtr arr = (IntPtr)NativeAlloc(interfaces.Length, IntPtr.Size); for (int i = 0; i < interfaces.Length; i++) { - ManagedHandle handle = GetTypeGCHandle(interfaces[i]); + ManagedHandle handle = GetTypeManagedHandle(interfaces[i]); Unsafe.Write(IntPtr.Add(arr, IntPtr.Size * i).ToPointer(), handle); } *classInterfaces = arr; @@ -477,7 +518,7 @@ namespace FlaxEngine.Interop { MethodHolder methodHolder = Unsafe.As(methodHandle.Target); Type returnType = methodHolder.returnType; - return GetTypeGCHandle(returnType); + return GetTypeManagedHandle(returnType); } [UnmanagedCallersOnly] @@ -488,7 +529,7 @@ namespace FlaxEngine.Interop IntPtr arr = (IntPtr)NativeAlloc(methodHolder.parameterTypes.Length, IntPtr.Size); for (int i = 0; i < methodHolder.parameterTypes.Length; i++) { - ManagedHandle typeHandle = GetTypeGCHandle(methodHolder.parameterTypes[i]); + ManagedHandle typeHandle = GetTypeManagedHandle(methodHolder.parameterTypes[i]); Unsafe.Write(IntPtr.Add(new IntPtr(arr), IntPtr.Size * i).ToPointer(), typeHandle); } *typeHandles = arr; @@ -509,22 +550,15 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static ManagedHandle NewObject(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); - if (type.IsAbstract) - { - // Dotnet doesn't allow to instantiate abstract type thus allow to use generated mock class usage (eg. for Script or GPUResource) for generated abstract types - var abstractWrapper = type.GetNestedType("AbstractWrapper", BindingFlags.NonPublic); - if (abstractWrapper != null) - type = abstractWrapper; - } - object value = RuntimeHelpers.GetUninitializedObject(type); + TypeHolder typeHolder = Unsafe.As(typeHandle.Target); + object value = typeHolder.CreateObject(); return ManagedHandle.Alloc(value); } [UnmanagedCallersOnly] internal static ManagedHandle NewArray(ManagedHandle typeHandle, long size) { - Type elementType = Unsafe.As(typeHandle.Target); + Type elementType = Unsafe.As(typeHandle.Target); Type marshalledType = ArrayFactory.GetMarshalledType(elementType); Type arrayType = ArrayFactory.GetArrayType(elementType); if (marshalledType.IsValueType) @@ -543,9 +577,9 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static ManagedHandle GetArrayTypeFromElementType(ManagedHandle elementTypeHandle) { - Type elementType = Unsafe.As(elementTypeHandle.Target); + Type elementType = Unsafe.As(elementTypeHandle.Target); Type classType = ArrayFactory.GetArrayType(elementType); - return GetTypeGCHandle(classType); + return GetTypeManagedHandle(classType); } [UnmanagedCallersOnly] @@ -559,6 +593,19 @@ namespace FlaxEngine.Interop return managedArray.Pointer; } + [UnmanagedCallersOnly] + internal static IntPtr GetArray(ManagedHandle handle) + { + if (!handle.IsAllocated) + return IntPtr.Zero; + object value = handle.Target; + if (value is ManagedArray) + return (IntPtr)handle; + if (value is Array) + return Invoker.MarshalReturnValueGeneric(value.GetType(), value); + return IntPtr.Zero; + } + [UnmanagedCallersOnly] internal static int GetArrayLength(ManagedHandle arrayHandle) { @@ -593,7 +640,7 @@ namespace FlaxEngine.Interop Type classType = obj.GetType(); if (classType == typeof(ManagedArray)) classType = ((ManagedArray)obj).ArrayType; - return GetTypeGCHandle(classType); + return GetTypeManagedHandle(classType); } [UnmanagedCallersOnly] @@ -634,7 +681,7 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static ManagedHandle BoxValue(ManagedHandle typeHandle, IntPtr valuePtr) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); object value = MarshalToManaged(valuePtr, type); return ManagedHandle.Alloc(value, GCHandleType.Weak); } @@ -677,6 +724,14 @@ namespace FlaxEngine.Interop } } + [UnmanagedCallersOnly] + internal static IntPtr GetObjectClass(ManagedHandle objectHandle) + { + object obj = objectHandle.Target; + TypeHolder typeHolder = GetTypeHolder(obj.GetType()); + return typeHolder.managedClassPointer; + } + [UnmanagedCallersOnly] internal static IntPtr InvokeMethod(ManagedHandle instanceHandle, ManagedHandle methodHandle, IntPtr paramPtr, IntPtr exceptionPtr) { @@ -693,7 +748,7 @@ namespace FlaxEngine.Interop catch (Exception exception) { if (exceptionPtr != IntPtr.Zero) - Marshal.WriteIntPtr(exceptionPtr, ManagedHandle.ToIntPtr(exception, GCHandleType.Weak)); + Unsafe.Write(exceptionPtr.ToPointer(), ManagedHandle.ToIntPtr(exception, GCHandleType.Weak)); return IntPtr.Zero; } return returnValue; @@ -708,7 +763,7 @@ namespace FlaxEngine.Interop for (int i = 0; i < numParams; i++) { - IntPtr nativePtr = Marshal.ReadIntPtr(IntPtr.Add(paramPtr, sizeof(IntPtr) * i)); + IntPtr nativePtr = Unsafe.Read((IntPtr.Add(paramPtr, sizeof(IntPtr) * i)).ToPointer()); methodParameters[i] = MarshalToManaged(nativePtr, methodHolder.parameterTypes[i]); } @@ -724,7 +779,7 @@ namespace FlaxEngine.Interop realException = exception.InnerException; if (exceptionPtr != IntPtr.Zero) - Marshal.WriteIntPtr(exceptionPtr, ManagedHandle.ToIntPtr(realException, GCHandleType.Weak)); + Unsafe.Write(exceptionPtr.ToPointer(), ManagedHandle.ToIntPtr(realException, GCHandleType.Weak)); else throw realException; return IntPtr.Zero; @@ -736,7 +791,7 @@ namespace FlaxEngine.Interop Type parameterType = methodHolder.parameterTypes[i]; if (parameterType.IsByRef) { - IntPtr nativePtr = Marshal.ReadIntPtr(IntPtr.Add(paramPtr, sizeof(IntPtr) * i)); + IntPtr nativePtr = Unsafe.Read((IntPtr.Add(paramPtr, sizeof(IntPtr) * i)).ToPointer()); MarshalToNative(methodParameters[i], nativePtr, parameterType.GetElementType()); } } @@ -790,7 +845,7 @@ namespace FlaxEngine.Interop internal static int FieldGetOffset(ManagedHandle fieldHandle) { FieldHolder field = Unsafe.As(fieldHandle.Target); - return (int)Marshal.OffsetOf(field.field.DeclaringType, field.field.Name); + return field.fieldOffset; } [UnmanagedCallersOnly] @@ -798,14 +853,47 @@ namespace FlaxEngine.Interop { object fieldOwner = fieldOwnerHandle.Target; FieldHolder field = Unsafe.As(fieldHandle.Target); - field.toNativeMarshaller(field.field, fieldOwner, valuePtr, out int fieldOffset); + field.toNativeMarshaller(field.field, field.fieldOffset, fieldOwner, valuePtr, out int fieldSize); + } + + [UnmanagedCallersOnly] + internal static void FieldGetValueReference(ManagedHandle fieldOwnerHandle, ManagedHandle fieldHandle, IntPtr valuePtr) + { + object fieldOwner = fieldOwnerHandle.Target; + FieldHolder field = Unsafe.As(fieldHandle.Target); + if (fieldOwner.GetType().IsValueType) + { + ref IntPtr fieldRef = ref FieldHelper.GetValueTypeFieldReference(field.fieldOffset, ref fieldOwner); + Unsafe.Write(valuePtr.ToPointer(), fieldRef); + } + else + { + ref IntPtr fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(field.fieldOffset, ref fieldOwner); + Unsafe.Write(valuePtr.ToPointer(), fieldRef); + } + } + + [UnmanagedCallersOnly] + internal static void FieldGetValueReferenceWithOffset(ManagedHandle fieldOwnerHandle, int fieldOffset, IntPtr valuePtr) + { + object fieldOwner = fieldOwnerHandle.Target; + if (fieldOwner.GetType().IsValueType) + { + ref IntPtr fieldRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); + Unsafe.Write(valuePtr.ToPointer(), fieldRef); + } + else + { + ref IntPtr fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); + Unsafe.Write(valuePtr.ToPointer(), fieldRef); + } } [UnmanagedCallersOnly] internal static IntPtr FieldGetValueBoxed(ManagedHandle fieldOwnerHandle, ManagedHandle fieldHandle) { - object fieldOwner = fieldOwnerHandle.Target; FieldHolder field = Unsafe.As(fieldHandle.Target); + object fieldOwner = field.field.IsStatic ? null : fieldOwnerHandle.Target; object fieldValue = field.field.GetValue(fieldOwner); return Invoker.MarshalReturnValueGeneric(field.field.FieldType, fieldValue); } @@ -826,7 +914,15 @@ namespace FlaxEngine.Interop } [UnmanagedCallersOnly] - internal static ManagedHandle LoadAssemblyImage(IntPtr assemblyPathPtr, IntPtr* assemblyName, IntPtr* assemblyFullName) + internal static void GetAssemblyName(ManagedHandle assemblyHandle, IntPtr* assemblyName, IntPtr* assemblyFullName) + { + Assembly assembly = Unsafe.As(assemblyHandle.Target); + *assemblyName = NativeAllocStringAnsi(assembly.GetName().Name); + *assemblyFullName = NativeAllocStringAnsi(assembly.FullName); + } + + [UnmanagedCallersOnly] + internal static ManagedHandle LoadAssemblyImage(IntPtr assemblyPathPtr) { if (!firstAssemblyLoaded) { @@ -834,55 +930,55 @@ namespace FlaxEngine.Interop firstAssemblyLoaded = true; Assembly flaxEngineAssembly = AssemblyLoadContext.Default.Assemblies.First(x => x.GetName().Name == "FlaxEngine.CSharp"); - *assemblyName = NativeAllocStringAnsi(flaxEngineAssembly.GetName().Name); - *assemblyFullName = NativeAllocStringAnsi(flaxEngineAssembly.FullName); return GetAssemblyHandle(flaxEngineAssembly); } + try + { + string assemblyPath = Marshal.PtrToStringUni(assemblyPathPtr); - string assemblyPath = Marshal.PtrToStringAnsi(assemblyPathPtr); - - Assembly assembly; + Assembly assembly; #if FLAX_EDITOR - // Load assembly from loaded bytes to prevent file locking in Editor - var assemblyBytes = File.ReadAllBytes(assemblyPath); - using MemoryStream stream = new MemoryStream(assemblyBytes); - var pdbPath = Path.ChangeExtension(assemblyPath, "pdb"); - if (File.Exists(pdbPath)) - { - // Load including debug symbols - using FileStream pdbStream = new FileStream(Path.ChangeExtension(assemblyPath, "pdb"), FileMode.Open); - assembly = scriptingAssemblyLoadContext.LoadFromStream(stream, pdbStream); - } - else - { - assembly = scriptingAssemblyLoadContext.LoadFromStream(stream); - } + // Load assembly from loaded bytes to prevent file locking in Editor + var assemblyBytes = File.ReadAllBytes(assemblyPath); + using MemoryStream stream = new MemoryStream(assemblyBytes); + var pdbPath = Path.ChangeExtension(assemblyPath, "pdb"); + if (File.Exists(pdbPath)) + { + // Load including debug symbols + using FileStream pdbStream = new FileStream(Path.ChangeExtension(assemblyPath, "pdb"), FileMode.Open); + assembly = scriptingAssemblyLoadContext.LoadFromStream(stream, pdbStream); + } + else + { + assembly = scriptingAssemblyLoadContext.LoadFromStream(stream); + } #else - // Load assembly from file - assembly = scriptingAssemblyLoadContext.LoadFromAssemblyPath(assemblyPath); + // Load assembly from file + assembly = scriptingAssemblyLoadContext.LoadFromAssemblyPath(assemblyPath); #endif - if (assembly == null) - return new ManagedHandle(); - NativeLibrary.SetDllImportResolver(assembly, NativeLibraryImportResolver); + if (assembly == null) + return new ManagedHandle(); + NativeLibrary.SetDllImportResolver(assembly, NativeLibraryImportResolver); - // Assemblies loaded via streams have no Location: https://github.com/dotnet/runtime/issues/12822 - AssemblyLocations.Add(assembly.FullName, assemblyPath); + // Assemblies loaded via streams have no Location: https://github.com/dotnet/runtime/issues/12822 + AssemblyLocations.Add(assembly.FullName, assemblyPath); - *assemblyName = NativeAllocStringAnsi(assembly.GetName().Name); - *assemblyFullName = NativeAllocStringAnsi(assembly.FullName); - return GetAssemblyHandle(assembly); + return GetAssemblyHandle(assembly); + } + catch (Exception ex) + { + Debug.LogException(ex); + } + return new ManagedHandle(); } [UnmanagedCallersOnly] - internal static ManagedHandle GetAssemblyByName(IntPtr namePtr, IntPtr* assemblyName, IntPtr* assemblyFullName) + internal static ManagedHandle GetAssemblyByName(IntPtr namePtr) { string name = Marshal.PtrToStringAnsi(namePtr); Assembly assembly = Utils.GetAssemblies().FirstOrDefault(x => x.GetName().Name == name); if (assembly == null) return new ManagedHandle(); - - *assemblyName = NativeAllocStringAnsi(assembly.GetName().Name); - *assemblyFullName = NativeAllocStringAnsi(assembly.FullName); return GetAssemblyHandle(assembly); } @@ -909,7 +1005,7 @@ namespace FlaxEngine.Interop loadedNativeLibraries.Remove(nativeLibraryName); } if (nativeLibraryName != null) - nativeLibraryPaths.Remove(nativeLibraryName); + libraryPaths.Remove(nativeLibraryName); } [UnmanagedCallersOnly] @@ -921,9 +1017,9 @@ namespace FlaxEngine.Interop // Release all references in collectible ALC cachedDelegatesCollectible.Clear(); - foreach (var pair in typeHandleCacheCollectible) - pair.Value.Free(); - typeHandleCacheCollectible.Clear(); + foreach (var pair in managedTypesCollectible) + pair.Value.handle.Free(); + managedTypesCollectible.Clear(); foreach (var handle in methodHandlesCollectible) handle.Free(); methodHandlesCollectible.Clear(); @@ -953,7 +1049,7 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static int NativeSizeOf(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); Type nativeType = GetInternalType(type) ?? type; if (nativeType == typeof(Version)) nativeType = typeof(NativeVersion); @@ -971,8 +1067,8 @@ namespace FlaxEngine.Interop if (typeHandle == otherTypeHandle) return 1; - Type type = Unsafe.As(typeHandle.Target); - Type otherType = Unsafe.As(otherTypeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); + Type otherType = Unsafe.As(otherTypeHandle.Target); if (type == otherType) return 1; @@ -989,37 +1085,39 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static byte TypeIsAssignableFrom(ManagedHandle typeHandle, ManagedHandle otherTypeHandle) { - Type type = Unsafe.As(typeHandle.Target); - Type otherType = Unsafe.As(otherTypeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); + Type otherType = Unsafe.As(otherTypeHandle.Target); return (byte)(type.IsAssignableFrom(otherType) ? 1 : 0); } [UnmanagedCallersOnly] internal static byte TypeIsValueType(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); return (byte)(type.IsValueType ? 1 : 0); } [UnmanagedCallersOnly] internal static byte TypeIsEnum(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); return (byte)(type.IsEnum ? 1 : 0); } [UnmanagedCallersOnly] - internal static ManagedHandle GetClassParent(ManagedHandle typeHandle) + internal static IntPtr GetClassParent(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); - return GetTypeGCHandle(type.BaseType); + TypeHolder typeHolder = Unsafe.As(typeHandle.Target); + TypeHolder baseTypeHolder = GetTypeHolder(typeHolder.type.BaseType); + return baseTypeHolder.managedClassPointer; } [UnmanagedCallersOnly] - internal static ManagedHandle GetElementClass(ManagedHandle typeHandle) + internal static IntPtr GetElementClass(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); - return GetTypeGCHandle(type.GetElementType()); + TypeHolder typeHolder = Unsafe.As(typeHandle.Target); + TypeHolder elementTypeHolder = GetTypeHolder(typeHolder.type.GetElementType()); + return elementTypeHolder.managedClassPointer; } [UnmanagedCallersOnly] @@ -1110,32 +1208,35 @@ namespace FlaxEngine.Interop } [UnmanagedCallersOnly] - internal static ManagedHandle GetTypeClass(ManagedHandle typeHandle) + internal static IntPtr GetTypeClass(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); - if (type.IsByRef) - type = type.GetElementType(); // Drop reference type (&) to get actual value type - return GetTypeGCHandle(type); + TypeHolder typeHolder = Unsafe.As(typeHandle.Target); + if (typeHolder.type.IsByRef) + { + // Drop reference type (&) to get actual value type + return GetTypeHolder(typeHolder.type.GetElementType()).managedClassPointer; + } + return typeHolder.managedClassPointer; } [UnmanagedCallersOnly] internal static bool GetTypeIsPointer(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); return type.IsPointer; } [UnmanagedCallersOnly] internal static bool GetTypeIsReference(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); - return type.IsByRef; + Type type = Unsafe.As(typeHandle.Target); + return !type.IsValueType; // Maybe also type.IsByRef? } [UnmanagedCallersOnly] internal static uint GetTypeMTypesEnum(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); if (type.IsByRef) type = type.GetElementType(); // Drop reference type (&) to get actual value type MTypes monoType; diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index dd6087b98..500636290 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -24,7 +24,8 @@ namespace FlaxEngine.Interop /// /// Provides a Mono-like API for native code to access managed runtime. /// - internal static unsafe partial class NativeInterop + [HideInEditor] + public static unsafe partial class NativeInterop { internal static Dictionary AssemblyLocations = new(); @@ -37,12 +38,12 @@ namespace FlaxEngine.Interop private static List methodHandles = new(); private static ConcurrentDictionary cachedDelegates = new(); - private static Dictionary typeHandleCache = new(); + private static Dictionary managedTypes = new(new TypeComparer()); private static List fieldHandleCache = new(); #if FLAX_EDITOR private static List methodHandlesCollectible = new(); private static ConcurrentDictionary cachedDelegatesCollectible = new(); - private static Dictionary typeHandleCacheCollectible = new(); + private static Dictionary managedTypesCollectible = new(new TypeComparer()); private static List fieldHandleCacheCollectible = new(); #endif private static Dictionary classAttributesCacheCollectible = new(); @@ -50,7 +51,7 @@ namespace FlaxEngine.Interop private static Dictionary _typeSizeCache = new(); private static Dictionary loadedNativeLibraries = new(); - internal static Dictionary nativeLibraryPaths = new(); + internal static Dictionary libraryPaths = new(); private static Dictionary assemblyOwnedNativeLibraries = new(); internal static AssemblyLoadContext scriptingAssemblyLoadContext; @@ -59,7 +60,7 @@ namespace FlaxEngine.Interop { if (!loadedNativeLibraries.TryGetValue(libraryName, out IntPtr nativeLibrary)) { - if (!nativeLibraryPaths.TryGetValue(libraryName, out var nativeLibraryPath)) + if (!libraryPaths.TryGetValue(libraryName, out var nativeLibraryPath)) nativeLibraryPath = libraryName; nativeLibrary = NativeLibrary.Load(nativeLibraryPath, assembly, dllImportSearchPath); @@ -101,9 +102,9 @@ namespace FlaxEngine.Interop private static Assembly OnScriptingAssemblyLoadContextResolving(AssemblyLoadContext assemblyLoadContext, AssemblyName assemblyName) { // FIXME: There should be a better way to resolve the path to EditorTargetPath where the dependencies are stored - foreach (string nativeLibraryPath in nativeLibraryPaths.Values) + foreach (string libraryPath in libraryPaths.Values) { - string editorTargetPath = Path.GetDirectoryName(nativeLibraryPath); + string editorTargetPath = Path.GetDirectoryName(libraryPath); var assemblyPath = Path.Combine(editorTargetPath, assemblyName.Name + ".dll"); if (File.Exists(assemblyPath)) @@ -118,6 +119,38 @@ namespace FlaxEngine.Interop { } + // Cache offsets to frequently accessed fields of FlaxEngine.Object + private static int unmanagedPtrFieldOffset = IntPtr.Size + (Unsafe.Read((typeof(FlaxEngine.Object).GetField("__unmanagedPtr", BindingFlags.Instance | BindingFlags.NonPublic).FieldHandle.Value + 4 + IntPtr.Size).ToPointer()) & 0xFFFFFF); + private static int internalIdFieldOffset = IntPtr.Size + (Unsafe.Read((typeof(FlaxEngine.Object).GetField("__internalId", BindingFlags.Instance | BindingFlags.NonPublic).FieldHandle.Value + 4 + IntPtr.Size).ToPointer()) & 0xFFFFFF); + + [UnmanagedCallersOnly] + internal static void ScriptingObjectSetInternalValues(ManagedHandle objectHandle, IntPtr unmanagedPtr, IntPtr idPtr) + { + object obj = objectHandle.Target; + if (obj is not Object) + return; + + { + ref IntPtr fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(unmanagedPtrFieldOffset, ref obj); + fieldRef = unmanagedPtr; + } + + if (idPtr != IntPtr.Zero) + { + ref Guid nativeId = ref Unsafe.AsRef(idPtr.ToPointer()); + ref Guid fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(internalIdFieldOffset, ref obj); + fieldRef = nativeId; + } + } + + [UnmanagedCallersOnly] + internal static ManagedHandle ScriptingObjectCreate(ManagedHandle typeHandle, IntPtr unmanagedPtr, IntPtr idPtr) + { + TypeHolder typeHolder = Unsafe.As(typeHandle.Target); + object obj = typeHolder.CreateScriptingObject(unmanagedPtr, idPtr); + return ManagedHandle.Alloc(obj); + } + internal static void* NativeAlloc(int byteCount) { return NativeMemory.AlignedAlloc((UIntPtr)byteCount, 16); @@ -147,7 +180,27 @@ namespace FlaxEngine.Interop NativeMemory.AlignedFree(ptr); } - internal static T[] GCHandleArrayToManagedArray(ManagedArray ptrArray) where T : class + /// + /// Converts a delegate into a function pointer that is callable from unmanaged code via but cached delegate to prevent collecting it by GC. + /// + /// The type of delegate to convert. + /// The delegate to be passed to unmanaged code. + /// A value that can be passed to unmanaged code, which, in turn, can use it to call the underlying managed delegate. + public static IntPtr GetFunctionPointerForDelegate(TDelegate d) where TDelegate : notnull + { + // Example use-case: C# script runs actions via JobSystem.Dispatch which causes crash due to GC collecting Delegate object + ManagedHandle.Alloc(d, GCHandleType.Weak); + + return Marshal.GetFunctionPointerForDelegate(d); + } + + /// + /// Converts array of GC Handles from native runtime to managed array. + /// + /// Array element type. + /// Input array. + /// Output array. + public static T[] GCHandleArrayToManagedArray(ManagedArray ptrArray) where T : class { Span span = ptrArray.ToSpan(); T[] managedArray = new T[ptrArray.Length]; @@ -156,7 +209,12 @@ namespace FlaxEngine.Interop return managedArray; } - internal static IntPtr[] ManagedArrayToGCHandleArray(Array array) + /// + /// Converts managed array wrapper into array of GC Handles for native runtime. + /// + /// Input array. + /// Output array. + public static IntPtr[] ManagedArrayToGCHandleArray(Array array) { if (array.Length == 0) return Array.Empty(); @@ -170,13 +228,26 @@ namespace FlaxEngine.Interop return pointerArray; } - internal static ManagedArray ManagedArrayToGCHandleWrappedArray(Array array) + /// + /// Converts managed array wrapper into array of GC Handles for native runtime. + /// + /// Input array. + /// Output array. + public static ManagedArray ManagedArrayToGCHandleWrappedArray(Array array) { IntPtr[] pointerArray = ManagedArrayToGCHandleArray(array); return ManagedArray.WrapNewArray(pointerArray, array.GetType()); } - internal static TDst[] ConvertArray(Span src, Func convertFunc) + /// + /// Converts array with a custom converter function for each element. + /// + /// Input data type. + /// Output data type. + /// The input array. + /// Converter callback. + /// The output array. + public static TDst[] ConvertArray(Span src, Func convertFunc) { TDst[] dst = new TDst[src.Length]; for (int i = 0; i < src.Length; i++) @@ -184,7 +255,15 @@ namespace FlaxEngine.Interop return dst; } - internal static TDst[] ConvertArray(TSrc[] src, Func convertFunc) + /// + /// Converts array with a custom converter function for each element. + /// + /// Input data type. + /// Output data type. + /// The input array. + /// Converter callback. + /// The output array. + public static TDst[] ConvertArray(TSrc[] src, Func convertFunc) { TDst[] dst = new TDst[src.Length]; for (int i = 0; i < src.Length; i++) @@ -279,19 +358,12 @@ namespace FlaxEngine.Interop return FindType(internalAssemblyQualifiedName); } - internal class ReferenceTypePlaceholder - { - } - - internal struct ValueTypePlaceholder - { - } + internal class ReferenceTypePlaceholder { } + internal struct ValueTypePlaceholder { } internal delegate object MarshalToManagedDelegate(IntPtr nativePtr, bool byRef); - internal delegate void MarshalToNativeDelegate(object managedObject, IntPtr nativePtr); - - internal delegate void MarshalToNativeFieldDelegate(FieldInfo field, object fieldOwner, IntPtr nativePtr, out int fieldOffset); + internal delegate void MarshalToNativeFieldDelegate(FieldInfo field, int fieldOffset, object fieldOwner, IntPtr nativePtr, out int fieldSize); internal static ConcurrentDictionary toManagedMarshallers = new ConcurrentDictionary(1, 3); internal static ConcurrentDictionary toNativeMarshallers = new ConcurrentDictionary(1, 3); @@ -333,7 +405,7 @@ namespace FlaxEngine.Interop deleg(managedObject, nativePtr); } - internal static MarshalToNativeFieldDelegate GetToNativeFieldMarshallerDelegate(Type type) + internal static MarshalToNativeFieldDelegate GetToNativeFieldMarshallerDelegate(FieldInfo field, Type type) { static MarshalToNativeFieldDelegate Factory(Type type) { @@ -350,9 +422,46 @@ namespace FlaxEngine.Interop return toNativeFieldMarshallers.GetOrAdd(type, Factory); } - internal static void MarshalToNativeField(FieldInfo field, object fieldOwner, IntPtr nativePtr, out int fieldOffset) + internal static class FieldHelper { - GetToNativeFieldMarshallerDelegate(fieldOwner.GetType())(field, fieldOwner, nativePtr, out fieldOffset); + /// + /// Returns the address of the field, relative to field owner. + /// + internal static int GetFieldOffset(FieldInfo field, Type type) + { + // Get the address of the field, source: https://stackoverflow.com/a/56512720 + int fieldOffset = Unsafe.Read((field.FieldHandle.Value + 4 + IntPtr.Size).ToPointer()) & 0xFFFFFF; + if (!type.IsValueType) + fieldOffset += IntPtr.Size; + return fieldOffset; + } + + /// + /// Returns a reference to the value of the field. + /// + internal static ref TField GetReferenceTypeFieldReference(int fieldOffset, ref object fieldOwner) + { + byte* fieldPtr = (byte*)Unsafe.As(ref fieldOwner) + fieldOffset; + return ref Unsafe.AsRef(fieldPtr); + } + + /// + /// Returns a reference to the value of the field. + /// + internal static ref TField GetValueTypeFieldReference(int fieldOffset, ref T fieldOwner) //where T : struct + { + byte* fieldPtr = (byte*)Unsafe.AsPointer(ref fieldOwner) + fieldOffset; + return ref Unsafe.AsRef(fieldPtr); + } + + /// + /// Returns a reference to the value of the field. + /// + internal static ref TField GetReferenceTypeFieldReference(int fieldOffset, ref T fieldOwner) //where T : class + { + byte* fieldPtr = (byte*)Unsafe.As(ref fieldOwner) + fieldOffset; + return ref Unsafe.AsRef(fieldPtr); + } } /// @@ -361,12 +470,12 @@ namespace FlaxEngine.Interop internal static class MarshalHelper { private delegate void MarshalToNativeTypedDelegate(ref T managedValue, IntPtr nativePtr); - private delegate void MarshalToManagedTypedDelegate(ref T managedValue, IntPtr nativePtr, bool byRef); - - internal delegate void MarshalFieldTypedDelegate(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset); + internal delegate void MarshalFieldTypedDelegate(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize); + internal delegate void* GetBasePointer(ref T fieldOwner); internal static FieldInfo[] marshallableFields; + internal static int[] marshallableFieldOffsets; internal static MarshalFieldTypedDelegate[] toManagedFieldMarshallers; internal static MarshalFieldTypedDelegate[] toNativeFieldMarshallers; @@ -377,28 +486,9 @@ namespace FlaxEngine.Interop { Type type = typeof(T); - // Setup marshallers for managed and native directions - MethodInfo toManagedMethod; - if (type.IsValueType) - toManagedMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type).GetMethod(nameof(MarshalHelperValueType.ToManaged), BindingFlags.Static | BindingFlags.NonPublic); - else if (type.IsArray && type.GetElementType().IsValueType) - toManagedMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type.GetElementType()).GetMethod(nameof(MarshalHelperValueType.ToManagedArray), BindingFlags.Static | BindingFlags.NonPublic); - else if (type.IsArray && !type.GetElementType().IsValueType) - toManagedMethod = typeof(MarshalHelperReferenceType<>).MakeGenericType(type.GetElementType()).GetMethod(nameof(MarshalHelperReferenceType.ToManagedArray), BindingFlags.Static | BindingFlags.NonPublic); - else - toManagedMethod = typeof(MarshalHelperReferenceType<>).MakeGenericType(type).GetMethod(nameof(MarshalHelperReferenceType.ToManaged), BindingFlags.Static | BindingFlags.NonPublic); - toManagedTypedMarshaller = toManagedMethod.CreateDelegate(); - - MethodInfo toNativeMethod; - if (type.IsValueType) - toNativeMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type).GetMethod(nameof(MarshalHelperValueType.ToNative), BindingFlags.Static | BindingFlags.NonPublic); - else - toNativeMethod = typeof(MarshalHelperReferenceType<>).MakeGenericType(type).GetMethod(nameof(MarshalHelperReferenceType.ToNative), BindingFlags.Static | BindingFlags.NonPublic); - toNativeTypedMarshaller = toNativeMethod.CreateDelegate(); - + // Setup field-by-field marshallers for reference types or structures containing references if (!type.IsPrimitive && !type.IsPointer && type != typeof(bool)) { - // Setup field-by-field marshallers for reference types or structures containing references marshallableFields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (type.IsValueType && !marshallableFields.Any(x => (x.FieldType.IsClass && !x.FieldType.IsPointer) || x.FieldType.Name == "Boolean")) marshallableFields = null; @@ -409,6 +499,7 @@ namespace FlaxEngine.Interop { toManagedFieldMarshallers = new MarshalFieldTypedDelegate[marshallableFields.Length]; toNativeFieldMarshallers = new MarshalFieldTypedDelegate[marshallableFields.Length]; + marshallableFieldOffsets = new int[marshallableFields.Length]; BindingFlags bindingFlags = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public; for (int i = 0; i < marshallableFields.Length; i++) { @@ -419,8 +510,16 @@ namespace FlaxEngine.Interop if (fieldType.IsPointer) { - toManagedFieldMethod = typeof(MarshalHelper<>).MakeGenericType(type).GetMethod(nameof(MarshalHelper.ToManagedFieldPointer), bindingFlags); - toNativeFieldMethod = typeof(MarshalHelper<>).MakeGenericType(type).GetMethod(nameof(MarshalHelper.ToNativeFieldPointer), bindingFlags); + if (type.IsValueType) + { + toManagedFieldMethod = typeof(MarshalHelper<>).MakeGenericType(type).GetMethod(nameof(MarshalHelper.ToManagedFieldPointerValueType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>).MakeGenericType(type).GetMethod(nameof(MarshalHelper.ToNativeFieldPointerValueType), bindingFlags); + } + else + { + toManagedFieldMethod = typeof(MarshalHelper<>).MakeGenericType(type).GetMethod(nameof(MarshalHelper.ToManagedFieldPointerReferenceType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>).MakeGenericType(type).GetMethod(nameof(MarshalHelper.ToNativeFieldPointerReferenceType), bindingFlags); + } } else if (fieldType.IsValueType) { @@ -431,8 +530,16 @@ namespace FlaxEngine.Interop } else { - toManagedFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToManagedField), bindingFlags); - toNativeFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToNativeField), bindingFlags); + if (type.IsValueType) + { + toManagedFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToManagedFieldValueType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToNativeFieldValueType), bindingFlags); + } + else + { + toManagedFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToManagedFieldReferenceType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToNativeFieldReferenceType), bindingFlags); + } } } else if (fieldType.IsArray) @@ -440,25 +547,124 @@ namespace FlaxEngine.Interop Type arrayElementType = fieldType.GetElementType(); if (arrayElementType.IsValueType) { - toManagedFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, arrayElementType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToManagedFieldArray), bindingFlags); - toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeField), bindingFlags); + if (type.IsValueType) + { + toManagedFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, arrayElementType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToManagedFieldArrayValueType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeFieldValueType), bindingFlags); + } + else + { + toManagedFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, arrayElementType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToManagedFieldArrayReferenceType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeFieldReferenceType), bindingFlags); + } } else { - toManagedFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, arrayElementType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToManagedFieldArray), bindingFlags); - toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeField), bindingFlags); + if (type.IsValueType) + { + toManagedFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, arrayElementType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToManagedFieldArrayValueType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeFieldValueType), bindingFlags); + } + else + { + toManagedFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, arrayElementType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToManagedFieldArrayReferenceType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeFieldReferenceType), bindingFlags); + } } } else { - toManagedFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToManagedField), bindingFlags); - toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeField), bindingFlags); + if (type.IsValueType) + { + toManagedFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToManagedFieldValueType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeFieldValueType), bindingFlags); + } + else + { + toManagedFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToManagedFieldReferenceType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeFieldReferenceType), bindingFlags); + } } toManagedFieldMarshallers[i] = toManagedFieldMethod.CreateDelegate(); toNativeFieldMarshallers[i] = toNativeFieldMethod.CreateDelegate(); + marshallableFieldOffsets[i] = FieldHelper.GetFieldOffset(field, type); } } } + + // Setup marshallers for managed and native directions + MethodInfo toManagedMethod; + if (type.IsValueType) + { + string methodName; + if (type == typeof(IntPtr)) + methodName = nameof(MarshalHelperValueType.ToManagedPointer); + else if (type == typeof(ManagedHandle)) + methodName = nameof(MarshalHelperValueType.ToManagedHandle); + else if (marshallableFields != null) + methodName = nameof(MarshalHelperValueType.ToManagedWithMarshallableFields); + else + methodName = nameof(MarshalHelperValueType.ToManaged); + toManagedMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type).GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic); + } + else if (type.IsArray) + { + Type elementType = type.GetElementType(); + if (elementType.IsValueType) + { + string methodName; + if (ArrayFactory.GetMarshalledType(elementType) == elementType) + methodName = nameof(MarshalHelperValueType.ToManagedArray); + else + methodName = nameof(MarshalHelperValueType.ToManagedArrayMarshalled); + toManagedMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type.GetElementType()).GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic); + } + else + toManagedMethod = typeof(MarshalHelperReferenceType<>).MakeGenericType(type.GetElementType()).GetMethod(nameof(MarshalHelperReferenceType.ToManagedArray), BindingFlags.Static | BindingFlags.NonPublic); + } + else + { + string methodName; + if (type == typeof(string)) + methodName = nameof(MarshalHelperReferenceType.ToManagedString); + else if (type == typeof(Type)) + methodName = nameof(MarshalHelperReferenceType.ToManagedType); + else if (type.IsClass) + methodName = nameof(MarshalHelperReferenceType.ToManagedClass); + else if (type.IsInterface) // Dictionary + methodName = nameof(MarshalHelperReferenceType.ToManagedInterface); + else + throw new NativeInteropException($"Unsupported type '{type.FullName}'"); + toManagedMethod = typeof(MarshalHelperReferenceType<>).MakeGenericType(type).GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic); + } + toManagedTypedMarshaller = toManagedMethod.CreateDelegate(); + + MethodInfo toNativeMethod; + if (type.IsValueType) + { + if (type.IsByRef) + throw new NotImplementedException(); // Is this possible? + if (marshallableFields != null) + toNativeMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type).GetMethod(nameof(MarshalHelperValueType.ToNativeWithMarshallableFields), BindingFlags.Static | BindingFlags.NonPublic); + else + toNativeMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type).GetMethod(nameof(MarshalHelperValueType.ToNative), BindingFlags.Static | BindingFlags.NonPublic); + } + else + { + string methodName; + if (type == typeof(string)) + methodName = nameof(MarshalHelperReferenceType.ToNativeString); + else if (type == typeof(Type)) + methodName = nameof(MarshalHelperReferenceType.ToNativeType); + else if (type.IsPointer) + methodName = nameof(MarshalHelperReferenceType.ToNativePointer); + else if (type.IsArray) + methodName = nameof(MarshalHelperReferenceType.ToNativeArray); + else + methodName = nameof(MarshalHelperReferenceType.ToNative); + toNativeMethod = typeof(MarshalHelperReferenceType<>).MakeGenericType(type).GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic); + } + toNativeTypedMarshaller = toNativeMethod.CreateDelegate(); } internal static object ToManagedWrapper(IntPtr nativePtr, bool byRef) @@ -476,16 +682,12 @@ namespace FlaxEngine.Interop [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static T ToManagedUnbox(IntPtr nativePtr) { - T managed = default; - if (nativePtr != IntPtr.Zero) - { - Type type = typeof(T); - if (type.IsArray) - managed = (T)MarshalToManaged(nativePtr, type); // Array might be in internal format of custom structs so unbox if need to - else - managed = (T)ManagedHandle.FromIntPtr(nativePtr).Target; - } - return managed; + T value = default; + if (nativePtr == IntPtr.Zero) + return value; + + MarshalHelper.ToManaged(ref value, nativePtr, false); + return value; } internal static Array ToManagedArray(Span ptrSpan) @@ -513,52 +715,48 @@ namespace FlaxEngine.Interop toNativeTypedMarshaller(ref managedValue, nativePtr); } - internal static void ToNativeField(FieldInfo field, ref T fieldOwner, IntPtr nativePtr, out int fieldOffset) + internal static void ToNativeField(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativePtr, out int fieldSize) { if (marshallableFields != null) { for (int i = 0; i < marshallableFields.Length; i++) { - if (marshallableFields[i] == field) + if (marshallableFieldOffsets[i] == fieldOffset) { - toNativeFieldMarshallers[i](marshallableFields[i], ref fieldOwner, nativePtr, out fieldOffset); + toNativeFieldMarshallers[i](field, fieldOffset, ref fieldOwner, nativePtr, out fieldSize); return; } } } - throw new NativeInteropException($"Invalid field {field.Name} to marshal for type {typeof(T).Name}"); + throw new NativeInteropException($"Invalid field with offset {fieldOffset} to marshal for type {typeof(T).Name}"); } - private static void ToManagedFieldPointer(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + private static void ToManagedFieldPointerValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { - ref IntPtr fieldValueRef = ref GetFieldReference(field, ref fieldOwner); - fieldValueRef = Unsafe.Read(fieldPtr.ToPointer()); - fieldOffset = IntPtr.Size; + ref IntPtr fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); + fieldValueRef = Unsafe.Read(nativeFieldPtr.ToPointer()); + fieldSize = IntPtr.Size; } - private static void ToNativeFieldPointer(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + private static void ToManagedFieldPointerReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { - ref IntPtr fieldValueRef = ref GetFieldReference(field, ref fieldOwner); - Unsafe.Write(fieldPtr.ToPointer(), fieldValueRef); - fieldOffset = IntPtr.Size; + ref IntPtr fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); + fieldValueRef = Unsafe.Read(nativeFieldPtr.ToPointer()); + fieldSize = IntPtr.Size; } - /// - /// Returns a reference to the value of the field. - /// - private static ref TField GetFieldReference(FieldInfo field, ref T fieldOwner) + private static void ToNativeFieldPointerValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { - // Get the address of the field, source: https://stackoverflow.com/a/56512720 - if (typeof(T).IsValueType) - { - byte* fieldPtr = (byte*)Unsafe.AsPointer(ref fieldOwner) + (Marshal.ReadInt32(field.FieldHandle.Value + 4 + IntPtr.Size) & 0xFFFFFF); - return ref Unsafe.AsRef(fieldPtr); - } - else - { - byte* fieldPtr = (byte*)Unsafe.As(ref fieldOwner) + IntPtr.Size + (Marshal.ReadInt32(field.FieldHandle.Value + 4 + IntPtr.Size) & 0xFFFFFF); - return ref Unsafe.AsRef(fieldPtr); - } + ref IntPtr fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); + Unsafe.Write(nativeFieldPtr.ToPointer(), fieldValueRef); + fieldSize = IntPtr.Size; + } + + private static void ToNativeFieldPointerReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class + { + ref IntPtr fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); + Unsafe.Write(nativeFieldPtr.ToPointer(), fieldValueRef); + fieldSize = IntPtr.Size; } private static IntPtr EnsureAlignment(IntPtr ptr, int alignment) @@ -589,48 +787,92 @@ namespace FlaxEngine.Interop fieldAlignment = GetTypeSize(fieldType); } - internal static void ToManagedField(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + internal static void ToManagedFieldValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { - fieldOffset = Unsafe.SizeOf(); + fieldSize = Unsafe.SizeOf(); if (fieldAlignment > 1) { - IntPtr fieldStartPtr = fieldPtr; - fieldPtr = EnsureAlignment(fieldPtr, fieldAlignment); - fieldOffset += (fieldPtr - fieldStartPtr).ToInt32(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, fieldAlignment); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); } - ref TField fieldValueRef = ref GetFieldReference(field, ref fieldOwner); - MarshalHelperValueType.ToManaged(ref fieldValueRef, fieldPtr, false); + ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToManaged(ref fieldValueRef, nativeFieldPtr, false); } - internal static void ToManagedFieldArray(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + internal static void ToManagedFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { - // Follows the same marshalling semantics with reference types - fieldOffset = Unsafe.SizeOf(); - IntPtr fieldStartPtr = fieldPtr; - fieldPtr = EnsureAlignment(fieldPtr, IntPtr.Size); - fieldOffset += (fieldPtr - fieldStartPtr).ToInt32(); - - ref TField[] fieldValueRef = ref GetFieldReference(field, ref fieldOwner); - MarshalHelperValueType.ToManagedArray(ref fieldValueRef, Unsafe.Read(fieldPtr.ToPointer()), false); - } - - internal static void ToNativeField(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) - { - fieldOffset = Unsafe.SizeOf(); + fieldSize = Unsafe.SizeOf(); if (fieldAlignment > 1) { - IntPtr startPtr = fieldPtr; - fieldPtr = EnsureAlignment(fieldPtr, fieldAlignment); - fieldOffset += (fieldPtr - startPtr).ToInt32(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, fieldAlignment); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); + } + + ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToManaged(ref fieldValueRef, nativeFieldPtr, false); + } + + internal static void ToManagedFieldArrayValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct + { + // Follows the same marshalling semantics with reference types + fieldSize = Unsafe.SizeOf(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); + + ref TField[] fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); + } + + internal static void ToManagedFieldArrayReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class + { + // Follows the same marshalling semantics with reference types + fieldSize = Unsafe.SizeOf(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); + + ref TField[] fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); + } + + internal static void ToNativeFieldValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct + { + fieldSize = Unsafe.SizeOf(); + if (fieldAlignment > 1) + { + IntPtr startPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, fieldAlignment); + fieldSize += (nativeFieldPtr - startPtr).ToInt32(); } #if USE_AOT TField fieldValueRef = (TField)field.GetValue(fieldOwner); #else - ref TField fieldValueRef = ref GetFieldReference(field, ref fieldOwner); + ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); #endif - MarshalHelperValueType.ToNative(ref fieldValueRef, fieldPtr); + MarshalHelper.ToNative(ref fieldValueRef, nativeFieldPtr); + } + + internal static void ToNativeFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class + { + fieldSize = Unsafe.SizeOf(); + if (fieldAlignment > 1) + { + IntPtr startPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, fieldAlignment); + fieldSize += (nativeFieldPtr - startPtr).ToInt32(); + } + +#if USE_AOT + TField fieldValueRef = (TField)field.GetValue(fieldOwner); +#else + ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); +#endif + MarshalHelper.ToNative(ref fieldValueRef, nativeFieldPtr); } } @@ -640,50 +882,83 @@ namespace FlaxEngine.Interop { } - internal static void ToManagedField(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + internal static void ToManagedField(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) { - fieldOffset = 0; + fieldSize = 0; } - internal static void ToNativeField(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + internal static void ToNativeField(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) { - fieldOffset = 0; + fieldSize = 0; } } private static class ReferenceTypeField where TField : class { - internal static void ToManagedField(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + internal static void ToManagedFieldValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { - fieldOffset = Unsafe.SizeOf(); - IntPtr fieldStartPtr = fieldPtr; - fieldPtr = EnsureAlignment(fieldPtr, IntPtr.Size); - fieldOffset += (fieldPtr - fieldStartPtr).ToInt32(); + fieldSize = Unsafe.SizeOf(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); - ref TField fieldValueRef = ref GetFieldReference(field, ref fieldOwner); - MarshalHelperReferenceType.ToManaged(ref fieldValueRef, Unsafe.Read(fieldPtr.ToPointer()), false); + ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); } - internal static void ToManagedFieldArray(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + internal static void ToManagedFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { - fieldOffset = Unsafe.SizeOf(); - IntPtr fieldStartPtr = fieldPtr; - fieldPtr = EnsureAlignment(fieldPtr, IntPtr.Size); - fieldOffset += (fieldPtr - fieldStartPtr).ToInt32(); + fieldSize = Unsafe.SizeOf(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); - ref TField[] fieldValueRef = ref GetFieldReference(field, ref fieldOwner); - MarshalHelperReferenceType.ToManagedArray(ref fieldValueRef, Unsafe.Read(fieldPtr.ToPointer()), false); + ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); } - internal static void ToNativeField(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + internal static void ToManagedFieldArrayValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { - fieldOffset = Unsafe.SizeOf(); - IntPtr fieldStartPtr = fieldPtr; - fieldPtr = EnsureAlignment(fieldPtr, IntPtr.Size); - fieldOffset += (fieldPtr - fieldStartPtr).ToInt32(); + fieldSize = Unsafe.SizeOf(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); - ref TField fieldValueRef = ref GetFieldReference(field, ref fieldOwner); - MarshalHelperReferenceType.ToNative(ref fieldValueRef, fieldPtr); + ref TField[] fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); + } + + internal static void ToManagedFieldArrayReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class + { + fieldSize = Unsafe.SizeOf(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); + + ref TField[] fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); + } + + internal static void ToNativeFieldValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct + { + fieldSize = Unsafe.SizeOf(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); + + ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToNative(ref fieldValueRef, nativeFieldPtr); + } + + internal static void ToNativeFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class + { + fieldSize = Unsafe.SizeOf(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); + + ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToNative(ref fieldValueRef, nativeFieldPtr); } } } @@ -692,80 +967,92 @@ namespace FlaxEngine.Interop { internal static void ToNativeWrapper(object managedObject, IntPtr nativePtr) { - ToNative(ref Unsafe.Unbox(managedObject), nativePtr); + MarshalHelper.ToNative(ref Unsafe.Unbox(managedObject), nativePtr); } - internal static void ToNativeFieldWrapper(FieldInfo field, object fieldOwner, IntPtr nativePtr, out int fieldOffset) + internal static void ToNativeFieldWrapper(FieldInfo field, int fieldOffset, object fieldOwner, IntPtr nativePtr, out int fieldSize) { - MarshalHelper.ToNativeField(field, ref Unsafe.Unbox(fieldOwner), nativePtr, out fieldOffset); + MarshalHelper.ToNativeField(field, fieldOffset, ref Unsafe.Unbox(fieldOwner), nativePtr, out fieldSize); + } + + internal static void ToManagedPointer(ref IntPtr managedValue, IntPtr nativePtr, bool byRef) + { + Type type = typeof(T); + byRef |= type.IsByRef; // Is this needed? + if (type.IsByRef) + Assert.IsTrue(type.GetElementType().IsValueType); + managedValue = byRef ? nativePtr : Unsafe.Read(nativePtr.ToPointer()); + } + + internal static void ToManagedHandle(ref ManagedHandle managedValue, IntPtr nativePtr, bool byRef) + { + managedValue = ManagedHandle.FromIntPtr(nativePtr); + } + + internal static void ToManagedWithMarshallableFields(ref T managedValue, IntPtr nativePtr, bool byRef) + { + IntPtr fieldPtr = nativePtr; + var fields = MarshalHelper.marshallableFields; + var offsets = MarshalHelper.marshallableFieldOffsets; + var marshallers = MarshalHelper.toManagedFieldMarshallers; + for (int i = 0; i < fields.Length; i++) + { + marshallers[i](fields[i], offsets[i], ref managedValue, fieldPtr, out int fieldSize); + fieldPtr += fieldSize; + } + Assert.IsTrue((fieldPtr - nativePtr) <= Unsafe.SizeOf()); } internal static void ToManaged(ref T managedValue, IntPtr nativePtr, bool byRef) { - Type type = typeof(T); - byRef |= type.IsByRef; - if (byRef) - { - if (type.IsByRef) - type = type.GetElementType(); - Assert.IsTrue(type.IsValueType); - } - - if (type == typeof(IntPtr) && byRef) - managedValue = (T)(object)nativePtr; - else if (type == typeof(ManagedHandle)) - managedValue = (T)(object)ManagedHandle.FromIntPtr(nativePtr); - else if (MarshalHelper.marshallableFields != null) - { - IntPtr fieldPtr = nativePtr; - for (int i = 0; i < MarshalHelper.marshallableFields.Length; i++) - { - MarshalHelper.toManagedFieldMarshallers[i](MarshalHelper.marshallableFields[i], ref managedValue, fieldPtr, out int fieldOffset); - fieldPtr += fieldOffset; - } - Assert.IsTrue((fieldPtr - nativePtr) <= Unsafe.SizeOf()); - } - else - managedValue = Unsafe.Read(nativePtr.ToPointer()); + managedValue = Unsafe.Read(nativePtr.ToPointer()); } internal static void ToManagedArray(ref T[] managedValue, IntPtr nativePtr, bool byRef) { if (byRef) - nativePtr = Marshal.ReadIntPtr(nativePtr); + nativePtr = Unsafe.Read(nativePtr.ToPointer()); - Type elementType = typeof(T); if (nativePtr != IntPtr.Zero) { ManagedArray managedArray = Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); - if (ArrayFactory.GetMarshalledType(elementType) == elementType) - managedValue = Unsafe.As(managedArray.ToArray()); - else if (elementType.IsValueType) - managedValue = Unsafe.As(MarshalHelper.ToManagedArray(managedArray)); - else - managedValue = Unsafe.As(MarshalHelper.ToManagedArray(managedArray.ToSpan())); + managedValue = Unsafe.As(managedArray.ToArray()); } else managedValue = null; } - internal static void ToNative(ref T managedValue, IntPtr nativePtr) + internal static void ToManagedArrayMarshalled(ref T[] managedValue, IntPtr nativePtr, bool byRef) { - if (typeof(T).IsByRef) - throw new NotImplementedException(); + if (byRef) + nativePtr = Unsafe.Read(nativePtr.ToPointer()); - if (MarshalHelper.marshallableFields != null) + if (nativePtr != IntPtr.Zero) { - IntPtr fieldPtr = nativePtr; - for (int i = 0; i < MarshalHelper.marshallableFields.Length; i++) - { - MarshalHelper.toNativeFieldMarshallers[i](MarshalHelper.marshallableFields[i], ref managedValue, nativePtr, out int fieldOffset); - nativePtr += fieldOffset; - } - Assert.IsTrue((nativePtr - fieldPtr) <= Unsafe.SizeOf()); + ManagedArray managedArray = Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); + managedValue = Unsafe.As(MarshalHelper.ToManagedArray(managedArray)); } else - Unsafe.AsRef(nativePtr.ToPointer()) = managedValue; + managedValue = null; + } + + internal static void ToNativeWithMarshallableFields(ref T managedValue, IntPtr nativePtr) + { + IntPtr fieldPtr = nativePtr; + var fields = MarshalHelper.marshallableFields; + var offsets = MarshalHelper.marshallableFieldOffsets; + var marshallers = MarshalHelper.toNativeFieldMarshallers; + for (int i = 0; i < MarshalHelper.marshallableFields.Length; i++) + { + marshallers[i](fields[i], offsets[i], ref managedValue, nativePtr, out int fieldSize); + nativePtr += fieldSize; + } + Assert.IsTrue((nativePtr - fieldPtr) <= Unsafe.SizeOf()); + } + + internal static void ToNative(ref T managedValue, IntPtr nativePtr) + { + Unsafe.AsRef(nativePtr.ToPointer()) = managedValue; } } @@ -774,37 +1061,47 @@ namespace FlaxEngine.Interop internal static void ToNativeWrapper(object managedObject, IntPtr nativePtr) { T managedValue = Unsafe.As(managedObject); - ToNative(ref managedValue, nativePtr); + MarshalHelper.ToNative(ref managedValue, nativePtr); } - internal static void ToNativeFieldWrapper(FieldInfo field, object managedObject, IntPtr nativePtr, out int fieldOffset) + internal static void ToNativeFieldWrapper(FieldInfo field, int fieldOffset, object fieldOwner, IntPtr nativePtr, out int fieldSize) { - T managedValue = Unsafe.As(managedObject); - MarshalHelper.ToNativeField(field, ref managedValue, nativePtr, out fieldOffset); + T managedValue = Unsafe.As(fieldOwner); + MarshalHelper.ToNativeField(field, fieldOffset, ref managedValue, nativePtr, out fieldSize); } - internal static void ToManaged(ref T managedValue, IntPtr nativePtr, bool byRef) + internal static void ToManagedString(ref string managedValue, IntPtr nativePtr, bool byRef) { - Type type = typeof(T); if (byRef) - nativePtr = Marshal.ReadIntPtr(nativePtr); + nativePtr = Unsafe.Read(nativePtr.ToPointer()); + managedValue = ManagedString.ToManaged(nativePtr); + } - if (type == typeof(string)) - managedValue = Unsafe.As(ManagedString.ToManaged(nativePtr)); - else if (nativePtr == IntPtr.Zero) - managedValue = null; - else if (type.IsClass) - managedValue = Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); - else if (type.IsInterface) // Dictionary - managedValue = Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); - else - throw new NotImplementedException(); + internal static void ToManagedType(ref Type managedValue, IntPtr nativePtr, bool byRef) + { + if (byRef) + nativePtr = Unsafe.Read(nativePtr.ToPointer()); + managedValue = nativePtr == IntPtr.Zero ? null : Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); + } + + internal static void ToManagedClass(ref T managedValue, IntPtr nativePtr, bool byRef) + { + if (byRef) + nativePtr = Unsafe.Read(nativePtr.ToPointer()); + managedValue = nativePtr == IntPtr.Zero ? null : Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); + } + + internal static void ToManagedInterface(ref T managedValue, IntPtr nativePtr, bool byRef) // Dictionary + { + if (byRef) + nativePtr = Unsafe.Read(nativePtr.ToPointer()); + managedValue = nativePtr == IntPtr.Zero ? null : Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); } internal static void ToManagedArray(ref T[] managedValue, IntPtr nativePtr, bool byRef) { if (byRef) - nativePtr = Marshal.ReadIntPtr(nativePtr); + nativePtr = Unsafe.Read(nativePtr.ToPointer()); if (nativePtr != IntPtr.Zero) { @@ -815,56 +1112,64 @@ namespace FlaxEngine.Interop managedValue = null; } + + internal static void ToNativeString(ref string managedValue, IntPtr nativePtr) + { + Unsafe.Write(nativePtr.ToPointer(), ManagedString.ToNativeWeak(managedValue)); + } + + internal static void ToNativeType(ref Type managedValue, IntPtr nativePtr) + { + Unsafe.Write(nativePtr.ToPointer(), managedValue != null ? ManagedHandle.ToIntPtr(GetTypeManagedHandle(managedValue)) : IntPtr.Zero); + } + + internal static void ToNativePointer(ref T managedValue, IntPtr nativePtr) + { + IntPtr managedPtr; + if (Pointer.Unbox(managedValue) == null) + managedPtr = IntPtr.Zero; + else if (managedValue is FlaxEngine.Object obj) + managedPtr = FlaxEngine.Object.GetUnmanagedPtr(obj); + else + managedPtr = ManagedHandle.ToIntPtr(managedValue, GCHandleType.Weak); + Unsafe.Write(nativePtr.ToPointer(), managedPtr); + } + + internal static void ToNativeArray(ref T managedValue, IntPtr nativePtr) + { + IntPtr managedPtr; + if (managedValue == null) + managedPtr = IntPtr.Zero; + else + { + Type type = typeof(T); + var elementType = type.GetElementType(); + var arr = Unsafe.As(managedValue); + var marshalledType = ArrayFactory.GetMarshalledType(elementType); + ManagedArray managedArray; + if (marshalledType == elementType) + managedArray = ManagedArray.WrapNewArray(arr, type); + else if (elementType.IsValueType) + { + // Convert array of custom structures into internal native layout + managedArray = ManagedArray.AllocateNewArray(arr.Length, type, marshalledType); + IntPtr managedArrayPtr = managedArray.Pointer; + for (int i = 0; i < arr.Length; i++) + { + MarshalToNative(arr.GetValue(i), managedArrayPtr, elementType); + managedArrayPtr += managedArray.ElementSize; + } + } + else + managedArray = ManagedArrayToGCHandleWrappedArray(arr); + managedPtr = ManagedHandle.ToIntPtr(managedArray, GCHandleType.Weak); + } + Unsafe.Write(nativePtr.ToPointer(), managedPtr); + } + internal static void ToNative(ref T managedValue, IntPtr nativePtr) { - Type type = typeof(T); - - IntPtr managedPtr; - if (type == typeof(string)) - managedPtr = ManagedString.ToNativeWeak(managedValue as string); - else if (type.IsPointer) - { - if (Pointer.Unbox(managedValue) == null) - managedPtr = IntPtr.Zero; - else if (managedValue is FlaxEngine.Object flaxObj) - managedPtr = FlaxEngine.Object.GetUnmanagedPtr(flaxObj); - else - managedPtr = ManagedHandle.ToIntPtr(managedValue, GCHandleType.Weak); - } - else if (type == typeof(Type)) - managedPtr = managedValue != null ? ManagedHandle.ToIntPtr(GetTypeGCHandle((Type)(object)managedValue)) : IntPtr.Zero; - else if (type.IsArray) - { - if (managedValue == null) - managedPtr = IntPtr.Zero; - else - { - var elementType = type.GetElementType(); - var arr = Unsafe.As(managedValue); - var marshalledType = ArrayFactory.GetMarshalledType(elementType); - ManagedArray managedArray; - if (marshalledType == elementType) - managedArray = ManagedArray.WrapNewArray(arr, type); - else if (elementType.IsValueType) - { - // Convert array of custom structures into internal native layout - managedArray = ManagedArray.AllocateNewArray(arr.Length, type, marshalledType); - IntPtr managedArrayPtr = managedArray.Pointer; - for (int i = 0; i < arr.Length; i++) - { - MarshalToNative(arr.GetValue(i), managedArrayPtr, elementType); - managedArrayPtr += managedArray.ElementSize; - } - } - else - managedArray = ManagedArrayToGCHandleWrappedArray(arr); - managedPtr = ManagedHandle.ToIntPtr(managedArray, GCHandleType.Weak); - } - } - else - managedPtr = managedValue != null ? ManagedHandle.ToIntPtr(managedValue, GCHandleType.Weak) : IntPtr.Zero; - - Unsafe.Write(nativePtr.ToPointer(), managedPtr); + Unsafe.Write(nativePtr.ToPointer(), managedValue != null ? ManagedHandle.ToIntPtr(managedValue, GCHandleType.Weak) : IntPtr.Zero); } } @@ -955,14 +1260,82 @@ namespace FlaxEngine.Interop { internal FieldInfo field; internal MarshalToNativeFieldDelegate toNativeMarshaller; + internal int fieldOffset; internal FieldHolder(FieldInfo field, Type type) { this.field = field; - toNativeMarshaller = GetToNativeFieldMarshallerDelegate(type); + toNativeMarshaller = GetToNativeFieldMarshallerDelegate(field, type); + fieldOffset = FieldHelper.GetFieldOffset(field, type); } } + internal class TypeComparer : IEqualityComparer + { + public bool Equals(Type x, Type y) => x == y; + public int GetHashCode(Type obj) => obj.GetHashCode(); + } + + internal class TypeHolder + { + internal Type type; + internal Type wrappedType; + internal ConstructorInfo ctor; + internal IntPtr managedClassPointer; // MClass* + + internal TypeHolder(Type type) + { + this.type = type; + wrappedType = type; + + if (type.IsAbstract) + { + // Dotnet doesn't allow to instantiate abstract type thus allow to use generated mock class usage (eg. for Script or GPUResource) for generated abstract types + var abstractWrapper = type.GetNestedType("AbstractWrapper", BindingFlags.NonPublic); + if (abstractWrapper != null) + wrappedType = abstractWrapper; + } + + ctor = wrappedType.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null); + } + + internal object CreateObject() + { + return RuntimeHelpers.GetUninitializedObject(wrappedType); + } + + internal object CreateScriptingObject(IntPtr unmanagedPtr, IntPtr idPtr) + { + object obj = CreateObject(); + if (obj is Object) + { + { + ref IntPtr fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(unmanagedPtrFieldOffset, ref obj); + fieldRef = unmanagedPtr; + } + + if (idPtr != IntPtr.Zero) + { + ref Guid nativeId = ref Unsafe.AsRef(idPtr.ToPointer()); + ref Guid fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(internalIdFieldOffset, ref obj); + fieldRef = nativeId; + } + } + + if (ctor != null) + ctor.Invoke(obj, null); + else + Debug.LogException(new Exception($"Missing empty constructor in type '{wrappedType}'.")); + + return obj; + } + + public static implicit operator Type(TypeHolder holder) => holder?.type ?? null; + public bool Equals(TypeHolder other) => type == other.type; + public bool Equals(Type other) => type == other; + public override int GetHashCode() => type.GetHashCode(); + } + internal static class ArrayFactory { private delegate Array CreateArrayDelegate(long size); @@ -1024,11 +1397,12 @@ namespace FlaxEngine.Interop private static uint pinnedBoxedValuesPointer = 0; private static (IntPtr ptr, int size)[] pinnedAllocations = new (IntPtr ptr, int size)[256]; private static uint pinnedAllocationsPointer = 0; - + private delegate TInternal ToNativeDelegate(T value); + private delegate IntPtr UnboxerDelegate(object value, object converter); - private static ConcurrentDictionary unboxers = new (1, 3); + private static ConcurrentDictionary unboxers = new(1, 3); private static MethodInfo unboxerMethod = typeof(ValueTypeUnboxer).GetMethod(nameof(ValueTypeUnboxer.UnboxPointer), BindingFlags.Static | BindingFlags.NonPublic); private static MethodInfo unboxerToNativeMethod = typeof(ValueTypeUnboxer).GetMethod(nameof(ValueTypeUnboxer.UnboxPointerWithConverter), BindingFlags.Static | BindingFlags.NonPublic); @@ -1089,7 +1463,8 @@ namespace FlaxEngine.Interop return new IntPtr(Unsafe.AsPointer(ref Unsafe.Unbox(value))); } - private static IntPtr UnboxPointerWithConverter(object value, object converter) where T : struct where TInternal : struct + private static IntPtr UnboxPointerWithConverter(object value, object converter) where T : struct + where TInternal : struct { ToNativeDelegate toNative = Unsafe.As>(converter); return PinValue(toNative(Unsafe.Unbox(value))); @@ -1099,12 +1474,12 @@ namespace FlaxEngine.Interop private delegate IntPtr InvokeThunkDelegate(ManagedHandle instanceHandle, IntPtr param1, IntPtr param2, IntPtr param3, IntPtr param4, IntPtr param5, IntPtr param6, IntPtr param7); /// - /// Returns all types that that owned by this assembly. + /// Returns all types owned by this assembly. /// private static Type[] GetAssemblyTypes(Assembly assembly) { var referencedAssemblies = assembly.GetReferencedAssemblies(); - var allAssemblies = AppDomain.CurrentDomain.GetAssemblies(); + var allAssemblies = Utils.GetAssemblies(); var referencedTypes = new List(); foreach (var assemblyName in referencedAssemblies) { @@ -1124,29 +1499,77 @@ namespace FlaxEngine.Interop return types; } - /// - /// Returns a static ManagedHandle for given Type, and caches it if needed. - /// - internal static ManagedHandle GetTypeGCHandle(Type type) + internal static TypeHolder GetTypeHolder(Type type) { - if (typeHandleCache.TryGetValue(type, out ManagedHandle handle)) - return handle; + if (managedTypes.TryGetValue(type, out (TypeHolder typeHolder, ManagedHandle handle) tuple)) + return tuple.typeHolder; #if FLAX_EDITOR - if (typeHandleCacheCollectible.TryGetValue(type, out handle)) - return handle; + if (managedTypesCollectible.TryGetValue(type, out tuple)) + return tuple.typeHolder; #endif + return RegisterType(type, true).typeHolder; + } - handle = ManagedHandle.Alloc(type); + internal static (TypeHolder typeHolder, ManagedHandle handle) GetTypeHolderAndManagedHandle(Type type) + { + if (managedTypes.TryGetValue(type, out (TypeHolder typeHolder, ManagedHandle handle) tuple)) + return tuple; #if FLAX_EDITOR - if (type.IsCollectible) // check if generic parameters are also collectible? - typeHandleCacheCollectible.Add(type, handle); + if (managedTypesCollectible.TryGetValue(type, out tuple)) + return tuple; +#endif + return RegisterType(type, true); + } + + /// + /// Returns a static ManagedHandle to TypeHolder for given Type, and caches it if needed. + /// + internal static ManagedHandle GetTypeManagedHandle(Type type) + { + if (managedTypes.TryGetValue(type, out (TypeHolder typeHolder, ManagedHandle handle) tuple)) + return tuple.handle; +#if FLAX_EDITOR + if (managedTypesCollectible.TryGetValue(type, out tuple)) + return tuple.handle; +#endif + return RegisterType(type, true).handle; + } + + internal static (TypeHolder typeHolder, ManagedHandle handle) RegisterType(Type type, bool registerNativeType = false) + { + // TODO: should this strip by-ref? + + (TypeHolder typeHolder, ManagedHandle handle) tuple; + tuple.typeHolder = new TypeHolder(type); + tuple.handle = ManagedHandle.Alloc(tuple.typeHolder); +#if FLAX_EDITOR + bool isCollectible = type.IsCollectible; + if (!isCollectible && type.IsGenericType && !type.Assembly.IsCollectible) + { + // The owning assembly of a generic type with type arguments referencing + // collectible assemblies must be one of the collectible assemblies. + foreach (var genericType in type.GetGenericArguments()) + { + if (genericType.Assembly.IsCollectible) + { + isCollectible = true; + break; + } + } + } + + if (isCollectible) + managedTypesCollectible.Add(type, tuple); else #endif { - typeHandleCache.Add(type, handle); + managedTypes.Add(type, tuple); } - return handle; + if (registerNativeType) + RegisterNativeClassFromType(tuple.typeHolder, tuple.handle); + + return tuple; } internal static int GetTypeSize(Type type) @@ -1290,7 +1713,7 @@ namespace FlaxEngine.Interop // Returned exception is the last parameter IntPtr exceptionPtr = nativePtrs[parameterTypes.Length]; if (exceptionPtr != IntPtr.Zero) - Marshal.WriteIntPtr(exceptionPtr, ManagedHandle.ToIntPtr(exception, GCHandleType.Weak)); + Unsafe.Write(exceptionPtr.ToPointer(), ManagedHandle.ToIntPtr(exception, GCHandleType.Weak)); return IntPtr.Zero; } return returnValue; @@ -1312,7 +1735,7 @@ namespace FlaxEngine.Interop if (type.IsByRef) { // References use indirection to support value returning - nativePtr = Marshal.ReadIntPtr(nativePtr); + nativePtr = Unsafe.Read(nativePtr.ToPointer()); type = elementType; } if (type.IsArray) @@ -1332,7 +1755,7 @@ namespace FlaxEngine.Interop // Returned exception is the last parameter IntPtr exceptionPtr = nativePtrs[numParams]; if (exceptionPtr != IntPtr.Zero) - Marshal.WriteIntPtr(exceptionPtr, ManagedHandle.ToIntPtr(exception, GCHandleType.Weak)); + Unsafe.Write(exceptionPtr.ToPointer(), ManagedHandle.ToIntPtr(exception, GCHandleType.Weak)); return IntPtr.Zero; } @@ -1346,11 +1769,11 @@ namespace FlaxEngine.Interop { type = type.GetElementType(); if (managed == null) - Marshal.WriteIntPtr(nativePtr, IntPtr.Zero); + Unsafe.Write(nativePtr.ToPointer(), IntPtr.Zero); else if (type.IsArray) MarshalToNative(managed, nativePtr, type); else - Marshal.WriteIntPtr(nativePtr, ManagedHandle.ToIntPtr(ManagedHandle.Alloc(managed, GCHandleType.Weak))); + Unsafe.Write(nativePtr.ToPointer(), ManagedHandle.ToIntPtr(ManagedHandle.Alloc(managed, GCHandleType.Weak))); } } diff --git a/Source/Engine/Engine/Screen.cpp b/Source/Engine/Engine/Screen.cpp index 2bc6e1f58..fc7e8a022 100644 --- a/Source/Engine/Engine/Screen.cpp +++ b/Source/Engine/Engine/Screen.cpp @@ -181,6 +181,11 @@ void Screen::SetGameWindowMode(GameWindowMode windowMode) #endif } +Window* Screen::GetMainWindow() +{ + return Engine::MainWindow; +} + void ScreenService::Update() { #if USE_EDITOR diff --git a/Source/Engine/Engine/Screen.h b/Source/Engine/Engine/Screen.h index 42be20a38..b7bd89c38 100644 --- a/Source/Engine/Engine/Screen.h +++ b/Source/Engine/Engine/Screen.h @@ -96,4 +96,10 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Screen); /// /// The window mode. API_PROPERTY() static void SetGameWindowMode(GameWindowMode windowMode); + + /// + /// Gets the main window. + /// + /// The current window. Will be null if fails. + API_PROPERTY() static Window* GetMainWindow(); }; diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp index b3069b4db..d8d69dc3e 100644 --- a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp @@ -76,11 +76,11 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, SpanEnvironmentProbes.Count(); i++) { const auto p = cache->EnvironmentProbes[i]; - if (p->GetSphere().Contains(drawCallOrigin) != ContainmentType::Disjoint) + if (CollisionsHelper::SphereIntersectsSphere(objectBoundsWorld, p->GetSphere())) { probe = p; break; @@ -99,10 +99,12 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, SpanPointLights.Count() && data.LocalLightsCount < MaxLocalLights; i++) { const auto& light = cache->PointLights[i]; - if (BoundingSphere(light.Position, light.Radius).Contains(drawCall.ObjectPosition) != ContainmentType::Disjoint) + if (CollisionsHelper::SphereIntersectsSphere(objectBounds, BoundingSphere(light.Position, light.Radius))) { light.SetupLightData(&data.LocalLights[data.LocalLightsCount], false); data.LocalLightsCount++; @@ -111,7 +113,7 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, SpanSpotLights.Count() && data.LocalLightsCount < MaxLocalLights; i++) { const auto& light = cache->SpotLights[i]; - if (BoundingSphere(light.Position, light.Radius).Contains(drawCall.ObjectPosition) != ContainmentType::Disjoint) + if (CollisionsHelper::SphereIntersectsSphere(objectBounds, BoundingSphere(light.Position, light.Radius))) { light.SetupLightData(&data.LocalLights[data.LocalLightsCount], false); data.LocalLightsCount++; diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index e5ba5488d..558b53b18 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -429,6 +429,7 @@ void Mesh::Draw(const RenderContext& renderContext, MaterialBase* material, cons drawCall.Material = material; drawCall.World = world; drawCall.ObjectPosition = drawCall.World.GetTranslation(); + drawCall.ObjectRadius = _sphere.Radius * drawCall.World.GetScaleVector().GetAbsolute().MaxValue(); drawCall.Surface.GeometrySize = _box.GetSize(); drawCall.Surface.PrevWorld = world; drawCall.Surface.Lightmap = nullptr; @@ -495,6 +496,7 @@ void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float drawCall.Material = material; drawCall.World = *info.World; drawCall.ObjectPosition = drawCall.World.GetTranslation(); + drawCall.ObjectRadius = info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition? drawCall.Surface.GeometrySize = _box.GetSize(); drawCall.Surface.PrevWorld = info.DrawState->PrevWorld; drawCall.Surface.Lightmap = (info.Flags & StaticFlags::Lightmap) != StaticFlags::None ? info.Lightmap : nullptr; @@ -555,6 +557,7 @@ void Mesh::Draw(const RenderContextBatch& renderContextBatch, const DrawInfo& in drawCall.Material = material; drawCall.World = *info.World; drawCall.ObjectPosition = drawCall.World.GetTranslation(); + drawCall.ObjectRadius = info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition? drawCall.Surface.GeometrySize = _box.GetSize(); drawCall.Surface.PrevWorld = info.DrawState->PrevWorld; drawCall.Surface.Lightmap = (info.Flags & StaticFlags::Lightmap) != StaticFlags::None ? info.Lightmap : nullptr; diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index b6c966508..f6d24c335 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -198,6 +198,7 @@ void SkinnedMesh::Draw(const RenderContext& renderContext, const DrawInfo& info, drawCall.Material = material; drawCall.World = *info.World; drawCall.ObjectPosition = drawCall.World.GetTranslation(); + drawCall.ObjectRadius = info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition? drawCall.Surface.GeometrySize = _box.GetSize(); drawCall.Surface.PrevWorld = info.DrawState->PrevWorld; drawCall.Surface.Lightmap = nullptr; @@ -258,6 +259,7 @@ void SkinnedMesh::Draw(const RenderContextBatch& renderContextBatch, const DrawI drawCall.Material = material; drawCall.World = *info.World; drawCall.ObjectPosition = drawCall.World.GetTranslation(); + drawCall.ObjectRadius = info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition? drawCall.Surface.GeometrySize = _box.GetSize(); drawCall.Surface.PrevWorld = info.DrawState->PrevWorld; drawCall.Surface.Lightmap = nullptr; diff --git a/Source/Engine/Graphics/Shaders/GPUShader.cpp b/Source/Engine/Graphics/Shaders/GPUShader.cpp index 3639e01b1..e76ee9996 100644 --- a/Source/Engine/Graphics/Shaders/GPUShader.cpp +++ b/Source/Engine/Graphics/Shaders/GPUShader.cpp @@ -120,7 +120,7 @@ bool GPUShader::Create(MemoryReadStream& stream) GPUShaderProgram* shader = CreateGPUShaderProgram(type, initializer, cache, cacheSize, stream); if (shader == nullptr) { - LOG(Warning, "Failed to create shader program."); + LOG(Error, "Failed to create {} Shader program '{}'.", ::ToString(type), String(initializer.Name)); return true; } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUBufferDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUBufferDX11.cpp index b83ab6a08..352fd921a 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUBufferDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUBufferDX11.cpp @@ -105,7 +105,7 @@ bool GPUBufferDX11::OnInit() data.SysMemPitch = bufferDesc.ByteWidth; data.SysMemSlicePitch = 0; } - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateBuffer(&bufferDesc, _desc.InitData ? &data : nullptr, &_resource)); + VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateBuffer(&bufferDesc, _desc.InitData ? &data : nullptr, &_resource)); // Set state DX_SET_DEBUG_NAME(_resource, GetName()); @@ -135,7 +135,7 @@ bool GPUBufferDX11::OnInit() srvDesc.Buffer.NumElements = numElements; } ID3D11ShaderResourceView* srv; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateShaderResourceView(_resource, &srvDesc, &srv)); + VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateShaderResourceView(_resource, &srvDesc, &srv)); _view.SetSRV(srv); } if (useUAV) @@ -156,7 +156,7 @@ bool GPUBufferDX11::OnInit() else uavDesc.Format = RenderToolsDX::ToDxgiFormat(PixelFormatExtensions::FindUnorderedAccessFormat(_desc.Format)); ID3D11UnorderedAccessView* uav; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateUnorderedAccessView(_resource, &uavDesc, &uav)); + VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateUnorderedAccessView(_resource, &uavDesc, &uav)); _view.SetUAV(uav); } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp index 10e60aebc..c29c6254d 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp @@ -143,7 +143,7 @@ GPUDevice* GPUDeviceDX11::Create() if (tempAdapter && TryCreateDevice(tempAdapter, maxAllowedFeatureLevel, &adapter.MaxFeatureLevel)) { adapter.Index = index; - VALIDATE_DIRECTX_RESULT(tempAdapter->GetDesc(&adapter.Description)); + VALIDATE_DIRECTX_CALL(tempAdapter->GetDesc(&adapter.Description)); uint32 outputs = RenderToolsDX::CountAdapterOutputs(tempAdapter); LOG(Info, "Adapter {1}: '{0}', DirectX {2}", adapter.Description.Description, index, RenderToolsDX::GetFeatureLevelString(adapter.MaxFeatureLevel)); @@ -163,7 +163,7 @@ GPUDevice* GPUDeviceDX11::Create() if (tempAdapter && TryCreateDevice(tempAdapter, maxAllowedFeatureLevel, &adapter.MaxFeatureLevel)) { DXGI_ADAPTER_DESC desc; - VALIDATE_DIRECTX_RESULT(tempAdapter->GetDesc(&desc)); + VALIDATE_DIRECTX_CALL(tempAdapter->GetDesc(&desc)); for (int i = 0; i < adapters.Count(); i++) { if (adapters[i].Description.AdapterLuid.LowPart == desc.AdapterLuid.LowPart && @@ -274,7 +274,7 @@ ID3D11BlendState* GPUDeviceDX11::GetBlendState(const BlendingMode& blending) #endif // Create object - VALIDATE_DIRECTX_RESULT(_device->CreateBlendState(&desc, &blendState)); + VALIDATE_DIRECTX_CALL(_device->CreateBlendState(&desc, &blendState)); // Cache blend state BlendStates.Add(blending, blendState); @@ -333,7 +333,7 @@ bool GPUDeviceDX11::Init() // Create DirectX device D3D_FEATURE_LEVEL createdFeatureLevel = static_cast(0); auto targetFeatureLevel = GetD3DFeatureLevel(); - VALIDATE_DIRECTX_RESULT(D3D11CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, NULL, flags, &targetFeatureLevel, 1, D3D11_SDK_VERSION, &_device, &createdFeatureLevel, &_imContext)); + VALIDATE_DIRECTX_CALL(D3D11CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, NULL, flags, &targetFeatureLevel, 1, D3D11_SDK_VERSION, &_device, &createdFeatureLevel, &_imContext)); // Validate result ASSERT(_device); @@ -409,7 +409,7 @@ bool GPUDeviceDX11::Init() // Init debug layer #if GPU_ENABLE_DIAGNOSTICS ComPtr infoQueue; - VALIDATE_DIRECTX_RESULT(_device->QueryInterface(IID_PPV_ARGS(&infoQueue))); + VALIDATE_DIRECTX_CALL(_device->QueryInterface(IID_PPV_ARGS(&infoQueue))); if (infoQueue) { D3D11_INFO_QUEUE_FILTER filter; @@ -457,7 +457,7 @@ bool GPUDeviceDX11::Init() samplerDesc.MinLOD = 0; samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; result = _device->CreateSamplerState(&samplerDesc, &_samplerLinearClamp); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); // Point Clamp samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; @@ -467,7 +467,7 @@ bool GPUDeviceDX11::Init() samplerDesc.MinLOD = 0; samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; result = _device->CreateSamplerState(&samplerDesc, &_samplerPointClamp); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); // Linear Wrap samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; @@ -477,7 +477,7 @@ bool GPUDeviceDX11::Init() samplerDesc.MinLOD = 0; samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; result = _device->CreateSamplerState(&samplerDesc, &_samplerLinearWrap); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); // Point Wrap samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; @@ -487,7 +487,7 @@ bool GPUDeviceDX11::Init() samplerDesc.MinLOD = 0; samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; result = _device->CreateSamplerState(&samplerDesc, &_samplerPointWrap); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); // Shadow samplerDesc.Filter = D3D11_FILTER_COMPARISON_MIN_MAG_MIP_POINT; @@ -500,7 +500,7 @@ bool GPUDeviceDX11::Init() samplerDesc.MinLOD = 0; samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; result = _device->CreateSamplerState(&samplerDesc, &_samplerShadow); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); // Shadow PCF samplerDesc.Filter = D3D11_FILTER_COMPARISON_MIN_MAG_MIP_LINEAR; @@ -514,7 +514,7 @@ bool GPUDeviceDX11::Init() samplerDesc.MinLOD = 0; samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; result = _device->CreateSamplerState(&samplerDesc, &_samplerShadowPCF); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); } // Rasterizer States @@ -534,7 +534,7 @@ bool GPUDeviceDX11::Init() rDesc.AntialiasedLineEnable = !!wireframe; \ rDesc.DepthClipEnable = !!depthClip; \ result = _device->CreateRasterizerState(&rDesc, &RasterizerStates[index]); \ - LOG_DIRECTX_RESULT_WITH_RETURN(result) + LOG_DIRECTX_RESULT_WITH_RETURN(result, true) CREATE_RASTERIZER_STATE(CullMode::Normal, D3D11_CULL_BACK, false, false); CREATE_RASTERIZER_STATE(CullMode::Inverted, D3D11_CULL_FRONT, false, false); CREATE_RASTERIZER_STATE(CullMode::TwoSided, D3D11_CULL_NONE, false, false); @@ -568,7 +568,7 @@ bool GPUDeviceDX11::Init() dsDesc.DepthFunc = (D3D11_COMPARISON_FUNC)depthFunc; \ index = (int32)depthFunc + (depthEnable ? 0 : 9) + (depthWrite ? 0 : 18); \ HRESULT result = _device->CreateDepthStencilState(&dsDesc, &DepthStencilStates[index]); \ - LOG_DIRECTX_RESULT_WITH_RETURN(result); } + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); } CREATE_DEPTH_STENCIL_STATE(false, false); CREATE_DEPTH_STENCIL_STATE(false, true); CREATE_DEPTH_STENCIL_STATE(true, true); @@ -666,7 +666,7 @@ void GPUDeviceDX11::DrawEnd() #if GPU_ENABLE_DIAGNOSTICS // Flush debug messages queue ComPtr infoQueue; - VALIDATE_DIRECTX_RESULT(_device->QueryInterface(IID_PPV_ARGS(&infoQueue))); + VALIDATE_DIRECTX_CALL(_device->QueryInterface(IID_PPV_ARGS(&infoQueue))); if (infoQueue) { Array data; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSamplerDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSamplerDX11.cpp index 428390637..fadb20ead 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSamplerDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSamplerDX11.cpp @@ -106,7 +106,7 @@ bool GPUSamplerDX11::OnInit() samplerDesc.MinLOD = _desc.MinMipLevel; samplerDesc.MaxLOD = _desc.MaxMipLevel; HRESULT result = _device->GetDevice()->CreateSamplerState(&samplerDesc, &SamplerState); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); ASSERT(SamplerState != nullptr); _memoryUsage = sizeof(D3D11_SAMPLER_DESC); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp index 0e4b7ac0c..b58684a4e 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp @@ -10,6 +10,7 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, byte* cacheBytes, uint32 cacheSize, MemoryReadStream& stream) { GPUShaderProgram* shader = nullptr; + HRESULT result; switch (type) { case ShaderStage::Vertex: @@ -90,12 +91,13 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const if (inputLayoutSize > 0) { // Create input layout - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateInputLayout(inputLayoutDesc, inputLayoutSize, cacheBytes, cacheSize, &inputLayout)); + VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateInputLayout(inputLayoutDesc, inputLayoutSize, cacheBytes, cacheSize, &inputLayout)); } // Create shader ID3D11VertexShader* buffer = nullptr; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateVertexShader(cacheBytes, cacheSize, nullptr, &buffer)); + result = _device->GetDevice()->CreateVertexShader(cacheBytes, cacheSize, nullptr, &buffer); + LOG_DIRECTX_RESULT_WITH_RETURN(result, nullptr); // Create object shader = New(initializer, buffer, inputLayout, inputLayoutSize); @@ -109,7 +111,8 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const // Create shader ID3D11HullShader* buffer = nullptr; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateHullShader(cacheBytes, cacheSize, nullptr, &buffer)); + result = _device->GetDevice()->CreateHullShader(cacheBytes, cacheSize, nullptr, &buffer); + LOG_DIRECTX_RESULT_WITH_RETURN(result, nullptr); // Create object shader = New(initializer, buffer, controlPointsCount); @@ -119,7 +122,8 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const { // Create shader ID3D11DomainShader* buffer = nullptr; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateDomainShader(cacheBytes, cacheSize, nullptr, &buffer)); + result = _device->GetDevice()->CreateDomainShader(cacheBytes, cacheSize, nullptr, &buffer); + LOG_DIRECTX_RESULT_WITH_RETURN(result, nullptr); // Create object shader = New(initializer, buffer); @@ -129,7 +133,8 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const { // Create shader ID3D11GeometryShader* buffer = nullptr; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateGeometryShader(cacheBytes, cacheSize, nullptr, &buffer)); + result = _device->GetDevice()->CreateGeometryShader(cacheBytes, cacheSize, nullptr, &buffer); + LOG_DIRECTX_RESULT_WITH_RETURN(result, nullptr); // Create object shader = New(initializer, buffer); @@ -139,7 +144,8 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const { // Create shader ID3D11PixelShader* buffer = nullptr; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreatePixelShader(cacheBytes, cacheSize, nullptr, &buffer)); + result = _device->GetDevice()->CreatePixelShader(cacheBytes, cacheSize, nullptr, &buffer); + LOG_DIRECTX_RESULT_WITH_RETURN(result, nullptr); // Create object shader = New(initializer, buffer); @@ -149,7 +155,8 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const { // Create shader ID3D11ComputeShader* buffer = nullptr; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateComputeShader(cacheBytes, cacheSize, nullptr, &buffer)); + result = _device->GetDevice()->CreateComputeShader(cacheBytes, cacheSize, nullptr, &buffer); + LOG_DIRECTX_RESULT_WITH_RETURN(result, nullptr); // Create object shader = New(initializer, buffer); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp index 7b9564f54..d5c0e5c55 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp @@ -28,13 +28,13 @@ GPUSwapChainDX11::GPUSwapChainDX11(GPUDeviceDX11* device, Window* window) void GPUSwapChainDX11::getBackBuffer() { - VALIDATE_DIRECTX_RESULT(_swapChain->GetBuffer(0, __uuidof(_backBuffer), reinterpret_cast(&_backBuffer))); + VALIDATE_DIRECTX_CALL(_swapChain->GetBuffer(0, __uuidof(_backBuffer), reinterpret_cast(&_backBuffer))); ID3D11RenderTargetView* rtv; ID3D11ShaderResourceView* srv; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateRenderTargetView(_backBuffer, nullptr, &rtv)); + VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateRenderTargetView(_backBuffer, nullptr, &rtv)); #if GPU_USE_WINDOW_SRV - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateShaderResourceView(_backBuffer, nullptr, &srv)); + VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateShaderResourceView(_backBuffer, nullptr, &srv)); #else srv = nullptr; #endif @@ -55,7 +55,7 @@ void GPUSwapChainDX11::OnReleaseGPU() // Disable fullscreen mode if (_swapChain) { - VALIDATE_DIRECTX_RESULT(_swapChain->SetFullscreenState(false, nullptr)); + VALIDATE_DIRECTX_CALL(_swapChain->SetFullscreenState(false, nullptr)); } #endif @@ -78,7 +78,7 @@ bool GPUSwapChainDX11::IsFullscreen() // Get state BOOL state; - VALIDATE_DIRECTX_RESULT(_swapChain->GetFullscreenState(&state, nullptr)); + VALIDATE_DIRECTX_CALL(_swapChain->GetFullscreenState(&state, nullptr)); return state == TRUE; } @@ -229,21 +229,21 @@ bool GPUSwapChainDX11::Resize(int32 width, int32 height) // Create swap chain #if PLATFORM_WINDOWS auto dxgi = _device->GetDXGIFactory(); - VALIDATE_DIRECTX_RESULT(dxgi->CreateSwapChain(_device->GetDevice(), &swapChainDesc, &_swapChain)); + VALIDATE_DIRECTX_CALL(dxgi->CreateSwapChain(_device->GetDevice(), &swapChainDesc, &_swapChain)); ASSERT(_swapChain); // Disable DXGI changes to the window - VALIDATE_DIRECTX_RESULT(dxgi->MakeWindowAssociation(_windowHandle, DXGI_MWA_NO_ALT_ENTER)); + VALIDATE_DIRECTX_CALL(dxgi->MakeWindowAssociation(_windowHandle, DXGI_MWA_NO_ALT_ENTER)); #else auto dxgiFactory = (IDXGIFactory2*)_device->GetDXGIFactory(); - VALIDATE_DIRECTX_RESULT(dxgiFactory->CreateSwapChainForCoreWindow(_device->GetDevice(), static_cast(_windowHandle), &swapChainDesc, nullptr, &_swapChain)); + VALIDATE_DIRECTX_CALL(dxgiFactory->CreateSwapChainForCoreWindow(_device->GetDevice(), static_cast(_windowHandle), &swapChainDesc, nullptr, &_swapChain)); ASSERT(_swapChain); // Ensure that DXGI does not queue more than one frame at a time. This both reduces latency and // ensures that the application will only render after each VSync, minimizing power consumption. ComPtr dxgiDevice; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->QueryInterface(IID_PPV_ARGS(&dxgiDevice))); - VALIDATE_DIRECTX_RESULT(dxgiDevice->SetMaximumFrameLatency(1)); + VALIDATE_DIRECTX_CALL(_device->GetDevice()->QueryInterface(IID_PPV_ARGS(&dxgiDevice))); + VALIDATE_DIRECTX_CALL(dxgiDevice->SetMaximumFrameLatency(1)); #endif } else @@ -252,10 +252,10 @@ bool GPUSwapChainDX11::Resize(int32 width, int32 height) #if PLATFORM_WINDOWS _swapChain->GetDesc(&swapChainDesc); - VALIDATE_DIRECTX_RESULT(_swapChain->ResizeBuffers(swapChainDesc.BufferCount, width, height, swapChainDesc.BufferDesc.Format, swapChainDesc.Flags)); + VALIDATE_DIRECTX_CALL(_swapChain->ResizeBuffers(swapChainDesc.BufferCount, width, height, swapChainDesc.BufferDesc.Format, swapChainDesc.Flags)); #else _swapChain->GetDesc1(&swapChainDesc); - VALIDATE_DIRECTX_RESULT(_swapChain->ResizeBuffers(swapChainDesc.BufferCount, width, height, swapChainDesc.Format, swapChainDesc.Flags)); + VALIDATE_DIRECTX_CALL(_swapChain->ResizeBuffers(swapChainDesc.BufferCount, width, height, swapChainDesc.Format, swapChainDesc.Flags)); #endif } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.cpp index 73be91108..3d1979954 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.cpp @@ -87,7 +87,7 @@ bool GPUTextureDX11::OnInit() result = device->CreateTexture2D(&textureDesc, nullptr, &texture); _resource = texture; } - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); ASSERT(_resource != nullptr); DX_SET_DEBUG_NAME(_resource, GetName()); @@ -135,7 +135,7 @@ void GPUTextureDX11::OnResidentMipsChanged() } ID3D11ShaderResourceView* srView = nullptr; if (mipLevels != 0) - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateShaderResourceView(_resource, &srDesc, &srView)); + VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateShaderResourceView(_resource, &srDesc, &srView)); GPUTextureViewDX11& view = IsVolume() ? _handleVolume : _handlesPerSlice[0]; if (view.GetParent() == nullptr) view.Init(this, nullptr, srView, nullptr, nullptr, Format(), MultiSampleLevel()); @@ -201,7 +201,7 @@ void GPUTextureDX11::initHandles() srDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D; srDesc.Texture3D.MostDetailedMip = 0; srDesc.Texture3D.MipLevels = mipLevels; - VALIDATE_DIRECTX_RESULT(device->CreateShaderResourceView(_resource, &srDesc, &srView)); + VALIDATE_DIRECTX_CALL(device->CreateShaderResourceView(_resource, &srDesc, &srView)); } if (useRTV) { @@ -209,7 +209,7 @@ void GPUTextureDX11::initHandles() rtDesc.Texture3D.MipSlice = 0; rtDesc.Texture3D.FirstWSlice = 0; rtDesc.Texture3D.WSize = Depth(); - VALIDATE_DIRECTX_RESULT(device->CreateRenderTargetView(_resource, &rtDesc, &rtView)); + VALIDATE_DIRECTX_CALL(device->CreateRenderTargetView(_resource, &rtDesc, &rtView)); } if (useUAV) { @@ -217,7 +217,7 @@ void GPUTextureDX11::initHandles() uaDesc.Texture3D.MipSlice = 0; uaDesc.Texture3D.WSize = Depth(); uaDesc.Texture3D.FirstWSlice = 0; - VALIDATE_DIRECTX_RESULT(device->CreateUnorderedAccessView(_resource, &uaDesc, &uaView)); + VALIDATE_DIRECTX_CALL(device->CreateUnorderedAccessView(_resource, &uaDesc, &uaView)); } _handleVolume.Init(this, rtView, srView, nullptr, uaView, format, msaa); @@ -232,7 +232,7 @@ void GPUTextureDX11::initHandles() for (int32 sliceIndex = 0; sliceIndex < Depth(); sliceIndex++) { rtDesc.Texture3D.FirstWSlice = sliceIndex; - VALIDATE_DIRECTX_RESULT(device->CreateRenderTargetView(_resource, &rtDesc, &rtView)); + VALIDATE_DIRECTX_CALL(device->CreateRenderTargetView(_resource, &rtDesc, &rtView)); _handlesPerSlice[sliceIndex].Init(this, rtView, nullptr, nullptr, nullptr, format, msaa); } } @@ -263,7 +263,7 @@ void GPUTextureDX11::initHandles() dsDesc.Texture2DArray.FirstArraySlice = arrayIndex; dsDesc.Texture2DArray.MipSlice = 0; } - VALIDATE_DIRECTX_RESULT(device->CreateDepthStencilView(_resource, &dsDesc, &dsView)); + VALIDATE_DIRECTX_CALL(device->CreateDepthStencilView(_resource, &dsDesc, &dsView)); } if (useRTV) { @@ -281,7 +281,7 @@ void GPUTextureDX11::initHandles() rtDesc.Texture2DArray.FirstArraySlice = arrayIndex; rtDesc.Texture2DArray.MipSlice = 0; } - VALIDATE_DIRECTX_RESULT(device->CreateRenderTargetView(_resource, &rtDesc, &rtView)); + VALIDATE_DIRECTX_CALL(device->CreateRenderTargetView(_resource, &rtDesc, &rtView)); } if (useSRV) { @@ -305,7 +305,7 @@ void GPUTextureDX11::initHandles() srDesc.Texture2DArray.MipLevels = mipLevels; srDesc.Texture2DArray.MostDetailedMip = 0; } - VALIDATE_DIRECTX_RESULT(device->CreateShaderResourceView(_resource, &srDesc, &srView)); + VALIDATE_DIRECTX_CALL(device->CreateShaderResourceView(_resource, &srDesc, &srView)); } } @@ -322,7 +322,7 @@ void GPUTextureDX11::initHandles() dsDesc.Texture2DArray.ArraySize = arraySize; dsDesc.Texture2DArray.FirstArraySlice = 0; dsDesc.Texture2DArray.MipSlice = 0; - VALIDATE_DIRECTX_RESULT(device->CreateDepthStencilView(_resource, &dsDesc, &dsView)); + VALIDATE_DIRECTX_CALL(device->CreateDepthStencilView(_resource, &dsDesc, &dsView)); } if (useRTV) { @@ -330,7 +330,7 @@ void GPUTextureDX11::initHandles() rtDesc.Texture2DArray.ArraySize = arraySize; rtDesc.Texture2DArray.FirstArraySlice = 0; rtDesc.Texture2DArray.MipSlice = 0; - VALIDATE_DIRECTX_RESULT(device->CreateRenderTargetView(_resource, &rtDesc, &rtView)); + VALIDATE_DIRECTX_CALL(device->CreateRenderTargetView(_resource, &rtDesc, &rtView)); } if (useSRV) { @@ -348,7 +348,7 @@ void GPUTextureDX11::initHandles() srDesc.Texture2DArray.MipLevels = mipLevels; srDesc.Texture2DArray.MostDetailedMip = 0; } - VALIDATE_DIRECTX_RESULT(device->CreateShaderResourceView(_resource, &srDesc, &srView)); + VALIDATE_DIRECTX_CALL(device->CreateShaderResourceView(_resource, &srDesc, &srView)); } if (useUAV) { @@ -356,7 +356,7 @@ void GPUTextureDX11::initHandles() uaDesc.Texture2DArray.MipSlice = 0; uaDesc.Texture2DArray.ArraySize = arraySize; uaDesc.Texture2DArray.FirstArraySlice = 0; - VALIDATE_DIRECTX_RESULT(device->CreateUnorderedAccessView(_resource, &uaDesc, &uaView)); + VALIDATE_DIRECTX_CALL(device->CreateUnorderedAccessView(_resource, &uaDesc, &uaView)); } _handleArray.Init(this, rtView, srView, dsView, uaView, format, msaa); } @@ -386,7 +386,7 @@ void GPUTextureDX11::initHandles() dsDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; dsDesc.Texture2D.MipSlice = 0; } - VALIDATE_DIRECTX_RESULT(device->CreateDepthStencilView(_resource, &dsDesc, &dsView)); + VALIDATE_DIRECTX_CALL(device->CreateDepthStencilView(_resource, &dsDesc, &dsView)); } if (useRTV) { @@ -406,7 +406,7 @@ void GPUTextureDX11::initHandles() rtDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; rtDesc.Texture2D.MipSlice = 0; } - VALIDATE_DIRECTX_RESULT(device->CreateRenderTargetView(_resource, &rtDesc, &rtView)); + VALIDATE_DIRECTX_CALL(device->CreateRenderTargetView(_resource, &rtDesc, &rtView)); } if (useSRV) { @@ -426,13 +426,13 @@ void GPUTextureDX11::initHandles() srDesc.Texture2D.MostDetailedMip = 0; srDesc.Texture2D.MipLevels = mipLevels; } - VALIDATE_DIRECTX_RESULT(device->CreateShaderResourceView(_resource, &srDesc, &srView)); + VALIDATE_DIRECTX_CALL(device->CreateShaderResourceView(_resource, &srDesc, &srView)); } if (useUAV) { uaDesc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2D; uaDesc.Texture2D.MipSlice = 0; - VALIDATE_DIRECTX_RESULT(device->CreateUnorderedAccessView(_resource, &uaDesc, &uaView)); + VALIDATE_DIRECTX_CALL(device->CreateUnorderedAccessView(_resource, &uaDesc, &uaView)); } _handlesPerSlice[0].Init(this, rtView, srView, dsView, uaView, format, msaa); } @@ -521,7 +521,7 @@ void GPUTextureDX11::initHandles() dsDesc.Flags = D3D11_DSV_READ_ONLY_DEPTH; if (PixelFormatExtensions::HasStencil(format)) dsDesc.Flags |= D3D11_DSV_READ_ONLY_STENCIL; - VALIDATE_DIRECTX_RESULT(device->CreateDepthStencilView(_resource, &dsDesc, &dsView)); + VALIDATE_DIRECTX_CALL(device->CreateDepthStencilView(_resource, &dsDesc, &dsView)); } ASSERT(!useRTV); rtView = nullptr; @@ -543,7 +543,7 @@ void GPUTextureDX11::initHandles() srDesc.Texture2D.MostDetailedMip = 0; srDesc.Texture2D.MipLevels = mipLevels; } - VALIDATE_DIRECTX_RESULT(device->CreateShaderResourceView(_resource, &srDesc, &srView)); + VALIDATE_DIRECTX_CALL(device->CreateShaderResourceView(_resource, &srDesc, &srView)); } _handleReadOnlyDepth.Init(this, rtView, srView, dsView, nullptr, format, msaa); } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/CommandAllocatorPoolDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/CommandAllocatorPoolDX12.cpp index f0ad790af..9bb185030 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/CommandAllocatorPoolDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/CommandAllocatorPoolDX12.cpp @@ -31,7 +31,7 @@ ID3D12CommandAllocator* CommandAllocatorPoolDX12::RequestAllocator(uint64 comple if (firstPair.First <= completedFenceValue) { allocator = firstPair.Second; - VALIDATE_DIRECTX_RESULT(allocator->Reset()); + VALIDATE_DIRECTX_CALL(allocator->Reset()); _ready.RemoveAtKeepOrder(0); } } @@ -39,7 +39,7 @@ ID3D12CommandAllocator* CommandAllocatorPoolDX12::RequestAllocator(uint64 comple // If no allocators were ready to be reused, create a new one if (allocator == nullptr) { - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateCommandAllocator(_type, IID_PPV_ARGS(&allocator))); + VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateCommandAllocator(_type, IID_PPV_ARGS(&allocator))); #if GPU_ENABLE_RESOURCE_NAMING Char name[32]; swprintf(name, 32, L"CommandAllocator %u", _pool.Count()); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/CommandQueueDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/CommandQueueDX12.cpp index 8f78b749e..3235f4015 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/CommandQueueDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/CommandQueueDX12.cpp @@ -111,7 +111,7 @@ bool CommandQueueDX12::Init() desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; desc.NodeMask = 0; HRESULT result = _device->GetDevice()->CreateCommandQueue(&desc, IID_PPV_ARGS(&_commandQueue)); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); #if GPU_ENABLE_RESOURCE_NAMING _commandQueue->SetName(TEXT("CommandQueueDX12::CommandQueue")); #endif @@ -148,7 +148,7 @@ void CommandQueueDX12::WaitForGPU() uint64 CommandQueueDX12::ExecuteCommandList(ID3D12CommandList* list) { - VALIDATE_DIRECTX_RESULT((static_cast(list))->Close()); + VALIDATE_DIRECTX_CALL((static_cast(list))->Close()); _commandQueue->ExecuteCommandLists(1, &list); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/DescriptorHeapDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/DescriptorHeapDX12.cpp index 238318b2c..2ee2ea023 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/DescriptorHeapDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/DescriptorHeapDX12.cpp @@ -73,7 +73,7 @@ bool DescriptorHeapWithSlotsDX12::Create(D3D12_DESCRIPTOR_HEAP_TYPE type, uint32 // Create heap const HRESULT result = _device->GetDevice()->CreateDescriptorHeap(&desc, __uuidof(ID3D12DescriptorHeap), reinterpret_cast(&_heap)); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); // Setup _type = type; @@ -196,7 +196,7 @@ bool DescriptorHeapRingBufferDX12::Init() desc.Flags = _shaderVisible ? D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE : D3D12_DESCRIPTOR_HEAP_FLAG_NONE; desc.NodeMask = 0; const HRESULT result = _device->GetDevice()->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&_heap)); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); // Setup _firstFree = 0; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUBufferDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUBufferDX12.cpp index 5d2d97f0b..8f948d246 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUBufferDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUBufferDX12.cpp @@ -136,7 +136,7 @@ bool GPUBufferDX12::OnInit() // Create resource ID3D12Resource* resource; D3D12_RESOURCE_STATES initialState = D3D12_RESOURCE_STATE_COPY_DEST; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateCommittedResource(&heapProperties, D3D12_HEAP_FLAG_NONE, &resourceDesc, initialState, nullptr, IID_PPV_ARGS(&resource))); + VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateCommittedResource(&heapProperties, D3D12_HEAP_FLAG_NONE, &resourceDesc, initialState, nullptr, IID_PPV_ARGS(&resource))); // Set state initResource(resource, initialState, 1); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp index 54f1a4290..e186054c2 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp @@ -83,7 +83,7 @@ GPUContextDX12::GPUContextDX12(GPUDeviceDX12* device, D3D12_COMMAND_LIST_TYPE ty FrameFenceValues[0] = 0; FrameFenceValues[1] = 0; _currentAllocator = _device->GetCommandQueue()->RequestAllocator(); - VALIDATE_DIRECTX_RESULT(device->GetDevice()->CreateCommandList(0, type, _currentAllocator, nullptr, IID_PPV_ARGS(&_commandList))); + VALIDATE_DIRECTX_CALL(device->GetDevice()->CreateCommandList(0, type, _currentAllocator, nullptr, IID_PPV_ARGS(&_commandList))); #if GPU_ENABLE_RESOURCE_NAMING _commandList->SetName(TEXT("GPUContextDX12::CommandList")); #endif diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp index 047b69bb5..e2266f551 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp @@ -77,7 +77,7 @@ GPUDevice* GPUDeviceDX12::Create() #endif #ifdef __ID3D12DeviceRemovedExtendedDataSettings_FWD_DEFINED__ ComPtr dredSettings; - VALIDATE_DIRECTX_RESULT(D3D12GetDebugInterface(IID_PPV_ARGS(&dredSettings))); + VALIDATE_DIRECTX_CALL(D3D12GetDebugInterface(IID_PPV_ARGS(&dredSettings))); if (dredSettings) { // Turn on AutoBreadcrumbs and Page Fault reporting @@ -116,7 +116,7 @@ GPUDevice* GPUDeviceDX12::Create() { adapter.Index = index; adapter.MaxFeatureLevel = D3D_FEATURE_LEVEL_12_0; - VALIDATE_DIRECTX_RESULT(tempAdapter->GetDesc(&adapter.Description)); + VALIDATE_DIRECTX_CALL(tempAdapter->GetDesc(&adapter.Description)); uint32 outputs = RenderToolsDX::CountAdapterOutputs(tempAdapter); // Send that info to the log @@ -137,7 +137,7 @@ GPUDevice* GPUDeviceDX12::Create() if (tempAdapter && CheckDX12Support(tempAdapter)) { DXGI_ADAPTER_DESC desc; - VALIDATE_DIRECTX_RESULT(tempAdapter->GetDesc(&desc)); + VALIDATE_DIRECTX_CALL(tempAdapter->GetDesc(&desc)); for (int i = 0; i < adapters.Count(); i++) { if (adapters[i].Description.AdapterLuid.LowPart == desc.AdapterLuid.LowPart && @@ -254,7 +254,7 @@ bool GPUDeviceDX12::Init() #if PLATFORM_XBOX_SCARLETT params.DisableDXR = TRUE; #endif - VALIDATE_DIRECTX_RESULT(D3D12XboxCreateDevice(nullptr, ¶ms, IID_GRAPHICS_PPV_ARGS(&_device))); + VALIDATE_DIRECTX_CALL(D3D12XboxCreateDevice(nullptr, ¶ms, IID_GRAPHICS_PPV_ARGS(&_device))); // Setup adapter D3D12XBOX_GPU_HARDWARE_CONFIGURATION hwConfig = {}; @@ -319,12 +319,12 @@ bool GPUDeviceDX12::Init() } // Create DirectX device - VALIDATE_DIRECTX_RESULT(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&_device))); + VALIDATE_DIRECTX_CALL(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&_device))); // Debug Layer #if GPU_ENABLE_DIAGNOSTICS ComPtr infoQueue; - VALIDATE_DIRECTX_RESULT(_device->QueryInterface(IID_PPV_ARGS(&infoQueue))); + VALIDATE_DIRECTX_CALL(_device->QueryInterface(IID_PPV_ARGS(&infoQueue))); if (infoQueue) { D3D12_INFO_QUEUE_FILTER filter; @@ -363,7 +363,7 @@ bool GPUDeviceDX12::Init() // Spawn some info about the hardware D3D12_FEATURE_DATA_D3D12_OPTIONS options; - VALIDATE_DIRECTX_RESULT(_device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, &options, sizeof(options))); + VALIDATE_DIRECTX_CALL(_device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, &options, sizeof(options))); LOG(Info, "Tiled Resources Tier: {0}", (int32)options.TiledResourcesTier); LOG(Info, "Resource Binding Tier: {0}", (int32)options.ResourceBindingTier); LOG(Info, "Conservative Rasterization Tier: {0}", (int32)options.ConservativeRasterizationTier); @@ -662,10 +662,10 @@ bool GPUDeviceDX12::Init() // Serialize ComPtr signature; ComPtr error; - VALIDATE_DIRECTX_RESULT(D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error)); + VALIDATE_DIRECTX_CALL(D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error)); // Create - VALIDATE_DIRECTX_RESULT(_device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&_rootSignature))); + VALIDATE_DIRECTX_CALL(_device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&_rootSignature))); } // Upload buffer @@ -896,14 +896,14 @@ void GPUDeviceDX12::OnResumed() void GPUDeviceDX12::updateFrameEvents() { ComPtr dxgiDevice; - VALIDATE_DIRECTX_RESULT(_device->QueryInterface(IID_GRAPHICS_PPV_ARGS(&dxgiDevice))); + VALIDATE_DIRECTX_CALL(_device->QueryInterface(IID_GRAPHICS_PPV_ARGS(&dxgiDevice))); ComPtr dxgiAdapter; - VALIDATE_DIRECTX_RESULT(dxgiDevice->GetAdapter(dxgiAdapter.GetAddressOf())); + VALIDATE_DIRECTX_CALL(dxgiDevice->GetAdapter(dxgiAdapter.GetAddressOf())); dxgiAdapter->GetDesc(&_adapter->Description); ComPtr dxgiOutput; - VALIDATE_DIRECTX_RESULT(dxgiAdapter->EnumOutputs(0, dxgiOutput.GetAddressOf())); - VALIDATE_DIRECTX_RESULT(_device->SetFrameIntervalX(dxgiOutput.Get(), D3D12XBOX_FRAME_INTERVAL_60_HZ, DX12_BACK_BUFFER_COUNT - 1u, D3D12XBOX_FRAME_INTERVAL_FLAG_NONE)); - VALIDATE_DIRECTX_RESULT(_device->ScheduleFrameEventX(D3D12XBOX_FRAME_EVENT_ORIGIN, 0U, nullptr, D3D12XBOX_SCHEDULE_FRAME_EVENT_FLAG_NONE)); + VALIDATE_DIRECTX_CALL(dxgiAdapter->EnumOutputs(0, dxgiOutput.GetAddressOf())); + VALIDATE_DIRECTX_CALL(_device->SetFrameIntervalX(dxgiOutput.Get(), D3D12XBOX_FRAME_INTERVAL_60_HZ, DX12_BACK_BUFFER_COUNT - 1u, D3D12XBOX_FRAME_INTERVAL_FLAG_NONE)); + VALIDATE_DIRECTX_CALL(_device->ScheduleFrameEventX(D3D12XBOX_FRAME_EVENT_ORIGIN, 0U, nullptr, D3D12XBOX_SCHEDULE_FRAME_EVENT_FLAG_NONE)); } #endif diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp index 85d79582f..8a9ca1c64 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp @@ -66,7 +66,7 @@ void GPUSwapChainDX12::OnReleaseGPU() // Disable fullscreen mode if (_swapChain) { - VALIDATE_DIRECTX_RESULT(_swapChain->SetFullscreenState(false, nullptr)); + VALIDATE_DIRECTX_CALL(_swapChain->SetFullscreenState(false, nullptr)); } #endif @@ -100,7 +100,7 @@ bool GPUSwapChainDX12::IsFullscreen() // Get state BOOL state; - VALIDATE_DIRECTX_RESULT(_swapChain->GetFullscreenState(&state, nullptr)); + VALIDATE_DIRECTX_CALL(_swapChain->GetFullscreenState(&state, nullptr)); return state == TRUE; #endif } @@ -221,7 +221,7 @@ bool GPUSwapChainDX12::Resize(int32 width, int32 height) // Create swap chain (it needs the queue so that it can force a flush on it) IDXGISwapChain1* swapChain; auto dxgiFactory = _device->GetDXGIFactory(); - VALIDATE_DIRECTX_RESULT(dxgiFactory->CreateSwapChainForHwnd(_device->GetCommandQueueDX12(), _windowHandle, &swapChainDesc, &fullscreenDesc, nullptr, &swapChain)); + VALIDATE_DIRECTX_CALL(dxgiFactory->CreateSwapChainForHwnd(_device->GetCommandQueueDX12(), _windowHandle, &swapChainDesc, &fullscreenDesc, nullptr, &swapChain)); _swapChain = static_cast(swapChain); ASSERT(_swapChain); DX_SET_DEBUG_NAME_EX(_swapChain, TEXT("RenderOutput"), TEXT("SwapChain"), TEXT("")); @@ -229,7 +229,7 @@ bool GPUSwapChainDX12::Resize(int32 width, int32 height) _backBuffers.Resize(swapChainDesc.BufferCount); // Disable DXGI changes to the window - VALIDATE_DIRECTX_RESULT(dxgiFactory->MakeWindowAssociation(_windowHandle, DXGI_MWA_NO_ALT_ENTER)); + VALIDATE_DIRECTX_CALL(dxgiFactory->MakeWindowAssociation(_windowHandle, DXGI_MWA_NO_ALT_ENTER)); } else { @@ -237,7 +237,7 @@ bool GPUSwapChainDX12::Resize(int32 width, int32 height) _swapChain->GetDesc1(&swapChainDesc); - VALIDATE_DIRECTX_RESULT(_swapChain->ResizeBuffers(swapChainDesc.BufferCount, width, height, swapChainDesc.Format, swapChainDesc.Flags)); + VALIDATE_DIRECTX_CALL(_swapChain->ResizeBuffers(swapChainDesc.BufferCount, width, height, swapChainDesc.Format, swapChainDesc.Flags)); } _currentFrameIndex = _swapChain->GetCurrentBackBufferIndex(); @@ -316,7 +316,7 @@ void GPUSwapChainDX12::getBackBuffer() swapChainBufferDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; D3D12_CLEAR_VALUE swapChainOptimizedClearValue = {}; swapChainOptimizedClearValue.Format = swapChainBufferDesc.Format; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateCommittedResource( + VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateCommittedResource( &swapChainHeapProperties, D3D12_HEAP_FLAG_ALLOW_DISPLAY, &swapChainBufferDesc, @@ -324,7 +324,7 @@ void GPUSwapChainDX12::getBackBuffer() &swapChainOptimizedClearValue, IID_GRAPHICS_PPV_ARGS(&backbuffer))); #else - VALIDATE_DIRECTX_RESULT(_swapChain->GetBuffer(i, IID_PPV_ARGS(&backbuffer))); + VALIDATE_DIRECTX_CALL(_swapChain->GetBuffer(i, IID_PPV_ARGS(&backbuffer))); #endif DX_SET_DEBUG_NAME_EX(backbuffer, TEXT("RenderOutput"), TEXT("BackBuffer"), i); _backBuffers[i].Setup(this, backbuffer); @@ -337,7 +337,7 @@ void GPUSwapChainDX12::Begin(RenderTask* task) { // Wait until frame start is signaled _framePipelineToken = D3D12XBOX_FRAME_PIPELINE_TOKEN_NULL; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->WaitFrameEventX(D3D12XBOX_FRAME_EVENT_ORIGIN, INFINITE, nullptr, D3D12XBOX_WAIT_FRAME_EVENT_FLAG_NONE, &_framePipelineToken)); + VALIDATE_DIRECTX_CALL(_device->GetDevice()->WaitFrameEventX(D3D12XBOX_FRAME_EVENT_ORIGIN, INFINITE, nullptr, D3D12XBOX_WAIT_FRAME_EVENT_FLAG_NONE, &_framePipelineToken)); GPUSwapChain::Begin(task); } @@ -366,7 +366,7 @@ void GPUSwapChainDX12::Present(bool vsync) planeParameters.Token = _framePipelineToken; planeParameters.ResourceCount = 1; planeParameters.ppResources = &backBuffer; - VALIDATE_DIRECTX_RESULT(_device->GetCommandQueueDX12()->PresentX(1, &planeParameters, nullptr)); + VALIDATE_DIRECTX_CALL(_device->GetCommandQueueDX12()->PresentX(1, &planeParameters, nullptr)); // Base GPUSwapChain::Present(vsync); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.cpp index dd7402097..b89d65275 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.cpp @@ -113,7 +113,7 @@ bool GPUTextureDX12::OnInit() resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; resourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE; auto result = device->CreateCommittedResource(&heapProperties, D3D12_HEAP_FLAG_NONE, &resourceDesc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&resource)); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); initResource(resource, D3D12_RESOURCE_STATE_COPY_DEST, 1); DX_SET_DEBUG_NAME(_resource, GetName()); _memoryUsage = totalSize; @@ -184,7 +184,7 @@ bool GPUTextureDX12::OnInit() // Create texture auto result = device->CreateCommittedResource(&heapProperties, D3D12_HEAP_FLAG_NONE, &resourceDesc, initialState, clearValuePtr, IID_PPV_ARGS(&resource)); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); // Set state bool isRead = useSRV || useUAV; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTimerQueryDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTimerQueryDX12.cpp index 6df638d23..7c814baed 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTimerQueryDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTimerQueryDX12.cpp @@ -37,7 +37,7 @@ void GPUTimerQueryDX12::End() heap.EndQuery(context, _end); const auto queue = _device->GetCommandQueue()->GetCommandQueue(); - VALIDATE_DIRECTX_RESULT(queue->GetTimestampFrequency(&_gpuFrequency)); + VALIDATE_DIRECTX_CALL(queue->GetTimestampFrequency(&_gpuFrequency)); _endCalled = true; } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/QueryHeapDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/QueryHeapDX12.cpp index dc6939cff..ea6af0a2d 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/QueryHeapDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/QueryHeapDX12.cpp @@ -41,7 +41,7 @@ bool QueryHeapDX12::Init() heapDesc.Count = _queryHeapCount; heapDesc.NodeMask = 0; HRESULT result = _device->GetDevice()->CreateQueryHeap(&heapDesc, IID_PPV_ARGS(&_queryHeap)); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); DX_SET_DEBUG_NAME(_queryHeap, "Query Heap"); // Create the result buffer @@ -64,7 +64,7 @@ bool QueryHeapDX12::Init() resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; resourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE; result = _device->GetDevice()->CreateCommittedResource(&heapProperties, D3D12_HEAP_FLAG_NONE, &resourceDesc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&_resultBuffer)); - LOG_DIRECTX_RESULT_WITH_RETURN(result); + LOG_DIRECTX_RESULT_WITH_RETURN(result, true); DX_SET_DEBUG_NAME(_resultBuffer, "Query Heap Result Buffer"); // Start out with an open query batch @@ -181,7 +181,7 @@ void* QueryHeapDX12::ResolveQuery(ElementHandle& handle) range.Begin = batch.Start * _resultSize; range.End = range.Begin + batch.Count * _resultSize; void* mapped = nullptr; - VALIDATE_DIRECTX_RESULT(_resultBuffer->Map(0, &range, &mapped)); + VALIDATE_DIRECTX_CALL(_resultBuffer->Map(0, &range, &mapped)); // Copy the results data Platform::MemoryCopy(_resultData.Get() + range.Begin, (byte*)mapped + range.Begin, batch.Count * _resultSize); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.cpp index 27aa4c697..11f1b7ffe 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.cpp @@ -229,7 +229,7 @@ UploadBufferPageDX12::UploadBufferPageDX12(GPUDeviceDX12* device, uint64 size) resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; resourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE; ID3D12Resource* resource; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateCommittedResource(&heapProperties, D3D12_HEAP_FLAG_NONE, &resourceDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&resource))); + VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateCommittedResource(&heapProperties, D3D12_HEAP_FLAG_NONE, &resourceDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&resource))); // Set state initResource(resource, D3D12_RESOURCE_STATE_GENERIC_READ, 1); @@ -238,7 +238,7 @@ UploadBufferPageDX12::UploadBufferPageDX12(GPUDeviceDX12* device, uint64 size) GPUAddress = _resource->GetGPUVirtualAddress(); // Map buffer - VALIDATE_DIRECTX_RESULT(_resource->Map(0, nullptr, &CPUAddress)); + VALIDATE_DIRECTX_CALL(_resource->Map(0, nullptr, &CPUAddress)); } void UploadBufferPageDX12::OnReleaseGPU() diff --git a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.h b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.h index 61d32cff0..ef783b218 100644 --- a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.h +++ b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.h @@ -272,15 +272,15 @@ namespace RenderToolsDX #if GPU_ENABLE_ASSERTION // DirectX results validation -#define VALIDATE_DIRECTX_RESULT(x) { HRESULT result = x; if (FAILED(result)) RenderToolsDX::ValidateD3DResult(result, __FILE__, __LINE__); } +#define VALIDATE_DIRECTX_CALL(x) { HRESULT result = x; if (FAILED(result)) RenderToolsDX::ValidateD3DResult(result, __FILE__, __LINE__); } #define LOG_DIRECTX_RESULT(result) if (FAILED(result)) RenderToolsDX::LogD3DResult(result, __FILE__, __LINE__) -#define LOG_DIRECTX_RESULT_WITH_RETURN(result) if (FAILED(result)) { RenderToolsDX::LogD3DResult(result, __FILE__, __LINE__); return true; } +#define LOG_DIRECTX_RESULT_WITH_RETURN(result, returnValue) if (FAILED(result)) { RenderToolsDX::LogD3DResult(result, __FILE__, __LINE__); return returnValue; } #else -#define VALIDATE_DIRECTX_RESULT(x) x +#define VALIDATE_DIRECTX_CALL(x) x #define LOG_DIRECTX_RESULT(result) if(FAILED(result)) RenderToolsDX::LogD3DResult(result) -#define LOG_DIRECTX_RESULT_WITH_RETURN(result) if(FAILED(result)) { RenderToolsDX::LogD3DResult(result); return true; } +#define LOG_DIRECTX_RESULT_WITH_RETURN(result, returnValue) if(FAILED(result)) { RenderToolsDX::LogD3DResult(result); return returnValue; } #endif diff --git a/Source/Engine/Level/Actor.cs b/Source/Engine/Level/Actor.cs index 661b350cb..dbe8a89b5 100644 --- a/Source/Engine/Level/Actor.cs +++ b/Source/Engine/Level/Actor.cs @@ -364,13 +364,20 @@ namespace FlaxEngine /// The point (world-space). /// The axis (normalized). /// The angle (in degrees). - public void RotateAround(Vector3 point, Vector3 axis, float angle) + /// /// Whether to orient the actor the same amount as rotation. + public void RotateAround(Vector3 point, Vector3 axis, float angle, bool orientActor = true) { var transform = Transform; var q = Quaternion.RotationAxis(axis, angle * Mathf.DegreesToRadians); - var dif = (transform.Translation - point) * q; - transform.Translation = point + dif; - transform.Orientation = q; + if (Vector3.NearEqual(point, transform.Translation) && orientActor) + transform.Orientation *= q; + else + { + var dif = (transform.Translation - point) * q; + transform.Translation = point + dif; + if (orientActor) + transform.Orientation *= q; + } Transform = transform; } diff --git a/Source/Engine/Level/Actors/Skybox.cpp b/Source/Engine/Level/Actors/Skybox.cpp index 7d35a2776..455c229c0 100644 --- a/Source/Engine/Level/Actors/Skybox.cpp +++ b/Source/Engine/Level/Actors/Skybox.cpp @@ -100,6 +100,7 @@ void Skybox::ApplySky(GPUContext* context, RenderContext& renderContext, const M Platform::MemoryClear(&drawCall, sizeof(DrawCall)); drawCall.World = world; drawCall.ObjectPosition = drawCall.World.GetTranslation(); + drawCall.ObjectRadius = _sphere.Radius; drawCall.Surface.GeometrySize = _box.GetSize(); drawCall.WorldDeterminantSign = Math::FloatSelect(world.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = GetPerInstanceRandom(); diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index 6ca841b41..6b7916e4d 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -405,6 +405,7 @@ void SplineModel::Draw(RenderContext& renderContext) const Transform splineTransform = GetTransform(); renderContext.View.GetWorldMatrix(splineTransform, drawCall.World); drawCall.ObjectPosition = drawCall.World.GetTranslation() + drawCall.Deformable.LocalMatrix.GetTranslation(); + drawCall.ObjectRadius = _sphere.Radius; // TODO: use radius for the spline chunk rather than whole spline const float worldDeterminantSign = drawCall.World.RotDeterminant() * drawCall.Deformable.LocalMatrix.RotDeterminant(); for (int32 segment = 0; segment < _instances.Count(); segment++) { diff --git a/Source/Engine/Level/Scene/SceneTicking.cpp b/Source/Engine/Level/Scene/SceneTicking.cpp index 235d7bbab..34de25a2e 100644 --- a/Source/Engine/Level/Scene/SceneTicking.cpp +++ b/Source/Engine/Level/Scene/SceneTicking.cpp @@ -32,7 +32,7 @@ void SceneTicking::TickData::RemoveTick(void* callee) { for (int32 i = 0; i < Ticks.Count(); i++) { - if (Ticks[i].Callee == callee) + if (Ticks.Get()[i].Callee == callee) { Ticks.RemoveAt(i); break; @@ -45,7 +45,7 @@ void SceneTicking::TickData::Tick() TickScripts(Scripts); for (int32 i = 0; i < Ticks.Count(); i++) - Ticks[i].Call(); + Ticks.Get()[i].Call(); } #if USE_EDITOR @@ -54,7 +54,7 @@ void SceneTicking::TickData::RemoveTickExecuteInEditor(void* callee) { for (int32 i = 0; i < TicksExecuteInEditor.Count(); i++) { - if (TicksExecuteInEditor[i].Callee == callee) + if (TicksExecuteInEditor.Get()[i].Callee == callee) { TicksExecuteInEditor.RemoveAt(i); break; @@ -67,7 +67,7 @@ void SceneTicking::TickData::TickExecuteInEditor() TickScripts(ScriptsExecuteInEditor); for (int32 i = 0; i < TicksExecuteInEditor.Count(); i++) - TicksExecuteInEditor[i].Call(); + TicksExecuteInEditor.Get()[i].Call(); } #endif diff --git a/Source/Engine/Main/Main.Build.cs b/Source/Engine/Main/Main.Build.cs index 27c9fb611..120d0bc5c 100644 --- a/Source/Engine/Main/Main.Build.cs +++ b/Source/Engine/Main/Main.Build.cs @@ -81,5 +81,6 @@ public class Main : EngineModule /// public override void GetFilesToDeploy(List files) { + files.Add(Path.Combine(FolderPath, "Android/android_native_app_glue.h")); } } diff --git a/Source/Engine/Navigation/NavMeshRuntime.cpp b/Source/Engine/Navigation/NavMeshRuntime.cpp index b09f8346a..acb73a8d5 100644 --- a/Source/Engine/Navigation/NavMeshRuntime.cpp +++ b/Source/Engine/Navigation/NavMeshRuntime.cpp @@ -21,6 +21,7 @@ namespace FORCE_INLINE void InitFilter(dtQueryFilter& filter) { Platform::MemoryCopy(filter.m_areaCost, NavMeshRuntime::NavAreasCosts, sizeof(NavMeshRuntime::NavAreasCosts)); + static_assert(sizeof(dtQueryFilter::m_areaCost) == sizeof(NavMeshRuntime::NavAreasCosts), "Invalid navmesh area cost list."); } } diff --git a/Source/Engine/Networking/NetworkInternal.h b/Source/Engine/Networking/NetworkInternal.h index 521e8a7a2..9484337d1 100644 --- a/Source/Engine/Networking/NetworkInternal.h +++ b/Source/Engine/Networking/NetworkInternal.h @@ -3,6 +3,9 @@ #pragma once #include "Types.h" +#if COMPILE_WITH_PROFILER +#include "Engine/Core/Collections/Dictionary.h" +#endif enum class NetworkMessageIDs : uint8 { @@ -35,4 +38,22 @@ public: static void OnNetworkMessageObjectDespawn(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer); static void OnNetworkMessageObjectRole(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer); static void OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer); + +#if COMPILE_WITH_PROFILER + + struct ProfilerEvent + { + uint16 Count = 0; + uint16 DataSize = 0; + uint16 MessageSize = 0; + uint16 Receivers = 0; + }; + + /// + /// Enables network usage profiling tools. Captures network objects replication and RPCs send statistics. + /// + static bool EnableProfiling; + + static Dictionary, ProfilerEvent> ProfilerEvents; +#endif }; diff --git a/Source/Engine/Networking/NetworkPeer.cpp b/Source/Engine/Networking/NetworkPeer.cpp index d86824156..b39b617e0 100644 --- a/Source/Engine/Networking/NetworkPeer.cpp +++ b/Source/Engine/Networking/NetworkPeer.cpp @@ -8,9 +8,10 @@ #include "Engine/Platform/CPUInfo.h" #include "Engine/Profiler/ProfilerCPU.h" +Array NetworkPeer::Peers; + namespace { - Array Peers; uint32 LastHostId = 0; } diff --git a/Source/Engine/Networking/NetworkPeer.h b/Source/Engine/Networking/NetworkPeer.h index 06d8c1ab9..6b36e3278 100644 --- a/Source/Engine/Networking/NetworkPeer.h +++ b/Source/Engine/Networking/NetworkPeer.h @@ -15,6 +15,9 @@ API_CLASS(sealed, NoSpawn, Namespace = "FlaxEngine.Networking") class FLAXENGINE { DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(NetworkPeer, ScriptingObject); + // List with all active peers. + API_FIELD(ReadOnly) static Array Peers; + public: int HostId = -1; NetworkConfig Config; diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 8133b6cf2..316ac4ae7 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -40,6 +40,11 @@ bool NetworkReplicator::EnableLog = false; #define NETWORK_REPLICATOR_LOG(messageType, format, ...) #endif +#if COMPILE_WITH_PROFILER +bool NetworkInternal::EnableProfiling = false; +Dictionary, NetworkInternal::ProfilerEvent> NetworkInternal::ProfilerEvents; +#endif + PACK_STRUCT(struct NetworkMessageObjectReplicate { NetworkMessageIDs ID = NetworkMessageIDs::ObjectReplicate; @@ -1806,6 +1811,7 @@ void NetworkInternal::NetworkReplicatorUpdate() NetworkMessage msg = peer->BeginSendMessage(); msg.WriteStructure(msgData); msg.WriteBytes(stream->GetBuffer(), msgDataSize); + uint32 dataSize = msgDataSize, messageSize = msg.Length; if (isClient) peer->EndSendMessage(NetworkChannelType::Unreliable, msg); else @@ -1824,6 +1830,8 @@ void NetworkInternal::NetworkReplicatorUpdate() msg = peer->BeginSendMessage(); msg.WriteStructure(msgDataPart); msg.WriteBytes(stream->GetBuffer() + msgDataPart.PartStart, msgDataPart.PartSize); + messageSize += msg.Length; + dataSize += msgDataPart.PartSize; dataStart += msgDataPart.PartSize; if (isClient) peer->EndSendMessage(NetworkChannelType::Unreliable, msg); @@ -1832,7 +1840,18 @@ void NetworkInternal::NetworkReplicatorUpdate() } ASSERT_LOW_LAYER(dataStart == size); - // TODO: stats for bytes send per object type +#if COMPILE_WITH_PROFILER + // Network stats recording + if (EnableProfiling) + { + const Pair name(obj->GetTypeHandle(), StringAnsiView::Empty); + auto& profileEvent = ProfilerEvents[name]; + profileEvent.Count++; + profileEvent.DataSize += dataSize; + profileEvent.MessageSize += messageSize; + profileEvent.Receivers += isClient ? 1 : CachedTargets.Count(); + } +#endif } } @@ -1873,6 +1892,7 @@ void NetworkInternal::NetworkReplicatorUpdate() NetworkMessage msg = peer->BeginSendMessage(); msg.WriteStructure(msgData); msg.WriteBytes(e.ArgsData.Get(), e.ArgsData.Length()); + uint32 dataSize = e.ArgsData.Length(), messageSize = msg.Length, receivers = 0; NetworkChannelType channel = (NetworkChannelType)e.Info.Channel; if (e.Info.Server && isClient) { @@ -1882,13 +1902,27 @@ void NetworkInternal::NetworkReplicatorUpdate() NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Server RPC '{}::{}' called with non-empty list of targets is not supported (only server will receive it)", e.Name.First.ToString(), e.Name.Second.ToString()); #endif peer->EndSendMessage(channel, msg); + receivers = 1; } else if (e.Info.Client && (isServer || isHost)) { // Server -> Client(s) BuildCachedTargets(NetworkManager::Clients, item.TargetClientIds, e.Targets, NetworkManager::LocalClientId); peer->EndSendMessage(channel, msg, CachedTargets); + receivers = CachedTargets.Count(); } + +#if COMPILE_WITH_PROFILER + // Network stats recording + if (EnableProfiling && receivers) + { + auto& profileEvent = ProfilerEvents[e.Name]; + profileEvent.Count++; + profileEvent.DataSize += dataSize; + profileEvent.MessageSize += messageSize; + profileEvent.Receivers += receivers; + } +#endif } RpcQueue.Clear(); } diff --git a/Source/Engine/Networking/NetworkStats.h b/Source/Engine/Networking/NetworkStats.h index 42c946aa0..e6f031f3b 100644 --- a/Source/Engine/Networking/NetworkStats.h +++ b/Source/Engine/Networking/NetworkStats.h @@ -8,7 +8,7 @@ /// /// The network transport driver statistics container. Contains information about INetworkDriver usage and performance. /// -API_STRUCT(Namespace="FlaxEngine.Networking") struct FLAXENGINE_API NetworkDriverStats +API_STRUCT(Namespace="FlaxEngine.Networking", NoDefault) struct FLAXENGINE_API NetworkDriverStats { DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkDriverStats); diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp index 8a767ba22..788a4263f 100644 --- a/Source/Engine/Particles/ParticleEffect.cpp +++ b/Source/Engine/Particles/ParticleEffect.cpp @@ -402,7 +402,7 @@ SceneRenderTask* ParticleEffect::GetRenderTask() const #if USE_EDITOR -Array ParticleEffect::GetParametersOverrides() +Array& ParticleEffect::GetParametersOverrides() { CacheModifiedParameters(); return _parametersOverrides; @@ -461,7 +461,6 @@ void ParticleEffect::CacheModifiedParameters() { if (_parameters.IsEmpty()) return; - _parametersOverrides.Clear(); auto& parameters = GetParameters(); for (auto& param : parameters) diff --git a/Source/Engine/Particles/ParticleEffect.h b/Source/Engine/Particles/ParticleEffect.h index bfee23a3b..9e9792a4c 100644 --- a/Source/Engine/Particles/ParticleEffect.h +++ b/Source/Engine/Particles/ParticleEffect.h @@ -382,7 +382,7 @@ public: #if USE_EDITOR protected: // Exposed parameters overrides for Editor Undo. - API_PROPERTY(Attributes="HideInEditor, Serialize") Array GetParametersOverrides(); + API_PROPERTY(Attributes="HideInEditor, Serialize") Array& GetParametersOverrides(); API_PROPERTY() void SetParametersOverrides(const Array& value); #endif diff --git a/Source/Engine/Particles/Particles.cpp b/Source/Engine/Particles/Particles.cpp index 873c4bd52..52234fd7a 100644 --- a/Source/Engine/Particles/Particles.cpp +++ b/Source/Engine/Particles/Particles.cpp @@ -938,7 +938,8 @@ void Particles::DrawParticles(RenderContext& renderContext, ParticleEffect* effe // Setup a draw call common data DrawCall drawCall; drawCall.PerInstanceRandom = effect->GetPerInstanceRandom(); - drawCall.ObjectPosition = effect->GetPosition(); + drawCall.ObjectPosition = effect->GetSphere().Center - view.Origin; + drawCall.ObjectRadius = effect->GetSphere().Radius; // Draw all emitters for (int32 emitterIndex = 0; emitterIndex < effect->Instance.Emitters.Count(); emitterIndex++) diff --git a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp index 4878750eb..42f9b7a59 100644 --- a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp +++ b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp @@ -15,9 +15,11 @@ namespace { if (collection.IsEmpty()) return; + const auto c = collection.Get(); for (int32 i = 0; i < collection.Count(); i++) { - if (collection[i].First == collider || collection[i].Second == collider) + const SimulationEventCallback::CollidersPair cc = c[i]; + if (cc.First == collider || cc.Second == collider) { collection.RemoveAt(i--); if (collection.IsEmpty()) @@ -32,7 +34,8 @@ namespace return; for (auto i = collection.Begin(); i.IsNotEnd(); ++i) { - if (i->Key.First == collider || i->Key.Second == collider) + const SimulationEventCallback::CollidersPair cc = i->Key; + if (cc.First == collider || cc.Second == collider) { collection.Remove(i); if (collection.IsEmpty()) diff --git a/Source/Engine/Platform/Apple/ApplePlatform.cpp b/Source/Engine/Platform/Apple/ApplePlatform.cpp index 8ae5a484f..473fc0987 100644 --- a/Source/Engine/Platform/Apple/ApplePlatform.cpp +++ b/Source/Engine/Platform/Apple/ApplePlatform.cpp @@ -70,6 +70,53 @@ CFStringRef AppleUtils::ToString(const StringView& str) return CFStringCreateWithBytes(nullptr, (const UInt8*)str.GetText(), str.Length() * sizeof(Char), kCFStringEncodingUTF16LE, false); } +NSString* AppleUtils::ToNSString(const StringView& str) +{ + NSString* ret = !str.IsEmpty() ? [[NSString alloc] initWithBytes: (const UInt8*)str.Get() length: str.Length() * sizeof(Char) encoding: NSUTF16LittleEndianStringEncoding] : nil; + return ret ? ret : @""; +} + +NSString* AppleUtils::ToNSString(const char* string) +{ + NSString* ret = string ? [NSString stringWithUTF8String: string] : nil; + return ret ? ret : @""; +} + + +NSArray* AppleUtils::ParseArguments(NSString* argsString) { + NSMutableArray *argsArray = [NSMutableArray array]; + NSMutableString *currentArg = [NSMutableString string]; + BOOL insideQuotes = NO; + + for (NSInteger i = 0; i < argsString.length; ++i) { + unichar c = [argsString characterAtIndex:i]; + + if (c == '\"') { + if (insideQuotes) { + [argsArray addObject:[currentArg copy]]; + [currentArg setString:@""]; + insideQuotes = NO; + } else { + insideQuotes = YES; + } + } else if (c == ' ' && !insideQuotes) { + if (currentArg.length > 0) { + [argsArray addObject:[currentArg copy]]; + [currentArg setString:@""]; + } + } else { + [currentArg appendFormat:@"%C", c]; + } + } + + if (currentArg.length > 0) { + [argsArray addObject:[currentArg copy]]; + } + + return [argsArray copy]; +} + + typedef uint16_t offset_t; #define align_mem_up(num, align) (((num) + ((align) - 1)) & ~((align) - 1)) diff --git a/Source/Engine/Platform/Apple/AppleUtils.h b/Source/Engine/Platform/Apple/AppleUtils.h index d6f639042..7144ba14b 100644 --- a/Source/Engine/Platform/Apple/AppleUtils.h +++ b/Source/Engine/Platform/Apple/AppleUtils.h @@ -13,6 +13,9 @@ class AppleUtils public: static String ToString(CFStringRef str); static CFStringRef ToString(const StringView& str); + static NSString* ToNSString(const StringView& str); + static NSString* ToNSString(const char* string); + static NSArray* ParseArguments(NSString* argsString); #if PLATFORM_MAC static Float2 PosToCoca(const Float2& pos); static Float2 CocaToPos(const Float2& pos); diff --git a/Source/Engine/Platform/CreateWindowSettings.cs b/Source/Engine/Platform/CreateWindowSettings.cs index 1e5d17484..d4d9ce727 100644 --- a/Source/Engine/Platform/CreateWindowSettings.cs +++ b/Source/Engine/Platform/CreateWindowSettings.cs @@ -23,6 +23,7 @@ namespace FlaxEngine AllowDragAndDrop = true, IsRegularWindow = true, HasSizingFrame = true, + ShowAfterFirstPaint = true, }; } } diff --git a/Source/Engine/Platform/CreateWindowSettings.h b/Source/Engine/Platform/CreateWindowSettings.h index 71bbf58c3..1ff596df9 100644 --- a/Source/Engine/Platform/CreateWindowSettings.h +++ b/Source/Engine/Platform/CreateWindowSettings.h @@ -131,7 +131,7 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(CreateWindowSettings); /// /// Enable/disable window auto-show after the first paint. /// - API_FIELD() bool ShowAfterFirstPaint = false; + API_FIELD() bool ShowAfterFirstPaint = true; /// /// The custom data (platform dependant). diff --git a/Source/Engine/Platform/FileSystemWatcher.h b/Source/Engine/Platform/FileSystemWatcher.h index c6b44ca80..1f02f5725 100644 --- a/Source/Engine/Platform/FileSystemWatcher.h +++ b/Source/Engine/Platform/FileSystemWatcher.h @@ -6,6 +6,8 @@ #include "Windows/WindowsFileSystemWatcher.h" #elif PLATFORM_LINUX #include "Linux/LinuxFileSystemWatcher.h" +#elif PLATFORM_MAC +#include "Mac/MacFileSystemWatcher.h" #else #include "Base/FileSystemWatcherBase.h" #endif diff --git a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp index f0d5ff499..279813470 100644 --- a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp +++ b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp @@ -136,7 +136,7 @@ bool LinuxFileSystem::ShowFileExplorer(const StringView& path) { const StringAsANSI<> pathAnsi(*path, path.Length()); char cmd[2048]; - sprintf(cmd, "nautilus %s &", pathAnsi.Get()); + sprintf(cmd, "xdg-open %s &", pathAnsi.Get()); system(cmd); return false; } diff --git a/Source/Engine/Platform/Mac/MacFileSystem.cpp b/Source/Engine/Platform/Mac/MacFileSystem.cpp index 5efa1905b..c765feef4 100644 --- a/Source/Engine/Platform/Mac/MacFileSystem.cpp +++ b/Source/Engine/Platform/Mac/MacFileSystem.cpp @@ -132,7 +132,7 @@ bool MacFileSystem::ShowBrowseFolderDialog(Window* parentWindow, const StringVie bool MacFileSystem::ShowFileExplorer(const StringView& path) { - return Platform::StartProcess(TEXT("open"), String::Format(TEXT("\"{0}\""), path), StringView::Empty) != 0; + return [[NSWorkspace sharedWorkspace] selectFile: AppleUtils::ToNSString(FileSystem::ConvertRelativePathToAbsolute(path)) inFileViewerRootedAtPath: @""]; } #endif diff --git a/Source/Engine/Platform/Mac/MacFileSystemWatcher.cpp b/Source/Engine/Platform/Mac/MacFileSystemWatcher.cpp new file mode 100644 index 000000000..be13e2737 --- /dev/null +++ b/Source/Engine/Platform/Mac/MacFileSystemWatcher.cpp @@ -0,0 +1,100 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#if PLATFORM_MAC +#include "MacFileSystemWatcher.h" +#include "Engine/Platform/Apple/AppleUtils.h" +#include "Engine/Platform/CriticalSection.h" +#include "Engine/Platform/Thread.h" +#include "Engine/Threading/ThreadSpawner.h" +#include "Engine/Core/Collections/Array.h" +#include "Engine/Core/Types/StringView.h" + +void DirectoryWatchCallback( ConstFSEventStreamRef StreamRef, void* FileWatcherPtr, size_t EventCount, void* EventPaths, const FSEventStreamEventFlags EventFlags[], const FSEventStreamEventId EventIDs[] ) +{ + MacFileSystemWatcher* macFileSystemWatcher = (MacFileSystemWatcher*)FileWatcherPtr; + if (macFileSystemWatcher) + { + CFArrayRef EventPathArray = (CFArrayRef)EventPaths; + for( size_t EventIndex = 0; EventIndex < EventCount; ++EventIndex ) + { + const FSEventStreamEventFlags Flags = EventFlags[EventIndex]; + if( !(Flags & kFSEventStreamEventFlagItemIsFile) && !(Flags & kFSEventStreamEventFlagItemIsDir) ) + { + // events about symlinks don't concern us + continue; + } + + auto action = FileSystemAction::Unknown; + + const bool added = ( Flags & kFSEventStreamEventFlagItemCreated ); + const bool renamed = ( Flags & kFSEventStreamEventFlagItemRenamed ); + const bool modified = ( Flags & kFSEventStreamEventFlagItemModified ); + const bool removed = ( Flags & kFSEventStreamEventFlagItemRemoved ); + + if (added) + { + action = FileSystemAction::Create; + } + + if (renamed || modified) + { + action = FileSystemAction::Delete; + } + + if (removed) + { + action = FileSystemAction::Modify; + } + + const String resolvedPath = AppleUtils::ToString((CFStringRef)CFArrayGetValueAtIndex(EventPathArray,EventIndex)); + + macFileSystemWatcher->OnEvent(resolvedPath, action); + } + } +} + +MacFileSystemWatcher::MacFileSystemWatcher(const String& directory, bool withSubDirs) + : FileSystemWatcherBase(directory, withSubDirs) +{ + + CFStringRef FullPathMac = AppleUtils::ToString(StringView(directory)); + CFArrayRef PathsToWatch = CFArrayCreate(NULL, (const void**)&FullPathMac, 1, NULL); + + CFAbsoluteTime Latency = 0.2; + + FSEventStreamContext Context; + Context.version = 0; + Context.info = this; + Context.retain = NULL; + Context.release = NULL; + Context.copyDescription = NULL; + + EventStream = FSEventStreamCreate( NULL, + &DirectoryWatchCallback, + &Context, + PathsToWatch, + kFSEventStreamEventIdSinceNow, + Latency, + kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents + ); + + CFRelease(PathsToWatch); + CFRelease(FullPathMac); + + FSEventStreamScheduleWithRunLoop( EventStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode ); + FSEventStreamStart( EventStream ); + + IsRunning = true; +} + +MacFileSystemWatcher::~MacFileSystemWatcher() +{ + if (IsRunning) + { + FSEventStreamStop(EventStream); + FSEventStreamUnscheduleFromRunLoop(EventStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + FSEventStreamInvalidate(EventStream); + FSEventStreamRelease(EventStream); + } +} +#endif diff --git a/Source/Engine/Platform/Mac/MacFileSystemWatcher.h b/Source/Engine/Platform/Mac/MacFileSystemWatcher.h new file mode 100644 index 000000000..97ca20bda --- /dev/null +++ b/Source/Engine/Platform/Mac/MacFileSystemWatcher.h @@ -0,0 +1,39 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#if PLATFORM_MAC +#include "Engine/Platform/Base/FileSystemWatcherBase.h" + +#include + + +/// +/// Mac platform implementation of the file system watching object. +/// +class FLAXENGINE_API MacFileSystemWatcher : public FileSystemWatcherBase +{ +public: + + /// + /// Initializes a new instance of the class. + /// + /// The directory to watch. + /// True if monitor the directory tree rooted at the specified directory or just a given directory. + MacFileSystemWatcher(const String& directory, bool withSubDirs); + + /// + /// Finalizes an instance of the class. + /// + ~MacFileSystemWatcher(); + +public: + + + +private: + + FSEventStreamRef EventStream; + bool IsRunning; +}; +#endif diff --git a/Source/Engine/Platform/Mac/MacPlatform.cpp b/Source/Engine/Platform/Mac/MacPlatform.cpp index 46b978e1d..5750cd260 100644 --- a/Source/Engine/Platform/Mac/MacPlatform.cpp +++ b/Source/Engine/Platform/Mac/MacPlatform.cpp @@ -324,13 +324,16 @@ void MacPlatform::BeforeRun() void MacPlatform::Tick() { // Process system events - while (true) + NSEvent* event = nil; + do { - NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES]; - if (event == nil) - break; - [NSApp sendEvent:event]; - } + event = [NSApp nextEventMatchingMask: NSEventMaskAny untilDate: nil inMode: NSDefaultRunLoopMode dequeue: YES]; + if (event) + { + [NSApp sendEvent:event]; + } + + } while(event); ApplePlatform::Tick(); } @@ -429,13 +432,6 @@ Window* MacPlatform::CreateWindow(const CreateWindowSettings& settings) int32 MacPlatform::CreateProcess(CreateProcessSettings& settings) { LOG(Info, "Command: {0} {1}", settings.FileName, settings.Arguments); - String cwd; - if (settings.WorkingDirectory.HasChars()) - { - LOG(Info, "Working directory: {0}", settings.WorkingDirectory); - cwd = Platform::GetWorkingDirectory(); - Platform::SetWorkingDirectory(settings.WorkingDirectory); - } const bool captureStdOut = settings.LogOutput || settings.SaveOutput; // Special case if filename points to the app package (use actual executable) @@ -462,44 +458,81 @@ int32 MacPlatform::CreateProcess(CreateProcessSettings& settings) } } - const String cmdLine = exePath + TEXT(" ") + settings.Arguments; - const StringAsANSI<> cmdLineAnsi(*cmdLine, cmdLine.Length()); - FILE* pipe = popen(cmdLineAnsi.Get(), "r"); - if (cwd.Length() != 0) - { - Platform::SetWorkingDirectory(cwd); - } - if (!pipe) - { - LOG(Warning, "Failed to start process, errno={}", errno); - return -1; - } - - // TODO: environment + NSTask *task = [[NSTask alloc] init]; + task.launchPath = AppleUtils::ToNSString(exePath); + task.arguments = AppleUtils::ParseArguments(AppleUtils::ToNSString(settings.Arguments)); + if (settings.WorkingDirectory.HasChars()) + task.currentDirectoryPath = AppleUtils::ToNSString(settings.WorkingDirectory); int32 returnCode = 0; if (settings.WaitForEnd) { + id outputObserver = nil; + if (captureStdOut) { - char lineBuffer[1024]; - while (fgets(lineBuffer, sizeof(lineBuffer), pipe) != NULL) + NSPipe *stdoutPipe = [NSPipe pipe]; + [task setStandardOutput:stdoutPipe]; + + outputObserver = [[NSNotificationCenter defaultCenter] + addObserverForName: NSFileHandleDataAvailableNotification + object: [stdoutPipe fileHandleForReading] + queue: nil + usingBlock:^(NSNotification* notification) { - char* p = lineBuffer + strlen(lineBuffer) - 1; - if (*p == '\n') *p = 0; - String line(lineBuffer); - if (settings.SaveOutput) - settings.Output.Add(line.Get(), line.Length()); - if (settings.LogOutput) - Log::Logger::Write(LogType::Info, line); + NSData* data = [stdoutPipe fileHandleForReading].availableData; + if (data.length) + { + String line((const char*)data.bytes, data.length); + if (settings.SaveOutput) + settings.Output.Add(line.Get(), line.Length()); + if (settings.LogOutput) + { + StringView lineView(line); + if (line[line.Length() - 1] == '\n') + lineView = StringView(line.Get(), line.Length() - 1); + Log::Logger::Write(LogType::Info, lineView); + } + [[stdoutPipe fileHandleForReading] waitForDataInBackgroundAndNotify]; + } } + ]; + + [[stdoutPipe fileHandleForReading] waitForDataInBackgroundAndNotify]; } - else + + String exception; + @try { - while (!feof(pipe)) - { - sleep(1); - } + [task launch]; + [task waitUntilExit]; + returnCode = [task terminationStatus]; + } + @catch (NSException* e) + { + exception = e.reason.UTF8String; + } + if (!exception.IsEmpty()) + { + LOG(Error, "Failed to run command {0} {1} with error {2}", settings.FileName, settings.Arguments, exception); + returnCode = -1; + } + } + else + { + String exception; + @try + { + [task launch]; + } + @catch (NSException* e) + { + exception = e.reason.UTF8String; + } + if (!exception.IsEmpty()) + { + LOG(Error, "Failed to run command {0} {1} with error {2}", settings.FileName, settings.Arguments, exception); + returnCode = -1; } } diff --git a/Source/Engine/Platform/Types.h b/Source/Engine/Platform/Types.h index 76135dcaf..863743121 100644 --- a/Source/Engine/Platform/Types.h +++ b/Source/Engine/Platform/Types.h @@ -237,8 +237,8 @@ class UnixConditionVariable; typedef UnixConditionVariable ConditionVariable; class MacFileSystem; typedef MacFileSystem FileSystem; -class FileSystemWatcherBase; -typedef FileSystemWatcherBase FileSystemWatcher; +class MacFileSystemWatcher; +typedef MacFileSystemWatcher FileSystemWatcher; class UnixFile; typedef UnixFile File; class MacPlatform; diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index 970ccfa10..c5bc44ab9 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -30,6 +30,9 @@ #endif #include "resource.h" +#define CLR_EXCEPTION 0xE0434352 +#define VCPP_EXCEPTION 0xE06D7363 + const Char* WindowsPlatform::ApplicationWindowClass = TEXT("FlaxWindow"); void* WindowsPlatform::Instance = nullptr; @@ -272,6 +275,12 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) LONG CALLBACK SehExceptionHandler(EXCEPTION_POINTERS* ep) { + if (ep->ExceptionRecord->ExceptionCode == CLR_EXCEPTION) + { + // Pass CLR exceptions back to runtime + return EXCEPTION_CONTINUE_SEARCH; + } + // Skip if engine already crashed if (Globals::FatalErrorOccurred) return EXCEPTION_CONTINUE_SEARCH; diff --git a/Source/Engine/Profiler/ProfilerCPU.cpp b/Source/Engine/Profiler/ProfilerCPU.cpp index 417178747..7b9e2c6e3 100644 --- a/Source/Engine/Profiler/ProfilerCPU.cpp +++ b/Source/Engine/Profiler/ProfilerCPU.cpp @@ -129,8 +129,15 @@ void ProfilerCPU::Thread::EndEvent() { const double time = Platform::GetTimeSeconds() * 1000.0; _depth--; - Event& e = (Buffer.Last()--).Event(); - e.End = time; + for (auto i = Buffer.Last(); i != Buffer.Begin(); --i) + { + Event& e = i.Event(); + if (e.End <= 0) + { + e.End = time; + break; + } + } } bool ProfilerCPU::IsProfilingCurrentThread() @@ -205,7 +212,7 @@ int32 ProfilerCPU::BeginEvent(const char* name) void ProfilerCPU::EndEvent(int32 index) { - if (Enabled && Thread::Current) + if (index != -1 && Thread::Current) Thread::Current->EndEvent(index); } diff --git a/Source/Engine/Profiler/ProfilerCPU.h b/Source/Engine/Profiler/ProfilerCPU.h index 124082c9b..ccfeb8ac4 100644 --- a/Source/Engine/Profiler/ProfilerCPU.h +++ b/Source/Engine/Profiler/ProfilerCPU.h @@ -121,15 +121,39 @@ public: EventBuffer* _buffer; int32 _index; - Iterator(EventBuffer* buffer, const int32 index) + FORCE_INLINE Iterator(EventBuffer* buffer, const int32 index) : _buffer(buffer) , _index(index) { } - Iterator(const Iterator& i) = default; - public: + FORCE_INLINE Iterator(const Iterator& other) + : _buffer(other._buffer) + , _index(other._index) + { + } + + FORCE_INLINE Iterator(Iterator&& other) noexcept + : _buffer(other._buffer) + , _index(other._index) + { + } + + FORCE_INLINE Iterator& operator=(Iterator&& other) + { + _buffer = other._buffer; + _index = other._index; + return *this; + } + + FORCE_INLINE Iterator& operator=(const Iterator& other) + { + _buffer = other._buffer; + _index = other._index; + return *this; + } + FORCE_INLINE int32 Index() const { return _index; @@ -141,15 +165,13 @@ public: return _buffer->Get(_index); } - bool IsEnd() const + FORCE_INLINE bool IsEnd() const { - ASSERT_LOW_LAYER(_buffer); return _index == _buffer->_head; } - bool IsNotEnd() const + FORCE_INLINE bool IsNotEnd() const { - ASSERT_LOW_LAYER(_buffer); return _index != _buffer->_head; } @@ -164,31 +186,27 @@ public: } public: - Iterator& operator++() + FORCE_INLINE Iterator& operator++() { - ASSERT(_buffer); _index = (_index + 1) & _buffer->_capacityMask; return *this; } - Iterator operator++(int) + FORCE_INLINE Iterator operator++(int) { - ASSERT(_buffer); Iterator temp = *this; _index = (_index + 1) & _buffer->_capacityMask; return temp; } - Iterator& operator--() + FORCE_INLINE Iterator& operator--() { - ASSERT(_buffer); _index = (_index - 1) & _buffer->_capacityMask; return *this; } - Iterator operator--(int) + FORCE_INLINE Iterator operator--(int) { - ASSERT(_buffer); Iterator temp = *this; _index = (_index - 1) & _buffer->_capacityMask; return temp; diff --git a/Source/Engine/Profiler/ProfilerGPU.cpp b/Source/Engine/Profiler/ProfilerGPU.cpp index c100b8df0..3054abe67 100644 --- a/Source/Engine/Profiler/ProfilerGPU.cpp +++ b/Source/Engine/Profiler/ProfilerGPU.cpp @@ -14,7 +14,7 @@ RenderStatsData RenderStatsData::Counter; int32 ProfilerGPU::_depth = 0; Array ProfilerGPU::_timerQueriesPool; Array ProfilerGPU::_timerQueriesFree; -bool ProfilerGPU::Enabled = true; +bool ProfilerGPU::Enabled = false; int32 ProfilerGPU::CurrentBuffer = 0; ProfilerGPU::EventBuffer ProfilerGPU::Buffers[PROFILER_GPU_EVENTS_FRAMES]; diff --git a/Source/Engine/Profiler/ProfilingTools.cpp b/Source/Engine/Profiler/ProfilingTools.cpp index 6ce8082ba..61c0e2c33 100644 --- a/Source/Engine/Profiler/ProfilingTools.cpp +++ b/Source/Engine/Profiler/ProfilingTools.cpp @@ -3,14 +3,17 @@ #if COMPILE_WITH_PROFILER #include "ProfilingTools.h" +#include "Engine/Core/Types/Pair.h" #include "Engine/Engine/Engine.h" #include "Engine/Engine/Time.h" #include "Engine/Engine/EngineService.h" #include "Engine/Graphics/GPUDevice.h" +#include "Engine/Networking/NetworkInternal.h" ProfilingTools::MainStats ProfilingTools::Stats; Array> ProfilingTools::EventsCPU; Array ProfilingTools::EventsGPU; +Array ProfilingTools::EventsNetwork; class ProfilingToolsService : public EngineService { @@ -120,6 +123,40 @@ void ProfilingToolsService::Update() frame.Extract(ProfilingTools::EventsGPU); } + // Get the last events from networking runtime + { + auto& networkEvents = ProfilingTools::EventsNetwork; + networkEvents.Resize(NetworkInternal::ProfilerEvents.Count()); + int32 i = 0; + for (const auto& e : NetworkInternal::ProfilerEvents) + { + const auto& src = e.Value; + auto& dst = networkEvents[i++]; + dst.Count = src.Count; + dst.DataSize = src.DataSize; + dst.MessageSize = src.MessageSize; + dst.Receivers = src.Receivers; + const StringAnsiView& typeName = e.Key.First.GetType().Fullname; + uint64 len = Math::Min(typeName.Length(), ARRAY_COUNT(dst.Name) - 10); + Platform::MemoryCopy(dst.Name, typeName.Get(), len); + const StringAnsiView& name = e.Key.Second; + if (name.HasChars()) + { + uint64 pos = len; + dst.Name[pos++] = ':'; + dst.Name[pos++] = ':'; + len = Math::Min(name.Length(), ARRAY_COUNT(dst.Name) - pos - 1); + Platform::MemoryCopy(dst.Name + pos, name.Get(), len); + dst.Name[pos + len] = 0; + } + else + { + dst.Name[len] = 0; + } + } + NetworkInternal::ProfilerEvents.Clear(); + } + #if 0 // Print CPU events to the log { @@ -173,6 +210,19 @@ void ProfilingToolsService::Dispose() ProfilingTools::EventsCPU.Clear(); ProfilingTools::EventsCPU.SetCapacity(0); ProfilingTools::EventsGPU.SetCapacity(0); + ProfilingTools::EventsNetwork.SetCapacity(0); +} + +bool ProfilingTools::GetEnabled() +{ + return ProfilerCPU::Enabled && ProfilerGPU::Enabled; +} + +void ProfilingTools::SetEnabled(bool enabled) +{ + ProfilerCPU::Enabled = enabled; + ProfilerGPU::Enabled = enabled; + NetworkInternal::EnableProfiling = enabled; } #endif diff --git a/Source/Engine/Profiler/ProfilingTools.h b/Source/Engine/Profiler/ProfilingTools.h index e1c010d77..f4039472f 100644 --- a/Source/Engine/Profiler/ProfilingTools.h +++ b/Source/Engine/Profiler/ProfilingTools.h @@ -105,7 +105,35 @@ public: API_FIELD() Array Events; }; + /// + /// The network stat. + /// + API_STRUCT(NoDefault) struct NetworkEventStat + { + DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkEventStat); + + // Amount of occurrences. + API_FIELD() uint16 Count; + // Transferred data size (in bytes). + API_FIELD() uint16 DataSize; + // Transferred message (data+header) size (in bytes). + API_FIELD() uint16 MessageSize; + // Amount of peers that will receive this message. + API_FIELD() uint16 Receivers; + API_FIELD(Private, NoArray) byte Name[120]; + }; + public: + /// + /// Controls the engine profiler (CPU, GPU, etc.) usage. + /// + API_PROPERTY() static bool GetEnabled(); + + /// + /// Controls the engine profiler (CPU, GPU, etc.) usage. + /// + API_PROPERTY() static void SetEnabled(bool enabled); + /// /// The current collected main stats by the profiler from the local session. Updated every frame. /// @@ -120,6 +148,11 @@ public: /// The GPU rendering profiler events. /// API_FIELD(ReadOnly) static Array EventsGPU; + + /// + /// The networking profiler events. + /// + API_FIELD(ReadOnly) static Array EventsNetwork; }; #endif diff --git a/Source/Engine/Renderer/DrawCall.h b/Source/Engine/Renderer/DrawCall.h index 9fd6c13b5..d51102166 100644 --- a/Source/Engine/Renderer/DrawCall.h +++ b/Source/Engine/Renderer/DrawCall.h @@ -258,6 +258,11 @@ struct DrawCall /// Float3 ObjectPosition; + /// + /// Object bounding sphere radius that contains it whole (sphere at ObjectPosition). + /// + float ObjectRadius; + /// /// The world matrix determinant sign (used for geometry that is two sided or has inverse scale - needs to flip normal vectors and change triangles culling). /// diff --git a/Source/Engine/Renderer/GBufferPass.cpp b/Source/Engine/Renderer/GBufferPass.cpp index 0e5e8508a..f824d2161 100644 --- a/Source/Engine/Renderer/GBufferPass.cpp +++ b/Source/Engine/Renderer/GBufferPass.cpp @@ -464,6 +464,7 @@ void GBufferPass::DrawDecals(RenderContext& renderContext, GPUTextureView* light transform.Scale *= decal->GetSize(); renderContext.View.GetWorldMatrix(transform, drawCall.World); drawCall.ObjectPosition = drawCall.World.GetTranslation(); + drawCall.ObjectRadius = decal->GetSphere().Radius; context->ResetRenderTarget(); diff --git a/Source/Engine/Renderer/HistogramPass.cpp b/Source/Engine/Renderer/HistogramPass.cpp index ed28d27b3..77d228430 100644 --- a/Source/Engine/Renderer/HistogramPass.cpp +++ b/Source/Engine/Renderer/HistogramPass.cpp @@ -26,7 +26,7 @@ PACK_STRUCT(struct HistogramData { GPUBuffer* HistogramPass::Render(RenderContext& renderContext, GPUTexture* colorBuffer) { auto device = GPUDevice::Instance; - auto context = device->GetMainContext();; + auto context = device->GetMainContext(); if (checkIfSkipPass() || !_isSupported) return nullptr; diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index 1a4cc0205..f24f25128 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -855,6 +855,7 @@ DRAW: { auto& batch = BatchedDrawCalls.Get()[list.PreBatchedDrawCalls.Get()[i]]; auto drawCall = batch.DrawCall; + drawCall.ObjectRadius = 0.0f; bindParams.FirstDrawCall = &drawCall; const auto* instancesData = batch.Instances.Get(); diff --git a/Source/Engine/Scripting/Attributes/SerializeAttribute.cs b/Source/Engine/Scripting/Attributes/SerializeAttribute.cs index 5a557902d..da7f8c308 100644 --- a/Source/Engine/Scripting/Attributes/SerializeAttribute.cs +++ b/Source/Engine/Scripting/Attributes/SerializeAttribute.cs @@ -5,7 +5,8 @@ using System; namespace FlaxEngine { /// - /// Indicates that a field or a property of a serializable class should be serialized. This class cannot be inherited. + /// Indicates that a field or a property of a serializable class should be serialized. + /// The attribute is required to show hidden fields in the editor. /// [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class SerializeAttribute : Attribute diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index 6ffdb14a9..7bdffd581 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -1009,12 +1009,36 @@ void ManagedBinaryModule::InitType(MClass* mclass) } if (baseType.Module == this) InitType(baseClass); // Ensure base is initialized before + baseType.Module->TypeNameToTypeIndex.TryGet(baseClass->GetFullName(), *(int32*)&baseType.TypeIndex); + + // So we must special case this flow of a generic class of which its possible the generic base class is not in the same module + if (baseType.TypeIndex == -1 && baseClass->IsGeneric()) + { + auto genericNameIndex = baseClass->GetFullName().FindLast('`'); + // we add 2 because of the way generic names work its `N + auto genericClassName = baseClass->GetFullName().Substring(0, genericNameIndex + 2); + + // We check for the generic class name instead of the baseclass fullname + baseType.Module->TypeNameToTypeIndex.TryGet(genericClassName, *(int32*)&baseType.TypeIndex); + } + if (!baseType) { LOG(Error, "Missing base class for managed class {0} from assembly {1}.", String(typeName), Assembly->ToString()); return; } + + if (baseType.TypeIndex == -1) + { + if (baseType.Module) + LOG(Error, "Missing base class for managed class {0} from assembly {1}.", String(baseClass->GetFullName()), baseType.Module->GetName().ToString()); + else + // Not sure this can happen but never hurts to account for it + LOG(Error, "Missing base class for managed class {0} from unknown assembly.", String(baseClass->GetFullName())); + return; + } + ScriptingTypeHandle nativeType = baseType; while (true) { @@ -1213,8 +1237,12 @@ bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Sp return true; } +#if USE_NETCORE + mInstance = instanceObject; +#else // For value-types instance is the actual boxed object data, not te object itself mInstance = instanceObjectClass->IsValueType() ? MCore::Object::Unbox(instanceObject) : instanceObject; +#endif } // Marshal parameters diff --git a/Source/Engine/Scripting/ManagedCLR/MAssembly.h b/Source/Engine/Scripting/ManagedCLR/MAssembly.h index e0952f9f6..3436df567 100644 --- a/Source/Engine/Scripting/ManagedCLR/MAssembly.h +++ b/Source/Engine/Scripting/ManagedCLR/MAssembly.h @@ -51,6 +51,15 @@ public: /// The assembly name. MAssembly(MDomain* domain, const StringAnsiView& name); + /// + /// Initializes a new instance of the class. + /// + /// The assembly domain. + /// The assembly name. + /// The assembly full name. + /// The managed handle of the assembly. + MAssembly(MDomain* domain, const StringAnsiView& name, const StringAnsiView& fullname, void* handle); + /// /// Finalizes an instance of the class. /// diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.cpp b/Source/Engine/Scripting/ManagedCLR/MCore.cpp index 86b906f81..d9cc6f863 100644 --- a/Source/Engine/Scripting/ManagedCLR/MCore.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MCore.cpp @@ -48,6 +48,18 @@ MAssembly::MAssembly(MDomain* domain, const StringAnsiView& name) { } +MAssembly::MAssembly(MDomain* domain, const StringAnsiView& name, const StringAnsiView& fullname, void* handle) + : _domain(domain) + , _isLoaded(false) + , _isLoading(false) + , _hasCachedClasses(false) + , _reloadCount(0) + , _name(name) + , _fullname(fullname) + , _handle(handle) +{ +} + MAssembly::~MAssembly() { Unload(); diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.h b/Source/Engine/Scripting/ManagedCLR/MCore.h index e1de3c207..6a3f24ba8 100644 --- a/Source/Engine/Scripting/ManagedCLR/MCore.h +++ b/Source/Engine/Scripting/ManagedCLR/MCore.h @@ -85,6 +85,7 @@ public: static MClass* GetClass(MClass* elementKlass); static int32 GetLength(const MArray* obj); static void* GetAddress(const MArray* obj); + static MArray* Unbox(MObject* obj); template FORCE_INLINE static T* GetAddress(const MArray* obj) @@ -189,4 +190,13 @@ public: static MClass* Double; static MClass* String; }; + + /// + /// Utilities for ScriptingObject management. + /// + struct FLAXENGINE_API ScriptingObject + { + static void SetInternalValues(MClass* klass, MObject* object, void* unmanagedPtr, const Guid* id); + static MObject* CreateScriptingObject(MClass* klass, void* unmanagedPtr, const Guid* id); + }; }; diff --git a/Source/Engine/Scripting/ManagedCLR/MField.h b/Source/Engine/Scripting/ManagedCLR/MField.h index 796de27f7..41545656d 100644 --- a/Source/Engine/Scripting/ManagedCLR/MField.h +++ b/Source/Engine/Scripting/ManagedCLR/MField.h @@ -19,6 +19,7 @@ protected: #elif USE_NETCORE void* _handle; void* _type; + int32 _fieldOffset; #endif MClass* _parentClass; @@ -35,7 +36,7 @@ public: #if USE_MONO explicit MField(MonoClassField* monoField, const char* name, MClass* parentClass); #elif USE_NETCORE - MField(MClass* parentClass, void* handle, const char* name, void* type, MFieldAttributes attributes); + MField(MClass* parentClass, void* handle, const char* name, void* type, int fieldOffset, MFieldAttributes attributes); #endif public: @@ -102,6 +103,16 @@ public: /// The return value of undefined type. void GetValue(MObject* instance, void* result) const; + /// + /// Retrieves value currently set in the field on the specified object instance. If field is static object instance can be null. + /// + /// + /// Value will be a pointer. + /// + /// The object of given type to get value from. + /// The return value of undefined type. + void GetValueReference(MObject* instance, void* result) const; + /// /// Retrieves value currently set in the field on the specified object instance. If field is static object instance can be null. If returned value is a value type it will be boxed. /// diff --git a/Source/Engine/Scripting/ManagedCLR/MUtils.h b/Source/Engine/Scripting/ManagedCLR/MUtils.h index 7b340e5c6..a9b2d8414 100644 --- a/Source/Engine/Scripting/ManagedCLR/MUtils.h +++ b/Source/Engine/Scripting/ManagedCLR/MUtils.h @@ -363,11 +363,12 @@ struct MConverter> void Unbox(Array& result, MObject* data) { - const int32 length = data ? MCore::Array::GetLength((MArray*)data) : 0; + MArray* array = MCore::Array::Unbox(data); + const int32 length = array ? MCore::Array::GetLength(array) : 0; result.Resize(length); MConverter converter; Span resultSpan(result.Get(), length); - converter.ToNativeArray(resultSpan, (MArray*)data); + converter.ToNativeArray(resultSpan, array); } }; diff --git a/Source/Engine/Scripting/Object.cs b/Source/Engine/Scripting/Object.cs index 170367b7e..c64532c2b 100644 --- a/Source/Engine/Scripting/Object.cs +++ b/Source/Engine/Scripting/Object.cs @@ -48,7 +48,7 @@ namespace FlaxEngine // Construct missing native object if managed objects gets created in managed world if (__unmanagedPtr == IntPtr.Zero) { - Internal_ManagedInstanceCreated(this); + Internal_ManagedInstanceCreated(this, FlaxEngine.Interop.NativeInterop.GetTypeHolder(GetType()).managedClassPointer); if (__unmanagedPtr == IntPtr.Zero) throw new Exception($"Failed to create native instance for object of type {GetType().FullName} (assembly: {GetType().Assembly.FullName})."); } @@ -320,7 +320,7 @@ namespace FlaxEngine internal static partial Object Internal_Create2(string typeName); [LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_ManagedInstanceCreated", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))] - internal static partial void Internal_ManagedInstanceCreated(Object managedInstance); + internal static partial void Internal_ManagedInstanceCreated(Object managedInstance, IntPtr theKlass); [LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_ManagedInstanceDeleted", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))] internal static partial void Internal_ManagedInstanceDeleted(IntPtr nativeInstance); diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 1bcef50b8..1d01a8ea3 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -13,6 +13,7 @@ #include "Engine/Platform/Platform.h" #include "Engine/Platform/File.h" #include "Engine/Platform/FileSystem.h" +#include "Engine/Scripting/Internal/InternalCalls.h" #include "Engine/Scripting/ManagedCLR/MCore.h" #include "Engine/Scripting/ManagedCLR/MAssembly.h" #include "Engine/Scripting/ManagedCLR/MClass.h" @@ -214,6 +215,7 @@ void* GetCustomAttribute(const MClass* klass, const MClass* attributeClass); struct NativeClassDefinitions { void* typeHandle; + MClass* nativePointer; const char* name; const char* fullname; const char* namespace_; @@ -233,6 +235,7 @@ struct NativeFieldDefinitions const char* name; void* fieldHandle; void* fieldType; + int fieldOffset; MFieldAttributes fieldAttributes; }; @@ -341,8 +344,8 @@ void MCore::Object::Init(MObject* obj) MClass* MCore::Object::GetClass(MObject* obj) { ASSERT(obj); - MType* typeHandle = GetObjectType(obj); - return GetOrCreateClass(typeHandle); + static void* GetObjectClassPtr = GetStaticMethodPointer(TEXT("GetObjectClass")); + return (MClass*)CallStaticMethod(GetObjectClassPtr, obj); } MString* MCore::Object::ToString(MObject* obj) @@ -408,6 +411,12 @@ void* MCore::Array::GetAddress(const MArray* obj) return CallStaticMethod(GetArrayPointerPtr, (void*)obj); } +MArray* MCore::Array::Unbox(MObject* obj) +{ + static void* GetArrayPtr = GetStaticMethodPointer(TEXT("GetArray")); + return (MArray*)CallStaticMethod(GetArrayPtr, (void*)obj); +} + MGCHandle MCore::GCHandle::New(MObject* obj, bool pinned) { ASSERT(obj); @@ -568,8 +577,7 @@ MObject* MCore::Exception::GetNotSupported(const char* msg) MClass* MCore::Type::GetClass(MType* type) { static void* GetTypeClassPtr = GetStaticMethodPointer(TEXT("GetTypeClass")); - type = (MType*)CallStaticMethod(GetTypeClassPtr, type); - return GetOrCreateClass(type); + return CallStaticMethod(GetTypeClassPtr, type); } MType* MCore::Type::GetElementType(MType* type) @@ -606,6 +614,18 @@ bool MCore::Type::IsReference(MType* type) return CallStaticMethod(GetTypeIsReferencePtr, type); } +void MCore::ScriptingObject::SetInternalValues(MClass* klass, MObject* object, void* unmanagedPtr, const Guid* id) +{ + static void* ScriptingObjectSetInternalValuesPtr = GetStaticMethodPointer(TEXT("ScriptingObjectSetInternalValues")); + CallStaticMethod(ScriptingObjectSetInternalValuesPtr, object, unmanagedPtr, id); +} + +MObject* MCore::ScriptingObject::CreateScriptingObject(MClass* klass, void* unmanagedPtr, const Guid* id) +{ + static void* ScriptingObjectSetInternalValuesPtr = GetStaticMethodPointer(TEXT("ScriptingObjectCreate")); + return CallStaticMethod(ScriptingObjectSetInternalValuesPtr, klass->_handle, unmanagedPtr, id); +} + const MAssembly::ClassesDictionary& MAssembly::GetClasses() const { if (_hasCachedClasses || !IsLoaded()) @@ -634,10 +654,16 @@ const MAssembly::ClassesDictionary& MAssembly::GetClasses() const MClass* klass = New(this, managedClass.typeHandle, managedClass.name, managedClass.fullname, managedClass.namespace_, managedClass.typeAttributes); _classes.Add(klass->GetFullName(), klass); + managedClass.nativePointer = klass; + MCore::GC::FreeMemory((void*)managedClasses[i].name); MCore::GC::FreeMemory((void*)managedClasses[i].fullname); MCore::GC::FreeMemory((void*)managedClasses[i].namespace_); } + + static void* RegisterManagedClassNativePointersPtr = GetStaticMethodPointer(TEXT("RegisterManagedClassNativePointers")); + CallStaticMethod(RegisterManagedClassNativePointersPtr, &managedClasses, classCount); + MCore::GC::FreeMemory(managedClasses); const auto endTime = DateTime::NowUTC(); @@ -652,6 +678,39 @@ const MAssembly::ClassesDictionary& MAssembly::GetClasses() const return _classes; } +void GetAssemblyName(void* assemblyHandle, StringAnsi& name, StringAnsi& fullname) +{ + static void* GetAssemblyNamePtr = GetStaticMethodPointer(TEXT("GetAssemblyName")); + const char* name_; + const char* fullname_; + CallStaticMethod(GetAssemblyNamePtr, assemblyHandle, &name_, &fullname_); + name = name_; + fullname = fullname_; + MCore::GC::FreeMemory((void*)name_); + MCore::GC::FreeMemory((void*)fullname_); +} + +DEFINE_INTERNAL_CALL(void) NativeInterop_CreateClass(NativeClassDefinitions* managedClass, void* assemblyHandle) +{ + MAssembly* assembly = GetAssembly(assemblyHandle); + if (assembly == nullptr) + { + StringAnsi assemblyName; + StringAnsi assemblyFullName; + GetAssemblyName(assemblyHandle, assemblyName, assemblyFullName); + + assembly = New(nullptr, assemblyName, assemblyFullName, assemblyHandle); + CachedAssemblyHandles.Add(assemblyHandle, assembly); + } + + MClass* klass = New(assembly, managedClass->typeHandle, managedClass->name, managedClass->fullname, managedClass->namespace_, managedClass->typeAttributes); + if (assembly != nullptr) + { + const_cast(assembly->GetClasses()).Add(klass->GetFullName(), klass); + } + managedClass->nativePointer = klass; +} + bool MAssembly::LoadCorlib() { if (IsLoaded()) @@ -671,14 +730,9 @@ bool MAssembly::LoadCorlib() // Load { - const char* name; - const char* fullname; static void* GetAssemblyByNamePtr = GetStaticMethodPointer(TEXT("GetAssemblyByName")); - _handle = CallStaticMethod(GetAssemblyByNamePtr, "System.Private.CoreLib", &name, &fullname); - _name = name; - _fullname = fullname; - MCore::GC::FreeMemory((void*)name); - MCore::GC::FreeMemory((void*)fullname); + _handle = CallStaticMethod(GetAssemblyByNamePtr, "System.Private.CoreLib"); + GetAssemblyName(_handle, _name, _fullname); } if (_handle == nullptr) { @@ -697,20 +751,14 @@ bool MAssembly::LoadImage(const String& assemblyPath, const StringView& nativePa { // TODO: Use new hostfxr delegate load_assembly_bytes? (.NET 8+) // Open .Net assembly - const StringAnsi assemblyPathAnsi = assemblyPath.ToStringAnsi(); - const char* name; - const char* fullname; static void* LoadAssemblyImagePtr = GetStaticMethodPointer(TEXT("LoadAssemblyImage")); - _handle = CallStaticMethod(LoadAssemblyImagePtr, assemblyPathAnsi.Get(), &name, &fullname); - _name = name; - _fullname = fullname; - MCore::GC::FreeMemory((void*)name); - MCore::GC::FreeMemory((void*)fullname); + _handle = CallStaticMethod(LoadAssemblyImagePtr, assemblyPath.Get()); if (_handle == nullptr) { Log::CLRInnerException(TEXT(".NET assembly image is invalid at ") + assemblyPath); return true; } + GetAssemblyName(_handle, _name, _fullname); CachedAssemblyHandles.Add(_handle, this); // Provide new path of hot-reloaded native library path for managed DllImport @@ -719,6 +767,13 @@ bool MAssembly::LoadImage(const String& assemblyPath, const StringView& nativePa StringAnsi nativeName = _name.EndsWith(".CSharp") ? StringAnsi(_name.Get(), _name.Length() - 7) : StringAnsi(_name); RegisterNativeLibrary(nativeName.Get(), StringAnsi(nativePath).Get()); } +#if USE_EDITOR + // Register the editor module location for Assembly resolver + else + { + RegisterNativeLibrary(_name.Get(), StringAnsi(assemblyPath).Get()); + } +#endif _hasCachedClasses = false; _assemblyPath = assemblyPath; @@ -833,8 +888,7 @@ MType* MClass::GetType() const MClass* MClass::GetBaseClass() const { static void* GetClassParentPtr = GetStaticMethodPointer(TEXT("GetClassParent")); - MType* parentTypeHandle = CallStaticMethod(GetClassParentPtr, _handle); - return GetOrCreateClass(parentTypeHandle); + return CallStaticMethod(GetClassParentPtr, _handle); } bool MClass::IsSubClassOf(const MClass* klass, bool checkInterfaces) const @@ -869,8 +923,7 @@ uint32 MClass::GetInstanceSize() const MClass* MClass::GetElementClass() const { static void* GetElementClassPtr = GetStaticMethodPointer(TEXT("GetElementClass")); - MType* elementTypeHandle = CallStaticMethod(GetElementClassPtr, _handle); - return GetOrCreateClass(elementTypeHandle); + return CallStaticMethod(GetElementClassPtr, _handle); } MMethod* MClass::GetMethod(const char* name, int32 numParams) const @@ -898,7 +951,6 @@ const Array& MClass::GetMethods() const NativeMethodDefinitions& definition = methods[i]; MMethod* method = New(const_cast(this), StringAnsi(definition.name), definition.handle, definition.numParameters, definition.methodAttributes); _methods.Add(method); - MCore::GC::FreeMemory((void*)definition.name); } MCore::GC::FreeMemory(methods); @@ -930,9 +982,8 @@ const Array& MClass::GetFields() const for (int32 i = 0; i < numFields; i++) { NativeFieldDefinitions& definition = fields[i]; - MField* field = New(const_cast(this), definition.fieldHandle, definition.name, definition.fieldType, definition.fieldAttributes); + MField* field = New(const_cast(this), definition.fieldHandle, definition.name, definition.fieldType, definition.fieldOffset, definition.fieldAttributes); _fields.Add(field); - MCore::GC::FreeMemory((void*)definition.name); } MCore::GC::FreeMemory(fields); @@ -977,7 +1028,6 @@ const Array& MClass::GetProperties() const const NativePropertyDefinitions& definition = foundProperties[i]; MProperty* property = New(const_cast(this), definition.name, definition.getterHandle, definition.setterHandle, definition.getterAttributes, definition.setterAttributes); _properties.Add(property); - MCore::GC::FreeMemory((void*)definition.name); } MCore::GC::FreeMemory(foundProperties); @@ -1013,7 +1063,7 @@ bool MClass::HasAttribute(const MClass* monoClass) const bool MClass::HasAttribute() const { - return GetCustomAttribute(this, nullptr) != nullptr; + return !GetAttributes().IsEmpty(); } MObject* MClass::GetAttribute(const MClass* monoClass) const @@ -1123,11 +1173,12 @@ MException::~MException() Delete(InnerException); } -MField::MField(MClass* parentClass, void* handle, const char* name, void* type, MFieldAttributes attributes) +MField::MField(MClass* parentClass, void* handle, const char* name, void* type, int fieldOffset, MFieldAttributes attributes) : _handle(handle) , _type(type) , _parentClass(parentClass) , _name(name) + , _fieldOffset(fieldOffset) , _hasCachedAttributes(false) { switch (attributes & MFieldAttributes::FieldAccessMask) @@ -1163,8 +1214,7 @@ MType* MField::GetType() const int32 MField::GetOffset() const { - static void* FieldGetOffsetPtr = GetStaticMethodPointer(TEXT("FieldGetOffset")); - return CallStaticMethod(FieldGetOffsetPtr, _handle); + return _fieldOffset; } void MField::GetValue(MObject* instance, void* result) const @@ -1173,6 +1223,12 @@ void MField::GetValue(MObject* instance, void* result) const CallStaticMethod(FieldGetValuePtr, instance, _handle, result); } +void MField::GetValueReference(MObject* instance, void* result) const +{ + static void* FieldGetValueReferencePtr = GetStaticMethodPointer(TEXT("FieldGetValueReferenceWithOffset")); + CallStaticMethod(FieldGetValueReferencePtr, instance, _fieldOffset, result); +} + MObject* MField::GetValueBoxed(MObject* instance) const { static void* FieldGetValueBoxedPtr = GetStaticMethodPointer(TEXT("FieldGetValueBoxed")); @@ -1505,8 +1561,7 @@ void* GetCustomAttribute(const MClass* klass, const MClass* attributeClass) const Array& attributes = klass->GetAttributes(); for (MObject* attr : attributes) { - MType* typeHandle = GetObjectType(attr); - MClass* attrClass = GetOrCreateClass(typeHandle); + MClass* attrClass = MCore::Object::GetClass(attr); if (attrClass == attributeClass) return attr; } @@ -1541,7 +1596,16 @@ bool InitHostfxr() get_hostfxr_params.size = sizeof(hostfxr_initialize_parameters); get_hostfxr_params.assembly_path = libraryPath.Get(); #if PLATFORM_MAC - get_hostfxr_params.dotnet_root = "/usr/local/share/dotnet"; + ::String macOSDotnetRoot = TEXT("/usr/local/share/dotnet"); +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(_M_X64) + // When emulating x64 on arm + const ::String dotnetRootEmulated = macOSDotnetRoot / TEXT("x64"); + if (FileSystem::FileExists(dotnetRootEmulated / TEXT("dotnet"))) { + macOSDotnetRoot = dotnetRootEmulated; + } +#endif + const FLAX_CORECLR_STRING& finalDotnetRootPath = FLAX_CORECLR_STRING(macOSDotnetRoot); + get_hostfxr_params.dotnet_root = finalDotnetRootPath.Get(); #else get_hostfxr_params.dotnet_root = nullptr; #endif @@ -1588,7 +1652,10 @@ bool InitHostfxr() void* hostfxr = Platform::LoadLibrary(path.Get()); if (hostfxr == nullptr) { - LOG(Fatal, "Failed to load hostfxr library ({0})", path); + if (FileSystem::FileExists(path)) + LOG(Fatal, "Failed to load hostfxr library, possible platform/architecture mismatch with the library. See log for more information. ({0})", path); + else + LOG(Fatal, "Failed to load hostfxr library ({0})", path); return true; } hostfxr_initialize_for_runtime_config = (hostfxr_initialize_for_runtime_config_fn)Platform::GetProcAddress(hostfxr, "hostfxr_initialize_for_runtime_config"); @@ -1627,7 +1694,28 @@ bool InitHostfxr() if (rc != 0 || handle == nullptr) { hostfxr_close(handle); - LOG(Fatal, "Failed to initialize hostfxr: {0:x} ({1})", (unsigned int)rc, String(init_params.dotnet_root)); + if (rc == 0x80008096) // FrameworkMissingFailure + { + String platformStr; + switch (PLATFORM_TYPE) + { + case PlatformType::Windows: + case PlatformType::UWP: + platformStr = PLATFORM_64BITS ? "Windows x64" : "Windows x86"; + break; + case PlatformType::Linux: + platformStr = PLATFORM_ARCH_ARM64 ? "Linux Arm64" : PLATFORM_ARCH_ARM ? "Linux Arm32" : PLATFORM_64BITS ? "Linux x64" : "Linux x86"; + break; + case PlatformType::Mac: + platformStr = PLATFORM_ARCH_ARM || PLATFORM_ARCH_ARM64 ? "macOS Arm64" : PLATFORM_64BITS ? "macOS x64" : "macOS x86"; + break; + default:; + platformStr = ""; + } + LOG(Fatal, "Failed to resolve compatible .NET runtime version in '{0}'. Make sure the correct platform version for runtime is installed ({1})", platformStr, String(init_params.dotnet_root)); + } + else + LOG(Fatal, "Failed to initialize hostfxr: {0:x} ({1})", (unsigned int)rc, String(init_params.dotnet_root)); return true; } diff --git a/Source/Engine/Scripting/Runtime/Mono.cpp b/Source/Engine/Scripting/Runtime/Mono.cpp index 0a60db42f..676453c21 100644 --- a/Source/Engine/Scripting/Runtime/Mono.cpp +++ b/Source/Engine/Scripting/Runtime/Mono.cpp @@ -804,6 +804,11 @@ void* MCore::Array::GetAddress(const MArray* obj) return mono_array_addr_with_size((MonoArray*)obj, 0, 0); } +MArray* MCore::Array::Unbox(MObject* obj) +{ + return (MArray*)obj; +} + MGCHandle MCore::GCHandle::New(MObject* obj, bool pinned) { return mono_gchandle_new(obj, pinned); @@ -2122,6 +2127,44 @@ const Array& MProperty::GetAttributes() const return _attributes; } +void MCore::ScriptingObject::SetInternalValues(MClass* klass, MObject* object, void* unmanagedPtr, const Guid* id) +{ + // Set handle to unmanaged object + const MField* monoUnmanagedPtrField = klass->GetField("__unmanagedPtr"); + if (monoUnmanagedPtrField) + { + const void* param = unmanagedPtr; + monoUnmanagedPtrField->SetValue(managedInstance, ¶m); + } + if (id != nullptr) + { + // Set object id + const MField* monoIdField = klass->GetField("__internalId"); + if (monoIdField) + { + monoIdField->SetValue(managedInstance, (void*)id); + } + } +} + +MObject* MCore::ScriptingObject::CreateScriptingObject(MClass* klass, void* unmanagedPtr, const Guid* id) +{ + // Ensure to have managed domain attached (this can be called from custom native thread, eg. content loader) + MCore::Thread::Attach(); + + // Allocate managed instance + MObject* managedInstance = MCore::Object::New(klass); + if (managedInstance) + { + // Set unmanaged object handle and id + MCore::ScriptingObject::SetInternalValues(klass, managedInstance, unmanagedPtr, _id); + + // Initialize managed instance (calls constructor) + MCore::Object::Init(managedInstance); + } + return managedInstance; +} + #endif #if USE_MONO && PLATFORM_WIN32 && !USE_MONO_DYNAMIC_LIB diff --git a/Source/Engine/Scripting/Runtime/None.cpp b/Source/Engine/Scripting/Runtime/None.cpp index ef9c118cf..a25d59c59 100644 --- a/Source/Engine/Scripting/Runtime/None.cpp +++ b/Source/Engine/Scripting/Runtime/None.cpp @@ -141,6 +141,11 @@ void* MCore::Array::GetAddress(const MArray* obj) return nullptr; } +MArray* MCore::Array::Unbox(MObject* obj) +{ + return nullptr; +} + MGCHandle MCore::GCHandle::New(MObject* obj, bool pinned) { return (MGCHandle)(uintptr)obj; @@ -560,4 +565,13 @@ const Array& MProperty::GetAttributes() const return _attributes; } +void MCore::ScriptingObject::SetInternalValues(MClass* klass, MObject* object, void* unmanagedPtr, const Guid* id) +{ +} + +MObject* MCore::ScriptingObject::CreateScriptingObject(MClass* klass, void* unmanagedPtr, const Guid* id) +{ + return nullptr; +} + #endif diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index 6210063b8..28c9a4ed1 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -104,7 +104,7 @@ namespace MMethod* _method_LateFixedUpdate = nullptr; MMethod* _method_Draw = nullptr; MMethod* _method_Exit = nullptr; - Array> _nonNativeModules; + Dictionary> _nonNativeModules; #if USE_EDITOR bool LastBinariesLoadTriggeredCompilation = false; #endif @@ -334,6 +334,8 @@ bool Scripting::LoadBinaryModules(const String& path, const String& projectFolde // Check if that module has been already registered BinaryModule* module = BinaryModule::GetModule(nameAnsi); + if (!module) + _nonNativeModules.TryGet(nameAnsi, module); if (!module) { // C++ @@ -403,7 +405,7 @@ bool Scripting::LoadBinaryModules(const String& path, const String& projectFolde { // Create module if native library is not used module = New(nameAnsi); - _nonNativeModules.Add(module); + _nonNativeModules.Add(nameAnsi, module); } } diff --git a/Source/Engine/Scripting/Scripting.cs b/Source/Engine/Scripting/Scripting.cs index 68da64bf7..8347fe7f8 100644 --- a/Source/Engine/Scripting/Scripting.cs +++ b/Source/Engine/Scripting/Scripting.cs @@ -135,8 +135,13 @@ namespace FlaxEngine { if (e.ExceptionObject is Exception exception) { - Debug.LogError("Unhandled Exception: " + exception.Message); - Debug.LogException(exception); + if (e.IsTerminating && !System.Diagnostics.Debugger.IsAttached) + Platform.Fatal($"Unhandled Exception: {exception}"); + else + { + Debug.LogError($"Unhandled Exception: {exception.Message}"); + Debug.LogException(exception); + } } } @@ -277,6 +282,12 @@ namespace FlaxEngine TextBoxBackgroundSelected = Color.FromBgra(0xFF3F3F46), CollectionBackgroundColor = Color.FromBgra(0x14CCCCCC), SharedTooltip = new Tooltip(), + Statusbar = new Style.StatusbarStyle() + { + PlayMode = Color.FromBgra(0xFF2F9135), + Failed = Color.FromBgra(0xFF9C2424), + Loading = Color.FromBgra(0xFF2D2D30) + } }; style.DragWindow = style.BackgroundSelected * 0.7f; diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp index 12144385d..b6d126bca 100644 --- a/Source/Engine/Scripting/ScriptingObject.cpp +++ b/Source/Engine/Scripting/ScriptingObject.cpp @@ -180,10 +180,14 @@ ScriptingObject* ScriptingObject::ToNative(MObject* obj) #if USE_CSHARP if (obj) { - // TODO: cache the field offset from object and read directly from object pointer +#if USE_MONO const auto ptrField = MCore::Object::GetClass(obj)->GetField(ScriptingObject_unmanagedPtr); CHECK_RETURN(ptrField, nullptr); ptrField->GetValue(obj, &ptr); +#else + static const MField* ptrField = MCore::Object::GetClass(obj)->GetField(ScriptingObject_unmanagedPtr); + ptrField->GetValueReference(obj, &ptr); +#endif } #endif return ptr; @@ -274,12 +278,7 @@ bool ScriptingObject::CreateManaged() if (const auto monoClass = GetClass()) { // Reset managed to unmanaged pointer - const MField* monoUnmanagedPtrField = monoClass->GetField(ScriptingObject_unmanagedPtr); - if (monoUnmanagedPtrField) - { - void* param = nullptr; - monoUnmanagedPtrField->SetValue(managedInstance, ¶m); - } + MCore::ScriptingObject::SetInternalValues(monoClass, managedInstance, nullptr, nullptr); } MCore::GCHandle::Free(handle); return true; @@ -305,34 +304,12 @@ MObject* ScriptingObject::CreateManagedInternal() return nullptr; } - // Ensure to have managed domain attached (this can be called from custom native thread, eg. content loader) - MCore::Thread::Attach(); - - // Allocate managed instance - MObject* managedInstance = MCore::Object::New(monoClass); + MObject* managedInstance = MCore::ScriptingObject::CreateScriptingObject(monoClass, this, &_id); if (managedInstance == nullptr) { LOG(Warning, "Failed to create new instance of the object of type {0}", String(monoClass->GetFullName())); } - // Set handle to unmanaged object - const MField* monoUnmanagedPtrField = monoClass->GetField(ScriptingObject_unmanagedPtr); - if (monoUnmanagedPtrField) - { - const void* value = this; - monoUnmanagedPtrField->SetValue(managedInstance, &value); - } - - // Set object id - const MField* monoIdField = monoClass->GetField(ScriptingObject_id); - if (monoIdField) - { - monoIdField->SetValue(managedInstance, (void*)&_id); - } - - // Initialize managed instance (calls constructor) - MCore::Object::Init(managedInstance); - return managedInstance; } @@ -349,12 +326,7 @@ void ScriptingObject::DestroyManaged() { if (const auto monoClass = GetClass()) { - const MField* monoUnmanagedPtrField = monoClass->GetField(ScriptingObject_unmanagedPtr); - if (monoUnmanagedPtrField) - { - void* param = nullptr; - monoUnmanagedPtrField->SetValue(managedInstance, ¶m); - } + MCore::ScriptingObject::SetInternalValues(monoClass, managedInstance, nullptr, nullptr); } } @@ -478,12 +450,7 @@ bool ManagedScriptingObject::CreateManaged() if (const auto monoClass = GetClass()) { // Reset managed to unmanaged pointer - const MField* monoUnmanagedPtrField = monoClass->GetField(ScriptingObject_unmanagedPtr); - if (monoUnmanagedPtrField) - { - void* param = nullptr; - monoUnmanagedPtrField->SetValue(managedInstance, ¶m); - } + MCore::ScriptingObject::SetInternalValues(monoClass, managedInstance, nullptr, nullptr); } MCore::GCHandle::Free(handle); return true; @@ -605,10 +572,8 @@ DEFINE_INTERNAL_CALL(MObject*) ObjectInternal_Create2(MString* typeNameObj) return managedInstance; } -DEFINE_INTERNAL_CALL(void) ObjectInternal_ManagedInstanceCreated(MObject* managedInstance) +DEFINE_INTERNAL_CALL(void) ObjectInternal_ManagedInstanceCreated(MObject* managedInstance, MClass* typeClass) { - MClass* typeClass = MCore::Object::GetClass(managedInstance); - // Get the assembly with that class auto module = ManagedBinaryModule::FindModule(typeClass); if (module == nullptr) @@ -645,22 +610,8 @@ DEFINE_INTERNAL_CALL(void) ObjectInternal_ManagedInstanceCreated(MObject* manage } MClass* monoClass = obj->GetClass(); - - // Set handle to unmanaged object - const MField* monoUnmanagedPtrField = monoClass->GetField(ScriptingObject_unmanagedPtr); - if (monoUnmanagedPtrField) - { - const void* value = obj; - monoUnmanagedPtrField->SetValue(managedInstance, &value); - } - - // Set object id - const MField* monoIdField = monoClass->GetField(ScriptingObject_id); - if (monoIdField) - { - const Guid id = obj->GetID(); - monoIdField->SetValue(managedInstance, (void*)&id); - } + const Guid id = obj->GetID(); + MCore::ScriptingObject::SetInternalValues(monoClass, managedInstance, obj, &id); // Register object if (!obj->IsRegistered()) diff --git a/Source/Engine/Terrain/TerrainChunk.cpp b/Source/Engine/Terrain/TerrainChunk.cpp index 7db786ec2..737ce08f8 100644 --- a/Source/Engine/Terrain/TerrainChunk.cpp +++ b/Source/Engine/Terrain/TerrainChunk.cpp @@ -45,7 +45,7 @@ bool TerrainChunk::PrepareDraw(const RenderContext& renderContext) // Calculate chunk distance to view const auto lodView = (renderContext.LodProxyView ? renderContext.LodProxyView : &renderContext.View); - const float distance = Float3::Distance(_boundsCenter - lodView->Origin, lodView->Position); + const float distance = Float3::Distance(_sphere.Center - lodView->Origin, lodView->Position); lod = (int32)Math::Pow(distance / chunkEdgeSize, lodDistribution); lod += lodBias; @@ -88,6 +88,7 @@ void TerrainChunk::Draw(const RenderContext& renderContext) const drawCall.Material = _cachedDrawMaterial; renderContext.View.GetWorldMatrix(_transform, drawCall.World); drawCall.ObjectPosition = drawCall.World.GetTranslation(); + drawCall.ObjectRadius = _sphere.Radius; drawCall.Terrain.Patch = _patch; drawCall.Terrain.HeightmapUVScaleBias = _heightmapUVScaleBias; drawCall.Terrain.OffsetUV = Vector2((float)(_patch->_x * TerrainPatch::CHUNKS_COUNT_EDGE + _x), (float)(_patch->_z * TerrainPatch::CHUNKS_COUNT_EDGE + _z)); @@ -145,6 +146,7 @@ void TerrainChunk::Draw(const RenderContext& renderContext, MaterialBase* materi drawCall.Material = material; renderContext.View.GetWorldMatrix(_transform, drawCall.World); drawCall.ObjectPosition = drawCall.World.GetTranslation(); + drawCall.ObjectRadius = _sphere.Radius; drawCall.Terrain.Patch = _patch; drawCall.Terrain.HeightmapUVScaleBias = _heightmapUVScaleBias; drawCall.Terrain.OffsetUV = Vector2((float)(_patch->_x * TerrainPatch::CHUNKS_COUNT_EDGE + _x), (float)(_patch->_z * TerrainPatch::CHUNKS_COUNT_EDGE + _z)); @@ -202,7 +204,7 @@ void TerrainChunk::UpdateBounds() OrientedBoundingBox obb(Vector3::Zero, Vector3::One); obb.Transform(localTransform); obb.GetBoundingBox(_bounds); - _boundsCenter = _bounds.GetCenter(); + BoundingSphere::FromBox(_bounds, _sphere); _bounds.Minimum -= boundsExtent; _bounds.Maximum += boundsExtent; diff --git a/Source/Engine/Terrain/TerrainChunk.h b/Source/Engine/Terrain/TerrainChunk.h index dd74a18a1..884160b45 100644 --- a/Source/Engine/Terrain/TerrainChunk.h +++ b/Source/Engine/Terrain/TerrainChunk.h @@ -3,6 +3,7 @@ #pragma once #include "Engine/Core/Math/BoundingBox.h" +#include "Engine/Core/Math/BoundingSphere.h" #include "Engine/Core/Math/Matrix.h" #include "Engine/Core/Math/Transform.h" #include "Engine/Core/ISerializable.h" @@ -29,7 +30,7 @@ private: Float4 _heightmapUVScaleBias; Transform _transform; BoundingBox _bounds; - Vector3 _boundsCenter; + BoundingSphere _sphere; float _perInstanceRandom; float _yOffset, _yHeight; diff --git a/Source/Engine/Tests/TestScripting.cs b/Source/Engine/Tests/TestScripting.cs index fd94b8017..93c6637fe 100644 --- a/Source/Engine/Tests/TestScripting.cs +++ b/Source/Engine/Tests/TestScripting.cs @@ -19,7 +19,7 @@ namespace FlaxEngine.Tests { var result = 0; var libraryName = "FlaxEngine"; - var library = NativeLibrary.Load(Interop.NativeInterop.nativeLibraryPaths[libraryName]); + var library = NativeLibrary.Load(Interop.NativeInterop.libraryPaths[libraryName]); if (library == IntPtr.Zero) return -1; var types = typeof(FlaxEngine.Object).Assembly.GetTypes(); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp index daecf8c45..7eff75514 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp @@ -42,7 +42,16 @@ public: void write(const char* message) override { String s(message); - s.Replace('\n', ' '); + if (s.Length() <= 0) + return; + for (int32 i = 0; i < s.Length(); i++) + { + Char& c = s[i]; + if (c == '\n') + c = ' '; + else if (c >= 255) + c = '?'; + } LOG(Info, "[Assimp]: {0}", s); } }; @@ -557,12 +566,17 @@ bool ImportMaterials(ImportedModelData& result, AssimpImporterData& data, String return false; } +bool IsMeshInvalid(const aiMesh* aMesh) +{ + return aMesh->mPrimitiveTypes != aiPrimitiveType_TRIANGLE || aMesh->mNumVertices == 0 || aMesh->mNumFaces == 0 || aMesh->mFaces[0].mNumIndices != 3; +} + bool ImportMesh(int32 i, ImportedModelData& result, AssimpImporterData& data, String& errorMsg) { const auto aMesh = data.Scene->mMeshes[i]; // Skip invalid meshes - if (aMesh->mPrimitiveTypes != aiPrimitiveType_TRIANGLE || aMesh->mNumVertices == 0 || aMesh->mNumFaces == 0 || aMesh->mFaces[0].mNumIndices != 3) + if (IsMeshInvalid(aMesh)) return false; // Skip unused meshes @@ -707,13 +721,13 @@ bool ModelTool::ImportDataAssimp(const char* path, ImportedModelData& data, Opti if (EnumHasAnyFlags(data.Types, ImportDataTypes::Geometry) && context->Scene->HasMeshes()) { const int meshCount = context->Scene->mNumMeshes; - if (options.SplitObjects && options.ObjectIndex == -1) + if (options.SplitObjects && options.ObjectIndex == -1 && meshCount > 1) { // Import the first object within this call options.SplitObjects = false; options.ObjectIndex = 0; - if (meshCount > 1 && options.OnSplitImport.IsBinded()) + if (options.OnSplitImport.IsBinded()) { // Split all animations into separate assets LOG(Info, "Splitting imported {0} meshes", meshCount); @@ -780,13 +794,13 @@ bool ModelTool::ImportDataAssimp(const char* path, ImportedModelData& data, Opti if (EnumHasAnyFlags(data.Types, ImportDataTypes::Animations) && context->Scene->HasAnimations()) { const int32 animCount = (int32)context->Scene->mNumAnimations; - if (options.SplitObjects && options.ObjectIndex == -1) + if (options.SplitObjects && options.ObjectIndex == -1 && animCount > 1) { // Import the first object within this call options.SplitObjects = false; options.ObjectIndex = 0; - if (animCount > 1 && options.OnSplitImport.IsBinded()) + if (options.OnSplitImport.IsBinded()) { // Split all animations into separate assets LOG(Info, "Splitting imported {0} animations", animCount); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp index 1eb9e1f27..215eadaba 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp @@ -923,8 +923,6 @@ bool ImportMesh(int32 index, ImportedModelData& result, OpenFbxImporterData& dat const auto aMesh = data.Scene->getMesh(index); const auto aGeometry = aMesh->getGeometry(); const auto trianglesCount = aGeometry->getVertexCount() / 3; - - // Skip invalid meshes if (IsMeshInvalid(aMesh)) return false; @@ -1245,13 +1243,13 @@ bool ModelTool::ImportDataOpenFBX(const char* path, ImportedModelData& data, Opt if (EnumHasAnyFlags(data.Types, ImportDataTypes::Geometry) && context->Scene->getMeshCount() > 0) { const int meshCount = context->Scene->getMeshCount(); - if (options.SplitObjects && options.ObjectIndex == -1) + if (options.SplitObjects && options.ObjectIndex == -1 && meshCount > 1) { // Import the first object within this call options.SplitObjects = false; options.ObjectIndex = 0; - if (meshCount > 1 && options.OnSplitImport.IsBinded()) + if (options.OnSplitImport.IsBinded()) { // Split all animations into separate assets LOG(Info, "Splitting imported {0} meshes", meshCount); @@ -1272,6 +1270,22 @@ bool ModelTool::ImportDataOpenFBX(const char* path, ImportedModelData& data, Opt const auto meshIndex = Math::Clamp(options.ObjectIndex, 0, meshCount - 1); if (ImportMesh(meshIndex, data, *context, errorMsg)) return true; + + // Let the firstly imported mesh import all materials from all meshes (index 0 is importing all following ones before itself during splitting - see code above) + if (options.ObjectIndex == 1) + { + for (int32 i = 0; i < meshCount; i++) + { + const auto aMesh = context->Scene->getMesh(i); + if (i == 1 || IsMeshInvalid(aMesh)) + continue; + for (int32 j = 0; j < aMesh->getMaterialCount(); j++) + { + const ofbx::Material* aMaterial = aMesh->getMaterial(j); + context->AddMaterial(data, aMaterial); + } + } + } } else { @@ -1328,13 +1342,13 @@ bool ModelTool::ImportDataOpenFBX(const char* path, ImportedModelData& data, Opt if (EnumHasAnyFlags(data.Types, ImportDataTypes::Animations)) { const int animCount = context->Scene->getAnimationStackCount(); - if (options.SplitObjects && options.ObjectIndex == -1) + if (options.SplitObjects && options.ObjectIndex == -1 && animCount > 1) { // Import the first object within this call options.SplitObjects = false; options.ObjectIndex = 0; - if (animCount > 1 && options.OnSplitImport.IsBinded()) + if (options.OnSplitImport.IsBinded()) { // Split all animations into separate assets LOG(Info, "Splitting imported {0} animations", animCount); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 19f203667..15c7cbc32 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -16,6 +16,7 @@ #include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Graphics/Models/ModelData.h" #include "Engine/Content/Assets/Model.h" +#include "Engine/Content/Content.h" #include "Engine/Serialization/MemoryWriteStream.h" #if USE_EDITOR #include "Engine/Core/Types/DateTime.h" @@ -994,6 +995,17 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op materialOptions.Info.CullMode = CullMode::TwoSided; if (!Math::IsOne(material.Opacity.Value) || material.Opacity.TextureIndex != -1) materialOptions.Info.BlendMode = MaterialBlendMode::Transparent; + + // When splitting imported meshes allow only the first mesh to import assets (mesh[0] is imported after all following ones so import assets during mesh[1]) + if (!options.SplitObjects && options.ObjectIndex != 1 && options.ObjectIndex != -1) + { + // Find that asset create previously + AssetInfo info; + if (Content::GetAssetInfo(assetPath, info)) + material.AssetID = info.ID; + continue; + } + AssetsImportingManager::Create(AssetsImportingManager::CreateMaterialTag, assetPath, material.AssetID, &materialOptions); #endif } diff --git a/Source/Engine/UI/GUI/Brushes/GPUTextureBrush.cs b/Source/Engine/UI/GUI/Brushes/GPUTextureBrush.cs index 9ad9db1d3..d3469670a 100644 --- a/Source/Engine/UI/GUI/Brushes/GPUTextureBrush.cs +++ b/Source/Engine/UI/GUI/Brushes/GPUTextureBrush.cs @@ -37,7 +37,7 @@ namespace FlaxEngine.GUI } /// - public Float2 Size => Texture?.Size ?? Float2.Zero; + public Float2 Size => Texture != null ? Texture.Size : Float2.Zero; /// public void Draw(Rectangle rect, Color color) diff --git a/Source/Engine/UI/GUI/Brushes/TextureBrush.cs b/Source/Engine/UI/GUI/Brushes/TextureBrush.cs index d49e5519b..755f7527b 100644 --- a/Source/Engine/UI/GUI/Brushes/TextureBrush.cs +++ b/Source/Engine/UI/GUI/Brushes/TextureBrush.cs @@ -104,7 +104,7 @@ namespace FlaxEngine.GUI } /// - public Float2 Size => Texture?.Size ?? Float2.Zero; + public Float2 Size => Texture != null ? Texture.Size : Float2.Zero; /// public unsafe void Draw(Rectangle rect, Color color) diff --git a/Source/Engine/UI/GUI/Common/Button.cs b/Source/Engine/UI/GUI/Common/Button.cs index 88ee0437e..b50f3dd46 100644 --- a/Source/Engine/UI/GUI/Common/Button.cs +++ b/Source/Engine/UI/GUI/Common/Button.cs @@ -84,17 +84,23 @@ namespace FlaxEngine.GUI /// [EditorDisplay("Border Style"), EditorOrder(2010), ExpandGroups] public bool HasBorder { get; set; } = true; + + /// + /// Gets or sets the border thickness. + /// + [EditorDisplay("Border Style"), EditorOrder(2011), Limit(0)] + public float BorderThickness { get; set; } = 1.0f; /// /// Gets or sets the color of the border. /// - [EditorDisplay("Border Style"), EditorOrder(2011), ExpandGroups] + [EditorDisplay("Border Style"), EditorOrder(2012)] public Color BorderColor { get; set; } /// /// Gets or sets the border color when button is highlighted. /// - [EditorDisplay("Border Style"), EditorOrder(2012)] + [EditorDisplay("Border Style"), EditorOrder(2013)] public Color BorderColorHighlighted { get; set; } /// @@ -252,7 +258,7 @@ namespace FlaxEngine.GUI else Render2D.FillRectangle(clientRect, backgroundColor); if (HasBorder) - Render2D.DrawRectangle(clientRect, borderColor); + Render2D.DrawRectangle(clientRect, borderColor, BorderThickness); // Draw text Render2D.DrawText(_font?.GetFont(), TextMaterial, _text, clientRect, textColor, TextAlignment.Center, TextAlignment.Center); diff --git a/Source/Engine/UI/GUI/Common/CheckBox.cs b/Source/Engine/UI/GUI/Common/CheckBox.cs index 939708f36..2f1ff42a9 100644 --- a/Source/Engine/UI/GUI/Common/CheckBox.cs +++ b/Source/Engine/UI/GUI/Common/CheckBox.cs @@ -107,17 +107,29 @@ namespace FlaxEngine.GUI CacheBox(); } } + + /// + /// Gets or sets whether to have a border. + /// + [EditorDisplay("Border Style"), EditorOrder(2010), Tooltip("Whether to have a border."), ExpandGroups] + public bool HasBorder { get; set; } = true; + + /// + /// Gets or sets the border thickness. + /// + [EditorDisplay("Border Style"), EditorOrder(2011), Tooltip("The thickness of the border."), Limit(0)] + public float BorderThickness { get; set; } = 1.0f; /// /// Gets or sets the color of the border. /// - [EditorDisplay("Border Style"), EditorOrder(2010), ExpandGroups] + [EditorDisplay("Border Style"), EditorOrder(2012)] public Color BorderColor { get; set; } /// /// Gets or sets the border color when checkbox is hovered. /// - [EditorDisplay("Border Style"), EditorOrder(2011)] + [EditorDisplay("Border Style"), EditorOrder(2013)] public Color BorderColorHighlighted { get; set; } /// @@ -221,12 +233,15 @@ namespace FlaxEngine.GUI bool enabled = EnabledInHierarchy; // Border - Color borderColor = BorderColor; - if (!enabled) - borderColor *= 0.5f; - else if (_isPressed || _mouseOverBox || IsNavFocused) - borderColor = BorderColorHighlighted; - Render2D.DrawRectangle(_box.MakeExpanded(-2.0f), borderColor); + if (HasBorder) + { + Color borderColor = BorderColor; + if (!enabled) + borderColor *= 0.5f; + else if (_isPressed || _mouseOverBox || IsNavFocused) + borderColor = BorderColorHighlighted; + Render2D.DrawRectangle(_box.MakeExpanded(-2.0f), borderColor, BorderThickness); + } // Icon if (_state != CheckBoxState.Default) diff --git a/Source/Engine/UI/GUI/Common/Label.cs b/Source/Engine/UI/GUI/Common/Label.cs index 2779dfdea..0e2d3c9cf 100644 --- a/Source/Engine/UI/GUI/Common/Label.cs +++ b/Source/Engine/UI/GUI/Common/Label.cs @@ -74,10 +74,16 @@ namespace FlaxEngine.GUI [EditorDisplay("Text Style"), EditorOrder(2022), Tooltip("The text wrapping within the control bounds.")] public TextWrapping Wrapping { get; set; } = TextWrapping.NoWrap; + /// + /// Gets or sets the text wrapping within the control bounds. + /// + [EditorDisplay("Text Style"), EditorOrder(2023), Tooltip("The gap between lines when wrapping and more than a single line is displayed."), Limit(0f)] + public float BaseLinesGapScale { get; set; } = 1.0f; + /// /// Gets or sets the font. /// - [EditorDisplay("Text Style"), EditorOrder(2023)] + [EditorDisplay("Text Style"), EditorOrder(2024)] public FontReference Font { get => _font; @@ -99,7 +105,7 @@ namespace FlaxEngine.GUI /// /// Gets or sets the custom material used to render the text. It must has domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data. /// - [EditorDisplay("Text Style"), EditorOrder(2024)] + [EditorDisplay("Text Style"), EditorOrder(2025)] public MaterialBase Material { get; set; } /// @@ -227,7 +233,7 @@ namespace FlaxEngine.GUI } } - Render2D.DrawText(_font.GetFont(), Material, _text, rect, color, hAlignment, wAlignment, Wrapping, 1.0f, scale); + Render2D.DrawText(_font.GetFont(), Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale); if (ClipText) Render2D.PopClip(); @@ -249,6 +255,7 @@ namespace FlaxEngine.GUI else if (_autoWidth && !_autoHeight) layout.Bounds.Size.Y = Height - Margin.Height; _textSize = font.MeasureText(_text, ref layout); + _textSize.Y *= BaseLinesGapScale; // Check if size is controlled via text if (_autoWidth || _autoHeight) diff --git a/Source/Engine/UI/GUI/Common/Slider.cs b/Source/Engine/UI/GUI/Common/Slider.cs index d7324ae0e..6dbd5082c 100644 --- a/Source/Engine/UI/GUI/Common/Slider.cs +++ b/Source/Engine/UI/GUI/Common/Slider.cs @@ -60,6 +60,7 @@ public class Slider : ContainerControl private float _thumbCenter; private Float2 _thumbSize = new Float2(16, 16); private bool _isSliding; + private bool _mouseOverThumb; /// /// Gets or sets the value (normalized to range 0-100). @@ -163,21 +164,27 @@ public class Slider : ContainerControl public IBrush FillTrackBrush { get; set; } /// - /// The color of the slider thumb when it's not selected + /// The color of the slider thumb when it's not selected. /// [EditorDisplay("Thumb Style"), EditorOrder(2030), Tooltip("The color of the slider thumb when it's not selected."), ExpandGroups] public Color ThumbColor { get; set; } + + /// + /// The color of the slider thumb when it's highlighted. + /// + [EditorDisplay("Thumb Style"), EditorOrder(2031), Tooltip("The color of the slider thumb when it's highlighted.")] + public Color ThumbColorHighlighted { get; set; } /// - /// The color of the slider thumb when it's selected + /// The color of the slider thumb when it's selected. /// - [EditorDisplay("Thumb Style"), EditorOrder(2031), Tooltip("The color of the slider thumb when it's selected.")] + [EditorDisplay("Thumb Style"), EditorOrder(2032), Tooltip("The color of the slider thumb when it's selected.")] public Color ThumbColorSelected { get; set; } /// /// Gets or sets the brush used for slider thumb drawing. /// - [EditorDisplay("Thumb Style"), EditorOrder(2032), Tooltip("The brush of the slider thumb.")] + [EditorDisplay("Thumb Style"), EditorOrder(2033), Tooltip("The brush of the slider thumb.")] public IBrush ThumbBrush { get; set; } /// @@ -222,6 +229,7 @@ public class Slider : ContainerControl TrackFillLineColor = style.LightBackground; ThumbColor = style.BackgroundNormal; ThumbColorSelected = style.BackgroundSelected; + ThumbColorHighlighted = style.BackgroundHighlighted; UpdateThumb(); } @@ -260,7 +268,7 @@ public class Slider : ContainerControl // Draw track fill if (FillTrack) { - var fillLineRect = new Rectangle(_thumbSize.X / 2, (Height - TrackHeight - 2) / 2, Width - (Width - _thumbCenter) - _thumbSize.X / 2, TrackHeight + 2); + var fillLineRect = new Rectangle(_thumbSize.X / 2 - 1, (Height - TrackHeight - 2) / 2, Width - (Width - _thumbCenter) - _thumbSize.X / 2, TrackHeight + 2); Render2D.PushClip(ref fillLineRect); if (FillTrackBrush != null) FillTrackBrush.Draw(lineRect, TrackFillLineColor); @@ -270,7 +278,7 @@ public class Slider : ContainerControl } // Draw thumb - var thumbColor = _isSliding ? ThumbColorSelected : ThumbColor; + var thumbColor = _isSliding ? ThumbColorSelected : (_mouseOverThumb ? ThumbColorHighlighted : ThumbColor); if (ThumbBrush != null) ThumbBrush.Draw(_thumbRect, thumbColor); else @@ -317,6 +325,7 @@ public class Slider : ContainerControl /// public override void OnMouseMove(Float2 location) { + _mouseOverThumb = _thumbRect.Contains(location); if (_isSliding) { // Update sliding diff --git a/Source/Engine/UI/GUI/Common/TextBox.cs b/Source/Engine/UI/GUI/Common/TextBox.cs index 5ec86a94e..ee4f744a6 100644 --- a/Source/Engine/UI/GUI/Common/TextBox.cs +++ b/Source/Engine/UI/GUI/Common/TextBox.cs @@ -155,7 +155,8 @@ namespace FlaxEngine.GUI if (IsMouseOver || IsNavFocused) backColor = BackgroundSelectedColor; Render2D.FillRectangle(rect, backColor); - Render2D.DrawRectangle(rect, IsFocused ? BorderSelectedColor : BorderColor); + if (HasBorder) + Render2D.DrawRectangle(rect, IsFocused ? BorderSelectedColor : BorderColor, BorderThickness); // Apply view offset and clip mask if (ClipText) diff --git a/Source/Engine/UI/GUI/Common/TextBoxBase.cs b/Source/Engine/UI/GUI/Common/TextBoxBase.cs index a4ccbe5d2..05183d57a 100644 --- a/Source/Engine/UI/GUI/Common/TextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/TextBoxBase.cs @@ -11,6 +11,11 @@ namespace FlaxEngine.GUI /// public abstract class TextBoxBase : ContainerControl { + /// + /// The delete control character (used for text filtering). + /// + protected const char DelChar = (char)0x7F; + /// /// The text separators (used for words skipping). /// @@ -270,16 +275,28 @@ namespace FlaxEngine.GUI [EditorDisplay("Background Style"), EditorOrder(2002), Tooltip("The speed of the selection background flashing animation.")] public float BackgroundSelectedFlashSpeed { get; set; } = 6.0f; + /// + /// Gets or sets whether to have a border. + /// + [EditorDisplay("Border Style"), EditorOrder(2010), Tooltip("Whether to have a border."), ExpandGroups] + public bool HasBorder { get; set; } = true; + + /// + /// Gets or sets the border thickness. + /// + [EditorDisplay("Border Style"), EditorOrder(2011), Tooltip("The thickness of the border."), Limit(0)] + public float BorderThickness { get; set; } = 1.0f; + /// /// Gets or sets the color of the border (Transparent if not used). /// - [EditorDisplay("Border Style"), EditorOrder(2010), Tooltip("The color of the border (Transparent if not used)."), ExpandGroups] + [EditorDisplay("Border Style"), EditorOrder(2012), Tooltip("The color of the border (Transparent if not used).")] public Color BorderColor { get; set; } /// /// Gets or sets the color of the border when control is focused (Transparent if not used). /// - [EditorDisplay("Border Style"), EditorOrder(2011), Tooltip("The color of the border when control is focused (Transparent if not used)")] + [EditorDisplay("Border Style"), EditorOrder(2013), Tooltip("The color of the border when control is focused (Transparent if not used)")] public Color BorderSelectedColor { get; set; } /// @@ -351,6 +368,10 @@ namespace FlaxEngine.GUI if (value.IndexOf('\r') != -1) value = value.Replace("\r", ""); + // Filter text (handle backspace control character) + if (value.IndexOf(DelChar) != -1) + value = value.Replace(DelChar.ToString(), ""); + // Clamp length if (value.Length > MaxLength) value = value.Substring(0, MaxLength); @@ -673,6 +694,8 @@ namespace FlaxEngine.GUI // Filter text if (str.IndexOf('\r') != -1) str = str.Replace("\r", ""); + if (str.IndexOf(DelChar) != -1) + str = str.Replace(DelChar.ToString(), ""); if (!IsMultiline && str.IndexOf('\n') != -1) str = str.Replace("\n", ""); @@ -1327,6 +1350,15 @@ namespace FlaxEngine.GUI if (IsReadOnly) return true; + if (ctrDown) + { + int prevWordBegin = FindPrevWordBegin(); + _text = _text.Remove(prevWordBegin, CaretPosition - prevWordBegin); + SetSelection(prevWordBegin); + OnTextChanged(); + return true; + } + int left = SelectionLeft; if (HasSelection) { diff --git a/Source/Engine/UI/GUI/ContainerControl.cs b/Source/Engine/UI/GUI/ContainerControl.cs index 2de96d0b3..69a75aacb 100644 --- a/Source/Engine/UI/GUI/ContainerControl.cs +++ b/Source/Engine/UI/GUI/ContainerControl.cs @@ -360,7 +360,7 @@ namespace FlaxEngine.GUI { var containerControl = child as ContainerControl; var childAtRecursive = containerControl?.GetChildAtRecursive(childLocation); - if (childAtRecursive != null) + if (childAtRecursive != null && childAtRecursive.Visible) { child = childAtRecursive; } @@ -507,15 +507,19 @@ namespace FlaxEngine.GUI // Perform automatic navigation based on the layout var result = NavigationRaycast(direction, location, visited); - if (result == null && direction == NavDirection.Next) + var rightMostLocation = location; + if (result == null && (direction == NavDirection.Next || direction == NavDirection.Previous)) { // Try wrap the navigation over the layout based on the direction var visitedWrap = new List(visited); - result = NavigationWrap(direction, location, visitedWrap); + result = NavigationWrap(direction, location, visitedWrap, out rightMostLocation); } if (result != null) { - result = result.OnNavigate(direction, result.PointFromParent(location), this, visited); + // HACK: only the 'previous' direction needs the rightMostLocation so i used a ternary conditional operator. + // The rightMostLocation can probably become a 'desired raycast origin' that gets calculated correctly in the NavigationWrap method. + var useLocation = direction == NavDirection.Previous ? rightMostLocation : location; + result = result.OnNavigate(direction, result.PointFromParent(useLocation), this, visited); if (result != null) return result; } @@ -551,8 +555,9 @@ namespace FlaxEngine.GUI /// The navigation direction. /// The navigation start location (in the control-space). /// The list with visited controls. Used to skip recursive navigation calls when doing traversal across the UI hierarchy. + /// Returns the rightmost location of the parent container for the raycast used by the child container /// The target navigation control or null if didn't performed any navigation. - protected virtual Control NavigationWrap(NavDirection direction, Float2 location, List visited) + protected virtual Control NavigationWrap(NavDirection direction, Float2 location, List visited, out Float2 rightMostLocation) { // This searches form a child that calls this navigation event (see Control.OnNavigate) to determinate the layout wrapping size based on that child size var currentChild = RootWindow?.FocusedControl; @@ -566,15 +571,22 @@ namespace FlaxEngine.GUI case NavDirection.Next: predictedLocation = new Float2(0, location.Y + layoutSize.Y); break; + case NavDirection.Previous: + predictedLocation = new Float2(Size.X, location.Y - layoutSize.Y); + break; } if (new Rectangle(Float2.Zero, Size).Contains(ref predictedLocation)) { var result = NavigationRaycast(direction, predictedLocation, visited); if (result != null) - return result; + { + rightMostLocation = predictedLocation; + return result; + } } } - return Parent?.NavigationWrap(direction, PointToParent(ref location), visited); + rightMostLocation = location; + return Parent?.NavigationWrap(direction, PointToParent(ref location), visited, out rightMostLocation); } private static bool CanGetAutoFocus(Control c) @@ -613,6 +625,10 @@ namespace FlaxEngine.GUI uiDir1 = new Float2(1, 0); uiDir2 = new Float2(0, 1); break; + case NavDirection.Previous: + uiDir1 = new Float2(-1, 0); + uiDir2 = new Float2(0, -1); + break; } Control result = null; var minDistance = float.MaxValue; diff --git a/Source/Engine/UI/GUI/Control.cs b/Source/Engine/UI/GUI/Control.cs index 3bc2610a4..5cb9501c0 100644 --- a/Source/Engine/UI/GUI/Control.cs +++ b/Source/Engine/UI/GUI/Control.cs @@ -634,6 +634,7 @@ namespace FlaxEngine.GUI case NavDirection.Left: return new Float2(0, size.Y * 0.5f); case NavDirection.Right: return new Float2(size.X, size.Y * 0.5f); case NavDirection.Next: return Float2.Zero; + case NavDirection.Previous: return size; default: return size * 0.5f; } } diff --git a/Source/Engine/UI/GUI/Enums.cs b/Source/Engine/UI/GUI/Enums.cs index d0b4fb61c..9672dcb9b 100644 --- a/Source/Engine/UI/GUI/Enums.cs +++ b/Source/Engine/UI/GUI/Enums.cs @@ -202,5 +202,10 @@ namespace FlaxEngine.GUI /// The next item (right with layout wrapping). /// Next, + + /// + /// The previous item (left with layout wrapping). + /// + Previous, } } diff --git a/Source/Engine/UI/GUI/Panels/BlurPanel.cs b/Source/Engine/UI/GUI/Panels/BlurPanel.cs index 0fe506e44..055535aa8 100644 --- a/Source/Engine/UI/GUI/Panels/BlurPanel.cs +++ b/Source/Engine/UI/GUI/Panels/BlurPanel.cs @@ -11,7 +11,7 @@ namespace FlaxEngine.GUI /// /// Gets or sets the blur strength. Defines how blurry the background is. Larger numbers increase blur, resulting in a larger runtime cost on the GPU. /// - [EditorOrder(0), Limit(0, 100, 0.0f)] + [EditorOrder(0), Limit(0, 100, 0.1f)] public float BlurStrength { get; set; } /// @@ -29,10 +29,9 @@ namespace FlaxEngine.GUI } /// - public override void Draw() + public override void DrawSelf() { - base.Draw(); - + base.DrawSelf(); var size = Size; var strength = BlurStrength; if (BlurScaleWithSize) diff --git a/Source/Engine/UI/GUI/Style.cs b/Source/Engine/UI/GUI/Style.cs index fac65e22f..8f34a703c 100644 --- a/Source/Engine/UI/GUI/Style.cs +++ b/Source/Engine/UI/GUI/Style.cs @@ -164,6 +164,12 @@ namespace FlaxEngine.GUI [EditorOrder(200)] public Color ProgressNormal; + /// + /// The status bar style + /// + [EditorOrder(210)] + public StatusbarStyle Statusbar; + /// /// The arrow right icon. /// @@ -241,5 +247,27 @@ namespace FlaxEngine.GUI /// [EditorOrder(340)] public Tooltip SharedTooltip; + + /// + /// Style for the Statusbar + /// + [System.Serializable, ShowInEditor] + public struct StatusbarStyle + { + /// + /// Color of the Statusbar when in Play Mode + /// + public Color PlayMode; + + /// + /// Color of the Statusbar when in loading state (e.g. when importing assets) + /// + public Color Loading; + + /// + /// Color of the Statusbar in its failed state (e.g. with compilation errors) + /// + public Color Failed; + } } } diff --git a/Source/Engine/UI/GUI/Tooltip.cs b/Source/Engine/UI/GUI/Tooltip.cs index 0a54610fd..734fb078f 100644 --- a/Source/Engine/UI/GUI/Tooltip.cs +++ b/Source/Engine/UI/GUI/Tooltip.cs @@ -68,26 +68,12 @@ namespace FlaxEngine.GUI var parentWin = target.Root; if (parentWin == null) return; - float dpiScale = target.RootWindow.DpiScale; + var dpiScale = target.RootWindow.DpiScale; var dpiSize = Size * dpiScale; var locationWS = target.PointToWindow(location); var locationSS = parentWin.PointToScreen(locationWS); - var monitorBounds = Platform.GetMonitorBounds(locationSS); - var rightBottomMonitorBounds = monitorBounds.BottomRight; - var rightBottomLocationSS = locationSS + dpiSize; - - // Prioritize tooltip placement within parent window, fall back to virtual desktop - if (rightBottomMonitorBounds.Y < rightBottomLocationSS.Y) - { - // Direction: up - locationSS.Y -= dpiSize.Y; - } - if (rightBottomMonitorBounds.X < rightBottomLocationSS.X) - { - // Direction: left - locationSS.X -= dpiSize.X; - } _showTarget = target; + WrapPosition(ref locationSS); // Create window var desc = CreateWindowSettings.Default; @@ -106,6 +92,7 @@ namespace FlaxEngine.GUI desc.IsTopmost = true; desc.IsRegularWindow = false; desc.HasSizingFrame = false; + desc.ShowAfterFirstPaint = true; _window = Platform.CreateWindow(ref desc); if (_window == null) throw new InvalidOperationException("Failed to create tooltip window."); @@ -192,11 +179,41 @@ namespace FlaxEngine.GUI } } + private void WrapPosition(ref Float2 locationSS, float flipOffset = 0.0f) + { + if (_showTarget?.RootWindow == null) + return; + + // Calculate popup direction + var dpiScale = _showTarget.RootWindow.DpiScale; + var dpiSize = Size * dpiScale; + var monitorBounds = Platform.GetMonitorBounds(locationSS); + var rightBottomMonitorBounds = monitorBounds.BottomRight; + var rightBottomLocationSS = locationSS + dpiSize; + + // Prioritize tooltip placement within parent window, fall back to virtual desktop + if (rightBottomMonitorBounds.Y < rightBottomLocationSS.Y) + { + // Direction: up + locationSS.Y -= dpiSize.Y + flipOffset; + } + if (rightBottomMonitorBounds.X < rightBottomLocationSS.X) + { + // Direction: left + locationSS.X -= dpiSize.X + flipOffset * 2; + } + } + /// public override void Update(float deltaTime) { - // Auto hide if mouse leaves control area + // Move window with mouse location var mousePos = Input.MouseScreenPosition; + WrapPosition(ref mousePos, 10); + if (_window) + _window.Position = mousePos + new Float2(15, 10); + + // Auto hide if mouse leaves control area var location = _showTarget.PointFromScreen(mousePos); if (!_showTarget.OnTestTooltipOverControl(ref location)) { diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index cd60abddf..d161db0ee 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -366,6 +366,7 @@ void TextRender::Draw(RenderContext& renderContext) DrawCall drawCall; drawCall.World = world; drawCall.ObjectPosition = drawCall.World.GetTranslation(); + drawCall.ObjectRadius = _sphere.Radius; drawCall.Surface.GeometrySize = _localBox.GetSize(); drawCall.Surface.PrevWorld = _drawState.PrevWorld; drawCall.Surface.Lightmap = nullptr; diff --git a/Source/Engine/UI/UICanvas.cs b/Source/Engine/UI/UICanvas.cs index 8cb43af62..edbec7f7d 100644 --- a/Source/Engine/UI/UICanvas.cs +++ b/Source/Engine/UI/UICanvas.cs @@ -493,7 +493,8 @@ namespace FlaxEngine if (_renderer) { #if FLAX_EDITOR - _editorTask?.RemoveCustomPostFx(_renderer); + if (_editorTask != null) + _editorTask.RemoveCustomPostFx(_renderer); #endif SceneRenderTask.RemoveGlobalCustomPostFx(_renderer); _renderer.Canvas = null; diff --git a/Source/Engine/UI/UIControl.cs b/Source/Engine/UI/UIControl.cs index dc76122c0..219a4fa4e 100644 --- a/Source/Engine/UI/UIControl.cs +++ b/Source/Engine/UI/UIControl.cs @@ -204,7 +204,7 @@ namespace FlaxEngine up = value; Internal_SetNavTargets(__unmanagedPtr, GetUnmanagedPtr(up), GetUnmanagedPtr(down), GetUnmanagedPtr(left), GetUnmanagedPtr(right)); if (_control != null) - _control.NavTargetUp = value?.Control; + _control.NavTargetUp = value != null ? value.Control : null; } } @@ -228,7 +228,7 @@ namespace FlaxEngine down = value; Internal_SetNavTargets(__unmanagedPtr, GetUnmanagedPtr(up), GetUnmanagedPtr(down), GetUnmanagedPtr(left), GetUnmanagedPtr(right)); if (_control != null) - _control.NavTargetDown = value?.Control; + _control.NavTargetDown = value != null ? value.Control : null; } } @@ -252,7 +252,7 @@ namespace FlaxEngine left = value; Internal_SetNavTargets(__unmanagedPtr, GetUnmanagedPtr(up), GetUnmanagedPtr(down), GetUnmanagedPtr(left), GetUnmanagedPtr(right)); if (_control != null) - _control.NavTargetLeft = value?.Control; + _control.NavTargetLeft = value != null ? value.Control : null; } } @@ -276,7 +276,7 @@ namespace FlaxEngine right = value; Internal_SetNavTargets(__unmanagedPtr, GetUnmanagedPtr(up), GetUnmanagedPtr(down), GetUnmanagedPtr(left), GetUnmanagedPtr(right)); if (_control != null) - _control.NavTargetRight = value?.Control; + _control.NavTargetRight = value != null ? value.Control : null; } } diff --git a/Source/Engine/Visject/ShaderGraphValue.cpp b/Source/Engine/Visject/ShaderGraphValue.cpp index b55da8cdb..56d7c9d06 100644 --- a/Source/Engine/Visject/ShaderGraphValue.cpp +++ b/Source/Engine/Visject/ShaderGraphValue.cpp @@ -40,11 +40,11 @@ ShaderGraphValue::ShaderGraphValue(const Variant& v) break; case VariantType::Float: Type = VariantType::Types::Float; - Value = String::Format(TEXT("{}"), v.AsFloat); + Value = String::Format(TEXT("{:.8f}"), v.AsFloat); break; case VariantType::Double: Type = VariantType::Types::Float; - Value = String::Format(TEXT("{}"), (float)v.AsDouble); + Value = String::Format(TEXT("{:.8f}"), (float)v.AsDouble); break; case VariantType::Float2: { diff --git a/Source/ThirdParty/recastnavigation/DetourAssert.h b/Source/ThirdParty/recastnavigation/DetourAssert.h index e05fd66fa..038d538a5 100644 --- a/Source/ThirdParty/recastnavigation/DetourAssert.h +++ b/Source/ThirdParty/recastnavigation/DetourAssert.h @@ -24,7 +24,7 @@ #ifdef NDEBUG -// From http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/ +// From https://web.archive.org/web/20210117002833/http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/ # define dtAssert(x) do { (void)sizeof(x); } while((void)(__LINE__==-1),false) #else diff --git a/Source/ThirdParty/recastnavigation/DetourCommon.cpp b/Source/ThirdParty/recastnavigation/DetourCommon.cpp index 7abdff072..8a9a4a42a 100644 --- a/Source/ThirdParty/recastnavigation/DetourCommon.cpp +++ b/Source/ThirdParty/recastnavigation/DetourCommon.cpp @@ -112,7 +112,7 @@ bool dtIntersectSegmentPoly2D(const float* p0, const float* p1, float& tmin, float& tmax, int& segMin, int& segMax) { - static const float EPS = 0.00000001f; + static const float EPS = 0.000001f; tmin = 0; tmax = 1; diff --git a/Source/ThirdParty/recastnavigation/DetourCommon.h b/Source/ThirdParty/recastnavigation/DetourCommon.h index a2597f3a2..656311e90 100644 --- a/Source/ThirdParty/recastnavigation/DetourCommon.h +++ b/Source/ThirdParty/recastnavigation/DetourCommon.h @@ -37,7 +37,6 @@ feature to find minor members. /// Used to ignore a function parameter. VS complains about unused parameters /// and this silences the warning. -/// @param [in] _ Unused parameter template void dtIgnoreUnused(const T&) { } /// Swaps the values of the two parameters. @@ -319,7 +318,7 @@ inline float dtVdot2D(const float* u, const float* v) /// Derives the xz-plane 2D perp product of the two vectors. (uz*vx - ux*vz) /// @param[in] u The LHV vector [(x, y, z)] /// @param[in] v The RHV vector [(x, y, z)] -/// @return The dot product on the xz-plane. +/// @return The perp dot product on the xz-plane. /// /// The vectors are projected onto the xz-plane, so the y-values are ignored. inline float dtVperp2D(const float* u, const float* v) diff --git a/Source/ThirdParty/recastnavigation/DetourCrowd.cpp b/Source/ThirdParty/recastnavigation/DetourCrowd.cpp index 3f0311f7f..d7cdae0b9 100644 --- a/Source/ThirdParty/recastnavigation/DetourCrowd.cpp +++ b/Source/ThirdParty/recastnavigation/DetourCrowd.cpp @@ -16,7 +16,6 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include #include diff --git a/Source/ThirdParty/recastnavigation/DetourCrowd.h b/Source/ThirdParty/recastnavigation/DetourCrowd.h index 952050878..854546fc1 100644 --- a/Source/ThirdParty/recastnavigation/DetourCrowd.h +++ b/Source/ThirdParty/recastnavigation/DetourCrowd.h @@ -66,7 +66,7 @@ enum CrowdAgentState { DT_CROWDAGENT_STATE_INVALID, ///< The agent is not in a valid state. DT_CROWDAGENT_STATE_WALKING, ///< The agent is traversing a normal navigation mesh polygon. - DT_CROWDAGENT_STATE_OFFMESH, ///< The agent is traversing an off-mesh connection. + DT_CROWDAGENT_STATE_OFFMESH ///< The agent is traversing an off-mesh connection. }; /// Configuration parameters for a crowd agent. @@ -108,7 +108,7 @@ enum MoveRequestState DT_CROWDAGENT_TARGET_REQUESTING, DT_CROWDAGENT_TARGET_WAITING_FOR_QUEUE, DT_CROWDAGENT_TARGET_WAITING_FOR_PATH, - DT_CROWDAGENT_TARGET_VELOCITY, + DT_CROWDAGENT_TARGET_VELOCITY }; /// Represents an agent managed by a #dtCrowd object. @@ -188,7 +188,7 @@ enum UpdateFlags DT_CROWD_OBSTACLE_AVOIDANCE = 2, DT_CROWD_SEPARATION = 4, DT_CROWD_OPTIMIZE_VIS = 8, ///< Use #dtPathCorridor::optimizePathVisibility() to optimize the agent path. - DT_CROWD_OPTIMIZE_TOPO = 16, ///< Use dtPathCorridor::optimizePathTopology() to optimize the agent path. + DT_CROWD_OPTIMIZE_TOPO = 16 ///< Use dtPathCorridor::optimizePathTopology() to optimize the agent path. }; struct dtCrowdAgentDebugInfo diff --git a/Source/ThirdParty/recastnavigation/DetourNavMesh.cpp b/Source/ThirdParty/recastnavigation/DetourNavMesh.cpp index b119cd541..2240526eb 100644 --- a/Source/ThirdParty/recastnavigation/DetourNavMesh.cpp +++ b/Source/ThirdParty/recastnavigation/DetourNavMesh.cpp @@ -433,8 +433,8 @@ void dtNavMesh::connectExtLinks(dtMeshTile* tile, dtMeshTile* target, int side) float tmax = (neia[k*2+1]-va[2]) / (vb[2]-va[2]); if (tmin > tmax) dtSwap(tmin,tmax); - link->bmin = (unsigned char)(dtClamp(tmin, 0.0f, 1.0f)*255.0f); - link->bmax = (unsigned char)(dtClamp(tmax, 0.0f, 1.0f)*255.0f); + link->bmin = (unsigned char)roundf(dtClamp(tmin, 0.0f, 1.0f)*255.0f); + link->bmax = (unsigned char)roundf(dtClamp(tmax, 0.0f, 1.0f)*255.0f); } else if (dir == 2 || dir == 6) { @@ -442,8 +442,8 @@ void dtNavMesh::connectExtLinks(dtMeshTile* tile, dtMeshTile* target, int side) float tmax = (neia[k*2+1]-va[0]) / (vb[0]-va[0]); if (tmin > tmax) dtSwap(tmin,tmax); - link->bmin = (unsigned char)(dtClamp(tmin, 0.0f, 1.0f)*255.0f); - link->bmax = (unsigned char)(dtClamp(tmax, 0.0f, 1.0f)*255.0f); + link->bmin = (unsigned char)roundf(dtClamp(tmin, 0.0f, 1.0f)*255.0f); + link->bmax = (unsigned char)roundf(dtClamp(tmax, 0.0f, 1.0f)*255.0f); } } } diff --git a/Source/ThirdParty/recastnavigation/DetourNavMesh.h b/Source/ThirdParty/recastnavigation/DetourNavMesh.h index 9ac1dc8d6..4c1277d78 100644 --- a/Source/ThirdParty/recastnavigation/DetourNavMesh.h +++ b/Source/ThirdParty/recastnavigation/DetourNavMesh.h @@ -99,7 +99,7 @@ static const int DT_MAX_AREAS = 64; enum dtTileFlags { /// The navigation mesh owns the tile memory and is responsible for freeing it. - DT_TILE_FREE_DATA = 0x01, + DT_TILE_FREE_DATA = 0x01 }; /// Vertex flags returned by dtNavMeshQuery::findStraightPath. @@ -107,32 +107,32 @@ enum dtStraightPathFlags { DT_STRAIGHTPATH_START = 0x01, ///< The vertex is the start position in the path. DT_STRAIGHTPATH_END = 0x02, ///< The vertex is the end position in the path. - DT_STRAIGHTPATH_OFFMESH_CONNECTION = 0x04, ///< The vertex is the start of an off-mesh connection. + DT_STRAIGHTPATH_OFFMESH_CONNECTION = 0x04 ///< The vertex is the start of an off-mesh connection. }; /// Options for dtNavMeshQuery::findStraightPath. enum dtStraightPathOptions { DT_STRAIGHTPATH_AREA_CROSSINGS = 0x01, ///< Add a vertex at every polygon edge crossing where area changes. - DT_STRAIGHTPATH_ALL_CROSSINGS = 0x02, ///< Add a vertex at every polygon edge crossing. + DT_STRAIGHTPATH_ALL_CROSSINGS = 0x02 ///< Add a vertex at every polygon edge crossing. }; /// Options for dtNavMeshQuery::initSlicedFindPath and updateSlicedFindPath enum dtFindPathOptions { - DT_FINDPATH_ANY_ANGLE = 0x02, ///< use raycasts during pathfind to "shortcut" (raycast still consider costs) + DT_FINDPATH_ANY_ANGLE = 0x02 ///< use raycasts during pathfind to "shortcut" (raycast still consider costs) }; /// Options for dtNavMeshQuery::raycast enum dtRaycastOptions { - DT_RAYCAST_USE_COSTS = 0x01, ///< Raycast should calculate movement cost along the ray and fill RaycastHit::cost + DT_RAYCAST_USE_COSTS = 0x01 ///< Raycast should calculate movement cost along the ray and fill RaycastHit::cost }; enum dtDetailTriEdgeFlags { - DT_DETAIL_EDGE_BOUNDARY = 0x01, ///< Detail triangle edge is part of the poly boundary + DT_DETAIL_EDGE_BOUNDARY = 0x01 ///< Detail triangle edge is part of the poly boundary }; @@ -146,7 +146,7 @@ enum dtPolyTypes /// The polygon is a standard convex polygon that is part of the surface of the mesh. DT_POLYTYPE_GROUND = 0, /// The polygon is an off-mesh connection consisting of two vertices. - DT_POLYTYPE_OFFMESH_CONNECTION = 1, + DT_POLYTYPE_OFFMESH_CONNECTION = 1 }; @@ -285,7 +285,7 @@ struct dtMeshTile unsigned int linksFreeList; ///< Index to the next free link. dtMeshHeader* header; ///< The tile header. dtPoly* polys; ///< The tile polygons. [Size: dtMeshHeader::polyCount] - float* verts; ///< The tile vertices. [Size: dtMeshHeader::vertCount] + float* verts; ///< The tile vertices. [(x, y, z) * dtMeshHeader::vertCount] dtLink* links; ///< The tile links. [Size: dtMeshHeader::maxLinkCount] dtPolyDetail* detailMeshes; ///< The tile's detail sub-meshes. [Size: dtMeshHeader::detailMeshCount] @@ -312,8 +312,8 @@ private: }; /// Get flags for edge in detail triangle. -/// @param triFlags[in] The flags for the triangle (last component of detail vertices above). -/// @param edgeIndex[in] The index of the first vertex of the edge. For instance, if 0, +/// @param[in] triFlags The flags for the triangle (last component of detail vertices above). +/// @param[in] edgeIndex The index of the first vertex of the edge. For instance, if 0, /// returns flags for edge AB. inline int dtGetDetailTriEdgeFlags(unsigned char triFlags, int edgeIndex) { diff --git a/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.cpp b/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.cpp index 1e519d211..7faefde81 100644 --- a/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.cpp +++ b/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.cpp @@ -117,6 +117,11 @@ void dtFreeNavMeshQuery(dtNavMeshQuery* navmesh) dtFree(navmesh); } +dtPolyQuery::~dtPolyQuery() +{ + // Defined out of line to fix the weak v-tables warning +} + ////////////////////////////////////////////////////////////////////////////////////////// /// @class dtNavMeshQuery @@ -301,11 +306,7 @@ dtStatus dtNavMeshQuery::findRandomPoint(const dtQueryFilter* filter, float (*fr float pt[3]; dtRandomPointInConvexPoly(verts, poly->vertCount, areas, s, t, pt); - float h = 0.0f; - dtStatus status = getPolyHeight(polyRef, pt, &h); - if (dtStatusFailed(status)) - return status; - pt[1] = h; + closestPointOnPoly(polyRef, pt, pt, NULL); dtVcopy(randomPt, pt); *randomRef = polyRef; @@ -481,26 +482,25 @@ dtStatus dtNavMeshQuery::findRandomPointAroundCircle(dtPolyRef startRef, const f v = &randomTile->verts[randomPoly->verts[j]*3]; dtVcopy(&verts[j*3],v); } - + float pt[3]; + int checksLimit = 100; do { const float s = frand(); - const float t = frand(); - dtRandomPointInConvexPoly(verts, randomPoly->vertCount, areas, s, t, pt); + const float t = frand(); + dtRandomPointInConvexPoly(verts, randomPoly->vertCount, areas, s, t, pt); } - while (dtDistancePtPtSqr2D(centerPos, pt) > radiusSqr); - - float h = 0.0f; - dtStatus stat = getPolyHeight(randomPolyRef, pt, &h); - if (dtStatusFailed(status)) - return stat; - pt[1] = h; + while (dtDistancePtPtSqr2D(centerPos, pt) > radiusSqr && checksLimit-- > 0); + if (checksLimit <= 0) + return DT_FAILURE; + + closestPointOnPoly(randomPolyRef, pt, pt, NULL); dtVcopy(randomPt, pt); *randomRef = randomPolyRef; - return DT_SUCCESS; + return status; } @@ -641,6 +641,8 @@ public: { } + virtual ~dtFindNearestPolyQuery(); + dtPolyRef nearestRef() const { return m_nearestRef; } const float* nearestPoint() const { return m_nearestPoint; } bool isOverPoly() const { return m_overPoly; } @@ -683,6 +685,11 @@ public: } }; +dtFindNearestPolyQuery::~dtFindNearestPolyQuery() +{ + // Defined out of line to fix the weak v-tables warning +} + /// @par /// /// @note If the search box does not intersect any polygons the search will @@ -858,6 +865,8 @@ public: { } + virtual ~dtCollectPolysQuery(); + int numCollected() const { return m_numCollected; } bool overflowed() const { return m_overflow; } @@ -879,6 +888,11 @@ public: } }; +dtCollectPolysQuery::~dtCollectPolysQuery() +{ + // Defined out of line to fix the weak v-tables warning +} + /// @par /// /// If no polygons are found, the function will return #DT_SUCCESS with a diff --git a/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.h b/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.h index 43c7268f2..244b20065 100644 --- a/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.h +++ b/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.h @@ -153,7 +153,7 @@ struct dtRaycastHit class dtPolyQuery { public: - virtual ~dtPolyQuery() { } + virtual ~dtPolyQuery(); /// Called for each batch of unique polygons touched by the search area in dtNavMeshQuery::queryPolygons. /// This can be called multiple times for a single query. @@ -176,7 +176,7 @@ public: dtStatus init(const dtNavMesh* nav, const int maxNodes); /// @name Standard Pathfinding Functions - // /@{ + /// @{ /// Finds a path from the start polygon to the end polygon. /// @param[in] startRef The refrence id of the start polygon. @@ -397,9 +397,9 @@ public: /// @param[in] startPos A position within the start polygon representing /// the start of the ray. [(x, y, z)] /// @param[in] endPos The position to cast the ray toward. [(x, y, z)] + /// @param[in] filter The polygon filter to apply to the query. /// @param[out] t The hit parameter. (FLT_MAX if no wall hit.) /// @param[out] hitNormal The normal of the nearest wall hit. [(x, y, z)] - /// @param[in] filter The polygon filter to apply to the query. /// @param[out] path The reference ids of the visited polygons. [opt] /// @param[out] pathCount The number of visited polygons. [opt] /// @param[in] maxPath The maximum number of polygons the @p path array can hold. @@ -415,7 +415,7 @@ public: /// the start of the ray. [(x, y, z)] /// @param[in] endPos The position to cast the ray toward. [(x, y, z)] /// @param[in] filter The polygon filter to apply to the query. - /// @param[in] flags govern how the raycast behaves. See dtRaycastOptions + /// @param[in] options govern how the raycast behaves. See dtRaycastOptions /// @param[out] hit Pointer to a raycast hit structure which will be filled by the results. /// @param[in] prevRef parent of start ref. Used during for cost calculation [opt] /// @returns The status flags for the query. @@ -466,6 +466,7 @@ public: /// The location is not exactly constrained by the circle, but it limits the visited polygons. /// @param[in] startRef The reference id of the polygon where the search starts. /// @param[in] centerPos The center of the search circle. [(x, y, z)] + /// @param[in] maxRadius The radius of the search circle. [Units: wu] /// @param[in] filter The polygon filter to apply to the query. /// @param[in] frand Function returning a random number [0..1). /// @param[out] randomRef The reference id of the random location. diff --git a/Source/ThirdParty/recastnavigation/DetourNode.h b/Source/ThirdParty/recastnavigation/DetourNode.h index db0974708..8918d4686 100644 --- a/Source/ThirdParty/recastnavigation/DetourNode.h +++ b/Source/ThirdParty/recastnavigation/DetourNode.h @@ -25,7 +25,7 @@ enum dtNodeFlags { DT_NODE_OPEN = 0x01, DT_NODE_CLOSED = 0x02, - DT_NODE_PARENT_DETACHED = 0x04, // parent of the node is not adjacent. Found using raycast. + DT_NODE_PARENT_DETACHED = 0x04 // parent of the node is not adjacent. Found using raycast. }; typedef unsigned short dtNodeIndex; diff --git a/Source/ThirdParty/recastnavigation/DetourTileCache.cpp b/Source/ThirdParty/recastnavigation/DetourTileCache.cpp index a82cd1350..6f97ab5b7 100644 --- a/Source/ThirdParty/recastnavigation/DetourTileCache.cpp +++ b/Source/ThirdParty/recastnavigation/DetourTileCache.cpp @@ -239,6 +239,11 @@ const dtTileCacheObstacle* dtTileCache::getObstacleByRef(dtObstacleRef ref) return ob; } +dtTileCacheMeshProcess::~dtTileCacheMeshProcess() +{ + // Defined out of line to fix the weak v-tables warning +} + dtStatus dtTileCache::addTile(unsigned char* data, const int dataSize, unsigned char flags, dtCompressedTileRef* result) { // Make sure the data is in right format. diff --git a/Source/ThirdParty/recastnavigation/DetourTileCache.h b/Source/ThirdParty/recastnavigation/DetourTileCache.h index 75713366d..0d346f17c 100644 --- a/Source/ThirdParty/recastnavigation/DetourTileCache.h +++ b/Source/ThirdParty/recastnavigation/DetourTileCache.h @@ -3,16 +3,13 @@ #include "DetourStatus.h" - - typedef unsigned int dtObstacleRef; - typedef unsigned int dtCompressedTileRef; /// Flags for addTile enum dtCompressedTileFlags { - DT_COMPRESSEDTILE_FREE_DATA = 0x01, ///< Navmesh owns the tile memory and should free it. + DT_COMPRESSEDTILE_FREE_DATA = 0x01 ///< Navmesh owns the tile memory and should free it. }; struct dtCompressedTile @@ -32,14 +29,14 @@ enum ObstacleState DT_OBSTACLE_EMPTY, DT_OBSTACLE_PROCESSING, DT_OBSTACLE_PROCESSED, - DT_OBSTACLE_REMOVING, + DT_OBSTACLE_REMOVING }; enum ObstacleType { DT_OBSTACLE_CYLINDER, DT_OBSTACLE_BOX, // AABB - DT_OBSTACLE_ORIENTED_BOX, // OBB + DT_OBSTACLE_ORIENTED_BOX // OBB }; struct dtObstacleCylinder @@ -97,13 +94,10 @@ struct dtTileCacheParams struct dtTileCacheMeshProcess { - virtual ~dtTileCacheMeshProcess() { } - - virtual void process(struct dtNavMeshCreateParams* params, - unsigned char* polyAreas, unsigned short* polyFlags) = 0; + virtual ~dtTileCacheMeshProcess(); + virtual void process(struct dtNavMeshCreateParams* params, unsigned char* polyAreas, unsigned short* polyFlags) = 0; }; - class dtTileCache { public: @@ -219,7 +213,7 @@ private: enum ObstacleRequestAction { REQUEST_ADD, - REQUEST_REMOVE, + REQUEST_REMOVE }; struct ObstacleRequest diff --git a/Source/ThirdParty/recastnavigation/DetourTileCacheBuilder.cpp b/Source/ThirdParty/recastnavigation/DetourTileCacheBuilder.cpp index 21a59f1a6..dbc09eb48 100644 --- a/Source/ThirdParty/recastnavigation/DetourTileCacheBuilder.cpp +++ b/Source/ThirdParty/recastnavigation/DetourTileCacheBuilder.cpp @@ -23,6 +23,15 @@ #include "DetourTileCacheBuilder.h" #include +dtTileCacheAlloc::~dtTileCacheAlloc() +{ + // Defined out of line to fix the weak v-tables warning +} + +dtTileCacheCompressor::~dtTileCacheCompressor() +{ + // Defined out of line to fix the weak v-tables warning +} template class dtFixedArray { @@ -881,7 +890,7 @@ static bool buildMeshAdjacency(dtTileCacheAlloc* alloc, const dtTileCacheContourSet& lcset) { // Based on code by Eric Lengyel from: - // http://www.terathon.com/code/edges.php + // https://web.archive.org/web/20080704083314/http://www.terathon.com/code/edges.php const int maxEdgeCount = npolys*MAX_VERTS_PER_POLY; dtFixedArray firstEdge(alloc, nverts + maxEdgeCount); @@ -1399,7 +1408,6 @@ static void pushBack(unsigned short v, unsigned short* arr, int& an) static bool canRemoveVertex(dtTileCachePolyMesh& mesh, const unsigned short rem) { // Count number of polygons to remove. - int numRemovedVerts = 0; int numTouchedVerts = 0; int numRemainingEdges = 0; for (int i = 0; i < mesh.npolys; ++i) @@ -1419,7 +1427,6 @@ static bool canRemoveVertex(dtTileCachePolyMesh& mesh, const unsigned short rem) } if (numRemoved) { - numRemovedVerts += numRemoved; numRemainingEdges += numVerts-(numRemoved+1); } } @@ -1551,7 +1558,7 @@ static dtStatus removeVertex(dtTileCachePolyMesh& mesh, const unsigned short rem } // Remove vertex. - for (int i = (int)rem; i < mesh.nverts; ++i) + for (int i = (int)rem; i < mesh.nverts - 1; ++i) { mesh.verts[i*3+0] = mesh.verts[(i+1)*3+0]; mesh.verts[i*3+1] = mesh.verts[(i+1)*3+1]; diff --git a/Source/ThirdParty/recastnavigation/DetourTileCacheBuilder.h b/Source/ThirdParty/recastnavigation/DetourTileCacheBuilder.h index ff6109193..9a6844c65 100644 --- a/Source/ThirdParty/recastnavigation/DetourTileCacheBuilder.h +++ b/Source/ThirdParty/recastnavigation/DetourTileCacheBuilder.h @@ -78,7 +78,7 @@ struct dtTileCachePolyMesh struct dtTileCacheAlloc { - virtual ~dtTileCacheAlloc() {} + virtual ~dtTileCacheAlloc(); virtual void reset() {} @@ -95,7 +95,7 @@ struct dtTileCacheAlloc struct dtTileCacheCompressor { - virtual ~dtTileCacheCompressor() { } + virtual ~dtTileCacheCompressor(); virtual int maxCompressedSize(const int bufferSize) = 0; virtual dtStatus compress(const unsigned char* buffer, const int bufferSize, diff --git a/Source/ThirdParty/recastnavigation/Recast.cpp b/Source/ThirdParty/recastnavigation/Recast.cpp index 1b71710cd..d75a9f59f 100644 --- a/Source/ThirdParty/recastnavigation/Recast.cpp +++ b/Source/ThirdParty/recastnavigation/Recast.cpp @@ -16,98 +16,93 @@ // 3. This notice may not be removed or altered from any source distribution. // -#include -#define _USE_MATH_DEFINES -#include -#include -#include -#include -#include #include "Recast.h" #include "RecastAlloc.h" #include "RecastAssert.h" +#include +#include +#include +#include + namespace { /// Allocates and constructs an object of the given type, returning a pointer. -/// TODO: Support constructor args. -/// @param[in] hint Hint to the allocator. -template -T* rcNew(rcAllocHint hint) { - T* ptr = (T*)rcAlloc(sizeof(T), hint); +/// @param[in] allocLifetime Allocation lifetime hint +template +T* rcNew(const rcAllocHint allocLifetime) +{ + T* ptr = (T*)rcAlloc(sizeof(T), allocLifetime); ::new(rcNewTag(), (void*)ptr) T(); return ptr; } /// Destroys and frees an object allocated with rcNew. /// @param[in] ptr The object pointer to delete. -template -void rcDelete(T* ptr) { - if (ptr) { +template +void rcDelete(T* ptr) +{ + if (ptr) + { ptr->~T(); rcFree((void*)ptr); } } -} // namespace - +} // anonymous namespace float rcSqrt(float x) { return sqrtf(x); } -/// @class rcContext -/// @par -/// -/// This class does not provide logging or timer functionality on its -/// own. Both must be provided by a concrete implementation -/// by overriding the protected member functions. Also, this class does not -/// provide an interface for extracting log messages. (Only adding them.) -/// So concrete implementations must provide one. -/// -/// If no logging or timers are required, just pass an instance of this -/// class through the Recast build process. -/// - -/// @par -/// -/// Example: -/// @code -/// // Where ctx is an instance of rcContext and filepath is a char array. -/// ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Could not load '%s'", filepath); -/// @endcode void rcContext::log(const rcLogCategory category, const char* format, ...) { if (!m_logEnabled) + { return; + } static const int MSG_SIZE = 512; char msg[MSG_SIZE]; - va_list ap; - va_start(ap, format); - int len = vsnprintf(msg, MSG_SIZE, format, ap); + va_list argList; + va_start(argList, format); + int len = vsnprintf(msg, MSG_SIZE, format, argList); if (len >= MSG_SIZE) { - len = MSG_SIZE-1; - msg[MSG_SIZE-1] = '\0'; + len = MSG_SIZE - 1; + msg[MSG_SIZE - 1] = '\0'; + + const char* errorMessage = "Log message was truncated"; + doLog(RC_LOG_ERROR, errorMessage, (int)strlen(errorMessage)); } - va_end(ap); + va_end(argList); doLog(category, msg, len); } +void rcContext::doResetLog() +{ + // Defined out of line to fix the weak v-tables warning +} + rcHeightfield* rcAllocHeightfield() { return rcNew(RC_ALLOC_PERM); } + +void rcFreeHeightField(rcHeightfield* heightfield) +{ + rcDelete(heightfield); +} + rcHeightfield::rcHeightfield() - : width() - , height() - , bmin() - , bmax() - , cs() - , ch() - , spans() - , pools() - , freelist() +: width() +, height() +, bmin() +, bmax() +, cs() +, ch() +, spans() +, pools() +, freelist() { } @@ -124,40 +119,36 @@ rcHeightfield::~rcHeightfield() } } -void rcFreeHeightField(rcHeightfield* hf) -{ - rcDelete(hf); -} - rcCompactHeightfield* rcAllocCompactHeightfield() { return rcNew(RC_ALLOC_PERM); } -void rcFreeCompactHeightfield(rcCompactHeightfield* chf) +void rcFreeCompactHeightfield(rcCompactHeightfield* compactHeightfield) { - rcDelete(chf); + rcDelete(compactHeightfield); } rcCompactHeightfield::rcCompactHeightfield() - : width(), - height(), - spanCount(), - walkableHeight(), - walkableClimb(), - borderSize(), - maxDistance(), - maxRegions(), - bmin(), - bmax(), - cs(), - ch(), - cells(), - spans(), - dist(), - areas() +: width() +, height() +, spanCount() +, walkableHeight() +, walkableClimb() +, borderSize() +, maxDistance() +, maxRegions() +, bmin() +, bmax() +, cs() +, ch() +, cells() +, spans() +, dist() +, areas() { } + rcCompactHeightfield::~rcCompactHeightfield() { rcFree(cells); @@ -170,13 +161,18 @@ rcHeightfieldLayerSet* rcAllocHeightfieldLayerSet() { return rcNew(RC_ALLOC_PERM); } -void rcFreeHeightfieldLayerSet(rcHeightfieldLayerSet* lset) + +void rcFreeHeightfieldLayerSet(rcHeightfieldLayerSet* layerSet) { - rcDelete(lset); + rcDelete(layerSet); } rcHeightfieldLayerSet::rcHeightfieldLayerSet() - : layers(), nlayers() {} +: layers() +, nlayers() +{ +} + rcHeightfieldLayerSet::~rcHeightfieldLayerSet() { for (int i = 0; i < nlayers; ++i) @@ -193,22 +189,26 @@ rcContourSet* rcAllocContourSet() { return rcNew(RC_ALLOC_PERM); } -void rcFreeContourSet(rcContourSet* cset) + +void rcFreeContourSet(rcContourSet* contourSet) { - rcDelete(cset); + rcDelete(contourSet); } rcContourSet::rcContourSet() - : conts(), - nconts(), - bmin(), - bmax(), - cs(), - ch(), - width(), - height(), - borderSize(), - maxError() {} +: conts() +, nconts() +, bmin() +, bmax() +, cs() +, ch() +, width() +, height() +, borderSize() +, maxError() +{ +} + rcContourSet::~rcContourSet() { for (int i = 0; i < nconts; ++i) @@ -219,32 +219,34 @@ rcContourSet::~rcContourSet() rcFree(conts); } - rcPolyMesh* rcAllocPolyMesh() { return rcNew(RC_ALLOC_PERM); } -void rcFreePolyMesh(rcPolyMesh* pmesh) + +void rcFreePolyMesh(rcPolyMesh* polyMesh) { - rcDelete(pmesh); + rcDelete(polyMesh); } rcPolyMesh::rcPolyMesh() - : verts(), - polys(), - regs(), - flags(), - areas(), - nverts(), - npolys(), - maxpolys(), - nvp(), - bmin(), - bmax(), - cs(), - ch(), - borderSize(), - maxEdgeError() {} +: verts() +, polys() +, regs() +, flags() +, areas() +, nverts() +, npolys() +, maxpolys() +, nvp() +, bmin() +, bmax() +, cs() +, ch() +, borderSize() +, maxEdgeError() +{ +} rcPolyMesh::~rcPolyMesh() { @@ -257,319 +259,284 @@ rcPolyMesh::~rcPolyMesh() rcPolyMeshDetail* rcAllocPolyMeshDetail() { - rcPolyMeshDetail* dmesh = (rcPolyMeshDetail*)rcAlloc(sizeof(rcPolyMeshDetail), RC_ALLOC_PERM); - memset(dmesh, 0, sizeof(rcPolyMeshDetail)); - return dmesh; + return rcNew(RC_ALLOC_PERM); } -void rcFreePolyMeshDetail(rcPolyMeshDetail* dmesh) +void rcFreePolyMeshDetail(rcPolyMeshDetail* detailMesh) { - if (!dmesh) return; - rcFree(dmesh->meshes); - rcFree(dmesh->verts); - rcFree(dmesh->tris); - rcFree(dmesh); + if (detailMesh == NULL) + { + return; + } + rcFree(detailMesh->meshes); + rcFree(detailMesh->verts); + rcFree(detailMesh->tris); + rcFree(detailMesh); } -void rcCalcBounds(const float* verts, int nv, float* bmin, float* bmax) +rcPolyMeshDetail::rcPolyMeshDetail() +: meshes() +, verts() +, tris() +, nmeshes() +, nverts() +, ntris() +{ +} + +void rcCalcBounds(const float* verts, int numVerts, float* minBounds, float* maxBounds) { // Calculate bounding box. - rcVcopy(bmin, verts); - rcVcopy(bmax, verts); - for (int i = 1; i < nv; ++i) + rcVcopy(minBounds, verts); + rcVcopy(maxBounds, verts); + for (int i = 1; i < numVerts; ++i) { - const float* v = &verts[i*3]; - rcVmin(bmin, v); - rcVmax(bmax, v); + const float* v = &verts[i * 3]; + rcVmin(minBounds, v); + rcVmax(maxBounds, v); } } -void rcCalcGridSize(const float* bmin, const float* bmax, float cs, int* w, int* h) +void rcCalcGridSize(const float* minBounds, const float* maxBounds, const float cellSize, int* sizeX, int* sizeZ) { - *w = (int)((bmax[0] - bmin[0])/cs+0.5f); - *h = (int)((bmax[2] - bmin[2])/cs+0.5f); + *sizeX = (int)((maxBounds[0] - minBounds[0]) / cellSize + 0.5f); + *sizeZ = (int)((maxBounds[2] - minBounds[2]) / cellSize + 0.5f); } -/// @par -/// -/// See the #rcConfig documentation for more information on the configuration parameters. -/// -/// @see rcAllocHeightfield, rcHeightfield -bool rcCreateHeightfield(rcContext* ctx, rcHeightfield& hf, int width, int height, - const float* bmin, const float* bmax, - float cs, float ch) +bool rcCreateHeightfield(rcContext* context, rcHeightfield& heightfield, int sizeX, int sizeZ, + const float* minBounds, const float* maxBounds, + float cellSize, float cellHeight) { - rcIgnoreUnused(ctx); - - hf.width = width; - hf.height = height; - rcVcopy(hf.bmin, bmin); - rcVcopy(hf.bmax, bmax); - hf.cs = cs; - hf.ch = ch; - hf.spans = (rcSpan**)rcAlloc(sizeof(rcSpan*)*hf.width*hf.height, RC_ALLOC_PERM); - if (!hf.spans) + rcIgnoreUnused(context); + + heightfield.width = sizeX; + heightfield.height = sizeZ; + rcVcopy(heightfield.bmin, minBounds); + rcVcopy(heightfield.bmax, maxBounds); + heightfield.cs = cellSize; + heightfield.ch = cellHeight; + heightfield.spans = (rcSpan**)rcAlloc(sizeof(rcSpan*) * heightfield.width * heightfield.height, RC_ALLOC_PERM); + if (!heightfield.spans) + { return false; - memset(hf.spans, 0, sizeof(rcSpan*)*hf.width*hf.height); + } + memset(heightfield.spans, 0, sizeof(rcSpan*) * heightfield.width * heightfield.height); return true; } -static void calcTriNormal(const float* v0, const float* v1, const float* v2, float* norm) +static void calcTriNormal(const float* v0, const float* v1, const float* v2, float* faceNormal) { float e0[3], e1[3]; rcVsub(e0, v1, v0); rcVsub(e1, v2, v0); - rcVcross(norm, e0, e1); - rcVnormalize(norm); + rcVcross(faceNormal, e0, e1); + rcVnormalize(faceNormal); } -/// @par -/// -/// Only sets the area id's for the walkable triangles. Does not alter the -/// area id's for unwalkable triangles. -/// -/// See the #rcConfig documentation for more information on the configuration parameters. -/// -/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles -void rcMarkWalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, - const float* verts, int nv, - const int* tris, int nt, - unsigned char* areas) +void rcMarkWalkableTriangles(rcContext* context, const float walkableSlopeAngle, + const float* verts, const int numVerts, + const int* tris, const int numTris, + unsigned char* triAreaIDs) { - rcIgnoreUnused(ctx); - rcIgnoreUnused(nv); - - const float walkableThr = cosf(walkableSlopeAngle/180.0f*RC_PI); + rcIgnoreUnused(context); + rcIgnoreUnused(numVerts); + + const float walkableThr = cosf(walkableSlopeAngle / 180.0f * RC_PI); float norm[3]; - - for (int i = 0; i < nt; ++i) + + for (int i = 0; i < numTris; ++i) { - const int* tri = &tris[i*3]; - calcTriNormal(&verts[tri[0]*3], &verts[tri[1]*3], &verts[tri[2]*3], norm); + const int* tri = &tris[i * 3]; + calcTriNormal(&verts[tri[0] * 3], &verts[tri[1] * 3], &verts[tri[2] * 3], norm); // Check if the face is walkable. if (norm[1] > walkableThr) - areas[i] = RC_WALKABLE_AREA; - } -} - -/// @par -/// -/// Only sets the area id's for the unwalkable triangles. Does not alter the -/// area id's for walkable triangles. -/// -/// See the #rcConfig documentation for more information on the configuration parameters. -/// -/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles -void rcClearUnwalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, - const float* verts, int /*nv*/, - const int* tris, int nt, - unsigned char* areas) -{ - rcIgnoreUnused(ctx); - - const float walkableThr = cosf(walkableSlopeAngle/180.0f*RC_PI); - - float norm[3]; - - for (int i = 0; i < nt; ++i) - { - const int* tri = &tris[i*3]; - calcTriNormal(&verts[tri[0]*3], &verts[tri[1]*3], &verts[tri[2]*3], norm); - // Check if the face is walkable. - if (norm[1] <= walkableThr) - areas[i] = RC_NULL_AREA; - } -} - -int rcGetHeightFieldSpanCount(rcContext* ctx, rcHeightfield& hf) -{ - rcIgnoreUnused(ctx); - - const int w = hf.width; - const int h = hf.height; - int spanCount = 0; - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) { - for (rcSpan* s = hf.spans[x + y*w]; s; s = s->next) + triAreaIDs[i] = RC_WALKABLE_AREA; + } + } +} + +void rcClearUnwalkableTriangles(rcContext* context, const float walkableSlopeAngle, + const float* verts, int numVerts, + const int* tris, int numTris, + unsigned char* triAreaIDs) +{ + rcIgnoreUnused(context); + rcIgnoreUnused(numVerts); + + // The minimum Y value for a face normal of a triangle with a walkable slope. + const float walkableLimitY = cosf(walkableSlopeAngle / 180.0f * RC_PI); + + float faceNormal[3]; + for (int i = 0; i < numTris; ++i) + { + const int* tri = &tris[i * 3]; + calcTriNormal(&verts[tri[0] * 3], &verts[tri[1] * 3], &verts[tri[2] * 3], faceNormal); + // Check if the face is walkable. + if (faceNormal[1] <= walkableLimitY) + { + triAreaIDs[i] = RC_NULL_AREA; + } + } +} + +int rcGetHeightFieldSpanCount(rcContext* context, const rcHeightfield& heightfield) +{ + rcIgnoreUnused(context); + + const int numCols = heightfield.width * heightfield.height; + int spanCount = 0; + for (int columnIndex = 0; columnIndex < numCols; ++columnIndex) + { + for (rcSpan* span = heightfield.spans[columnIndex]; span != NULL; span = span->next) + { + if (span->area != RC_NULL_AREA) { - if (s->area != RC_NULL_AREA) - spanCount++; + spanCount++; } } } return spanCount; } -/// @par -/// -/// This is just the beginning of the process of fully building a compact heightfield. -/// Various filters may be applied, then the distance field and regions built. -/// E.g: #rcBuildDistanceField and #rcBuildRegions -/// -/// See the #rcConfig documentation for more information on the configuration parameters. -/// -/// @see rcAllocCompactHeightfield, rcHeightfield, rcCompactHeightfield, rcConfig -bool rcBuildCompactHeightfield(rcContext* ctx, const int walkableHeight, const int walkableClimb, - rcHeightfield& hf, rcCompactHeightfield& chf) +bool rcBuildCompactHeightfield(rcContext* context, const int walkableHeight, const int walkableClimb, + const rcHeightfield& heightfield, rcCompactHeightfield& compactHeightfield) { - rcAssert(ctx); - - rcScopedTimer timer(ctx, RC_TIMER_BUILD_COMPACTHEIGHTFIELD); - - const int w = hf.width; - const int h = hf.height; - const int spanCount = rcGetHeightFieldSpanCount(ctx, hf); + rcAssert(context); + + rcScopedTimer timer(context, RC_TIMER_BUILD_COMPACTHEIGHTFIELD); + + const int xSize = heightfield.width; + const int zSize = heightfield.height; + const int spanCount = rcGetHeightFieldSpanCount(context, heightfield); // Fill in header. - chf.width = w; - chf.height = h; - chf.spanCount = spanCount; - chf.walkableHeight = walkableHeight; - chf.walkableClimb = walkableClimb; - chf.maxRegions = 0; - rcVcopy(chf.bmin, hf.bmin); - rcVcopy(chf.bmax, hf.bmax); - chf.bmax[1] += walkableHeight*hf.ch; - chf.cs = hf.cs; - chf.ch = hf.ch; - chf.cells = (rcCompactCell*)rcAlloc(sizeof(rcCompactCell)*w*h, RC_ALLOC_PERM); - if (!chf.cells) + compactHeightfield.width = xSize; + compactHeightfield.height = zSize; + compactHeightfield.spanCount = spanCount; + compactHeightfield.walkableHeight = walkableHeight; + compactHeightfield.walkableClimb = walkableClimb; + compactHeightfield.maxRegions = 0; + rcVcopy(compactHeightfield.bmin, heightfield.bmin); + rcVcopy(compactHeightfield.bmax, heightfield.bmax); + compactHeightfield.bmax[1] += walkableHeight * heightfield.ch; + compactHeightfield.cs = heightfield.cs; + compactHeightfield.ch = heightfield.ch; + compactHeightfield.cells = (rcCompactCell*)rcAlloc(sizeof(rcCompactCell) * xSize * zSize, RC_ALLOC_PERM); + if (!compactHeightfield.cells) { - ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.cells' (%d)", w*h); + context->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.cells' (%d)", xSize * zSize); return false; } - memset(chf.cells, 0, sizeof(rcCompactCell)*w*h); - chf.spans = (rcCompactSpan*)rcAlloc(sizeof(rcCompactSpan)*spanCount, RC_ALLOC_PERM); - if (!chf.spans) + memset(compactHeightfield.cells, 0, sizeof(rcCompactCell) * xSize * zSize); + compactHeightfield.spans = (rcCompactSpan*)rcAlloc(sizeof(rcCompactSpan) * spanCount, RC_ALLOC_PERM); + if (!compactHeightfield.spans) { - ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.spans' (%d)", spanCount); + context->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.spans' (%d)", spanCount); return false; } - memset(chf.spans, 0, sizeof(rcCompactSpan)*spanCount); - chf.areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*spanCount, RC_ALLOC_PERM); - if (!chf.areas) + memset(compactHeightfield.spans, 0, sizeof(rcCompactSpan) * spanCount); + compactHeightfield.areas = (unsigned char*)rcAlloc(sizeof(unsigned char) * spanCount, RC_ALLOC_PERM); + if (!compactHeightfield.areas) { - ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.areas' (%d)", spanCount); + context->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.areas' (%d)", spanCount); return false; } - memset(chf.areas, RC_NULL_AREA, sizeof(unsigned char)*spanCount); - + memset(compactHeightfield.areas, RC_NULL_AREA, sizeof(unsigned char) * spanCount); + const int MAX_HEIGHT = 0xffff; - + // Fill in cells and spans. - int idx = 0; - for (int y = 0; y < h; ++y) + int currentCellIndex = 0; + const int numColumns = xSize * zSize; + for (int columnIndex = 0; columnIndex < numColumns; ++columnIndex) { - for (int x = 0; x < w; ++x) + const rcSpan* span = heightfield.spans[columnIndex]; + + // If there are no spans at this cell, just leave the data to index=0, count=0. + if (span == NULL) { - const rcSpan* s = hf.spans[x + y*w]; - // If there are no spans at this cell, just leave the data to index=0, count=0. - if (!s) continue; - rcCompactCell& c = chf.cells[x+y*w]; - c.index = idx; - c.count = 0; - while (s) + continue; + } + + rcCompactCell& cell = compactHeightfield.cells[columnIndex]; + cell.index = currentCellIndex; + cell.count = 0; + + for (; span != NULL; span = span->next) + { + if (span->area != RC_NULL_AREA) { - if (s->area != RC_NULL_AREA) - { - const int bot = (int)s->smax; - const int top = s->next ? (int)s->next->smin : MAX_HEIGHT; - chf.spans[idx].y = (unsigned short)rcClamp(bot, 0, 0xffff); - chf.spans[idx].h = (unsigned char)rcClamp(top - bot, 0, 0xff); - chf.areas[idx] = s->area; - idx++; - c.count++; - } - s = s->next; + const int bot = (int)span->smax; + const int top = span->next ? (int)span->next->smin : MAX_HEIGHT; + compactHeightfield.spans[currentCellIndex].y = (unsigned short)rcClamp(bot, 0, 0xffff); + compactHeightfield.spans[currentCellIndex].h = (unsigned char)rcClamp(top - bot, 0, 0xff); + compactHeightfield.areas[currentCellIndex] = span->area; + currentCellIndex++; + cell.count++; } } } - + // Find neighbour connections. - const int MAX_LAYERS = RC_NOT_CONNECTED-1; - int tooHighNeighbour = 0; - for (int y = 0; y < h; ++y) + const int MAX_LAYERS = RC_NOT_CONNECTED - 1; + int maxLayerIndex = 0; + const int zStride = xSize; // for readability + for (int z = 0; z < zSize; ++z) { - for (int x = 0; x < w; ++x) + for (int x = 0; x < xSize; ++x) { - const rcCompactCell& c = chf.cells[x+y*w]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + const rcCompactCell& cell = compactHeightfield.cells[x + z * zStride]; + for (int i = (int)cell.index, ni = (int)(cell.index + cell.count); i < ni; ++i) { - rcCompactSpan& s = chf.spans[i]; - + rcCompactSpan& span = compactHeightfield.spans[i]; + for (int dir = 0; dir < 4; ++dir) { - rcSetCon(s, dir, RC_NOT_CONNECTED); - const int nx = x + rcGetDirOffsetX(dir); - const int ny = y + rcGetDirOffsetY(dir); + rcSetCon(span, dir, RC_NOT_CONNECTED); + const int neighborX = x + rcGetDirOffsetX(dir); + const int neighborZ = z + rcGetDirOffsetY(dir); // First check that the neighbour cell is in bounds. - if (nx < 0 || ny < 0 || nx >= w || ny >= h) + if (neighborX < 0 || neighborZ < 0 || neighborX >= xSize || neighborZ >= zSize) + { continue; - + } + // Iterate over all neighbour spans and check if any of the is // accessible from current cell. - const rcCompactCell& nc = chf.cells[nx+ny*w]; - for (int k = (int)nc.index, nk = (int)(nc.index+nc.count); k < nk; ++k) + const rcCompactCell& neighborCell = compactHeightfield.cells[neighborX + neighborZ * zStride]; + for (int k = (int)neighborCell.index, nk = (int)(neighborCell.index + neighborCell.count); k < nk; ++k) { - const rcCompactSpan& ns = chf.spans[k]; - const int bot = rcMax(s.y, ns.y); - const int top = rcMin(s.y+s.h, ns.y+ns.h); + const rcCompactSpan& neighborSpan = compactHeightfield.spans[k]; + const int bot = rcMax(span.y, neighborSpan.y); + const int top = rcMin(span.y + span.h, neighborSpan.y + neighborSpan.h); // Check that the gap between the spans is walkable, // and that the climb height between the gaps is not too high. - if ((top - bot) >= walkableHeight && rcAbs((int)ns.y - (int)s.y) <= walkableClimb) + if ((top - bot) >= walkableHeight && rcAbs((int)neighborSpan.y - (int)span.y) <= walkableClimb) { // Mark direction as walkable. - const int lidx = k - (int)nc.index; - if (lidx < 0 || lidx > MAX_LAYERS) + const int layerIndex = k - (int)neighborCell.index; + if (layerIndex < 0 || layerIndex > MAX_LAYERS) { - tooHighNeighbour = rcMax(tooHighNeighbour, lidx); + maxLayerIndex = rcMax(maxLayerIndex, layerIndex); continue; } - rcSetCon(s, dir, lidx); + rcSetCon(span, dir, layerIndex); break; } } - } } } } - - if (tooHighNeighbour > MAX_LAYERS) + + if (maxLayerIndex > MAX_LAYERS) { - ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Heightfield has too many layers %d (max: %d)", - tooHighNeighbour, MAX_LAYERS); + context->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Heightfield has too many layers %d (max: %d)", + maxLayerIndex, MAX_LAYERS); } - + return true; } - -/* -static int getHeightfieldMemoryUsage(const rcHeightfield& hf) -{ - int size = 0; - size += sizeof(hf); - size += hf.width * hf.height * sizeof(rcSpan*); - - rcSpanPool* pool = hf.pools; - while (pool) - { - size += (sizeof(rcSpanPool) - sizeof(rcSpan)) + sizeof(rcSpan)*RC_SPANS_PER_POOL; - pool = pool->next; - } - return size; -} - -static int getCompactHeightFieldMemoryusage(const rcCompactHeightfield& chf) -{ - int size = 0; - size += sizeof(rcCompactHeightfield); - size += sizeof(rcCompactSpan) * chf.spanCount; - size += sizeof(rcCompactCell) * chf.width * chf.height; - return size; -} -*/ diff --git a/Source/ThirdParty/recastnavigation/Recast.h b/Source/ThirdParty/recastnavigation/Recast.h index 4d557389b..9def8fd2a 100644 --- a/Source/ThirdParty/recastnavigation/Recast.h +++ b/Source/ThirdParty/recastnavigation/Recast.h @@ -22,13 +22,16 @@ /// The value of PI used by Recast. static const float RC_PI = 3.14159265f; +/// Used to ignore unused function parameters and silence any compiler warnings. +template void rcIgnoreUnused(const T&) { } + /// Recast log categories. /// @see rcContext enum rcLogCategory { RC_LOG_PROGRESS = 1, ///< A progress log entry. RC_LOG_WARNING, ///< A warning log entry. - RC_LOG_ERROR, ///< An error log entry. + RC_LOG_ERROR ///< An error log entry. }; /// Recast performance timer categories. @@ -97,12 +100,21 @@ enum rcTimerLabel /// Provides an interface for optional logging and performance tracking of the Recast /// build process. +/// +/// This class does not provide logging or timer functionality on its +/// own. Both must be provided by a concrete implementation +/// by overriding the protected member functions. Also, this class does not +/// provide an interface for extracting log messages. (Only adding them.) +/// So concrete implementations must provide one. +/// +/// If no logging or timers are required, just pass an instance of this +/// class through the Recast build process. +/// /// @ingroup recast class rcContext { public: - - /// Contructor. + /// Constructor. /// @param[in] state TRUE if the logging and performance timers should be enabled. [Default: true] inline rcContext(bool state = true) : m_logEnabled(state), m_timerEnabled(state) {} virtual ~rcContext() {} @@ -115,56 +127,62 @@ public: inline void resetLog() { if (m_logEnabled) doResetLog(); } /// Logs a message. - /// @param[in] category The category of the message. - /// @param[in] format The message. + /// + /// Example: + /// @code + /// // Where ctx is an instance of rcContext and filepath is a char array. + /// ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Could not load '%s'", filepath); + /// @endcode + /// + /// @param[in] category The category of the message. + /// @param[in] format The message. void log(const rcLogCategory category, const char* format, ...); /// Enables or disables the performance timers. /// @param[in] state TRUE if timers should be enabled. inline void enableTimer(bool state) { m_timerEnabled = state; } - /// Clears all peformance timers. (Resets all to unused.) + /// Clears all performance timers. (Resets all to unused.) inline void resetTimers() { if (m_timerEnabled) doResetTimers(); } /// Starts the specified performance timer. - /// @param label The category of the timer. + /// @param label The category of the timer. inline void startTimer(const rcTimerLabel label) { if (m_timerEnabled) doStartTimer(label); } /// Stops the specified performance timer. - /// @param label The category of the timer. + /// @param label The category of the timer. inline void stopTimer(const rcTimerLabel label) { if (m_timerEnabled) doStopTimer(label); } /// Returns the total accumulated time of the specified performance timer. - /// @param label The category of the timer. - /// @return The accumulated time of the timer, or -1 if timers are disabled or the timer has never been started. + /// @param label The category of the timer. + /// @return The accumulated time of the timer, or -1 if timers are disabled or the timer has never been started. inline int getAccumulatedTime(const rcTimerLabel label) const { return m_timerEnabled ? doGetAccumulatedTime(label) : -1; } protected: - /// Clears all log entries. - virtual void doResetLog() {} + virtual void doResetLog(); /// Logs a message. - /// @param[in] category The category of the message. - /// @param[in] msg The formatted message. - /// @param[in] len The length of the formatted message. - virtual void doLog(const rcLogCategory /*category*/, const char* /*msg*/, const int /*len*/) {} + /// @param[in] category The category of the message. + /// @param[in] msg The formatted message. + /// @param[in] len The length of the formatted message. + virtual void doLog(const rcLogCategory category, const char* msg, const int len) { rcIgnoreUnused(category); rcIgnoreUnused(msg); rcIgnoreUnused(len); } /// Clears all timers. (Resets all to unused.) virtual void doResetTimers() {} /// Starts the specified performance timer. - /// @param[in] label The category of timer. - virtual void doStartTimer(const rcTimerLabel /*label*/) {} + /// @param[in] label The category of timer. + virtual void doStartTimer(const rcTimerLabel label) { rcIgnoreUnused(label); } /// Stops the specified performance timer. - /// @param[in] label The category of the timer. - virtual void doStopTimer(const rcTimerLabel /*label*/) {} + /// @param[in] label The category of the timer. + virtual void doStopTimer(const rcTimerLabel label) { rcIgnoreUnused(label); } /// Returns the total accumulated time of the specified performance timer. - /// @param[in] label The category of the timer. - /// @return The accumulated time of the timer, or -1 if timers are disabled or the timer has never been started. - virtual int doGetAccumulatedTime(const rcTimerLabel /*label*/) const { return -1; } + /// @param[in] label The category of the timer. + /// @return The accumulated time of the timer, or -1 if timers are disabled or the timer has never been started. + virtual int doGetAccumulatedTime(const rcTimerLabel label) const { rcIgnoreUnused(label); return -1; } /// True if logging is enabled. bool m_logEnabled; @@ -238,7 +256,7 @@ struct rcConfig /// The maximum allowed length for contour edges along the border of the mesh. [Limit: >=0] [Units: vx] int maxEdgeLen; - /// The maximum distance a simplfied contour's border edges should deviate + /// The maximum distance a simplified contour's border edges should deviate /// the original raw contour. [Limit: >=0] [Units: vx] float maxSimplificationError; @@ -334,6 +352,7 @@ struct rcCompactHeightfield { rcCompactHeightfield(); ~rcCompactHeightfield(); + int width; ///< The width of the heightfield. (Along the x-axis in cell units.) int height; ///< The height of the heightfield. (Along the z-axis in cell units.) int spanCount; ///< The number of spans in the heightfield. @@ -350,6 +369,11 @@ struct rcCompactHeightfield rcCompactSpan* spans; ///< Array of spans. [Size: #spanCount] unsigned short* dist; ///< Array containing border distance data. [Size: #spanCount] unsigned char* areas; ///< Array containing area id data. [Size: #spanCount] + +private: + // Explicitly-disabled copy constructor and copy assignment operator. + rcCompactHeightfield(const rcCompactHeightfield&); + rcCompactHeightfield& operator=(const rcCompactHeightfield&); }; /// Represents a heightfield layer within a layer set. @@ -380,8 +404,14 @@ struct rcHeightfieldLayerSet { rcHeightfieldLayerSet(); ~rcHeightfieldLayerSet(); + rcHeightfieldLayer* layers; ///< The layers in the set. [Size: #nlayers] int nlayers; ///< The number of layers in the set. + +private: + // Explicitly-disabled copy constructor and copy assignment operator. + rcHeightfieldLayerSet(const rcHeightfieldLayerSet&); + rcHeightfieldLayerSet& operator=(const rcHeightfieldLayerSet&); }; /// Represents a simple, non-overlapping contour in field space. @@ -401,6 +431,7 @@ struct rcContourSet { rcContourSet(); ~rcContourSet(); + rcContour* conts; ///< An array of the contours in the set. [Size: #nconts] int nconts; ///< The number of contours in the set. float bmin[3]; ///< The minimum bounds in world space. [(x, y, z)] @@ -411,6 +442,11 @@ struct rcContourSet int height; ///< The height of the set. (Along the z-axis in cell units.) int borderSize; ///< The AABB border size used to generate the source data from which the contours were derived. float maxError; ///< The max edge error that this contour set was simplified with. + +private: + // Explicitly-disabled copy constructor and copy assignment operator. + rcContourSet(const rcContourSet&); + rcContourSet& operator=(const rcContourSet&); }; /// Represents a polygon mesh suitable for use in building a navigation mesh. @@ -419,6 +455,7 @@ struct rcPolyMesh { rcPolyMesh(); ~rcPolyMesh(); + unsigned short* verts; ///< The mesh vertices. [Form: (x, y, z) * #nverts] unsigned short* polys; ///< Polygon and neighbor data. [Length: #maxpolys * 2 * #nvp] unsigned short* regs; ///< The region id assigned to each polygon. [Length: #maxpolys] @@ -434,6 +471,11 @@ struct rcPolyMesh float ch; ///< The height of each cell. (The minimum increment along the y-axis.) int borderSize; ///< The AABB border size used to generate the source data from which the mesh was derived. float maxEdgeError; ///< The max error of the polygon edges in the mesh. + +private: + // Explicitly-disabled copy constructor and copy assignment operator. + rcPolyMesh(const rcPolyMesh&); + rcPolyMesh& operator=(const rcPolyMesh&); }; /// Contains triangle meshes that represent detailed height data associated @@ -441,12 +483,19 @@ struct rcPolyMesh /// @ingroup recast struct rcPolyMeshDetail { + rcPolyMeshDetail(); + unsigned int* meshes; ///< The sub-mesh data. [Size: 4*#nmeshes] float* verts; ///< The mesh vertices. [Size: 3*#nverts] unsigned char* tris; ///< The mesh triangles. [Size: 4*#ntris] int nmeshes; ///< The number of sub-meshes defined by #meshes. int nverts; ///< The number of vertices in #verts. int ntris; ///< The number of triangles in #tris. + +private: + // Explicitly-disabled copy constructor and copy assignment operator. + rcPolyMeshDetail(const rcPolyMeshDetail&); + rcPolyMeshDetail& operator=(const rcPolyMeshDetail&); }; /// @name Allocation Functions @@ -455,82 +504,82 @@ struct rcPolyMeshDetail /// @{ /// Allocates a heightfield object using the Recast allocator. -/// @return A heightfield that is ready for initialization, or null on failure. -/// @ingroup recast -/// @see rcCreateHeightfield, rcFreeHeightField +/// @return A heightfield that is ready for initialization, or null on failure. +/// @ingroup recast +/// @see rcCreateHeightfield, rcFreeHeightField rcHeightfield* rcAllocHeightfield(); /// Frees the specified heightfield object using the Recast allocator. -/// @param[in] hf A heightfield allocated using #rcAllocHeightfield -/// @ingroup recast -/// @see rcAllocHeightfield -void rcFreeHeightField(rcHeightfield* hf); +/// @param[in] heightfield A heightfield allocated using #rcAllocHeightfield +/// @ingroup recast +/// @see rcAllocHeightfield +void rcFreeHeightField(rcHeightfield* heightfield); /// Allocates a compact heightfield object using the Recast allocator. -/// @return A compact heightfield that is ready for initialization, or null on failure. -/// @ingroup recast -/// @see rcBuildCompactHeightfield, rcFreeCompactHeightfield +/// @return A compact heightfield that is ready for initialization, or null on failure. +/// @ingroup recast +/// @see rcBuildCompactHeightfield, rcFreeCompactHeightfield rcCompactHeightfield* rcAllocCompactHeightfield(); /// Frees the specified compact heightfield object using the Recast allocator. -/// @param[in] chf A compact heightfield allocated using #rcAllocCompactHeightfield -/// @ingroup recast -/// @see rcAllocCompactHeightfield -void rcFreeCompactHeightfield(rcCompactHeightfield* chf); +/// @param[in] compactHeightfield A compact heightfield allocated using #rcAllocCompactHeightfield +/// @ingroup recast +/// @see rcAllocCompactHeightfield +void rcFreeCompactHeightfield(rcCompactHeightfield* compactHeightfield); /// Allocates a heightfield layer set using the Recast allocator. -/// @return A heightfield layer set that is ready for initialization, or null on failure. -/// @ingroup recast -/// @see rcBuildHeightfieldLayers, rcFreeHeightfieldLayerSet +/// @return A heightfield layer set that is ready for initialization, or null on failure. +/// @ingroup recast +/// @see rcBuildHeightfieldLayers, rcFreeHeightfieldLayerSet rcHeightfieldLayerSet* rcAllocHeightfieldLayerSet(); /// Frees the specified heightfield layer set using the Recast allocator. -/// @param[in] lset A heightfield layer set allocated using #rcAllocHeightfieldLayerSet -/// @ingroup recast -/// @see rcAllocHeightfieldLayerSet -void rcFreeHeightfieldLayerSet(rcHeightfieldLayerSet* lset); +/// @param[in] layerSet A heightfield layer set allocated using #rcAllocHeightfieldLayerSet +/// @ingroup recast +/// @see rcAllocHeightfieldLayerSet +void rcFreeHeightfieldLayerSet(rcHeightfieldLayerSet* layerSet); /// Allocates a contour set object using the Recast allocator. -/// @return A contour set that is ready for initialization, or null on failure. -/// @ingroup recast -/// @see rcBuildContours, rcFreeContourSet +/// @return A contour set that is ready for initialization, or null on failure. +/// @ingroup recast +/// @see rcBuildContours, rcFreeContourSet rcContourSet* rcAllocContourSet(); /// Frees the specified contour set using the Recast allocator. -/// @param[in] cset A contour set allocated using #rcAllocContourSet -/// @ingroup recast -/// @see rcAllocContourSet -void rcFreeContourSet(rcContourSet* cset); +/// @param[in] contourSet A contour set allocated using #rcAllocContourSet +/// @ingroup recast +/// @see rcAllocContourSet +void rcFreeContourSet(rcContourSet* contourSet); /// Allocates a polygon mesh object using the Recast allocator. -/// @return A polygon mesh that is ready for initialization, or null on failure. -/// @ingroup recast -/// @see rcBuildPolyMesh, rcFreePolyMesh +/// @return A polygon mesh that is ready for initialization, or null on failure. +/// @ingroup recast +/// @see rcBuildPolyMesh, rcFreePolyMesh rcPolyMesh* rcAllocPolyMesh(); /// Frees the specified polygon mesh using the Recast allocator. -/// @param[in] pmesh A polygon mesh allocated using #rcAllocPolyMesh -/// @ingroup recast -/// @see rcAllocPolyMesh -void rcFreePolyMesh(rcPolyMesh* pmesh); +/// @param[in] polyMesh A polygon mesh allocated using #rcAllocPolyMesh +/// @ingroup recast +/// @see rcAllocPolyMesh +void rcFreePolyMesh(rcPolyMesh* polyMesh); /// Allocates a detail mesh object using the Recast allocator. -/// @return A detail mesh that is ready for initialization, or null on failure. -/// @ingroup recast -/// @see rcBuildPolyMeshDetail, rcFreePolyMeshDetail +/// @return A detail mesh that is ready for initialization, or null on failure. +/// @ingroup recast +/// @see rcBuildPolyMeshDetail, rcFreePolyMeshDetail rcPolyMeshDetail* rcAllocPolyMeshDetail(); /// Frees the specified detail mesh using the Recast allocator. -/// @param[in] dmesh A detail mesh allocated using #rcAllocPolyMeshDetail -/// @ingroup recast -/// @see rcAllocPolyMeshDetail -void rcFreePolyMeshDetail(rcPolyMeshDetail* dmesh); +/// @param[in] detailMesh A detail mesh allocated using #rcAllocPolyMeshDetail +/// @ingroup recast +/// @see rcAllocPolyMeshDetail +void rcFreePolyMeshDetail(rcPolyMeshDetail* detailMesh); /// @} -/// Heighfield border flag. +/// Heightfield border flag. /// If a heightfield region ID has this bit set, then the region is a border -/// region and its spans are considered unwalkable. +/// region and its spans are considered un-walkable. /// (Used during the region and contour build process.) /// @see rcCompactSpan::reg static const unsigned short RC_BORDER_REG = 0x8000; @@ -564,7 +613,7 @@ static const int RC_AREA_BORDER = 0x20000; enum rcBuildContoursFlags { RC_CONTOUR_TESS_WALL_EDGES = 0x01, ///< Tessellate solid (impassable) edges during contour simplification. - RC_CONTOUR_TESS_AREA_EDGES = 0x02, ///< Tessellate edges between areas during contour simplification. + RC_CONTOUR_TESS_AREA_EDGES = 0x02 ///< Tessellate edges between areas during contour simplification. }; /// Applied to the region id field of contour vertices in order to extract the region id. @@ -580,7 +629,7 @@ static const unsigned short RC_MESH_NULL_IDX = 0xffff; /// Represents the null area. /// When a data element is given this value it is considered to no longer be -/// assigned to a usable area. (E.g. It is unwalkable.) +/// assigned to a usable area. (E.g. It is un-walkable.) static const unsigned char RC_NULL_AREA = 0; /// The default area id used to indicate a walkable polygon. @@ -595,44 +644,42 @@ static const int RC_NOT_CONNECTED = 0x3f; /// @name General helper functions /// @{ -/// Used to ignore a function parameter. VS complains about unused parameters -/// and this silences the warning. -/// @param [in] _ Unused parameter -template void rcIgnoreUnused(const T&) { } - /// Swaps the values of the two parameters. -/// @param[in,out] a Value A -/// @param[in,out] b Value B +/// @param[in,out] a Value A +/// @param[in,out] b Value B template inline void rcSwap(T& a, T& b) { T t = a; a = b; b = t; } /// Returns the minimum of two values. -/// @param[in] a Value A -/// @param[in] b Value B -/// @return The minimum of the two values. +/// @param[in] a Value A +/// @param[in] b Value B +/// @return The minimum of the two values. template inline T rcMin(T a, T b) { return a < b ? a : b; } /// Returns the maximum of two values. -/// @param[in] a Value A -/// @param[in] b Value B -/// @return The maximum of the two values. +/// @param[in] a Value A +/// @param[in] b Value B +/// @return The maximum of the two values. template inline T rcMax(T a, T b) { return a > b ? a : b; } /// Returns the absolute value. -/// @param[in] a The value. -/// @return The absolute value of the specified value. +/// @param[in] a The value. +/// @return The absolute value of the specified value. template inline T rcAbs(T a) { return a < 0 ? -a : a; } /// Returns the square of the value. -/// @param[in] a The value. -/// @return The square of the value. +/// @param[in] a The value. +/// @return The square of the value. template inline T rcSqr(T a) { return a*a; } /// Clamps the value to the specified range. -/// @param[in] v The value to clamp. -/// @param[in] mn The minimum permitted return value. -/// @param[in] mx The maximum permitted return value. -/// @return The value, clamped to the specified range. -template inline T rcClamp(T v, T mn, T mx) { return v < mn ? mn : (v > mx ? mx : v); } +/// @param[in] value The value to clamp. +/// @param[in] minInclusive The minimum permitted return value. +/// @param[in] maxInclusive The maximum permitted return value. +/// @return The value, clamped to the specified range. +template inline T rcClamp(T value, T minInclusive, T maxInclusive) +{ + return value < minInclusive ? minInclusive: (value > maxInclusive ? maxInclusive : value); +} /// Returns the square root of the value. /// @param[in] x The value. @@ -644,9 +691,9 @@ float rcSqrt(float x); /// @{ /// Derives the cross product of two vectors. (@p v1 x @p v2) -/// @param[out] dest The cross product. [(x, y, z)] -/// @param[in] v1 A Vector [(x, y, z)] -/// @param[in] v2 A vector [(x, y, z)] +/// @param[out] dest The cross product. [(x, y, z)] +/// @param[in] v1 A Vector [(x, y, z)] +/// @param[in] v2 A vector [(x, y, z)] inline void rcVcross(float* dest, const float* v1, const float* v2) { dest[0] = v1[1]*v2[2] - v1[2]*v2[1]; @@ -655,8 +702,8 @@ inline void rcVcross(float* dest, const float* v1, const float* v2) } /// Derives the dot product of two vectors. (@p v1 . @p v2) -/// @param[in] v1 A Vector [(x, y, z)] -/// @param[in] v2 A vector [(x, y, z)] +/// @param[in] v1 A Vector [(x, y, z)] +/// @param[in] v2 A vector [(x, y, z)] /// @return The dot product. inline float rcVdot(const float* v1, const float* v2) { @@ -664,10 +711,10 @@ inline float rcVdot(const float* v1, const float* v2) } /// Performs a scaled vector addition. (@p v1 + (@p v2 * @p s)) -/// @param[out] dest The result vector. [(x, y, z)] -/// @param[in] v1 The base vector. [(x, y, z)] -/// @param[in] v2 The vector to scale and add to @p v1. [(x, y, z)] -/// @param[in] s The amount to scale @p v2 by before adding to @p v1. +/// @param[out] dest The result vector. [(x, y, z)] +/// @param[in] v1 The base vector. [(x, y, z)] +/// @param[in] v2 The vector to scale and add to @p v1. [(x, y, z)] +/// @param[in] s The amount to scale @p v2 by before adding to @p v1. inline void rcVmad(float* dest, const float* v1, const float* v2, const float s) { dest[0] = v1[0]+v2[0]*s; @@ -676,9 +723,9 @@ inline void rcVmad(float* dest, const float* v1, const float* v2, const float s) } /// Performs a vector addition. (@p v1 + @p v2) -/// @param[out] dest The result vector. [(x, y, z)] -/// @param[in] v1 The base vector. [(x, y, z)] -/// @param[in] v2 The vector to add to @p v1. [(x, y, z)] +/// @param[out] dest The result vector. [(x, y, z)] +/// @param[in] v1 The base vector. [(x, y, z)] +/// @param[in] v2 The vector to add to @p v1. [(x, y, z)] inline void rcVadd(float* dest, const float* v1, const float* v2) { dest[0] = v1[0]+v2[0]; @@ -687,9 +734,9 @@ inline void rcVadd(float* dest, const float* v1, const float* v2) } /// Performs a vector subtraction. (@p v1 - @p v2) -/// @param[out] dest The result vector. [(x, y, z)] -/// @param[in] v1 The base vector. [(x, y, z)] -/// @param[in] v2 The vector to subtract from @p v1. [(x, y, z)] +/// @param[out] dest The result vector. [(x, y, z)] +/// @param[in] v1 The base vector. [(x, y, z)] +/// @param[in] v2 The vector to subtract from @p v1. [(x, y, z)] inline void rcVsub(float* dest, const float* v1, const float* v2) { dest[0] = v1[0]-v2[0]; @@ -698,8 +745,8 @@ inline void rcVsub(float* dest, const float* v1, const float* v2) } /// Selects the minimum value of each element from the specified vectors. -/// @param[in,out] mn A vector. (Will be updated with the result.) [(x, y, z)] -/// @param[in] v A vector. [(x, y, z)] +/// @param[in,out] mn A vector. (Will be updated with the result.) [(x, y, z)] +/// @param[in] v A vector. [(x, y, z)] inline void rcVmin(float* mn, const float* v) { mn[0] = rcMin(mn[0], v[0]); @@ -708,8 +755,8 @@ inline void rcVmin(float* mn, const float* v) } /// Selects the maximum value of each element from the specified vectors. -/// @param[in,out] mx A vector. (Will be updated with the result.) [(x, y, z)] -/// @param[in] v A vector. [(x, y, z)] +/// @param[in,out] mx A vector. (Will be updated with the result.) [(x, y, z)] +/// @param[in] v A vector. [(x, y, z)] inline void rcVmax(float* mx, const float* v) { mx[0] = rcMax(mx[0], v[0]); @@ -718,8 +765,8 @@ inline void rcVmax(float* mx, const float* v) } /// Performs a vector copy. -/// @param[out] dest The result. [(x, y, z)] -/// @param[in] v The vector to copy. [(x, y, z)] +/// @param[out] dest The result. [(x, y, z)] +/// @param[in] v The vector to copy. [(x, y, z)] inline void rcVcopy(float* dest, const float* v) { dest[0] = v[0]; @@ -728,8 +775,8 @@ inline void rcVcopy(float* dest, const float* v) } /// Returns the distance between two points. -/// @param[in] v1 A point. [(x, y, z)] -/// @param[in] v2 A point. [(x, y, z)] +/// @param[in] v1 A point. [(x, y, z)] +/// @param[in] v2 A point. [(x, y, z)] /// @return The distance between the two points. inline float rcVdist(const float* v1, const float* v2) { @@ -740,8 +787,8 @@ inline float rcVdist(const float* v1, const float* v2) } /// Returns the square of the distance between two points. -/// @param[in] v1 A point. [(x, y, z)] -/// @param[in] v2 A point. [(x, y, z)] +/// @param[in] v1 A point. [(x, y, z)] +/// @param[in] v2 A point. [(x, y, z)] /// @return The square of the distance between the two points. inline float rcVdistSqr(const float* v1, const float* v2) { @@ -752,7 +799,7 @@ inline float rcVdistSqr(const float* v1, const float* v2) } /// Normalizes the vector. -/// @param[in,out] v The vector to normalize. [(x, y, z)] +/// @param[in,out] v The vector to normalize. [(x, y, z)] inline void rcVnormalize(float* v) { float d = 1.0f / rcSqrt(rcSqr(v[0]) + rcSqr(v[1]) + rcSqr(v[2])); @@ -767,174 +814,249 @@ inline void rcVnormalize(float* v) /// @{ /// Calculates the bounding box of an array of vertices. -/// @ingroup recast -/// @param[in] verts An array of vertices. [(x, y, z) * @p nv] -/// @param[in] nv The number of vertices in the @p verts array. -/// @param[out] bmin The minimum bounds of the AABB. [(x, y, z)] [Units: wu] -/// @param[out] bmax The maximum bounds of the AABB. [(x, y, z)] [Units: wu] -void rcCalcBounds(const float* verts, int nv, float* bmin, float* bmax); +/// @ingroup recast +/// @param[in] verts An array of vertices. [(x, y, z) * @p nv] +/// @param[in] numVerts The number of vertices in the @p verts array. +/// @param[out] minBounds The minimum bounds of the AABB. [(x, y, z)] [Units: wu] +/// @param[out] maxBounds The maximum bounds of the AABB. [(x, y, z)] [Units: wu] +void rcCalcBounds(const float* verts, int numVerts, float* minBounds, float* maxBounds); /// Calculates the grid size based on the bounding box and grid cell size. -/// @ingroup recast -/// @param[in] bmin The minimum bounds of the AABB. [(x, y, z)] [Units: wu] -/// @param[in] bmax The maximum bounds of the AABB. [(x, y, z)] [Units: wu] -/// @param[in] cs The xz-plane cell size. [Limit: > 0] [Units: wu] -/// @param[out] w The width along the x-axis. [Limit: >= 0] [Units: vx] -/// @param[out] h The height along the z-axis. [Limit: >= 0] [Units: vx] -void rcCalcGridSize(const float* bmin, const float* bmax, float cs, int* w, int* h); +/// @ingroup recast +/// @param[in] minBounds The minimum bounds of the AABB. [(x, y, z)] [Units: wu] +/// @param[in] maxBounds The maximum bounds of the AABB. [(x, y, z)] [Units: wu] +/// @param[in] cellSize The xz-plane cell size. [Limit: > 0] [Units: wu] +/// @param[out] sizeX The width along the x-axis. [Limit: >= 0] [Units: vx] +/// @param[out] sizeZ The height along the z-axis. [Limit: >= 0] [Units: vx] +void rcCalcGridSize(const float* minBounds, const float* maxBounds, float cellSize, int* sizeX, int* sizeZ); /// Initializes a new heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] hf The allocated heightfield to initialize. -/// @param[in] width The width of the field along the x-axis. [Limit: >= 0] [Units: vx] -/// @param[in] height The height of the field along the z-axis. [Limit: >= 0] [Units: vx] -/// @param[in] bmin The minimum bounds of the field's AABB. [(x, y, z)] [Units: wu] -/// @param[in] bmax The maximum bounds of the field's AABB. [(x, y, z)] [Units: wu] -/// @param[in] cs The xz-plane cell size to use for the field. [Limit: > 0] [Units: wu] -/// @param[in] ch The y-axis cell size to use for field. [Limit: > 0] [Units: wu] -/// @returns True if the operation completed successfully. -bool rcCreateHeightfield(rcContext* ctx, rcHeightfield& hf, int width, int height, - const float* bmin, const float* bmax, - float cs, float ch); +/// See the #rcConfig documentation for more information on the configuration parameters. +/// +/// @see rcAllocHeightfield, rcHeightfield +/// @ingroup recast +/// +/// @param[in,out] context The build context to use during the operation. +/// @param[in,out] heightfield The allocated heightfield to initialize. +/// @param[in] sizeX The width of the field along the x-axis. [Limit: >= 0] [Units: vx] +/// @param[in] sizeZ The height of the field along the z-axis. [Limit: >= 0] [Units: vx] +/// @param[in] minBounds The minimum bounds of the field's AABB. [(x, y, z)] [Units: wu] +/// @param[in] maxBounds The maximum bounds of the field's AABB. [(x, y, z)] [Units: wu] +/// @param[in] cellSize The xz-plane cell size to use for the field. [Limit: > 0] [Units: wu] +/// @param[in] cellHeight The y-axis cell size to use for field. [Limit: > 0] [Units: wu] +/// @returns True if the operation completed successfully. +bool rcCreateHeightfield(rcContext* context, rcHeightfield& heightfield, int sizeX, int sizeZ, + const float* minBounds, const float* maxBounds, + float cellSize, float cellHeight); /// Sets the area id of all triangles with a slope below the specified value /// to #RC_WALKABLE_AREA. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] walkableSlopeAngle The maximum slope that is considered walkable. -/// [Limits: 0 <= value < 90] [Units: Degrees] -/// @param[in] verts The vertices. [(x, y, z) * @p nv] -/// @param[in] nv The number of vertices. -/// @param[in] tris The triangle vertex indices. [(vertA, vertB, vertC) * @p nt] -/// @param[in] nt The number of triangles. -/// @param[out] areas The triangle area ids. [Length: >= @p nt] -void rcMarkWalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, const float* verts, int nv, - const int* tris, int nt, unsigned char* areas); +/// +/// Only sets the area id's for the walkable triangles. Does not alter the +/// area id's for un-walkable triangles. +/// +/// See the #rcConfig documentation for more information on the configuration parameters. +/// +/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles +/// +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in] walkableSlopeAngle The maximum slope that is considered walkable. +/// [Limits: 0 <= value < 90] [Units: Degrees] +/// @param[in] verts The vertices. [(x, y, z) * @p nv] +/// @param[in] numVerts The number of vertices. +/// @param[in] tris The triangle vertex indices. [(vertA, vertB, vertC) * @p nt] +/// @param[in] numTris The number of triangles. +/// @param[out] triAreaIDs The triangle area ids. [Length: >= @p nt] +void rcMarkWalkableTriangles(rcContext* context, float walkableSlopeAngle, const float* verts, int numVerts, + const int* tris, int numTris, unsigned char* triAreaIDs); /// Sets the area id of all triangles with a slope greater than or equal to the specified value to #RC_NULL_AREA. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] walkableSlopeAngle The maximum slope that is considered walkable. -/// [Limits: 0 <= value < 90] [Units: Degrees] -/// @param[in] verts The vertices. [(x, y, z) * @p nv] -/// @param[in] nv The number of vertices. -/// @param[in] tris The triangle vertex indices. [(vertA, vertB, vertC) * @p nt] -/// @param[in] nt The number of triangles. -/// @param[out] areas The triangle area ids. [Length: >= @p nt] -void rcClearUnwalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, const float* verts, int nv, - const int* tris, int nt, unsigned char* areas); +/// +/// Only sets the area id's for the un-walkable triangles. Does not alter the +/// area id's for walkable triangles. +/// +/// See the #rcConfig documentation for more information on the configuration parameters. +/// +/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles +/// +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in] walkableSlopeAngle The maximum slope that is considered walkable. +/// [Limits: 0 <= value < 90] [Units: Degrees] +/// @param[in] verts The vertices. [(x, y, z) * @p nv] +/// @param[in] numVerts The number of vertices. +/// @param[in] tris The triangle vertex indices. [(vertA, vertB, vertC) * @p nt] +/// @param[in] numTris The number of triangles. +/// @param[out] triAreaIDs The triangle area ids. [Length: >= @p nt] +void rcClearUnwalkableTriangles(rcContext* context, float walkableSlopeAngle, const float* verts, int numVerts, + const int* tris, int numTris, unsigned char* triAreaIDs); /// Adds a span to the specified heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] hf An initialized heightfield. -/// @param[in] x The width index where the span is to be added. -/// [Limits: 0 <= value < rcHeightfield::width] -/// @param[in] y The height index where the span is to be added. -/// [Limits: 0 <= value < rcHeightfield::height] -/// @param[in] smin The minimum height of the span. [Limit: < @p smax] [Units: vx] -/// @param[in] smax The maximum height of the span. [Limit: <= #RC_SPAN_MAX_HEIGHT] [Units: vx] -/// @param[in] area The area id of the span. [Limit: <= #RC_WALKABLE_AREA) -/// @param[in] flagMergeThr The merge theshold. [Limit: >= 0] [Units: vx] -/// @returns True if the operation completed successfully. -bool rcAddSpan(rcContext* ctx, rcHeightfield& hf, const int x, const int y, - const unsigned short smin, const unsigned short smax, - const unsigned char area, const int flagMergeThr); +/// +/// The span addition can be set to favor flags. If the span is merged to +/// another span and the new @p spanMax is within @p flagMergeThreshold units +/// from the existing span, the span flags are merged. +/// +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in,out] heightfield An initialized heightfield. +/// @param[in] x The column x index where the span is to be added. +/// [Limits: 0 <= value < rcHeightfield::width] +/// @param[in] z The column z index where the span is to be added. +/// [Limits: 0 <= value < rcHeightfield::height] +/// @param[in] spanMin The minimum height of the span. [Limit: < @p spanMax] [Units: vx] +/// @param[in] spanMax The maximum height of the span. [Limit: <= #RC_SPAN_MAX_HEIGHT] [Units: vx] +/// @param[in] areaID The area id of the span. [Limit: <= #RC_WALKABLE_AREA) +/// @param[in] flagMergeThreshold The merge threshold. [Limit: >= 0] [Units: vx] +/// @returns True if the operation completed successfully. +bool rcAddSpan(rcContext* context, rcHeightfield& heightfield, + int x, int z, + unsigned short spanMin, unsigned short spanMax, + unsigned char areaID, int flagMergeThreshold); -/// Rasterizes a triangle into the specified heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] v0 Triangle vertex 0 [(x, y, z)] -/// @param[in] v1 Triangle vertex 1 [(x, y, z)] -/// @param[in] v2 Triangle vertex 2 [(x, y, z)] -/// @param[in] area The area id of the triangle. [Limit: <= #RC_WALKABLE_AREA] -/// @param[in,out] solid An initialized heightfield. -/// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. -/// [Limit: >= 0] [Units: vx] -/// @returns True if the operation completed successfully. -bool rcRasterizeTriangle(rcContext* ctx, const float* v0, const float* v1, const float* v2, - const unsigned char area, rcHeightfield& solid, - const int flagMergeThr = 1); +/// Rasterizes a single triangle into the specified heightfield. +/// +/// Calling this for each triangle in a mesh is less efficient than calling rcRasterizeTriangles +/// +/// No spans will be added if the triangle does not overlap the heightfield grid. +/// +/// @see rcHeightfield +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in] v0 Triangle vertex 0 [(x, y, z)] +/// @param[in] v1 Triangle vertex 1 [(x, y, z)] +/// @param[in] v2 Triangle vertex 2 [(x, y, z)] +/// @param[in] areaID The area id of the triangle. [Limit: <= #RC_WALKABLE_AREA] +/// @param[in,out] heightfield An initialized heightfield. +/// @param[in] flagMergeThreshold The distance where the walkable flag is favored over the non-walkable flag. +/// [Limit: >= 0] [Units: vx] +/// @returns True if the operation completed successfully. +bool rcRasterizeTriangle(rcContext* context, + const float* v0, const float* v1, const float* v2, + unsigned char areaID, rcHeightfield& heightfield, int flagMergeThreshold = 1); /// Rasterizes an indexed triangle mesh into the specified heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] verts The vertices. [(x, y, z) * @p nv] -/// @param[in] nv The number of vertices. -/// @param[in] tris The triangle indices. [(vertA, vertB, vertC) * @p nt] -/// @param[in] areas The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] -/// @param[in] nt The number of triangles. -/// @param[in,out] solid An initialized heightfield. -/// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. -/// [Limit: >= 0] [Units: vx] -/// @returns True if the operation completed successfully. -bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const int nv, - const int* tris, const unsigned char* areas, const int nt, - rcHeightfield& solid, const int flagMergeThr = 1); +/// +/// Spans will only be added for triangles that overlap the heightfield grid. +/// +/// @see rcHeightfield +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in] verts The vertices. [(x, y, z) * @p nv] +/// @param[in] numVerts The number of vertices. (unused) TODO (graham): Remove in next major release +/// @param[in] tris The triangle indices. [(vertA, vertB, vertC) * @p nt] +/// @param[in] triAreaIDs The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] +/// @param[in] numTris The number of triangles. +/// @param[in,out] heightfield An initialized heightfield. +/// @param[in] flagMergeThreshold The distance where the walkable flag is favored over the non-walkable flag. +/// [Limit: >= 0] [Units: vx] +/// @returns True if the operation completed successfully. +bool rcRasterizeTriangles(rcContext* context, + const float* verts, int numVerts, + const int* tris, const unsigned char* triAreaIDs, int numTris, + rcHeightfield& heightfield, int flagMergeThreshold = 1); /// Rasterizes an indexed triangle mesh into the specified heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] verts The vertices. [(x, y, z) * @p nv] -/// @param[in] nv The number of vertices. -/// @param[in] tris The triangle indices. [(vertA, vertB, vertC) * @p nt] -/// @param[in] areas The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] -/// @param[in] nt The number of triangles. -/// @param[in,out] solid An initialized heightfield. -/// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. -/// [Limit: >= 0] [Units: vx] -/// @returns True if the operation completed successfully. -bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const int nv, - const unsigned short* tris, const unsigned char* areas, const int nt, - rcHeightfield& solid, const int flagMergeThr = 1); +/// +/// Spans will only be added for triangles that overlap the heightfield grid. +/// +/// @see rcHeightfield +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in] verts The vertices. [(x, y, z) * @p nv] +/// @param[in] numVerts The number of vertices. (unused) TODO (graham): Remove in next major release +/// @param[in] tris The triangle indices. [(vertA, vertB, vertC) * @p nt] +/// @param[in] triAreaIDs The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] +/// @param[in] numTris The number of triangles. +/// @param[in,out] heightfield An initialized heightfield. +/// @param[in] flagMergeThreshold The distance where the walkable flag is favored over the non-walkable flag. +/// [Limit: >= 0] [Units: vx] +/// @returns True if the operation completed successfully. +bool rcRasterizeTriangles(rcContext* context, + const float* verts, int numVerts, + const unsigned short* tris, const unsigned char* triAreaIDs, int numTris, + rcHeightfield& heightfield, int flagMergeThreshold = 1); -/// Rasterizes triangles into the specified heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] verts The triangle vertices. [(ax, ay, az, bx, by, bz, cx, by, cx) * @p nt] -/// @param[in] areas The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] -/// @param[in] nt The number of triangles. -/// @param[in,out] solid An initialized heightfield. -/// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. -/// [Limit: >= 0] [Units: vx] -/// @returns True if the operation completed successfully. -bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const unsigned char* areas, const int nt, - rcHeightfield& solid, const int flagMergeThr = 1); +/// Rasterizes a triangle list into the specified heightfield. +/// +/// Expects each triangle to be specified as three sequential vertices of 3 floats. +/// +/// Spans will only be added for triangles that overlap the heightfield grid. +/// +/// @see rcHeightfield +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in] verts The triangle vertices. [(ax, ay, az, bx, by, bz, cx, by, cx) * @p nt] +/// @param[in] triAreaIDs The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] +/// @param[in] numTris The number of triangles. +/// @param[in,out] heightfield An initialized heightfield. +/// @param[in] flagMergeThreshold The distance where the walkable flag is favored over the non-walkable flag. +/// [Limit: >= 0] [Units: vx] +/// @returns True if the operation completed successfully. +bool rcRasterizeTriangles(rcContext* context, + const float* verts, const unsigned char* triAreaIDs, int numTris, + rcHeightfield& heightfield, int flagMergeThreshold = 1); -/// Marks non-walkable spans as walkable if their maximum is within @p walkableClimp of a walkable neighbor. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. -/// [Limit: >=0] [Units: vx] -/// @param[in,out] solid A fully built heightfield. (All spans have been added.) -void rcFilterLowHangingWalkableObstacles(rcContext* ctx, const int walkableClimb, rcHeightfield& solid); +/// Marks non-walkable spans as walkable if their maximum is within @p walkableClimb of a walkable neighbor. +/// +/// Allows the formation of walkable regions that will flow over low lying +/// objects such as curbs, and up structures such as stairways. +/// +/// Two neighboring spans are walkable if: rcAbs(currentSpan.smax - neighborSpan.smax) < waklableClimb +/// +/// @warning Will override the effect of #rcFilterLedgeSpans. So if both filters are used, call +/// #rcFilterLedgeSpans after calling this filter. +/// +/// @see rcHeightfield, rcConfig +/// +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. +/// [Limit: >=0] [Units: vx] +/// @param[in,out] heightfield A fully built heightfield. (All spans have been added.) +void rcFilterLowHangingWalkableObstacles(rcContext* context, int walkableClimb, rcHeightfield& heightfield); -/// Marks spans that are ledges as not-walkable. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area to -/// be considered walkable. [Limit: >= 3] [Units: vx] -/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. -/// [Limit: >=0] [Units: vx] -/// @param[in,out] solid A fully built heightfield. (All spans have been added.) -void rcFilterLedgeSpans(rcContext* ctx, const int walkableHeight, - const int walkableClimb, rcHeightfield& solid); +/// Marks spans that are ledges as not-walkable. +/// +/// A ledge is a span with one or more neighbors whose maximum is further away than @p walkableClimb +/// from the current span's maximum. +/// This method removes the impact of the overestimation of conservative voxelization +/// so the resulting mesh will not have regions hanging in the air over ledges. +/// +/// A span is a ledge if: rcAbs(currentSpan.smax - neighborSpan.smax) > walkableClimb +/// +/// @see rcHeightfield, rcConfig +/// +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area to +/// be considered walkable. [Limit: >= 3] [Units: vx] +/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. +/// [Limit: >=0] [Units: vx] +/// @param[in,out] heightfield A fully built heightfield. (All spans have been added.) +void rcFilterLedgeSpans(rcContext* context, int walkableHeight, int walkableClimb, rcHeightfield& heightfield); -/// Marks walkable spans as not walkable if the clearence above the span is less than the specified height. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area to -/// be considered walkable. [Limit: >= 3] [Units: vx] -/// @param[in,out] solid A fully built heightfield. (All spans have been added.) -void rcFilterWalkableLowHeightSpans(rcContext* ctx, int walkableHeight, rcHeightfield& solid); +/// Marks walkable spans as not walkable if the clearance above the span is less than the specified height. +/// +/// For this filter, the clearance above the span is the distance from the span's +/// maximum to the next higher span's minimum. (Same grid column.) +/// +/// @see rcHeightfield, rcConfig +/// @ingroup recast +/// +/// @param[in,out] context The build context to use during the operation. +/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area to +/// be considered walkable. [Limit: >= 3] [Units: vx] +/// @param[in,out] heightfield A fully built heightfield. (All spans have been added.) +void rcFilterWalkableLowHeightSpans(rcContext* context, int walkableHeight, rcHeightfield& heightfield); /// Returns the number of spans contained in the specified heightfield. /// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] hf An initialized heightfield. +/// @param[in,out] context The build context to use during the operation. +/// @param[in] heightfield An initialized heightfield. /// @returns The number of spans in the heightfield. -int rcGetHeightFieldSpanCount(rcContext* ctx, rcHeightfield& hf); +int rcGetHeightFieldSpanCount(rcContext* context, const rcHeightfield& heightfield); /// @} /// @name Compact Heightfield Functions @@ -942,175 +1064,181 @@ int rcGetHeightFieldSpanCount(rcContext* ctx, rcHeightfield& hf); /// @{ /// Builds a compact heightfield representing open space, from a heightfield representing solid space. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area -/// to be considered walkable. [Limit: >= 3] [Units: vx] -/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. -/// [Limit: >=0] [Units: vx] -/// @param[in] hf The heightfield to be compacted. -/// @param[out] chf The resulting compact heightfield. (Must be pre-allocated.) -/// @returns True if the operation completed successfully. -bool rcBuildCompactHeightfield(rcContext* ctx, const int walkableHeight, const int walkableClimb, - rcHeightfield& hf, rcCompactHeightfield& chf); +/// +/// This is just the beginning of the process of fully building a compact heightfield. +/// Various filters may be applied, then the distance field and regions built. +/// E.g: #rcBuildDistanceField and #rcBuildRegions +/// +/// See the #rcConfig documentation for more information on the configuration parameters. +/// +/// @see rcAllocCompactHeightfield, rcHeightfield, rcCompactHeightfield, rcConfig +/// @ingroup recast +/// +/// @param[in,out] context The build context to use during the operation. +/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area +/// to be considered walkable. [Limit: >= 3] [Units: vx] +/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. +/// [Limit: >=0] [Units: vx] +/// @param[in] heightfield The heightfield to be compacted. +/// @param[out] compactHeightfield The resulting compact heightfield. (Must be pre-allocated.) +/// @returns True if the operation completed successfully. +bool rcBuildCompactHeightfield(rcContext* context, int walkableHeight, int walkableClimb, + const rcHeightfield& heightfield, rcCompactHeightfield& compactHeightfield); /// Erodes the walkable area within the heightfield by the specified radius. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] radius The radius of erosion. [Limits: 0 < value < 255] [Units: vx] -/// @param[in,out] chf The populated compact heightfield to erode. -/// @returns True if the operation completed successfully. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] radius The radius of erosion. [Limits: 0 < value < 255] [Units: vx] +/// @param[in,out] chf The populated compact heightfield to erode. +/// @returns True if the operation completed successfully. bool rcErodeWalkableArea(rcContext* ctx, int radius, rcCompactHeightfield& chf); /// Applies a median filter to walkable area types (based on area id), removing noise. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] chf A populated compact heightfield. -/// @returns True if the operation completed successfully. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in,out] chf A populated compact heightfield. +/// @returns True if the operation completed successfully. bool rcMedianFilterWalkableArea(rcContext* ctx, rcCompactHeightfield& chf); /// Applies an area id to all spans within the specified bounding box. (AABB) -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] bmin The minimum of the bounding box. [(x, y, z)] -/// @param[in] bmax The maximum of the bounding box. [(x, y, z)] -/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA] -/// @param[in,out] chf A populated compact heightfield. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] bmin The minimum of the bounding box. [(x, y, z)] +/// @param[in] bmax The maximum of the bounding box. [(x, y, z)] +/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA] +/// @param[in,out] chf A populated compact heightfield. void rcMarkBoxArea(rcContext* ctx, const float* bmin, const float* bmax, unsigned char areaId, rcCompactHeightfield& chf); /// Applies the area id to the all spans within the specified convex polygon. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] verts The vertices of the polygon [Fomr: (x, y, z) * @p nverts] -/// @param[in] nverts The number of vertices in the polygon. -/// @param[in] hmin The height of the base of the polygon. -/// @param[in] hmax The height of the top of the polygon. -/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA] -/// @param[in,out] chf A populated compact heightfield. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] verts The vertices of the polygon [Fomr: (x, y, z) * @p nverts] +/// @param[in] nverts The number of vertices in the polygon. +/// @param[in] hmin The height of the base of the polygon. +/// @param[in] hmax The height of the top of the polygon. +/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA] +/// @param[in,out] chf A populated compact heightfield. void rcMarkConvexPolyArea(rcContext* ctx, const float* verts, const int nverts, const float hmin, const float hmax, unsigned char areaId, rcCompactHeightfield& chf); /// Helper function to offset voncex polygons for rcMarkConvexPolyArea. -/// @ingroup recast -/// @param[in] verts The vertices of the polygon [Form: (x, y, z) * @p nverts] -/// @param[in] nverts The number of vertices in the polygon. -/// @param[out] outVerts The offset vertices (should hold up to 2 * @p nverts) [Form: (x, y, z) * return value] -/// @param[in] maxOutVerts The max number of vertices that can be stored to @p outVerts. -/// @returns Number of vertices in the offset polygon or 0 if too few vertices in @p outVerts. +/// @ingroup recast +/// @param[in] verts The vertices of the polygon [Form: (x, y, z) * @p nverts] +/// @param[in] nverts The number of vertices in the polygon. +/// @param[in] offset How much to offset the polygon by. [Units: wu] +/// @param[out] outVerts The offset vertices (should hold up to 2 * @p nverts) [Form: (x, y, z) * return value] +/// @param[in] maxOutVerts The max number of vertices that can be stored to @p outVerts. +/// @returns Number of vertices in the offset polygon or 0 if too few vertices in @p outVerts. int rcOffsetPoly(const float* verts, const int nverts, const float offset, float* outVerts, const int maxOutVerts); /// Applies the area id to all spans within the specified cylinder. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] pos The center of the base of the cylinder. [Form: (x, y, z)] -/// @param[in] r The radius of the cylinder. -/// @param[in] h The height of the cylinder. -/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA] -/// @param[in,out] chf A populated compact heightfield. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] pos The center of the base of the cylinder. [Form: (x, y, z)] +/// @param[in] r The radius of the cylinder. +/// @param[in] h The height of the cylinder. +/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA] +/// @param[in,out] chf A populated compact heightfield. void rcMarkCylinderArea(rcContext* ctx, const float* pos, const float r, const float h, unsigned char areaId, rcCompactHeightfield& chf); /// Builds the distance field for the specified compact heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] chf A populated compact heightfield. -/// @returns True if the operation completed successfully. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in,out] chf A populated compact heightfield. +/// @returns True if the operation completed successfully. bool rcBuildDistanceField(rcContext* ctx, rcCompactHeightfield& chf); /// Builds region data for the heightfield using watershed partitioning. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] chf A populated compact heightfield. -/// @param[in] borderSize The size of the non-navigable border around the heightfield. -/// [Limit: >=0] [Units: vx] -/// @param[in] minRegionArea The minimum number of cells allowed to form isolated island areas. -/// [Limit: >=0] [Units: vx]. -/// @param[in] mergeRegionArea Any regions with a span count smaller than this value will, if possible, -/// be merged with larger regions. [Limit: >=0] [Units: vx] -/// @returns True if the operation completed successfully. -bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf, - const int borderSize, const int minRegionArea, const int mergeRegionArea); +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in,out] chf A populated compact heightfield. +/// @param[in] borderSize The size of the non-navigable border around the heightfield. +/// [Limit: >=0] [Units: vx] +/// @param[in] minRegionArea The minimum number of cells allowed to form isolated island areas. +/// [Limit: >=0] [Units: vx]. +/// @param[in] mergeRegionArea Any regions with a span count smaller than this value will, if possible, +/// be merged with larger regions. [Limit: >=0] [Units: vx] +/// @returns True if the operation completed successfully. +bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf, int borderSize, int minRegionArea, int mergeRegionArea); /// Builds region data for the heightfield by partitioning the heightfield in non-overlapping layers. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] chf A populated compact heightfield. -/// @param[in] borderSize The size of the non-navigable border around the heightfield. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in,out] chf A populated compact heightfield. +/// @param[in] borderSize The size of the non-navigable border around the heightfield. /// [Limit: >=0] [Units: vx] -/// @param[in] minRegionArea The minimum number of cells allowed to form isolated island areas. +/// @param[in] minRegionArea The minimum number of cells allowed to form isolated island areas. /// [Limit: >=0] [Units: vx]. -/// @returns True if the operation completed successfully. -bool rcBuildLayerRegions(rcContext* ctx, rcCompactHeightfield& chf, - const int borderSize, const int minRegionArea); +/// @returns True if the operation completed successfully. +bool rcBuildLayerRegions(rcContext* ctx, rcCompactHeightfield& chf, int borderSize, int minRegionArea); /// Builds region data for the heightfield using simple monotone partitioning. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] chf A populated compact heightfield. -/// @param[in] borderSize The size of the non-navigable border around the heightfield. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in,out] chf A populated compact heightfield. +/// @param[in] borderSize The size of the non-navigable border around the heightfield. /// [Limit: >=0] [Units: vx] -/// @param[in] minRegionArea The minimum number of cells allowed to form isolated island areas. +/// @param[in] minRegionArea The minimum number of cells allowed to form isolated island areas. /// [Limit: >=0] [Units: vx]. -/// @param[in] mergeRegionArea Any regions with a span count smaller than this value will, if possible, +/// @param[in] mergeRegionArea Any regions with a span count smaller than this value will, if possible, /// be merged with larger regions. [Limit: >=0] [Units: vx] -/// @returns True if the operation completed successfully. +/// @returns True if the operation completed successfully. bool rcBuildRegionsMonotone(rcContext* ctx, rcCompactHeightfield& chf, - const int borderSize, const int minRegionArea, const int mergeRegionArea); + int borderSize, int minRegionArea, int mergeRegionArea); /// Sets the neighbor connection data for the specified direction. -/// @param[in] s The span to update. -/// @param[in] dir The direction to set. [Limits: 0 <= value < 4] -/// @param[in] i The index of the neighbor span. -inline void rcSetCon(rcCompactSpan& s, int dir, int i) +/// @param[in] span The span to update. +/// @param[in] direction The direction to set. [Limits: 0 <= value < 4] +/// @param[in] neighborIndex The index of the neighbor span. +inline void rcSetCon(rcCompactSpan& span, int direction, int neighborIndex) { - const unsigned int shift = (unsigned int)dir*6; - unsigned int con = s.con; - s.con = (con & ~(0x3f << shift)) | (((unsigned int)i & 0x3f) << shift); + const unsigned int shift = (unsigned int)direction * 6; + const unsigned int con = span.con; + span.con = (con & ~(0x3f << shift)) | (((unsigned int)neighborIndex & 0x3f) << shift); } /// Gets neighbor connection data for the specified direction. -/// @param[in] s The span to check. -/// @param[in] dir The direction to check. [Limits: 0 <= value < 4] -/// @return The neighbor connection data for the specified direction, -/// or #RC_NOT_CONNECTED if there is no connection. -inline int rcGetCon(const rcCompactSpan& s, int dir) +/// @param[in] span The span to check. +/// @param[in] direction The direction to check. [Limits: 0 <= value < 4] +/// @return The neighbor connection data for the specified direction, or #RC_NOT_CONNECTED if there is no connection. +inline int rcGetCon(const rcCompactSpan& span, int direction) { - const unsigned int shift = (unsigned int)dir*6; - return (s.con >> shift) & 0x3f; + const unsigned int shift = (unsigned int)direction * 6; + return (span.con >> shift) & 0x3f; } /// Gets the standard width (x-axis) offset for the specified direction. -/// @param[in] dir The direction. [Limits: 0 <= value < 4] -/// @return The width offset to apply to the current cell position to move -/// in the direction. -inline int rcGetDirOffsetX(int dir) +/// @param[in] direction The direction. [Limits: 0 <= value < 4] +/// @return The width offset to apply to the current cell position to move in the direction. +inline int rcGetDirOffsetX(int direction) { static const int offset[4] = { -1, 0, 1, 0, }; - return offset[dir&0x03]; + return offset[direction & 0x03]; } +// TODO (graham): Rename this to rcGetDirOffsetZ /// Gets the standard height (z-axis) offset for the specified direction. -/// @param[in] dir The direction. [Limits: 0 <= value < 4] -/// @return The height offset to apply to the current cell position to move -/// in the direction. -inline int rcGetDirOffsetY(int dir) +/// @param[in] direction The direction. [Limits: 0 <= value < 4] +/// @return The height offset to apply to the current cell position to move in the direction. +inline int rcGetDirOffsetY(int direction) { static const int offset[4] = { 0, 1, 0, -1 }; - return offset[dir&0x03]; + return offset[direction & 0x03]; } /// Gets the direction for the specified offset. One of x and y should be 0. -/// @param[in] x The x offset. [Limits: -1 <= value <= 1] -/// @param[in] y The y offset. [Limits: -1 <= value <= 1] -/// @return The direction that represents the offset. -inline int rcGetDirForOffset(int x, int y) +/// @param[in] offsetX The x offset. [Limits: -1 <= value <= 1] +/// @param[in] offsetZ The z offset. [Limits: -1 <= value <= 1] +/// @return The direction that represents the offset. +inline int rcGetDirForOffset(int offsetX, int offsetZ) { static const int dirs[5] = { 3, 0, -1, 2, 1 }; - return dirs[((y+1)<<1)+x]; + return dirs[((offsetZ + 1) << 1) + offsetX]; } /// @} @@ -1119,43 +1247,43 @@ inline int rcGetDirForOffset(int x, int y) /// @{ /// Builds a layer set from the specified compact heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] chf A fully built compact heightfield. -/// @param[in] borderSize The size of the non-navigable border around the heightfield. [Limit: >=0] -/// [Units: vx] -/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area -/// to be considered walkable. [Limit: >= 3] [Units: vx] -/// @param[out] lset The resulting layer set. (Must be pre-allocated.) -/// @returns True if the operation completed successfully. -bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, - const int borderSize, const int walkableHeight, +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] chf A fully built compact heightfield. +/// @param[in] borderSize The size of the non-navigable border around the heightfield. [Limit: >=0] +/// [Units: vx] +/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area +/// to be considered walkable. [Limit: >= 3] [Units: vx] +/// @param[out] lset The resulting layer set. (Must be pre-allocated.) +/// @returns True if the operation completed successfully. +bool rcBuildHeightfieldLayers(rcContext* ctx, const rcCompactHeightfield& chf, + int borderSize, int walkableHeight, rcHeightfieldLayerSet& lset); /// Builds a contour set from the region outlines in the provided compact heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] chf A fully built compact heightfield. -/// @param[in] maxError The maximum distance a simplfied contour's border edges should deviate -/// the original raw contour. [Limit: >=0] [Units: wu] -/// @param[in] maxEdgeLen The maximum allowed length for contour edges along the border of the mesh. -/// [Limit: >=0] [Units: vx] -/// @param[out] cset The resulting contour set. (Must be pre-allocated.) -/// @param[in] buildFlags The build flags. (See: #rcBuildContoursFlags) -/// @returns True if the operation completed successfully. -bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, - const float maxError, const int maxEdgeLen, - rcContourSet& cset, const int buildFlags = RC_CONTOUR_TESS_WALL_EDGES); +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] chf A fully built compact heightfield. +/// @param[in] maxError The maximum distance a simplified contour's border edges should deviate +/// the original raw contour. [Limit: >=0] [Units: wu] +/// @param[in] maxEdgeLen The maximum allowed length for contour edges along the border of the mesh. +/// [Limit: >=0] [Units: vx] +/// @param[out] cset The resulting contour set. (Must be pre-allocated.) +/// @param[in] buildFlags The build flags. (See: #rcBuildContoursFlags) +/// @returns True if the operation completed successfully. +bool rcBuildContours(rcContext* ctx, const rcCompactHeightfield& chf, + float maxError, int maxEdgeLen, + rcContourSet& cset, int buildFlags = RC_CONTOUR_TESS_WALL_EDGES); /// Builds a polygon mesh from the provided contours. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] cset A fully built contour set. -/// @param[in] nvp The maximum number of vertices allowed for polygons generated during the -/// contour to polygon conversion process. [Limit: >= 3] -/// @param[out] mesh The resulting polygon mesh. (Must be re-allocated.) -/// @returns True if the operation completed successfully. -bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, const int nvp, rcPolyMesh& mesh); +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] cset A fully built contour set. +/// @param[in] nvp The maximum number of vertices allowed for polygons generated during the +/// contour to polygon conversion process. [Limit: >= 3] +/// @param[out] mesh The resulting polygon mesh. (Must be re-allocated.) +/// @returns True if the operation completed successfully. +bool rcBuildPolyMesh(rcContext* ctx, const rcContourSet& cset, const int nvp, rcPolyMesh& mesh); /// Merges multiple polygon meshes into a single mesh. /// @ingroup recast @@ -1167,34 +1295,34 @@ bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, const int nvp, rcPolyMe bool rcMergePolyMeshes(rcContext* ctx, rcPolyMesh** meshes, const int nmeshes, rcPolyMesh& mesh); /// Builds a detail mesh from the provided polygon mesh. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] mesh A fully built polygon mesh. -/// @param[in] chf The compact heightfield used to build the polygon mesh. -/// @param[in] sampleDist Sets the distance to use when samping the heightfield. [Limit: >=0] [Units: wu] -/// @param[in] sampleMaxError The maximum distance the detail mesh surface should deviate from -/// heightfield data. [Limit: >=0] [Units: wu] -/// @param[out] dmesh The resulting detail mesh. (Must be pre-allocated.) -/// @returns True if the operation completed successfully. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] mesh A fully built polygon mesh. +/// @param[in] chf The compact heightfield used to build the polygon mesh. +/// @param[in] sampleDist Sets the distance to use when sampling the heightfield. [Limit: >=0] [Units: wu] +/// @param[in] sampleMaxError The maximum distance the detail mesh surface should deviate from +/// heightfield data. [Limit: >=0] [Units: wu] +/// @param[out] dmesh The resulting detail mesh. (Must be pre-allocated.) +/// @returns True if the operation completed successfully. bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompactHeightfield& chf, - const float sampleDist, const float sampleMaxError, + float sampleDist, float sampleMaxError, rcPolyMeshDetail& dmesh); /// Copies the poly mesh data from src to dst. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] src The source mesh to copy from. -/// @param[out] dst The resulting detail mesh. (Must be pre-allocated, must be empty mesh.) -/// @returns True if the operation completed successfully. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] src The source mesh to copy from. +/// @param[out] dst The resulting detail mesh. (Must be pre-allocated, must be empty mesh.) +/// @returns True if the operation completed successfully. bool rcCopyPolyMesh(rcContext* ctx, const rcPolyMesh& src, rcPolyMesh& dst); /// Merges multiple detail meshes into a single detail mesh. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] meshes An array of detail meshes to merge. [Size: @p nmeshes] -/// @param[in] nmeshes The number of detail meshes in the meshes array. -/// @param[out] mesh The resulting detail mesh. (Must be pre-allocated.) -/// @returns True if the operation completed successfully. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] meshes An array of detail meshes to merge. [Size: @p nmeshes] +/// @param[in] nmeshes The number of detail meshes in the meshes array. +/// @param[out] mesh The resulting detail mesh. (Must be pre-allocated.) +/// @returns True if the operation completed successfully. bool rcMergePolyMeshDetails(rcContext* ctx, rcPolyMeshDetail** meshes, const int nmeshes, rcPolyMeshDetail& mesh); /// @} diff --git a/Source/ThirdParty/recastnavigation/RecastAlloc.cpp b/Source/ThirdParty/recastnavigation/RecastAlloc.cpp index bdc366116..e919daf7b 100644 --- a/Source/ThirdParty/recastnavigation/RecastAlloc.cpp +++ b/Source/ThirdParty/recastnavigation/RecastAlloc.cpp @@ -16,12 +16,9 @@ // 3. This notice may not be removed or altered from any source distribution. // -#include -#include #include "RecastAlloc.h" -#include "RecastAssert.h" -static void *rcAllocDefault(size_t size, rcAllocHint) +static void* rcAllocDefault(size_t size, rcAllocHint) { return malloc(size); } @@ -34,27 +31,21 @@ static void rcFreeDefault(void *ptr) static rcAllocFunc* sRecastAllocFunc = rcAllocDefault; static rcFreeFunc* sRecastFreeFunc = rcFreeDefault; -/// @see rcAlloc, rcFree -void rcAllocSetCustom(rcAllocFunc *allocFunc, rcFreeFunc *freeFunc) +void rcAllocSetCustom(rcAllocFunc* allocFunc, rcFreeFunc* freeFunc) { sRecastAllocFunc = allocFunc ? allocFunc : rcAllocDefault; sRecastFreeFunc = freeFunc ? freeFunc : rcFreeDefault; } -/// @see rcAllocSetCustom void* rcAlloc(size_t size, rcAllocHint hint) { return sRecastAllocFunc(size, hint); } -/// @par -/// -/// @warning This function leaves the value of @p ptr unchanged. So it still -/// points to the same (now invalid) location, and not to null. -/// -/// @see rcAllocSetCustom void rcFree(void* ptr) { - if (ptr) + if (ptr != NULL) + { sRecastFreeFunc(ptr); + } } diff --git a/Source/ThirdParty/recastnavigation/RecastAlloc.h b/Source/ThirdParty/recastnavigation/RecastAlloc.h index 071278d65..1741de9f0 100644 --- a/Source/ThirdParty/recastnavigation/RecastAlloc.h +++ b/Source/ThirdParty/recastnavigation/RecastAlloc.h @@ -19,11 +19,11 @@ #ifndef RECASTALLOC_H #define RECASTALLOC_H -#include -#include - #include "RecastAssert.h" +#include +#include + /// Provides hint values to the memory allocator on how long the /// memory is expected to be used. enum rcAllocHint @@ -47,18 +47,27 @@ typedef void (rcFreeFunc)(void* ptr); /// Sets the base custom allocation functions to be used by Recast. /// @param[in] allocFunc The memory allocation function to be used by #rcAlloc /// @param[in] freeFunc The memory de-allocation function to be used by #rcFree +/// +/// @see rcAlloc, rcFree void rcAllocSetCustom(rcAllocFunc *allocFunc, rcFreeFunc *freeFunc); /// Allocates a memory block. -/// @param[in] size The size, in bytes of memory, to allocate. -/// @param[in] hint A hint to the allocator on how long the memory is expected to be in use. -/// @return A pointer to the beginning of the allocated memory block, or null if the allocation failed. -/// @see rcFree +/// +/// @param[in] size The size, in bytes of memory, to allocate. +/// @param[in] hint A hint to the allocator on how long the memory is expected to be in use. +/// @return A pointer to the beginning of the allocated memory block, or null if the allocation failed. +/// +/// @see rcFree, rcAllocSetCustom void* rcAlloc(size_t size, rcAllocHint hint); -/// Deallocates a memory block. -/// @param[in] ptr A pointer to a memory block previously allocated using #rcAlloc. -/// @see rcAlloc +/// Deallocates a memory block. If @p ptr is NULL, this does nothing. +/// +/// @warning This function leaves the value of @p ptr unchanged. So it still +/// points to the same (now invalid) location, and not to null. +/// +/// @param[in] ptr A pointer to a memory block previously allocated using #rcAlloc. +/// +/// @see rcAlloc, rcAllocSetCustom void rcFree(void* ptr); /// An implementation of operator new usable for placement new. The default one is part of STL (which we don't use). @@ -112,7 +121,7 @@ class rcVectorBase { typedef rcSizeType size_type; typedef T value_type; - rcVectorBase() : m_size(0), m_cap(0), m_data(0) {}; + rcVectorBase() : m_size(0), m_cap(0), m_data(0) {} rcVectorBase(const rcVectorBase& other) : m_size(0), m_cap(0), m_data(0) { assign(other.begin(), other.end()); } explicit rcVectorBase(rcSizeType count) : m_size(0), m_cap(0), m_data(0) { resize(count); } rcVectorBase(rcSizeType count, const T& value) : m_size(0), m_cap(0), m_data(0) { resize(count, value); } @@ -142,8 +151,8 @@ class rcVectorBase { const T& front() const { rcAssert(m_size); return m_data[0]; } T& front() { rcAssert(m_size); return m_data[0]; } - const T& back() const { rcAssert(m_size); return m_data[m_size - 1]; }; - T& back() { rcAssert(m_size); return m_data[m_size - 1]; }; + const T& back() const { rcAssert(m_size); return m_data[m_size - 1]; } + T& back() { rcAssert(m_size); return m_data[m_size - 1]; } const T* data() const { return m_data; } T* data() { return m_data; } diff --git a/Source/ThirdParty/recastnavigation/RecastArea.cpp b/Source/ThirdParty/recastnavigation/RecastArea.cpp index 97139cf99..07fb02189 100644 --- a/Source/ThirdParty/recastnavigation/RecastArea.cpp +++ b/Source/ThirdParty/recastnavigation/RecastArea.cpp @@ -17,7 +17,6 @@ // #include -#define _USE_MATH_DEFINES #include #include #include diff --git a/Source/ThirdParty/recastnavigation/RecastAssert.cpp b/Source/ThirdParty/recastnavigation/RecastAssert.cpp index 6297d4202..973b68112 100644 --- a/Source/ThirdParty/recastnavigation/RecastAssert.cpp +++ b/Source/ThirdParty/recastnavigation/RecastAssert.cpp @@ -22,7 +22,7 @@ static rcAssertFailFunc* sRecastAssertFailFunc = 0; -void rcAssertFailSetCustom(rcAssertFailFunc *assertFailFunc) +void rcAssertFailSetCustom(rcAssertFailFunc* assertFailFunc) { sRecastAssertFailFunc = assertFailFunc; } diff --git a/Source/ThirdParty/recastnavigation/RecastAssert.h b/Source/ThirdParty/recastnavigation/RecastAssert.h index e7cc10e49..81705bbe0 100644 --- a/Source/ThirdParty/recastnavigation/RecastAssert.h +++ b/Source/ThirdParty/recastnavigation/RecastAssert.h @@ -19,13 +19,10 @@ #ifndef RECASTASSERT_H #define RECASTASSERT_H -// Note: This header file's only purpose is to include define assert. -// Feel free to change the file and include your own implementation instead. - #ifdef NDEBUG -// From http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/ -# define rcAssert(x) do { (void)sizeof(x); } while((void)(__LINE__==-1),false) +// From https://web.archive.org/web/20210117002833/http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/ +# define rcAssert(x) do { (void)sizeof(x); } while ((void)(__LINE__==-1), false) #else @@ -38,7 +35,7 @@ typedef void (rcAssertFailFunc)(const char* expression, const char* file, int li /// Sets the base custom assertion failure function to be used by Recast. /// @param[in] assertFailFunc The function to be used in case of failure of #dtAssert -void rcAssertFailSetCustom(rcAssertFailFunc *assertFailFunc); +void rcAssertFailSetCustom(rcAssertFailFunc* assertFailFunc); /// Gets the base custom assertion failure function to be used by Recast. rcAssertFailFunc* rcAssertFailGetCustom(); @@ -47,8 +44,8 @@ rcAssertFailFunc* rcAssertFailGetCustom(); # define rcAssert(expression) \ { \ rcAssertFailFunc* failFunc = rcAssertFailGetCustom(); \ - if(failFunc == NULL) { assert(expression); } \ - else if(!(expression)) { (*failFunc)(#expression, __FILE__, __LINE__); } \ + if (failFunc == NULL) { assert(expression); } \ + else if (!(expression)) { (*failFunc)(#expression, __FILE__, __LINE__); } \ } #endif diff --git a/Source/ThirdParty/recastnavigation/RecastContour.cpp b/Source/ThirdParty/recastnavigation/RecastContour.cpp index 1293d4fbd..5508a98a7 100644 --- a/Source/ThirdParty/recastnavigation/RecastContour.cpp +++ b/Source/ThirdParty/recastnavigation/RecastContour.cpp @@ -16,7 +16,6 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include #include @@ -102,7 +101,7 @@ static int getCornerHeight(int x, int y, int i, int dir, } static void walkContour(int x, int y, int i, - rcCompactHeightfield& chf, + const rcCompactHeightfield& chf, unsigned char* flags, rcIntArray& points) { // Choose the first non-connected edge @@ -542,7 +541,7 @@ static bool vequal(const int* a, const int* b) return a[0] == b[0] && a[2] == b[2]; } -static bool intersectSegCountour(const int* d0, const int* d1, int i, int n, const int* verts) +static bool intersectSegContour(const int* d0, const int* d1, int i, int n, const int* verts) { // For each edge (k,k+1) of P for (int k = 0; k < n; k++) @@ -778,9 +777,9 @@ static void mergeRegionHoles(rcContext* ctx, rcContourRegion& region) for (int j = 0; j < ndiags; j++) { const int* pt = &outline->verts[diags[j].vert*4]; - bool intersect = intersectSegCountour(pt, corner, diags[i].vert, outline->nverts, outline->verts); + bool intersect = intersectSegContour(pt, corner, diags[i].vert, outline->nverts, outline->verts); for (int k = i; k < region.nholes && !intersect; k++) - intersect |= intersectSegCountour(pt, corner, -1, region.holes[k].contour->nverts, region.holes[k].contour->verts); + intersect |= intersectSegContour(pt, corner, -1, region.holes[k].contour->nverts, region.holes[k].contour->verts); if (!intersect) { index = diags[j].vert; @@ -821,7 +820,7 @@ static void mergeRegionHoles(rcContext* ctx, rcContourRegion& region) /// See the #rcConfig documentation for more information on the configuration parameters. /// /// @see rcAllocContourSet, rcCompactHeightfield, rcContourSet, rcConfig -bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, +bool rcBuildContours(rcContext* ctx, const rcCompactHeightfield& chf, const float maxError, const int maxEdgeLen, rcContourSet& cset, const int buildFlags) { diff --git a/Source/ThirdParty/recastnavigation/RecastFilter.cpp b/Source/ThirdParty/recastnavigation/RecastFilter.cpp index 9d3e63c48..b5adba43e 100644 --- a/Source/ThirdParty/recastnavigation/RecastFilter.cpp +++ b/Source/ThirdParty/recastnavigation/RecastFilter.cpp @@ -16,186 +16,168 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES -#include -#include #include "Recast.h" #include "RecastAssert.h" -/// @par -/// -/// Allows the formation of walkable regions that will flow over low lying -/// objects such as curbs, and up structures such as stairways. -/// -/// Two neighboring spans are walkable if: rcAbs(currentSpan.smax - neighborSpan.smax) < waklableClimb -/// -/// @warning Will override the effect of #rcFilterLedgeSpans. So if both filters are used, call -/// #rcFilterLedgeSpans after calling this filter. -/// -/// @see rcHeightfield, rcConfig -void rcFilterLowHangingWalkableObstacles(rcContext* ctx, const int walkableClimb, rcHeightfield& solid) -{ - rcAssert(ctx); +#include - rcScopedTimer timer(ctx, RC_TIMER_FILTER_LOW_OBSTACLES); - - const int w = solid.width; - const int h = solid.height; - - for (int y = 0; y < h; ++y) +void rcFilterLowHangingWalkableObstacles(rcContext* context, const int walkableClimb, rcHeightfield& heightfield) +{ + rcAssert(context); + + rcScopedTimer timer(context, RC_TIMER_FILTER_LOW_OBSTACLES); + + const int xSize = heightfield.width; + const int zSize = heightfield.height; + + for (int z = 0; z < zSize; ++z) { - for (int x = 0; x < w; ++x) + for (int x = 0; x < xSize; ++x) { - rcSpan* ps = 0; - bool previousWalkable = false; + rcSpan* previousSpan = NULL; + bool previousWasWalkable = false; unsigned char previousArea = RC_NULL_AREA; - - for (rcSpan* s = solid.spans[x + y*w]; s; ps = s, s = s->next) + + for (rcSpan* span = heightfield.spans[x + z * xSize]; span != NULL; previousSpan = span, span = span->next) { - const bool walkable = s->area != RC_NULL_AREA; + const bool walkable = span->area != RC_NULL_AREA; // If current span is not walkable, but there is walkable // span just below it, mark the span above it walkable too. - if (!walkable && previousWalkable) + if (!walkable && previousWasWalkable) { - if (rcAbs((int)s->smax - (int)ps->smax) <= walkableClimb) - s->area = previousArea; + if (rcAbs((int)span->smax - (int)previousSpan->smax) <= walkableClimb) + { + span->area = previousArea; + } } // Copy walkable flag so that it cannot propagate // past multiple non-walkable objects. - previousWalkable = walkable; - previousArea = s->area; + previousWasWalkable = walkable; + previousArea = span->area; } } } } -/// @par -/// -/// A ledge is a span with one or more neighbors whose maximum is further away than @p walkableClimb -/// from the current span's maximum. -/// This method removes the impact of the overestimation of conservative voxelization -/// so the resulting mesh will not have regions hanging in the air over ledges. -/// -/// A span is a ledge if: rcAbs(currentSpan.smax - neighborSpan.smax) > walkableClimb -/// -/// @see rcHeightfield, rcConfig -void rcFilterLedgeSpans(rcContext* ctx, const int walkableHeight, const int walkableClimb, - rcHeightfield& solid) +void rcFilterLedgeSpans(rcContext* context, const int walkableHeight, const int walkableClimb, + rcHeightfield& heightfield) { - rcAssert(ctx); + rcAssert(context); - rcScopedTimer timer(ctx, RC_TIMER_FILTER_BORDER); + rcScopedTimer timer(context, RC_TIMER_FILTER_BORDER); - const int w = solid.width; - const int h = solid.height; - const int MAX_HEIGHT = 0xffff; + const int xSize = heightfield.width; + const int zSize = heightfield.height; + const int MAX_HEIGHT = 0xffff; // TODO (graham): Move this to a more visible constant and update usages. // Mark border spans. - for (int y = 0; y < h; ++y) + for (int z = 0; z < zSize; ++z) { - for (int x = 0; x < w; ++x) + for (int x = 0; x < xSize; ++x) { - for (rcSpan* s = solid.spans[x + y*w]; s; s = s->next) + for (rcSpan* span = heightfield.spans[x + z * xSize]; span; span = span->next) { // Skip non walkable spans. - if (s->area == RC_NULL_AREA) + if (span->area == RC_NULL_AREA) + { continue; - - const int bot = (int)(s->smax); - const int top = s->next ? (int)(s->next->smin) : MAX_HEIGHT; - + } + + const int bot = (int)(span->smax); + const int top = span->next ? (int)(span->next->smin) : MAX_HEIGHT; + // Find neighbours minimum height. - int minh = MAX_HEIGHT; + int minNeighborHeight = MAX_HEIGHT; // Min and max height of accessible neighbours. - int asmin = s->smax; - int asmax = s->smax; + int accessibleNeighborMinHeight = span->smax; + int accessibleNeighborMaxHeight = span->smax; - for (int dir = 0; dir < 4; ++dir) + for (int direction = 0; direction < 4; ++direction) { - int dx = x + rcGetDirOffsetX(dir); - int dy = y + rcGetDirOffsetY(dir); + int dx = x + rcGetDirOffsetX(direction); + int dy = z + rcGetDirOffsetY(direction); // Skip neighbours which are out of bounds. - if (dx < 0 || dy < 0 || dx >= w || dy >= h) + if (dx < 0 || dy < 0 || dx >= xSize || dy >= zSize) { - minh = rcMin(minh, -walkableClimb - bot); + minNeighborHeight = rcMin(minNeighborHeight, -walkableClimb - bot); continue; } // From minus infinity to the first span. - rcSpan* ns = solid.spans[dx + dy*w]; - int nbot = -walkableClimb; - int ntop = ns ? (int)ns->smin : MAX_HEIGHT; - // Skip neightbour if the gap between the spans is too small. - if (rcMin(top,ntop) - rcMax(bot,nbot) > walkableHeight) - minh = rcMin(minh, nbot - bot); + const rcSpan* neighborSpan = heightfield.spans[dx + dy * xSize]; + int neighborBot = -walkableClimb; + int neighborTop = neighborSpan ? (int)neighborSpan->smin : MAX_HEIGHT; - // Rest of the spans. - for (ns = solid.spans[dx + dy*w]; ns; ns = ns->next) + // Skip neighbour if the gap between the spans is too small. + if (rcMin(top, neighborTop) - rcMax(bot, neighborBot) > walkableHeight) { - nbot = (int)ns->smax; - ntop = ns->next ? (int)ns->next->smin : MAX_HEIGHT; - // Skip neightbour if the gap between the spans is too small. - if (rcMin(top,ntop) - rcMax(bot,nbot) > walkableHeight) - { - minh = rcMin(minh, nbot - bot); + minNeighborHeight = rcMin(minNeighborHeight, neighborBot - bot); + } + + // Rest of the spans. + for (neighborSpan = heightfield.spans[dx + dy * xSize]; neighborSpan; neighborSpan = neighborSpan->next) + { + neighborBot = (int)neighborSpan->smax; + neighborTop = neighborSpan->next ? (int)neighborSpan->next->smin : MAX_HEIGHT; + // Skip neighbour if the gap between the spans is too small. + if (rcMin(top, neighborTop) - rcMax(bot, neighborBot) > walkableHeight) + { + minNeighborHeight = rcMin(minNeighborHeight, neighborBot - bot); + // Find min/max accessible neighbour height. - if (rcAbs(nbot - bot) <= walkableClimb) + if (rcAbs(neighborBot - bot) <= walkableClimb) { - if (nbot < asmin) asmin = nbot; - if (nbot > asmax) asmax = nbot; + if (neighborBot < accessibleNeighborMinHeight) accessibleNeighborMinHeight = neighborBot; + if (neighborBot > accessibleNeighborMaxHeight) accessibleNeighborMaxHeight = neighborBot; } - + } } } - + // The current span is close to a ledge if the drop to any // neighbour span is less than the walkableClimb. - if (minh < -walkableClimb) + if (minNeighborHeight < -walkableClimb) { - s->area = RC_NULL_AREA; + span->area = RC_NULL_AREA; } // If the difference between all neighbours is too large, // we are at steep slope, mark the span as ledge. - else if ((asmax - asmin) > walkableClimb) + else if ((accessibleNeighborMaxHeight - accessibleNeighborMinHeight) > walkableClimb) { - s->area = RC_NULL_AREA; + span->area = RC_NULL_AREA; } } } } } -/// @par -/// -/// For this filter, the clearance above the span is the distance from the span's -/// maximum to the next higher span's minimum. (Same grid column.) -/// -/// @see rcHeightfield, rcConfig -void rcFilterWalkableLowHeightSpans(rcContext* ctx, int walkableHeight, rcHeightfield& solid) +void rcFilterWalkableLowHeightSpans(rcContext* context, const int walkableHeight, rcHeightfield& heightfield) { - rcAssert(ctx); + rcAssert(context); - rcScopedTimer timer(ctx, RC_TIMER_FILTER_WALKABLE); + rcScopedTimer timer(context, RC_TIMER_FILTER_WALKABLE); - const int w = solid.width; - const int h = solid.height; + const int xSize = heightfield.width; + const int zSize = heightfield.height; const int MAX_HEIGHT = 0xffff; // Remove walkable flag from spans which do not have enough // space above them for the agent to stand there. - for (int y = 0; y < h; ++y) + for (int z = 0; z < zSize; ++z) { - for (int x = 0; x < w; ++x) + for (int x = 0; x < xSize; ++x) { - for (rcSpan* s = solid.spans[x + y*w]; s; s = s->next) + for (rcSpan* span = heightfield.spans[x + z*xSize]; span; span = span->next) { - const int bot = (int)(s->smax); - const int top = s->next ? (int)(s->next->smin) : MAX_HEIGHT; - if ((top - bot) <= walkableHeight) - s->area = RC_NULL_AREA; + const int bot = (int)(span->smax); + const int top = span->next ? (int)(span->next->smin) : MAX_HEIGHT; + if ((top - bot) < walkableHeight) + { + span->area = RC_NULL_AREA; + } } } } diff --git a/Source/ThirdParty/recastnavigation/RecastLayers.cpp b/Source/ThirdParty/recastnavigation/RecastLayers.cpp index acc97e44f..ca37ebba7 100644 --- a/Source/ThirdParty/recastnavigation/RecastLayers.cpp +++ b/Source/ThirdParty/recastnavigation/RecastLayers.cpp @@ -17,7 +17,6 @@ // #include -#define _USE_MATH_DEFINES #include #include #include @@ -29,8 +28,21 @@ // Must be 255 or smaller (not 256) because layer IDs are stored as // a byte where 255 is a special value. -static const int RC_MAX_LAYERS = 63; -static const int RC_MAX_NEIS = 16; +#ifndef RC_MAX_LAYERS_DEF +#define RC_MAX_LAYERS_DEF 63 +#endif + +#if RC_MAX_LAYERS_DEF > 255 +#error RC_MAX_LAYERS_DEF must be 255 or smaller +#endif + +#ifndef RC_MAX_NEIS_DEF +#define RC_MAX_NEIS_DEF 16 +#endif + +// Keep type checking. +static const int RC_MAX_LAYERS = RC_MAX_LAYERS_DEF; +static const int RC_MAX_NEIS = RC_MAX_NEIS_DEF; struct rcLayerRegion { @@ -89,7 +101,7 @@ struct rcLayerSweepSpan /// See the #rcConfig documentation for more information on the configuration parameters. /// /// @see rcAllocHeightfieldLayerSet, rcCompactHeightfield, rcHeightfieldLayerSet, rcConfig -bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, +bool rcBuildHeightfieldLayers(rcContext* ctx, const rcCompactHeightfield& chf, const int borderSize, const int walkableHeight, rcHeightfieldLayerSet& lset) { diff --git a/Source/ThirdParty/recastnavigation/RecastMesh.cpp b/Source/ThirdParty/recastnavigation/RecastMesh.cpp index e99eaebb7..c2c0d5174 100644 --- a/Source/ThirdParty/recastnavigation/RecastMesh.cpp +++ b/Source/ThirdParty/recastnavigation/RecastMesh.cpp @@ -16,7 +16,6 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include #include @@ -35,7 +34,7 @@ static bool buildMeshAdjacency(unsigned short* polys, const int npolys, const int nverts, const int vertsPerPoly) { // Based on code by Eric Lengyel from: - // http://www.terathon.com/code/edges.php + // https://web.archive.org/web/20080704083314/http://www.terathon.com/code/edges.php int maxEdgeCount = npolys*vertsPerPoly; unsigned short* firstEdge = (unsigned short*)rcAlloc(sizeof(unsigned short)*(nverts + maxEdgeCount), RC_ALLOC_TEMP); @@ -566,7 +565,6 @@ static bool canRemoveVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned sho const int nvp = mesh.nvp; // Count number of polygons to remove. - int numRemovedVerts = 0; int numTouchedVerts = 0; int numRemainingEdges = 0; for (int i = 0; i < mesh.npolys; ++i) @@ -586,7 +584,6 @@ static bool canRemoveVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned sho } if (numRemoved) { - numRemovedVerts += numRemoved; numRemainingEdges += numVerts-(numRemoved+1); } } @@ -989,7 +986,7 @@ static bool removeVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned short /// limit must be retricted to <= #DT_VERTS_PER_POLYGON. /// /// @see rcAllocPolyMesh, rcContourSet, rcPolyMesh, rcConfig -bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, const int nvp, rcPolyMesh& mesh) +bool rcBuildPolyMesh(rcContext* ctx, const rcContourSet& cset, const int nvp, rcPolyMesh& mesh) { rcAssert(ctx); diff --git a/Source/ThirdParty/recastnavigation/RecastMeshDetail.cpp b/Source/ThirdParty/recastnavigation/RecastMeshDetail.cpp index 1999200c1..40f5b8c60 100644 --- a/Source/ThirdParty/recastnavigation/RecastMeshDetail.cpp +++ b/Source/ThirdParty/recastnavigation/RecastMeshDetail.cpp @@ -17,7 +17,6 @@ // #include -#define _USE_MATH_DEFINES #include #include #include @@ -284,7 +283,7 @@ static unsigned short getHeight(const float fx, const float fy, const float fz, enum EdgeValues { EV_UNDEF = -1, - EV_HULL = -2, + EV_HULL = -2 }; static int findEdge(const int* edges, int nedges, int s, int t) diff --git a/Source/ThirdParty/recastnavigation/RecastRasterization.cpp b/Source/ThirdParty/recastnavigation/RecastRasterization.cpp index a4cef7490..2a4f619fb 100644 --- a/Source/ThirdParty/recastnavigation/RecastRasterization.cpp +++ b/Source/ThirdParty/recastnavigation/RecastRasterization.cpp @@ -16,373 +16,485 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include #include "Recast.h" #include "RecastAlloc.h" #include "RecastAssert.h" -inline bool overlapBounds(const float* amin, const float* amax, const float* bmin, const float* bmax) +/// Check whether two bounding boxes overlap +/// +/// @param[in] aMin Min axis extents of bounding box A +/// @param[in] aMax Max axis extents of bounding box A +/// @param[in] bMin Min axis extents of bounding box B +/// @param[in] bMax Max axis extents of bounding box B +/// @returns true if the two bounding boxes overlap. False otherwise. +static bool overlapBounds(const float* aMin, const float* aMax, const float* bMin, const float* bMax) { - bool overlap = true; - overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap; - overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap; - overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap; - return overlap; + return + aMin[0] <= bMax[0] && aMax[0] >= bMin[0] && + aMin[1] <= bMax[1] && aMax[1] >= bMin[1] && + aMin[2] <= bMax[2] && aMax[2] >= bMin[2]; } -inline bool overlapInterval(unsigned short amin, unsigned short amax, - unsigned short bmin, unsigned short bmax) -{ - if (amax < bmin) return false; - if (amin > bmax) return false; - return true; -} - - +/// Allocates a new span in the heightfield. +/// Use a memory pool and free list to minimize actual allocations. +/// +/// @param[in] hf The heightfield +/// @returns A pointer to the allocated or re-used span memory. static rcSpan* allocSpan(rcHeightfield& hf) { - // If running out of memory, allocate new page and update the freelist. - if (!hf.freelist || !hf.freelist->next) + // If necessary, allocate new page and update the freelist. + if (hf.freelist == NULL || hf.freelist->next == NULL) { // Create new page. // Allocate memory for the new pool. - rcSpanPool* pool = (rcSpanPool*)rcAlloc(sizeof(rcSpanPool), RC_ALLOC_PERM); - if (!pool) return 0; + rcSpanPool* spanPool = (rcSpanPool*)rcAlloc(sizeof(rcSpanPool), RC_ALLOC_PERM); + if (spanPool == NULL) + { + return NULL; + } // Add the pool into the list of pools. - pool->next = hf.pools; - hf.pools = pool; - // Add new items to the free list. - rcSpan* freelist = hf.freelist; - rcSpan* head = &pool->items[0]; - rcSpan* it = &pool->items[RC_SPANS_PER_POOL]; + spanPool->next = hf.pools; + hf.pools = spanPool; + + // Add new spans to the free list. + rcSpan* freeList = hf.freelist; + rcSpan* head = &spanPool->items[0]; + rcSpan* it = &spanPool->items[RC_SPANS_PER_POOL]; do { --it; - it->next = freelist; - freelist = it; + it->next = freeList; + freeList = it; } while (it != head); hf.freelist = it; } - - // Pop item from in front of the free list. - rcSpan* it = hf.freelist; + + // Pop item from the front of the free list. + rcSpan* newSpan = hf.freelist; hf.freelist = hf.freelist->next; - return it; + return newSpan; } -static void freeSpan(rcHeightfield& hf, rcSpan* ptr) +/// Releases the memory used by the span back to the heightfield, so it can be re-used for new spans. +/// @param[in] hf The heightfield. +/// @param[in] span A pointer to the span to free +static void freeSpan(rcHeightfield& hf, rcSpan* span) { - if (!ptr) return; - // Add the node in front of the free list. - ptr->next = hf.freelist; - hf.freelist = ptr; -} - -static bool addSpan(rcHeightfield& hf, const int x, const int y, - const unsigned short smin, const unsigned short smax, - const unsigned char area, const int flagMergeThr) -{ - - int idx = x + y*hf.width; - - rcSpan* s = allocSpan(hf); - if (!s) - return false; - s->smin = smin; - s->smax = smax; - s->area = area; - s->next = 0; - - // Empty cell, add the first span. - if (!hf.spans[idx]) + if (span == NULL) { - hf.spans[idx] = s; - return true; + return; } - rcSpan* prev = 0; - rcSpan* cur = hf.spans[idx]; - - // Insert and merge spans. - while (cur) + // Add the span to the front of the free list. + span->next = hf.freelist; + hf.freelist = span; +} + +/// Adds a span to the heightfield. If the new span overlaps existing spans, +/// it will merge the new span with the existing ones. +/// +/// @param[in] hf Heightfield to add spans to +/// @param[in] x The new span's column cell x index +/// @param[in] z The new span's column cell z index +/// @param[in] min The new span's minimum cell index +/// @param[in] max The new span's maximum cell index +/// @param[in] areaID The new span's area type ID +/// @param[in] flagMergeThreshold How close two spans maximum extents need to be to merge area type IDs +static bool addSpan(rcHeightfield& hf, + const int x, const int z, + const unsigned short min, const unsigned short max, + const unsigned char areaID, const int flagMergeThreshold) +{ + // Create the new span. + rcSpan* newSpan = allocSpan(hf); + if (newSpan == NULL) { - if (cur->smin > s->smax) + return false; + } + newSpan->smin = min; + newSpan->smax = max; + newSpan->area = areaID; + newSpan->next = NULL; + + const int columnIndex = x + z * hf.width; + rcSpan* previousSpan = NULL; + rcSpan* currentSpan = hf.spans[columnIndex]; + + // Insert the new span, possibly merging it with existing spans. + while (currentSpan != NULL) + { + if (currentSpan->smin > newSpan->smax) { - // Current span is further than the new span, break. + // Current span is completely after the new span, break. break; } - else if (cur->smax < s->smin) + + if (currentSpan->smax < newSpan->smin) { - // Current span is before the new span advance. - prev = cur; - cur = cur->next; + // Current span is completely before the new span. Keep going. + previousSpan = currentSpan; + currentSpan = currentSpan->next; } else { - // Merge spans. - if (cur->smin < s->smin) - s->smin = cur->smin; - if (cur->smax > s->smax) - s->smax = cur->smax; + // The new span overlaps with an existing span. Merge them. + if (currentSpan->smin < newSpan->smin) + { + newSpan->smin = currentSpan->smin; + } + if (currentSpan->smax > newSpan->smax) + { + newSpan->smax = currentSpan->smax; + } // Merge flags. - if (rcAbs((int)s->smax - (int)cur->smax) <= flagMergeThr) - s->area = rcMax(s->area, cur->area); + if (rcAbs((int)newSpan->smax - (int)currentSpan->smax) <= flagMergeThreshold) + { + // Higher area ID numbers indicate higher resolution priority. + newSpan->area = rcMax(newSpan->area, currentSpan->area); + } - // Remove current span. - rcSpan* next = cur->next; - freeSpan(hf, cur); - if (prev) - prev->next = next; + // Remove the current span since it's now merged with newSpan. + // Keep going because there might be other overlapping spans that also need to be merged. + rcSpan* next = currentSpan->next; + freeSpan(hf, currentSpan); + if (previousSpan) + { + previousSpan->next = next; + } else - hf.spans[idx] = next; - cur = next; + { + hf.spans[columnIndex] = next; + } + currentSpan = next; } } - // Insert new span. - if (prev) + // Insert new span after prev + if (previousSpan != NULL) { - s->next = prev->next; - prev->next = s; + newSpan->next = previousSpan->next; + previousSpan->next = newSpan; } else { - s->next = hf.spans[idx]; - hf.spans[idx] = s; + // This span should go before the others in the list + newSpan->next = hf.spans[columnIndex]; + hf.spans[columnIndex] = newSpan; } return true; } -/// @par -/// -/// The span addition can be set to favor flags. If the span is merged to -/// another span and the new @p smax is within @p flagMergeThr units -/// from the existing span, the span flags are merged. -/// -/// @see rcHeightfield, rcSpan. -bool rcAddSpan(rcContext* ctx, rcHeightfield& hf, const int x, const int y, - const unsigned short smin, const unsigned short smax, - const unsigned char area, const int flagMergeThr) +bool rcAddSpan(rcContext* context, rcHeightfield& heightfield, + const int x, const int z, + const unsigned short spanMin, const unsigned short spanMax, + const unsigned char areaID, const int flagMergeThreshold) { - rcAssert(ctx); + rcAssert(context); - if (!addSpan(hf, x, y, smin, smax, area, flagMergeThr)) + if (!addSpan(heightfield, x, z, spanMin, spanMax, areaID, flagMergeThreshold)) { - ctx->log(RC_LOG_ERROR, "rcAddSpan: Out of memory."); + context->log(RC_LOG_ERROR, "rcAddSpan: Out of memory."); return false; } return true; } -// divides a convex polygons into two convex polygons on both sides of a line -static void dividePoly(const float* in, int nin, - float* out1, int* nout1, - float* out2, int* nout2, - float x, int axis) +enum rcAxis { - float d[12]; - for (int i = 0; i < nin; ++i) - d[i] = x - in[i*3+axis]; + RC_AXIS_X = 0, + RC_AXIS_Y = 1, + RC_AXIS_Z = 2 +}; - int m = 0, n = 0; - for (int i = 0, j = nin-1; i < nin; j=i, ++i) +/// Divides a convex polygon of max 12 vertices into two convex polygons +/// across a separating axis. +/// +/// @param[in] inVerts The input polygon vertices +/// @param[in] inVertsCount The number of input polygon vertices +/// @param[out] outVerts1 Resulting polygon 1's vertices +/// @param[out] outVerts1Count The number of resulting polygon 1 vertices +/// @param[out] outVerts2 Resulting polygon 2's vertices +/// @param[out] outVerts2Count The number of resulting polygon 2 vertices +/// @param[in] axisOffset THe offset along the specified axis +/// @param[in] axis The separating axis +static void dividePoly(const float* inVerts, int inVertsCount, + float* outVerts1, int* outVerts1Count, + float* outVerts2, int* outVerts2Count, + float axisOffset, rcAxis axis) +{ + rcAssert(inVertsCount <= 12); + + // How far positive or negative away from the separating axis is each vertex. + float inVertAxisDelta[12]; + for (int inVert = 0; inVert < inVertsCount; ++inVert) { - bool ina = d[j] >= 0; - bool inb = d[i] >= 0; - if (ina != inb) + inVertAxisDelta[inVert] = axisOffset - inVerts[inVert * 3 + axis]; + } + + int poly1Vert = 0; + int poly2Vert = 0; + for (int inVertA = 0, inVertB = inVertsCount - 1; inVertA < inVertsCount; inVertB = inVertA, ++inVertA) + { + // If the two vertices are on the same side of the separating axis + bool sameSide = (inVertAxisDelta[inVertA] >= 0) == (inVertAxisDelta[inVertB] >= 0); + + if (!sameSide) { - float s = d[j] / (d[j] - d[i]); - out1[m*3+0] = in[j*3+0] + (in[i*3+0] - in[j*3+0])*s; - out1[m*3+1] = in[j*3+1] + (in[i*3+1] - in[j*3+1])*s; - out1[m*3+2] = in[j*3+2] + (in[i*3+2] - in[j*3+2])*s; - rcVcopy(out2 + n*3, out1 + m*3); - m++; - n++; - // add the i'th point to the right polygon. Do NOT add points that are on the dividing line + float s = inVertAxisDelta[inVertB] / (inVertAxisDelta[inVertB] - inVertAxisDelta[inVertA]); + outVerts1[poly1Vert * 3 + 0] = inVerts[inVertB * 3 + 0] + (inVerts[inVertA * 3 + 0] - inVerts[inVertB * 3 + 0]) * s; + outVerts1[poly1Vert * 3 + 1] = inVerts[inVertB * 3 + 1] + (inVerts[inVertA * 3 + 1] - inVerts[inVertB * 3 + 1]) * s; + outVerts1[poly1Vert * 3 + 2] = inVerts[inVertB * 3 + 2] + (inVerts[inVertA * 3 + 2] - inVerts[inVertB * 3 + 2]) * s; + rcVcopy(&outVerts2[poly2Vert * 3], &outVerts1[poly1Vert * 3]); + poly1Vert++; + poly2Vert++; + + // add the inVertA point to the right polygon. Do NOT add points that are on the dividing line // since these were already added above - if (d[i] > 0) + if (inVertAxisDelta[inVertA] > 0) { - rcVcopy(out1 + m*3, in + i*3); - m++; + rcVcopy(&outVerts1[poly1Vert * 3], &inVerts[inVertA * 3]); + poly1Vert++; } - else if (d[i] < 0) + else if (inVertAxisDelta[inVertA] < 0) { - rcVcopy(out2 + n*3, in + i*3); - n++; + rcVcopy(&outVerts2[poly2Vert * 3], &inVerts[inVertA * 3]); + poly2Vert++; } } - else // same side + else { - // add the i'th point to the right polygon. Addition is done even for points on the dividing line - if (d[i] >= 0) + // add the inVertA point to the right polygon. Addition is done even for points on the dividing line + if (inVertAxisDelta[inVertA] >= 0) { - rcVcopy(out1 + m*3, in + i*3); - m++; - if (d[i] != 0) + rcVcopy(&outVerts1[poly1Vert * 3], &inVerts[inVertA * 3]); + poly1Vert++; + if (inVertAxisDelta[inVertA] != 0) + { continue; + } } - rcVcopy(out2 + n*3, in + i*3); - n++; + rcVcopy(&outVerts2[poly2Vert * 3], &inVerts[inVertA * 3]); + poly2Vert++; } } - *nout1 = m; - *nout2 = n; + *outVerts1Count = poly1Vert; + *outVerts2Count = poly2Vert; } - - +/// Rasterize a single triangle to the heightfield. +/// +/// This code is extremely hot, so much care should be given to maintaining maximum perf here. +/// +/// @param[in] v0 Triangle vertex 0 +/// @param[in] v1 Triangle vertex 1 +/// @param[in] v2 Triangle vertex 2 +/// @param[in] areaID The area ID to assign to the rasterized spans +/// @param[in] hf Heightfield to rasterize into +/// @param[in] hfBBMin The min extents of the heightfield bounding box +/// @param[in] hfBBMax The max extents of the heightfield bounding box +/// @param[in] cellSize The x and z axis size of a voxel in the heightfield +/// @param[in] inverseCellSize 1 / cellSize +/// @param[in] inverseCellHeight 1 / cellHeight +/// @param[in] flagMergeThreshold The threshold in which area flags will be merged +/// @returns true if the operation completes successfully. false if there was an error adding spans to the heightfield. static bool rasterizeTri(const float* v0, const float* v1, const float* v2, - const unsigned char area, rcHeightfield& hf, - const float* bmin, const float* bmax, - const float cs, const float ics, const float ich, - const int flagMergeThr) + const unsigned char areaID, rcHeightfield& hf, + const float* hfBBMin, const float* hfBBMax, + const float cellSize, const float inverseCellSize, const float inverseCellHeight, + const int flagMergeThreshold) { + // Calculate the bounding box of the triangle. + float triBBMin[3]; + rcVcopy(triBBMin, v0); + rcVmin(triBBMin, v1); + rcVmin(triBBMin, v2); + + float triBBMax[3]; + rcVcopy(triBBMax, v0); + rcVmax(triBBMax, v1); + rcVmax(triBBMax, v2); + + // If the triangle does not touch the bounding box of the heightfield, skip the triangle. + if (!overlapBounds(triBBMin, triBBMax, hfBBMin, hfBBMax)) + { + return true; + } + const int w = hf.width; const int h = hf.height; - float tmin[3], tmax[3]; - const float by = bmax[1] - bmin[1]; - - // Calculate the bounding box of the triangle. - rcVcopy(tmin, v0); - rcVcopy(tmax, v0); - rcVmin(tmin, v1); - rcVmin(tmin, v2); - rcVmax(tmax, v1); - rcVmax(tmax, v2); - - // If the triangle does not touch the bbox of the heightfield, skip the triagle. - if (!overlapBounds(bmin, bmax, tmin, tmax)) - return true; - - // Calculate the footprint of the triangle on the grid's y-axis - int y0 = (int)((tmin[2] - bmin[2])*ics); - int y1 = (int)((tmax[2] - bmin[2])*ics); - y0 = rcClamp(y0, 0, h-1); - y1 = rcClamp(y1, 0, h-1); - + const float by = hfBBMax[1] - hfBBMin[1]; + + // Calculate the footprint of the triangle on the grid's z-axis + int z0 = (int)((triBBMin[2] - hfBBMin[2]) * inverseCellSize); + int z1 = (int)((triBBMax[2] - hfBBMin[2]) * inverseCellSize); + + // use -1 rather than 0 to cut the polygon properly at the start of the tile + z0 = rcClamp(z0, -1, h - 1); + z1 = rcClamp(z1, 0, h - 1); + // Clip the triangle into all grid cells it touches. - float buf[7*3*4]; - float *in = buf, *inrow = buf+7*3, *p1 = inrow+7*3, *p2 = p1+7*3; + float buf[7 * 3 * 4]; + float* in = buf; + float* inRow = buf + 7 * 3; + float* p1 = inRow + 7 * 3; + float* p2 = p1 + 7 * 3; rcVcopy(&in[0], v0); - rcVcopy(&in[1*3], v1); - rcVcopy(&in[2*3], v2); - int nvrow, nvIn = 3; - - for (int y = y0; y <= y1; ++y) + rcVcopy(&in[1 * 3], v1); + rcVcopy(&in[2 * 3], v2); + int nvRow; + int nvIn = 3; + + for (int z = z0; z <= z1; ++z) { // Clip polygon to row. Store the remaining polygon as well - const float cz = bmin[2] + y*cs; - dividePoly(in, nvIn, inrow, &nvrow, p1, &nvIn, cz+cs, 2); + const float cellZ = hfBBMin[2] + (float)z * cellSize; + dividePoly(in, nvIn, inRow, &nvRow, p1, &nvIn, cellZ + cellSize, RC_AXIS_Z); rcSwap(in, p1); - if (nvrow < 3) continue; - // find the horizontal bounds in the row - float minX = inrow[0], maxX = inrow[0]; - for (int i=1; i inrow[i*3]) minX = inrow[i*3]; - if (maxX < inrow[i*3]) maxX = inrow[i*3]; + continue; } - int x0 = (int)((minX - bmin[0])*ics); - int x1 = (int)((maxX - bmin[0])*ics); - x0 = rcClamp(x0, 0, w-1); - x1 = rcClamp(x1, 0, w-1); + if (z < 0) + { + continue; + } + + // find X-axis bounds of the row + float minX = inRow[0]; + float maxX = inRow[0]; + for (int vert = 1; vert < nvRow; ++vert) + { + if (minX > inRow[vert * 3]) + { + minX = inRow[vert * 3]; + } + if (maxX < inRow[vert * 3]) + { + maxX = inRow[vert * 3]; + } + } + int x0 = (int)((minX - hfBBMin[0]) * inverseCellSize); + int x1 = (int)((maxX - hfBBMin[0]) * inverseCellSize); + if (x1 < 0 || x0 >= w) + { + continue; + } + x0 = rcClamp(x0, -1, w - 1); + x1 = rcClamp(x1, 0, w - 1); - int nv, nv2 = nvrow; + int nv; + int nv2 = nvRow; for (int x = x0; x <= x1; ++x) { // Clip polygon to column. store the remaining polygon as well - const float cx = bmin[0] + x*cs; - dividePoly(inrow, nv2, p1, &nv, p2, &nv2, cx+cs, 0); - rcSwap(inrow, p2); - if (nv < 3) continue; + const float cx = hfBBMin[0] + (float)x * cellSize; + dividePoly(inRow, nv2, p1, &nv, p2, &nv2, cx + cellSize, RC_AXIS_X); + rcSwap(inRow, p2); + + if (nv < 3) + { + continue; + } + if (x < 0) + { + continue; + } // Calculate min and max of the span. - float smin = p1[1], smax = p1[1]; - for (int i = 1; i < nv; ++i) + float spanMin = p1[1]; + float spanMax = p1[1]; + for (int vert = 1; vert < nv; ++vert) { - smin = rcMin(smin, p1[i*3+1]); - smax = rcMax(smax, p1[i*3+1]); + spanMin = rcMin(spanMin, p1[vert * 3 + 1]); + spanMax = rcMax(spanMax, p1[vert * 3 + 1]); } - smin -= bmin[1]; - smax -= bmin[1]; - // Skip the span if it is outside the heightfield bbox - if (smax < 0.0f) continue; - if (smin > by) continue; - // Clamp the span to the heightfield bbox. - if (smin < 0.0f) smin = 0; - if (smax > by) smax = by; + spanMin -= hfBBMin[1]; + spanMax -= hfBBMin[1]; + // Skip the span if it's completely outside the heightfield bounding box + if (spanMax < 0.0f) + { + continue; + } + if (spanMin > by) + { + continue; + } + + // Clamp the span to the heightfield bounding box. + if (spanMin < 0.0f) + { + spanMin = 0; + } + if (spanMax > by) + { + spanMax = by; + } + // Snap the span to the heightfield height grid. - unsigned short ismin = (unsigned short)rcClamp((int)floorf(smin * ich), 0, RC_SPAN_MAX_HEIGHT); - unsigned short ismax = (unsigned short)rcClamp((int)ceilf(smax * ich), (int)ismin+1, RC_SPAN_MAX_HEIGHT); - - if (!addSpan(hf, x, y, ismin, ismax, area, flagMergeThr)) + unsigned short spanMinCellIndex = (unsigned short)rcClamp((int)floorf(spanMin * inverseCellHeight), 0, RC_SPAN_MAX_HEIGHT); + unsigned short spanMaxCellIndex = (unsigned short)rcClamp((int)ceilf(spanMax * inverseCellHeight), (int)spanMinCellIndex + 1, RC_SPAN_MAX_HEIGHT); + + if (!addSpan(hf, x, z, spanMinCellIndex, spanMaxCellIndex, areaID, flagMergeThreshold)) + { return false; + } } } return true; } -/// @par -/// -/// No spans will be added if the triangle does not overlap the heightfield grid. -/// -/// @see rcHeightfield -bool rcRasterizeTriangle(rcContext* ctx, const float* v0, const float* v1, const float* v2, - const unsigned char area, rcHeightfield& solid, - const int flagMergeThr) +bool rcRasterizeTriangle(rcContext* context, + const float* v0, const float* v1, const float* v2, + const unsigned char areaID, rcHeightfield& heightfield, const int flagMergeThreshold) { - rcAssert(ctx); + rcAssert(context != NULL); - rcScopedTimer timer(ctx, RC_TIMER_RASTERIZE_TRIANGLES); + rcScopedTimer timer(context, RC_TIMER_RASTERIZE_TRIANGLES); - const float ics = 1.0f/solid.cs; - const float ich = 1.0f/solid.ch; - if (!rasterizeTri(v0, v1, v2, area, solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr)) + // Rasterize the single triangle. + const float inverseCellSize = 1.0f / heightfield.cs; + const float inverseCellHeight = 1.0f / heightfield.ch; + if (!rasterizeTri(v0, v1, v2, areaID, heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, inverseCellSize, inverseCellHeight, flagMergeThreshold)) { - ctx->log(RC_LOG_ERROR, "rcRasterizeTriangle: Out of memory."); + context->log(RC_LOG_ERROR, "rcRasterizeTriangle: Out of memory."); return false; } return true; } -/// @par -/// -/// Spans will only be added for triangles that overlap the heightfield grid. -/// -/// @see rcHeightfield -bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/, - const int* tris, const unsigned char* areas, const int nt, - rcHeightfield& solid, const int flagMergeThr) +bool rcRasterizeTriangles(rcContext* context, + const float* verts, const int /*nv*/, + const int* tris, const unsigned char* triAreaIDs, const int numTris, + rcHeightfield& heightfield, const int flagMergeThreshold) { - rcAssert(ctx); + rcAssert(context != NULL); - rcScopedTimer timer(ctx, RC_TIMER_RASTERIZE_TRIANGLES); + rcScopedTimer timer(context, RC_TIMER_RASTERIZE_TRIANGLES); - const float ics = 1.0f/solid.cs; - const float ich = 1.0f/solid.ch; - // Rasterize triangles. - for (int i = 0; i < nt; ++i) + // Rasterize the triangles. + const float inverseCellSize = 1.0f / heightfield.cs; + const float inverseCellHeight = 1.0f / heightfield.ch; + for (int triIndex = 0; triIndex < numTris; ++triIndex) { - const float* v0 = &verts[tris[i*3+0]*3]; - const float* v1 = &verts[tris[i*3+1]*3]; - const float* v2 = &verts[tris[i*3+2]*3]; - // Rasterize. - if (!rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr)) + const float* v0 = &verts[tris[triIndex * 3 + 0] * 3]; + const float* v1 = &verts[tris[triIndex * 3 + 1] * 3]; + const float* v2 = &verts[tris[triIndex * 3 + 2] * 3]; + if (!rasterizeTri(v0, v1, v2, triAreaIDs[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, inverseCellSize, inverseCellHeight, flagMergeThreshold)) { - ctx->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory."); + context->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory."); return false; } } @@ -390,31 +502,26 @@ bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/, return true; } -/// @par -/// -/// Spans will only be added for triangles that overlap the heightfield grid. -/// -/// @see rcHeightfield -bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/, - const unsigned short* tris, const unsigned char* areas, const int nt, - rcHeightfield& solid, const int flagMergeThr) +bool rcRasterizeTriangles(rcContext* context, + const float* verts, const int /*nv*/, + const unsigned short* tris, const unsigned char* triAreaIDs, const int numTris, + rcHeightfield& heightfield, const int flagMergeThreshold) { - rcAssert(ctx); + rcAssert(context != NULL); - rcScopedTimer timer(ctx, RC_TIMER_RASTERIZE_TRIANGLES); - - const float ics = 1.0f/solid.cs; - const float ich = 1.0f/solid.ch; - // Rasterize triangles. - for (int i = 0; i < nt; ++i) + rcScopedTimer timer(context, RC_TIMER_RASTERIZE_TRIANGLES); + + // Rasterize the triangles. + const float inverseCellSize = 1.0f / heightfield.cs; + const float inverseCellHeight = 1.0f / heightfield.ch; + for (int triIndex = 0; triIndex < numTris; ++triIndex) { - const float* v0 = &verts[tris[i*3+0]*3]; - const float* v1 = &verts[tris[i*3+1]*3]; - const float* v2 = &verts[tris[i*3+2]*3]; - // Rasterize. - if (!rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr)) + const float* v0 = &verts[tris[triIndex * 3 + 0] * 3]; + const float* v1 = &verts[tris[triIndex * 3 + 1] * 3]; + const float* v2 = &verts[tris[triIndex * 3 + 2] * 3]; + if (!rasterizeTri(v0, v1, v2, triAreaIDs[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, inverseCellSize, inverseCellHeight, flagMergeThreshold)) { - ctx->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory."); + context->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory."); return false; } } @@ -422,30 +529,25 @@ bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/, return true; } -/// @par -/// -/// Spans will only be added for triangles that overlap the heightfield grid. -/// -/// @see rcHeightfield -bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const unsigned char* areas, const int nt, - rcHeightfield& solid, const int flagMergeThr) +bool rcRasterizeTriangles(rcContext* context, + const float* verts, const unsigned char* triAreaIDs, const int numTris, + rcHeightfield& heightfield, const int flagMergeThreshold) { - rcAssert(ctx); + rcAssert(context != NULL); + + rcScopedTimer timer(context, RC_TIMER_RASTERIZE_TRIANGLES); - rcScopedTimer timer(ctx, RC_TIMER_RASTERIZE_TRIANGLES); - - const float ics = 1.0f/solid.cs; - const float ich = 1.0f/solid.ch; - // Rasterize triangles. - for (int i = 0; i < nt; ++i) + // Rasterize the triangles. + const float inverseCellSize = 1.0f / heightfield.cs; + const float inverseCellHeight = 1.0f / heightfield.ch; + for (int triIndex = 0; triIndex < numTris; ++triIndex) { - const float* v0 = &verts[(i*3+0)*3]; - const float* v1 = &verts[(i*3+1)*3]; - const float* v2 = &verts[(i*3+2)*3]; - // Rasterize. - if (!rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr)) + const float* v0 = &verts[(triIndex * 3 + 0) * 3]; + const float* v1 = &verts[(triIndex * 3 + 1) * 3]; + const float* v2 = &verts[(triIndex * 3 + 2) * 3]; + if (!rasterizeTri(v0, v1, v2, triAreaIDs[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, inverseCellSize, inverseCellHeight, flagMergeThreshold)) { - ctx->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory."); + context->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory."); return false; } } diff --git a/Source/ThirdParty/recastnavigation/RecastRegion.cpp b/Source/ThirdParty/recastnavigation/RecastRegion.cpp index 48318688b..4a7e841a9 100644 --- a/Source/ThirdParty/recastnavigation/RecastRegion.cpp +++ b/Source/ThirdParty/recastnavigation/RecastRegion.cpp @@ -17,7 +17,6 @@ // #include -#define _USE_MATH_DEFINES #include #include #include diff --git a/Source/Tools/Flax.Build.Tests/TestCommandLine.cs b/Source/Tools/Flax.Build.Tests/TestCommandLine.cs index db530b70d..905dc99af 100644 --- a/Source/Tools/Flax.Build.Tests/TestCommandLine.cs +++ b/Source/Tools/Flax.Build.Tests/TestCommandLine.cs @@ -12,6 +12,7 @@ namespace Flax.Build.Tests [Test, Sequential] public void TestParseOptionOnly([Values( "-something", + "something", " \t \t-\t \tsomething\t ", "-something=")] string commandLine) @@ -25,6 +26,11 @@ namespace Flax.Build.Tests [Test, Sequential] public void TestParseOneValue([Values( "-something=value", + "something=value", + "-something=\"value\"", + "-something=\\\"value\\\"", + "\"-something=\"value\"\"", + "\"-something=\\\"value\\\"\"", " \t \t-\t \tsomething\t =value ", "-something=value ")] string commandLine) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index b825f5ac1..40e5204d5 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -299,6 +299,8 @@ namespace Flax.Build.Bindings // Object reference property if (typeInfo.IsObjectRef) return GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller); + if (typeInfo.Type == "SoftTypeReference" || typeInfo.Type == "SoftObjectReference") + return typeInfo.Type; // Array or Span or DataContainer #if USE_NETCORE @@ -464,7 +466,7 @@ namespace Flax.Build.Bindings return "FlaxEngine.Object.GetUnmanagedPtr({0})"; case "Function": // delegate - return "Marshal.GetFunctionPointerForDelegate({0})"; + return "NativeInterop.GetFunctionPointerForDelegate({0})"; default: var apiType = FindApiTypeInfo(buildData, typeInfo, caller); if (apiType != null) @@ -953,7 +955,7 @@ namespace Flax.Build.Bindings CppParamsWrappersCache[i] = result; } - string eventSignature; + string eventType; if (useCustomDelegateSignature) { contents.Append(indent).Append($"/// The delegate for event {eventInfo.Name}.").AppendLine(); @@ -968,24 +970,25 @@ namespace Flax.Build.Bindings contents.Append(CppParamsWrappersCache[i]).Append(" arg").Append(i); } contents.Append(");").AppendLine().AppendLine(); - eventSignature = "event " + eventInfo.Name + "Delegate"; + eventType = eventInfo.Name + "Delegate"; } else { - eventSignature = "event Action"; + eventType = "Action"; if (paramsCount != 0) { - eventSignature += '<'; + eventType += '<'; for (var i = 0; i < paramsCount; i++) { if (i != 0) - eventSignature += ", "; + eventType += ", "; CppParamsWrappersCache[i] = GenerateCSharpNativeToManaged(buildData, eventInfo.Type.GenericArgs[i], classInfo); - eventSignature += CppParamsWrappersCache[i]; + eventType += CppParamsWrappersCache[i]; } - eventSignature += '>'; + eventType += '>'; } } + string eventSignature = "event " + eventType; GenerateCSharpComment(contents, indent, eventInfo.Comment, true); GenerateCSharpAttributes(buildData, contents, indent, classInfo, eventInfo, useUnmanaged); @@ -998,11 +1001,7 @@ namespace Flax.Build.Bindings indent += " "; var eventInstance = eventInfo.IsStatic ? string.Empty : "__unmanagedPtr, "; contents.Append(indent).Append($"add {{ Internal_{eventInfo.Name} += value; if (Internal_{eventInfo.Name}_Count++ == 0) Internal_{eventInfo.Name}_Bind({eventInstance}true); }}").AppendLine(); - contents.Append(indent).Append("remove { ").AppendLine(); - contents.Append("#if FLAX_EDITOR || BUILD_DEBUG").AppendLine(); - contents.Append(indent).Append($"if (Internal_{eventInfo.Name} != null) {{ bool invalid = true; foreach (Delegate e in Internal_{eventInfo.Name}.GetInvocationList()) {{ if (e == (Delegate)value) {{ invalid = false; break; }} }} if (invalid) throw new Exception(\"Cannot unregister from event if not registered before.\"); }}").AppendLine(); - contents.Append("#endif").AppendLine(); - contents.Append(indent).Append($"Internal_{eventInfo.Name} -= value; if (--Internal_{eventInfo.Name}_Count == 0) Internal_{eventInfo.Name}_Bind({eventInstance}false); }}").AppendLine(); + contents.Append(indent).Append($"remove {{ var __delegate = ({eventType})Delegate.Remove(Internal_{eventInfo.Name}, value); if (__delegate != Internal_{eventInfo.Name}) {{ Internal_{eventInfo.Name} = __delegate; if (--Internal_{eventInfo.Name}_Count == 0) Internal_{eventInfo.Name}_Bind({eventInstance}false); }} }}").AppendLine(); indent = indent.Substring(0, indent.Length - 4); contents.Append(indent).Append('}').AppendLine(); @@ -1019,6 +1018,7 @@ namespace Flax.Build.Bindings contents.Append("static "); contents.Append($"{eventSignature} Internal_{eventInfo.Name};"); contents.AppendLine(); + contents.Append("#pragma warning restore 67").AppendLine(); contents.AppendLine(); contents.Append(indent).Append("internal "); @@ -1340,6 +1340,7 @@ namespace Flax.Build.Bindings public static class NativeToManaged { public static {{classInfo.Name}} ConvertToManaged(IntPtr unmanaged) => Unsafe.As<{{classInfo.Name}}>(ManagedHandleMarshaller.NativeToManaged.ConvertToManaged(unmanaged)); + public static IntPtr ConvertToUnmanaged({{classInfo.Name}} managed) => ManagedHandleMarshaller.ManagedToNative.ConvertToUnmanaged(managed); public static void Free(IntPtr unmanaged) => ManagedHandleMarshaller.NativeToManaged.Free(unmanaged); } #if FLAX_EDITOR @@ -1347,6 +1348,7 @@ namespace Flax.Build.Bindings #endif public static class ManagedToNative { + public static {{classInfo.Name}} ConvertToManaged(IntPtr unmanaged) => Unsafe.As<{{classInfo.Name}}>(ManagedHandleMarshaller.NativeToManaged.ConvertToManaged(unmanaged)); public static IntPtr ConvertToUnmanaged({{classInfo.Name}} managed) => ManagedHandleMarshaller.ManagedToNative.ConvertToUnmanaged(managed); public static void Free(IntPtr unmanaged) => ManagedHandleMarshaller.ManagedToNative.Free(unmanaged); } @@ -1510,8 +1512,8 @@ namespace Flax.Build.Bindings type = "IntPtr"; else if (type == "bool") type = "byte"; - else if (type == "object") - type = "NativeVariant"; + else if (fieldInfo.Type.Type == "Variant") + type = "IntPtr"; else if (internalType) { internalTypeMarshaller = type + "Marshaller"; @@ -1528,9 +1530,6 @@ namespace Flax.Build.Bindings if (fieldInfo.NoArray && fieldInfo.Type.IsArray) continue; - if (type == "NativeVariant") - continue; // TODO: FIXME - if (useSeparator) { toManagedContent.Append(", "); @@ -1632,6 +1631,12 @@ namespace Flax.Build.Bindings toManagedContent.Append($"managed.{fieldInfo.Name} != 0"); toNativeContent.Append($"managed.{fieldInfo.Name} ? (byte)1 : (byte)0"); } + else if (fieldInfo.Type.Type == "Variant") + { + // Variant passed as boxed object handle + toManagedContent.Append($"ManagedHandleMarshaller.NativeToManaged.ConvertToManaged(managed.{fieldInfo.Name})"); + toNativeContent.Append($"ManagedHandleMarshaller.NativeToManaged.ConvertToUnmanaged(managed.{fieldInfo.Name})"); + } else if (internalType) { toManagedContent.Append($"{internalTypeMarshaller}.ToManaged(managed.{fieldInfo.Name})"); @@ -1668,6 +1673,7 @@ namespace Flax.Build.Bindings contents.Append(indent).AppendLine("[HideInEditor]"); contents.Append(indent).AppendLine("public static class NativeToManaged").Append(indent).AppendLine("{"); contents.Append(indent2).AppendLine($"public static {structureInfo.Name} ConvertToManaged({structureInfo.Name}Internal unmanaged) => {marshallerName}.ToManaged(unmanaged);"); + contents.Append(indent2).AppendLine($"public static {structureInfo.Name}Internal ConvertToUnmanaged({structureInfo.Name} managed) => {marshallerName}.ToNative(managed);"); contents.Append(indent2).AppendLine($"public static void Free({structureInfo.Name}Internal unmanaged)"); contents.Append(indent2).AppendLine("{").Append(indent3).AppendLine(freeContents2.Replace("\n", "\n" + indent3).ToString().TrimEnd()).Append(indent2).AppendLine("}"); contents.Append(indent).AppendLine("}"); @@ -1676,6 +1682,7 @@ namespace Flax.Build.Bindings if (buildData.Target != null && buildData.Target.IsEditor) contents.Append(indent).AppendLine("[HideInEditor]"); contents.Append(indent).AppendLine($"public static class ManagedToNative").Append(indent).AppendLine("{"); + contents.Append(indent2).AppendLine($"public static {structureInfo.Name} ConvertToManaged({structureInfo.Name}Internal unmanaged) => {marshallerName}.ToManaged(unmanaged);"); contents.Append(indent2).AppendLine($"public static {structureInfo.Name}Internal ConvertToUnmanaged({structureInfo.Name} managed) => {marshallerName}.ToNative(managed);"); contents.Append(indent2).AppendLine($"public static void Free({structureInfo.Name}Internal unmanaged) => {marshallerName}.Free(unmanaged);"); contents.Append(indent).AppendLine("}"); @@ -1763,6 +1770,10 @@ namespace Flax.Build.Bindings // char's are not blittable, store as short instead contents.Append($"fixed short {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}]; // {managedType}*").AppendLine(); } + else if (managedType == "byte") + { + contents.Append($"fixed byte {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}]; // {managedType}*").AppendLine(); + } else #endif { diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index da94a0056..4e5d5c158 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -1452,7 +1452,8 @@ namespace Flax.Build.Bindings var useThunk = buildData.Platform.HasDynamicCodeExecutionSupport && Configuration.AOTMode == DotNetAOTModes.None; if (useThunk) { - contents.AppendLine($" PROFILE_CPU_NAMED(\"{classInfo.FullNameManaged}::{functionInfo.Name}\");"); + //contents.AppendLine($" PROFILE_CPU_NAMED(\"{classInfo.FullNameManaged}::{functionInfo.Name}\");"); + contents.AppendLine(" PROFILE_CPU_SRC_LOC(method->ProfilerData);"); // Convert parameters into managed format as boxed values var thunkParams = string.Empty; diff --git a/Source/Tools/Flax.Build/Build/Builder.Projects.cs b/Source/Tools/Flax.Build/Build/Builder.Projects.cs index 4fbabe986..3e403e956 100644 --- a/Source/Tools/Flax.Build/Build/Builder.Projects.cs +++ b/Source/Tools/Flax.Build/Build/Builder.Projects.cs @@ -126,6 +126,10 @@ namespace Flax.Build if (!platform.HasRequiredSDKsInstalled && (!projectInfo.IsCSharpOnlyProject || platform != Platform.BuildPlatform)) continue; + // Prevent generating configuration data for Windows x86 + if (architecture == TargetArchitecture.x86 && targetPlatform == TargetPlatform.Windows) + continue; + string configurationText = targetName + '.' + platformName + '.' + configurationName; string architectureName = architecture.ToString(); if (platform is IProjectCustomizer customizer) @@ -175,7 +179,7 @@ namespace Flax.Build using (new ProfileEventScope("GenerateProjects")) { // Pick the project format - List projectFormats = new List(); + HashSet projectFormats = new HashSet(); if (Configuration.ProjectFormatVS2022) projectFormats.Add(ProjectFormat.VisualStudio2022); @@ -187,12 +191,17 @@ namespace Flax.Build projectFormats.Add(ProjectFormat.VisualStudio2015); if (Configuration.ProjectFormatVSCode) projectFormats.Add(ProjectFormat.VisualStudioCode); + if (Configuration.ProjectFormatRider) + projectFormats.Add(ProjectFormat.VisualStudio2022); if (!string.IsNullOrEmpty(Configuration.ProjectFormatCustom)) projectFormats.Add(ProjectFormat.Custom); if (projectFormats.Count == 0) projectFormats.Add(Platform.BuildPlatform.DefaultProjectFormat); + // Always generate VS solution files for project (needed for C# Intellisense support) + projectFormats.Add(ProjectFormat.VisualStudio2022); + foreach (ProjectFormat projectFormat in projectFormats) GenerateProject(projectFormat); } @@ -219,6 +228,8 @@ namespace Flax.Build var projectToModulesBuildOptions = new Dictionary>(); Project mainSolutionProject = null; ProjectGenerator nativeProjectGenerator = ProjectGenerator.Create(projectFormat, TargetType.NativeCpp); + var solutionName = rootProject.Name; + var solutionPath = Path.Combine(workspaceRoot, solutionName + '.' + nativeProjectGenerator.SolutionFileExtension); // Group targets by project name and sort groups based on the project (ensures that referenced plugin source projects are generated firstly before main source projects) var targetGroups = new List(); @@ -493,7 +504,7 @@ namespace Flax.Build else if (dependencyModule.BinaryModuleName == "FlaxEngine") { // TODO: instead of this hack find a way to reference the prebuilt target bindings binary (example: game C# project references FlaxEngine C# prebuilt dll) - project.CSharp.FileReferences.Add(Path.Combine(Globals.EngineRoot, "Binaries/Editor/Win64/Development/FlaxEngine.CSharp.dll")); + project.CSharp.FileReferences.Add(Path.Combine(Globals.EngineRoot, Platform.GetEditorBinaryDirectory(), "Development/FlaxEngine.CSharp.dll")); } } } @@ -501,6 +512,35 @@ namespace Flax.Build } } + // When generating C#-only projects for Game that uses source-engine distribution replace dependencies on FlaxEngine with fixed dll file refs to fix Intellisense issues + if (rootProject.IsCSharpOnlyProject) + { + Project flaxDependencyToRemove = null; + foreach (var project in projects) + { + if (project.BaseName != "FlaxEngine") + { + var flaxDependency = project.Dependencies.FirstOrDefault(x => x.BaseName == "FlaxEngine"); + if (flaxDependency != null) + { + project.Dependencies.Remove(flaxDependency); + + // TODO: instead of this hack find a way to reference the prebuilt target bindings binary (example: game C# project references FlaxEngine C# prebuilt dll) + project.CSharp.FileReferences.Add(Path.Combine(Globals.EngineRoot, Platform.GetEditorBinaryDirectory(), "Development/FlaxEngine.CSharp.dll")); + + // Remove FlaxEngine from projects to prevent duplicated types errors in Intellisense (eg. Actor type defined in both FlaxEngine.CSharp.dll and FlaxEngine.csproj) + flaxDependencyToRemove = flaxDependency; + } + } + } + if (flaxDependencyToRemove != null) + { + projects.Remove(flaxDependencyToRemove); + foreach (var project in projects) + project.Dependencies.Remove(flaxDependencyToRemove); + } + } + // Setup custom projects GenerateCustomProjects?.Invoke(projects); nativeProjectGenerator.GenerateCustomProjects(projects); @@ -511,7 +551,7 @@ namespace Flax.Build foreach (var project in projects) { Log.Verbose(project.Name + " -> " + project.Path); - project.Generate(); + project.Generate(solutionPath); } } @@ -590,7 +630,7 @@ namespace Flax.Build using (new ProfileEventScope("GenerateProject")) { Log.Verbose("Project " + rulesProjectName + " -> " + project.Path); - dotNetProjectGenerator.GenerateProject(project); + dotNetProjectGenerator.GenerateProject(project, solutionPath); } projects.Add(project); @@ -603,9 +643,9 @@ namespace Flax.Build using (new ProfileEventScope("CreateSolution")) { solution = nativeProjectGenerator.CreateSolution(); - solution.Name = rootProject.Name; + solution.Name = solutionName; solution.WorkspaceRootPath = workspaceRoot; - solution.Path = Path.Combine(workspaceRoot, solution.Name + '.' + nativeProjectGenerator.SolutionFileExtension); + solution.Path = solutionPath; solution.Projects = projects.ToArray(); solution.MainProject = mainSolutionProject; } diff --git a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs index 43c54a92d..c64ee38b3 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs @@ -207,6 +207,7 @@ namespace Flax.Build case TargetPlatform.Mac: { #if USE_NETCORE + dotnetPath = Path.Combine(dotnetSdk.RootPath, "dotnet"); cscPath = Path.Combine(dotnetSdk.RootPath, $"sdk/{dotnetSdk.VersionName}/Roslyn/bincore/csc.dll"); referenceAssemblies = Path.Combine(dotnetSdk.RootPath, $"packs/Microsoft.NETCore.App.Ref/{dotnetSdk.RuntimeVersionName}/ref/net{runtimeVersionShort}/"); referenceAnalyzers = Path.Combine(dotnetSdk.RootPath, $"packs/Microsoft.NETCore.App.Ref/{dotnetSdk.RuntimeVersionName}/analyzers/dotnet/cs/"); diff --git a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs index d3a706a43..eff24dbf3 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs @@ -191,19 +191,10 @@ namespace Flax.Build } case TargetPlatform.Linux: { - // TODO: Support /etc/dotnet/install_location - - // Detect custom RID in some distros - rid = Utilities.ReadProcessOutput("dotnet", "--info").Split('\n').FirstOrDefault(x => x.StartsWith(" RID:"), "").Replace("RID:", "").Trim(); - ridFallback = $"linux-{arch}"; - if (rid == ridFallback) - ridFallback = ""; + rid = $"linux-{arch}"; + ridFallback = Utilities.ReadProcessOutput("dotnet", "--info").Split('\n').FirstOrDefault(x => x.StartsWith(" RID:"), "").Replace("RID:", "").Trim(); if (string.IsNullOrEmpty(dotnetPath)) - { - dotnetPath = "/usr/lib/dotnet/"; - if (!Directory.Exists(dotnetPath)) - dotnetPath = "/usr/share/dotnet/"; - } + dotnetPath ??= SearchForDotnetLocationLinux(); break; } case TargetPlatform.Mac: @@ -211,7 +202,29 @@ namespace Flax.Build rid = $"osx-{arch}"; ridFallback = ""; if (string.IsNullOrEmpty(dotnetPath)) + { dotnetPath = "/usr/local/share/dotnet/"; + if (!Directory.Exists(dotnetPath)) // Officially recommended dotnet location + { + if (Environment.GetEnvironmentVariable("PATH") is string globalBinPath) + dotnetPath = globalBinPath.Split(':').FirstOrDefault(x => File.Exists(Path.Combine(x, "dotnet"))); + else + dotnetPath = string.Empty; + } + } + + bool isRunningOnArm64Targetx64 = architecture == TargetArchitecture.ARM64 && (Configuration.BuildArchitectures != null && Configuration.BuildArchitectures[0] == TargetArchitecture.x64); + + // We need to support two paths here: + // 1. We are running an x64 binary and we are running on an arm64 host machine + // 2. We are running an Arm64 binary and we are targeting an x64 host machine + if (Flax.Build.Platforms.MacPlatform.GetProcessIsTranslated() || isRunningOnArm64Targetx64) + { + rid = "osx-x64"; + dotnetPath = Path.Combine(dotnetPath, "x64"); + architecture = TargetArchitecture.x64; + } + break; } default: throw new InvalidPlatformException(platform); @@ -271,7 +284,7 @@ namespace Flax.Build // Found IsValid = true; - Log.Verbose($"Found .NET SDK {VersionName} (runtime {RuntimeVersionName}) at {RootPath}"); + Log.Info($"Using .NET SDK {VersionName}, runtime {RuntimeVersionName} ({RootPath})"); foreach (var e in _hostRuntimes) Log.Verbose($" - Host Runtime for {e.Key.Key} {e.Key.Value}"); } @@ -427,7 +440,8 @@ namespace Flax.Build version = version.Substring(0, version.IndexOf("-")); rev = 0; } - Version ver = new Version(version); + if (!Version.TryParse(version, out var ver)) + return null; return new Version(ver.Major, ver.Minor, ver.Build, rev); } @@ -441,5 +455,18 @@ namespace Flax.Build // TODO: reject 'future' versions like .Net 8? return versions.OrderByDescending(ParseVersion).FirstOrDefault(); } + + private static string SearchForDotnetLocationLinux() + { + if (File.Exists("/etc/dotnet/install_location")) // Officially recommended dotnet location file + return File.ReadAllText("/etc/dotnet/install_location").Trim(); + if (File.Exists("/usr/share/dotnet/dotnet")) // Officially recommended dotnet location + return "/usr/share/dotnet"; + if (File.Exists("/usr/lib/dotnet/dotnet")) // Deprecated recommended dotnet location + return "/usr/lib/dotnet"; + if (Environment.GetEnvironmentVariable("PATH") is string globalBinPath) // Searching for dotnet binary + return globalBinPath.Split(':').FirstOrDefault(x => File.Exists(Path.Combine(x, "dotnet"))); + return null; + } } } diff --git a/Source/Tools/Flax.Build/CommandLine.cs b/Source/Tools/Flax.Build/CommandLine.cs index 91b6febce..f0a631145 100644 --- a/Source/Tools/Flax.Build/CommandLine.cs +++ b/Source/Tools/Flax.Build/CommandLine.cs @@ -249,6 +249,8 @@ namespace Flax.Build var wholeQuote = commandLine[i] == '\"'; if (wholeQuote) i++; + if (i == length) + break; if (commandLine[i] == '-') i++; else if (commandLine[i] == '/') @@ -279,7 +281,7 @@ namespace Flax.Build }); if (wholeQuote) i++; - if (i != length && commandLine[i] != '\"') + if (i < length && commandLine[i] != '\"') i++; continue; } @@ -287,23 +289,36 @@ namespace Flax.Build // Read value i++; int valueStart, valueEnd; - if (commandLine[i] == '\"') + if (commandLine.Length > i + 1 && commandLine[i] == '\\' && commandLine[i + 1] == '\"') { - valueStart = i + 1; + valueStart = i + 2; i++; - while (i < length && commandLine[i] != '\"') + while (i + 1 < length && commandLine[i] != '\\' && commandLine[i + 1] != '\"') i++; valueEnd = i; - i++; + i += 2; + if (wholeQuote) + { + while (i < length && commandLine[i] != '\"') + i++; + i++; + } } - else if (commandLine[i] == '\'') + else if (commandLine[i] == '\"' || commandLine[i] == '\'') { + var quoteChar = commandLine[i]; valueStart = i + 1; i++; - while (i < length && commandLine[i] != '\'') + while (i < length && commandLine[i] != quoteChar) i++; valueEnd = i; i++; + if (wholeQuote) + { + while (i < length && commandLine[i] != '\"') + i++; + i++; + } } else if (wholeQuote) { @@ -321,10 +336,13 @@ namespace Flax.Build valueEnd = i; } string value = commandLine.Substring(valueStart, valueEnd - valueStart); + value = value.Trim(); + if (value.StartsWith("\\\"") && value.EndsWith("\\\"")) + value = value.Substring(2, value.Length - 4); options.Add(new Option { Name = name, - Value = value.Trim() + Value = value }); } diff --git a/Source/Tools/Flax.Build/Configuration.cs b/Source/Tools/Flax.Build/Configuration.cs index 8fa17a235..19522f403 100644 --- a/Source/Tools/Flax.Build/Configuration.cs +++ b/Source/Tools/Flax.Build/Configuration.cs @@ -213,6 +213,12 @@ namespace Flax.Build [CommandLine("vscode", "Generates Visual Studio Code project format files. Valid only with -genproject option.")] public static bool ProjectFormatVSCode = false; + /// + /// Generates Visual Studio 2022 project format files for Rider. Valid only with -genproject option. + /// + [CommandLine("rider", "Generates Visual Studio 2022 project format files for Rider. Valid only with -genproject option.")] + public static bool ProjectFormatRider = false; + /// /// Generates code project files for a custom project format type. Valid only with -genproject option. /// diff --git a/Source/Tools/Flax.Build/Platforms/Linux/LinuxPlatform.cs b/Source/Tools/Flax.Build/Platforms/Linux/LinuxPlatform.cs index 2fabb2c46..44a2c247a 100644 --- a/Source/Tools/Flax.Build/Platforms/Linux/LinuxPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/Linux/LinuxPlatform.cs @@ -44,7 +44,7 @@ namespace Flax.Build.Platforms Compiler = Configuration.Compiler; else { - for (int ver = 15; ver >= 6; ver--) + for (int ver = 17; ver >= 6; ver--) { var compiler = "clang++-" + ver; if (Which(compiler) != null) diff --git a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs index 53d299515..ef89c8b4e 100644 --- a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs @@ -1,5 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using System.Runtime.InteropServices; + namespace Flax.Build.Platforms { /// @@ -41,5 +43,20 @@ namespace Flax.Build.Platforms default: return false; } } + + /// + /// Returns true if running an x64 binary an arm64 host machine. + /// + public unsafe static bool GetProcessIsTranslated() + { + int ret = 0; + ulong size = sizeof(int); + if (sysctlbyname("sysctl.proc_translated", &ret, &size, null, 0) == -1) + return false; + return ret != 0; + } + + [DllImport("c")] + private static unsafe extern int sysctlbyname(string name, void* oldp, ulong* oldlenp, void* newp, ulong newlen); } } diff --git a/Source/Tools/Flax.Build/Platforms/Unix/UnixPlatform.cs b/Source/Tools/Flax.Build/Platforms/Unix/UnixPlatform.cs index 9d9206f0f..b498f66e4 100644 --- a/Source/Tools/Flax.Build/Platforms/Unix/UnixPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/Unix/UnixPlatform.cs @@ -68,8 +68,11 @@ namespace Flax.Build.Platforms string path = proc.StandardOutput.ReadLine(); Log.Verbose(string.Format("which {0} exit code: {1}, result: {2}", name, proc.ExitCode, path)); - if (proc.ExitCode == 0 && string.IsNullOrEmpty(proc.StandardError.ReadToEnd())) + if (proc.ExitCode == 0) { + string err = proc.StandardError.ReadToEnd(); + if (!string.IsNullOrEmpty(err)) + Log.Verbose(string.Format("which stderr: {0}", err)); return path; } diff --git a/Source/Tools/Flax.Build/Platforms/iOS/iOSPlatform.cs b/Source/Tools/Flax.Build/Platforms/iOS/iOSPlatform.cs index 2d7a3372a..d4eaa5006 100644 --- a/Source/Tools/Flax.Build/Platforms/iOS/iOSPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/iOS/iOSPlatform.cs @@ -1,5 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using System.IO; + namespace Flax.Build.Platforms { /// @@ -11,6 +13,9 @@ namespace Flax.Build.Platforms /// public override TargetPlatform Target => TargetPlatform.iOS; + /// + public override bool HasRequiredSDKsInstalled { get; } + /// public override bool HasDynamicCodeExecutionSupport => false; @@ -21,11 +26,21 @@ namespace Flax.Build.Platforms { if (Platform.BuildTargetPlatform != TargetPlatform.Mac) return; - if (!HasRequiredSDKsInstalled) + if (!XCode.Instance.IsValid) { Log.Warning("Missing XCode. Cannot build for iOS platform."); return; } + + // We should check and see if the actual iphoneSDK is installed + string iphoneSDKPath = Utilities.ReadProcessOutput("/usr/bin/xcrun", "--sdk iphoneos --show-sdk-path"); + if (string.IsNullOrEmpty(iphoneSDKPath) || !Directory.Exists(iphoneSDKPath)) + { + Log.Warning("Missing iPhoneSDK. Cannot build for iOS platform."); + HasRequiredSDKsInstalled = false; + } + else + HasRequiredSDKsInstalled = true; } /// diff --git a/Source/Tools/Flax.Build/Projects/Project.cs b/Source/Tools/Flax.Build/Projects/Project.cs index 148e97485..0c98f73bb 100644 --- a/Source/Tools/Flax.Build/Projects/Project.cs +++ b/Source/Tools/Flax.Build/Projects/Project.cs @@ -253,9 +253,9 @@ namespace Flax.Build.Projects /// /// Generates the project. /// - public virtual void Generate() + public virtual void Generate(string solutionPath) { - Generator.GenerateProject(this); + Generator.GenerateProject(this, solutionPath); } /// diff --git a/Source/Tools/Flax.Build/Projects/ProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/ProjectGenerator.cs index 8641cf81b..a3da268a9 100644 --- a/Source/Tools/Flax.Build/Projects/ProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/ProjectGenerator.cs @@ -52,7 +52,7 @@ namespace Flax.Build.Projects /// Generates the project. /// /// The project. - public abstract void GenerateProject(Project project); + public abstract void GenerateProject(Project project, string solutionPath); /// /// Generates the solution. diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/CSProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/CSProjectGenerator.cs index 266097a3f..4e4e7dbfe 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/CSProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/CSProjectGenerator.cs @@ -26,7 +26,7 @@ namespace Flax.Build.Projects.VisualStudio public override TargetType? Type => TargetType.DotNet; /// - public override void GenerateProject(Project project) + public override void GenerateProject(Project project, string solutionPath) { var csProjectFileContent = new StringBuilder(); @@ -49,6 +49,11 @@ namespace Flax.Build.Projects.VisualStudio if (vsProject.CSharp.UseFlaxVS && VisualStudioInstance.HasFlaxVS) projectTypes = ProjectTypeGuids.ToOption(ProjectTypeGuids.FlaxVS) + ';' + projectTypes; + // Try to reuse the existing project guid from solution file + vsProject.ProjectGuid = GetProjectGuid(solutionPath, vsProject.Name); + if (vsProject.ProjectGuid == Guid.Empty) + vsProject.ProjectGuid = Guid.NewGuid(); + // Header csProjectFileContent.AppendLine(""); diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs index 5fba0dc43..387a0dcff 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs @@ -26,7 +26,7 @@ namespace Flax.Build.Projects.VisualStudio public override TargetType? Type => TargetType.DotNetCore; /// - public override void GenerateProject(Project project) + public override void GenerateProject(Project project, string solutionPath) { var csProjectFileContent = new StringBuilder(); @@ -53,6 +53,11 @@ namespace Flax.Build.Projects.VisualStudio } } + // Try to reuse the existing project guid from solution file + vsProject.ProjectGuid = GetProjectGuid(solutionPath, vsProject.Name); + if (vsProject.ProjectGuid == Guid.Empty) + vsProject.ProjectGuid = Guid.NewGuid(); + // Header csProjectFileContent.AppendLine(""); csProjectFileContent.AppendLine(""); @@ -174,8 +179,35 @@ namespace Flax.Build.Projects.VisualStudio else fileType = "None"; - var projectPath = Utilities.MakePathRelativeTo(file, projectDirectory); - csProjectFileContent.AppendLine(string.Format(" <{0} Include=\"{1}\" />", fileType, projectPath)); + var filePath = file.Replace('/', '\\'); // Normalize path + var projectPath = Utilities.MakePathRelativeTo(filePath, projectDirectory); + string linkPath = null; + if (projectPath.StartsWith(@"..\..\..\")) + { + // Create folder structure for project external files + var sourceIndex = filePath.LastIndexOf(@"\Source\"); + if (sourceIndex != -1) + { + projectPath = filePath; + string fileProjectRoot = filePath.Substring(0, sourceIndex); + string fileProjectName = Path.GetFileName(fileProjectRoot); + string fileProjectRelativePath = filePath.Substring(sourceIndex + 1); + + // Remove Source-directory from path + if (fileProjectRelativePath.IndexOf('\\') != -1) + fileProjectRelativePath = fileProjectRelativePath.Substring(fileProjectRelativePath.IndexOf('\\') + 1); + + if (fileProjectRoot == project.SourceFolderPath) + linkPath = fileProjectRelativePath; + else // BuildScripts project + linkPath = Path.Combine(fileProjectName, fileProjectRelativePath); + } + } + + if (!string.IsNullOrEmpty(linkPath)) + csProjectFileContent.AppendLine(string.Format(" <{0} Include=\"{1}\" Link=\"{2}\" />", fileType, projectPath, linkPath)); + else + csProjectFileContent.AppendLine(string.Format(" <{0} Include=\"{1}\" />", fileType, projectPath)); } if (project.GeneratedSourceFiles != null) @@ -188,7 +220,8 @@ namespace Flax.Build.Projects.VisualStudio else fileType = "None"; - csProjectFileContent.AppendLine(string.Format(" <{0} Visible=\"false\" Include=\"{1}\" />", fileType, file)); + var filePath = file.Replace('/', '\\'); + csProjectFileContent.AppendLine(string.Format(" <{0} Visible=\"false\" Include=\"{1}\" />", fileType, filePath)); } } diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs index 3e2a6c9b7..84607f6a1 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs @@ -55,7 +55,7 @@ namespace Flax.Build.Projects.VisualStudio } /// - public override void GenerateProject(Project project) + public override void GenerateProject(Project project, string solutionPath) { var vcProjectFileContent = new StringBuilder(); var vcFiltersFileContent = new StringBuilder(); @@ -67,6 +67,13 @@ namespace Flax.Build.Projects.VisualStudio var projectDirectory = Path.GetDirectoryName(project.Path); var filtersDirectory = project.SourceFolderPath; + // Try to reuse the existing project guid from existing files + vsProject.ProjectGuid = GetProjectGuid(vsProject.Path, vsProject.Name); + if (vsProject.ProjectGuid == Guid.Empty) + vsProject.ProjectGuid = GetProjectGuid(solutionPath, vsProject.Name); + if (vsProject.ProjectGuid == Guid.Empty) + vsProject.ProjectGuid = Guid.NewGuid(); + // Header vcProjectFileContent.AppendLine(""); vcProjectFileContent.AppendLine(string.Format("", projectFileToolVersion)); diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProject.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProject.cs index 5ec0ca8e4..b9b2d2383 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProject.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProject.cs @@ -36,20 +36,5 @@ namespace Flax.Build.Projects.VisualStudio } } } - - /// - public override string Path - { - get => base.Path; - set - { - base.Path = value; - - if (ProjectGuid == Guid.Empty) - { - ProjectGuid = VisualStudioProjectGenerator.GetProjectGuid(Path); - } - } - } } } diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs index 1955a57ba..a6684d6eb 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs @@ -22,7 +22,7 @@ namespace Flax.Build.Projects.VisualStudio public override Guid ProjectTypeGuid => ProjectTypeGuids.Android; /// - public override void Generate() + public override void Generate(string solutionPath) { var gen = (VisualStudioProjectGenerator)Generator; var projectFileToolVersion = gen.ProjectFileToolVersion; @@ -141,9 +141,10 @@ namespace Flax.Build.Projects.VisualStudio /// /// The path. /// The project ID. - public static Guid GetProjectGuid(string path) + public static Guid GetProjectGuid(string path, string projectName) { - if (File.Exists(path)) + // Look up for the guid in VC++-project file + if (File.Exists(path) && Path.GetExtension(path).Equals(".vcxproj", StringComparison.OrdinalIgnoreCase)) { try { @@ -161,8 +162,27 @@ namespace Flax.Build.Projects.VisualStudio // Hide errors } } + if (File.Exists(path) && Path.GetExtension(path).Equals(".sln", StringComparison.OrdinalIgnoreCase)) + { + try + { + Regex projectRegex = new Regex(@"Project\(""{(\S+)}""\) = \""(\S+)\"", \""(\S+)\"", \""{(\S+)}\"""); + MatchCollection matches = projectRegex.Matches(File.ReadAllText(path)); + for (int i = 0; i < matches.Count; i++) + { + if (matches[i].Groups[1].Value.Equals("2150E333-8FDC-42A3-9474-1A3956D46DE8", StringComparison.OrdinalIgnoreCase)) + continue; + if (matches[i].Groups[2].Value == projectName) + return Guid.ParseExact(matches[i].Groups[4].Value, "D"); + } + } + catch + { + // Hide errors + } + } - return Guid.NewGuid(); + return Guid.Empty; } /// @@ -236,7 +256,21 @@ namespace Flax.Build.Projects.VisualStudio /// public override void GenerateSolution(Solution solution) { - // Try to extract info from the existing solution file to make random IDs stable + // Ensure that the main project is the first one (initially selected by Visual Studio) + if (solution.MainProject != null && solution.Projects.Length != 0 && solution.Projects[0] != solution.MainProject) + { + for (int i = 1; i < solution.Projects.Length; i++) + { + if (solution.Projects[i] == solution.MainProject) + { + solution.Projects[i] = solution.Projects[0]; + solution.Projects[0] = solution.MainProject; + break; + } + } + } + + // Try to extract solution folder info from the existing solution file to make random IDs stable var solutionId = Guid.NewGuid(); var folderIds = new Dictionary(); if (File.Exists(solution.Path)) @@ -252,16 +286,12 @@ namespace Flax.Build.Projects.VisualStudio solutionId = Guid.ParseExact(value.Substring(15), "B"); } - var folderIdsMatch = Regex.Match(contents, "Project\\(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\"\\) = \"(.*?)\", \"(.*?)\", \"{(.*?)}\""); - if (folderIdsMatch.Success) + var folderIdMatches = new Regex("Project\\(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\"\\) = \"(.*?)\", \"(.*?)\", \"{(.*?)}\"").Matches(contents); + foreach (Match match in folderIdMatches) { - foreach (Capture capture in folderIdsMatch.Captures) - { - var value = capture.Value.Substring("Project(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"".Length); - var folder = value.Substring(0, value.IndexOf('\"')); - var folderId = Guid.ParseExact(value.Substring(folder.Length * 2 + "\", \"".Length + "\", \"".Length, 38), "B"); - folderIds["Source\\" + folder] = folderId; - } + var folder = match.Groups[1].Value; + var folderId = Guid.ParseExact(match.Groups[3].Value, "D"); + folderIds[folder] = folderId; } } catch (Exception ex) @@ -348,7 +378,8 @@ namespace Flax.Build.Projects.VisualStudio { if (!folderIds.TryGetValue(folderPath, out project.FolderGuid)) { - project.FolderGuid = Guid.NewGuid(); + if (!folderIds.TryGetValue(folderParents[i], out project.FolderGuid)) + project.FolderGuid = Guid.NewGuid(); folderIds.Add(folderPath, project.FolderGuid); } folderNames.Add(folderPath); @@ -402,6 +433,10 @@ namespace Flax.Build.Projects.VisualStudio if (project.Configurations == null || project.Configurations.Count == 0) throw new Exception("Missing configurations for project " + project.Name); + // Prevent generating default Debug|AnyCPU and Release|AnyCPU configurations from Flax projects + if (project.Name == "BuildScripts" || project.Name == "Flax.Build" || project.Name == "Flax.Build.Tests") + continue; + foreach (var configuration in project.Configurations) { configurations.Add(new SolutionConfiguration(configuration)); @@ -447,7 +482,7 @@ namespace Flax.Build.Projects.VisualStudio { SolutionConfiguration projectConfiguration; bool build = false; - int firstFullMatch = -1, firstPlatformMatch = -1; + int firstFullMatch = -1, firstPlatformMatch = -1, firstEditorMatch = -1; for (int i = 0; i < project.Configurations.Count; i++) { var e = new SolutionConfiguration(project.Configurations[i]); @@ -460,18 +495,31 @@ namespace Flax.Build.Projects.VisualStudio { firstPlatformMatch = i; } + if (firstEditorMatch == -1 && e.Configuration == configuration.Configuration) + { + firstEditorMatch = i; + } } if (firstFullMatch != -1) { projectConfiguration = configuration; build = solution.MainProject == project || (solution.MainProject == null && project.Name == solution.Name); } - else if (firstPlatformMatch != -1) + else if (firstPlatformMatch != -1 && !configuration.Name.StartsWith("Editor.")) { + // No exact match, pick the first configuration for matching platform projectConfiguration = new SolutionConfiguration(project.Configurations[firstPlatformMatch]); } + else if (firstEditorMatch != -1 && configuration.Name.StartsWith("Editor.")) + { + // No exact match, pick the matching editor configuration for different platform. + // As an example, Editor configuration for Android projects should be remapped + // to desktop platform in order to provide working Intellisense information. + projectConfiguration = new SolutionConfiguration(project.Configurations[firstEditorMatch]); + } else { + // No match projectConfiguration = new SolutionConfiguration(project.Configurations[0]); } @@ -540,8 +588,8 @@ namespace Flax.Build.Projects.VisualStudio { var profiles = new Dictionary(); var profile = new StringBuilder(); - var editorPath = Path.Combine(Globals.EngineRoot, "Binaries/Editor/Win64/Development/FlaxEditor.exe").Replace('/', '\\').Replace("\\", "\\\\"); - var workspacePath = solutionDirectory.Replace('/', '\\').Replace("\\", "\\\\"); + var editorPath = Utilities.NormalizePath(Path.Combine(Globals.EngineRoot, Platform.GetEditorBinaryDirectory(), $"Development/FlaxEditor{Utilities.GetPlatformExecutableExt()}")); + var workspacePath = Utilities.NormalizePath(solutionDirectory); foreach (var project in projects) { if (project.Type == TargetType.DotNetCore) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs index eb6c2bb24..887197258 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs @@ -38,7 +38,7 @@ namespace Flax.Build.Projects.VisualStudioCode } /// - public override void GenerateProject(Project project) + public override void GenerateProject(Project project, string solutionPath) { // Not used, solution contains all projects definitions } @@ -155,122 +155,122 @@ namespace Flax.Build.Projects.VisualStudioCode { foreach (var project in solution.Projects) { - // C++ project - if (project.Type == TargetType.NativeCpp) + if (project.Name == "BuildScripts") + continue; + + // Skip duplicate build tasks + if (project.Name == "FlaxEngine" || (solution.MainProject.Name != "Flax" && solution.MainProject != project)) + continue; + + bool defaultTask = project == solution.MainProject; + foreach (var configuration in project.Configurations) { - bool defaultTask = project == solution.MainProject; - foreach (var configuration in project.Configurations) + var target = configuration.Target; + var name = project.Name + '|' + configuration.Name; + + json.BeginObject(); + + json.AddField("label", name); + + bool isDefaultTask = defaultTask && configuration.Configuration == TargetConfiguration.Development && configuration.Platform == Platform.BuildPlatform.Target; + + json.BeginObject("group"); { - var target = configuration.Target; - var name = project.Name + '|' + configuration.Name; - - json.BeginObject(); - - json.AddField("label", name); - - if (defaultTask && configuration.Configuration == TargetConfiguration.Development && configuration.Platform == Platform.BuildPlatform.Target) - { - defaultTask = false; - json.BeginObject("group"); - { - json.AddField("kind", "build"); - json.AddField("isDefault", true); - } - json.EndObject(); - } - else - { - json.AddField("group", "build"); - } - - switch (Platform.BuildPlatform.Target) - { - case TargetPlatform.Windows: - { - json.AddField("command", buildToolPath); - json.BeginArray("args"); - { - json.AddUnnamedField("-build"); - json.AddUnnamedField("-log"); - json.AddUnnamedField("-mutex"); - json.AddUnnamedField(string.Format("\\\"-workspace={0}\\\"", buildToolWorkspace)); - json.AddUnnamedField(string.Format("-arch={0}", configuration.ArchitectureName)); - json.AddUnnamedField(string.Format("-configuration={0}", configuration.ConfigurationName)); - json.AddUnnamedField(string.Format("-platform={0}", configuration.PlatformName)); - json.AddUnnamedField(string.Format("-buildTargets={0}", target.Name)); - if (!string.IsNullOrEmpty(Configuration.Compiler)) - json.AddUnnamedField(string.Format("-compiler={0}", Configuration.Compiler)); - } - json.EndArray(); - - json.AddField("type", "shell"); - - json.BeginObject("options"); - { - json.AddField("cwd", buildToolWorkspace); - } - json.EndObject(); - break; - } - case TargetPlatform.Linux: - { - json.AddField("command", buildToolPath); - json.BeginArray("args"); - { - json.AddUnnamedField("--build"); - json.AddUnnamedField("--log"); - json.AddUnnamedField("--mutex"); - json.AddUnnamedField(string.Format("--workspace=\\\"{0}\\\"", buildToolWorkspace)); - json.AddUnnamedField(string.Format("--arch={0}", configuration.Architecture)); - json.AddUnnamedField(string.Format("--configuration={0}", configuration.ConfigurationName)); - json.AddUnnamedField(string.Format("--platform={0}", configuration.PlatformName)); - json.AddUnnamedField(string.Format("--buildTargets={0}", target.Name)); - if (!string.IsNullOrEmpty(Configuration.Compiler)) - json.AddUnnamedField(string.Format("--compiler={0}", Configuration.Compiler)); - } - json.EndArray(); - - json.AddField("type", "shell"); - - json.BeginObject("options"); - { - json.AddField("cwd", buildToolWorkspace); - } - json.EndObject(); - break; - } - case TargetPlatform.Mac: - { - json.AddField("command", buildToolPath); - json.BeginArray("args"); - { - json.AddUnnamedField("--build"); - json.AddUnnamedField("--log"); - json.AddUnnamedField("--mutex"); - json.AddUnnamedField(string.Format("--workspace=\\\"{0}\\\"", buildToolWorkspace)); - json.AddUnnamedField(string.Format("--arch={0}", configuration.Architecture)); - json.AddUnnamedField(string.Format("--configuration={0}", configuration.ConfigurationName)); - json.AddUnnamedField(string.Format("--platform={0}", configuration.PlatformName)); - json.AddUnnamedField(string.Format("--buildTargets={0}", target.Name)); - if (!string.IsNullOrEmpty(Configuration.Compiler)) - json.AddUnnamedField(string.Format("--compiler={0}", Configuration.Compiler)); - } - json.EndArray(); - - json.AddField("type", "shell"); - - json.BeginObject("options"); - { - json.AddField("cwd", buildToolWorkspace); - } - json.EndObject(); - break; - } - default: throw new Exception("Visual Code project generator does not support current platform."); - } - - json.EndObject(); + json.AddField("kind", "build"); + json.AddField("isDefault", isDefaultTask); } + json.EndObject(); + + if (isDefaultTask) + defaultTask = false; + + switch (Platform.BuildPlatform.Target) + { + case TargetPlatform.Windows: + { + json.AddField("command", buildToolPath); + json.BeginArray("args"); + { + json.AddUnnamedField("-build"); + json.AddUnnamedField("-log"); + json.AddUnnamedField("-mutex"); + json.AddUnnamedField(string.Format("\\\"-workspace={0}\\\"", buildToolWorkspace)); + json.AddUnnamedField(string.Format("-arch={0}", configuration.ArchitectureName)); + json.AddUnnamedField(string.Format("-configuration={0}", configuration.ConfigurationName)); + json.AddUnnamedField(string.Format("-platform={0}", configuration.PlatformName)); + json.AddUnnamedField(string.Format("-buildTargets={0}", target.Name)); + if (!string.IsNullOrEmpty(Configuration.Compiler)) + json.AddUnnamedField(string.Format("-compiler={0}", Configuration.Compiler)); + } + json.EndArray(); + + json.AddField("type", "shell"); + + json.BeginObject("options"); + { + json.AddField("cwd", buildToolWorkspace); + } + json.EndObject(); + break; + } + case TargetPlatform.Linux: + { + json.AddField("command", buildToolPath); + json.BeginArray("args"); + { + json.AddUnnamedField("--build"); + json.AddUnnamedField("--log"); + json.AddUnnamedField("--mutex"); + json.AddUnnamedField(string.Format("--workspace=\\\"{0}\\\"", buildToolWorkspace)); + json.AddUnnamedField(string.Format("--arch={0}", configuration.Architecture)); + json.AddUnnamedField(string.Format("--configuration={0}", configuration.ConfigurationName)); + json.AddUnnamedField(string.Format("--platform={0}", configuration.PlatformName)); + json.AddUnnamedField(string.Format("--buildTargets={0}", target.Name)); + if (!string.IsNullOrEmpty(Configuration.Compiler)) + json.AddUnnamedField(string.Format("--compiler={0}", Configuration.Compiler)); + } + json.EndArray(); + + json.AddField("type", "shell"); + + json.BeginObject("options"); + { + json.AddField("cwd", buildToolWorkspace); + } + json.EndObject(); + break; + } + case TargetPlatform.Mac: + { + json.AddField("command", buildToolPath); + json.BeginArray("args"); + { + json.AddUnnamedField("--build"); + json.AddUnnamedField("--log"); + json.AddUnnamedField("--mutex"); + json.AddUnnamedField(string.Format("--workspace=\\\"{0}\\\"", buildToolWorkspace)); + json.AddUnnamedField(string.Format("--arch={0}", configuration.Architecture)); + json.AddUnnamedField(string.Format("--configuration={0}", configuration.ConfigurationName)); + json.AddUnnamedField(string.Format("--platform={0}", configuration.PlatformName)); + json.AddUnnamedField(string.Format("--buildTargets={0}", target.Name)); + if (!string.IsNullOrEmpty(Configuration.Compiler)) + json.AddUnnamedField(string.Format("--compiler={0}", Configuration.Compiler)); + } + json.EndArray(); + + json.AddField("type", "shell"); + + json.BeginObject("options"); + { + json.AddField("cwd", buildToolWorkspace); + } + json.EndObject(); + break; + } + default: throw new Exception("Visual Code project generator does not support current platform."); + } + + json.EndObject(); } } } @@ -281,6 +281,10 @@ namespace Flax.Build.Projects.VisualStudioCode json.Save(Path.Combine(vsCodeFolder, "tasks.json")); } + bool hasNativeProjects = solution.Projects.Any(x => x.Type == TargetType.NativeCpp); + bool hasMonoProjects = solution.Projects.Any(x => x.Type == TargetType.DotNet); + bool hasDotnetProjects = solution.Projects.Any(x => x.Type == TargetType.DotNetCore); + // Create launch file using (var json = new JsonWriter()) { @@ -292,9 +296,15 @@ namespace Flax.Build.Projects.VisualStudioCode { foreach (var project in solution.Projects) { + if (project.Name == "BuildScripts") + continue; // C++ project if (project.Type == TargetType.NativeCpp) { + // Skip generating launch profiles for plugins and dependencies + if (solution.MainProject.Name != "Flax" && project.Name != "Flax.Build" && solution.MainProject.WorkspaceRootPath != project.WorkspaceRootPath) + continue; + foreach (var configuration in project.Configurations) { var name = project.Name + '|' + configuration.Name; @@ -314,115 +324,49 @@ namespace Flax.Build.Projects.VisualStudioCode json.AddField("preLaunchTask", name); json.AddField("cwd", buildToolWorkspace); - switch (Platform.BuildPlatform.Target) + WriteNativePlatformLaunchSettings(json, configuration.Platform); + + if (outputType != TargetOutputType.Executable && configuration.Name.StartsWith("Editor.")) { - case TargetPlatform.Windows: - if (configuration.Platform == TargetPlatform.Windows && outputType != TargetOutputType.Executable && configuration.Name.StartsWith("Editor.")) + if (configuration.Platform == TargetPlatform.Windows) { var editorFolder = configuration.Architecture == TargetArchitecture.x64 ? "Win64" : "Win32"; json.AddField("program", Path.Combine(Globals.EngineRoot, "Binaries", "Editor", editorFolder, configuration.ConfigurationName, "FlaxEditor.exe")); - json.BeginArray("args"); + } + else if (configuration.Platform == TargetPlatform.Linux) + json.AddField("program", Path.Combine(Globals.EngineRoot, "Binaries", "Editor", "Linux", configuration.ConfigurationName, "FlaxEditor")); + else if (configuration.Platform == TargetPlatform.Mac) + json.AddField("program", Path.Combine(Globals.EngineRoot, "Binaries", "Editor", "Mac", configuration.ConfigurationName, "FlaxEditor")); + + json.BeginArray("args"); + { + json.AddUnnamedField("-project"); + json.AddUnnamedField(buildToolWorkspace); + json.AddUnnamedField("-skipCompile"); + if (hasMonoProjects) { - json.AddUnnamedField("-project"); - json.AddUnnamedField(buildToolWorkspace); - json.AddUnnamedField("-skipCompile"); json.AddUnnamedField("-debug"); json.AddUnnamedField("127.0.0.1:55555"); } - json.EndArray(); } - else - { - json.AddField("program", outputTargetFilePath); - } - break; - case TargetPlatform.Linux: - if (configuration.Platform == TargetPlatform.Linux && (outputType != TargetOutputType.Executable || project.BaseName == "Flax") && configuration.Name.StartsWith("Editor.")) - { - json.AddField("program", Path.Combine(Globals.EngineRoot, "Binaries", "Editor", "Linux", configuration.ConfigurationName, "FlaxEditor")); - } - else - { - json.AddField("program", outputTargetFilePath); - } - if (configuration.Platform == TargetPlatform.Linux) - { - json.AddField("MIMode", "gdb"); - json.BeginArray("setupCommands"); - { - json.BeginObject(); - json.AddField("description", "Enable pretty-printing for gdb"); - json.AddField("text", "-enable-pretty-printing"); - json.AddField("ignoreFailures", true); - json.EndObject(); - - // Ignore signals used by C# runtime - json.BeginObject(); - json.AddField("description", "ignore SIG34 signal"); - json.AddField("text", "handle SIG34 nostop noprint pass"); - json.EndObject(); - json.BeginObject(); - json.AddField("description", "ignore SIG35 signal"); - json.AddField("text", "handle SIG35 nostop noprint pass"); - json.EndObject(); - json.BeginObject(); - json.AddField("description", "ignore SIG36 signal"); - json.AddField("text", "handle SIG36 nostop noprint pass"); - json.EndObject(); - json.BeginObject(); - json.AddField("description", "ignore SIG357 signal"); - json.AddField("text", "handle SIG37 nostop noprint pass"); - json.EndObject(); - } - json.EndArray(); - json.BeginArray("args"); - { - json.AddUnnamedField("--std"); - if (outputType != TargetOutputType.Executable && configuration.Name.StartsWith("Editor.")) - { - json.AddUnnamedField("--project"); - json.AddUnnamedField(buildToolWorkspace); - json.AddUnnamedField("--skipCompile"); - } - } - json.EndArray(); - } - break; - case TargetPlatform.Mac: - if (configuration.Platform == TargetPlatform.Mac && (outputType != TargetOutputType.Executable || project.BaseName == "Flax") && configuration.Name.StartsWith("Editor.")) - { - json.AddField("program", Path.Combine(Globals.EngineRoot, "Binaries", "Editor", "Mac", configuration.ConfigurationName, "FlaxEditor")); - } - else - { - json.AddField("program", outputTargetFilePath); - } - if (configuration.Platform == TargetPlatform.Mac) - { - json.AddField("MIMode", "lldb"); - json.BeginArray("args"); - { - json.AddUnnamedField("--std"); - if (outputType != TargetOutputType.Executable && configuration.Name.StartsWith("Editor.")) - { - json.AddUnnamedField("--project"); - json.AddUnnamedField(buildToolWorkspace); - json.AddUnnamedField("--skipCompile"); - } - } - json.EndArray(); - } - break; + json.EndArray(); } - switch (configuration.Platform) + else { - case TargetPlatform.Windows: - json.AddField("stopAtEntry", false); - json.AddField("externalConsole", true); - break; - case TargetPlatform.Linux: - break; + json.AddField("program", outputTargetFilePath); + json.BeginArray("args"); + { + if (configuration.Platform == TargetPlatform.Linux || configuration.Platform == TargetPlatform.Mac) + json.AddUnnamedField("--std"); + if (hasMonoProjects) + { + json.AddUnnamedField("-debug"); + json.AddUnnamedField("127.0.0.1:55555"); + } + } + json.EndArray(); } + json.AddField("visualizerFile", Path.Combine(Globals.EngineRoot, "Source", "flax.natvis")); } json.EndObject(); @@ -431,16 +375,9 @@ namespace Flax.Build.Projects.VisualStudioCode // C# project else if (project.Type == TargetType.DotNetCore) { - // TODO: Skip generating launch profiles for plugins and dependencies - - json.BeginObject(); - { - json.AddField("type", "coreclr"); - json.AddField("name", project.Name + " (C# attach Editor)"); - json.AddField("request", "attach"); - json.AddField("processName", "FlaxEditor"); - } - json.EndObject(); + // Skip generating launch profiles for plugins and dependencies + if (solution.MainProject.WorkspaceRootPath != project.WorkspaceRootPath) + continue; foreach (var configuration in project.Configurations) { @@ -484,24 +421,54 @@ namespace Flax.Build.Projects.VisualStudioCode json.EndObject(); } } - // Mono C# project - else if (project.Type == TargetType.DotNet) - { - foreach (var configuration in project.Configurations) - { - json.BeginObject(); - { - json.AddField("type", "mono"); - json.AddField("name", project.Name + " (C# attach)" + '|' + configuration.Name); - json.AddField("request", "attach"); - json.AddField("address", "localhost"); - json.AddField("port", 55555); - } - json.EndObject(); - } - } } } + + if (hasNativeProjects) + { + foreach (var platform in solution.Projects.SelectMany(x => x.Configurations).Select(x => x.Platform).Distinct()) + { + json.BeginObject(); + { + if (platform == TargetPlatform.Windows) + json.AddField("type", "cppvsdbg"); + else + json.AddField("type", "cppdbg"); + json.AddField("name", solution.Name + " (Attach Editor)"); + json.AddField("request", "attach"); + json.AddField("processId", "${command:pickProcess}"); // Does not seem to be possible to attach by process name? + + WriteNativePlatformLaunchSettings(json, platform); + + json.AddField("visualizerFile", Path.Combine(Globals.EngineRoot, "Source", "flax.natvis")); + } + json.EndObject(); + } + } + if (hasDotnetProjects) + { + json.BeginObject(); + { + json.AddField("type", "coreclr"); + json.AddField("name", solution.Name + " (C# Attach Editor)"); + json.AddField("request", "attach"); + json.AddField("processName", "FlaxEditor"); + } + json.EndObject(); + } + if (hasMonoProjects) + { + json.BeginObject(); + { + json.AddField("type", "mono"); + json.AddField("name", solution.Name + " (C# Attach)"); + json.AddField("request", "attach"); + json.AddField("address", "localhost"); + json.AddField("port", 55555); + } + json.EndObject(); + } + json.EndArray(); } json.EndRootObject(); @@ -509,6 +476,59 @@ namespace Flax.Build.Projects.VisualStudioCode json.Save(Path.Combine(vsCodeFolder, "launch.json")); } + static void WriteNativePlatformLaunchSettings(JsonWriter json, TargetPlatform platform) + { + switch (Platform.BuildPlatform.Target) + { + case TargetPlatform.Linux: + if (platform == TargetPlatform.Linux) + { + json.AddField("MIMode", "gdb"); + json.BeginArray("setupCommands"); + { + json.BeginObject(); + json.AddField("description", "Enable pretty-printing for gdb"); + json.AddField("text", "-enable-pretty-printing"); + json.AddField("ignoreFailures", true); + json.EndObject(); + + // Ignore signals used by C# runtime + json.BeginObject(); + json.AddField("description", "ignore SIG34 signal"); + json.AddField("text", "handle SIG34 nostop noprint pass"); + json.EndObject(); + json.BeginObject(); + json.AddField("description", "ignore SIG35 signal"); + json.AddField("text", "handle SIG35 nostop noprint pass"); + json.EndObject(); + json.BeginObject(); + json.AddField("description", "ignore SIG36 signal"); + json.AddField("text", "handle SIG36 nostop noprint pass"); + json.EndObject(); + json.BeginObject(); + json.AddField("description", "ignore SIG357 signal"); + json.AddField("text", "handle SIG37 nostop noprint pass"); + json.EndObject(); + } + json.EndArray(); + } + break; + case TargetPlatform.Mac: + if (platform == TargetPlatform.Mac) + { + json.AddField("MIMode", "lldb"); + } + break; + } + switch (platform) + { + case TargetPlatform.Windows: + json.AddField("stopAtEntry", false); + json.AddField("externalConsole", true); + break; + } + } + // Create C++ properties file using (var json = new JsonWriter()) { diff --git a/Source/Tools/Flax.Build/Projects/XCodeProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/XCodeProjectGenerator.cs index 48ba9c0f0..85ac0ff7e 100644 --- a/Source/Tools/Flax.Build/Projects/XCodeProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/XCodeProjectGenerator.cs @@ -42,7 +42,7 @@ namespace Flax.Build.Projects } /// - public override void GenerateProject(Project project) + public override void GenerateProject(Project project, string solutionPath) { } diff --git a/Source/Tools/Flax.Build/Utilities/Utilities.cs b/Source/Tools/Flax.Build/Utilities/Utilities.cs index af6777199..59e13aa73 100644 --- a/Source/Tools/Flax.Build/Utilities/Utilities.cs +++ b/Source/Tools/Flax.Build/Utilities/Utilities.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using System.Text; namespace Flax.Build @@ -746,5 +747,19 @@ namespace Flax.Build text = text.Replace(findWhat, replaceWith); File.WriteAllText(file, text); } + + /// + /// Returns back the exe ext for the current platform + /// + public static string GetPlatformExecutableExt() + { + var extEnding = ".exe"; + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + extEnding = ""; + } + + return extEnding; + } } }