Compare commits

...

109 Commits

Author SHA1 Message Date
mafiesto4 be5e13301e Fix displaying 'No Camera' overlay in editor viewport for game when using custom rendering 2026-06-07 23:16:23 +02:00
mafiesto4 f41e32683a Fix building deps for Windows ARM64 2026-06-07 22:50:03 +02:00
mafiesto4 1a8827ba76 Fix Web build when python is installed in folder with whitespaces in path 2026-06-03 14:21:38 +02:00
mafiesto4 eed227aa79 Add distance-scale to vertex paint vertices
Add vertex paint brush size changing with shift+scroll
Fix vertex paint brush size to match the highlight sphere
2026-06-03 14:15:40 +02:00
mafiesto4 27ee42b0a1 Bump up build number 2026-06-03 13:01:26 +02:00
mafiesto4 fd8ae9bc2b Rename SceneRenderTask::RenderingPercentage to RenderScale 2026-06-03 13:01:11 +02:00
mafiesto4 e0f234c667 Add enum serialization as string via EnumString attribute 2026-06-03 13:00:43 +02:00
mafiesto4 bdeb89538c Optimize auto generated Variant Types in bindings to reduce dynamic memory allocs in game builds 2026-06-03 11:05:17 +02:00
mafiesto4 0f86537099 Add simpler Variant::Enum that auto-setups variant type from enum scripting info 2026-06-03 11:01:14 +02:00
mafiesto4 f6f7bbb3d0 Fix Variant static typenames caching bug in Editor 2026-06-03 10:58:17 +02:00
mafiesto4 422300adbd Add VariantType::GetScriptingType for easier type information access 2026-06-03 10:57:51 +02:00
mafiesto4 89a1f00c57 Fix Guid diff serialization and loading invalid values 2026-06-03 05:11:53 +02:00
mafiesto4 f4be035f04 Add Physics::DeleteScene 2026-06-03 05:03:32 +02:00
mafiesto4 c2ec3fe2cb Simplify async render flushing code 2026-06-03 05:03:22 +02:00
mafiesto4 fca6ed43cc Fix compilation regression 2026-06-02 19:17:40 +02:00
Murry Lancashire 84ccb9df0c Add more LOD Generation options to model import settings (eg. borders lock, preserve UVs)
https://github.com/LOOPDISK/FlaxEngine/commit/1cfd4634727055c2a7249dff447528877332beab
2026-06-02 16:42:30 +02:00
mafiesto4 777602fee6 Fix asset storage handling when file is locked
https://github.com/LOOPDISK/FlaxEngine/pull/45
2026-06-02 15:54:36 +02:00
mafiesto4 24654e5b02 Add CHECK_NO_RETURN for checks in code that should continue function execution 2026-06-02 15:54:00 +02:00
Murry Lancashire 44117084c8 Fix asset cache eviction for locked files to retain cached info
https://github.com/LOOPDISK/FlaxEngine/pull/45
2026-06-02 15:53:35 +02:00
mafiesto4 2531a4b918 Fix asset 'Reload' option to be available when asset failed to load for manual load 2026-06-02 15:51:46 +02:00
mafiesto4 9ce6026192 Optimize model actors entries serialization 2026-06-02 14:07:28 +02:00
mafiesto4 a12c5e2203 Add more profiler events to assets code 2026-06-02 13:24:10 +02:00
mafiesto4 ff526ecafb Fix nested prefab stack overflow when adding new object to nested prefabs hierarchy
https://github.com/LOOPDISK/FlaxEngine/pull/44
2026-06-02 13:23:21 +02:00
mafiesto4 1badeda31c Fix error when drawing animated model thumbnail 2026-06-01 19:20:00 +02:00
mafiesto4 018c7cf33d Update editor icons atlas with new folder color and add original .psd file 2026-06-01 19:19:50 +02:00
mafiesto4 6daec81db1 Fix crash when using curves inside Anim Graph
https://forum.flaxengine.com/t/bug-report-v1-12-using-curves-in-the-animation-graph-causes-a-crash/2594
2026-05-28 13:30:40 +02:00
mafiesto4 c36c39df37 Add auto focus on model when changing Base Model in Anim Graph 2026-05-28 11:38:34 +02:00
mafiesto4 b7a59447a3 Fix Web build files location to properly exist in final package
https://forum.flaxengine.com/t/web-build-issue-with-c-script/2590
2026-05-28 11:32:24 +02:00
mafiesto4 6fa38b75d5 Add material instance creation out of other instance
#4063
2026-05-28 10:40:51 +02:00
mafiesto4 de1b515f1e Merge branch 'xxSeys1-SkeletonAndSkeletonDebugDrawImprovements' 2026-05-28 10:32:22 +02:00
mafiesto4 b004e90606 Adjust category names #4014 2026-05-28 10:32:16 +02:00
mafiesto4 b7185bfe72 Merge branch 'SkeletonAndSkeletonDebugDrawImprovements' of https://github.com/xxSeys1/FlaxEngine into xxSeys1-SkeletonAndSkeletonDebugDrawImprovements 2026-05-28 10:10:19 +02:00
mafiesto4 7a41e7d6d8 Merge branch 'xxSeys1-ScrollToNewOnAnimTrackAdd' 2026-05-28 10:07:19 +02:00
mafiesto4 3e670f5e80 Remove not needed line
#4013
2026-05-28 10:07:16 +02:00
mafiesto4 7e19ffbe40 Merge branch 'ScrollToNewOnAnimTrackAdd' of https://github.com/xxSeys1/FlaxEngine into xxSeys1-ScrollToNewOnAnimTrackAdd 2026-05-28 10:04:45 +02:00
mafiesto4 80e20744aa Merge branch 'Duroxxigar-show-debug-draw-default' 2026-05-28 09:59:03 +02:00
mafiesto4 16effd8328 Merge branch 'show-debug-draw-default' of https://github.com/Duroxxigar/FlaxEngine into Duroxxigar-show-debug-draw-default 2026-05-28 09:58:57 +02:00
mafiesto4 e7c5f257e9 Minor fixes and improvements 2026-05-28 09:54:35 +02:00
mafiesto4 320d37d9a2 Fix MeshAccessor triangle count calculation bug
#4121
2026-05-28 09:54:18 +02:00
mafiesto4 791fb785cf Revert "Simplify apple platform include defines"
This reverts commit 3140c711a4.
2026-05-27 17:54:06 +02:00
mafiesto4 ab6b5927f8 Fix missing Int2/Int3/Int4 in Variant support implementation parts
#4114
2026-05-27 17:23:22 +02:00
mafiesto4 7c1df5c980 Fix crash when loading invalid VisjectMeta
#4114
2026-05-27 17:22:22 +02:00
mafiesto4 14ad3f892e Merge branch 'xxSeys1-DirectionGizmoRotation' 2026-05-27 15:01:43 +02:00
mafiesto4 bd3129e9e7 Merge branch 'DirectionGizmoRotation' of https://github.com/xxSeys1/FlaxEngine into xxSeys1-DirectionGizmoRotation 2026-05-27 14:59:55 +02:00
mafiesto4 45cc04d434 Merge branch 'GoaLitiuM-decorations_dpi_fix' 2026-05-27 14:55:13 +02:00
mafiesto4 f78954c174 Merge branch 'decorations_dpi_fix' of https://github.com/GoaLitiuM/FlaxEngine into GoaLitiuM-decorations_dpi_fix 2026-05-27 14:52:58 +02:00
mafiesto4 e43a12a9a8 Merge branch 'GoaLitiuM-windows_cursor_pos_fix' 2026-05-27 14:45:32 +02:00
mafiesto4 1e6fdb7f02 Merge branch 'windows_cursor_pos_fix' of https://github.com/GoaLitiuM/FlaxEngine into GoaLitiuM-windows_cursor_pos_fix 2026-05-27 14:45:11 +02:00
mafiesto4 067c8ae5b8 Bump up build number 2026-05-27 14:40:41 +02:00
mafiesto4 95aa04c334 Merge branch 'ifromstone-dev/MacOSVulkanTimestampQueries' 2026-05-27 14:39:58 +02:00
mafiesto4 efd6bc27b8 Merge branch 'dev/MacOSVulkanTimestampQueries' of https://github.com/ifromstone/FlaxEngine into ifromstone-dev/MacOSVulkanTimestampQueries 2026-05-27 14:39:52 +02:00
mafiesto4 3140c711a4 Simplify apple platform include defines 2026-05-27 14:39:45 +02:00
mafiesto4 848e9c92aa Merge branch 'ifromstone-dev/MacOSRiderSearch' 2026-05-27 14:28:09 +02:00
mafiesto4 6dad8259d6 Merge branch 'dev/MacOSRiderSearch' of https://github.com/ifromstone/FlaxEngine into ifromstone-dev/MacOSRiderSearch 2026-05-27 14:28:05 +02:00
mafiesto4 9f72c465fe Merge branch 'GoaLitiuM-winsdk_28000' 2026-05-27 14:27:39 +02:00
Andrei Gagua 285762bfdb Fix Vulkan Tracy timestamp queries on MoltenVK
Disable Vulkan Tracy GPU profiling when Vulkan timer queries are disabled.
Apple platforms already set VULKAN_USE_TIMER_QUERIES to 0
2026-05-24 11:13:41 +03:00
Andrei Gagua 571821bf3d Fix Rider detection on macOS when installed in the user Applications folder 2026-05-23 21:09:37 +03:00
Saas 96a081bf93 use mouse wrapping and rename mouse wrapping function parameter 2026-05-21 21:43:26 +02:00
Saas 637f3dc176 make code a bit nicer 2026-05-21 00:26:45 +02:00
Saas 5010597bd2 add being able to rotate view with direction gizmo 2026-05-21 00:20:52 +02:00
Ari Vuollet 4d4d1a589f Fix mouse position in events reported from caption area on Windows 2026-05-19 23:46:32 +03:00
Ari Vuollet 1176dc515d Fix window decoration buttons not clickable with DPI scale 2026-05-19 23:44:46 +03:00
Ari Vuollet 96d670efb7 Add support for Windows 11 SDK version 28000 2026-05-19 22:49:58 +03:00
mafiesto4 00e4b09e7e Migrate to a new LFS server
Build Android / Game (Android, Release ARM64) (push) Has been cancelled
Build iOS / Game (iOS, Release ARM64) (push) Has been cancelled
Build Linux / Editor (Linux, Development x64) (push) Has been cancelled
Build Linux / Game (Linux, Release x64) (push) Has been cancelled
Build macOS / Editor (Mac, Development ARM64) (push) Has been cancelled
Build macOS / Game (Mac, Release ARM64) (push) Has been cancelled
Build Windows / Editor (Windows, Development x64) (push) Has been cancelled
Build Windows / Game (Windows, Release x64) (push) Has been cancelled
Cooker / Cook (Mac) (push) Has been cancelled
Tests / Tests (Linux) (push) Has been cancelled
Tests / Tests (Windows) (push) Has been cancelled
2026-05-17 11:53:08 +02:00
mafiesto4 ec73cc6b0d Merge branch '1.12' 2026-05-14 15:32:53 +02:00
mafiesto4 3542f20787 Merge branch '1.12' 2026-05-13 18:17:53 +02:00
mafiesto4 43511a96b0 Fix Editor build in Release 2026-05-13 17:06:06 +02:00
mafiesto4 97bcdacd9a Fix minor doc issues 2026-05-13 15:43:44 +02:00
mafiesto4 253442abd1 Add filtering whitespaces to Xml doc comments of type members and optimize filtering 2026-05-13 10:58:50 +02:00
mafiesto4 c8912ad100 Add help support to Debug Commands via XML docs parsing 2026-05-13 10:47:21 +02:00
mafiesto4 40413edbab Fix property get/set function name length in dotnet bindings 2026-05-13 10:43:03 +02:00
mafiesto4 c10cfc8e45 Fix output log history popup management to smoother usage 2026-05-13 10:42:56 +02:00
mafiesto4 5739c0bef4 Fix assertion on shader load failure
#2702
2026-05-12 22:53:29 +02:00
mafiesto4 4f97225c46 Merge branch 'GoaLitiuM-xmldoc_optimizations' 2026-05-12 22:45:27 +02:00
mafiesto4 2c8da4ea04 Merge branch 'xmldoc_optimizations' of https://github.com/GoaLitiuM/FlaxEngine into GoaLitiuM-xmldoc_optimizations 2026-05-12 22:32:28 +02:00
mafiesto4 d697bd7402 Update old API usage
#4080 #4081
2026-05-12 18:40:21 +02:00
mafiesto4 0bcc01c3c7 Merge branch 'Tryibion-blackboard-utils' 2026-05-12 18:25:03 +02:00
mafiesto4 de76d3623e Fix BehaviorKnowledge of object type casting
#4072
2026-05-12 18:24:59 +02:00
mafiesto4 33caae6935 Merge branch 'blackboard-utils' of https://github.com/Tryibion/FlaxEngine into Tryibion-blackboard-utils 2026-05-12 18:22:30 +02:00
mafiesto4 75d9e36bd2 Merge branch 'Duroxxigar-change-lookingatdirection-name' 2026-05-12 18:21:34 +02:00
mafiesto4 72f2c8f5cd Merge branch 'change-lookingatdirection-name' of https://github.com/Duroxxigar/FlaxEngine into Duroxxigar-change-lookingatdirection-name 2026-05-12 18:21:31 +02:00
mafiesto4 9cfb3dd220 Merge branch 'Duroxxigar-change-direction-naming' 2026-05-12 18:20:59 +02:00
mafiesto4 62d6658444 Merge branch 'change-direction-naming' of https://github.com/Duroxxigar/FlaxEngine into Duroxxigar-change-direction-naming 2026-05-12 18:20:51 +02:00
mafiesto4 66818802b1 Merge branch 'Tryibion-gameplayglobal-getval' 2026-05-12 18:20:27 +02:00
mafiesto4 cc4fdf5cc2 Merge branch 'gameplayglobal-getval' of https://github.com/Tryibion/FlaxEngine into Tryibion-gameplayglobal-getval 2026-05-12 18:20:24 +02:00
mafiesto4 fde40949b2 Merge branch 'ThePhantomMask-AddVideoPlayerStatus' 2026-05-12 18:19:52 +02:00
mafiesto4 65c1a8258e Merge branch 'AddVideoPlayerStatus' of https://github.com/ThePhantomMask/FlaxEngine into ThePhantomMask-AddVideoPlayerStatus 2026-05-12 18:19:49 +02:00
mafiesto4 6e89f152ef Merge branch 'xxSeys1-ViewportZoomInOutFOV' 2026-05-12 18:19:17 +02:00
mafiesto4 e71f43ea79 Fix additional FOV to be included in Direction Gizmo
#4016
2026-05-12 18:19:10 +02:00
mafiesto4 d33ebc3105 Merge branch 'ViewportZoomInOutFOV' of https://github.com/xxSeys1/FlaxEngine into xxSeys1-ViewportZoomInOutFOV 2026-05-12 18:15:00 +02:00
mafiesto4 0de61ba218 Merge branch 'xxSeys1-SphereMaskNames' 2026-05-12 18:13:59 +02:00
mafiesto4 283a3e6bf9 Merge branch 'SphereMaskNames' of https://github.com/xxSeys1/FlaxEngine into xxSeys1-SphereMaskNames 2026-05-12 18:13:50 +02:00
Saas 8281e743cd rename Sphere Mask node A and B inputs 2026-05-12 14:05:04 +02:00
Jake Young ba48b2e4f3 Deprecate the Direction properties and replace with forward. Also give more descriptive description of what they do. 2026-05-08 13:48:28 -04:00
Jake Young 64708a14d9 Update LookingAt to be a more descriptive name. 2026-05-08 13:47:59 -04:00
Tryibion af3836d611 Add GetValue by type in c# for gameplay globals 2026-05-07 18:04:50 -05:00
Phantom a421effd1b Add Video playback value 2026-05-07 20:53:35 +02:00
Jake Young 811bf0d630 Change the default for showing debug draw to be on by default. 2026-05-07 01:56:09 -04:00
Tryibion 94e529e801 Add way to get blackboard by type. 2026-05-01 16:27:15 -05:00
Saas a7c9eff959 add zooming in and out (FOV) in editor viewport using C and Z 2026-03-21 14:49:34 +01:00
Saas a831e15bf7 fix rare bug where tracks pannel wouldn't scroll all the way through 2026-03-19 20:47:34 +01:00
Saas d77e1e9a53 wrap GetShowDebugDrawOptions() with USE_EDITOR preprocessor 2026-03-19 18:25:00 +01:00
Saas 699fb12604 improve Properties Panel layout for SkinnedModel 2026-03-19 17:35:57 +01:00
Saas 9505be310f fix spelling in ShowBitDepth 2026-03-19 17:34:01 +01:00
Saas 503a0e6763 add slider to adjust skeleton name font size 2026-03-19 17:33:45 +01:00
Saas 65b35a4b8a auto scroll to new track when adding track to timeline 2026-03-18 21:40:22 +01:00
Ari Vuollet c2e8e492d7 Defer loading XML documentation during while loading the editor 2025-05-02 14:03:53 +03:00
Ari Vuollet c916fb1844 Avoid clearing cached XML documentation data for Editor assembly 2025-05-02 14:03:52 +03:00
Ari Vuollet 004339b81e Improve XML documentation reference parsing and overall performance 2025-05-02 14:03:52 +03:00
128 changed files with 2010 additions and 902 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
# Redirect to our own Git LFS server
[lfs]
url="https://gitlab.flaxengine.com/flax/flaxengine.git/info/lfs"
url="https://git.flaxengine.com/flax/flaxengine.git/info/lfs"
locksverify = false
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -4,7 +4,7 @@
"Major": 1,
"Minor": 12,
"Revision": 0,
"Build": 6912
"Build": 6914
},
"Company": "Flax",
"Copyright": "Copyright (c) 2012-2026 Wojciech Figat. All rights reserved.",
@@ -12,7 +12,7 @@ namespace FlaxEngine.Tools
{
partial struct Options
{
private bool ShowBtiDepth => Format != AudioFormat.Vorbis;
private bool ShowBitDepth => Format != AudioFormat.Vorbis;
}
}
}
@@ -19,6 +19,7 @@ namespace FlaxEngine.Tools
private bool ShowRootMotion => ShowAnimation && RootMotion != RootMotionMode.None;
private bool ShowSmoothingNormalsAngle => ShowGeometry && CalculateNormals;
private bool ShowSmoothingTangentsAngle => ShowGeometry && CalculateTangents;
private bool ShowGenerateLODs => ShowGeometry && GenerateLODs;
private bool ShowFramesRange => ShowAnimation && Duration == AnimationDuration.Custom;
private bool ShowSplitting => Type != ModelType.Prefab;
}
+2 -2
View File
@@ -98,12 +98,12 @@ namespace FlaxEditor.Content
}
/// <summary>
/// Reloads the asset (if it's loaded).
/// Reloads the asset (if it's loaded or failed to load).
/// </summary>
public void Reload()
{
var asset = FlaxEngine.Content.GetAsset(ID);
if (asset != null && asset.IsLoaded)
if (asset != null && (asset.IsLoaded || asset.LastLoadFailed))
{
asset.Reload();
}
@@ -0,0 +1,109 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using FlaxEditor.Content.Thumbnails;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Viewport.Previews;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Content
{
/// <summary>
/// A base class for <see cref="MaterialBase"/> asset proxy object.
/// </summary>
/// <seealso cref="FlaxEditor.Content.BinaryAssetProxy" />
public abstract class MaterialBaseProxy : BinaryAssetProxy
{
/// <summary>
/// The material preview drawer.
/// </summary>
protected MaterialPreview _preview;
/// <inheritdoc />
public override bool CanCreate(ContentFolder targetLocation)
{
return targetLocation.CanHaveAssets;
}
/// <inheritdoc />
public override void OnContentWindowContextMenu(ContextMenu menu, ContentItem item)
{
base.OnContentWindowContextMenu(menu, item);
if (item is BinaryAssetItem binaryAssetItem)
{
var button = menu.AddButton("Create Material Instance", CreateMaterialInstanceClicked);
button.Tag = binaryAssetItem;
}
}
private void CreateMaterialInstanceClicked(ContextMenuButton button)
{
var binaryAssetItem = (BinaryAssetItem)button.Tag;
CreateMaterialInstance(binaryAssetItem);
}
/// <summary>
/// Creates the material instance from the given material.
/// </summary>
/// <param name="materialItem">The material item to use as a base material.</param>
public static void CreateMaterialInstance(BinaryAssetItem materialItem)
{
var materialInstanceName = materialItem.ShortName + " Instance";
var materialInstanceProxy = Editor.Instance.ContentDatabase.GetProxy<MaterialInstance>();
Editor.Instance.Windows.ContentWin.NewItem(materialInstanceProxy, null, item => OnMaterialInstanceCreated(item, materialItem), materialInstanceName);
}
private static void OnMaterialInstanceCreated(ContentItem item, BinaryAssetItem materialItem)
{
var assetItem = (AssetItem)item;
var materialInstance = FlaxEngine.Content.LoadAsync<MaterialInstance>(assetItem.ID);
if (materialInstance == null || materialInstance.WaitForLoaded())
{
Editor.LogError("Failed to load created material instance.");
return;
}
materialInstance.BaseMaterial = FlaxEngine.Content.LoadAsync<MaterialBase>(materialItem.ID);
materialInstance.Save();
}
/// <inheritdoc />
public override void OnThumbnailDrawPrepare(ThumbnailRequest request)
{
if (_preview == null)
{
_preview = new MaterialPreview(false);
InitAssetPreview(_preview);
}
}
/// <inheritdoc />
public override void OnThumbnailDrawBegin(ThumbnailRequest request, ContainerControl guiRoot, GPUContext context)
{
_preview.Material = (MaterialBase)request.Asset;
_preview.Parent = guiRoot;
_preview.SyncBackbufferSize();
_preview.Task.OnDraw();
}
/// <inheritdoc />
public override void OnThumbnailDrawEnd(ThumbnailRequest request, ContainerControl guiRoot)
{
_preview.Material = null;
_preview.Parent = null;
}
/// <inheritdoc />
public override void Dispose()
{
if (_preview != null)
{
_preview.Dispose();
_preview = null;
}
base.Dispose();
}
}
}
@@ -2,23 +2,18 @@
using System;
using FlaxEditor.Content.Thumbnails;
using FlaxEditor.Viewport.Previews;
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Content
{
/// <summary>
/// A <see cref="MaterialInstance"/> asset proxy object.
/// </summary>
/// <seealso cref="FlaxEditor.Content.BinaryAssetProxy" />
[ContentContextMenu("New/Material/Material Instance")]
public class MaterialInstanceProxy : BinaryAssetProxy
public class MaterialInstanceProxy : MaterialBaseProxy
{
private MaterialPreview _preview;
/// <inheritdoc />
public override string Name => "Material Instance";
@@ -34,12 +29,6 @@ namespace FlaxEditor.Content
/// <inheritdoc />
public override Type AssetType => typeof(MaterialInstance);
/// <inheritdoc />
public override bool CanCreate(ContentFolder targetLocation)
{
return targetLocation.CanHaveAssets;
}
/// <inheritdoc />
public override void Create(string outputPath, object arg)
{
@@ -47,49 +36,10 @@ namespace FlaxEditor.Content
throw new Exception("Failed to create new asset.");
}
/// <inheritdoc />
public override void OnThumbnailDrawPrepare(ThumbnailRequest request)
{
if (_preview == null)
{
_preview = new MaterialPreview(false);
InitAssetPreview(_preview);
}
}
/// <inheritdoc />
public override bool CanDrawThumbnail(ThumbnailRequest request)
{
return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((MaterialInstance)request.Asset);
}
/// <inheritdoc />
public override void OnThumbnailDrawBegin(ThumbnailRequest request, ContainerControl guiRoot, GPUContext context)
{
_preview.Material = (MaterialInstance)request.Asset;
_preview.Parent = guiRoot;
_preview.SyncBackbufferSize();
_preview.Task.OnDraw();
}
/// <inheritdoc />
public override void OnThumbnailDrawEnd(ThumbnailRequest request, ContainerControl guiRoot)
{
_preview.Material = null;
_preview.Parent = null;
}
/// <inheritdoc />
public override void Dispose()
{
if (_preview != null)
{
_preview.Dispose();
_preview = null;
}
base.Dispose();
}
}
}
+1 -95
View File
@@ -2,24 +2,18 @@
using System;
using FlaxEditor.Content.Thumbnails;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Viewport.Previews;
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Content
{
/// <summary>
/// A <see cref="Material"/> asset proxy object.
/// </summary>
/// <seealso cref="FlaxEditor.Content.BinaryAssetProxy" />
[ContentContextMenu("New/Material/Material")]
public class MaterialProxy : BinaryAssetProxy
public class MaterialProxy : MaterialBaseProxy
{
private MaterialPreview _preview;
/// <inheritdoc />
public override string Name => "Material";
@@ -35,12 +29,6 @@ namespace FlaxEditor.Content
/// <inheritdoc />
public override Type AssetType => typeof(Material);
/// <inheritdoc />
public override bool CanCreate(ContentFolder targetLocation)
{
return targetLocation.CanHaveAssets;
}
/// <inheritdoc />
public override void Create(string outputPath, object arg)
{
@@ -48,92 +36,10 @@ namespace FlaxEditor.Content
throw new Exception("Failed to create new asset.");
}
/// <inheritdoc />
public override void OnContentWindowContextMenu(ContextMenu menu, ContentItem item)
{
base.OnContentWindowContextMenu(menu, item);
if (item is BinaryAssetItem binaryAssetItem)
{
var button = menu.AddButton("Create Material Instance", CreateMaterialInstanceClicked);
button.Tag = binaryAssetItem;
}
}
private void CreateMaterialInstanceClicked(ContextMenuButton obj)
{
var binaryAssetItem = (BinaryAssetItem)obj.Tag;
CreateMaterialInstance(binaryAssetItem);
}
/// <summary>
/// Creates the material instance from the given material.
/// </summary>
/// <param name="materialItem">The material item to use as a base material.</param>
public static void CreateMaterialInstance(BinaryAssetItem materialItem)
{
var materialInstanceName = materialItem.ShortName + " Instance";
var materialInstanceProxy = Editor.Instance.ContentDatabase.GetProxy<MaterialInstance>();
Editor.Instance.Windows.ContentWin.NewItem(materialInstanceProxy, null, item => OnMaterialInstanceCreated(item, materialItem), materialInstanceName);
}
private static void OnMaterialInstanceCreated(ContentItem item, BinaryAssetItem materialItem)
{
var assetItem = (AssetItem)item;
var materialInstance = FlaxEngine.Content.LoadAsync<MaterialInstance>(assetItem.ID);
if (materialInstance == null || materialInstance.WaitForLoaded())
{
Editor.LogError("Failed to load created material instance.");
return;
}
materialInstance.BaseMaterial = FlaxEngine.Content.LoadAsync<Material>(materialItem.ID);
materialInstance.Save();
}
/// <inheritdoc />
public override void OnThumbnailDrawPrepare(ThumbnailRequest request)
{
if (_preview == null)
{
_preview = new MaterialPreview(false);
InitAssetPreview(_preview);
}
}
/// <inheritdoc />
public override bool CanDrawThumbnail(ThumbnailRequest request)
{
return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((Material)request.Asset);
}
/// <inheritdoc />
public override void OnThumbnailDrawBegin(ThumbnailRequest request, ContainerControl guiRoot, GPUContext context)
{
_preview.Material = (Material)request.Asset;
_preview.Parent = guiRoot;
_preview.SyncBackbufferSize();
_preview.Task.OnDraw();
}
/// <inheritdoc />
public override void OnThumbnailDrawEnd(ThumbnailRequest request, ContainerControl guiRoot)
{
_preview.Material = null;
_preview.Parent = null;
}
/// <inheritdoc />
public override void Dispose()
{
if (_preview != null)
{
_preview.Dispose();
_preview = null;
}
base.Dispose();
}
}
}
@@ -191,7 +191,7 @@ bool WebPlatformTools::OnPostProcess(CookingData& data)
FileSystem::GetChildDirectories(pythons, emscriptenSdk / TEXT("/python"));
if (pythons.HasItems())
{
procSettings.Arguments = procSettings.FileName + TEXT(".py ") + procSettings.Arguments;
procSettings.Arguments = String::Format(TEXT("\"{}.py\" {}"), procSettings.FileName, procSettings.Arguments);
#if PLATFORM_WINDOWS
procSettings.FileName = pythons[0] / TEXT("/python.exe");
#else
@@ -343,6 +343,7 @@ namespace FlaxEditor.CustomEditors
}
private bool _buildOnUpdate;
private bool _initialized;
private bool _readOnly;
/// <summary>
@@ -430,6 +431,7 @@ namespace FlaxEditor.CustomEditors
ClearLayout();
_buildOnUpdate = false;
_initialized = true;
Editor.Setup(this);
Panel.IsLayoutLocked = false;
@@ -506,7 +508,11 @@ namespace FlaxEditor.CustomEditors
/// </summary>
protected virtual void OnSelectionChanged()
{
BuildLayout();
// Defer building the layout after we have initialized to improve initial loading times
if (!_initialized)
_buildOnUpdate = true;
else
BuildLayout();
SelectionChanged?.Invoke();
}
@@ -355,7 +355,7 @@ namespace FlaxEditor.GUI.ContextMenu
if (_previouslyFocused != null)
{
_previouslyFocused.RootWindow?.Focus();
_previouslyFocused.Focus();
_previouslyFocused?.Focus();
_previouslyFocused = null;
}
@@ -589,6 +589,8 @@ namespace FlaxEditor.GUI
// Get the next item
bool controlDown = Root.GetKey(KeyboardKeys.Control);
var items = GetVisibleItems(!controlDown);
if (items.Count == 0)
return true;
var focusedIndex = items.IndexOf(focusedItem);
int delta = key == KeyboardKeys.ArrowDown ? -1 : 1;
+22 -6
View File
@@ -229,6 +229,7 @@ namespace FlaxEditor.GUI.Timeline
private List<Track> _mediaMoveStartTracks;
private byte[][] _mediaMoveStartData;
private float _zoom = 1.0f;
private float _tracksVScrollTarget;
private bool _isMovingPositionHandle;
private bool _canPlayPause = true, _canStop = true;
private List<IUndoAction> _batchedUndoActions;
@@ -1301,10 +1302,13 @@ namespace FlaxEditor.GUI.Timeline
if (track.ParentTrack != null)
OnTracksOrderChanged();
track.OnSpawned();
_tracksPanelArea.ScrollViewTo(track);
MarkAsEdited();
if (withUndo)
Undo?.AddAction(new AddRemoveTrackAction(this, track, true));
// Scroll to track
_tracksPanelArea.ScrollViewTo(track);
_tracksVScrollTarget = _tracksPanelArea.VScrollBar.TargetValue;
}
/// <summary>
@@ -2033,12 +2037,24 @@ namespace FlaxEditor.GUI.Timeline
base.Update(deltaTime);
// Synchronize scroll vertical bars for tracks and media panels to keep the view in sync
var scroll1 = _tracksPanelArea.VScrollBar;
var scroll2 = _backgroundArea.VScrollBar;
if (scroll1.IsThumbClicked || _tracksPanelArea.IsMouseOver)
scroll2.TargetValue = scroll1.Value;
var tracksVScroll = _tracksPanelArea.VScrollBar;
var backgroundVScroll = _backgroundArea.VScrollBar;
bool forceBackgroundToTracksScroll = _tracksVScrollTarget > 0;
if (forceBackgroundToTracksScroll)
{
backgroundVScroll.TargetValue = tracksVScroll.Value;
if (Mathf.Abs(tracksVScroll.Value - _tracksVScrollTarget) < 0.5f)
_tracksVScrollTarget = 0f;
}
else if (tracksVScroll.IsThumbClicked || _tracksPanelArea.IsMouseOver)
{
backgroundVScroll.TargetValue = tracksVScroll.Value;
}
else
scroll1.TargetValue = scroll2.Value;
{
tracksVScroll.TargetValue = backgroundVScroll.Value;
}
// Batch undo actions
if (_batchedUndoActions != null && _batchedUndoActions.Count != 0)
+2 -1
View File
@@ -181,7 +181,7 @@ public class WindowDecorations : ContainerControl
return WindowHitCodes.NoWhere;
var dpiScale = _window.DpiScale;
var pos = _window.ScreenToClient(mouse * dpiScale); // pos is not DPI adjusted
var pos = _window.ScreenToClient(mouse * dpiScale); // pos is DPI adjusted in window space
if (!_window.IsMaximized)
{
var winSize = _window.Size;
@@ -214,6 +214,7 @@ public class WindowDecorations : ContainerControl
return WindowHitCodes.Bottom;
}
pos /= dpiScale; // The position should not be DPI adjusted in control space
var controlUnderMouse = GetChildAt(pos, control => control != _title);
if (_title.Bounds.Contains(pos) && controlUnderMouse == null)
return WindowHitCodes.Caption;
+52 -4
View File
@@ -36,8 +36,10 @@ internal class DirectionGizmo : ContainerControl
private List<AxisData> _axisData = new List<AxisData>();
private int _hoveredAxisIndex = -1;
private bool _mouseDown;
private Float2 _mouseDownLocation;
private SpriteHandle _posHandle;
private SpriteHandle _negHandle;
private FontReference _fontReference;
@@ -110,7 +112,6 @@ internal class DirectionGizmo : ContainerControl
var editor = Editor.Instance;
_posHandle = editor.Icons.VisjectBoxClosed32;
_negHandle = editor.Icons.VisjectBoxOpen32;
_fontReference = new FontReference(Style.Current.FontSmall);
@@ -142,7 +143,25 @@ internal class DirectionGizmo : ContainerControl
public override void OnMouseMove(Float2 location)
{
_hoveredAxisIndex = -1;
if (_mouseDown)
{
StartMouseCapture(true);
Cursor = CursorType.Hidden;
const float sensitivity = 0.125f;
Float2 delta = Input.MousePositionDelta;
delta *= Mathf.DegreesToRadians;
delta *= sensitivity;
const float orbitRadius = 500f;
Quaternion newOrientation = _viewport.ViewOrientation * Quaternion.RotationYawPitchRoll(delta.X , delta.Y, 0f);
Vector3 orbitCenter = _viewport.ViewPosition + _viewport.ViewDirection * orbitRadius;
_viewport.ViewportCamera.SetArcBallView(newOrientation, orbitCenter, orbitRadius);
return;
}
// Check which axis is being hovered - check from closest to farthest for proper layering
for (int i = _spritePositions.Count - 1; i >= 0; i--)
{
@@ -156,9 +175,30 @@ internal class DirectionGizmo : ContainerControl
base.OnMouseMove(location);
}
public override bool OnMouseDown(Float2 location, MouseButton button)
{
_mouseDown = true;
_mouseDownLocation = location;
return true;
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (_mouseDown)
{
_mouseDown = false;
if (_mouseDownLocation != location)
{
_mouseDown = false;
EndMouseCapture();
Root.MousePosition = PointToParent(Root, _mouseDownLocation);
Cursor = CursorType.Default;
return true;
}
}
if (base.OnMouseUp(location, button))
return true;
@@ -222,7 +262,10 @@ internal class DirectionGizmo : ContainerControl
else
{
// This could be some actual math expression, not that hack
var fov = _owner.Viewport.FieldOfView / 60.0f;
float fov = _owner.Viewport.FieldOfView;
if (_owner.Viewport.ViewportCamera is Viewport.Cameras.FPSCamera fpsCam)
fov += fpsCam.AdditionalZoomFOV;
fov /= 60.0f;
float scaleAt30 = 0.1f, scaleAt60 = 1.0f, scaleAt120 = 1.5f, scaleAt180 = 3.0f;
heightNormalization /= Mathf.Lerp(scaleAt30, scaleAt60, fov);
heightNormalization /= Mathf.Lerp(scaleAt60, scaleAt120, Mathf.Saturate(fov - 1));
@@ -266,7 +309,12 @@ internal class DirectionGizmo : ContainerControl
// Rebuild sprite positions list for hover detection
_spritePositions.Clear();
Render2D.DrawSprite(_posHandle, new Rectangle(0, 0, Size), Color.Black.AlphaMultiplied(_backgroundOpacity));
if (IsMouseOver)
{
Rectangle backgroundRect = new Rectangle(0, 0, Size);
Color backgroundColor = Color.DarkGray.AlphaMultiplied(_backgroundOpacity);
Render2D.DrawSprite(_posHandle, backgroundRect, backgroundColor);
}
// Draw in order from farthest to closest
for (int i = 0; i < _axisData.Count; i++)
@@ -2,12 +2,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using FlaxEditor.Scripting;
using FlaxEngine;
@@ -21,7 +17,6 @@ namespace FlaxEditor.Modules.SourceCodeEditing
{
private Dictionary<ScriptType, string> _typeCache = new Dictionary<ScriptType, string>();
private Dictionary<ScriptMemberInfo, string> _memberCache = new Dictionary<ScriptMemberInfo, string>();
private Dictionary<Assembly, Dictionary<string, string>> _xmlCache = new Dictionary<Assembly, Dictionary<string, string>>();
internal CodeDocsModule(Editor editor)
: base(editor)
@@ -61,13 +56,9 @@ namespace FlaxEditor.Modules.SourceCodeEditing
else if (type.Type != null)
{
// Try to use xml docs for managed type
var xml = GetXmlDocs(type.Type.Assembly);
if (xml != null)
{
var key = "T:" + GetXmlKey(type.Type.FullName);
if (xml.TryGetValue(key, out var xmlDoc))
text += '\n' + FilterWhitespaces(xmlDoc);
}
var xmlDoc = DebugCommands.GetXml(type.Type);
if (xmlDoc != null)
text += '\n' + xmlDoc;
}
_typeCache.Add(type, text);
@@ -108,242 +99,20 @@ namespace FlaxEditor.Modules.SourceCodeEditing
else if (member.Type != null)
{
// Try to use xml docs for managed member
var memberInfo = member.Type;
var xml = GetXmlDocs(memberInfo.DeclaringType.Assembly);
if (xml != null)
{
// [Reference: MSDN Magazine, October 2019, Volume 34 Number 10, "Accessing XML Documentation via Reflection"]
// https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/october/csharp-accessing-xml-documentation-via-reflection
var memberType = memberInfo.MemberType;
string key = null;
if (memberType.HasFlag(MemberTypes.Field))
{
var fieldInfo = (FieldInfo)memberInfo;
key = "F:" + GetXmlKey(fieldInfo.DeclaringType.FullName) + "." + fieldInfo.Name;
}
else if (memberType.HasFlag(MemberTypes.Property))
{
var propertyInfo = (PropertyInfo)memberInfo;
key = "P:" + GetXmlKey(propertyInfo.DeclaringType.FullName) + "." + propertyInfo.Name;
}
else if (memberType.HasFlag(MemberTypes.Event))
{
var eventInfo = (EventInfo)memberInfo;
key = "E:" + GetXmlKey(eventInfo.DeclaringType.FullName) + "." + eventInfo.Name;
}
else if (memberType.HasFlag(MemberTypes.Constructor))
{
var constructorInfo = (ConstructorInfo)memberInfo;
key = GetXmlKey(constructorInfo);
}
else if (memberType.HasFlag(MemberTypes.Method))
{
var methodInfo = (MethodInfo)memberInfo;
key = GetXmlKey(methodInfo);
}
else if (memberType.HasFlag(MemberTypes.TypeInfo) || memberType.HasFlag(MemberTypes.NestedType))
{
var typeInfo = (TypeInfo)memberInfo;
key = "T:" + GetXmlKey(typeInfo.FullName);
}
if (key != null)
xml.TryGetValue(key, out text);
// Customize tooltips for properties to be more human-readable in UI
if (text != null && memberType.HasFlag(MemberTypes.Property) && text.StartsWith("Gets or sets ", StringComparison.Ordinal))
{
text = text.Substring(13);
unsafe
{
fixed (char* e = text)
e[0] = char.ToUpper(e[0]);
}
}
}
var xmlDoc = DebugCommands.GetXml(member.Type);
if (xmlDoc != null)
text = xmlDoc;
}
_memberCache.Add(member, text);
return text;
}
// [Reference: MSDN Magazine, October 2019, Volume 34 Number 10, "Accessing XML Documentation via Reflection"]
// https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/october/csharp-accessing-xml-documentation-via-reflection
private string GetXmlKey(MethodInfo methodInfo)
{
var typeGenericMap = new Dictionary<string, int>();
var methodGenericMap = new Dictionary<string, int>();
ParameterInfo[] parameterInfos = methodInfo.GetParameters();
if (methodInfo.DeclaringType.IsGenericType)
{
var methods = methodInfo.DeclaringType.GetGenericTypeDefinition().GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
methodInfo = methods.First(x => x.MetadataToken == methodInfo.MetadataToken);
}
Type[] typeGenericArguments = methodInfo.DeclaringType.GetGenericArguments();
for (int i = 0; i < typeGenericArguments.Length; i++)
{
Type typeGeneric = typeGenericArguments[i];
typeGenericMap[typeGeneric.Name] = i;
}
Type[] methodGenericArguments = methodInfo.GetGenericArguments();
for (int i = 0; i < methodGenericArguments.Length; i++)
{
Type methodGeneric = methodGenericArguments[i];
methodGenericMap[methodGeneric.Name] = i;
}
string declarationTypeString = GetXmlKey(methodInfo.DeclaringType, false, typeGenericMap, methodGenericMap);
string memberNameString = methodInfo.Name;
string methodGenericArgumentsString = methodGenericMap.Count > 0 ? "``" + methodGenericMap.Count : string.Empty;
string parametersString = parameterInfos.Length > 0 ? "(" + string.Join(",", methodInfo.GetParameters().Select(x => GetXmlKey(x.ParameterType, true, typeGenericMap, methodGenericMap))) + ")" : string.Empty;
string key = "M:" + declarationTypeString + "." + memberNameString + methodGenericArgumentsString + parametersString;
if (methodInfo.Name is "op_Implicit" || methodInfo.Name is "op_Explicit")
{
key += "~" + GetXmlKey(methodInfo.ReturnType, true, typeGenericMap, methodGenericMap);
}
return key;
}
private string GetXmlKey(ConstructorInfo constructorInfo)
{
var typeGenericMap = new Dictionary<string, int>();
var methodGenericMap = new Dictionary<string, int>();
ParameterInfo[] parameterInfos = constructorInfo.GetParameters();
Type[] typeGenericArguments = constructorInfo.DeclaringType.GetGenericArguments();
for (int i = 0; i < typeGenericArguments.Length; i++)
{
Type typeGeneric = typeGenericArguments[i];
typeGenericMap[typeGeneric.Name] = i;
}
string declarationTypeString = GetXmlKey(constructorInfo.DeclaringType, false, typeGenericMap, methodGenericMap);
string methodGenericArgumentsString = methodGenericMap.Count > 0 ? "``" + methodGenericMap.Count : string.Empty;
string parametersString = parameterInfos.Length > 0 ? "(" + string.Join(",", constructorInfo.GetParameters().Select(x => GetXmlKey(x.ParameterType, true, typeGenericMap, methodGenericMap))) + ")" : string.Empty;
return "M:" + declarationTypeString + "." + "#ctor" + methodGenericArgumentsString + parametersString;
}
internal static string GetXmlKey(Type type, bool isMethodParameter, Dictionary<string, int> typeGenericMap, Dictionary<string, int> methodGenericMap)
{
if (type.IsGenericParameter)
{
if (methodGenericMap.TryGetValue(type.Name, out var methodIndex))
return "``" + methodIndex;
if (typeGenericMap.TryGetValue(type.Name, out var typeKey))
return "`" + typeKey;
return "`";
}
if (type.HasElementType)
{
string elementTypeString = GetXmlKey(type.GetElementType(), isMethodParameter, typeGenericMap, methodGenericMap);
if (type.IsPointer)
return elementTypeString + "*";
if (type.IsByRef)
return elementTypeString + "@";
if (type.IsArray)
{
int rank = type.GetArrayRank();
string arrayDimensionsString = rank > 1 ? "[" + string.Join(",", Enumerable.Repeat("0:", rank)) + "]" : "[]";
return elementTypeString + arrayDimensionsString;
}
throw new Exception();
}
string prefaceString = type.IsNested ? GetXmlKey(type.DeclaringType, isMethodParameter, typeGenericMap, methodGenericMap) + "." : type.Namespace + ".";
string typeNameString = isMethodParameter ? Regex.Replace(type.Name, @"`\d+", string.Empty) : type.Name;
string genericArgumentsString = type.IsGenericType && isMethodParameter ? "{" + string.Join(",", type.GetGenericArguments().Select(argument => GetXmlKey(argument, true, typeGenericMap, methodGenericMap))) + "}" : string.Empty;
return prefaceString + typeNameString + genericArgumentsString;
}
private static string GetXmlKey(string typeFullNameString)
{
return Regex.Replace(typeFullNameString, @"\[.*\]", string.Empty).Replace('+', '.');
}
private static string FilterWhitespaces(string str)
{
if (str.Contains(" ", StringComparison.Ordinal))
{
var sb = new StringBuilder();
var prev = str[0];
sb.Append(prev);
for (int i = 1; i < str.Length; i++)
{
var c = str[i];
if (prev != ' ' || c != ' ')
{
sb.Append(c);
}
prev = c;
}
str = sb.ToString();
}
return str;
}
private Dictionary<string, string> GetXmlDocs(Assembly assembly)
{
if (!_xmlCache.TryGetValue(assembly, out var result))
{
Profiler.BeginEvent("GetXmlDocs");
var assemblyPath = Utils.GetAssemblyLocation(assembly);
var assemblyName = assembly.GetName().Name;
var xmlFilePath = Path.ChangeExtension(assemblyPath, ".xml");
if (!File.Exists(assemblyPath) && !string.IsNullOrEmpty(assemblyPath))
{
var uri = new UriBuilder(assemblyPath);
var path = Uri.UnescapeDataString(uri.Path);
xmlFilePath = Path.Combine(Path.GetDirectoryName(path), assemblyName + ".xml");
}
if (File.Exists(xmlFilePath))
{
Profiler.BeginEvent(assemblyName);
try
{
// Parse xml documentation
using (var xmlReader = XmlReader.Create(new StreamReader(xmlFilePath)))
{
result = new Dictionary<string, string>();
while (xmlReader.Read())
{
if (xmlReader.NodeType == XmlNodeType.Element && string.Equals(xmlReader.Name, "member", StringComparison.Ordinal))
{
string rawName = xmlReader["name"];
var memberReader = xmlReader.ReadSubtree();
if (memberReader.ReadToDescendant("summary"))
{
// Remove <see cref=""/> and replace them with the captured group (the content of the cref). Additionally, getting rid of prefixes
const string crefPattern = @"<see\s+cref=""(?:[A-Z]:FlaxEngine\.)?([^""]+)""\s*\/>";
result[rawName] = Regex.Replace(memberReader.ReadInnerXml(), crefPattern, "$1").Replace('\n', ' ').Trim();
}
}
}
}
}
catch
{
// Ignore errors
}
Profiler.EndEvent();
}
_xmlCache[assembly] = result;
Profiler.EndEvent();
}
return result;
}
private void OnTypesCleared()
{
_typeCache.Clear();
_memberCache.Clear();
_xmlCache.Clear();
DebugCommands.ClearXml();
}
/// <inheritdoc />
+8
View File
@@ -347,6 +347,14 @@ namespace FlaxEditor.Options
[EditorDisplay("Viewport"), EditorOrder(1550)]
public InputBinding Down = new InputBinding(KeyboardKeys.Q);
[DefaultValue(typeof(InputBinding), "C")]
[EditorDisplay("Viewport"), EditorOrder(1551)]
public InputBinding ZoomIn = new InputBinding(KeyboardKeys.C);
[DefaultValue(typeof(InputBinding), "Z")]
[EditorDisplay("Viewport"), EditorOrder(1552)]
public InputBinding ZoomOut = new InputBinding(KeyboardKeys.Z);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("Viewport", "Toggle Camera Rotation"), EditorOrder(1560)]
public InputBinding CameraToggleRotation = new InputBinding(KeyboardKeys.None);
+1 -1
View File
@@ -187,7 +187,7 @@ namespace FlaxEditor.Options
public float DirectionGizmoScale { get; set; } = 1f;
/// <summary>
/// Gets or sets a value for the opacity of the main viewports <see cref="Gizmo.DirectionGizmo"/> background.
/// Gets or sets a value for the opacity of the main viewports <see cref="Gizmo.DirectionGizmo"/> background. Background will only show when the gizmo is hovered.
/// </summary>
[DefaultValue(0.1f), Limit(0.0f, 1.0f)]
[EditorDisplay("Direction Gizmo"), EditorOrder(502), Tooltip("The background opacity of the of the direction gizmo in the main viewport.")]
@@ -14,6 +14,9 @@
#if PLATFORM_WINDOWS
#include "Engine/Platform/Win32/IncludeWindowsHeaders.h"
#elif PLATFORM_MAC
#include "Engine/Platform/Apple/AppleUtils.h"
#include <AppKit/AppKit.h>
#endif
namespace
@@ -68,10 +71,14 @@ namespace
if (!launcherPath.HasChars() || !FileSystem::FileExists(exePath))
return;
if (launchOverridePath != String::Empty)
installations->Add(New<RiderInstallation>(launchOverridePath, versionMember->value.GetText()));
else
installations->Add(New<RiderInstallation>(exePath, versionMember->value.GetText()));
String installPath = launchOverridePath != String::Empty ? launchOverridePath : exePath;
StringUtils::PathRemoveRelativeParts(installPath);
for (RiderInstallation* installation : *installations)
{
if (installation->path == installPath)
return;
}
installations->Add(New<RiderInstallation>(installPath, versionMember->value.GetText()));
}
#if PLATFORM_WINDOWS
@@ -221,17 +228,29 @@ void RiderCodeEditor::FindEditors(Array<CodeEditor*>* output)
String applicationSupportFolder;
FileSystem::GetSpecialFolderPath(SpecialFolder::ProgramData, applicationSupportFolder);
NSURL* appURL = [[NSWorkspace sharedWorkspace] URLForApplicationWithBundleIdentifier:@"com.jetbrains.rider"];
if (appURL != nullptr)
{
const String appPath = AppleUtils::ToString((CFStringRef)[appURL path]);
SearchDirectory(&installations, appPath / TEXT("Contents/Resources"), appPath);
}
Array<String> 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);
String riderAppPath = directory / TEXT("Rider.app");
SearchDirectory(&installations, riderAppPath / TEXT("Contents/Resources"), riderAppPath);
}
// Check the local installer version
SearchDirectory(&installations, TEXT("/Applications/Rider.app/Contents/Resources"));
SearchDirectory(&installations, TEXT("/Applications/Rider.app/Contents/Resources"), TEXT("/Applications/Rider.app"));
String userFolder;
FileSystem::GetSpecialFolderPath(SpecialFolder::Documents, userFolder);
String riderAppPath = userFolder / TEXT("../Applications/Rider.app");
SearchDirectory(&installations, riderAppPath / TEXT("Contents/Resources"), riderAppPath);
#endif
for (const String& directory : subDirectories)
+2 -2
View File
@@ -807,8 +807,8 @@ namespace FlaxEditor.Surface.Archetypes
},
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "A", true, null, 0),
NodeElementArchetype.Factory.Input(1, "B", true, null, 1),
NodeElementArchetype.Factory.Input(0, "UV", true, null, 0),
NodeElementArchetype.Factory.Input(1, "Center", true, null, 1),
NodeElementArchetype.Factory.Input(2, "Radius", true, typeof(float), 2, 0),
NodeElementArchetype.Factory.Input(3, "Hardness", true, typeof(float), 3, 1),
NodeElementArchetype.Factory.Input(4, "Invert", true, typeof(bool), 4, 2),
+15 -5
View File
@@ -8,6 +8,7 @@ using FlaxEditor.Gizmo;
using FlaxEditor.GUI.Tabs;
using FlaxEditor.Modules;
using FlaxEditor.SceneGraph;
using FlaxEditor.Utilities;
using FlaxEditor.Viewport.Modes;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -307,7 +308,7 @@ namespace FlaxEditor.Tools
public VertexPaintingGizmo Gizmo;
public VertexColorsPreviewMode PreviewMode = VertexColorsPreviewMode.RGB;
public float PreviewVertexSize = 6.0f;
public float PreviewVertexSize = 4.0f;
public float BrushSize = 100.0f;
public float BrushStrength = 1.0f;
public float BrushFalloff = 1.0f;
@@ -402,7 +403,7 @@ namespace FlaxEditor.Tools
if (meshDatas == null)
throw new Exception("Missing mesh data of the model to paint.");
var instanceTransform = _selectedModel.Transform;
var brushSphere = new BoundingSphere(_hitLocation, _gizmoMode.BrushSize);
var brushSphere = new BoundingSphere(_hitLocation, _gizmoMode.BrushSize * 0.5f);
if (_paintUpdateCount == 0 && !_selectedModel.HasVertexColors)
{
// Initialize the instance vertex colors with originals from the asset
@@ -509,6 +510,13 @@ namespace FlaxEditor.Tools
return;
}
// Increase or decrease brush size with scroll
if (Input.GetKey(KeyboardKeys.Shift) && !Input.GetMouseButton(MouseButton.Right))
{
_gizmoMode.BrushSize += dt * _gizmoMode.BrushSize * Input.Mouse.ScrollDelta * 5f;
_gizmoMode.BrushSize = Mathf.Clamp(_gizmoMode.BrushSize, 0.0001f, 100000.0f);
}
// Perform detailed tracing to find cursor location for the brush
var ray = Owner.MouseRay;
var view = new Ray(Owner.ViewPosition, Owner.ViewDirection);
@@ -570,7 +578,7 @@ namespace FlaxEditor.Tools
}
if (_brushModel && _brushMaterial)
{
_brushMaterial.SetParameterValue("Color", new Color(1.0f, 0.85f, 0.0f)); // TODO: expose to editor options
_brushMaterial.SetParameterValue("Color", new Color(1.0f, 0.85f, 0.0f));
_brushMaterial.SetParameterValue("DepthBuffer", Owner.RenderTask.Buffers.DepthBuffer);
Quaternion rotation = RootNode.RaycastNormalRotation(ref _hitNormal);
Matrix transform = Matrix.Scaling(_gizmoMode.BrushSize * 0.01f) * Matrix.RotationQuaternion(rotation) * Matrix.Translation(_hitLocation - viewOrigin);
@@ -586,8 +594,10 @@ namespace FlaxEditor.Tools
_verticesPreviewMaterial = FlaxEngine.Content.LoadAsyncInternal<MaterialBase>(EditorAssets.WiresDebugMaterial);
}
var instanceTransform = _selectedModel.Transform;
var modelScaleMatrix = Matrix.Scaling(_gizmoMode.PreviewVertexSize * 0.01f);
var brushSphere = new BoundingSphere(_hitLocation, _gizmoMode.BrushSize);
var distanceScale = (float)Vector3.Distance(instanceTransform.Translation, renderContext.View.Position) / (10.0f * Units.Meters2Units);
var vertexScale = Mathf.Lerp(0.005f, 0.01f, Mathf.Saturate(distanceScale));
var modelScaleMatrix = Matrix.Scaling(_gizmoMode.PreviewVertexSize * vertexScale);
var brushSphere = new BoundingSphere(_hitLocation, _gizmoMode.BrushSize * 0.5f);
var lodIndex = _gizmoMode.ModelLOD == -1 ? RenderTools.ComputeModelLOD(_selectedModel.Model, ref renderContext.View.Position, (float)_selectedModel.Sphere.Radius, ref renderContext) : _gizmoMode.ModelLOD;
lodIndex = Mathf.Clamp(lodIndex, 0, meshDatas.Length - 1);
var lodData = meshDatas[lodIndex];
+22 -1
View File
@@ -21,6 +21,7 @@ namespace FlaxEditor.Viewport.Cameras
private Transform _startMove;
private Transform _endMove;
private float _moveStartTime = -1;
private float _additionalFOV;
/// <summary>
/// Gets a value indicating whether this viewport is animating movement.
@@ -32,6 +33,15 @@ namespace FlaxEditor.Viewport.Cameras
/// </summary>
public Vector3 TargetPoint = new Vector3(-200);
/// <summary>
/// Additional field of view used for zooming the camera in and out.
/// </summary>
public float AdditionalZoomFOV
{
get => _additionalFOV;
private set => _additionalFOV = Mathf.Clamp(value, 5 - Viewport.FieldOfView, 160f - Viewport.FieldOfView);
}
/// <summary>
/// Sets view.
/// </summary>
@@ -216,7 +226,7 @@ namespace FlaxEditor.Viewport.Cameras
pitch += mouseDelta.Y;
}
// Zoom in/out
// Zoom in/out with mouse wheel
if (input.IsZooming && !input.IsRotating)
{
position += forward * (Viewport.MouseWheelZoomSpeedFactor * input.MouseWheelDelta * 25.0f);
@@ -226,6 +236,17 @@ namespace FlaxEditor.Viewport.Cameras
}
}
// Zoom in and out by changing FOV
if (input.IsRotating && (input.ZoomInDown || input.ZoomOutDown))
{
float delta = (input.ZoomInDown ? -0.8f : 0.8f);
AdditionalZoomFOV += delta;
}
else if (!input.IsRotating)
{
AdditionalZoomFOV = 0f;
}
// Move camera with the gizmo
if (input.IsOrbiting && isUsingGizmo)
{
+18 -1
View File
@@ -51,6 +51,16 @@ namespace FlaxEditor.Viewport
/// </summary>
public bool IsOrbiting;
/// <summary>
/// The zoom in state.
/// </summary>
public bool ZoomInDown;
/// <summary>
/// The zoom out state.
/// </summary>
public bool ZoomOutDown;
/// <summary>
/// The is control down flag.
/// </summary>
@@ -109,6 +119,10 @@ namespace FlaxEditor.Viewport
IsAltDown = window.GetKey(KeyboardKeys.Alt);
WasAltDownBefore = prevInput.WasAltDownBefore || prevInput.IsAltDown;
InputOptions inputOptions = Editor.Instance.Options.Options.Input;
ZoomInDown = window.GetKey(inputOptions.ZoomIn.Key);
ZoomOutDown = window.GetKey(inputOptions.ZoomOut.Key);
IsMouseRightDown = useMouse && window.GetMouseButton(MouseButton.Right);
IsMouseMiddleDown = useMouse && window.GetMouseButton(MouseButton.Middle);
IsMouseLeftDown = useMouse && window.GetMouseButton(MouseButton.Left);
@@ -1433,7 +1447,10 @@ namespace FlaxEditor.Viewport
else
{
float aspect = Width / Height;
Matrix.PerspectiveFov(_fieldOfView * Mathf.DegreesToRadians, aspect, _nearPlane, _farPlane, out result);
float fov = _fieldOfView;
if (_camera is FPSCamera fpsCam)
fov += fpsCam.AdditionalZoomFOV;
Matrix.PerspectiveFov(fov * Mathf.DegreesToRadians, aspect, _nearPlane, _farPlane, out result);
}
}
@@ -706,7 +706,7 @@ namespace FlaxEditor.Viewport
{
base.OnLeftMouseButtonDown();
if (!IsAltKeyDown)
if (!IsAltKeyDown && !_directionGizmo.IsMouseOver)
_rubberBandSelector.TryStartingRubberBandSelection(_viewMousePos);
}
@@ -714,7 +714,7 @@ namespace FlaxEditor.Viewport
protected override void OnLeftMouseButtonUp()
{
// Skip if was controlling mouse or mouse is not over the area
if (_prevInput.IsControllingMouse || !Bounds.Contains(ref _viewMousePos))
if (_prevInput.IsControllingMouse || !Bounds.Contains(ref _viewMousePos) || _directionGizmo.IsMouseOver)
return;
// Select rubberbanded rect actor nodes or pick with gizmo
@@ -2,6 +2,7 @@
using System;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input;
using FlaxEngine;
using Object = FlaxEngine.Object;
@@ -14,7 +15,7 @@ namespace FlaxEditor.Viewport.Previews
public class AnimatedModelPreview : AssetPreview
{
private AnimatedModel _previewModel;
private ContextMenuButton _showNodesButton, _showBoundsButton, _showFloorButton, _showNodesNamesButton;
private ContextMenuButton _showNodesButton, _showBoundsButton, _showFloorButton, _showNodesNamesButton, _nodeNameSizeButton;
private bool _showNodes, _showBounds, _showFloor, _showNodesNames;
private StaticModel _floorModel;
private bool _playAnimation, _playAnimationOnce, _autoAdjustCamera = true;
@@ -110,9 +111,16 @@ namespace FlaxEditor.Viewport.Previews
ShowDebugDraw = true;
if (_showNodesNamesButton != null)
_showNodesNamesButton.Checked = value;
if (_nodeNameSizeButton != null)
_nodeNameSizeButton.Enabled = value;
}
}
/// <summary>
/// The font size used in the node name debug draw.
/// </summary>
public int NodeNamesSize = 10;
/// <summary>
/// Gets or sets a value indicating whether show animated model bounding box debug view.
/// </summary>
@@ -208,6 +216,16 @@ namespace FlaxEditor.Viewport.Previews
_showFloorButton = ViewWidgetShowMenu.AddButton("Floor", button => ShowFloor = !ShowFloor);
_showFloorButton.IndexInParent = 1;
_showFloorButton.CloseMenuOnClick = false;
// Skeleton Names Size
_nodeNameSizeButton = ViewWidgetButtonMenu.AddButton("Skeleton Names Size");
_nodeNameSizeButton.CloseMenuOnClick = false;
var nodeNameSizeValue = new IntValueBox(NodeNamesSize, 118, 2, 70.0f, 1, 32)
{
Parent = _nodeNameSizeButton
};
_nodeNameSizeButton.Enabled = ShowNodesNames;
nodeNameSizeValue.ValueChanged += () => NodeNamesSize = nodeNameSizeValue.Value;
}
// Enable shadows
@@ -378,7 +396,7 @@ namespace FlaxEditor.Viewport.Previews
if (nodesMask != null && !nodesMask[nodeIndex])
continue;
//var t = new Transform(pose[nodeIndex].TranslationVector, Quaternion.Identity, new Float3(0.1f));
DebugDraw.DrawText(nodes[nodeIndex].Name, pose[nodeIndex].TranslationVector, Color.White, 20, 0.0f, 0.1f);
DebugDraw.DrawText(nodes[nodeIndex].Name, pose[nodeIndex].TranslationVector, Color.White, NodeNamesSize, 0.0f, 0.25f);
}
}
}
@@ -1,6 +1,5 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input;
using FlaxEngine;
using FlaxEditor.Viewport.Widgets;
@@ -14,6 +13,7 @@ namespace FlaxEditor.Viewport.Previews
/// <seealso cref="AnimatedModelPreview" />
public class AnimationPreview : AnimatedModelPreview
{
private bool _baseModelMissing;
private ViewportWidgetButton _playPauseButton;
/// <summary>
@@ -94,14 +94,23 @@ namespace FlaxEditor.Viewport.Previews
var style = Style.Current;
var skinnedModel = SkinnedModel;
var baseModelMissing = false;
if (skinnedModel == null)
{
Render2D.DrawText(style.FontLarge, "Missing Base Model", new Rectangle(Float2.Zero, Size), Color.Red, TextAlignment.Center, TextAlignment.Center, TextWrapping.WrapWords);
baseModelMissing = true;
}
else if (!skinnedModel.IsLoaded)
{
Render2D.DrawText(style.FontLarge, skinnedModel.LastLoadFailed ? "Failed to load" : "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center);
baseModelMissing = true;
}
if (_baseModelMissing && !baseModelMissing)
{
// Focus model when base model appears
ResetCamera();
}
_baseModelMissing = baseModelMissing;
}
/// <inheritdoc />
@@ -132,7 +132,8 @@ namespace FlaxEditor.Windows
if (item is AssetItem assetItem)
{
if (assetItem.IsLoaded)
var asset = FlaxEngine.Content.GetAsset(assetItem.ID);
if (asset != null && (asset.IsLoaded || asset.LastLoadFailed))
cm.AddButton("Reload", assetItem.Reload);
cm.AddButton("Copy asset ID", () => Clipboard.Text = JsonSerializer.GetStringID(assetItem.ID));
cm.AddButton("Select actors using this asset", () => Editor.SceneEditing.SelectActorsUsingAsset(assetItem.ID));
+3 -2
View File
@@ -123,7 +123,7 @@ namespace FlaxEditor.Windows
private readonly ScaledRenderOutputControl _viewport;
private readonly GameRoot _guiRoot;
private bool _showGUI = true, _editGUI = true;
private bool _showDebugDraw = false;
private bool _showDebugDraw = true;
private bool _audioMuted = false;
private float _audioVolume = 1;
private bool _isMaximized = false, _isUnlockingMouse = false;
@@ -821,7 +821,8 @@ namespace FlaxEditor.Windows
{
base.Draw();
if (Camera.MainCamera == null)
var mainRenderTask = MainRenderTask.Instance;
if (Camera.MainCamera == null && (mainRenderTask == null || !mainRenderTask.IsCustomRendering))
{
var style = Style.Current;
Render2D.DrawText(style.FontLarge, "No camera", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center);
@@ -98,10 +98,10 @@ namespace FlaxEditor.Windows
[NoSerialize, DefaultValue(1.0f), Limit(0.05f, 5, 0)]
[EditorOrder(1400), EditorDisplay("Quality")]
[Tooltip("The scale of the rendering resolution relative to the output dimensions. If lower than 1 the scene and postprocessing will be rendered at a lower resolution and upscaled to the output backbuffer.")]
public float RenderingPercentage
public float RenderScale
{
get => MainRenderTask.Instance.RenderingPercentage;
set => MainRenderTask.Instance.RenderingPercentage = value;
get => MainRenderTask.Instance.RenderScale;
set => MainRenderTask.Instance.RenderScale = value;
}
[NoSerialize, DefaultValue(RenderingUpscaleLocation.AfterAntiAliasingPass), VisibleIf(nameof(UpscaleLocation_Visible))]
@@ -113,7 +113,7 @@ namespace FlaxEditor.Windows
set => MainRenderTask.Instance.UpscaleLocation = value;
}
private bool UpscaleLocation_Visible => MainRenderTask.Instance.RenderingPercentage < 1.0f;
private bool UpscaleLocation_Visible => MainRenderTask.Instance.RenderScale < 1.0f;
[NoSerialize, DefaultValue(1.0f), Limit(0, 1)]
[EditorOrder(1500), EditorDisplay("Quality"), Tooltip("The global density scale for all foliage instances. The default value is 1. Use values from range 0-1. Lower values decrease amount of foliage instances in-game. Use it to tweak game performance for slower devices.")]
+28 -7
View File
@@ -176,7 +176,8 @@ namespace FlaxEditor.Windows
if (Owner != null && (!Owner._searchPopup?.Visible ?? true))
{
// Focus back the input field as user want to modify command from history
Owner._searchPopup?.Hide();
Owner.HideHistory();
Owner.HideSearch();
Owner.RootWindow.Focus();
Owner.Focus();
Owner.OnKeyDown(key);
@@ -209,6 +210,7 @@ namespace FlaxEditor.Windows
private OutputLogWindow _window;
private ItemsListContextMenu _searchPopup;
private ItemsListContextMenu _historyPopup;
private bool _isSettingText;
public CommandLineBox(float x, float y, float width, OutputLogWindow window)
@@ -226,6 +228,24 @@ namespace FlaxEditor.Windows
_isSettingText = false;
}
private void HideSearch()
{
if (_searchPopup != null)
{
_searchPopup.Hide();
_searchPopup = null;
}
}
private void HideHistory()
{
if (_historyPopup != null)
{
_historyPopup.Dispose();
_historyPopup = null;
}
}
private void ShowPopup(ref ItemsListContextMenu cm, IEnumerable<string> commands, string searchText = null)
{
if (cm == null)
@@ -295,7 +315,7 @@ namespace FlaxEditor.Windows
private void OnRootWindowLostFocus()
{
// Prevent popup from staying active when editor window looses focus
_searchPopup?.Hide();
HideSearch();
if (RootWindow?.Window != null)
RootWindow.Window.LostFocus -= OnRootWindowLostFocus;
}
@@ -330,6 +350,7 @@ namespace FlaxEditor.Windows
if (isWhitespaceOnly)
DebugCommands.GetAllCommands(out commands);
HideHistory();
ShowPopup(ref _searchPopup, isWhitespaceOnly ? commands : matches, text);
if (isWhitespaceOnly)
@@ -342,7 +363,7 @@ namespace FlaxEditor.Windows
return;
}
}
_searchPopup?.Hide();
HideSearch();
}
/// <inheritdoc />
@@ -353,7 +374,8 @@ namespace FlaxEditor.Windows
case KeyboardKeys.Return:
{
// Run command
_searchPopup?.Hide();
HideSearch();
HideHistory();
var command = Text.Trim();
if (command.Length == 0)
return true;
@@ -430,9 +452,8 @@ namespace FlaxEditor.Windows
if (_window._commandHistory != null && _window._commandHistory.Count != 0)
{
// Show command history popup
_searchPopup?.Hide();
ItemsListContextMenu cm = null;
ShowPopup(ref cm, _window._commandHistory);
HideSearch();
ShowPopup(ref _historyPopup, _window._commandHistory);
}
}
return true;
+9
View File
@@ -57,6 +57,15 @@ public:
{
return &_knowledge;
}
/// <summary>
/// Gets the blackboard of a given type.
/// </summary>
template<typename T>
FORCE_INLINE T* GetBlackboard()
{
return _knowledge.GetBlackboard<T>();
}
/// <summary>
/// Gets the last behavior tree execution result.
+5 -8
View File
@@ -205,10 +205,9 @@ bool BehaviorKnowledge::Set(const StringAnsiView& path, const Variant& value)
bool BehaviorKnowledge::HasGoal(ScriptingTypeHandle type) const
{
for (int32 i = 0; i < Goals.Count(); i++)
for (const Variant& goal : Goals)
{
const ScriptingTypeHandle goalType = Scripting::FindScriptingType(Goals[i].Type.GetTypeName());
if (goalType == type)
if (goal.Type.GetScriptingType() == type)
return true;
}
return false;
@@ -218,8 +217,7 @@ const Variant& BehaviorKnowledge::GetGoal(ScriptingTypeHandle type) const
{
for (const Variant& goal : Goals)
{
const ScriptingTypeHandle goalType = Scripting::FindScriptingType(goal.Type.GetTypeName());
if (goalType == type)
if (goal.Type.GetScriptingType() == type)
return goal;
}
return Variant::Null;
@@ -242,10 +240,9 @@ void BehaviorKnowledge::RemoveGoal(ScriptingTypeHandle type)
{
for (int32 i = 0; i < Goals.Count(); i++)
{
const ScriptingTypeHandle goalType = Scripting::FindScriptingType(Goals[i].Type.GetTypeName());
if (goalType == type)
if (Goals[i].Type.GetScriptingType() == type)
{
Goals.RemoveAt(i);
Goals.RemoveAtKeepOrder(i);
break;
}
}
+12
View File
@@ -124,6 +124,18 @@ public:
RemoveGoal(T::TypeInitializer);
}
/// <summary>
/// Gets the blackboard of a given type.
/// </summary>
template<typename T>
FORCE_INLINE T* GetBlackboard()
{
auto* structure = Blackboard.AsStructure<T>();
if (structure)
return structure;
return Cast<T>((ScriptingObject*)Blackboard);
}
public:
/// <summary>
/// Compares two values and returns the comparision result.
+22
View File
@@ -11,6 +11,18 @@ using FlaxEngine.GUI;
namespace FlaxEngine
{
partial class Behavior
{
/// <summary>
/// Gets the blackboard of the given type.
/// </summary>
/// <typeparam name="T"> The blackboard type.</typeparam>
public T GetBlackboard<T>()
{
return Knowledge.GetBlackboard<T>();
}
}
partial class BehaviorKnowledge
{
/// <summary>
@@ -33,6 +45,16 @@ namespace FlaxEngine
{
RemoveGoal(typeof(T));
}
/// <summary>
/// Gets the blackboard of the given type.
/// </summary>
/// <typeparam name="T"> The blackboard type.</typeparam>
[Unmanaged]
public T GetBlackboard<T>()
{
return (T)Blackboard;
}
}
partial class BehaviorTreeRootNode
@@ -61,14 +61,14 @@ void AnimGraphBase::Clear()
StateTransitions.Resize(0);
// Base
GraphType::Clear();
VisjectGraph::Clear();
}
#if USE_EDITOR
void AnimGraphBase::GetReferences(Array<Guid>& output) const
{
GraphType::GetReferences(output);
VisjectGraph::GetReferences(output);
// Collect references from nested graph (assets used in state machines)
for (const auto* subGraph : SubGraphs)
@@ -873,65 +873,41 @@ void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& valu
switch (param->Type.Type)
{
case VariantType::Float2:
switch (box->ID)
{
case 1:
case 2:
if (box->ID >= 1 && box->ID <= 2)
value = value.AsFloat2().Raw[box->ID - 1];
break;
}
break;
case VariantType::Float3:
switch (box->ID)
{
case 1:
case 2:
case 3:
if (box->ID >= 1 && box->ID <= 3)
value = value.AsFloat3().Raw[box->ID - 1];
break;
}
break;
case VariantType::Float4:
case VariantType::Color:
switch (box->ID)
{
case 1:
case 2:
case 3:
case 4:
if (box->ID >= 1 && box->ID <= 4)
value = value.AsFloat4().Raw[box->ID - 1];
break;
}
break;
case VariantType::Double2:
switch (box->ID)
{
case 1:
case 2:
if (box->ID >= 1 && box->ID <= 2)
value = value.AsDouble2().Raw[box->ID - 1];
break;
}
break;
case VariantType::Double3:
switch (box->ID)
{
case 1:
case 2:
case 3:
if (box->ID >= 1 && box->ID <= 3)
value = value.AsDouble3().Raw[box->ID - 1];
break;
}
break;
case VariantType::Double4:
switch (box->ID)
{
case 1:
case 2:
case 3:
case 4:
if (box->ID >= 1 && box->ID <= 4)
value = value.AsDouble4().Raw[box->ID - 1];
break;
}
break;
case VariantType::Int2:
if (box->ID >= 1 && box->ID <= 2)
value = value.AsInt2().Raw[box->ID - 1];
break;
case VariantType::Int3:
if (box->ID >= 1 && box->ID <= 3)
value = value.AsInt3().Raw[box->ID - 1];
break;
case VariantType::Int4:
if (box->ID >= 1 && box->ID <= 4)
value = value.AsInt4().Raw[box->ID - 1];
break;
case VariantType::Matrix:
{
+1
View File
@@ -272,6 +272,7 @@ String Asset::ToString() const
void Asset::OnDeleteObject()
{
PROFILE_CPU_NAMED("Asset.Unload");
ASSERT(IsInMainThread());
// Send event to the gameplay so it can release handle to this asset
@@ -163,7 +163,7 @@ VisjectExecutor::Value VisualScriptExecutor::eatBox(Node* caller, Box* box)
// Add to the calling stack
VisualScripting::StackFrame frame = *stack.Stack;
frame.Node = parentNode;
frame.Node = (VisualScriptGraphNode*)parentNode;
frame.Box = box;
frame.PreviousFrame = stack.Stack;
stack.Stack = &frame;
@@ -189,7 +189,7 @@ VisjectExecutor::Value VisualScriptExecutor::eatBox(Node* caller, Box* box)
VisjectExecutor::Graph* VisualScriptExecutor::GetCurrentGraph() const
{
auto& stack = ThreadStacks.Get();
return stack.Stack && stack.Stack->Script ? &stack.Stack->Script->Graph : nullptr;
return stack.Stack && stack.Stack->Script ? (Graph*)&stack.Stack->Script->Graph : nullptr;
}
void VisualScriptExecutor::ProcessGroupParameters(Box* box, Node* node, Value& value)
@@ -339,7 +339,7 @@ void VisualScriptExecutor::ProcessGroupTools(Box* box, Node* node, Value& value)
obj = Value::Null;
#else
const ScriptingTypeHandle type = Scripting::FindScriptingType(StringAnsiView(typeNameAnsi.Get(), typeName.Length()));
const ScriptingTypeHandle objType = Scripting::FindScriptingType(obj.Type.GetTypeName());
const ScriptingTypeHandle objType = obj.Type.GetScriptingType();
if (!type || !objType || !objType.IsSubclassOf(type))
obj = Value::Null;
#endif
@@ -432,7 +432,7 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value&
// Call Impulse or Pure Method
if (boxBase->ID == 0 || (bool)node->Values[3])
{
auto& cache = node->Data.InvokeMethod;
auto& cache = ((VisualScriptGraphNode*)node)->Data.InvokeMethod;
if (!cache.Method)
{
// Load method signature
@@ -667,7 +667,7 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value&
// Get Field
case 7:
{
auto& cache = node->Data.GetSetField;
auto& cache = ((VisualScriptGraphNode*)node)->Data.GetSetField;
if (!cache.Field)
{
const auto typeName = (StringView)node->Values[0];
@@ -753,7 +753,7 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value&
// Get Field
case 8:
{
auto& cache = node->Data.GetSetField;
auto& cache = ((VisualScriptGraphNode*)node)->Data.GetSetField;
if (!cache.Field)
{
const auto typeName = (StringView)node->Values[0];
+38 -2
View File
@@ -12,11 +12,47 @@
#define VISUAL_SCRIPT_GRAPH_MAX_CALL_STACK 250
#define VISUAL_SCRIPT_DEBUGGING USE_EDITOR
#define VisualScriptGraphNode VisjectGraphNode<>
class VisualScripting;
class VisualScriptingBinaryModule;
/// <summary>
/// Visual Script graph node.
/// </summary>
class VisualScriptGraphNode : public VisjectGraphNode<>
{
public:
struct InvokeMethodData
{
void* Method;
BinaryModule* Module;
int32 ParamsCount;
uint32 OutParamsMask;
bool IsStatic;
};
struct GetSetFieldData
{
void* Field;
BinaryModule* Module;
bool IsStatic;
};
/// <summary>
/// Custom cached data per node type. Compact to use as small amount of memory as possible.
/// </summary>
struct AdditionalData
{
union
{
InvokeMethodData InvokeMethod;
GetSetFieldData GetSetField;
};
};
// The custom per-node data. Used to cache data for faster usage at runtime.
AdditionalData Data;
};
/// <summary>
/// The Visual Script graph data.
/// </summary>
+1 -4
View File
@@ -564,10 +564,7 @@ ContentLoadTask* BinaryAsset::createLoadingTask()
loadTask = preLoadChunksTask;
}
// Before asset loading we have to initialize storage
// TODO: maybe in build game we could do it in place?
// This step is only for opening asset files in background and upgrading them
// In build game we have only a few packages which are ready to use
// Before asset loading we have to initialize storage and pull the asset header
auto initTask = New<InitAssetTask>(this);
initTask->ContinueWith(loadTask);
loadTask = initTask;
+79 -51
View File
@@ -113,7 +113,7 @@ void AssetsCache::Init()
}
// Use only valid entries
if (IsEntryValid(e))
if (IsEntryValid(e) != EntryValidation::Invalid)
_registry.Add(e.Info.ID, e);
else
rejectedCount++;
@@ -295,14 +295,23 @@ bool AssetsCache::FindAsset(const StringView& path, AssetInfo& info)
auto& e = i->Value;
if (e.Info.Path == path)
{
if (!IsEntryValid(e))
const auto validation = IsEntryValid(e);
if (validation == EntryValidation::Invalid)
{
LOG(Warning, "Missing file from registry: \'{0}\':{1}:{2}", e.Info.Path, e.Info.ID, e.Info.TypeName);
_registry.Remove(i);
}
else
{
// Found
#if ENABLE_ASSETS_DISCOVERY
if (validation == EntryValidation::Inaccessible && !e.WarnedInaccessible)
{
e.WarnedInaccessible = true;
LOG(Warning, "Asset file locked, keeping cached entry: \'{0}\':{1}:{2}", e.Info.Path, e.Info.ID, e.Info.TypeName);
}
#endif
// Found valid or inaccessible but return cached info either way
result = true;
info = e.Info;
}
@@ -322,13 +331,22 @@ bool AssetsCache::FindAsset(const Guid& id, AssetInfo& info)
auto e = _registry.TryGet(id);
if (e != nullptr)
{
if (!IsEntryValid(*e))
const auto validation = IsEntryValid(*e);
if (validation == EntryValidation::Invalid)
{
LOG(Warning, "Missing file from registry: \'{0}\':{1}:{2}", e->Info.Path, e->Info.ID, e->Info.TypeName);
_registry.Remove(id);
}
else
{
#if ENABLE_ASSETS_DISCOVERY
if (validation == EntryValidation::Inaccessible && !e->WarnedInaccessible)
{
e->WarnedInaccessible = true;
LOG(Warning, "Asset file locked, keeping cached entry: \'{0}\':{1}:{2}", e->Info.Path, e->Info.ID, e->Info.TypeName);
}
#endif
// Found
result = true;
info = e->Info;
@@ -360,13 +378,13 @@ void AssetsCache::GetAllByTypeName(const StringView& typeName, Array<Guid>& resu
void AssetsCache::RegisterAssets(FlaxStorage* storage)
{
PROFILE_CPU();
ASSERT(storage);
// Get all entries
Array<FlaxStorage::Entry> entries;
storage->GetEntries(entries);
ASSERT(entries.HasItems());
if (entries.IsEmpty())
return;
ASSETS_CACHE_LOCK();
auto storagePath = storage->GetPath();
@@ -567,60 +585,70 @@ bool AssetsCache::RenameAsset(const StringView& oldPath, const StringView& newPa
#endif
bool AssetsCache::IsEntryValid(Entry& e)
AssetsCache::EntryValidation AssetsCache::IsEntryValid(Entry& e)
{
#if ENABLE_ASSETS_DISCOVERY
// Check if file exists
if (FileSystem::FileExists(e.Info.Path))
if (!FileSystem::FileExists(e.Info.Path))
return EntryValidation::Invalid;
// Check if file hasn't been modified
const auto fileModified = FileSystem::GetFileLastEditTime(e.Info.Path);
if (fileModified == e.FileModified)
{
// Check if file hasn't been modified
const auto fileModified = FileSystem::GetFileLastEditTime(e.Info.Path);
if (fileModified == e.FileModified)
return true;
const auto extension = FileSystem::GetExtension(e.Info.Path).ToLower();
// Check if it's a binary asset
if (ContentStorageManager::IsFlaxStorageExtension(extension))
{
// Validate ID within storage container
const auto storage = ContentStorageManager::GetStorage(e.Info.Path);
if (storage)
{
// Check if storage at given location contains that asset
const bool isValid = storage->HasAsset(e.Info);
// Update entry and mark cache as dirty
e.FileModified = fileModified;
_isDirty = true;
return isValid;
}
}
// Check for json resource
else if (JsonStorageProxy::IsValidExtension(extension))
{
// Check Json storage layer
Guid jsonId;
String jsonTypeName;
if (JsonStorageProxy::GetAssetInfo(e.Info.Path, jsonId, jsonTypeName))
{
const bool isValid = e.Info.ID == jsonId && e.Info.TypeName == jsonTypeName;
// Update entry and mark cache as dirty
e.FileModified = fileModified;
_isDirty = true;
return isValid;
}
}
e.WarnedInaccessible = false;
return EntryValidation::Valid;
}
return false;
const auto extension = FileSystem::GetExtension(e.Info.Path).ToLower();
// Check if it's a binary asset
if (ContentStorageManager::IsFlaxStorageExtension(extension))
{
// Validate ID within storage container
const auto storage = ContentStorageManager::GetStorage(e.Info.Path);
if (storage)
{
// Check if storage at given location contains that asset
const bool isValid = storage->HasAsset(e.Info);
// Update entry and mark cache as dirty
e.FileModified = fileModified;
e.WarnedInaccessible = false;
_isDirty = true;
return isValid ? EntryValidation::Valid : EntryValidation::Invalid;
}
}
// Check for json resource
else if (JsonStorageProxy::IsValidExtension(extension))
{
// Check Json storage layer
Guid jsonId;
String jsonTypeName;
if (JsonStorageProxy::GetAssetInfo(e.Info.Path, jsonId, jsonTypeName))
{
const bool isValid = e.Info.ID == jsonId && e.Info.TypeName == jsonTypeName;
// Update entry and mark cache as dirty
e.FileModified = fileModified;
e.WarnedInaccessible = false;
_isDirty = true;
return isValid ? EntryValidation::Valid : EntryValidation::Invalid;
}
}
else
{
// Unknown file type
return EntryValidation::Invalid;
}
// File exists but cannot be read (likely locked by git or another process)
return EntryValidation::Inaccessible;
#else
// In game we don't care about it because all cached asset entries are valid (precached)
// Skip only entries with missing file
return e.Info.Path.HasChars();
return e.Info.Path.HasChars() ? EntryValidation::Valid : EntryValidation::Invalid;
#endif
}
+28 -2
View File
@@ -58,6 +58,11 @@ public:
/// The file modified date.
/// </summary>
DateTime FileModified;
/// <summary>
/// True if a warning about this entry being inaccessible has already been logged (prevents log spam). Runtime-only, not serialized.
/// </summary>
bool WarnedInaccessible = false;
#endif
Entry()
@@ -73,6 +78,27 @@ public:
}
};
/// <summary>
/// Result of validating an asset cache entry.
/// </summary>
enum class EntryValidation
{
/// <summary>
/// File verified, contains this asset.
/// </summary>
Valid,
/// <summary>
/// File missing or contains a different asset.
/// </summary>
Invalid,
/// <summary>
/// File exists but cannot be opened (locked by another process).
/// </summary>
Inaccessible,
};
typedef Dictionary<Guid, Entry> Registry;
typedef Dictionary<String, Guid> PathsMapping;
@@ -232,6 +258,6 @@ public:
/// Determines whether cached asset entry is valid.
/// </summary>
/// <param name="e">The asset entry.</param>
/// <returns>True if is valid, otherwise false.</returns>
bool IsEntryValid(Entry& e);
/// <returns>The validation result.</returns>
EntryValidation IsEntryValid(Entry& e);
};
@@ -54,6 +54,7 @@ FlaxStorageReference ContentStorageManager::GetStorage(const StringView& path, b
Locker.Lock();
// Try fast lookup
bool wasCached = true;
FlaxStorage* storage;
if (!StorageMap.TryGet(path, storage))
{
@@ -74,6 +75,7 @@ FlaxStorageReference ContentStorageManager::GetStorage(const StringView& path, b
// Register storage container
StorageMap.Add(path, storage);
wasCached = false;
}
// Build reference (before releasing the lock so ContentStorageSystem::Job won't delete it when running from async thread)
@@ -90,6 +92,8 @@ FlaxStorageReference ContentStorageManager::GetStorage(const StringView& path, b
if (loadFailed)
{
LOG(Error, "Failed to load {0}.", path);
if (wasCached)
return result;
Locker.Lock();
StorageMap.Remove(path);
if (storage->IsPackage())
@@ -243,9 +243,9 @@ FlaxStorage::~FlaxStorage()
{
// Validate if has been disposed
ASSERT(IsDisposed());
CHECK(_chunksLock == 0);
CHECK(_refCount == 0);
CHECK(_isUnloadingData == 0);
CHECK_NO_RETURN(_chunksLock == 0);
CHECK_NO_RETURN(_refCount == 0);
CHECK_NO_RETURN(_isUnloadingData == 0);
ASSERT(_chunks.IsEmpty());
#if USE_EDITOR
+2
View File
@@ -9,6 +9,8 @@
// Float
static_assert(sizeof(Float2) == 8, "Invalid Float2 type size.");
static_assert(sizeof(Int2) == 8, "Invalid Int2 type size.");
static_assert(sizeof(Double2) == 16, "Invalid Double2 type size.");
template<>
const Float2 Float2::Zero(0.0f);
+2
View File
@@ -13,6 +13,8 @@
// Float
static_assert(sizeof(Float3) == 12, "Invalid Float3 type size.");
static_assert(sizeof(Int3) == 12, "Invalid Int3 type size.");
static_assert(sizeof(Double3) == 24, "Invalid Double3 type size.");
template<>
const Float3 Float3::Zero(0.0f);
+2
View File
@@ -11,6 +11,8 @@
// Float
static_assert(sizeof(Float4) == 16, "Invalid Float4 type size.");
static_assert(sizeof(Int4) == 16, "Invalid Int4 type size.");
static_assert(sizeof(Double4) == 32, "Invalid Double4 type size.");
template<>
const Float4 Float4::Zero(0.0f);
+130 -11
View File
@@ -4,6 +4,7 @@
#include "Engine/Core/Collections/HashFunctions.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Content/Asset.h"
#include "Engine/Content/Deprecated.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Mathd.h"
#include "Engine/Core/Math/BoundingBox.h"
@@ -124,7 +125,7 @@ VariantType::VariantType(Types type, const StringAnsiView& typeName, bool static
VariantType::VariantType(Types type, const ScriptingType& sType)
: VariantType(type)
{
SetTypeName(sType.Fullname, sType.Module->CanReload);
SetTypeName(sType.Fullname, !sType.Module->CanReload);
}
VariantType::VariantType(Types type, const MClass* klass)
@@ -344,13 +345,13 @@ void VariantType::SetTypeName(const StringAnsiView& typeName, bool staticName)
void VariantType::SetTypeName(const ScriptingType& type)
{
SetTypeName(type.Fullname, type.Module->CanReload);
SetTypeName(type.Fullname, !type.Module->CanReload);
}
void VariantType::SetTypeName(const MClass& klass)
{
#if USE_CSHARP
SetTypeName(klass.GetFullName(), klass.GetAssembly()->CanReload());
SetTypeName(klass.GetFullName(), !klass.GetAssembly()->CanReload());
#endif
}
@@ -361,6 +362,11 @@ const char* VariantType::GetTypeName() const
return InBuiltTypesTypeNames[Type];
}
ScriptingTypeHandle VariantType::GetScriptingType() const
{
return Scripting::FindScriptingType(GetTypeName());
}
VariantType VariantType::GetElementType() const
{
if (Type == Array)
@@ -3193,6 +3199,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to)
case VariantType::Double2:
case VariantType::Double3:
case VariantType::Double4:
case VariantType::Int2:
case VariantType::Int3:
case VariantType::Int4:
case VariantType::Enum:
return true;
default:
@@ -3216,6 +3225,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to)
case VariantType::Double2:
case VariantType::Double3:
case VariantType::Double4:
case VariantType::Int2:
case VariantType::Int3:
case VariantType::Int4:
case VariantType::Enum:
return true;
default:
@@ -3239,6 +3251,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to)
case VariantType::Double2:
case VariantType::Double3:
case VariantType::Double4:
case VariantType::Int2:
case VariantType::Int3:
case VariantType::Int4:
case VariantType::Enum:
return true;
default:
@@ -3262,6 +3277,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to)
case VariantType::Double2:
case VariantType::Double3:
case VariantType::Double4:
case VariantType::Int2:
case VariantType::Int3:
case VariantType::Int4:
case VariantType::Enum:
return true;
default:
@@ -3285,6 +3303,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to)
case VariantType::Double2:
case VariantType::Double3:
case VariantType::Double4:
case VariantType::Int2:
case VariantType::Int3:
case VariantType::Int4:
case VariantType::Enum:
return true;
default:
@@ -3308,6 +3329,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to)
case VariantType::Double2:
case VariantType::Double3:
case VariantType::Double4:
case VariantType::Int2:
case VariantType::Int3:
case VariantType::Int4:
case VariantType::Enum:
return true;
default:
@@ -3331,6 +3355,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to)
case VariantType::Double2:
case VariantType::Double3:
case VariantType::Double4:
case VariantType::Int2:
case VariantType::Int3:
case VariantType::Int4:
case VariantType::Enum:
return true;
default:
@@ -3354,6 +3381,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to)
case VariantType::Double2:
case VariantType::Double3:
case VariantType::Double4:
case VariantType::Int2:
case VariantType::Int3:
case VariantType::Int4:
case VariantType::Enum:
return true;
default:
@@ -3377,6 +3407,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to)
case VariantType::Double2:
case VariantType::Double3:
case VariantType::Double4:
case VariantType::Int2:
case VariantType::Int3:
case VariantType::Int4:
case VariantType::Enum:
return true;
default:
@@ -3520,6 +3553,12 @@ Variant Variant::Cast(const Variant& v, const VariantType& to)
return Variant(Double3(v.AsBool ? 1.0 : 0.0));
case VariantType::Double4:
return Variant(Double4(v.AsBool ? 1.0 : 0.0));
case VariantType::Int2:
return Variant(Int2(v.AsBool ? 1 : 0));
case VariantType::Int3:
return Variant(Int3(v.AsBool ? 1 : 0));
case VariantType::Int4:
return Variant(Int4(v.AsBool ? 1 : 0));
case VariantType::Enum:
return Enum(to, v.AsBool ? 1 : 0);
default: ;
@@ -3558,6 +3597,12 @@ Variant Variant::Cast(const Variant& v, const VariantType& to)
return Variant(Double3((double)v.AsInt16));
case VariantType::Double4:
return Variant(Double4((double)v.AsInt16));
case VariantType::Int2:
return Variant(Int2(v.AsInt16));
case VariantType::Int3:
return Variant(Int3(v.AsInt16));
case VariantType::Int4:
return Variant(Int4(v.AsInt16));
case VariantType::Enum:
return Enum(to, (int64)v.AsInt16);
default: ;
@@ -3588,6 +3633,18 @@ Variant Variant::Cast(const Variant& v, const VariantType& to)
return Variant(Float3((float)v.AsInt));
case VariantType::Float4:
return Variant(Float4((float)v.AsInt));
case VariantType::Double2:
return Variant(Double2((double)v.AsInt));
case VariantType::Double3:
return Variant(Double3((double)v.AsInt));
case VariantType::Double4:
return Variant(Double4((double)v.AsInt));
case VariantType::Int2:
return Variant(Int2(v.AsInt));
case VariantType::Int3:
return Variant(Int3(v.AsInt));
case VariantType::Int4:
return Variant(Int4(v.AsInt));
case VariantType::Color:
return Variant(Color((float)v.AsInt));
case VariantType::Enum:
@@ -3628,6 +3685,12 @@ Variant Variant::Cast(const Variant& v, const VariantType& to)
return Variant(Double3((double)v.AsUint16));
case VariantType::Double4:
return Variant(Double4((double)v.AsUint16));
case VariantType::Int2:
return Variant(Int2(v.AsUint16));
case VariantType::Int3:
return Variant(Int3(v.AsUint16));
case VariantType::Int4:
return Variant(Int4(v.AsUint16));
case VariantType::Enum:
return Enum(to, (int64)v.AsUint16);
default: ;
@@ -3666,6 +3729,12 @@ Variant Variant::Cast(const Variant& v, const VariantType& to)
return Variant(Double3((double)v.AsUint));
case VariantType::Double4:
return Variant(Double4((double)v.AsUint));
case VariantType::Int2:
return Variant(Int2((int32)v.AsUint));
case VariantType::Int3:
return Variant(Int3((int32)v.AsUint));
case VariantType::Int4:
return Variant(Int4((int32)v.AsUint));
case VariantType::Enum:
return Enum(to, (int64)v.AsUint);
default: ;
@@ -3704,6 +3773,12 @@ Variant Variant::Cast(const Variant& v, const VariantType& to)
return Variant(Double3((double)v.AsInt64));
case VariantType::Double4:
return Variant(Double4((double)v.AsInt64));
case VariantType::Int2:
return Variant(Int2((int32)v.AsInt64));
case VariantType::Int3:
return Variant(Int3((int32)v.AsInt64));
case VariantType::Int4:
return Variant(Int4((int32)v.AsInt64));
case VariantType::Enum:
return Enum(to, (int64)v.AsInt64);
default: ;
@@ -3723,25 +3798,31 @@ Variant Variant::Cast(const Variant& v, const VariantType& to)
case VariantType::Uint16:
return Variant((uint16)v.AsUint16);
case VariantType::Uint:
return Variant((uint32)v.AsUint);
return Variant((uint32)v.AsUint64);
case VariantType::Float:
return Variant((float)v.AsUint64);
case VariantType::Double:
return Variant((double)v.AsUint64);
case VariantType::Float2:
return Variant(Float2((float)v.AsInt));
return Variant(Float2((float)v.AsUint64));
case VariantType::Float3:
return Variant(Float3((float)v.AsInt));
return Variant(Float3((float)v.AsUint64));
case VariantType::Float4:
return Variant(Float4((float)v.AsInt));
return Variant(Float4((float)v.AsUint64));
case VariantType::Color:
return Variant(Color((float)v.AsInt));
return Variant(Color((float)v.AsUint64));
case VariantType::Double2:
return Variant(Double2((double)v.AsInt));
return Variant(Double2((double)v.AsUint64));
case VariantType::Double3:
return Variant(Double3((double)v.AsInt));
return Variant(Double3((double)v.AsUint64));
case VariantType::Double4:
return Variant(Double4((double)v.AsInt));
return Variant(Double4((double)v.AsUint64));
case VariantType::Int2:
return Variant(Int2((int32)v.AsUint64));
case VariantType::Int3:
return Variant(Int3((int32)v.AsUint64));
case VariantType::Int4:
return Variant(Int4((int32)v.AsUint64));
case VariantType::Enum:
return Enum(to, (int64)v.AsInt);
default: ;
@@ -3780,6 +3861,12 @@ Variant Variant::Cast(const Variant& v, const VariantType& to)
return Variant(Double3(v.AsFloat));
case VariantType::Double4:
return Variant(Double4(v.AsFloat));
case VariantType::Int2:
return Variant(Int2((int32)v.AsFloat));
case VariantType::Int3:
return Variant(Int3((int32)v.AsFloat));
case VariantType::Int4:
return Variant(Int4((int32)v.AsFloat));
case VariantType::Enum:
return Enum(to, (int64)v.AsFloat);
default: ;
@@ -3818,6 +3905,12 @@ Variant Variant::Cast(const Variant& v, const VariantType& to)
return Variant(Double3(v.AsDouble));
case VariantType::Double4:
return Variant(Double4(v.AsDouble));
case VariantType::Int2:
return Variant(Int2((int32)v.AsFloat));
case VariantType::Int3:
return Variant(Int3((int32)v.AsFloat));
case VariantType::Int4:
return Variant(Int4((int32)v.AsFloat));
case VariantType::Enum:
return Enum(to, (int64)v.AsDouble);
default: ;
@@ -4246,6 +4339,7 @@ void Variant::AllocStructure()
AsBlob.Length = 2;
AsBlob.Data = Allocator::Allocate(AsBlob.Length);
*((int16*)AsBlob.Data) = 0;
MARK_CONTENT_DEPRECATED();
}
#if USE_CSHARP
else if (const auto mclass = Scripting::FindClass(typeName))
@@ -4371,6 +4465,13 @@ uint32 GetHash(const Variant& key)
return GetHash((void*)key.AsObject);
case VariantType::Structure:
case VariantType::Blob:
case VariantType::Transform:
case VariantType::Matrix:
#if USE_LARGE_WORLDS
case VariantType::BoundingSphere:
case VariantType::BoundingBox:
case VariantType::Ray:
#endif
return Crc::MemCrc32(key.AsBlob.Data, key.AsBlob.Length);
case VariantType::Asset:
return GetHash((void*)key.AsAsset);
@@ -4380,6 +4481,24 @@ uint32 GetHash(const Variant& key)
return GetHash(*(Guid*)key.AsData);
case VariantType::Typename:
return GetHash((const char*)key.AsBlob.Data);
case VariantType::Float2:
return GetHash(*(const Float2*)key.AsData);
case VariantType::Float3:
return GetHash(*(const Float3*)key.AsData);
case VariantType::Float4:
return GetHash(*(const Float4*)key.AsData);
case VariantType::Int2:
return GetHash(*(const Int2*)key.AsData);
case VariantType::Int3:
return GetHash(*(const Int3*)key.AsData);
case VariantType::Int4:
return GetHash(*(const Int4*)key.AsData);
case VariantType::Double2:
return GetHash(*(const Double2*)key.AsData);
case VariantType::Double3:
return GetHash(*(const Double3*)key.AsData);
case VariantType::Double4:
return GetHash(*(const Double4*)key.AsBlob.Data);
case VariantType::ManagedObject:
#if USE_CSHARP
return key.MANAGED_GC_HANDLE ? (uint32)MCore::Object::GetHashCode(MCore::GCHandle::GetTarget(key.MANAGED_GC_HANDLE)) : 0;
+10
View File
@@ -151,6 +151,7 @@ public:
void SetTypeName(const ScriptingType& type);
void SetTypeName(const MClass& klass);
const char* GetTypeName() const;
ScriptingTypeHandle GetScriptingType() const;
VariantType GetElementType() const;
// Drops custom type name into the name allocated by the scripting module to reduce memory allocations when referencing types.
void Inline();
@@ -420,6 +421,15 @@ public:
return MoveTemp(v);
}
template<typename T>
static typename TEnableIf<TIsEnum<T>::Value, Variant>::Type Enum(const T value)
{
Variant v;
v.SetType(VariantType(VariantType::Enum, StaticType<T>().GetType()));
v.AsUint64 = (uint64)value;
return MoveTemp(v);
}
template<typename T>
static typename TEnableIf<!TIsEnum<T>::Value && !TIsPointer<T>::Value, Variant>::Type Structure(VariantType&& type, const T& value)
{
+8
View File
@@ -19,6 +19,14 @@ public class Debug : EngineModule
{
options.PublicDefinitions.Add("COMPILE_WITH_DEBUG_DRAW");
}
if (options.Target.IsEditor || options.Configuration != TargetConfiguration.Release)
{
// Used by DebugCommands to parse Xml documentation
options.ScriptingAPI.SystemReferences.Add("System.Xml");
options.ScriptingAPI.SystemReferences.Add("System.Xml.ReaderWriter");
options.ScriptingAPI.SystemReferences.Add("System.Text.RegularExpressions");
}
}
/// <inheritdoc />
+78 -21
View File
@@ -16,8 +16,11 @@
#include "Engine/Scripting/ManagedCLR/MMethod.h"
#include "Engine/Scripting/ManagedCLR/MField.h"
#include "Engine/Scripting/ManagedCLR/MProperty.h"
#include "Engine/Scripting/ManagedCLR/MUtils.h"
#include "FlaxEngine.Gen.h"
#define WITH_HELP (USE_EDITOR || !BUILD_RELEASE) && USE_CSHARP
struct CommandData
{
String Name;
@@ -26,6 +29,38 @@ struct CommandData
void* MethodGet = nullptr;
void* MethodSet = nullptr;
void* Field = nullptr;
#if WITH_HELP
mutable String Help;
StringView GetHelp() const
{
if (Help.IsEmpty())
{
if (dynamic_cast<ManagedBinaryModule*>(Module))
{
// Get C# type and member name
const MClass* mclass = nullptr;
StringAnsiView name;
if (auto field = (MField*)Field)
{
mclass = field->GetParentClass();
name = field->GetName();
}
else if (auto method = (MMethod*)(Method ? Method : (MethodGet ? MethodGet : MethodSet)))
{
mclass = method->GetParentClass();
name = method->GetName();
}
// Use Xml docs reader used by Editor to get tooltips
auto getXmlInternal = DebugCommands::TypeInitializer.GetClass()->GetMethod("GetXmlInternal", 2);
void* params[2] = { INTERNAL_TYPE_GET_OBJECT(mclass->GetType()), MUtils::ToString(name) };
Help = MUtils::ToString((MString*)getXmlInternal->Invoke(nullptr, params, nullptr));
}
}
return Help;
}
#endif
static void PrettyPrint(StringBuilder& sb, const Variant& value)
{
@@ -50,7 +85,7 @@ struct CommandData
else if (value.Type.Type == VariantType::Structure)
{
// Prettify structure printing
ScriptingTypeHandle resultType = Scripting::FindScriptingType(value.Type.GetTypeName());
ScriptingTypeHandle resultType = value.Type.GetScriptingType();
if (resultType)
{
Array<void*> fields;
@@ -122,7 +157,11 @@ struct CommandData
// Parse arguments
if (args == StringView(TEXT("?"), 1))
{
LOG(Warning, "TODO: debug commands help/docs printing"); // TODO: debug commands help/docs printing (use CodeDocsModule that parses XML docs)
#if WITH_HELP
// Print command description
LOG(Info, "> {} ?", Name);
LOG_STR(Info, GetHelp());
#endif
return;
}
Array<Variant> params;
@@ -356,6 +395,22 @@ namespace
InitCommands();
Locker.Unlock();
}
const CommandData* GetCommand(StringView command)
{
if (command.FindLast(' ') != -1)
command = command.Left(command.Find(' '));
// TODO: fix missing string handle on 1st command execution (command gets invalid after InitCommands due to dotnet GC or dotnet interop handles flush)
String commandCopy = command;
command = commandCopy;
EnsureInited();
for (auto& e : Commands)
{
if (e.Name == command)
return &e;
}
return nullptr;
}
}
class DebugCommandsService : public EngineService
@@ -475,30 +530,32 @@ void DebugCommands::GetAllCommands(Array<StringView>& commands)
DebugCommands::CommandFlags DebugCommands::GetCommandFlags(StringView command)
{
CommandFlags result = CommandFlags::None;
if (command.FindLast(' ') != -1)
command = command.Left(command.Find(' '));
// TODO: fix missing string handle on 1st command execution (command gets invalid after InitCommands due to dotnet GC or dotnet interop handles flush)
String commandCopy = command;
command = commandCopy;
EnsureInited();
for (auto& e : Commands)
if (auto cmd = GetCommand(command))
{
if (e.Name == command)
{
if (e.Method)
result |= CommandFlags::Exec;
else if (e.Field)
result |= CommandFlags::ReadWrite;
if (e.MethodGet)
result |= CommandFlags::Read;
if (e.MethodSet)
result |= CommandFlags::Write;
break;
}
if (cmd->Method)
result |= CommandFlags::Exec;
else if (cmd->Field)
result |= CommandFlags::ReadWrite;
if (cmd->MethodGet)
result |= CommandFlags::Read;
if (cmd->MethodSet)
result |= CommandFlags::Write;
}
return result;
}
StringView DebugCommands::GetCommandHelp(StringView command)
{
StringView result;
#if WITH_HELP
if (auto cmd = GetCommand(command))
{
result = cmd->GetHelp();
}
#endif
return result;
}
bool DebugCommands::Iterate(const StringView& searchText, int32& index)
{
EnsureInited();
+338
View File
@@ -1,6 +1,17 @@
// Copyright (c) Wojciech Figat. All rights reserved.
#if FLAX_EDITOR || !BUILD_RELEASE
#define WITH_HELP
#endif
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
namespace FlaxEngine
{
@@ -12,4 +23,331 @@ namespace FlaxEngine
public sealed class DebugCommand : Attribute
{
}
partial class DebugCommands
{
#if WITH_HELP
private static Dictionary<Assembly, Dictionary<string, string>> _xmlCache;
private static StringBuilder _sb;
internal static void ClearXml()
{
if (_xmlCache == null)
return;
foreach (var asm in _xmlCache.Keys.ToArray())
{
if (asm.IsCollectible)
_xmlCache.Remove(asm);
}
}
internal static string GetXmlInternal(Type type, string memberName)
{
// Redirect into type when no member specified
if (string.IsNullOrEmpty(memberName))
return GetXml(type);
// Redirect property function getter/setter into owning property docs
if (memberName.StartsWith("get_") || memberName.StartsWith("set_"))
memberName = memberName.Substring(4);
// Find member of that name
var members = type.GetMember(memberName, BindingFlags.Static | BindingFlags.Public);
if (members.Length == 0)
return null;
return GetXml(members[0]);
}
/// <summary>
/// Gets the XML docs text for the type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>The documentation help.</returns>
public static string GetXml(Type type)
{
var xml = GetXmlDocs(type.Assembly);
if (xml != null)
{
var key = "T:" + GetXmlKey(type.FullName);
if (xml.TryGetValue(key, out var xmlDoc))
return FilterWhitespaces(xmlDoc);
}
return null;
}
/// <summary>
/// Gets the XML docs text for the type member.
/// </summary>
/// <param name="member">The type member.</param>
/// <returns>The documentation help.</returns>
public static string GetXml(MemberInfo member)
{
string text = null;
var xml = GetXmlDocs(member.DeclaringType.Assembly);
if (xml != null)
{
// [Reference: MSDN Magazine, October 2019, Volume 34 Number 10, "Accessing XML Documentation via Reflection"]
// https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/october/csharp-accessing-xml-documentation-via-reflection
var memberType = member.MemberType;
string key = null;
if (memberType.HasFlag(MemberTypes.Field))
{
var fieldInfo = (FieldInfo)member;
key = "F:" + GetXmlKey(fieldInfo.DeclaringType.FullName) + "." + fieldInfo.Name;
}
else if (memberType.HasFlag(MemberTypes.Property))
{
var propertyInfo = (PropertyInfo)member;
key = "P:" + GetXmlKey(propertyInfo.DeclaringType.FullName) + "." + propertyInfo.Name;
}
else if (memberType.HasFlag(MemberTypes.Event))
{
var eventInfo = (EventInfo)member;
key = "E:" + GetXmlKey(eventInfo.DeclaringType.FullName) + "." + eventInfo.Name;
}
else if (memberType.HasFlag(MemberTypes.Constructor))
{
var constructorInfo = (ConstructorInfo)member;
key = GetXmlKey(constructorInfo);
}
else if (memberType.HasFlag(MemberTypes.Method))
{
var methodInfo = (MethodInfo)member;
key = GetXmlKey(methodInfo);
}
else if (memberType.HasFlag(MemberTypes.TypeInfo) || memberType.HasFlag(MemberTypes.NestedType))
{
var typeInfo = (TypeInfo)member;
key = "T:" + GetXmlKey(typeInfo.FullName);
}
if (key != null)
{
xml.TryGetValue(key, out text);
text = FilterWhitespaces(text);
}
// Customize tooltips for properties to be more human-readable in UI
if (text != null && memberType.HasFlag(MemberTypes.Property) && text.StartsWith("Gets or sets ", StringComparison.Ordinal))
{
text = text.Substring(13);
unsafe
{
fixed (char* e = text)
e[0] = char.ToUpper(e[0]);
}
}
}
return text;
}
private static string FilterWhitespaces(string str)
{
if (str != null && str.Contains(" ", StringComparison.Ordinal))
{
if (_sb == null)
_sb = new StringBuilder();
else
_sb.Clear();
var sb = _sb;
var prev = str[0];
sb.Append(prev);
for (int i = 1; i < str.Length; i++)
{
var c = str[i];
if (prev != ' ' || c != ' ')
sb.Append(c);
prev = c;
}
str = sb.ToString();
}
return str;
}
// [Reference: MSDN Magazine, October 2019, Volume 34 Number 10, "Accessing XML Documentation via Reflection"]
// https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/october/csharp-accessing-xml-documentation-via-reflection
private static string GetXmlKey(MethodInfo methodInfo)
{
var typeGenericMap = new Dictionary<string, int>();
var methodGenericMap = new Dictionary<string, int>();
ParameterInfo[] parameterInfos = methodInfo.GetParameters();
if (methodInfo.DeclaringType.IsGenericType)
{
var methods = methodInfo.DeclaringType.GetGenericTypeDefinition().GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
methodInfo = methods.First(x => x.MetadataToken == methodInfo.MetadataToken);
}
Type[] typeGenericArguments = methodInfo.DeclaringType.GetGenericArguments();
for (int i = 0; i < typeGenericArguments.Length; i++)
{
Type typeGeneric = typeGenericArguments[i];
typeGenericMap[typeGeneric.Name] = i;
}
Type[] methodGenericArguments = methodInfo.GetGenericArguments();
for (int i = 0; i < methodGenericArguments.Length; i++)
{
Type methodGeneric = methodGenericArguments[i];
methodGenericMap[methodGeneric.Name] = i;
}
string declarationTypeString = GetXmlKey(methodInfo.DeclaringType, false, typeGenericMap, methodGenericMap);
string memberNameString = methodInfo.Name;
string methodGenericArgumentsString = methodGenericMap.Count > 0 ? "``" + methodGenericMap.Count : string.Empty;
string parametersString = parameterInfos.Length > 0 ? "(" + string.Join(",", methodInfo.GetParameters().Select(x => GetXmlKey(x.ParameterType, true, typeGenericMap, methodGenericMap))) + ")" : string.Empty;
string key = "M:" + declarationTypeString + "." + memberNameString + methodGenericArgumentsString + parametersString;
if (methodInfo.Name is "op_Implicit" || methodInfo.Name is "op_Explicit")
{
key += "~" + GetXmlKey(methodInfo.ReturnType, true, typeGenericMap, methodGenericMap);
}
return key;
}
private static string GetXmlKey(ConstructorInfo constructorInfo)
{
var typeGenericMap = new Dictionary<string, int>();
var methodGenericMap = new Dictionary<string, int>();
ParameterInfo[] parameterInfos = constructorInfo.GetParameters();
Type[] typeGenericArguments = constructorInfo.DeclaringType.GetGenericArguments();
for (int i = 0; i < typeGenericArguments.Length; i++)
{
Type typeGeneric = typeGenericArguments[i];
typeGenericMap[typeGeneric.Name] = i;
}
string declarationTypeString = GetXmlKey(constructorInfo.DeclaringType, false, typeGenericMap, methodGenericMap);
string methodGenericArgumentsString = methodGenericMap.Count > 0 ? "``" + methodGenericMap.Count : string.Empty;
string parametersString = parameterInfos.Length > 0 ? "(" + string.Join(",", constructorInfo.GetParameters().Select(x => GetXmlKey(x.ParameterType, true, typeGenericMap, methodGenericMap))) + ")" : string.Empty;
return "M:" + declarationTypeString + "." + "#ctor" + methodGenericArgumentsString + parametersString;
}
internal static string GetXmlKey(Type type, bool isMethodParameter, Dictionary<string, int> typeGenericMap, Dictionary<string, int> methodGenericMap)
{
if (type.IsGenericParameter)
{
if (methodGenericMap.TryGetValue(type.Name, out var methodIndex))
return "``" + methodIndex;
if (typeGenericMap.TryGetValue(type.Name, out var typeKey))
return "`" + typeKey;
return "`";
}
if (type.HasElementType)
{
string elementTypeString = GetXmlKey(type.GetElementType(), isMethodParameter, typeGenericMap, methodGenericMap);
if (type.IsPointer)
return elementTypeString + "*";
if (type.IsByRef)
return elementTypeString + "@";
if (type.IsArray)
{
int rank = type.GetArrayRank();
string arrayDimensionsString = rank > 1 ? "[" + string.Join(",", Enumerable.Repeat("0:", rank)) + "]" : "[]";
return elementTypeString + arrayDimensionsString;
}
throw new Exception();
}
string prefaceString = type.IsNested ? GetXmlKey(type.DeclaringType, isMethodParameter, typeGenericMap, methodGenericMap) + "." : type.Namespace + ".";
string typeNameString = isMethodParameter ? Regex.Replace(type.Name, @"`\d+", string.Empty) : type.Name;
string genericArgumentsString = type.IsGenericType && isMethodParameter ? "{" + string.Join(",", type.GetGenericArguments().Select(argument => GetXmlKey(argument, true, typeGenericMap, methodGenericMap))) + "}" : string.Empty;
return prefaceString + typeNameString + genericArgumentsString;
}
private static string GetXmlKey(string typeFullNameString)
{
return Regex.Replace(typeFullNameString, @"\[.*\]", string.Empty).Replace('+', '.');
}
private static Dictionary<string, string> GetXmlDocs(Assembly assembly)
{
if (_xmlCache == null)
_xmlCache = new Dictionary<Assembly, Dictionary<string, string>>();
if (!_xmlCache.TryGetValue(assembly, out var result))
{
Profiler.BeginEvent("GetXmlDocs");
// Find XML file path (based on assembly location)
var assemblyPath = Utils.GetAssemblyLocation(assembly);
var assemblyName = assembly.GetName().Name;
var xmlFilePath = Path.ChangeExtension(assemblyPath, ".xml");
if (!File.Exists(assemblyPath) && !string.IsNullOrEmpty(assemblyPath))
{
var uri = new UriBuilder(assemblyPath);
var path = Uri.UnescapeDataString(uri.Path);
xmlFilePath = Path.Combine(Path.GetDirectoryName(path), assemblyName + ".xml");
}
if (File.Exists(xmlFilePath))
{
Profiler.BeginEvent(assemblyName);
try
{
// Parse xml documentation
using (var xmlReader = XmlReader.Create(new StreamReader(xmlFilePath)))
{
result = new Dictionary<string, string>();
StringBuilder content = new StringBuilder(2048);
while (xmlReader.Read())
{
if (xmlReader.NodeType == XmlNodeType.Element && string.Equals(xmlReader.Name, "member", StringComparison.Ordinal))
{
string rawName = xmlReader["name"];
var memberReader = xmlReader.ReadSubtree();
if (memberReader.ReadToDescendant("summary"))
{
content.Clear();
do
{
if (memberReader.NodeType == XmlNodeType.Element && memberReader.Read())
{
while (memberReader.NodeType == XmlNodeType.Text)
{
content.Append(memberReader.Value);
if (memberReader.Read() && memberReader.NodeType == XmlNodeType.Element)
{
var nodeRef = TrimRef(memberReader.GetAttribute("cref")); // <see cref=""/>
if (nodeRef == null)
nodeRef = memberReader.GetAttribute("name"); // <paramref name=""/>
content.Append(nodeRef);
memberReader.Read();
string TrimRef(string str)
{
if (str == null)
return null;
if (str.IndexOf(":FlaxEngine.") == 1)
return str.Substring("T:FlaxEngine.".Length);
return str.Substring("T:".Length);
}
}
}
}
if (memberReader.NodeType == XmlNodeType.EndElement && memberReader.Name == "summary")
break;
} while (memberReader.Read());
result[rawName] = content.ToString().Trim(' ', '\r', '\n');
}
}
}
}
}
catch
{
// Ignore errors
}
Profiler.EndEvent();
}
_xmlCache[assembly] = result;
Profiler.EndEvent();
}
return result;
}
#endif
}
}
+9
View File
@@ -56,8 +56,17 @@ public:
/// Returns flags of the command.
/// </summary>
/// <param name="command">The full name of the command.</param>
/// <returns>Command flags.</returns>
API_FUNCTION() static CommandFlags GetCommandFlags(StringView command);
/// <summary>
/// Returns help text of the command (from documentation comment).
/// </summary>
/// <remarks>Only available in non-Release builds and Editor.</remarks>
/// <param name="command">The full name of the command.</param>
/// <returns>Command help text or empty if failed to get it.</returns>
API_FUNCTION() static StringView GetCommandHelp(StringView command);
public:
static bool Iterate(const StringView& searchText, int32& index);
static StringView GetCommandName(int32 index);
+18
View File
@@ -0,0 +1,18 @@
namespace FlaxEngine;
partial class GameplayGlobals
{
/// <summary>
/// Gets a value of a given type from the global variables. (it must be added first).
/// </summary>
/// <param name="name">The name of the variable to retrieve.</param>
/// <typeparam name="T">The type of the variable to retrieve.</typeparam>
/// <returns>The value of the variable, or default if not found or type mismatch.</returns>
public T GetValue<T>(string name)
{
var obj = GetValue(name);
if (obj is T t)
return t;
return default;
}
}
-8
View File
@@ -13,8 +13,6 @@ API_CLASS(Static, Attributes="DebugCommand") class FLAXENGINE_API Globals
DECLARE_SCRIPTING_TYPE_NO_SPAWN(Globals);
public:
// Paths
// Main engine directory path.
API_FIELD(ReadOnly) static String StartupFolder;
@@ -54,8 +52,6 @@ public:
#endif
public:
// State
// True if fatal error occurred (engine is exiting).
// [Deprecated in v1.10]
DEPRECATED("Use Engine::FatalError instead.") static bool FatalErrorOccurred;
@@ -91,14 +87,10 @@ public:
DEPRECATED("Use Engine::ExitCode instead.") static int32 ExitCode;
public:
// Threading
// Main Engine thread id.
API_FIELD(ReadOnly) static uint64 MainThreadID;
public:
// Config
/// <summary>
/// The full engine version.
/// </summary>
@@ -76,9 +76,10 @@ void GPUTasksContext::OnFrameBegin()
{
auto task = _tasksSyncing[i];
auto state = task->GetState();
if (EnumHasAllFlags(task->Flags, ObjectFlags::WasMarkedToDelete))
state = TaskState::Finished;
if (task->GetSyncPoint() <= _currentSyncPoint && state != TaskState::Finished)
{
// TODO: add stats counter and count performed jobs, print to log on exit.
task->Sync();
}
if (state == TaskState::Failed || state == TaskState::Canceled)
+8 -8
View File
@@ -19,32 +19,32 @@ public:
API_FIELD() static bool UseVSync;
/// <summary>
/// Anti Aliasing quality setting.
/// Anti Aliasing quality setting. Available values are: Low, Medium, High, Ultra (or 0, 1, 2, 3).
/// </summary>
API_FIELD() static Quality AAQuality;
/// <summary>
/// Screen Space Reflections quality setting.
/// Screen Space Reflections quality setting. Available values are: Low, Medium, High, Ultra (or 0, 1, 2, 3).
/// </summary>
API_FIELD() static Quality SSRQuality;
/// <summary>
/// Screen Space Ambient Occlusion quality setting.
/// Screen Space Ambient Occlusion quality setting. Available values are: Low, Medium, High, Ultra (or 0, 1, 2, 3).
/// </summary>
API_FIELD() static Quality SSAOQuality;
/// <summary>
/// Volumetric Fog quality setting.
/// Volumetric Fog quality setting. Available values are: Low, Medium, High, Ultra (or 0, 1, 2, 3).
/// </summary>
API_FIELD() static Quality VolumetricFogQuality;
/// <summary>
/// The shadows quality.
/// The shadows quality. Available values are: Low, Medium, High, Ultra (or 0, 1, 2, 3).
/// </summary>
API_FIELD() static Quality ShadowsQuality;
/// <summary>
/// The shadow maps quality (textures resolution).
/// The shadow maps quality (textures resolution). Available values are: Low, Medium, High, Ultra (or 0, 1, 2, 3).
/// </summary>
API_FIELD() static Quality ShadowMapsQuality;
@@ -59,12 +59,12 @@ public:
API_FIELD() static bool AllowCSMBlending;
/// <summary>
/// The Global SDF quality. Controls the volume texture resolution and amount of cascades to use.
/// The Global SDF quality. Controls the volume texture resolution and amount of cascades to use. Available values are: Low, Medium, High, Ultra (or 0, 1, 2, 3).
/// </summary>
API_FIELD() static Quality GlobalSDFQuality;
/// <summary>
/// The Global Illumination quality. Controls the quality of the GI effect.
/// The Global Illumination quality. Controls the quality of the GI effect. Available values are: Low, Medium, High, Ultra (or 0, 1, 2, 3).
/// </summary>
API_FIELD() static Quality GIQuality;
@@ -460,6 +460,18 @@ void MaterialParameter::Bind(BindMeta& meta) const
ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(Float4)));
*((Float4*)(meta.Constants.Get() + _offset)) = (Float4)e->Value.AsDouble4();
break;
case VariantType::Int2:
ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(Int2)));
*((Int2*)(meta.Constants.Get() + _offset)) = (Int2)e->Value.AsInt2();
break;
case VariantType::Int3:
ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(Int3)));
*((Int3*)(meta.Constants.Get() + _offset)) = (Int3)e->Value.AsInt3();
break;
case VariantType::Int4:
ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(Int4)));
*((Int4*)(meta.Constants.Get() + _offset)) = (Int4)e->Value.AsInt4();
break;
default: ;
}
}
+10 -5
View File
@@ -621,7 +621,7 @@ namespace FlaxEngine
{
ibData = dataPtr[IB];
use16BitIndexBuffer = _formats[IB] == PixelFormat.R16_UInt;
triangles = (uint)(_data[IB].Length / PixelFormatExtensions.SizeInBytes(_formats[IB]));
triangles = (uint)(_data[IB].Length / (PixelFormatExtensions.SizeInBytes(_formats[IB]) * 3));
}
if (mesh.Init(vertices, triangles, vbData, ibData, use16BitIndexBuffer, vbLayout))
@@ -643,11 +643,16 @@ namespace FlaxEngine
else
{
Float3 min = Float3.Maximum, max = Float3.Minimum;
for (int i = 0; i < vertices; i++)
PixelFormatSampler.Get(positionStream.Format, out var positionSampler);
int positionStride = positionStream.Stride;
fixed (byte* data = positionStream.Data)
{
Float3 pos = positionStream.GetFloat3(i);
Float3.Min(ref min, ref pos, out min);
Float3.Max(ref max, ref pos, out max);
for (int i = 0; i < vertices; i++)
{
Float3 pos = new Float3(positionSampler.Read(data + i * positionStride));
Float3.Min(ref min, ref pos, out min);
Float3.Max(ref max, ref pos, out max);
}
}
bounds = new BoundingBox(min, max);
}
@@ -21,6 +21,13 @@ bool ModelInstanceEntries::HasContentLoaded() const
return result;
}
bool ModelInstanceEntries::ShouldSerialize(const void* otherObj) const
{
if (!otherObj)
return true;
return !(*this == *(const ModelInstanceEntries*)otherObj);
}
void ModelInstanceEntries::Serialize(SerializeStream& stream, const void* otherObj)
{
SERIALIZE_GET_OTHER_OBJ(ModelInstanceEntries);
@@ -43,12 +50,13 @@ void ModelInstanceEntries::Serialize(SerializeStream& stream, const void* otherO
void ModelInstanceEntries::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
{
PROFILE_MEM(Graphics);
const DeserializeStream& entries = stream["Entries"];
ASSERT(entries.IsArray());
Resize(entries.Size());
for (rapidjson::SizeType i = 0; i < entries.Size(); i++)
const DeserializeStream& entriesData = stream[rapidjson_flax::Value(rapidjson::StringRef("Entries", 7))];
CHECK(entriesData.IsArray());
Resize(entriesData.Size());
ModelInstanceEntry* entries = Get();
for (int32 i = 0; i < Count(); i++)
{
At(i).Deserialize((DeserializeStream&)entries[i], modifier);
entries[i].Deserialize((DeserializeStream&)entriesData[i], modifier);
}
}
@@ -115,6 +115,7 @@ public:
public:
// [ISerializable]
bool ShouldSerialize(const void* otherObj) const override;
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
};
+21 -11
View File
@@ -353,8 +353,11 @@ Viewport SceneRenderTask::GetViewport() const
viewport = Buffers->GetViewport();
else
viewport = Viewport(0, 0, 1280, 720);
viewport.Width *= RenderingPercentage;
viewport.Height *= RenderingPercentage;
PRAGMA_DISABLE_DEPRECATION_WARNINGS
float renderScale = RenderingPercentage * RenderScale;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
viewport.Width *= renderScale;
viewport.Height *= renderScale;
return viewport;
}
@@ -394,13 +397,16 @@ void SceneRenderTask::OnBegin(GPUContext* context)
}
// Setup render buffers for the output rendering resolution
PRAGMA_DISABLE_DEPRECATION_WARNINGS
float renderScale = RenderingPercentage * RenderScale;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
if (Output)
{
Buffers->Init((int32)((float)Output->Width() * RenderingPercentage), (int32)((float)Output->Height() * RenderingPercentage));
Buffers->Init((int32)((float)Output->Width() * renderScale), (int32)((float)Output->Height() * renderScale));
}
else if (SwapChain)
{
Buffers->Init((int32)((float)SwapChain->GetWidth() * RenderingPercentage), (int32)((float)SwapChain->GetHeight() * RenderingPercentage));
Buffers->Init((int32)((float)SwapChain->GetWidth() * renderScale), (int32)((float)SwapChain->GetHeight() * renderScale));
}
}
@@ -434,7 +440,10 @@ bool SceneRenderTask::Resize(int32 width, int32 height)
PROFILE_MEM(Graphics);
if (Output && Output->Resize(width, height))
return true;
if (Buffers && Buffers->Init((int32)((float)width * RenderingPercentage), (int32)((float)height * RenderingPercentage)))
PRAGMA_DISABLE_DEPRECATION_WARNINGS
float renderScale = RenderingPercentage * RenderScale;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
if (Buffers && Buffers->Init((int32)((float)width * renderScale), (int32)((float)height * renderScale)))
return true;
return false;
}
@@ -477,12 +486,6 @@ void MainRenderTask::OnBegin(GPUContext* context)
// Use the main camera for the game (can be later overriden in Begin event by external code)
Camera = Camera::GetMainCamera();
#if !USE_EDITOR
// Sync render buffers size with the backbuffer
const auto size = Screen::GetSize();
Buffers->Init((int32)(size.X * RenderingPercentage), (int32)(size.Y * RenderingPercentage));
#endif
SceneRenderTask::OnBegin(context);
}
@@ -507,3 +510,10 @@ RenderContextBatch::RenderContextBatch(const RenderContext& context)
Contexts.Add(context);
EnableAsync = JobSystem::GetThreadsCount() > 1;
}
void RenderContextBatch::FlushWaitLabels()
{
for (const int64 label : WaitLabels)
JobSystem::Wait(label);
WaitLabels.Clear();
}
+12 -1
View File
@@ -268,8 +268,14 @@ public:
/// <summary>
/// The scale of the rendering resolution relative to the output dimensions. If lower than 1 the scene and postprocessing will be rendered at a lower resolution and upscaled to the output backbuffer.
/// [Deprecated in v1.13]
/// </summary>
API_FIELD() float RenderingPercentage = 1.0f;
API_FIELD() DEPRECATED("Use RenderScale instead.") float RenderingPercentage = 1.0f;
/// <summary>
/// The scale of the rendering resolution relative to the output dimensions. If lower than 1 the scene and postprocessing will be rendered at a lower resolution and upscaled to the output backbuffer.
/// </summary>
API_FIELD() float RenderScale = 1.0f;
/// <summary>
/// The image resolution upscale location within rendering pipeline. Unused if RenderingPercentage is 1.
@@ -533,4 +539,9 @@ API_STRUCT(NoDefault) struct FLAXENGINE_API RenderContextBatch
{
return Contexts.Get()[0];
}
/// <summary>
/// Waits for all scheduled async jobs to complete and clears WaitLabels.
/// </summary>
void FlushWaitLabels();
};
+1 -1
View File
@@ -195,7 +195,7 @@ void RenderView::CopyFrom(const Camera* camera, const Viewport* viewport)
const Vector3 cameraPos = camera->GetPosition();
LargeWorlds::UpdateOrigin(Origin, cameraPos);
Position = cameraPos - Origin;
Direction = camera->GetDirection();
Direction = camera->GetForward();
Near = camera->GetNearPlane();
Far = camera->GetFarPlane();
camera->GetMatrices(View, Projection, viewport ? *viewport : camera->GetViewport(), Origin);
+1 -1
View File
@@ -96,7 +96,7 @@ namespace FlaxEngine
Vector3 cameraPos = camera.Position;
LargeWorlds.UpdateOrigin(ref Origin, cameraPos);
Position = cameraPos - Origin;
Direction = camera.Direction;
Direction = camera.Forward;
Near = camera.NearPlane;
Far = camera.FarPlane;
camera.GetMatrices(out View, out Projection, ref viewport, ref Origin);
@@ -360,14 +360,12 @@ bool ShaderAssetBase::LoadShaderCache(ShaderCacheResult& result)
return true;
}
ASSERT(result.Data.IsValid());
#if COMPILE_WITH_SHADER_COMPILER
// Read includes from cache
IsValidShaderCache(result.Data, result.Includes);
#endif
return false;
return result.Data.IsInvalid();
}
#if COMPILE_WITH_SHADER_COMPILER
@@ -57,7 +57,7 @@ void CmdBufferVulkan::End()
if (vkCmdEndDebugUtilsLabelEXT)
vkCmdEndDebugUtilsLabelEXT(GetHandle());
#endif
#if GPU_ENABLE_TRACY
#if VULKAN_USE_TRACY_GPU
tracy::EndVkZoneScope(_tracyZones.Last().Data);
_tracyZones.RemoveLast();
#endif
@@ -101,7 +101,7 @@ void CmdBufferVulkan::BeginEvent(const Char* name, void* tracyContext)
char buffer[60];
int32 bufferSize = StringUtils::Copy(buffer, name, sizeof(buffer));
#if GPU_ENABLE_TRACY
#if VULKAN_USE_TRACY_GPU
auto& zone = _tracyZones.AddOne();
tracy::BeginVkZoneScope(zone.Data, tracyContext, GetHandle(), buffer, bufferSize);
#endif
@@ -128,7 +128,7 @@ void CmdBufferVulkan::EndEvent()
vkCmdEndDebugUtilsLabelEXT(GetHandle());
#endif
#if GPU_ENABLE_TRACY
#if VULKAN_USE_TRACY_GPU
tracy::EndVkZoneScope(_tracyZones.Last().Data);
_tracyZones.RemoveLast();
#endif
@@ -43,8 +43,10 @@ private:
FenceVulkan* _fence;
#if GPU_ALLOW_PROFILE_EVENTS
int32 _eventsBegin = 0;
#if VULKAN_USE_TRACY_GPU
struct TracyZone { byte Data[TracyVulkanZoneSize]; };
Array<TracyZone, InlinedAllocation<32>> _tracyZones;
#endif
#endif
// The latest value when command buffer was submitted.
@@ -49,6 +49,10 @@
#define VULKAN_USE_TIMER_QUERIES 1
#endif
#ifndef VULKAN_USE_TRACY_GPU
#define VULKAN_USE_TRACY_GPU (GPU_ENABLE_TRACY && VULKAN_USE_TIMER_QUERIES)
#endif
// Fence wait operation timeout in seconds
#ifndef VULKAN_WAIT_TIMEOUT
#define VULKAN_WAIT_TIMEOUT 5.0f
@@ -111,7 +111,7 @@ GPUContextVulkan::GPUContextVulkan(GPUDeviceVulkan* device, QueueVulkan* queue)
_handlesSizes[(int32)SpirvShaderResourceBindingType::UAV] = GPU_MAX_UA_BINDED;
#endif
#if GPU_ENABLE_TRACY
#if VULKAN_USE_TRACY_GPU
#if VK_EXT_calibrated_timestamps && VK_EXT_host_query_reset && !PLATFORM_SWITCH
// Use calibrated timestamps extension
if (vkResetQueryPoolEXT && vkGetCalibratedTimestampsEXT && _device->PhysicalDeviceFeatures12.hostQueryReset)
@@ -138,7 +138,7 @@ GPUContextVulkan::GPUContextVulkan(GPUDeviceVulkan* device, QueueVulkan* queue)
GPUContextVulkan::~GPUContextVulkan()
{
#if GPU_ENABLE_TRACY
#if VULKAN_USE_TRACY_GPU
tracy::DestroyVkContext(_tracyContext);
#endif
for (int32 i = 0; i < _descriptorPools.Count(); i++)
@@ -799,7 +799,7 @@ void GPUContextVulkan::FrameEnd()
// Execute any queued layout transitions that weren't already handled by the render pass
FlushBarriers();
#if GPU_ENABLE_TRACY
#if VULKAN_USE_TRACY_GPU
if (cmdBuffer)
tracy::CollectVkContext(_tracyContext, cmdBuffer->GetHandle());
#endif
@@ -813,7 +813,7 @@ void GPUContextVulkan::FrameEnd()
void GPUContextVulkan::EventBegin(const Char* name)
{
const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer();
#if COMPILE_WITH_PROFILER
#if VULKAN_USE_TRACY_GPU
void* tracyContext = _tracyContext;
#else
void* tracyContext = nullptr;
@@ -45,7 +45,7 @@ static const char* GInstanceExtensions[] =
#if defined(VK_KHR_display) && 0
VK_KHR_DISPLAY_EXTENSION_NAME,
#endif
#if GPU_ENABLE_TRACY && VK_EXT_calibrated_timestamps && VK_EXT_host_query_reset
#if VULKAN_USE_TRACY_GPU && VK_EXT_calibrated_timestamps && VK_EXT_host_query_reset
VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, // Required by VK_EXT_host_query_reset (unless using Vulkan 1.1 or newer)
#endif
nullptr
@@ -66,7 +66,7 @@ static const char* GDeviceExtensions[] =
#if VK_KHR_sampler_mirror_clamp_to_edge
VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME,
#endif
#if GPU_ENABLE_TRACY && VK_EXT_calibrated_timestamps && VK_EXT_host_query_reset
#if VULKAN_USE_TRACY_GPU && VK_EXT_calibrated_timestamps && VK_EXT_host_query_reset
VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME,
VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME,
#endif
@@ -1677,7 +1677,7 @@ bool GPUDeviceVulkan::Init()
VulkanPlatform::RestrictEnabledPhysicalDeviceFeatures(PhysicalDeviceFeatures, enabledFeatures);
deviceInfo.pEnabledFeatures = &enabledFeatures;
#if GPU_ENABLE_TRACY && VK_EXT_calibrated_timestamps && VK_EXT_host_query_reset
#if VULKAN_USE_TRACY_GPU && VK_EXT_calibrated_timestamps && VK_EXT_host_query_reset
VkPhysicalDeviceHostQueryResetFeatures resetFeatures;
if (PhysicalDeviceFeatures12.hostQueryReset)
{
+19 -5
View File
@@ -822,6 +822,11 @@ void Actor::SetRotation(const Matrix& value)
}
void Actor::SetDirection(const Float3& value)
{
SetForward(value);
}
void Actor::SetForward(const Float3& value)
{
CHECK(!value.IsNanOrInfinity());
Quaternion orientation;
@@ -1714,17 +1719,22 @@ Actor* Actor::Intersects(const Ray& ray, Real& distance, Vector3& normal)
void Actor::LookAt(const Vector3& worldPos)
{
const Quaternion orientation = LookingAt(worldPos);
const Quaternion orientation = GetLookAtDirection(worldPos);
SetOrientation(orientation);
}
void Actor::LookAt(const Vector3& worldPos, const Vector3& worldUp)
{
const Quaternion orientation = LookingAt(worldPos, worldUp);
const Quaternion orientation = GetLookAtDirection(worldPos, worldUp);
SetOrientation(orientation);
}
Quaternion Actor::LookingAt(const Vector3& worldPos) const
{
return GetLookAtDirection(worldPos);
}
Quaternion Actor::GetLookAtDirection(const Vector3& worldPos) const
{
const Vector3 direction = worldPos - _transform.Translation;
if (direction.LengthSquared() < ZeroTolerance)
@@ -1752,16 +1762,20 @@ Quaternion Actor::LookingAt(const Vector3& worldPos) const
}
Quaternion Actor::LookingAt(const Vector3& worldPos, const Vector3& worldUp) const
{
return GetLookAtDirection(worldPos, worldUp);
}
Quaternion Actor::GetLookAtDirection(const Vector3& worldPos, const Vector3& worldUp) const
{
const Vector3 direction = worldPos - _transform.Translation;
if (direction.LengthSquared() < ZeroTolerance)
return _parent ? _parent->GetOrientation() : Quaternion::Identity;
const Float3 forward = Vector3::Normalize(direction);
const Float3 up = Vector3::Normalize(worldUp);
if (Math::IsOne(Float3::Dot(forward, up)))
{
return LookingAt(worldPos);
}
return GetLookAtDirection(worldPos);
Quaternion orientation;
Quaternion::LookRotation(direction, up, orientation);
+37 -4
View File
@@ -549,17 +549,35 @@ public:
/// <summary>
/// Gets actor direction vector (forward vector).
/// [Deprecated in v1.13]
/// </summary>
API_PROPERTY(Attributes="HideInEditor, NoSerialize") FORCE_INLINE Float3 GetDirection() const
API_PROPERTY(Attributes="HideInEditor, NoSerialize") DEPRECATED("Use GetForward instead.")
FORCE_INLINE Float3 GetDirection() const
{
return GetForward();
}
/// <summary>
/// Gets the actor's forward vector (direction).
/// </summary>
API_PROPERTY(Attributes="HideInEditor, NoSerialize") FORCE_INLINE Float3 GetForward() const
{
return Float3::Transform(Float3::Forward, GetOrientation());
}
/// <summary>
/// Sets actor direction vector (forward)
/// [Deprecated in v1.13]
/// </summary>
/// <param name="value">The value to set.</param>
API_PROPERTY() void SetDirection(const Float3& value);
API_PROPERTY() DEPRECATED("Use SetForward instead.")
void SetDirection(const Float3& value);
/// <summary>
/// Rotates the actor to align its forward vector with the passed in value (direction).
/// </summary>
/// <param name="value">The value to align to.</param>
API_PROPERTY() void SetForward(const Float3& value);
public:
/// <summary>
@@ -897,16 +915,31 @@ public:
/// <summary>
/// Gets rotation of the actor oriented towards the specified world position.
/// [Deprecated in v1.13]
/// </summary>
/// <param name="worldPos">The world position to orient towards.</param>
API_FUNCTION() Quaternion LookingAt(const Vector3& worldPos) const;
API_FUNCTION() DEPRECATED("Use GetLookAtDirection instead.") Quaternion LookingAt(const Vector3& worldPos) const;
/// <summary>
/// Gets rotation of the actor oriented towards the specified world position.
/// </summary>
/// <param name="worldPos">The world position to orient towards.</param>
API_FUNCTION() Quaternion GetLookAtDirection(const Vector3& worldPos) const;
/// <summary>
/// Gets rotation of the actor oriented towards the specified world position with upwards direction.
/// [Deprecated in v1.13]
/// </summary>
/// <param name="worldPos">The world position to orient towards.</param>
/// <param name="worldUp">The up direction that constrains up axis orientation to a plane this vector lies on. This rule might be broken if forward and up direction are nearly parallel.</param>
API_FUNCTION() DEPRECATED("Use GetLookAtDirection instead.") Quaternion LookingAt(const Vector3& worldPos, const Vector3& worldUp) const;
/// <summary>
/// Gets rotation of the actor oriented towards the specified world position with upwards direction.
/// </summary>
/// <param name="worldPos">The world position to orient towards.</param>
/// <param name="worldUp">The up direction that constrains up axis orientation to a plane this vector lies on. This rule might be broken if forward and up direction are nearly parallel.</param>
API_FUNCTION() Quaternion LookingAt(const Vector3& worldPos, const Vector3& worldUp) const;
API_FUNCTION() Quaternion GetLookAtDirection(const Vector3& worldPos, const Vector3& worldUp) const;
public:
/// <summary>
+3 -7
View File
@@ -820,8 +820,7 @@ void AnimatedModel::RunBlendShapeDeformer(const MeshBase* mesh, MeshDeformationD
void AnimatedModel::BeginPlay(SceneBeginData* data)
{
if (SkinnedModel && SkinnedModel->IsLoaded())
PreInitSkinningData();
PreInitSkinningData();
// Base
ModelInstanceActor::BeginPlay(data);
@@ -1263,9 +1262,7 @@ void AnimatedModel::Serialize(SerializeStream& stream, const void* otherObj)
SERIALIZE(ShadowsMode);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
SERIALIZE(RootMotionTarget);
stream.JKEY("Buffer");
stream.Object(&Entries, other ? &other->Entries : nullptr);
SERIALIZE_MEMBER(Buffer, Entries);
}
void AnimatedModel::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
@@ -1290,8 +1287,7 @@ void AnimatedModel::Deserialize(DeserializeStream& stream, ISerializeModifier* m
DESERIALIZE(ShadowsMode);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
DESERIALIZE(RootMotionTarget);
Entries.DeserializeIfExists(stream, "Buffer", modifier);
DESERIALIZE_MEMBER(Buffer, Entries);
// [Deprecated on 07.02.2022, expires on 07.02.2024]
if (modifier->EngineBuild <= 6330)
+27 -17
View File
@@ -98,99 +98,99 @@ public:
/// <summary>
/// The skinned model asset used for rendering.
/// </summary>
API_FIELD(Attributes="EditorOrder(10), DefaultValue(null), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(10), DefaultValue(null), EditorDisplay(\"Skeleton\")")
AssetReference<SkinnedModel> SkinnedModel;
/// <summary>
/// The animation graph asset used for the skinned mesh skeleton bones evaluation (controls the animation).
/// </summary>
API_FIELD(Attributes="EditorOrder(15), DefaultValue(null), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(15), DefaultValue(null), EditorDisplay(\"Skeleton\")")
AssetReference<AnimationGraph> AnimationGraph;
/// <summary>
/// If true, use per-bone motion blur on this skeletal model. It requires additional rendering, can be disabled to save performance.
/// </summary>
API_FIELD(Attributes="EditorOrder(20), DefaultValue(true), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(20), DefaultValue(true), EditorDisplay(\"Drawing\")")
bool PerBoneMotionBlur = true;
/// <summary>
/// If true, animation speed will be affected by the global timescale parameter.
/// </summary>
API_FIELD(Attributes="EditorOrder(30), DefaultValue(true), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(30), DefaultValue(true), EditorDisplay(\"Updating\")")
bool UseTimeScale = true;
/// <summary>
/// If true, the animation will be updated even when an actor cannot be seen by any camera. Otherwise, the animations themselves will also stop running when the actor is off-screen.
/// </summary>
API_FIELD(Attributes="EditorOrder(40), DefaultValue(false), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(40), DefaultValue(false), EditorDisplay(\"Updating\")")
bool UpdateWhenOffscreen = false;
/// <summary>
/// The animation update delta timescale. Can be used to speed up animation playback or create slow motion effect.
/// </summary>
API_FIELD(Attributes="EditorOrder(45), Limit(0, float.MaxValue, 0.025f), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(45), Limit(0, float.MaxValue, 0.025f), EditorDisplay(\"Updating\")")
float UpdateSpeed = 1.0f;
/// <summary>
/// The animation update mode. Can be used to optimize the performance.
/// </summary>
API_FIELD(Attributes="EditorOrder(50), DefaultValue(AnimationUpdateMode.Auto), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(50), DefaultValue(AnimationUpdateMode.Auto), EditorDisplay(\"Updating\")")
AnimationUpdateMode UpdateMode = AnimationUpdateMode::Auto;
/// <summary>
/// The master scale parameter for the actor bounding box. Helps to reduce mesh flickering effect on screen edges.
/// </summary>
API_FIELD(Attributes="EditorOrder(60), DefaultValue(1.5f), Limit(0, float.MaxValue, 0.025f), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(60), DefaultValue(1.5f), Limit(0, float.MaxValue, 0.025f), EditorDisplay(\"Drawing\")")
float BoundsScale = 1.5f;
/// <summary>
/// The custom bounds(in actor local space). If set to empty bounds then source skinned model bind pose bounds will be used.
/// </summary>
API_FIELD(Attributes="EditorOrder(70), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(70), EditorDisplay(\"Drawing\")")
BoundingBox CustomBounds = BoundingBox::Zero;
/// <summary>
/// The model Level Of Detail bias value. Allows to increase or decrease rendered model quality.
/// </summary>
API_FIELD(Attributes="EditorOrder(80), DefaultValue(0), Limit(-100, 100, 0.1f), EditorDisplay(\"Skinned Model\", \"LOD Bias\")")
API_FIELD(Attributes="EditorOrder(80), DefaultValue(0), Limit(-100, 100, 0.1f), EditorDisplay(\"Drawing\", \"LOD Bias\")")
int32 LODBias = 0;
/// <summary>
/// Gets the model forced Level Of Detail index. Allows to bind the given model LOD to show. Value -1 disables this feature.
/// </summary>
API_FIELD(Attributes="EditorOrder(90), DefaultValue(-1), Limit(-1, 100, 0.1f), EditorDisplay(\"Skinned Model\", \"Forced LOD\")")
API_FIELD(Attributes="EditorOrder(90), DefaultValue(-1), Limit(-1, 100, 0.1f), EditorDisplay(\"Drawing\", \"Forced LOD\")")
int32 ForcedLOD = -1;
/// <summary>
/// The draw passes to use for rendering this object.
/// </summary>
API_FIELD(Attributes="EditorOrder(100), DefaultValue(DrawPass.Default), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(100), DefaultValue(DrawPass.Default), EditorDisplay(\"Drawing\")")
DrawPass DrawModes = DrawPass::Default;
/// <summary>
/// The object sort order key used when sorting drawable objects during rendering. Use lower values to draw object before others, higher values are rendered later (on top). Can be used to control transparency drawing.
/// </summary>
API_FIELD(Attributes="EditorDisplay(\"Skinned Model\"), EditorOrder(110), DefaultValue(0)")
API_FIELD(Attributes="EditorOrder(110), DefaultValue(0), EditorDisplay(\"Drawing\")")
int8 SortOrder = 0;
/// <summary>
/// The shadows casting mode.
/// [Deprecated on 26.10.2022, expires on 26.10.2024]
/// </summary>
API_FIELD(Attributes="EditorOrder(110), DefaultValue(ShadowsCastingMode.All), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(110), DefaultValue(ShadowsCastingMode.All), EditorDisplay(\"Drawing\")")
DEPRECATED() ShadowsCastingMode ShadowsMode = ShadowsCastingMode::All;
/// <summary>
/// The animation root motion apply target. If not specified the animated model will apply it itself.
/// </summary>
API_FIELD(Attributes="EditorOrder(120), DefaultValue(null), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(120), DefaultValue(null), EditorDisplay(\"Skeleton\")")
ScriptingObjectReference<Actor> RootMotionTarget;
#if USE_EDITOR
/// <summary>
/// If checked, the skeleton pose will be shawn during debug shapes drawing.
/// If checked, the skeleton pose will be shown during debug shapes drawing.
/// </summary>
API_FIELD(Attributes="EditorOrder(200), EditorDisplay(\"Skinned Model\")") bool ShowDebugDrawSkeleton = false;
API_FIELD(Attributes="EditorOrder(200), EditorDisplay(\"Skeleton\"), VisibleIf(nameof(ShowDebugDrawOptions))") bool ShowDebugDrawSkeleton = false;
#endif
public:
@@ -440,6 +440,16 @@ public:
API_FUNCTION() bool IsPlayingSlotAnimation(const StringView& slotName, Animation* anim = nullptr);
private:
#if USE_EDITOR
/// <summary>
/// Used to hide <see cref="ShowDebugDrawSkeleton"/> options if when the skinned model or animation graph is null.
/// </summary>
API_PROPERTY(Attributes="HideInEditor, NoSerialize") bool GetShowDebugDrawOptions() const
{
return SkinnedModel != nullptr && AnimationGraph != nullptr;
}
#endif
void ApplyRootMotion(const Transform& rootMotionDelta);
void SyncParameters();
void RunBlendShapeDeformer(const MeshBase* mesh, struct MeshDeformationData& deformation);
+1 -1
View File
@@ -210,7 +210,7 @@ Ray Camera::ConvertMouseToRay(const Float2& mousePosition, const Viewport& viewp
{
Vector3 position = GetPosition();
if (viewport.Width < ZeroTolerance || viewport.Height < ZeroTolerance || mousePosition.IsNaN())
return Ray(position, GetDirection());
return Ray(position, GetForward());
// Use different logic in orthographic projection
if (!_usePerspective)
@@ -30,7 +30,7 @@ void DirectionalLight::Draw(RenderContext& renderContext)
data.ShadowsDistance = ShadowsDistance;
data.Color = Color.ToFloat3() * (Color.A * brightness);
data.ShadowsStrength = ShadowsStrength;
data.Direction = GetDirection();
data.Direction = GetForward();
data.ShadowsFadeDistance = ShadowsFadeDistance;
data.ShadowsNormalOffsetScale = ShadowsNormalOffsetScale;
data.ShadowsDepthBias = ShadowsDepthBias;
@@ -163,7 +163,7 @@ void ExponentialHeightFog::GetExponentialHeightFogData(const RenderView& view, S
result.FogCutoffDistance = FogCutoffDistance >= 0 ? FogCutoffDistance : view.Far + FogCutoffDistance;
if (useDirectionalLightInscattering)
{
result.InscatteringLightDirection = -DirectionalInscatteringLight->GetDirection();
result.InscatteringLightDirection = -DirectionalInscatteringLight->GetForward();
result.DirectionalInscatteringColor = DirectionalInscatteringColor.ToFloat3();
result.DirectionalInscatteringExponent = Math::Clamp(DirectionalInscatteringExponent, 0.000001f, 1000.0f);
result.DirectionalInscatteringStartDistance = Math::Min(DirectionalInscatteringStartDistance, view.Far - 1.0f);
+1 -1
View File
@@ -73,7 +73,7 @@ void Sky::InitConfig(ShaderAtmosphericFogData& config) const
if (SunLight)
{
config.AtmosphericFogSunDirection = -SunLight->GetDirection();
config.AtmosphericFogSunDirection = -SunLight->GetForward();
config.AtmosphericFogSunColor = SunLight->Color.ToFloat3() * SunLight->Color.A;
}
else
+2 -5
View File
@@ -497,9 +497,7 @@ void SplineModel::Serialize(SerializeStream& stream, const void* otherObj)
SERIALIZE_MEMBER(PreTransform, _preTransform)
SERIALIZE(Model);
SERIALIZE(DrawModes);
stream.JKEY("Buffer");
stream.Object(&Entries, other ? &other->Entries : nullptr);
SERIALIZE_MEMBER(Buffer, Entries);
}
void SplineModel::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
@@ -514,8 +512,7 @@ void SplineModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod
DESERIALIZE_MEMBER(PreTransform, _preTransform);
DESERIALIZE(Model);
DESERIALIZE(DrawModes);
Entries.DeserializeIfExists(stream, "Buffer", modifier);
DESERIALIZE_MEMBER(Buffer, Entries);
// [Deprecated on 07.02.2022, expires on 07.02.2024]
if (modifier->EngineBuild <= 6330)
+4 -4
View File
@@ -27,7 +27,7 @@ SpotLight::SpotLight(const SpawnParams& params)
_cosInnerCone = Math::Cos(_innerConeAngle * DegreesToRadians);
_invCosConeDifference = 1.0f / (_cosInnerCone - _cosOuterCone);
const float boundsRadius = Math::Sqrt(1.25f * _radius * _radius - _radius * _radius * _cosOuterCone);
_sphere = BoundingSphere(GetPosition() + 0.5f * GetDirection() * _radius, boundsRadius);
_sphere = BoundingSphere(GetPosition() + 0.5f * GetForward() * _radius, boundsRadius);
BoundingBox::FromSphere(_sphere, _box);
}
@@ -113,7 +113,7 @@ void SpotLight::UpdateBounds()
// Note: we use the law of cosines to find the distance to the furthest edge of the spotlight cone from a position that is halfway down the spotlight direction
const float radius = GetScaledRadius();
const float boundsRadius = Math::Sqrt(1.25f * radius * radius - radius * radius * _cosOuterCone);
_sphere = BoundingSphere(GetPosition() + 0.5f * GetDirection() * radius, boundsRadius);
_sphere = BoundingSphere(GetPosition() + 0.5f * GetForward() * radius, boundsRadius);
BoundingBox::FromSphere(_sphere, _box);
if (_sceneRenderingKey != -1)
@@ -199,7 +199,7 @@ void SpotLight::OnDebugDrawSelected()
const auto color = Color::Yellow;
Vector3 right = _transform.GetRight();
Vector3 up = _transform.GetUp();
Vector3 forward = GetDirection();
Vector3 forward = GetForward();
float radius = GetScaledRadius();
float discRadius = radius * Math::Tan(_outerConeAngle * DegreesToRadians);
float falloffDiscRadius = radius * Math::Tan(_innerConeAngle * DegreesToRadians);
@@ -231,7 +231,7 @@ void SpotLight::DrawLightsDebug(RenderView& view)
const auto color = Color::Yellow;
Vector3 right = _transform.GetRight();
Vector3 up = _transform.GetUp();
Vector3 forward = GetDirection();
Vector3 forward = GetForward();
float radius = GetScaledRadius();
float discRadius = radius * Math::Tan(_outerConeAngle * DegreesToRadians);
float falloffDiscRadius = radius * Math::Tan(_innerConeAngle * DegreesToRadians);
+2 -4
View File
@@ -464,8 +464,7 @@ void StaticModel::Serialize(SerializeStream& stream, const void* otherObj)
stream.Rectangle(Lightmap.UVsArea);
}
stream.JKEY("Buffer");
stream.Object(&Entries, other ? &other->Entries : nullptr);
SERIALIZE_MEMBER(Buffer, Entries);
if (_vertexColorsCount)
{
@@ -504,8 +503,7 @@ void StaticModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod
DESERIALIZE_MEMBER(DrawModes, _drawModes);
DESERIALIZE_MEMBER(LightmapIndex, Lightmap.TextureIndex);
DESERIALIZE_MEMBER(LightmapArea, Lightmap.UVsArea);
Entries.DeserializeIfExists(stream, "Buffer", modifier);
DESERIALIZE_MEMBER(Buffer, Entries);
{
const auto member = stream.FindMember("VertexColors");
+35 -25
View File
@@ -24,6 +24,7 @@
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Threading/MainThreadTask.h"
#include "Editor/Editor.h"
#include "FlaxEngine.Gen.h"
// Apply flow:
// - collect all prefabs using this prefab (load and create default instances)
@@ -772,7 +773,13 @@ bool Prefab::ApplyAll(Actor* targetActor)
if (ApplyAllInternal(targetActor, true, thisPrefabInstancesData))
return true;
SyncNestedPrefabs(allPrefabs, allPrefabsInstancesData);
// Sync nested prefabs
if (allPrefabs.HasItems())
{
LOG(Info, "Updating referencing prefabs");
HashSet<Guid> synced;
SyncNestedPrefabs(allPrefabs, allPrefabsInstancesData, synced);
}
const auto endTime = DateTime::NowUTC();
LOG(Info, "Prefab updated! {0} ms", (int32)(endTime - startTime).GetTotalMilliseconds());
@@ -1027,8 +1034,14 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr
rapidjson_flax::Document targetDataDocument;
if (NestedPrefabs.HasItems())
{
// Use initial data buffer (unstripped) but reorder objects to match the sequence (eg. when new object was added to the nested prefab)
targetDataDocument.Parse(dataBuffer.GetString(), dataBuffer.GetSize());
SceneObjectsFactory::PrefabSyncData prefabSyncData(*sceneObjects.Value, targetDataDocument, modifier.Value);
Array<SceneObject*> reorderedObjects = *sceneObjects.Value;
newPrefabInstanceIdToDataIndexCounter = 0;
for (auto i = newPrefabInstanceIdToDataIndex.Begin(); i.IsNotEnd(); ++i)
reorderedObjects.Insert(i->Value, sceneObjects->At(newPrefabInstanceIdToDataIndexStart + newPrefabInstanceIdToDataIndexCounter++));
reorderedObjects.Resize(sceneObjects.Value->Count()); // reorderedObjects matches order in targetDataDocument
SceneObjectsFactory::PrefabSyncData prefabSyncData(reorderedObjects, targetDataDocument, modifier.Value);
SceneObjectsFactory::SetupPrefabInstances(context, prefabSyncData);
if (context.Instances.HasItems())
@@ -1236,7 +1249,7 @@ bool Prefab::UpdateInternal(const Array<SceneObject*>& defaultInstanceObjects, r
{
return Init(TypeName, StringAnsiView(tmpBuffer.GetString(), (int32)tmpBuffer.GetSize()));
}
#if 1 // Set to 0 to use memory-only reload that does not modifies the source file - useful for testing and debugging prefabs apply
#if 1 // Set to 0 to use memory-only reload that does not modify the source file - useful for testing and debugging prefabs apply
#if COMPILE_WITH_ASSETS_IMPORTER
Locker.Unlock();
@@ -1295,7 +1308,7 @@ bool Prefab::UpdateInternal(const Array<SceneObject*>& defaultInstanceObjects, r
_defaultInstance->DeleteObject();
_defaultInstance = nullptr;
}
_isLoaded = false;
_loadState = 0;
// Update prefab data manually (to prevent updating source asset file - just for testing)
Document.Parse(buffer.GetString(), buffer.GetSize());
@@ -1348,7 +1361,7 @@ bool Prefab::UpdateInternal(const Array<SceneObject*>& defaultInstanceObjects, r
NestedPrefabs.Add(prefabId);
}
}
_isLoaded = true;
_loadState = 1;
}
#endif
@@ -1395,34 +1408,31 @@ bool Prefab::SyncChangesInternal(PrefabInstancesData& prefabInstancesData)
return ApplyAllInternal(targetActor, false, prefabInstancesData);
}
void Prefab::SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array<PrefabInstancesData>& allPrefabsInstancesData) const
void Prefab::SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array<PrefabInstancesData>& allPrefabsInstancesData, HashSet<Guid>& synced) const
{
PROFILE_CPU();
LOG(Info, "Updating referencing prefabs");
// TODO: this may not work well for very complex prefab nesting -> loop order matters, maybe build a graph of dependencies?
// Call recursive for all referencing prefab assets to refresh nested prefabs
for (int32 i = 0; i < allPrefabs.Count(); i++)
{
auto nestedPrefab = allPrefabs[i].Get();
if (nestedPrefab)
Prefab* nestedPrefab = allPrefabs[i].Get();
if (!nestedPrefab || synced.Contains(nestedPrefab->GetID()))
continue;
if (nestedPrefab->WaitForLoaded())
{
if (nestedPrefab->WaitForLoaded())
{
LOG(Warning, "Waiting for prefab asset load failed.");
continue;
}
LOG(Warning, "Waiting for '{}' load failed.", nestedPrefab->ToString());
continue;
}
// Sync only if prefab is used by this prefab (directly) and it has been captured before
const int32 nestedPrefabIndex = nestedPrefab->NestedPrefabs.Find(GetID());
if (nestedPrefabIndex != -1)
{
if (nestedPrefab->SyncChangesInternal(allPrefabsInstancesData[i]))
continue;
nestedPrefab->SyncNestedPrefabs(allPrefabs, allPrefabsInstancesData);
ObjectsRemovalService::Flush();
}
// Sync only if prefab is used by this prefab (directly) and it has been captured before
const int32 nestedPrefabIndex = nestedPrefab->NestedPrefabs.Find(GetID());
if (nestedPrefabIndex != -1)
{
synced.Add(nestedPrefab->GetID());
if (nestedPrefab->SyncChangesInternal(allPrefabsInstancesData[i]))
continue;
nestedPrefab->SyncNestedPrefabs(allPrefabs, allPrefabsInstancesData, synced);
ObjectsRemovalService::Flush();
}
}
}
+4 -1
View File
@@ -7,6 +7,7 @@
#include "Engine/Core/Log.h"
#include "Engine/Level/Prefabs/PrefabManager.h"
#include "Engine/Level/Actor.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Scripting/Scripting.h"
@@ -22,6 +23,7 @@ Prefab::Prefab(const SpawnParams& params, const AssetInfo* info)
Guid Prefab::GetRootObjectId() const
{
PROFILE_CPU();
ASSERT(IsLoaded());
ScopeLock lock(Locker);
@@ -57,11 +59,12 @@ Actor* Prefab::GetDefaultInstance()
// Skip if already created (reuse cached result)
if (_defaultInstance)
return _defaultInstance;
PROFILE_CPU();
// Skip if not loaded
if (!IsLoaded())
{
LOG(Warning, "Cannot instantiate object from not loaded prefab asset.");
LOG(Warning, "Cannot instantiate object from not loaded prefab asset ({}, {})", GetPath(), GetID());
return nullptr;
}
+1 -1
View File
@@ -104,7 +104,7 @@ private:
bool ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPrefab, PrefabInstancesData& prefabInstancesData);
bool UpdateInternal(const Array<SceneObject*>& defaultInstanceObjects, rapidjson_flax::StringBuffer& tmpBuffer);
bool SyncChangesInternal(PrefabInstancesData& prefabInstancesData);
void SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array<PrefabInstancesData>& allPrefabsInstancesData) const;
void SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array<PrefabInstancesData>& allPrefabsInstancesData, HashSet<Guid, HeapAllocation>& synced) const;
#endif
void DeleteDefaultInstance();
+1 -1
View File
@@ -752,7 +752,7 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Context& context, PrefabSyn
obj->SetOrderInParent(order);
}
// Setup hierarchy for the prefab instances (ensure any new objects are connected)
// Setup hierarchy for the prefab instances (after adding new objects to ensure they are connected, eg. when reparenting existing prefab into a new root)
for (const auto& instance : context.Instances)
{
const auto& prefabStartData = data.Data[instance.StatIndex];
@@ -27,65 +27,41 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParameters(Box* box, Node* nod
switch (param->Type.Type)
{
case VariantType::Float2:
switch (box->ID)
{
case 1:
case 2:
if (box->ID >= 1 && box->ID <= 2)
value = value.AsFloat2().Raw[box->ID - 1];
break;
}
break;
case VariantType::Float3:
switch (box->ID)
{
case 1:
case 2:
case 3:
if (box->ID >= 1 && box->ID <= 3)
value = value.AsFloat3().Raw[box->ID - 1];
break;
}
break;
case VariantType::Float4:
case VariantType::Color:
switch (box->ID)
{
case 1:
case 2:
case 3:
case 4:
if (box->ID >= 1 && box->ID <= 4)
value = value.AsFloat4().Raw[box->ID - 1];
break;
}
break;
case VariantType::Double2:
switch (box->ID)
{
case 1:
case 2:
if (box->ID >= 1 && box->ID <= 2)
value = value.AsDouble2().Raw[box->ID - 1];
break;
}
break;
case VariantType::Double3:
switch (box->ID)
{
case 1:
case 2:
case 3:
if (box->ID >= 1 && box->ID <= 3)
value = value.AsDouble3().Raw[box->ID - 1];
break;
}
break;
case VariantType::Double4:
switch (box->ID)
{
case 1:
case 2:
case 3:
case 4:
if (box->ID >= 1 && box->ID <= 4)
value = value.AsDouble4().Raw[box->ID - 1];
break;
}
break;
case VariantType::Int2:
if (box->ID >= 1 && box->ID <= 2)
value = value.AsInt2().Raw[box->ID - 1];
break;
case VariantType::Int3:
if (box->ID >= 1 && box->ID <= 3)
value = value.AsInt3().Raw[box->ID - 1];
break;
case VariantType::Int4:
if (box->ID >= 1 && box->ID <= 4)
value = value.AsInt4().Raw[box->ID - 1];
break;
case VariantType::Matrix:
{
+9
View File
@@ -184,6 +184,15 @@ PhysicsScene* Physics::FindScene(const StringView& name)
return nullptr;
}
void Physics::DeleteScene(PhysicsScene* scene)
{
if (scene == nullptr || scene == DefaultScene)
return;
scene->CollectResults();
Scenes.RemoveKeepOrder(scene);
Delete(scene);
}
bool Physics::GetAutoSimulation()
{
return !DefaultScene || DefaultScene->GetAutoSimulation();
+5
View File
@@ -32,6 +32,11 @@ API_CLASS(Static) class FLAXENGINE_API Physics
/// </summary>
API_FUNCTION() static PhysicsScene* FindScene(const StringView& name);
/// <summary>
/// Delete an existing scene (excluding the default one).
/// </summary>
API_FUNCTION() static void DeleteScene(PhysicsScene* scene);
public:
/// <summary>
/// The automatic simulation feature. True if perform physics simulation after on fixed update by auto, otherwise user should do it.
+1 -1
View File
@@ -36,7 +36,7 @@ public class Platform : EngineModule
{
options.OutputFiles.Add("dbghelp.lib");
options.DelayLoadLibraries.Add("dbghelp.dll");
options.DependencyFiles.Add(Path.Combine(options.DepsFolder, "dbghelp.dll"));
options.OptionalDependencyFiles.Add(Path.Combine(options.DepsFolder, "dbghelp.dll"));
}
if (options.Target.IsEditor)
{
+6
View File
@@ -75,6 +75,12 @@
Platform::CheckFailed(#expression, __FILE__, __LINE__); \
return returnValue; \
}
// Performs a soft check of the expression. Logs the expression failure and continues execution.
#define CHECK_NO_RETURN(expression) \
if (!(expression)) \
{ \
Platform::CheckFailed(#expression, __FILE__, __LINE__); \
}
#if ENABLE_ASSERTION
// Performs a soft check of the expression. Logs the expression failure and returns from the function call.
@@ -189,6 +189,7 @@ bool WindowsMouse::WndProc(Window* window, const UINT msg, WPARAM wParam, LPARAM
POINT p;
p.x = static_cast<LONG>(WINDOWS_GET_X_LPARAM(lParam));
p.y = static_cast<LONG>(WINDOWS_GET_Y_LPARAM(lParam));
const Float2 mousePosScreen(static_cast<float>(p.x), static_cast<float>(p.y));
::ClientToScreen(window->GetHWND(), &p);
const Float2 mousePos(static_cast<float>(p.x), static_cast<float>(p.y));
@@ -203,7 +204,8 @@ bool WindowsMouse::WndProc(Window* window, const UINT msg, WPARAM wParam, LPARAM
}
case WM_NCMOUSEMOVE:
{
OnMouseMove(mousePos, window);
// The position in the message is already reported in screen-space
OnMouseMove(mousePosScreen, window);
result = true;
break;
}
+5 -7
View File
@@ -356,9 +356,7 @@ void Renderer::DrawActors(RenderContext& renderContext, const Array<Actor*>& cus
Level::DrawActors(renderContextBatch, SceneRendering::DrawCategory::SceneDraw);
Level::DrawActors(renderContextBatch, SceneRendering::DrawCategory::SceneDrawAsync);
JobSystem::SetJobStartingOnDispatch(true);
for (const int64 label : renderContextBatch.WaitLabels)
JobSystem::Wait(label);
renderContextBatch.WaitLabels.Clear();
renderContextBatch.FlushWaitLabels();
}
}
@@ -483,9 +481,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
// Wait for async jobs to finish
JobSystem::SetJobStartingOnDispatch(true);
for (const int64 label : renderContextBatch.WaitLabels)
JobSystem::Wait(label);
renderContextBatch.WaitLabels.Clear();
renderContextBatch.FlushWaitLabels();
// Perform custom post-scene drawing (eg. GPU dispatches used by VFX)
for (int32 i = 0; i < renderContextBatch.Contexts.Count(); i++)
@@ -737,7 +733,9 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
}
// Upscaling after scene rendering but before post processing
bool useUpscaling = task->RenderingPercentage < 1.0f;
PRAGMA_DISABLE_DEPRECATION_WARNINGS
bool useUpscaling = task->RenderingPercentage * task->RenderScale < 1.0f;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
const Viewport outputViewport = task->GetOutputViewport();
if (useUpscaling && setup.UpscaleLocation == RenderingUpscaleLocation::BeforePostProcessingPass)
{
@@ -0,0 +1,15 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System;
namespace FlaxEngine
{
/// <summary>
/// Changes enum serialization to use string names instead of integer values. This makes saved data resilient to enum reordering or changes in values (but not to renaming enums). Deserialization accepts both string names and integer values for backward compatibility.
/// </summary>
/// <seealso cref="System.Attribute" />
[AttributeUsage(AttributeTargets.Enum)]
public sealed class EnumStringAttribute : Attribute
{
}
}
+26 -37
View File
@@ -204,7 +204,7 @@ ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* modul
Struct.SetField = setField;
}
ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, EnumItem* items)
ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, EnumItem* items, bool stringSerialization)
: ManagedClass(nullptr)
, Module(module)
, InitRuntime(DefaultInitRuntime)
@@ -215,6 +215,7 @@ ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* modul
, Size(size)
{
Enum.Items = items;
Enum.StringSerialization = stringSerialization;
}
ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, InitRuntimeHandler initRuntime, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable, GetInterfaceWrapper getInterfaceWrapper)
@@ -270,6 +271,7 @@ ScriptingType::ScriptingType(const ScriptingType& other)
break;
case ScriptingTypes::Enum:
Enum.Items = other.Enum.Items;
Enum.StringSerialization = other.Enum.StringSerialization;
break;
case ScriptingTypes::Interface:
Interface.SetupScriptVTable = other.Interface.SetupScriptVTable;
@@ -323,6 +325,7 @@ ScriptingType::ScriptingType(ScriptingType&& other)
break;
case ScriptingTypes::Enum:
Enum.Items = other.Enum.Items;
Enum.StringSerialization = other.Enum.StringSerialization;
break;
case ScriptingTypes::Interface:
Interface.SetupScriptVTable = other.Interface.SetupScriptVTable;
@@ -604,71 +607,57 @@ StringAnsiView ScriptingType::GetName() const
return Fullname;
}
#if BUILD_DEBUG || USE_EDITOR
#define INIT_TYPE(...) \
module->Types.AddUninitialized(); \
new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, ##__VA_ARGS__); \
if (module->TypeNameToTypeIndex.ContainsKey(fullname)) \
LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); \
module->TypeNameToTypeIndex[fullname] = TypeIndex;
#else
#define INIT_TYPE(...) \
module->Types.AddUninitialized(); \
new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, ##__VA_ARGS__); \
module->TypeNameToTypeIndex[fullname] = TypeIndex;
#endif
ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::SpawnHandler spawn, ScriptingTypeInitializer* baseType, ScriptingType::SetupScriptVTableHandler setupScriptVTable, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable, const ScriptingType::InterfaceImplementation* interfaces)
: ScriptingTypeHandle(module, module->Types.Count())
{
// Script
module->Types.AddUninitialized();
new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, spawn, baseType, setupScriptVTable, setupScriptObjectVTable, interfaces);
#if BUILD_DEBUG
if (module->TypeNameToTypeIndex.ContainsKey(fullname))
LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName()));
#endif
module->TypeNameToTypeIndex[fullname] = TypeIndex;
INIT_TYPE(size, initRuntime, spawn, baseType, setupScriptVTable, setupScriptObjectVTable, interfaces);
}
ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces)
: ScriptingTypeHandle(module, module->Types.Count())
{
// Class
module->Types.AddUninitialized();
new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, baseType, interfaces);
#if BUILD_DEBUG
if (module->TypeNameToTypeIndex.ContainsKey(fullname))
LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName()));
#endif
module->TypeNameToTypeIndex[fullname] = TypeIndex;
INIT_TYPE(size, initRuntime, ctor, dtor, baseType, interfaces);
}
ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces)
: ScriptingTypeHandle(module, module->Types.Count())
{
// Structure
module->Types.AddUninitialized();
new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, copy, box, unbox, getField, setField, baseType, interfaces);
#if BUILD_DEBUG
if (module->TypeNameToTypeIndex.ContainsKey(fullname))
LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName()));
#endif
module->TypeNameToTypeIndex[fullname] = TypeIndex;
INIT_TYPE(size, initRuntime, ctor, dtor, copy, box, unbox, getField, setField, baseType, interfaces);
}
ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::EnumItem* items)
ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::EnumItem* items, bool stringSerialization)
: ScriptingTypeHandle(module, module->Types.Count())
{
// Enum
module->Types.AddUninitialized();
new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, items);
#if BUILD_DEBUG
if (module->TypeNameToTypeIndex.ContainsKey(fullname))
LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName()));
#endif
module->TypeNameToTypeIndex[fullname] = TypeIndex;
INIT_TYPE(size, items, stringSerialization);
}
ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::SetupScriptVTableHandler setupScriptVTable, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable, ScriptingType::GetInterfaceWrapper getInterfaceWrapper)
: ScriptingTypeHandle(module, module->Types.Count())
{
// Interface
module->Types.AddUninitialized();
new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, initRuntime, setupScriptVTable, setupScriptObjectVTable, getInterfaceWrapper);
#if BUILD_DEBUG
if (module->TypeNameToTypeIndex.ContainsKey(fullname))
LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName()));
#endif
module->TypeNameToTypeIndex[fullname] = TypeIndex;
INIT_TYPE(initRuntime, setupScriptVTable, setupScriptObjectVTable, getInterfaceWrapper);
}
#undef INIT_TYPE
CriticalSection BinaryModule::Locker;
BinaryModule::BinaryModulesList& BinaryModule::GetModules()
@@ -609,6 +609,12 @@ MObject* MUtils::BoxVariant(const Variant& value)
return MCore::Object::Box((void*)&value.AsData, Double3::TypeInitializer.GetClass());
case VariantType::Double4:
return MCore::Object::Box((void*)&value.AsData, Double4::TypeInitializer.GetClass());
case VariantType::Int2:
return MCore::Object::Box((void*)&value.AsData, Int2::TypeInitializer.GetClass());
case VariantType::Int3:
return MCore::Object::Box((void*)&value.AsData, Int3::TypeInitializer.GetClass());
case VariantType::Int4:
return MCore::Object::Box((void*)&value.AsData, Int4::TypeInitializer.GetClass());
case VariantType::Color:
return MCore::Object::Box((void*)&value.AsData, stdTypes.ColorClass);
case VariantType::Guid:
@@ -889,6 +895,12 @@ MClass* MUtils::GetClass(const VariantType& value)
return Double3::TypeInitializer.GetClass();
case VariantType::Double4:
return Double4::TypeInitializer.GetClass();
case VariantType::Int2:
return Int2::TypeInitializer.GetClass();
case VariantType::Int3:
return Int3::TypeInitializer.GetClass();
case VariantType::Int4:
return Int4::TypeInitializer.GetClass();
case VariantType::Color:
return Color::TypeInitializer.GetClass();
case VariantType::Guid:
@@ -979,6 +991,12 @@ MClass* MUtils::GetClass(const Variant& value)
return Double3::TypeInitializer.GetClass();
case VariantType::Double4:
return Double4::TypeInitializer.GetClass();
case VariantType::Int2:
return Int2::TypeInitializer.GetClass();
case VariantType::Int3:
return Int3::TypeInitializer.GetClass();
case VariantType::Int4:
return Int4::TypeInitializer.GetClass();
case VariantType::Color:
return stdTypes.ColorClass;
case VariantType::Guid:
@@ -1148,6 +1166,9 @@ void* MUtils::VariantToManagedArgPtr(Variant& value, MType* type, bool& failed)
CASE_IN_BUILD_TYPE(Double2, AsData);
CASE_IN_BUILD_TYPE(Double3, AsData);
CASE_IN_BUILD_TYPE(Double4, AsBlob.Data);
CASE_IN_BUILD_TYPE(Int2, AsData);
CASE_IN_BUILD_TYPE(Int3, AsData);
CASE_IN_BUILD_TYPE(Int4, AsData);
#undef CASE_IN_BUILD_TYPE
if (klass->IsValueType())
{
+1 -1
View File
@@ -1636,7 +1636,7 @@ FORCE_INLINE StringAnsiView GetPropertyMethodName(MProperty* property, StringAns
Platform::MemoryCopy(mem, prefix.Get(), prefix.Length());
Platform::MemoryCopy(mem + prefix.Length(), name.Get(), name.Length());
mem[name.Length() + prefix.Length()] = 0;
return StringAnsiView(mem, name.Length() + prefix.Length() + 1);
return StringAnsiView(mem, name.Length() + prefix.Length());
}
MProperty::MProperty(MClass* parentClass, const char* name, void* handle, void* getterHandle, void* setterHandle, MMethodAttributes getterAttributes, MMethodAttributes setterAttributes)

Some files were not shown because too many files have changed in this diff Show More