Merge branch 'FlaxEngine:master' into flax-msdf-font
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b73d774c71bd7b46c9c4198a4c957055e6447e31d8252813b272db92301475e7
|
||||
oid sha256:0bbce0a3252f993e9d7ea49fe31a75e7ccd96eb9ffc75154a5ea026232973da6
|
||||
size 29533
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c4ec07a3b7e0a2dfd4332598a982c3192c0c357c6bcd128d7a7797fb483780e7
|
||||
oid sha256:9b955c07a63629b4f7c4e0566ab7a0856868a3a0e91691c8aeee7528262a3a0b
|
||||
size 31445
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2830919bea988e1f8bd8299ceac34b8a3695418e2f22ca670f2fec3b3d6d1a2f
|
||||
oid sha256:9e6835bbc536e31289713cd1b8fd85d7565fd25cfb4014ab6b5be9e25d207761
|
||||
size 41149
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:588c29a4b239c32d4b125052e4054a29cf5140562e90ca6fac4d2952e03f66c7
|
||||
oid sha256:0901efdfbbd4102291969c561557a16fb617f6a15ab60d02c3905ae525991f8d
|
||||
size 10397
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b39cd76254f341c93e83625475b6e7896ef34f1d6d650da52e649bc055d0d03e
|
||||
size 33503
|
||||
oid sha256:843e7bf7332d934354137e65bcf22b123e6aaaa86c76737855e2ad4517242782
|
||||
size 33659
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5861e912cf822c9478f824390f6258d84821b7289e3e993a7dee38b77c5a2f80
|
||||
oid sha256:f767d7e0df4020f9dbb35a8916965b693ad01918787ed9228d75d390cea789cb
|
||||
size 29398
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b9ed2869a2a754423e0b8c456eed621bd06bdb50cacf7a972a7f024e40a1ea6a
|
||||
size 32954
|
||||
oid sha256:a44ae61dca9073913ceaa3dc4d6806b6b2b2c45deef435e5263445fbcc94b6b4
|
||||
size 33110
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:05c27ac416ef922ee247adc12a115fd522eb3a1d8873e1056914cd96893a3097
|
||||
oid sha256:7d97b88aa9780d2d4367670cdffcce82f70daf7cd89d928e1a92cb9a5e3fc8c5
|
||||
size 21096
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8e3d4ca149e143fee31e2d038b8efec526ca995dbe13258fbb68c89cd43ecbf7
|
||||
oid sha256:d706889c353547241c10782826ce09e081e38ee636fb8a70a3f714cd4d98c693
|
||||
size 29627
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7af1150d6e7cb6ecce5cd039f0edc92967c986a13903a201d6dc15ed0751dc57
|
||||
size 39637
|
||||
oid sha256:cba9bb7a8bf3cdb1f10f6167324225699ef0885d2b4b9de49ad567f17dd4e71d
|
||||
size 39793
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d575ca1b202c84b8268687b391be5fc8d55497ffa23fb3cd4287fa667de654ab
|
||||
size 34240
|
||||
oid sha256:07c7c09b0a888f940c221b73277a3582cfe82eed1416790ea37ca48052ec9191
|
||||
size 34396
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:26f2d88aab9c0cad36ae527b038a36b69755ff3a5a920e8c4563dd5e1ed8ec65
|
||||
size 32689
|
||||
oid sha256:02297bb31c7e41529e29811bfe1c63c3e34d15ff9f3dffbd2e3edd3a56407790
|
||||
size 32845
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5bb75934622d9251a8a9e72cfe4905091770798ffed22de680a70f98434d0ed7
|
||||
oid sha256:f791cbb4556aa5aa709436d17042abf6536e325cc4bcdc99c570b331f68fee84
|
||||
size 16241
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a1afa76c3f9400da065c150a6a58adc904c3596f650e04dfd87b5e1c1b34695e
|
||||
oid sha256:c93ddf1c826f396f81707da7f692d65462bb897758a50f0bd90c085d42f35edd
|
||||
size 30655
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1290ae85e4fe41f9d8c1919b33e165287f79377aeddc68f9117c1795ca341003
|
||||
size 31267
|
||||
oid sha256:ee3bc010414450b37578fcf9a6ccd89875c959634717b7e431c85c4bd57a6443
|
||||
size 31423
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:340cc500a160344b43b21ed8c4c22b6d776f406581f606ced62a3e92c5bef18a
|
||||
size 31300
|
||||
oid sha256:318aa93c17e75b7349f8c001d81238a1e6b26ae017e1f116dc9b9b2555fcfa84
|
||||
size 31456
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d444cd33ec8d2e1e0e6651c3979260f05c06c8bac33ce2441d6974ae4fa178e4
|
||||
oid sha256:f65ec8f63e49e7bafb2248d2ba5001590e40754c6af919e285bb45650bc266dc
|
||||
size 20443
|
||||
|
||||
@@ -28,6 +28,7 @@ TextureCube SkyLightTexture : register(t__SRV__);
|
||||
Buffer<float4> ShadowsBuffer : register(t__SRV__);
|
||||
Texture2D<float> ShadowMap : register(t__SRV__);
|
||||
Texture3D VolumetricFogTexture : register(t__SRV__);
|
||||
Texture2D PreIntegratedGF : register(t__SRV__);
|
||||
@4// Forward Shading: Utilities
|
||||
// Public accessors for lighting data, use them as data binding might change but those methods will remain.
|
||||
LightData GetDirectionalLight() { return DirectionalLight; }
|
||||
@@ -108,7 +109,8 @@ void PS_Forward(
|
||||
|
||||
// Calculate reflections
|
||||
#if USE_REFLECTIONS
|
||||
float3 reflections = SampleReflectionProbe(ViewPos, EnvProbe, EnvironmentProbe, gBuffer.WorldPos, gBuffer.Normal, gBuffer.Roughness).rgb;
|
||||
float4 reflections = SampleReflectionProbe(ViewPos, EnvProbe, EnvironmentProbe, gBuffer.WorldPos, gBuffer.Normal, gBuffer.Roughness);
|
||||
reflections.rgb *= reflections.a;
|
||||
|
||||
#if MATERIAL_REFLECTIONS == MATERIAL_REFLECTIONS_SSR
|
||||
// Screen Space Reflections
|
||||
@@ -124,7 +126,7 @@ void PS_Forward(
|
||||
if (hit.z > 0)
|
||||
{
|
||||
float3 screenColor = sceneColorTexture.SampleLevel(SamplerPointClamp, hit.xy, 0).rgb;
|
||||
reflections = lerp(reflections, screenColor, hit.z);
|
||||
reflections.rgb = lerp(reflections.rgb, screenColor, hit.z);
|
||||
}
|
||||
|
||||
// Fallback to software tracing if possible
|
||||
@@ -136,17 +138,17 @@ void PS_Forward(
|
||||
if (TraceSDFSoftwareReflections(gBuffer, reflectWS, surfaceAtlas))
|
||||
{
|
||||
float3 screenColor = sceneColorTexture.SampleLevel(SamplerPointClamp, hit.xy, 0).rgb;
|
||||
reflections = lerp(surfaceAtlas, float4(screenColor, 1), hit.z);
|
||||
reflections.rgb = lerp(surfaceAtlas, float4(screenColor, 1), hit.z);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
light.rgb += reflections * GetReflectionSpecularLighting(ViewPos, gBuffer) * light.a;
|
||||
light.rgb += reflections.rgb * GetReflectionSpecularLighting(PreIntegratedGF, ViewPos, gBuffer);
|
||||
#endif
|
||||
|
||||
// Add lighting (apply ambient occlusion)
|
||||
output.rgb += light.rgb * gBuffer.AO;
|
||||
// Add lighting
|
||||
output.rgb += light.rgb;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -158,7 +160,8 @@ void PS_Forward(
|
||||
#else
|
||||
float fogSceneDistance = gBuffer.ViewPos.z;
|
||||
#endif
|
||||
float4 fog = GetExponentialHeightFog(ExponentialHeightFog, materialInput.WorldPosition, ViewPos, 0, fogSceneDistance);
|
||||
float fogSkipDistance = max(ExponentialHeightFog.VolumetricFogMaxDistance - 100, 0);
|
||||
float4 fog = GetExponentialHeightFog(ExponentialHeightFog, materialInput.WorldPosition, ViewPos, fogSkipDistance, fogSceneDistance);
|
||||
|
||||
if (ExponentialHeightFog.VolumetricFogMaxDistance > 0)
|
||||
{
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:906443c7db821361b32780c17735bc9477ea96c8979dee371a4899635246af48
|
||||
size 31708
|
||||
oid sha256:11d9f756a1ea3f8b463b410eebf1d463a56d16d0eb97b74d743ae55260a3c8c8
|
||||
size 31864
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:16db9c1a18b64aea2dcdf3e74f9a44c652bf8bd9b33a5bfda39555d8c002a358
|
||||
size 39774
|
||||
oid sha256:3f49348233e05529c7ec435660a2e78f3c2f4ca105d92e79e0caac6b22f2a605
|
||||
size 39931
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:56254b02ffc937d61e8e8fa6492d4805e944ca639c7fcfc0f751b4ac2442365d
|
||||
oid sha256:3cef7efc0f1446707c3e31fb5ab3b372377037819e7e8b841bccde11415d5bfd
|
||||
size 30734
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:16eefa75a2ae99bba658c4e9b8e8741187b90e577193f76394872764fff2ca0b
|
||||
oid sha256:7bfdfe90e3b11caf1143d463e2fa9f3a09f24f5bedf8b4e676d4fbfb9b2553df
|
||||
size 28232
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e25a3c9e130e51b28dfe5ce43678f52c277c0def83142a2853c4c8ca84dbf417
|
||||
oid sha256:2afea0340cc0a151b449316cca850f1dccdd16ffa349523bf84ead871075cba5
|
||||
size 21179
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:79de09ba0616eb6066171c2b80cdb6c4235cb52be4836d23162bb9c2585760a0
|
||||
oid sha256:6ddd3fef4c2e14f0c1e84a081aaf22b79cff9434bf15973679f2469a5bd2598a
|
||||
size 11058
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:02d4c767fb59c67fef16ccc081f6f371bad329a5333047f9f79fd3d50b911f93
|
||||
size 31753
|
||||
oid sha256:8480dd3ec9d240e1429ffd638cb57561ebe0e651b0c5e52110c4572ab78cc10c
|
||||
size 31909
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d1f556b230cea8e83d00bd4357d34a77e5e468389a5f3bb615e30f6a3ce3ace4
|
||||
oid sha256:490af9df20c7a5c81eb2664b1923ba13567abfa35ad3c989a2b2fe906c876034
|
||||
size 19734
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c4ec872b3433d58f8aed640c6efee3d911f226740b4844cb07ed0bf94c00ea18
|
||||
oid sha256:b0d004b06fded8a4d8cf2d6a9960753a3ac6030358740955bab8a6f7c0600085
|
||||
size 32080
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0da99403c069966d05daea7fc11d32f20f88bac0463fbc08724840e249ee3bd2
|
||||
oid sha256:16b758ddffb7b39d7ada6f171eca464e401c2d00ea788d0ef3c9b7f88ffed619
|
||||
size 21700
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bdfa3b4842a5734d2cd8110af03599b4a5280b33a72b2ba435cd19487cebcde6
|
||||
oid sha256:37fb6eb71d4e9be122229e393c057f0584fa8e4370b15c90a61b49b66fa66b4f
|
||||
size 24082
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6ff8f127d46e68e3423339a352f623c079f2c5d93512c5e9b25841edc7cd0f05
|
||||
oid sha256:ebfd6324585b0e117287bb5e6931223624995070c024ccd64b472ff035a269fe
|
||||
size 29615
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:14c9833ed19302ea7c6e730fff63f1b72dbac71dc2b49c1d62edb61ccaa68b6f
|
||||
oid sha256:088472984c47f9ae024ae7f2573afdbc7701f7736b11737af9f2bf7de23287cf
|
||||
size 31974
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
"Major": 1,
|
||||
"Minor": 11,
|
||||
"Revision": 0,
|
||||
"Build": 6806
|
||||
"Build": 6807
|
||||
},
|
||||
"Company": "Flax",
|
||||
"Copyright": "Copyright (c) 2012-2025 Wojciech Figat. All rights reserved.",
|
||||
"Copyright": "Copyright (c) 2012-2026 Wojciech Figat. All rights reserved.",
|
||||
"GameTarget": "FlaxGame",
|
||||
"EditorTarget": "FlaxEditor",
|
||||
"Configuration": {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for <see cref="NavMeshBoundsVolume"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="ActorEditor" />
|
||||
[CustomEditor(typeof(NavMeshBoundsVolume)), DefaultEditor]
|
||||
internal class NavMeshBoundsVolumeEditor : ActorEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
base.Initialize(layout);
|
||||
|
||||
if (Values.HasDifferentTypes == false)
|
||||
{
|
||||
var button = layout.Button("Build");
|
||||
button.Button.Clicked += OnBuildClicked;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBuildClicked()
|
||||
{
|
||||
foreach (var value in Values)
|
||||
{
|
||||
if (value is NavMeshBoundsVolume volume)
|
||||
{
|
||||
Navigation.BuildNavMesh(volume.Box, volume.Scene);
|
||||
Editor.Instance.Scene.MarkSceneEdited(volume.Scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -593,11 +593,12 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
panel.Panel.Offsets = new Margin(7, 7, 0, 0);
|
||||
panel.Panel.BackgroundColor = _background;
|
||||
var elementType = ElementType;
|
||||
var bindingAttr = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public;
|
||||
bool single = elementType.IsPrimitive ||
|
||||
elementType.Equals(new ScriptType(typeof(string))) ||
|
||||
elementType.IsEnum ||
|
||||
(elementType.GetFields().Length == 1 && elementType.GetProperties().Length == 0) ||
|
||||
(elementType.GetProperties().Length == 1 && elementType.GetFields().Length == 0) ||
|
||||
(elementType.GetFields(bindingAttr).Length == 1 && elementType.GetProperties(bindingAttr).Length == 0) ||
|
||||
(elementType.GetProperties(bindingAttr).Length == 1 && elementType.GetFields(bindingAttr).Length == 0) ||
|
||||
elementType.Equals(new ScriptType(typeof(JsonAsset))) ||
|
||||
elementType.Equals(new ScriptType(typeof(SettingsBase)));
|
||||
if (_cachedDropPanels == null)
|
||||
|
||||
@@ -23,6 +23,7 @@ using FlaxEngine.Assertions;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEngine.Interop;
|
||||
using FlaxEngine.Json;
|
||||
using FlaxEngine.Utilities;
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
@@ -1370,7 +1371,7 @@ namespace FlaxEditor
|
||||
public void BuildCSG()
|
||||
{
|
||||
var scenes = Level.Scenes;
|
||||
scenes.ToList().ForEach(x => x.BuildCSG(0));
|
||||
scenes.ForEach(x => x.BuildCSG(0));
|
||||
Scene.MarkSceneEdited(scenes);
|
||||
}
|
||||
|
||||
@@ -1380,7 +1381,7 @@ namespace FlaxEditor
|
||||
public void BuildNavMesh()
|
||||
{
|
||||
var scenes = Level.Scenes;
|
||||
scenes.ToList().ForEach(x => Navigation.BuildNavMesh(x, 0));
|
||||
Navigation.BuildNavMesh();
|
||||
Scene.MarkSceneEdited(scenes);
|
||||
}
|
||||
|
||||
|
||||
@@ -502,6 +502,7 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
if (base.OnKeyDown(key))
|
||||
return true;
|
||||
|
||||
// Keyboard navigation around the menu
|
||||
switch (key)
|
||||
{
|
||||
case KeyboardKeys.ArrowDown:
|
||||
@@ -526,6 +527,20 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
}
|
||||
}
|
||||
break;
|
||||
case KeyboardKeys.ArrowRight:
|
||||
for (int i = 0; i < _panel.Children.Count; i++)
|
||||
{
|
||||
if (_panel.Children[i] is ContextMenuChildMenu item && item.Visible && item.IsFocused && !item.ContextMenu.IsOpened)
|
||||
{
|
||||
item.ShowChild(this);
|
||||
item.ContextMenu._panel.Children.FirstOrDefault(x => x is ContextMenuButton && x.Visible)?.Focus();
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case KeyboardKeys.ArrowLeft:
|
||||
ParentCM?.RootWindow.Focus();
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -72,6 +72,11 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
/// </summary>
|
||||
public bool HasChildCMOpened => _childCM != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent context menu (if exists).
|
||||
/// </summary>
|
||||
public ContextMenuBase ParentCM => _parentCM;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the topmost context menu.
|
||||
/// </summary>
|
||||
@@ -81,9 +86,7 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
{
|
||||
var cm = this;
|
||||
while (cm._parentCM != null && cm._isSubMenu)
|
||||
{
|
||||
cm = cm._parentCM;
|
||||
}
|
||||
return cm;
|
||||
}
|
||||
}
|
||||
@@ -108,6 +111,11 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
/// </summary>
|
||||
public bool UseInput = true;
|
||||
|
||||
/// <summary>
|
||||
/// Optional flag that can disable UI navigation (tab/enter).
|
||||
/// </summary>
|
||||
public bool UseNavigation = true;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContextMenuBase"/> class.
|
||||
/// </summary>
|
||||
@@ -594,6 +602,21 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
case KeyboardKeys.Escape:
|
||||
Hide();
|
||||
return true;
|
||||
case KeyboardKeys.Return:
|
||||
if (UseNavigation && Root?.FocusedControl != null)
|
||||
{
|
||||
Root.SubmitFocused();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case KeyboardKeys.Tab:
|
||||
if (UseNavigation && Root != null)
|
||||
{
|
||||
bool shiftDown = Root.GetKey(KeyboardKeys.Shift);
|
||||
Root.Navigate(shiftDown ? NavDirection.Previous : NavDirection.Next);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
CloseMenuOnClick = false;
|
||||
}
|
||||
|
||||
private void ShowChild(ContextMenu parentContextMenu)
|
||||
internal void ShowChild(ContextMenu parentContextMenu)
|
||||
{
|
||||
// Hide parent CM popups and set itself as child
|
||||
var vAlign = parentContextMenu.ItemsAreaMargin.Top;
|
||||
|
||||
@@ -522,6 +522,16 @@ namespace FlaxEditor.GUI
|
||||
cm.AddButton("Show whole curve", _editor.ShowWholeCurve);
|
||||
cm.AddButton("Reset view", _editor.ResetView);
|
||||
}
|
||||
cm.AddSeparator();
|
||||
var presetCm = cm.AddChildMenu("Apply preset");
|
||||
foreach (var value in Enum.GetValues(typeof(CurvePreset)))
|
||||
{
|
||||
CurvePreset preset = (CurvePreset)value;
|
||||
string name = Utilities.Utils.GetPropertyNameUI(preset.ToString());
|
||||
var b = presetCm.ContextMenu.AddButton(name, () => _editor.ApplyPreset(preset));
|
||||
b.Enabled = !(_editor is LinearCurveEditor<T> && (preset != CurvePreset.Constant && preset != CurvePreset.Linear));
|
||||
}
|
||||
|
||||
_editor.OnShowContextMenu(cm, selectionCount);
|
||||
cm.Show(this, location);
|
||||
}
|
||||
@@ -619,6 +629,33 @@ namespace FlaxEditor.GUI
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A list of avaliable curve presets for the <see cref="CurveEditor{T}"/>.
|
||||
/// </summary>
|
||||
public enum CurvePreset
|
||||
{
|
||||
/// <summary>
|
||||
/// A curve where every point has the same value.
|
||||
/// </summary>
|
||||
Constant,
|
||||
/// <summary>
|
||||
/// A curve linear curve.
|
||||
/// </summary>
|
||||
Linear,
|
||||
/// <summary>
|
||||
/// A curve that starts a slowly and then accelerates until the end.
|
||||
/// </summary>
|
||||
EaseIn,
|
||||
/// <summary>
|
||||
/// A curve that starts a steep and then flattens until the end.
|
||||
/// </summary>
|
||||
EaseOut,
|
||||
/// <summary>
|
||||
/// A combination of the <see cref="CurvePreset.EaseIn"/> and <see cref="CurvePreset.EaseOut"/> preset.
|
||||
/// </summary>
|
||||
Smoothstep
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnKeyframesDeselect(IKeyframesEditor editor)
|
||||
{
|
||||
|
||||
@@ -19,6 +19,48 @@ namespace FlaxEditor.GUI
|
||||
/// <seealso cref="CurveEditorBase" />
|
||||
public abstract partial class CurveEditor<T> : CurveEditorBase where T : new()
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single point in a <see cref="CurveEditorPreset"/>.
|
||||
/// </summary>
|
||||
protected struct CurvePresetPoint
|
||||
{
|
||||
/// <summary>
|
||||
/// The time.
|
||||
/// </summary>
|
||||
public float Time;
|
||||
|
||||
/// <summary>
|
||||
/// The value.
|
||||
/// </summary>
|
||||
public float Value;
|
||||
|
||||
/// <summary>
|
||||
/// The in tangent. Will be ignored in <see cref="LinearCurveEditor{T}"/>
|
||||
/// </summary>
|
||||
public float TangentIn;
|
||||
|
||||
/// <summary>
|
||||
/// The out tangent. Will be ignored in <see cref="LinearCurveEditor{T}"/>
|
||||
/// </summary>
|
||||
public float TangentOut;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A curve preset.
|
||||
/// </summary>
|
||||
protected struct CurveEditorPreset()
|
||||
{
|
||||
/// <summary>
|
||||
/// If the tangents will be linear or smooth.
|
||||
/// </summary>
|
||||
public bool LinearTangents;
|
||||
|
||||
/// <summary>
|
||||
/// The points of the preset.
|
||||
/// </summary>
|
||||
public List<CurvePresetPoint> Points;
|
||||
}
|
||||
|
||||
private class Popup : ContextMenuBase
|
||||
{
|
||||
private CustomEditorPresenter _presenter;
|
||||
@@ -26,11 +68,12 @@ namespace FlaxEditor.GUI
|
||||
private List<int> _keyframeIndices;
|
||||
private bool _isDirty;
|
||||
|
||||
public Popup(CurveEditor<T> editor, object[] selection, List<int> keyframeIndices = null, float height = 140.0f)
|
||||
: this(editor, height)
|
||||
public Popup(CurveEditor<T> editor, object[] selection, List<int> keyframeIndices = null, float maxHeight = 140.0f)
|
||||
: this(editor, maxHeight)
|
||||
{
|
||||
_presenter.Select(selection);
|
||||
_presenter.OpenAllGroups();
|
||||
Size = new Float2(Size.X, Mathf.Min(_presenter.ContainerControl.Size.Y, maxHeight));
|
||||
_keyframeIndices = keyframeIndices;
|
||||
if (keyframeIndices != null && selection.Length != keyframeIndices.Count)
|
||||
throw new Exception();
|
||||
@@ -169,7 +212,7 @@ namespace FlaxEditor.GUI
|
||||
if (IsSelected)
|
||||
color = Editor.ContainsFocus ? style.SelectionBorder : Color.Lerp(style.ForegroundDisabled, style.SelectionBorder, 0.4f);
|
||||
if (IsMouseOver)
|
||||
color *= 1.1f;
|
||||
color *= 1.5f;
|
||||
Render2D.FillRectangle(rect, color);
|
||||
}
|
||||
|
||||
@@ -285,7 +328,7 @@ namespace FlaxEditor.GUI
|
||||
/// <summary>
|
||||
/// The keyframes size.
|
||||
/// </summary>
|
||||
protected static readonly Float2 KeyframesSize = new Float2(7.0f);
|
||||
protected static readonly Float2 KeyframesSize = new Float2(8.0f);
|
||||
|
||||
/// <summary>
|
||||
/// The colors for the keyframe points.
|
||||
@@ -326,6 +369,63 @@ namespace FlaxEditor.GUI
|
||||
private Color _labelsColor;
|
||||
private Font _labelsFont;
|
||||
|
||||
/// <summary>
|
||||
/// Preset values for <see cref="CurvePreset"/> to be applied to a <see cref="CurveEditor{T}"/>.
|
||||
/// </summary>
|
||||
protected Dictionary<CurvePreset, CurveEditorPreset> Presets = new Dictionary<CurvePreset, CurveEditorPreset>
|
||||
{
|
||||
{ CurvePreset.Constant, new CurveEditorPreset
|
||||
{
|
||||
LinearTangents = true,
|
||||
Points = new List<CurvePresetPoint>
|
||||
{
|
||||
new CurvePresetPoint { Time = 0f, Value = 0.5f, TangentIn = 0f, TangentOut = 0f },
|
||||
new CurvePresetPoint { Time = 1f, Value = 0.5f, TangentIn = 0f, TangentOut = 0f },
|
||||
}
|
||||
}
|
||||
},
|
||||
{ CurvePreset.EaseIn, new CurveEditorPreset
|
||||
{
|
||||
LinearTangents = false,
|
||||
Points = new List<CurvePresetPoint>
|
||||
{
|
||||
new CurvePresetPoint { Time = 0f, Value = 0f, TangentIn = 0f, TangentOut = 0f },
|
||||
new CurvePresetPoint { Time = 1f, Value = 1f, TangentIn = -1.4f, TangentOut = 0f },
|
||||
}
|
||||
}
|
||||
},
|
||||
{ CurvePreset.EaseOut, new CurveEditorPreset
|
||||
{
|
||||
LinearTangents = false,
|
||||
Points = new List<CurvePresetPoint>
|
||||
{
|
||||
new CurvePresetPoint { Time = 1f, Value = 1f, TangentIn = 0f, TangentOut = 0f },
|
||||
new CurvePresetPoint { Time = 0f, Value = 0f, TangentIn = 0f, TangentOut = 1.4f },
|
||||
}
|
||||
}
|
||||
},
|
||||
{ CurvePreset.Linear, new CurveEditorPreset
|
||||
{
|
||||
LinearTangents = true,
|
||||
Points = new List<CurvePresetPoint>
|
||||
{
|
||||
new CurvePresetPoint { Time = 0f, Value = 0f, TangentIn = 0f, TangentOut = 0f },
|
||||
new CurvePresetPoint { Time = 1f, Value = 1f, TangentIn = 0f, TangentOut = 0f },
|
||||
}
|
||||
}
|
||||
},
|
||||
{ CurvePreset.Smoothstep, new CurveEditorPreset
|
||||
{
|
||||
LinearTangents = false,
|
||||
Points = new List<CurvePresetPoint>
|
||||
{
|
||||
new CurvePresetPoint { Time = 0f, Value = 0f, TangentIn = 0f, TangentOut = 0f },
|
||||
new CurvePresetPoint { Time = 1f, Value = 1f, TangentIn = 0f, TangentOut = 0f },
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The keyframe UI points.
|
||||
/// </summary>
|
||||
@@ -568,6 +668,28 @@ namespace FlaxEditor.GUI
|
||||
/// <param name="indicesToRemove">The list of indices of the keyframes to remove.</param>
|
||||
protected abstract void RemoveKeyframesInternal(HashSet<int> indicesToRemove);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to convert a float to the type of the type wildcard of the curve editor.
|
||||
/// </summary>
|
||||
/// <param name="value">The float.</param>
|
||||
/// <returns>The converted value.</returns>
|
||||
public static object ConvertCurvePresetValueToCurveEditorType(float value)
|
||||
{
|
||||
if (typeof(T) == typeof(Float2))
|
||||
return new Float2(value);
|
||||
if (typeof(T) == typeof(Float3))
|
||||
return new Float3(value);
|
||||
if (typeof(T) == typeof(Float4))
|
||||
return new Float4(value);
|
||||
if (typeof(T) == typeof(Vector2))
|
||||
return new Vector2(value);
|
||||
if (typeof(T) == typeof(Vector3))
|
||||
return new Vector3(value);
|
||||
if (typeof(T) == typeof(Vector4))
|
||||
return new Vector4(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when showing a context menu. Can be used to add custom buttons with actions.
|
||||
/// </summary>
|
||||
@@ -752,6 +874,17 @@ namespace FlaxEditor.GUI
|
||||
ShowCurve(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a <see cref="CurvePreset"/> to the curve editor.
|
||||
/// </summary>
|
||||
/// <param name="preset">The preset.</param>
|
||||
public virtual void ApplyPreset(CurvePreset preset)
|
||||
{
|
||||
// Remove existing keyframes
|
||||
SelectAll();
|
||||
RemoveKeyframes();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Evaluate(out object result, float time, bool loop = false)
|
||||
{
|
||||
@@ -1028,6 +1161,31 @@ namespace FlaxEditor.GUI
|
||||
return true;
|
||||
}
|
||||
|
||||
bool left = key == KeyboardKeys.ArrowLeft;
|
||||
bool right = key == KeyboardKeys.ArrowRight;
|
||||
bool up = key == KeyboardKeys.ArrowUp;
|
||||
bool down = key == KeyboardKeys.ArrowDown;
|
||||
|
||||
if (left || right || up || down)
|
||||
{
|
||||
bool shift = Root.GetKey(KeyboardKeys.Shift);
|
||||
bool alt = Root.GetKey(KeyboardKeys.Alt);
|
||||
float deltaValue = 10f;
|
||||
if (shift || alt)
|
||||
deltaValue = shift ? 2.5f : 5f;
|
||||
|
||||
Float2 moveDelta = Float2.Zero;
|
||||
if (left || right)
|
||||
moveDelta.X = left ? -deltaValue : deltaValue;
|
||||
if (up || down)
|
||||
moveDelta.Y = up ? -deltaValue : deltaValue;
|
||||
|
||||
_contents.OnMoveStart(Float2.Zero);
|
||||
_contents.OnMove(moveDelta);
|
||||
_contents.OnMoveEnd(Float2.Zero);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1526,6 +1684,22 @@ namespace FlaxEditor.GUI
|
||||
_tangents[i].Visible = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ApplyPreset(CurvePreset preset)
|
||||
{
|
||||
base.ApplyPreset(preset);
|
||||
|
||||
CurveEditorPreset data = Presets[preset];
|
||||
foreach (var point in data.Points)
|
||||
{
|
||||
float time = point.Time;
|
||||
object value = ConvertCurvePresetValueToCurveEditorType((float)point.Value);
|
||||
AddKeyframe(time, value);
|
||||
}
|
||||
|
||||
ShowWholeCurve();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DrawCurve(ref Rectangle viewRect)
|
||||
{
|
||||
@@ -2312,6 +2486,30 @@ namespace FlaxEditor.GUI
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ApplyPreset(CurvePreset preset)
|
||||
{
|
||||
base.ApplyPreset(preset);
|
||||
|
||||
CurveEditorPreset data = Presets[preset];
|
||||
|
||||
foreach (var point in data.Points)
|
||||
{
|
||||
float time = point.Time;
|
||||
object value = ConvertCurvePresetValueToCurveEditorType((float)point.Value);
|
||||
object tangentIn = ConvertCurvePresetValueToCurveEditorType((float)point.TangentIn);
|
||||
object tangentOut = ConvertCurvePresetValueToCurveEditorType((float)point.TangentOut);
|
||||
|
||||
AddKeyframe(time, value, tangentIn, tangentOut);
|
||||
}
|
||||
|
||||
SelectAll();
|
||||
if (data.LinearTangents)
|
||||
SetTangentsLinear();
|
||||
|
||||
ShowWholeCurve();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void SetScaleInternal(ref Float2 scale)
|
||||
{
|
||||
|
||||
@@ -469,7 +469,7 @@ namespace FlaxEditor.GUI.Docking
|
||||
var childPanels = _childPanels.ToArray();
|
||||
if (childPanels.Length != 0)
|
||||
{
|
||||
// Move tabs from child panels into this one
|
||||
// Fallback: move tabs from child panels into this one.
|
||||
DockWindow selectedTab = null;
|
||||
foreach (var childPanel in childPanels)
|
||||
{
|
||||
@@ -490,7 +490,8 @@ namespace FlaxEditor.GUI.Docking
|
||||
{
|
||||
// Unlink splitter
|
||||
var splitterParent = splitter.Parent;
|
||||
Assert.IsNotNull(splitterParent);
|
||||
if (splitterParent == null)
|
||||
return;
|
||||
splitter.Parent = null;
|
||||
|
||||
// Move controls from second split panel to the split panel parent
|
||||
@@ -507,17 +508,63 @@ namespace FlaxEditor.GUI.Docking
|
||||
splitter.Dispose();
|
||||
}
|
||||
}
|
||||
else if (IsMaster && _childPanels.Count != 0)
|
||||
{
|
||||
if (TryCollapseSplitter(_tabsProxy?.Parent as Panel))
|
||||
return;
|
||||
}
|
||||
else if (!IsMaster)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
else if (_childPanels.Count != 0)
|
||||
{
|
||||
if (TryCollapseSplitter(_tabsProxy?.Parent as Panel))
|
||||
return;
|
||||
}
|
||||
else if (!IsMaster)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
internal bool CollapseEmptyTabsProxy()
|
||||
{
|
||||
if (TabsCount == 0 && ChildPanelsCount > 0)
|
||||
{
|
||||
return TryCollapseSplitter(_tabsProxy?.Parent as Panel);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryCollapseSplitter(Panel removedPanelParent)
|
||||
{
|
||||
if (removedPanelParent == null)
|
||||
return false;
|
||||
if (!(removedPanelParent.Parent is SplitPanel tabsSplitter))
|
||||
return false;
|
||||
|
||||
var splitterParent = tabsSplitter.Parent;
|
||||
if (splitterParent == null)
|
||||
return false;
|
||||
tabsSplitter.Parent = null;
|
||||
|
||||
var scrPanel = removedPanelParent == tabsSplitter.Panel2 ? tabsSplitter.Panel1 : tabsSplitter.Panel2;
|
||||
var srcPanelChildrenCount = scrPanel.ChildrenCount;
|
||||
for (int i = srcPanelChildrenCount - 1; i >= 0 && scrPanel.ChildrenCount > 0; i--)
|
||||
{
|
||||
scrPanel.GetChild(i).Parent = splitterParent;
|
||||
}
|
||||
Assert.IsTrue(scrPanel.ChildrenCount == 0);
|
||||
Assert.IsTrue(splitterParent.ChildrenCount == srcPanelChildrenCount);
|
||||
|
||||
tabsSplitter.Dispose();
|
||||
if (_tabsProxy != null && _tabsProxy.Parent == removedPanelParent)
|
||||
_tabsProxy = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
internal virtual void DockWindowInternal(DockState state, DockWindow window, bool autoSelect = true, float? splitterValue = null)
|
||||
{
|
||||
DockWindow(state, window, autoSelect, splitterValue);
|
||||
|
||||
@@ -1140,8 +1140,11 @@ namespace FlaxEditor.GUI.Tree
|
||||
ParentTree.DraggedOverNode = this;
|
||||
|
||||
// Expand node if mouse goes over arrow
|
||||
if (ArrowRect.Contains(location) && HasAnyVisibleChild)
|
||||
if (ArrowRect.Contains(location) && HasAnyVisibleChild && IsCollapsed)
|
||||
{
|
||||
Expand(true);
|
||||
ParentTree?.FlushPendingPerformLayout();
|
||||
}
|
||||
|
||||
result = OnDragEnterHeader(data);
|
||||
}
|
||||
@@ -1172,8 +1175,11 @@ namespace FlaxEditor.GUI.Tree
|
||||
ParentTree.DraggedOverNode = this;
|
||||
|
||||
// Expand node if mouse goes over arrow
|
||||
if (ArrowRect.Contains(location) && HasAnyVisibleChild)
|
||||
if (ArrowRect.Contains(location) && HasAnyVisibleChild && IsCollapsed)
|
||||
{
|
||||
Expand(true);
|
||||
ParentTree?.FlushPendingPerformLayout();
|
||||
}
|
||||
|
||||
if (!_isDragOverHeader)
|
||||
result = OnDragEnterHeader(data);
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace FlaxEditor.Gizmo
|
||||
public abstract class GizmoBase
|
||||
{
|
||||
private IGizmoOwner _owner;
|
||||
private bool _visible = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the gizmo owner.
|
||||
@@ -34,6 +35,11 @@ namespace FlaxEditor.Gizmo
|
||||
/// </summary>
|
||||
public virtual BoundingSphere FocusBounds => BoundingSphere.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this gizmo is visible.
|
||||
/// </summary>
|
||||
public bool Visible { get { return _visible; } set { _visible = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GizmoBase"/> class.
|
||||
/// </summary>
|
||||
|
||||
@@ -36,11 +36,12 @@ public sealed class ViewportRubberBandSelector
|
||||
/// Triggers the start of a rubber band selection.
|
||||
/// </summary>
|
||||
/// <returns>True if selection started, otherwise false.</returns>
|
||||
public bool TryStartingRubberBandSelection()
|
||||
public bool TryStartingRubberBandSelection(Float2 mousePosition)
|
||||
{
|
||||
if (!_isRubberBandSpanning && _owner.Gizmos.Active != null && !_owner.Gizmos.Active.IsControllingMouse && !_owner.IsRightMouseButtonDown)
|
||||
{
|
||||
_tryStartRubberBand = true;
|
||||
_cachedStartingMousePosition = mousePosition;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -82,12 +83,15 @@ public sealed class ViewportRubberBandSelector
|
||||
return;
|
||||
}
|
||||
|
||||
if (_tryStartRubberBand && (Mathf.Abs(_owner.MouseDelta.X) > 0.1f || Mathf.Abs(_owner.MouseDelta.Y) > 0.1f) && canStart)
|
||||
if (_tryStartRubberBand && canStart)
|
||||
{
|
||||
_isRubberBandSpanning = true;
|
||||
_cachedStartingMousePosition = mousePosition;
|
||||
_rubberBandRect = new Rectangle(_cachedStartingMousePosition, Float2.Zero);
|
||||
_tryStartRubberBand = false;
|
||||
var delta = mousePosition - _cachedStartingMousePosition;
|
||||
if (Mathf.Abs(delta.X) > 0.1f || Mathf.Abs(delta.Y) > 0.1f)
|
||||
{
|
||||
_isRubberBandSpanning = true;
|
||||
_rubberBandRect = new Rectangle(_cachedStartingMousePosition, Float2.Zero);
|
||||
_tryStartRubberBand = false;
|
||||
}
|
||||
}
|
||||
else if (_isRubberBandSpanning && _owner.Gizmos.Active != null && !_owner.Gizmos.Active.IsControllingMouse && !_owner.IsRightMouseButtonDown)
|
||||
{
|
||||
|
||||
@@ -229,7 +229,7 @@ namespace FlaxEditor.Modules
|
||||
if (!isPlayMode && options.General.AutoRebuildNavMesh && actor.Scene && node.AffectsNavigationWithChildren)
|
||||
{
|
||||
var bounds = actor.BoxWithChildren;
|
||||
Navigation.BuildNavMesh(actor.Scene, bounds, options.General.AutoRebuildNavMeshTimeoutMs);
|
||||
Navigation.BuildNavMesh(bounds, options.General.AutoRebuildNavMeshTimeoutMs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -491,10 +491,15 @@ namespace FlaxEditor.Modules
|
||||
Editor.LogWarning("Empty panel inside layout.");
|
||||
p.RemoveIt();
|
||||
}
|
||||
else
|
||||
{
|
||||
p.CollapseEmptyTabsProxy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
panel.SelectTab(selectedTab);
|
||||
panel.CollapseEmptyTabsProxy();
|
||||
}
|
||||
|
||||
private static void SaveBounds(XmlWriter writer, Window win)
|
||||
|
||||
@@ -387,6 +387,14 @@ namespace FlaxEditor.Options
|
||||
[EditorDisplay("Viewport"), EditorOrder(1760)]
|
||||
public InputBinding ToggleOrthographic = new InputBinding(KeyboardKeys.NumpadDecimal);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "G")]
|
||||
[EditorDisplay("Viewport"), EditorOrder(1770)]
|
||||
public InputBinding ToggleGameView = new InputBinding(KeyboardKeys.G);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "P")]
|
||||
[EditorDisplay("Viewport"), EditorOrder(1770)]
|
||||
public InputBinding ToggleNavMeshVisibility = new InputBinding(KeyboardKeys.P);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Debug Views
|
||||
|
||||
@@ -555,7 +555,7 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
var options = Editor.Instance.Options.Options.General;
|
||||
if (options.AutoRebuildNavMesh)
|
||||
{
|
||||
Navigation.BuildNavMesh(collider.Scene, collider.Box, options.AutoRebuildNavMeshTimeoutMs);
|
||||
Navigation.BuildNavMesh(collider.Box, options.AutoRebuildNavMeshTimeoutMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,11 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the model used by this actor.
|
||||
/// </summary>
|
||||
public Model Model => ((StaticModel)Actor).Model;
|
||||
|
||||
/// <inheritdoc />
|
||||
public StaticModelNode(Actor actor)
|
||||
: base(actor)
|
||||
@@ -120,12 +125,12 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
{
|
||||
base.OnContextMenu(contextMenu, window);
|
||||
|
||||
// Check if every selected node is a primitive
|
||||
// Check if every selected node is a primitive or has collision asset
|
||||
var selection = GetSelection(window);
|
||||
bool autoOptionEnabled = true;
|
||||
foreach (var node in selection)
|
||||
{
|
||||
if (node is StaticModelNode staticModelNode && !staticModelNode.IsPrimitive)
|
||||
if (node is StaticModelNode staticModelNode && (!staticModelNode.IsPrimitive && GetCollisionData(staticModelNode.Model) == null))
|
||||
{
|
||||
autoOptionEnabled = false;
|
||||
break;
|
||||
@@ -201,6 +206,54 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
return Array.Empty<SceneGraphNode>();
|
||||
}
|
||||
|
||||
private static bool TryCollisionData(Model model, BinaryAssetItem assetItem, out CollisionData collisionData)
|
||||
{
|
||||
collisionData = FlaxEngine.Content.Load<CollisionData>(assetItem.ID);
|
||||
if (collisionData)
|
||||
{
|
||||
var options = collisionData.Options;
|
||||
if (options.Model == model.ID || options.Model == Guid.Empty)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private CollisionData GetCollisionData(Model model)
|
||||
{
|
||||
if (model == null)
|
||||
return null;
|
||||
|
||||
// Check if there already is collision data for that model to reuse
|
||||
var modelItem = (AssetItem)Editor.Instance.ContentDatabase.Find(model.ID);
|
||||
if (modelItem?.ParentFolder != null)
|
||||
{
|
||||
foreach (var child in modelItem.ParentFolder.Children)
|
||||
{
|
||||
// Check if there is collision that was made with this model
|
||||
if (child is BinaryAssetItem b && b.IsOfType<CollisionData>())
|
||||
{
|
||||
if (TryCollisionData(model, b, out var collisionData))
|
||||
return collisionData;
|
||||
}
|
||||
|
||||
// Check if there is an auto-imported collision
|
||||
if (child is ContentFolder childFolder && childFolder.ShortName == modelItem.ShortName)
|
||||
{
|
||||
foreach (var childFolderChild in childFolder.Children)
|
||||
{
|
||||
if (childFolderChild is BinaryAssetItem c && c.IsOfType<CollisionData>())
|
||||
{
|
||||
if (TryCollisionData(model, c, out var collisionData))
|
||||
return collisionData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void CreateAuto(StaticModel actor, Spawner spawner, bool singleNode)
|
||||
{
|
||||
// Special case for in-built Editor models that can use analytical collision
|
||||
@@ -243,6 +296,15 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
collider.LocalPosition = new Vector3(0, 50.0f, 0);
|
||||
collider.LocalOrientation = Quaternion.Euler(0, 0, 90.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
var collider = new MeshCollider
|
||||
{
|
||||
Transform = actor.Transform,
|
||||
CollisionData = GetCollisionData(model),
|
||||
};
|
||||
spawner(collider);
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateBox(StaticModel actor, Spawner spawner, bool singleNode)
|
||||
|
||||
@@ -304,25 +304,14 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class CustomCodeNode : SurfaceNode
|
||||
internal sealed class CustomCodeNode : ResizableSurfaceNode
|
||||
{
|
||||
private Rectangle _resizeButtonRect;
|
||||
private Float2 _startResizingSize;
|
||||
private Float2 _startResizingCornerOffset;
|
||||
private bool _isResizing;
|
||||
private CustomCodeTextBox _textBox;
|
||||
|
||||
private int SizeValueIndex => Archetype.TypeID == 8 ? 1 : 3; // Index of the Size stored in Values array
|
||||
|
||||
private Float2 SizeValue
|
||||
{
|
||||
get => (Float2)Values[SizeValueIndex];
|
||||
set => SetValue(SizeValueIndex, value, false);
|
||||
}
|
||||
|
||||
public CustomCodeNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
_sizeValueIndex = Archetype.TypeID == 8 ? 1 : 3; // Index of the Size stored in Values array
|
||||
Float2 pos = new Float2(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize), size;
|
||||
if (nodeArch.TypeID == 8)
|
||||
{
|
||||
@@ -345,126 +334,19 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
_textBox.EditEnd += () => SetValue(0, _textBox.Text);
|
||||
}
|
||||
|
||||
public override bool CanSelect(ref Float2 location)
|
||||
{
|
||||
return base.CanSelect(ref location) && !_resizeButtonRect.MakeOffsetted(Location).Contains(ref location);
|
||||
}
|
||||
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
_textBox.Text = (string)Values[0];
|
||||
|
||||
var size = SizeValue;
|
||||
if (Surface != null && Surface.GridSnappingEnabled)
|
||||
size = Surface.SnapToGrid(size, true);
|
||||
Resize(size.X, size.Y);
|
||||
}
|
||||
|
||||
public override void OnValuesChanged()
|
||||
{
|
||||
base.OnValuesChanged();
|
||||
|
||||
var size = SizeValue;
|
||||
Resize(size.X, size.Y);
|
||||
_textBox.Text = (string)Values[0];
|
||||
}
|
||||
|
||||
protected override void UpdateRectangles()
|
||||
{
|
||||
base.UpdateRectangles();
|
||||
|
||||
const float buttonMargin = FlaxEditor.Surface.Constants.NodeCloseButtonMargin;
|
||||
const float buttonSize = FlaxEditor.Surface.Constants.NodeCloseButtonSize;
|
||||
_resizeButtonRect = new Rectangle(_closeButtonRect.Left, Height - buttonSize - buttonMargin - 4, buttonSize, buttonSize);
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
var style = Style.Current;
|
||||
if (_isResizing)
|
||||
{
|
||||
Render2D.FillRectangle(_resizeButtonRect, style.Selection);
|
||||
Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder);
|
||||
}
|
||||
Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
|
||||
}
|
||||
|
||||
public override void OnLostFocus()
|
||||
{
|
||||
if (_isResizing)
|
||||
EndResizing();
|
||||
|
||||
base.OnLostFocus();
|
||||
}
|
||||
|
||||
public override void OnEndMouseCapture()
|
||||
{
|
||||
if (_isResizing)
|
||||
EndResizing();
|
||||
|
||||
base.OnEndMouseCapture();
|
||||
}
|
||||
|
||||
public override bool OnMouseDown(Float2 location, MouseButton button)
|
||||
{
|
||||
if (base.OnMouseDown(location, button))
|
||||
return true;
|
||||
|
||||
if (button == MouseButton.Left && _resizeButtonRect.Contains(ref location) && Surface.CanEdit)
|
||||
{
|
||||
// Start sliding
|
||||
_isResizing = true;
|
||||
_startResizingSize = Size;
|
||||
_startResizingCornerOffset = Size - location;
|
||||
StartMouseCapture();
|
||||
Cursor = CursorType.SizeNWSE;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void OnMouseMove(Float2 location)
|
||||
{
|
||||
if (_isResizing)
|
||||
{
|
||||
var emptySize = CalculateNodeSize(0, 0);
|
||||
var size = Float2.Max(location - emptySize + _startResizingCornerOffset, new Float2(240, 160));
|
||||
Resize(size.X, size.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnMouseMove(location);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left && _isResizing)
|
||||
{
|
||||
EndResizing();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
private void EndResizing()
|
||||
{
|
||||
Cursor = CursorType.Default;
|
||||
EndMouseCapture();
|
||||
_isResizing = false;
|
||||
if (_startResizingSize != Size)
|
||||
{
|
||||
var emptySize = CalculateNodeSize(0, 0);
|
||||
SizeValue = Size - emptySize;
|
||||
Surface.MarkAsEdited(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal enum MaterialTemplateInputsMapping
|
||||
|
||||
@@ -1100,6 +1100,27 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
NodeElementArchetype.Factory.ComboBox(2 + 20, 0, 116)
|
||||
}
|
||||
},
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 5,
|
||||
Create = (id, context, arch, groupArch) => new SurfaceNodeParamsSet(id, context, arch, groupArch),
|
||||
Title = "Set Parameter",
|
||||
Description = "Parameter value setter invoked when the animation pose is evaluated (output pose comes from input)",
|
||||
Flags = NodeFlags.AnimGraph,
|
||||
Size = new Float2(140, 40),
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
Guid.Empty,
|
||||
null
|
||||
},
|
||||
Elements = new[]
|
||||
{
|
||||
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(void), 0),
|
||||
NodeElementArchetype.Factory.Input(0, string.Empty, true, typeof(void), 2),
|
||||
NodeElementArchetype.Factory.Input(1, string.Empty, true, ScriptType.Null, 1, 1),
|
||||
NodeElementArchetype.Factory.ComboBox(2 + 20, 0, 116)
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,11 +23,14 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
TextureGroup = 4,
|
||||
}
|
||||
|
||||
internal class SampleTextureNode : SurfaceNode
|
||||
internal class TextureSamplerNode : SurfaceNode
|
||||
{
|
||||
private ComboBox _textureGroupPicker;
|
||||
protected int _samplerTypeValueIndex = -1;
|
||||
protected int _textureGroupValueIndex = -1;
|
||||
protected int _level = 5;
|
||||
|
||||
public SampleTextureNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
protected TextureSamplerNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
}
|
||||
@@ -48,13 +51,13 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
|
||||
private void UpdateUI()
|
||||
{
|
||||
if ((int)Values[0] == (int)CommonSamplerType.TextureGroup)
|
||||
if ((int)Values[_samplerTypeValueIndex] == (int)CommonSamplerType.TextureGroup)
|
||||
{
|
||||
if (_textureGroupPicker == null)
|
||||
{
|
||||
_textureGroupPicker = new ComboBox
|
||||
{
|
||||
Location = new Float2(FlaxEditor.Surface.Constants.NodeMarginX + 50, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize + FlaxEditor.Surface.Constants.LayoutOffsetY * 5),
|
||||
Location = new Float2(FlaxEditor.Surface.Constants.NodeMarginX + 50, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize + FlaxEditor.Surface.Constants.LayoutOffsetY * _level),
|
||||
Width = 100,
|
||||
Parent = this,
|
||||
};
|
||||
@@ -71,7 +74,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
_textureGroupPicker.Visible = true;
|
||||
}
|
||||
_textureGroupPicker.SelectedIndexChanged -= OnSelectedTextureGroupChanged;
|
||||
_textureGroupPicker.SelectedIndex = (int)Values[2];
|
||||
_textureGroupPicker.SelectedIndex = (int)Values[_textureGroupValueIndex];
|
||||
_textureGroupPicker.SelectedIndexChanged += OnSelectedTextureGroupChanged;
|
||||
}
|
||||
else if (_textureGroupPicker != null)
|
||||
@@ -83,7 +86,39 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
|
||||
private void OnSelectedTextureGroupChanged(ComboBox comboBox)
|
||||
{
|
||||
SetValue(2, _textureGroupPicker.SelectedIndex);
|
||||
SetValue(_textureGroupValueIndex, _textureGroupPicker.SelectedIndex);
|
||||
}
|
||||
}
|
||||
|
||||
internal class SampleTextureNode : TextureSamplerNode
|
||||
{
|
||||
public SampleTextureNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
_samplerTypeValueIndex = 0;
|
||||
_textureGroupValueIndex = 2;
|
||||
}
|
||||
}
|
||||
|
||||
internal class TriplanarSampleTextureNode : TextureSamplerNode
|
||||
{
|
||||
public TriplanarSampleTextureNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
_samplerTypeValueIndex = 3;
|
||||
_textureGroupValueIndex = 5;
|
||||
_level = 5;
|
||||
}
|
||||
}
|
||||
|
||||
internal class ProceduralSampleTextureNode : TextureSamplerNode
|
||||
{
|
||||
public ProceduralSampleTextureNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
_samplerTypeValueIndex = 0;
|
||||
_textureGroupValueIndex = 2;
|
||||
_level = 4;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,9 +315,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
ConnectionsHints = ConnectionsHint.Vector,
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
0,
|
||||
-1.0f,
|
||||
0,
|
||||
(int)CommonSamplerType.LinearClamp, // Sampler
|
||||
-1.0f, // Level
|
||||
0, // Texture Group
|
||||
},
|
||||
Elements = new[]
|
||||
{
|
||||
@@ -402,6 +437,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 16,
|
||||
Create = (id, context, arch, groupArch) => new TriplanarSampleTextureNode(id, context, arch, groupArch),
|
||||
Title = "Triplanar Texture",
|
||||
Description = "Projects a texture using world-space coordinates with triplanar mapping.",
|
||||
Flags = NodeFlags.MaterialGraph,
|
||||
@@ -411,8 +447,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
Float3.One, // Scale
|
||||
1.0f, // Blend
|
||||
Float2.Zero, // Offset
|
||||
2, // Sampler
|
||||
(int)CommonSamplerType.LinearWrap, // Sampler
|
||||
false, // Local
|
||||
0, // Texture Group
|
||||
},
|
||||
Elements = new[]
|
||||
{
|
||||
@@ -430,17 +467,17 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 17,
|
||||
Create = (id, context, arch, groupArch) => new SampleTextureNode(id, context, arch, groupArch),
|
||||
Create = (id, context, arch, groupArch) => new ProceduralSampleTextureNode(id, context, arch, groupArch),
|
||||
Title = "Procedural Sample Texture",
|
||||
Description = "Samples a texture to create a more natural look with less obvious tiling.",
|
||||
Flags = NodeFlags.MaterialGraph,
|
||||
Size = new Float2(240, 110),
|
||||
Size = new Float2(240, 130),
|
||||
ConnectionsHints = ConnectionsHint.Vector,
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
2,
|
||||
-1.0f,
|
||||
0,
|
||||
(int)CommonSamplerType.LinearWrap, // Sampler
|
||||
-1.0f, // Level
|
||||
0, // Texture Group
|
||||
},
|
||||
Elements = new[]
|
||||
{
|
||||
@@ -448,8 +485,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
NodeElementArchetype.Factory.Input(1, "UVs", true, null, 1),
|
||||
NodeElementArchetype.Factory.Input(2, "Offset", true, typeof(Float2), 3),
|
||||
NodeElementArchetype.Factory.Output(0, "Color", typeof(Float4), 4),
|
||||
NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 4, "Sampler"),
|
||||
NodeElementArchetype.Factory.ComboBox(50, Surface.Constants.LayoutOffsetY * 4, 100, 0, typeof(CommonSamplerType))
|
||||
NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 3, "Sampler"),
|
||||
NodeElementArchetype.Factory.ComboBox(50, Surface.Constants.LayoutOffsetY * 3, 100, 0, typeof(CommonSamplerType))
|
||||
}
|
||||
},
|
||||
new NodeArchetype
|
||||
@@ -469,6 +506,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
TypeID = 23,
|
||||
Title = "Triplanar Normal Map",
|
||||
Create = (id, context, arch, groupArch) => new TriplanarSampleTextureNode(id, context, arch, groupArch),
|
||||
Description = "Projects a normal map texture using world-space coordinates with triplanar mapping.",
|
||||
Flags = NodeFlags.MaterialGraph,
|
||||
Size = new Float2(280, 100),
|
||||
@@ -477,8 +515,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
Float3.One, // Scale
|
||||
1.0f, // Blend
|
||||
Float2.Zero, // Offset
|
||||
2, // Sampler
|
||||
(int)CommonSamplerType.LinearWrap, // Sampler
|
||||
false, // Local
|
||||
0, // Texture Group
|
||||
},
|
||||
Elements = new[]
|
||||
{
|
||||
|
||||
@@ -453,7 +453,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
}
|
||||
|
||||
private class CurveNode<T> : SurfaceNode where T : struct
|
||||
private class CurveNode<T> : ResizableSurfaceNode where T : struct
|
||||
{
|
||||
private BezierCurveEditor<T> _curve;
|
||||
private bool _isSavingCurve;
|
||||
@@ -467,7 +467,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
Create = (id, context, arch, groupArch) => new CurveNode<T>(id, context, arch, groupArch),
|
||||
Description = "An animation spline represented by a set of keyframes, each representing an endpoint of a Bezier curve.",
|
||||
Flags = NodeFlags.AllGraphs,
|
||||
Size = new Float2(400, 180.0f),
|
||||
Size = new Float2(400, 180),
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
// Keyframes count
|
||||
@@ -491,6 +491,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
0.0f, zero, zero, zero,
|
||||
0.0f, zero, zero, zero,
|
||||
0.0f, zero, zero, zero,
|
||||
|
||||
new Float2(400, 180),
|
||||
},
|
||||
Elements = new[]
|
||||
{
|
||||
@@ -504,30 +506,52 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
public CurveNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
_sizeValueIndex = 29; // Index of the Size stored in Values array
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded(action);
|
||||
|
||||
// Create curve editor
|
||||
var upperLeft = GetBox(0).BottomLeft;
|
||||
var upperRight = GetBox(1).BottomRight;
|
||||
float curveMargin = 20.0f;
|
||||
|
||||
_curve = new BezierCurveEditor<T>
|
||||
{
|
||||
MaxKeyframes = 7,
|
||||
Bounds = new Rectangle(upperLeft + new Float2(curveMargin, 10.0f), upperRight.X - upperLeft.X - curveMargin * 2.0f, 140.0f),
|
||||
Parent = this
|
||||
Parent = this,
|
||||
AnchorMax = Float2.One,
|
||||
};
|
||||
_curve.Edited += OnCurveEdited;
|
||||
_curve.UnlockChildrenRecursive();
|
||||
_curve.PerformLayout();
|
||||
|
||||
// Sync keyframes
|
||||
UpdateCurveKeyframes();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
// Ensure the whole curve is shown
|
||||
_curve.ShowWholeCurve();
|
||||
}
|
||||
|
||||
public override void OnValuesChanged()
|
||||
{
|
||||
base.OnValuesChanged();
|
||||
|
||||
if (!_isSavingCurve)
|
||||
{
|
||||
UpdateCurveKeyframes();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCurveEdited()
|
||||
{
|
||||
if (_isSavingCurve)
|
||||
@@ -553,17 +577,6 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
_isSavingCurve = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnValuesChanged()
|
||||
{
|
||||
base.OnValuesChanged();
|
||||
|
||||
if (!_isSavingCurve)
|
||||
{
|
||||
UpdateCurveKeyframes();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCurveKeyframes()
|
||||
{
|
||||
var count = (int)Values[0];
|
||||
@@ -1575,7 +1588,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
Guid.Empty,
|
||||
string.Empty
|
||||
string.Empty,
|
||||
},
|
||||
Elements = new[]
|
||||
{
|
||||
|
||||
182
Source/Editor/Surface/ResizableSurfaceNode.cs
Normal file
182
Source/Editor/Surface/ResizableSurfaceNode.cs
Normal file
@@ -0,0 +1,182 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.Surface
|
||||
{
|
||||
/// <summary>
|
||||
/// Visject Surface node control that cna be resized.
|
||||
/// </summary>
|
||||
/// <seealso cref="SurfaceNode" />
|
||||
[HideInEditor]
|
||||
public class ResizableSurfaceNode : SurfaceNode
|
||||
{
|
||||
private Float2 _startResizingSize;
|
||||
private Float2 _startResizingCornerOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the node is currently being resized.
|
||||
/// </summary>
|
||||
protected bool _isResizing;
|
||||
|
||||
/// <summary>
|
||||
/// Index of the Float2 value in the node values list to store node size.
|
||||
/// </summary>
|
||||
protected int _sizeValueIndex = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum node size.
|
||||
/// </summary>
|
||||
protected Float2 _sizeMin = new Float2(240, 160);
|
||||
|
||||
/// <summary>
|
||||
/// Node resizing rectangle bounds.
|
||||
/// </summary>
|
||||
protected Rectangle _resizeButtonRect;
|
||||
|
||||
private Float2 SizeValue
|
||||
{
|
||||
get => (Float2)Values[_sizeValueIndex];
|
||||
set => SetValue(_sizeValueIndex, value, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ResizableSurfaceNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanSelect(ref Float2 location)
|
||||
{
|
||||
return base.CanSelect(ref location) && !_resizeButtonRect.MakeOffsetted(Location).Contains(ref location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
// Reapply the curve node size
|
||||
var size = SizeValue;
|
||||
if (Surface != null && Surface.GridSnappingEnabled)
|
||||
size = Surface.SnapToGrid(size, true);
|
||||
Resize(size.X, size.Y);
|
||||
|
||||
base.OnSurfaceLoaded(action);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnValuesChanged()
|
||||
{
|
||||
base.OnValuesChanged();
|
||||
|
||||
var size = SizeValue;
|
||||
Resize(size.X, size.Y);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
if (Surface.CanEdit)
|
||||
{
|
||||
var style = Style.Current;
|
||||
if (_isResizing)
|
||||
{
|
||||
Render2D.FillRectangle(_resizeButtonRect, style.Selection);
|
||||
Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder);
|
||||
}
|
||||
Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLostFocus()
|
||||
{
|
||||
if (_isResizing)
|
||||
EndResizing();
|
||||
|
||||
base.OnLostFocus();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnEndMouseCapture()
|
||||
{
|
||||
if (_isResizing)
|
||||
EndResizing();
|
||||
|
||||
base.OnEndMouseCapture();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDown(Float2 location, MouseButton button)
|
||||
{
|
||||
if (base.OnMouseDown(location, button))
|
||||
return true;
|
||||
|
||||
if (button == MouseButton.Left && _resizeButtonRect.Contains(ref location) && Surface.CanEdit)
|
||||
{
|
||||
// Start resizing
|
||||
_isResizing = true;
|
||||
_startResizingSize = Size;
|
||||
_startResizingCornerOffset = Size - location;
|
||||
StartMouseCapture();
|
||||
Cursor = CursorType.SizeNWSE;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseMove(Float2 location)
|
||||
{
|
||||
if (_isResizing)
|
||||
{
|
||||
var emptySize = CalculateNodeSize(0, 0);
|
||||
var size = Float2.Max(location - emptySize + _startResizingCornerOffset, _sizeMin);
|
||||
Resize(size.X, size.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnMouseMove(location);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left && _isResizing)
|
||||
{
|
||||
EndResizing();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateRectangles()
|
||||
{
|
||||
base.UpdateRectangles();
|
||||
|
||||
const float buttonMargin = Constants.NodeCloseButtonMargin;
|
||||
const float buttonSize = Constants.NodeCloseButtonSize;
|
||||
_resizeButtonRect = new Rectangle(_closeButtonRect.Left, Height - buttonSize - buttonMargin - 4, buttonSize, buttonSize);
|
||||
}
|
||||
|
||||
private void EndResizing()
|
||||
{
|
||||
Cursor = CursorType.Default;
|
||||
EndMouseCapture();
|
||||
_isResizing = false;
|
||||
if (_startResizingSize != Size)
|
||||
{
|
||||
var emptySize = CalculateNodeSize(0, 0);
|
||||
SizeValue = Size - emptySize;
|
||||
Surface.MarkAsEdited(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,18 +14,11 @@ namespace FlaxEditor.Surface
|
||||
/// </summary>
|
||||
/// <seealso cref="SurfaceNode" />
|
||||
[HideInEditor]
|
||||
public class SurfaceComment : SurfaceNode
|
||||
public class SurfaceComment : ResizableSurfaceNode
|
||||
{
|
||||
private Rectangle _colorButtonRect;
|
||||
private Rectangle _resizeButtonRect;
|
||||
private Float2 _startResizingSize;
|
||||
private readonly TextBox _renameTextBox;
|
||||
|
||||
/// <summary>
|
||||
/// True if sizing tool is in use.
|
||||
/// </summary>
|
||||
protected bool _isResizing;
|
||||
|
||||
/// <summary>
|
||||
/// True if rename textbox is active in order to rename comment
|
||||
/// </summary>
|
||||
@@ -52,12 +45,6 @@ namespace FlaxEditor.Surface
|
||||
set => SetValue(1, value, false);
|
||||
}
|
||||
|
||||
private Float2 SizeValue
|
||||
{
|
||||
get => (Float2)Values[2];
|
||||
set => SetValue(2, value, false);
|
||||
}
|
||||
|
||||
private int OrderValue
|
||||
{
|
||||
get => (int)Values[3];
|
||||
@@ -68,6 +55,8 @@ namespace FlaxEditor.Surface
|
||||
public SurfaceComment(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
_sizeValueIndex = 2; // Index of the Size stored in Values array
|
||||
_sizeMin = new Float2(140.0f, Constants.NodeHeaderSize);
|
||||
_renameTextBox = new TextBox(false, 0, 0, Width)
|
||||
{
|
||||
Height = Constants.NodeHeaderSize,
|
||||
@@ -86,10 +75,6 @@ namespace FlaxEditor.Surface
|
||||
// Read node data
|
||||
Title = TitleValue;
|
||||
Color = ColorValue;
|
||||
var size = SizeValue;
|
||||
if (Surface != null && Surface.GridSnappingEnabled)
|
||||
size = Surface.SnapToGrid(size, true);
|
||||
Size = size;
|
||||
|
||||
// Order
|
||||
// Backwards compatibility - When opening with an older version send the old comments to the back
|
||||
@@ -126,27 +111,6 @@ namespace FlaxEditor.Surface
|
||||
// Read node data
|
||||
Title = TitleValue;
|
||||
Color = ColorValue;
|
||||
Size = SizeValue;
|
||||
}
|
||||
|
||||
private void EndResizing()
|
||||
{
|
||||
// Clear state
|
||||
_isResizing = false;
|
||||
|
||||
if (_startResizingSize != Size)
|
||||
{
|
||||
SizeValue = Size;
|
||||
Surface.MarkAsEdited(false);
|
||||
}
|
||||
|
||||
EndMouseCapture();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanSelect(ref Float2 location)
|
||||
{
|
||||
return _headerRect.MakeOffsetted(Location).Contains(ref location) && !_resizeButtonRect.MakeOffsetted(Location).Contains(ref location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -158,6 +122,8 @@ namespace FlaxEditor.Surface
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateRectangles()
|
||||
{
|
||||
base.UpdateRectangles();
|
||||
|
||||
const float headerSize = Constants.NodeHeaderSize;
|
||||
const float buttonMargin = Constants.NodeCloseButtonMargin;
|
||||
const float buttonSize = Constants.NodeCloseButtonSize;
|
||||
@@ -222,16 +188,13 @@ namespace FlaxEditor.Surface
|
||||
// Color button
|
||||
Render2D.DrawSprite(style.Settings, _colorButtonRect, _colorButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
|
||||
|
||||
// Check if is resizing
|
||||
// Resize button
|
||||
if (_isResizing)
|
||||
{
|
||||
// Draw overlay
|
||||
Render2D.FillRectangle(_resizeButtonRect, style.Selection);
|
||||
Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder);
|
||||
}
|
||||
|
||||
// Resize button
|
||||
Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
|
||||
Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
|
||||
}
|
||||
|
||||
// Selection outline
|
||||
@@ -247,88 +210,28 @@ namespace FlaxEditor.Surface
|
||||
/// <inheritdoc />
|
||||
protected override Float2 CalculateNodeSize(float width, float height)
|
||||
{
|
||||
return Size;
|
||||
// No margins or headers
|
||||
return new Float2(width, height);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLostFocus()
|
||||
{
|
||||
// Check if was resizing
|
||||
if (_isResizing)
|
||||
{
|
||||
EndResizing();
|
||||
}
|
||||
|
||||
// Check if was renaming
|
||||
if (_isRenaming)
|
||||
{
|
||||
Rename(_renameTextBox.Text);
|
||||
StopRenaming();
|
||||
}
|
||||
|
||||
// Base
|
||||
base.OnLostFocus();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnEndMouseCapture()
|
||||
{
|
||||
// Check if was resizing
|
||||
if (_isResizing)
|
||||
{
|
||||
EndResizing();
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnEndMouseCapture();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool ContainsPoint(ref Float2 location, bool precise)
|
||||
{
|
||||
return _headerRect.Contains(ref location) || _resizeButtonRect.Contains(ref location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDown(Float2 location, MouseButton button)
|
||||
{
|
||||
if (base.OnMouseDown(location, button))
|
||||
return true;
|
||||
|
||||
// Check if can start resizing
|
||||
if (button == MouseButton.Left && _resizeButtonRect.Contains(ref location) && Surface.CanEdit)
|
||||
{
|
||||
// Start sliding
|
||||
_isResizing = true;
|
||||
_startResizingSize = Size;
|
||||
StartMouseCapture();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseMove(Float2 location)
|
||||
{
|
||||
// Check if is resizing
|
||||
if (_isResizing)
|
||||
{
|
||||
// Update size
|
||||
var size = Float2.Max(location, new Float2(140.0f, _headerRect.Bottom));
|
||||
if (Surface.GridSnappingEnabled)
|
||||
size = Surface.SnapToGrid(size, true);
|
||||
Size = size;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Base
|
||||
base.OnMouseMove(location);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
|
||||
{
|
||||
@@ -394,12 +297,6 @@ namespace FlaxEditor.Surface
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left && _isResizing)
|
||||
{
|
||||
EndResizing();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (base.OnMouseUp(location, button))
|
||||
return true;
|
||||
|
||||
|
||||
@@ -724,7 +724,12 @@ namespace FlaxEditor.Surface
|
||||
|
||||
if (HasNodesSelection)
|
||||
{
|
||||
var keyMoveRange = 50;
|
||||
var keyMoveDelta = 50;
|
||||
bool altDown = RootWindow.GetKey(KeyboardKeys.Alt);
|
||||
bool shiftDown = RootWindow.GetKey(KeyboardKeys.Shift);
|
||||
if (altDown || shiftDown)
|
||||
keyMoveDelta = shiftDown ? 10 : 25;
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case KeyboardKeys.Backspace:
|
||||
@@ -752,17 +757,18 @@ namespace FlaxEditor.Surface
|
||||
Box selectedBox = GetSelectedBox(SelectedNodes);
|
||||
if (selectedBox != null)
|
||||
{
|
||||
Box toSelect = (key == KeyboardKeys.ArrowUp) ? selectedBox?.ParentNode.GetPreviousBox(selectedBox) : selectedBox?.ParentNode.GetNextBox(selectedBox);
|
||||
if (toSelect != null && toSelect.IsOutput == selectedBox.IsOutput)
|
||||
{
|
||||
Select(toSelect.ParentNode);
|
||||
toSelect.ParentNode.SelectBox(toSelect);
|
||||
}
|
||||
int delta = key == KeyboardKeys.ArrowDown ? 1 : -1;
|
||||
List<Box> boxes = selectedBox.ParentNode.GetBoxes().FindAll(b => b.IsOutput == selectedBox.IsOutput);
|
||||
int selectedIndex = boxes.IndexOf(selectedBox);
|
||||
Box toSelect = boxes[Mathf.Wrap(selectedIndex + delta, 0, boxes.Count - 1)];
|
||||
|
||||
Select(toSelect.ParentNode);
|
||||
toSelect.ParentNode.SelectBox(toSelect);
|
||||
}
|
||||
else if (!IsMovingSelection && CanEdit)
|
||||
{
|
||||
// Move selected nodes
|
||||
var delta = new Float2(0, key == KeyboardKeys.ArrowUp ? -keyMoveRange : keyMoveRange);
|
||||
var delta = new Float2(0, key == KeyboardKeys.ArrowUp ? -keyMoveDelta : keyMoveDelta);
|
||||
MoveSelectedNodes(delta);
|
||||
}
|
||||
return true;
|
||||
@@ -775,12 +781,8 @@ namespace FlaxEditor.Surface
|
||||
if (selectedBox != null)
|
||||
{
|
||||
Box toSelect = null;
|
||||
if ((key == KeyboardKeys.ArrowRight && selectedBox.IsOutput) || (key == KeyboardKeys.ArrowLeft && !selectedBox.IsOutput))
|
||||
if (((key == KeyboardKeys.ArrowRight && selectedBox.IsOutput) || (key == KeyboardKeys.ArrowLeft && !selectedBox.IsOutput)) && selectedBox.HasAnyConnection)
|
||||
{
|
||||
if (_selectedConnectionIndex < 0 || _selectedConnectionIndex >= selectedBox.Connections.Count)
|
||||
{
|
||||
_selectedConnectionIndex = 0;
|
||||
}
|
||||
toSelect = selectedBox.Connections[_selectedConnectionIndex];
|
||||
}
|
||||
else
|
||||
@@ -808,7 +810,7 @@ namespace FlaxEditor.Surface
|
||||
else if (!IsMovingSelection && CanEdit)
|
||||
{
|
||||
// Move selected nodes
|
||||
var delta = new Float2(key == KeyboardKeys.ArrowLeft ? -keyMoveRange : keyMoveRange, 0);
|
||||
var delta = new Float2(key == KeyboardKeys.ArrowLeft ? -keyMoveDelta : keyMoveDelta, 0);
|
||||
MoveSelectedNodes(delta);
|
||||
}
|
||||
return true;
|
||||
@@ -824,13 +826,9 @@ namespace FlaxEditor.Surface
|
||||
return true;
|
||||
|
||||
if (Root.GetKey(KeyboardKeys.Shift))
|
||||
{
|
||||
_selectedConnectionIndex = ((_selectedConnectionIndex - 1) % connectionCount + connectionCount) % connectionCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectedConnectionIndex = (_selectedConnectionIndex + 1) % connectionCount;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ namespace FlaxEditor.Tools.Terrain
|
||||
{
|
||||
if (terrain.Scene && terrain.HasStaticFlag(StaticFlags.Navigation))
|
||||
{
|
||||
Navigation.BuildNavMesh(terrain.Scene, patchBounds, editorOptions.General.AutoRebuildNavMeshTimeoutMs);
|
||||
Navigation.BuildNavMesh(patchBounds, editorOptions.General.AutoRebuildNavMeshTimeoutMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ namespace FlaxEditor.Tools.Terrain
|
||||
{
|
||||
if (terrain.Scene && terrain.HasStaticFlag(StaticFlags.Navigation))
|
||||
{
|
||||
Navigation.BuildNavMesh(terrain.Scene, patchBounds, editorOptions.General.AutoRebuildNavMeshTimeoutMs);
|
||||
Navigation.BuildNavMesh(patchBounds, editorOptions.General.AutoRebuildNavMeshTimeoutMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ namespace FlaxEditor.Tools.Terrain.Undo
|
||||
if (_navmeshBoundsModifications != null)
|
||||
{
|
||||
foreach (var bounds in _navmeshBoundsModifications)
|
||||
Navigation.BuildNavMesh(scene, bounds, _dirtyNavMeshTimeoutMs);
|
||||
Navigation.BuildNavMesh(bounds, _dirtyNavMeshTimeoutMs);
|
||||
}
|
||||
|
||||
Editor.Instance.Scene.MarkSceneEdited(scene);
|
||||
@@ -217,11 +217,10 @@ namespace FlaxEditor.Tools.Terrain.Undo
|
||||
}
|
||||
|
||||
// Update navmesh
|
||||
var scene = Terrain.Scene;
|
||||
if (_navmeshBoundsModifications != null)
|
||||
{
|
||||
foreach (var bounds in _navmeshBoundsModifications)
|
||||
Navigation.BuildNavMesh(scene, bounds, _dirtyNavMeshTimeoutMs);
|
||||
Navigation.BuildNavMesh(bounds, _dirtyNavMeshTimeoutMs);
|
||||
}
|
||||
|
||||
Editor.Instance.Scene.MarkSceneEdited(Terrain.Scene);
|
||||
|
||||
@@ -303,7 +303,7 @@ namespace FlaxEditor.Actions
|
||||
if (_nodeParents[i] is ActorNode node && node.Actor && node.Actor.Scene && node.AffectsNavigationWithChildren)
|
||||
{
|
||||
var bounds = node.Actor.BoxWithChildren;
|
||||
Navigation.BuildNavMesh(node.Actor.Scene, bounds, options.General.AutoRebuildNavMeshTimeoutMs);
|
||||
Navigation.BuildNavMesh(bounds, options.General.AutoRebuildNavMeshTimeoutMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,8 @@ namespace FlaxEditor.Actions
|
||||
var obj = Object.Find<SceneObject>(ref item.ID);
|
||||
if (obj != null)
|
||||
{
|
||||
scenes.Add(obj.Parent.Scene);
|
||||
if (obj.Parent != null)
|
||||
scenes.Add(obj.Parent.Scene);
|
||||
if (obj is Actor actor)
|
||||
actor.SetParent(newParent, _worldPositionsStays, true);
|
||||
else
|
||||
|
||||
@@ -121,12 +121,12 @@ namespace FlaxEditor
|
||||
// Handle simple case where objects were moved just a little and use one navmesh build request to improve performance
|
||||
if (data.BeforeBounds.Intersects(ref data.AfterBounds))
|
||||
{
|
||||
Navigation.BuildNavMesh(data.Scene, BoundingBox.Merge(data.BeforeBounds, data.AfterBounds), options.General.AutoRebuildNavMeshTimeoutMs);
|
||||
Navigation.BuildNavMesh(BoundingBox.Merge(data.BeforeBounds, data.AfterBounds), options.General.AutoRebuildNavMeshTimeoutMs);
|
||||
}
|
||||
else
|
||||
{
|
||||
Navigation.BuildNavMesh(data.Scene, data.BeforeBounds, options.General.AutoRebuildNavMeshTimeoutMs);
|
||||
Navigation.BuildNavMesh(data.Scene, data.AfterBounds, options.General.AutoRebuildNavMeshTimeoutMs);
|
||||
Navigation.BuildNavMesh(data.BeforeBounds, options.General.AutoRebuildNavMeshTimeoutMs);
|
||||
Navigation.BuildNavMesh(data.AfterBounds, options.General.AutoRebuildNavMeshTimeoutMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -584,7 +584,7 @@ namespace FlaxEditor.Viewport
|
||||
_cameraButton = new ViewportWidgetButton(string.Format(MovementSpeedTextFormat, _movementSpeed), _editor.Icons.Camera64, cameraCM, false, cameraSpeedTextWidth)
|
||||
{
|
||||
Tag = this,
|
||||
TooltipText = "Camera Settings",
|
||||
TooltipText = "Camera Settings.",
|
||||
Parent = _cameraWidget
|
||||
};
|
||||
_cameraWidget.Parent = this;
|
||||
@@ -593,7 +593,7 @@ namespace FlaxEditor.Viewport
|
||||
_orthographicModeButton = new ViewportWidgetButton(string.Empty, _editor.Icons.CamSpeed32, null, true)
|
||||
{
|
||||
Checked = !_isOrtho,
|
||||
TooltipText = "Toggle Orthographic/Perspective Mode",
|
||||
TooltipText = "Toggle Orthographic/Perspective Mode.",
|
||||
Parent = _cameraWidget
|
||||
};
|
||||
_orthographicModeButton.Toggled += OnOrthographicModeToggled;
|
||||
@@ -832,7 +832,7 @@ namespace FlaxEditor.Viewport
|
||||
ViewWidgetButtonMenu = new ContextMenu();
|
||||
var viewModeButton = new ViewportWidgetButton("View", SpriteHandle.Invalid, ViewWidgetButtonMenu)
|
||||
{
|
||||
TooltipText = "View properties",
|
||||
TooltipText = "View properties.",
|
||||
Parent = viewMode
|
||||
};
|
||||
viewMode.Parent = this;
|
||||
@@ -863,8 +863,10 @@ namespace FlaxEditor.Viewport
|
||||
{
|
||||
}
|
||||
});
|
||||
viewLayers.AddButton("Reset layers", () => Task.ViewLayersMask = LayersMask.Default).Icon = _editor.Icons.Rotate32;
|
||||
viewLayers.AddButton("Disable layers", () => Task.ViewLayersMask = new LayersMask(0));
|
||||
viewLayers.AddButton("Reset layers", () => Task.ViewLayersMask = LayersMask.Default).Icon = Editor.Instance.Icons.Rotate32;
|
||||
viewLayers.AddSeparator();
|
||||
viewLayers.AddButton("Enable all", () => Task.ViewLayersMask = new LayersMask(-1)).Icon = Editor.Instance.Icons.CheckBoxTick12;
|
||||
viewLayers.AddButton("Disable all", () => Task.ViewLayersMask = new LayersMask(0)).Icon = Editor.Instance.Icons.Cross12;
|
||||
viewLayers.AddSeparator();
|
||||
var layers = LayersAndTagsSettings.GetCurrentLayers();
|
||||
if (layers != null && layers.Length > 0)
|
||||
@@ -904,8 +906,10 @@ namespace FlaxEditor.Viewport
|
||||
{
|
||||
}
|
||||
});
|
||||
viewFlags.AddButton("Reset flags", () => Task.ViewFlags = ViewFlags.DefaultEditor).Icon = _editor.Icons.Rotate32;
|
||||
viewFlags.AddButton("Disable flags", () => Task.ViewFlags = ViewFlags.None);
|
||||
viewFlags.AddButton("Reset flags", () => Task.ViewFlags = ViewFlags.DefaultEditor).Icon = Editor.Instance.Icons.Rotate32;
|
||||
viewFlags.AddSeparator();
|
||||
viewFlags.AddButton("Enable all", () => Task.ViewFlags = ViewFlags.All).Icon = Editor.Instance.Icons.CheckBoxTick12;
|
||||
viewFlags.AddButton("Disable all", () => Task.ViewFlags = ViewFlags.None).Icon = Editor.Instance.Icons.Cross12;
|
||||
viewFlags.AddSeparator();
|
||||
for (int i = 0; i < ViewFlagsValues.Length; i++)
|
||||
{
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace FlaxEditor.Viewport
|
||||
private readonly Editor _editor;
|
||||
private readonly ContextMenuButton _showGridButton;
|
||||
private readonly ContextMenuButton _showNavigationButton;
|
||||
private readonly ContextMenuButton _toggleGameViewButton;
|
||||
private SelectionOutline _customSelectionOutline;
|
||||
|
||||
/// <summary>
|
||||
@@ -108,6 +109,13 @@ namespace FlaxEditor.Viewport
|
||||
private EditorSpritesRenderer _editorSpritesRenderer;
|
||||
private ViewportRubberBandSelector _rubberBandSelector;
|
||||
|
||||
private bool _gameViewActive;
|
||||
private ViewFlags _preGameViewFlags;
|
||||
private ViewMode _preGameViewViewMode;
|
||||
private bool _gameViewWasGridShown;
|
||||
private bool _gameViewWasFpsCounterShown;
|
||||
private bool _gameViewWasNagivationShown;
|
||||
|
||||
/// <summary>
|
||||
/// Drag and drop handlers
|
||||
/// </summary>
|
||||
@@ -185,6 +193,7 @@ namespace FlaxEditor.Viewport
|
||||
: base(Object.New<SceneRenderTask>(), editor.Undo, editor.Scene.Root)
|
||||
{
|
||||
_editor = editor;
|
||||
var inputOptions = _editor.Options.Options.Input;
|
||||
DragHandlers = new ViewportDragHandlers(this, this, ValidateDragItem, ValidateDragActorType, ValidateDragScriptItem);
|
||||
|
||||
// Prepare rendering task
|
||||
@@ -232,9 +241,14 @@ namespace FlaxEditor.Viewport
|
||||
_showGridButton.CloseMenuOnClick = false;
|
||||
|
||||
// Show navigation widget
|
||||
_showNavigationButton = ViewWidgetShowMenu.AddButton("Navigation", () => ShowNavigation = !ShowNavigation);
|
||||
_showNavigationButton = ViewWidgetShowMenu.AddButton("Navigation", inputOptions.ToggleNavMeshVisibility, () => ShowNavigation = !ShowNavigation);
|
||||
_showNavigationButton.CloseMenuOnClick = false;
|
||||
|
||||
// Game View
|
||||
ViewWidgetButtonMenu.AddSeparator();
|
||||
_toggleGameViewButton = ViewWidgetButtonMenu.AddButton("Game View", inputOptions.ToggleGameView, ToggleGameView);
|
||||
_toggleGameViewButton.CloseMenuOnClick = false;
|
||||
|
||||
// Create camera widget
|
||||
ViewWidgetButtonMenu.AddSeparator();
|
||||
ViewWidgetButtonMenu.AddButton("Create camera here", CreateCameraAtView);
|
||||
@@ -259,6 +273,10 @@ namespace FlaxEditor.Viewport
|
||||
InputActions.Add(options => options.FocusSelection, FocusSelection);
|
||||
InputActions.Add(options => options.RotateSelection, RotateSelection);
|
||||
InputActions.Add(options => options.Delete, _editor.SceneEditing.Delete);
|
||||
InputActions.Add(options => options.ToggleNavMeshVisibility, () => ShowNavigation = !ShowNavigation);
|
||||
|
||||
// Game View
|
||||
InputActions.Add(options => options.ToggleGameView, ToggleGameView);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -373,9 +391,12 @@ namespace FlaxEditor.Viewport
|
||||
public void DrawEditorPrimitives(GPUContext context, ref RenderContext renderContext, GPUTexture target, GPUTexture targetDepth)
|
||||
{
|
||||
// Draw gizmos
|
||||
for (int i = 0; i < Gizmos.Count; i++)
|
||||
foreach (var gizmo in Gizmos)
|
||||
{
|
||||
Gizmos[i].Draw(ref renderContext);
|
||||
if (gizmo.Visible)
|
||||
{
|
||||
gizmo.Draw(ref renderContext);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw selected objects debug shapes and visuals
|
||||
@@ -481,6 +502,36 @@ namespace FlaxEditor.Viewport
|
||||
TransformGizmo.EndTransforming();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles game view view mode on or off.
|
||||
/// </summary>
|
||||
public void ToggleGameView()
|
||||
{
|
||||
if (!_gameViewActive)
|
||||
{
|
||||
// Cache flags & values
|
||||
_preGameViewFlags = Task.ViewFlags;
|
||||
_preGameViewViewMode = Task.ViewMode;
|
||||
_gameViewWasGridShown = Grid.Enabled;
|
||||
_gameViewWasFpsCounterShown = ShowFpsCounter;
|
||||
_gameViewWasNagivationShown = ShowNavigation;
|
||||
}
|
||||
|
||||
// Set flags & values
|
||||
Task.ViewFlags = _gameViewActive ? _preGameViewFlags : ViewFlags.DefaultGame;
|
||||
Task.ViewMode = _gameViewActive ? _preGameViewViewMode : ViewMode.Default;
|
||||
ShowFpsCounter = _gameViewActive ? _gameViewWasFpsCounterShown : false;
|
||||
ShowNavigation = _gameViewActive ? _gameViewWasNagivationShown : false;
|
||||
Grid.Enabled = _gameViewActive ? _gameViewWasGridShown : false;
|
||||
|
||||
_gameViewActive = !_gameViewActive;
|
||||
|
||||
TransformGizmo.Visible = !_gameViewActive;
|
||||
SelectionOutline.ShowSelectionOutline = !_gameViewActive;
|
||||
|
||||
_toggleGameViewButton.Icon = _gameViewActive ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLostFocus()
|
||||
{
|
||||
@@ -620,7 +671,7 @@ namespace FlaxEditor.Viewport
|
||||
{
|
||||
base.OnLeftMouseButtonDown();
|
||||
|
||||
_rubberBandSelector.TryStartingRubberBandSelection();
|
||||
_rubberBandSelector.TryStartingRubberBandSelection(_viewMousePos);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -720,9 +720,12 @@ namespace FlaxEditor.Viewport
|
||||
public override void DrawEditorPrimitives(GPUContext context, ref RenderContext renderContext, GPUTexture target, GPUTexture targetDepth)
|
||||
{
|
||||
// Draw gizmos
|
||||
for (int i = 0; i < Gizmos.Count; i++)
|
||||
foreach (var gizmo in Gizmos)
|
||||
{
|
||||
Gizmos[i].Draw(ref renderContext);
|
||||
if (gizmo.Visible)
|
||||
{
|
||||
gizmo.Draw(ref renderContext);
|
||||
}
|
||||
}
|
||||
|
||||
base.DrawEditorPrimitives(context, ref renderContext, target, targetDepth);
|
||||
|
||||
@@ -99,7 +99,14 @@ namespace FlaxEditor.Windows.Assets
|
||||
Window = window;
|
||||
var surfaceParam = window.Surface.GetParameter(BaseModelId);
|
||||
if (surfaceParam != null)
|
||||
BaseModel = FlaxEngine.Content.LoadAsync<SkinnedModel>((Guid)surfaceParam.Value);
|
||||
{
|
||||
if (surfaceParam.Value is Guid asGuid)
|
||||
BaseModel = FlaxEngine.Content.LoadAsync<SkinnedModel>(asGuid);
|
||||
else if (surfaceParam.Value is SkinnedModel asModel)
|
||||
BaseModel = asModel;
|
||||
else
|
||||
BaseModel = null;
|
||||
}
|
||||
else
|
||||
BaseModel = window.PreviewActor.GetParameterValue(BaseModelId) as SkinnedModel;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Xml;
|
||||
using FlaxEditor.Content;
|
||||
@@ -25,7 +24,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
/// </summary>
|
||||
/// <seealso cref="Animation" />
|
||||
/// <seealso cref="FlaxEditor.Windows.Assets.AssetEditorWindow" />
|
||||
public sealed class AnimationWindow : AssetEditorWindowBase<Animation>
|
||||
public sealed class AnimationWindow : ClonedAssetEditorWindowBase<Animation>
|
||||
{
|
||||
private sealed class Preview : AnimationPreview
|
||||
{
|
||||
@@ -255,6 +254,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
private bool _isWaitingForTimelineLoad;
|
||||
private SkinnedModel _initialPreviewModel, _initialBaseModel;
|
||||
private float _initialPanel2Splitter = 0.6f;
|
||||
private bool _timelineIsDirty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the animation timeline editor.
|
||||
@@ -295,7 +295,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
Parent = _panel1.Panel1,
|
||||
Enabled = false
|
||||
};
|
||||
_timeline.Modified += MarkAsEdited;
|
||||
_timeline.Modified += OnTimelineModified;
|
||||
_timeline.SetNoTracksText("Loading...");
|
||||
|
||||
// Asset properties
|
||||
@@ -321,11 +321,31 @@ namespace FlaxEditor.Windows.Assets
|
||||
{
|
||||
MarkAsEdited();
|
||||
UpdateToolstrip();
|
||||
_propertiesPresenter.BuildLayout();
|
||||
}
|
||||
|
||||
private void OnTimelineModified()
|
||||
{
|
||||
_timelineIsDirty = true;
|
||||
MarkAsEdited();
|
||||
}
|
||||
|
||||
private bool RefreshTempAsset()
|
||||
{
|
||||
if (_asset == null || _isWaitingForTimelineLoad)
|
||||
return true;
|
||||
if (_timeline.IsModified)
|
||||
{
|
||||
_timeline.Save(_asset);
|
||||
}
|
||||
_propertiesPresenter.BuildLayoutOnUpdate();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private string GetPreviewModelCacheName()
|
||||
{
|
||||
return _asset.ID + ".PreviewModel";
|
||||
return _item.ID + ".PreviewModel";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -361,7 +381,11 @@ namespace FlaxEditor.Windows.Assets
|
||||
if (!IsEdited)
|
||||
return;
|
||||
|
||||
_timeline.Save(_asset);
|
||||
if (RefreshTempAsset())
|
||||
return;
|
||||
if (SaveToOriginal())
|
||||
return;
|
||||
|
||||
ClearEditedFlag();
|
||||
_item.RefreshThumbnail();
|
||||
}
|
||||
@@ -414,10 +438,18 @@ namespace FlaxEditor.Windows.Assets
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
|
||||
// Check if temporary asset need to be updated
|
||||
if (_timelineIsDirty)
|
||||
{
|
||||
_timelineIsDirty = false;
|
||||
RefreshTempAsset();
|
||||
}
|
||||
|
||||
// Check if need to load timeline
|
||||
if (_isWaitingForTimelineLoad && _asset.IsLoaded)
|
||||
{
|
||||
_isWaitingForTimelineLoad = false;
|
||||
_timeline._id = _asset.ID;
|
||||
_timeline._id = _item.ID;
|
||||
_timeline.Load(_asset);
|
||||
_undo.Clear();
|
||||
_timeline.Enabled = true;
|
||||
|
||||
@@ -70,6 +70,13 @@ namespace FlaxEditor.Windows.Assets
|
||||
return;
|
||||
var nodes = proxy.Asset.Nodes;
|
||||
var bones = proxy.Asset.Bones;
|
||||
var blendShapes = proxy.Asset.BlendShapes;
|
||||
|
||||
// Info
|
||||
{
|
||||
var group = layout.Group("Info");
|
||||
group.Label($"Nodes: {nodes.Length}\nBones: {bones.Length}\nBlend Shapes: {blendShapes.Length}").AddCopyContextMenu().Label.Height *= 2.5f;
|
||||
}
|
||||
|
||||
// Skeleton Bones
|
||||
{
|
||||
@@ -109,7 +116,6 @@ namespace FlaxEditor.Windows.Assets
|
||||
}
|
||||
|
||||
// Blend Shapes
|
||||
var blendShapes = proxy.Asset.BlendShapes;
|
||||
if (blendShapes.Length != 0)
|
||||
{
|
||||
var group = layout.Group("Blend Shapes");
|
||||
|
||||
@@ -429,6 +429,7 @@ namespace FlaxEditor.Windows
|
||||
writer.WriteAttributeString("FarPlane", Viewport.FarPlane.ToString());
|
||||
writer.WriteAttributeString("FieldOfView", Viewport.FieldOfView.ToString());
|
||||
writer.WriteAttributeString("MovementSpeed", Viewport.MovementSpeed.ToString());
|
||||
writer.WriteAttributeString("ViewportIconsScale", ViewportIconsRenderer.Scale.ToString());
|
||||
writer.WriteAttributeString("OrthographicScale", Viewport.OrthographicScale.ToString());
|
||||
writer.WriteAttributeString("UseOrthographicProjection", Viewport.UseOrthographicProjection.ToString());
|
||||
writer.WriteAttributeString("ViewFlags", ((ulong)Viewport.Task.View.Flags).ToString());
|
||||
@@ -439,31 +440,24 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
if (bool.TryParse(node.GetAttribute("GridEnabled"), out bool value1))
|
||||
Viewport.Grid.Enabled = value1;
|
||||
|
||||
if (bool.TryParse(node.GetAttribute("ShowFpsCounter"), out value1))
|
||||
Viewport.ShowFpsCounter = value1;
|
||||
|
||||
if (bool.TryParse(node.GetAttribute("ShowNavigation"), out value1))
|
||||
Viewport.ShowNavigation = value1;
|
||||
|
||||
if (float.TryParse(node.GetAttribute("NearPlane"), out float value2))
|
||||
Viewport.NearPlane = value2;
|
||||
|
||||
if (float.TryParse(node.GetAttribute("FarPlane"), out value2))
|
||||
Viewport.FarPlane = value2;
|
||||
|
||||
if (float.TryParse(node.GetAttribute("FieldOfView"), out value2))
|
||||
Viewport.FieldOfView = value2;
|
||||
|
||||
if (float.TryParse(node.GetAttribute("MovementSpeed"), out value2))
|
||||
Viewport.MovementSpeed = value2;
|
||||
|
||||
if (float.TryParse(node.GetAttribute("ViewportIconsScale"), out value2))
|
||||
ViewportIconsRenderer.Scale = value2;
|
||||
if (float.TryParse(node.GetAttribute("OrthographicScale"), out value2))
|
||||
Viewport.OrthographicScale = value2;
|
||||
|
||||
if (bool.TryParse(node.GetAttribute("UseOrthographicProjection"), out value1))
|
||||
Viewport.UseOrthographicProjection = value1;
|
||||
|
||||
if (ulong.TryParse(node.GetAttribute("ViewFlags"), out ulong value3))
|
||||
Viewport.Task.ViewFlags = (ViewFlags)value3;
|
||||
|
||||
|
||||
@@ -336,11 +336,13 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
|
||||
SkeletonData* animResultSkeleton = &skeleton;
|
||||
|
||||
// Retarget animation when using output pose from other skeleton
|
||||
AnimGraphImpulse retargetNodes;
|
||||
if (_graph.BaseModel != data.NodesSkeleton)
|
||||
{
|
||||
ANIM_GRAPH_PROFILE_EVENT("Retarget");
|
||||
auto& targetSkeleton = data.NodesSkeleton->Skeleton;
|
||||
if (context.PoseCacheSize == context.PoseCache.Count())
|
||||
context.PoseCache.AddOne();
|
||||
auto& retargetNodes = context.PoseCache[context.PoseCacheSize++];
|
||||
retargetNodes = *animResult;
|
||||
retargetNodes.Nodes.Resize(targetSkeleton.Nodes.Count());
|
||||
Transform* targetNodes = retargetNodes.Nodes.Get();
|
||||
|
||||
@@ -109,86 +109,84 @@ namespace
|
||||
nodes->RootMotion.Orientation.Normalize();
|
||||
}
|
||||
}
|
||||
|
||||
Matrix ComputeWorldMatrixRecursive(const SkeletonData& skeleton, int32 index, Matrix localMatrix)
|
||||
{
|
||||
const auto& node = skeleton.Nodes[index];
|
||||
index = node.ParentIndex;
|
||||
while (index != -1)
|
||||
{
|
||||
const auto& parent = skeleton.Nodes[index];
|
||||
localMatrix *= parent.LocalTransform.GetWorld();
|
||||
index = parent.ParentIndex;
|
||||
}
|
||||
return localMatrix;
|
||||
}
|
||||
|
||||
Matrix ComputeInverseParentMatrixRecursive(const SkeletonData& skeleton, int32 index)
|
||||
{
|
||||
Matrix inverseParentMatrix = Matrix::Identity;
|
||||
const auto& node = skeleton.Nodes[index];
|
||||
if (node.ParentIndex != -1)
|
||||
{
|
||||
inverseParentMatrix = ComputeWorldMatrixRecursive(skeleton, index, inverseParentMatrix);
|
||||
inverseParentMatrix = Matrix::Invert(inverseParentMatrix);
|
||||
}
|
||||
return inverseParentMatrix;
|
||||
}
|
||||
}
|
||||
|
||||
void RetargetSkeletonNode(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& sourceMapping, Transform& node, int32 targetIndex)
|
||||
// Utility for retargeting animation poses between skeletons.
|
||||
struct Retargeting
|
||||
{
|
||||
// sourceSkeleton - skeleton of Anim Graph (Base Locomotion pack)
|
||||
// targetSkeleton - visual mesh skeleton (City Characters pack)
|
||||
// target - anim graph input/output transformation of that node
|
||||
const auto& targetNode = targetSkeleton.Nodes[targetIndex];
|
||||
const int32 sourceIndex = sourceMapping.NodesMapping[targetIndex];
|
||||
if (sourceIndex == -1)
|
||||
private:
|
||||
const Matrix* _sourcePosePtr, * _targetPosePtr;
|
||||
const SkeletonData* _sourceSkeleton, *_targetSkeleton;
|
||||
const SkinnedModel::SkeletonMapping* _sourceMapping;
|
||||
|
||||
public:
|
||||
void Init(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& sourceMapping)
|
||||
{
|
||||
// Use T-pose
|
||||
node = targetNode.LocalTransform;
|
||||
return;
|
||||
ASSERT_LOW_LAYER(targetSkeleton.Nodes.Count() == sourceMapping.NodesMapping.Length());
|
||||
|
||||
// Cache world-space poses for source and target skeletons to avoid redundant calculations during retargeting
|
||||
_sourcePosePtr = sourceSkeleton.GetNodesPose().Get();
|
||||
_targetPosePtr = targetSkeleton.GetNodesPose().Get();
|
||||
|
||||
_sourceSkeleton = &sourceSkeleton;
|
||||
_targetSkeleton = &targetSkeleton;
|
||||
_sourceMapping = &sourceMapping;
|
||||
}
|
||||
const auto& sourceNode = sourceSkeleton.Nodes[sourceIndex];
|
||||
|
||||
// [Reference: https://wickedengine.net/2022/09/animation-retargeting/comment-page-1/]
|
||||
|
||||
// Calculate T-Pose of source node, target node and target parent node
|
||||
Matrix bindMatrix = ComputeWorldMatrixRecursive(sourceSkeleton, sourceIndex, sourceNode.LocalTransform.GetWorld());
|
||||
Matrix inverseBindMatrix = Matrix::Invert(bindMatrix);
|
||||
Matrix targetMatrix = ComputeWorldMatrixRecursive(targetSkeleton, targetIndex, targetNode.LocalTransform.GetWorld());
|
||||
Matrix inverseParentMatrix = ComputeInverseParentMatrixRecursive(targetSkeleton, targetIndex);
|
||||
|
||||
// Target node animation is world-space difference of the animated source node inside the target's parent node world-space
|
||||
Matrix localMatrix = inverseBindMatrix * ComputeWorldMatrixRecursive(sourceSkeleton, sourceIndex, node.GetWorld());
|
||||
localMatrix = targetMatrix * localMatrix * inverseParentMatrix;
|
||||
|
||||
// Extract local node transformation
|
||||
localMatrix.Decompose(node);
|
||||
}
|
||||
|
||||
void RetargetSkeletonPose(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& mapping, const Transform* sourceNodes, Transform* targetNodes)
|
||||
{
|
||||
// TODO: cache source and target skeletons world-space poses for faster retargeting (use some pooled memory)
|
||||
ASSERT_LOW_LAYER(targetSkeleton.Nodes.Count() == mapping.NodesMapping.Length());
|
||||
for (int32 targetIndex = 0; targetIndex < targetSkeleton.Nodes.Count(); targetIndex++)
|
||||
void RetargetNode(const Transform& source, Transform& target, int32 sourceIndex, int32 targetIndex)
|
||||
{
|
||||
auto& targetNode = targetSkeleton.Nodes.Get()[targetIndex];
|
||||
const int32 sourceIndex = mapping.NodesMapping.Get()[targetIndex];
|
||||
Transform node;
|
||||
// sourceSkeleton - skeleton of Anim Graph
|
||||
// targetSkeleton - visual mesh skeleton
|
||||
// target - anim graph input/output transformation of that node
|
||||
const SkeletonNode& targetNode = _targetSkeleton->Nodes.Get()[targetIndex];
|
||||
if (sourceIndex == -1)
|
||||
{
|
||||
// Use T-pose
|
||||
node = targetNode.LocalTransform;
|
||||
target = targetNode.LocalTransform;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Retarget
|
||||
node = sourceNodes[sourceIndex];
|
||||
RetargetSkeletonNode(sourceSkeleton, targetSkeleton, mapping, node, targetIndex);
|
||||
// [Reference: https://wickedengine.net/2022/09/animation-retargeting/comment-page-1/]
|
||||
|
||||
// Calculate T-Pose of source node, target node and target parent node
|
||||
const Matrix* sourcePosePtr = _sourcePosePtr;
|
||||
const Matrix* targetPosePtr = _targetPosePtr;
|
||||
const Matrix& bindMatrix = sourcePosePtr[sourceIndex];
|
||||
const Matrix& targetMatrix = targetPosePtr[targetIndex];
|
||||
Matrix inverseParentMatrix;
|
||||
if (targetNode.ParentIndex != -1)
|
||||
Matrix::Invert(targetPosePtr[targetNode.ParentIndex], inverseParentMatrix);
|
||||
else
|
||||
inverseParentMatrix = Matrix::Identity;
|
||||
|
||||
// Target node animation is world-space difference of the animated source node inside the target's parent node world-space
|
||||
const SkeletonNode& sourceNode = _sourceSkeleton->Nodes.Get()[sourceIndex];
|
||||
Matrix localMatrix = source.GetWorld();
|
||||
if (sourceNode.ParentIndex != -1)
|
||||
localMatrix = localMatrix * sourcePosePtr[sourceNode.ParentIndex];
|
||||
localMatrix = Matrix::Invert(bindMatrix) * localMatrix;
|
||||
localMatrix = targetMatrix * localMatrix * inverseParentMatrix;
|
||||
|
||||
// Extract local node transformation
|
||||
localMatrix.Decompose(target);
|
||||
}
|
||||
targetNodes[targetIndex] = node;
|
||||
}
|
||||
|
||||
FORCE_INLINE void RetargetPose(const Transform* sourceNodes, Transform* targetNodes)
|
||||
{
|
||||
for (int32 targetIndex = 0; targetIndex < _targetSkeleton->Nodes.Count(); targetIndex++)
|
||||
{
|
||||
const int32 sourceIndex = _sourceMapping->NodesMapping.Get()[targetIndex];
|
||||
RetargetNode(sourceNodes[sourceIndex], targetNodes[targetIndex], sourceIndex, targetIndex);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void RetargetSkeletonPose(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& mapping, const Transform* sourceNodes, Transform* targetNodes)
|
||||
{
|
||||
Retargeting retargeting;
|
||||
retargeting.Init(sourceSkeleton, targetSkeleton, mapping);
|
||||
retargeting.RetargetPose(sourceNodes, targetNodes);
|
||||
}
|
||||
|
||||
AnimGraphTraceEvent& AnimGraphContext::AddTraceEvent(const AnimGraphNode* node)
|
||||
@@ -431,9 +429,13 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
|
||||
const bool weighted = weight < 1.0f;
|
||||
const bool retarget = mapping.SourceSkeleton && mapping.SourceSkeleton != mapping.TargetSkeleton;
|
||||
const auto emptyNodes = GetEmptyNodes();
|
||||
Retargeting retargeting;
|
||||
SkinnedModel::SkeletonMapping sourceMapping;
|
||||
if (retarget)
|
||||
{
|
||||
sourceMapping = _graph.BaseModel->GetSkeletonMapping(mapping.SourceSkeleton);
|
||||
retargeting.Init(mapping.SourceSkeleton->Skeleton, mapping.TargetSkeleton->Skeleton, mapping);
|
||||
}
|
||||
for (int32 nodeIndex = 0; nodeIndex < nodes->Nodes.Count(); nodeIndex++)
|
||||
{
|
||||
const int32 nodeToChannel = mapping.NodesMapping[nodeIndex];
|
||||
@@ -447,7 +449,8 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
|
||||
// Optionally retarget animation into the skeleton used by the Anim Graph
|
||||
if (retarget)
|
||||
{
|
||||
RetargetSkeletonNode(mapping.SourceSkeleton->Skeleton, mapping.TargetSkeleton->Skeleton, sourceMapping, srcNode, nodeIndex);
|
||||
const int32 sourceIndex = sourceMapping.NodesMapping[nodeIndex];
|
||||
retargeting.RetargetNode(srcNode, srcNode, sourceIndex, nodeIndex);
|
||||
}
|
||||
|
||||
// Mark node as used
|
||||
@@ -958,6 +961,21 @@ void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& valu
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Set Parameter
|
||||
case 5:
|
||||
{
|
||||
// Set parameter value
|
||||
int32 paramIndex;
|
||||
const auto param = _graph.GetParameter((Guid)node->Values[0], paramIndex);
|
||||
if (param)
|
||||
{
|
||||
context.Data->Parameters[paramIndex].Value = tryGetValue(node->GetBox(1), 1, Value::Null);
|
||||
}
|
||||
|
||||
// Pass over the pose
|
||||
value = tryGetValue(node->GetBox(2), Value::Null);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -132,9 +132,7 @@ int32 AudioClip::GetFirstBufferIndex(float time, float& offset) const
|
||||
if (_buffersStartTimes[i + 1] > time)
|
||||
{
|
||||
offset = time - _buffersStartTimes[i];
|
||||
#if BUILD_DEBUG
|
||||
ASSERT(Math::Abs(GetBufferStartTime(i) + offset - time) < 0.001f);
|
||||
#endif
|
||||
ASSERT_LOW_LAYER(Math::Abs(GetBufferStartTime(i) + offset - time) < 0.001f);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ class AudioSource;
|
||||
API_CLASS(NoSpawn) class FLAXENGINE_API AudioClip : public BinaryAsset, public StreamableResource
|
||||
{
|
||||
DECLARE_BINARY_ASSET_HEADER(AudioClip, 2);
|
||||
friend class AudioBackendOAL;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
|
||||
@@ -37,15 +37,17 @@ void AudioListener::OnEnable()
|
||||
{
|
||||
_prevPos = GetPosition();
|
||||
_velocity = Vector3::Zero;
|
||||
|
||||
ASSERT(!Audio::Listeners.Contains(this));
|
||||
if (Audio::Listeners.Count() >= AUDIO_MAX_LISTENERS)
|
||||
{
|
||||
LOG(Error, "Unsupported amount of the audio listeners!");
|
||||
if IF_CONSTEXPR (AUDIO_MAX_LISTENERS == 1)
|
||||
LOG(Warning, "There is more than one Audio Listener active. Please make sure only exactly one is active at any given time.");
|
||||
else
|
||||
LOG(Warning, "Too many Audio Listener active.");
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT(!Audio::Listeners.Contains(this));
|
||||
if (Audio::Listeners.Count() > 0)
|
||||
LOG(Warning, "There is more than one Audio Listener active. Please make sure only exactly one is active at any given time.");
|
||||
Audio::Listeners.Add(this);
|
||||
AudioBackend::Listener::Reset();
|
||||
AudioBackend::Listener::TransformChanged(GetPosition(), GetOrientation());
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
#include "Engine/Audio/AudioListener.h"
|
||||
#include "Engine/Audio/AudioSource.h"
|
||||
#include "Engine/Audio/AudioSettings.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Level/Level.h"
|
||||
#include "Engine/Video/VideoPlayer.h"
|
||||
|
||||
// Include OpenAL library
|
||||
// Source: https://github.com/kcat/openal-soft
|
||||
@@ -73,6 +76,7 @@ namespace ALC
|
||||
ALCdevice* Device = nullptr;
|
||||
ALCcontext* Context = nullptr;
|
||||
AudioBackend::FeatureFlags Features = AudioBackend::FeatureFlags::None;
|
||||
bool Inited = false;
|
||||
CriticalSection Locker;
|
||||
Dictionary<uint32, SourceData> SourcesData;
|
||||
|
||||
@@ -164,12 +168,9 @@ namespace ALC
|
||||
float Time;
|
||||
};
|
||||
|
||||
void RebuildContext(const Array<AudioSourceState>& states)
|
||||
void RebuildContext()
|
||||
{
|
||||
LOG(Info, "Rebuilding audio contexts");
|
||||
|
||||
ClearContext();
|
||||
|
||||
if (Device == nullptr)
|
||||
return;
|
||||
|
||||
@@ -182,10 +183,16 @@ namespace ALC
|
||||
|
||||
Context = alcCreateContext(Device, attrList);
|
||||
alcMakeContextCurrent(Context);
|
||||
|
||||
}
|
||||
|
||||
void RebuildListeners()
|
||||
{
|
||||
for (AudioListener* listener : Audio::Listeners)
|
||||
Listener::Rebuild(listener);
|
||||
|
||||
}
|
||||
|
||||
void RebuildSources(const Array<AudioSourceState>& states)
|
||||
{
|
||||
for (int32 i = 0; i < states.Count(); i++)
|
||||
{
|
||||
AudioSource* source = Audio::Sources[i];
|
||||
@@ -205,6 +212,13 @@ namespace ALC
|
||||
}
|
||||
}
|
||||
|
||||
void RebuildContext(const Array<AudioSourceState>& states)
|
||||
{
|
||||
RebuildContext();
|
||||
RebuildListeners();
|
||||
RebuildSources(states);
|
||||
}
|
||||
|
||||
void RebuildContext(bool isChangingDevice)
|
||||
{
|
||||
Array<AudioSourceState> states;
|
||||
@@ -400,7 +414,7 @@ void AudioBackendOAL::Source_IsLoopingChanged(uint32 sourceID, bool loop)
|
||||
void AudioBackendOAL::Source_SpatialSetupChanged(uint32 sourceID, bool spatial, float attenuation, float minDistance, float doppler)
|
||||
{
|
||||
ALC::Locker.Lock();
|
||||
const bool pan = ALC::SourcesData[sourceID].Spatial;
|
||||
const float pan = ALC::SourcesData[sourceID].Pan;
|
||||
ALC::Locker.Unlock();
|
||||
if (spatial)
|
||||
{
|
||||
@@ -629,6 +643,7 @@ AudioBackend::FeatureFlags AudioBackendOAL::Base_Features()
|
||||
|
||||
void AudioBackendOAL::Base_OnActiveDeviceChanged()
|
||||
{
|
||||
PROFILE_CPU();
|
||||
PROFILE_MEM(Audio);
|
||||
|
||||
// Cleanup
|
||||
@@ -659,9 +674,53 @@ void AudioBackendOAL::Base_OnActiveDeviceChanged()
|
||||
LOG(Fatal, "Failed to open OpenAL device ({0}).", String(name));
|
||||
return;
|
||||
}
|
||||
if (ALC::Inited)
|
||||
LOG(Info, "Changed audio device to: {}", String(Audio::GetActiveDevice()->Name));
|
||||
|
||||
// Setup
|
||||
ALC::RebuildContext(states);
|
||||
// Rebuild context
|
||||
ALC::RebuildContext();
|
||||
if (ALC::Inited)
|
||||
{
|
||||
// Reload all audio clips to recreate their buffers
|
||||
for (AudioClip* audioClip : Content::GetAssets<AudioClip>())
|
||||
{
|
||||
audioClip->WaitForLoaded();
|
||||
ScopeLock lock(audioClip->Locker);
|
||||
|
||||
// Clear old buffer IDs
|
||||
for (uint32& bufferID : audioClip->Buffers)
|
||||
bufferID = 0;
|
||||
|
||||
if (audioClip->IsStreamable())
|
||||
{
|
||||
// Let the streaming recreate missing buffers
|
||||
audioClip->RequestStreamingUpdate();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reload audio clip
|
||||
auto assetLock = audioClip->Storage->Lock();
|
||||
audioClip->LoadChunk(0);
|
||||
audioClip->Buffers[0] = AudioBackend::Buffer::Create();
|
||||
audioClip->WriteBuffer(0);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Reload all videos to recreate their buffers
|
||||
for (VideoPlayer* videoPlayer : Level::GetActors<VideoPlayer>(true))
|
||||
{
|
||||
VideoBackendPlayer& player = videoPlayer->_player;
|
||||
|
||||
// Clear audio state
|
||||
for (uint32& bufferID : player.AudioBuffers)
|
||||
bufferID = 0;
|
||||
player.NextAudioBuffer = 0;
|
||||
player.AudioSource = 0;
|
||||
}
|
||||
}
|
||||
ALC::RebuildListeners();
|
||||
ALC::RebuildSources(states);
|
||||
}
|
||||
|
||||
void AudioBackendOAL::Base_SetDopplerFactor(float value)
|
||||
@@ -782,6 +841,7 @@ bool AudioBackendOAL::Base_Init()
|
||||
if (ALC::IsExtensionSupported("AL_SOFT_source_spatialize"))
|
||||
ALC::Features = EnumAddFlags(ALC::Features, FeatureFlags::SpatialMultiChannel);
|
||||
#endif
|
||||
ALC::Inited = true;
|
||||
|
||||
// Log service info
|
||||
LOG(Info, "{0} ({1})", String(alGetString(AL_RENDERER)), String(alGetString(AL_VERSION)));
|
||||
|
||||
@@ -672,7 +672,7 @@ bool AudioBackendXAudio2::Base_Init()
|
||||
HRESULT hr = XAudio2Create(&XAudio2::Instance, 0, XAUDIO2_DEFAULT_PROCESSOR);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG(Error, "Failed to initalize XAudio2. Error: 0x{0:x}", hr);
|
||||
LOG(Error, "Failed to initialize XAudio2. Error: 0x{0:x}", hr);
|
||||
return true;
|
||||
}
|
||||
XAudio2::Instance->RegisterForCallbacks(&XAudio2::Callback);
|
||||
@@ -681,7 +681,7 @@ bool AudioBackendXAudio2::Base_Init()
|
||||
hr = XAudio2::Instance->CreateMasteringVoice(&XAudio2::MasteringVoice);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG(Error, "Failed to initalize XAudio2 mastering voice. Error: 0x{0:x}", hr);
|
||||
LOG(Error, "Failed to initialize XAudio2 mastering voice. Error: 0x{0:x}", hr);
|
||||
return true;
|
||||
}
|
||||
XAUDIO2_VOICE_DETAILS details;
|
||||
|
||||
@@ -487,6 +487,8 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const
|
||||
const auto loadingTask = (ContentLoadTask*)Platform::AtomicRead(&_loadingTask);
|
||||
if (loadingTask == nullptr)
|
||||
{
|
||||
if (IsLoaded())
|
||||
return false;
|
||||
LOG(Warning, "WaitForLoaded asset \'{0}\' failed. No loading task attached and asset is not loaded.", ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -61,16 +61,24 @@ Array<String> SkinnedModel::GetBlendShapes()
|
||||
|
||||
SkinnedModel::SkeletonMapping SkinnedModel::GetSkeletonMapping(Asset* source, bool autoRetarget)
|
||||
{
|
||||
// Fast-path to use cached mapping
|
||||
SkeletonMapping mapping;
|
||||
mapping.TargetSkeleton = this;
|
||||
SkeletonMappingData mappingData;
|
||||
if (_skeletonMappingCache.TryGet(source, mappingData))
|
||||
{
|
||||
mapping.SourceSkeleton = mappingData.SourceSkeleton;
|
||||
mapping.NodesMapping = mappingData.NodesMapping;
|
||||
return mapping;
|
||||
}
|
||||
mapping.SourceSkeleton = nullptr;
|
||||
|
||||
if (WaitForLoaded() || !source || source->WaitForLoaded())
|
||||
return mapping;
|
||||
PROFILE_CPU();
|
||||
ScopeLock lock(Locker);
|
||||
SkeletonMappingData mappingData;
|
||||
if (!_skeletonMappingCache.TryGet(source, mappingData))
|
||||
{
|
||||
PROFILE_CPU();
|
||||
|
||||
// Initialize the mapping
|
||||
SkeletonRetarget* retarget = nullptr;
|
||||
const Guid sourceId = source->GetID();
|
||||
@@ -370,6 +378,7 @@ bool SkinnedModel::SetupSkeleton(const Array<SkeletonNode>& nodes)
|
||||
model->Skeleton.Bones[i].LocalTransform = node.LocalTransform;
|
||||
model->Skeleton.Bones[i].NodeIndex = i;
|
||||
}
|
||||
model->Skeleton.Dirty();
|
||||
ClearSkeletonMapping();
|
||||
|
||||
// Calculate offset matrix (inverse bind pose transform) for every bone manually
|
||||
@@ -427,6 +436,7 @@ bool SkinnedModel::SetupSkeleton(const Array<SkeletonNode>& nodes, const Array<S
|
||||
// Setup
|
||||
model->Skeleton.Nodes = nodes;
|
||||
model->Skeleton.Bones = bones;
|
||||
model->Skeleton.Dirty();
|
||||
ClearSkeletonMapping();
|
||||
|
||||
// Calculate offset matrix (inverse bind pose transform) for every bone manually
|
||||
@@ -823,13 +833,13 @@ bool SkinnedModel::SaveMesh(WriteStream& stream, const ModelData& modelData, int
|
||||
|
||||
void SkinnedModel::ClearSkeletonMapping()
|
||||
{
|
||||
for (auto& e : _skeletonMappingCache)
|
||||
for (const auto& e : _skeletonMappingCache)
|
||||
{
|
||||
e.Key->OnUnloaded.Unbind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
|
||||
#if USE_EDITOR
|
||||
e.Key->OnReloading.Unbind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
|
||||
#endif
|
||||
Allocator::Free(e.Value.NodesMapping.Get());
|
||||
Allocator::Free((void*)e.Value.NodesMapping.Get());
|
||||
}
|
||||
_skeletonMappingCache.Clear();
|
||||
}
|
||||
@@ -837,8 +847,9 @@ void SkinnedModel::ClearSkeletonMapping()
|
||||
void SkinnedModel::OnSkeletonMappingSourceAssetUnloaded(Asset* obj)
|
||||
{
|
||||
ScopeLock lock(Locker);
|
||||
auto i = _skeletonMappingCache.Find(obj);
|
||||
ASSERT(i != _skeletonMappingCache.End());
|
||||
SkeletonMappingData mappingData;
|
||||
bool found = _skeletonMappingCache.TryGet(obj, mappingData);
|
||||
ASSERT(found);
|
||||
|
||||
// Unlink event
|
||||
obj->OnUnloaded.Unbind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
|
||||
@@ -847,8 +858,8 @@ void SkinnedModel::OnSkeletonMappingSourceAssetUnloaded(Asset* obj)
|
||||
#endif
|
||||
|
||||
// Clear cache
|
||||
Allocator::Free(i->Value.NodesMapping.Get());
|
||||
_skeletonMappingCache.Remove(i);
|
||||
Allocator::Free(mappingData.NodesMapping.Get());
|
||||
_skeletonMappingCache.Remove(obj);
|
||||
}
|
||||
|
||||
uint64 SkinnedModel::GetMemoryUsage() const
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "ModelBase.h"
|
||||
#include "Engine/Core/Collections/Dictionary.h"
|
||||
#include "Engine/Threading/ConcurrentDictionary.h"
|
||||
#include "Engine/Graphics/Models/SkinnedMesh.h"
|
||||
#include "Engine/Graphics/Models/SkeletonData.h"
|
||||
|
||||
@@ -101,9 +101,9 @@ public:
|
||||
struct FLAXENGINE_API SkeletonMapping
|
||||
{
|
||||
// Target skeleton.
|
||||
AssetReference<SkinnedModel> TargetSkeleton;
|
||||
SkinnedModel* TargetSkeleton;
|
||||
// Source skeleton.
|
||||
AssetReference<SkinnedModel> SourceSkeleton;
|
||||
SkinnedModel* SourceSkeleton;
|
||||
// The node-to-node mapping for the fast animation sampling for the skinned model skeleton nodes. Each item is index of the source skeleton node into target skeleton node.
|
||||
Span<int32> NodesMapping;
|
||||
};
|
||||
@@ -115,7 +115,7 @@ private:
|
||||
Span<int32> NodesMapping;
|
||||
};
|
||||
|
||||
Dictionary<Asset*, SkeletonMappingData> _skeletonMappingCache;
|
||||
ConcurrentDictionary<Asset*, SkeletonMappingData> _skeletonMappingCache;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
|
||||
@@ -1700,6 +1700,8 @@ void VisualScript::CacheScriptingType()
|
||||
VisualScriptingBinaryModule::VisualScriptingBinaryModule()
|
||||
: _name("Visual Scripting")
|
||||
{
|
||||
// Visual Scripts can be unloaded and loaded again even in game
|
||||
CanReload = true;
|
||||
}
|
||||
|
||||
ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const ScriptingObjectSpawnParams& params)
|
||||
|
||||
@@ -684,6 +684,19 @@ Array<Asset*> Content::GetAssets()
|
||||
return assets;
|
||||
}
|
||||
|
||||
Array<Asset*> Content::GetAssets(const MClass* type)
|
||||
{
|
||||
Array<Asset*> assets;
|
||||
AssetsLocker.Lock();
|
||||
for (auto& e : Assets)
|
||||
{
|
||||
if (e.Value->Is(type))
|
||||
assets.Add(e.Value);
|
||||
}
|
||||
AssetsLocker.Unlock();
|
||||
return assets;
|
||||
}
|
||||
|
||||
const Dictionary<Guid, Asset*>& Content::GetAssetsRaw()
|
||||
{
|
||||
AssetsLocker.Lock();
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Scripting/ScriptingType.h"
|
||||
#ifndef _MSC_VER
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#endif
|
||||
#include "AssetInfo.h"
|
||||
#include "Asset.h"
|
||||
#include "Config.h"
|
||||
@@ -122,7 +125,26 @@ public:
|
||||
/// Gets the assets (loaded or during load).
|
||||
/// </summary>
|
||||
/// <returns>The collection of assets.</returns>
|
||||
static Array<Asset*, HeapAllocation> GetAssets();
|
||||
API_FUNCTION() static Array<Asset*, HeapAllocation> GetAssets();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the assets (loaded or during load).
|
||||
/// </summary>
|
||||
/// <param name="type">Type of the assets to search for. Includes any assets derived from the type.</param>
|
||||
/// <returns>Found actors list.</returns>
|
||||
API_FUNCTION() static Array<Asset*, HeapAllocation> GetAssets(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the assets (loaded or during load).
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the object.</typeparam>
|
||||
/// <returns>Found actors list.</returns>
|
||||
template<typename T>
|
||||
static Array<T*, HeapAllocation> GetAssets()
|
||||
{
|
||||
Array<Asset*, HeapAllocation> assets = GetAssets(T::GetStaticClass());
|
||||
return *(Array<T*, HeapAllocation>*) & assets;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw dictionary of assets (loaded or during load).
|
||||
|
||||
@@ -478,16 +478,23 @@ CreateAssetResult ImportModel::Import(CreateAssetContext& context)
|
||||
}
|
||||
|
||||
// Check if restore local changes on asset reimport
|
||||
constexpr bool RestoreModelOptionsOnReimport = true;
|
||||
constexpr bool RestoreAnimEventsOnReimport = true;
|
||||
const bool restoreModelOptions = RestoreModelOptionsOnReimport && (options.Type == ModelTool::ModelType::Model || options.Type == ModelTool::ModelType::SkinnedModel);
|
||||
const bool restoreMaterials = options.RestoreMaterialsOnReimport && data->Materials.HasItems();
|
||||
const bool restoreAnimEvents = RestoreAnimEventsOnReimport && options.Type == ModelTool::ModelType::Animation && data->Animations.HasItems();
|
||||
if ((restoreMaterials || restoreAnimEvents) && FileSystem::FileExists(context.TargetAssetPath))
|
||||
if ((restoreModelOptions || restoreMaterials || restoreAnimEvents) && FileSystem::FileExists(context.TargetAssetPath))
|
||||
{
|
||||
AssetReference<Asset> asset = Content::LoadAsync<Asset>(context.TargetAssetPath);
|
||||
if (asset && !asset->WaitForLoaded())
|
||||
{
|
||||
auto* model = ScriptingObject::Cast<ModelBase>(asset);
|
||||
auto* animation = ScriptingObject::Cast<Animation>(asset);
|
||||
if (restoreModelOptions && model)
|
||||
{
|
||||
// Copy general properties
|
||||
data->MinScreenSize = model->MinScreenSize;
|
||||
}
|
||||
if (restoreMaterials && model)
|
||||
{
|
||||
// Copy material settings
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
|
||||
#include "HashSetBase.h"
|
||||
|
||||
template<typename KeyType, typename ValueType, typename AllocationType>
|
||||
class ConcurrentDictionary;
|
||||
|
||||
/// <summary>
|
||||
/// Describes single portion of space for the key and value pair in a hash map.
|
||||
/// </summary>
|
||||
@@ -13,6 +16,7 @@ struct DictionaryBucket
|
||||
friend Memory;
|
||||
friend HashSetBase<AllocationType, DictionaryBucket>;
|
||||
friend Dictionary<KeyType, ValueType, AllocationType>;
|
||||
friend ConcurrentDictionary<KeyType, ValueType, AllocationType>;
|
||||
|
||||
/// <summary>The key.</summary>
|
||||
KeyType Key;
|
||||
|
||||
@@ -57,5 +57,5 @@
|
||||
#define API_PARAM(...)
|
||||
#define API_TYPEDEF(...)
|
||||
#define API_INJECT_CODE(...)
|
||||
#define API_AUTO_SERIALIZATION(...) public: void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
|
||||
#define API_AUTO_SERIALIZATION(...) public: bool ShouldSerialize(const void* otherObj) const override; void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
|
||||
#define DECLARE_SCRIPTING_TYPE_MINIMAL(type) public: friend class type##Internal; static struct ScriptingTypeInitializer TypeInitializer;
|
||||
|
||||
@@ -36,6 +36,13 @@ public:
|
||||
/// </summary>
|
||||
virtual ~ISerializable() = default;
|
||||
|
||||
/// <summary>
|
||||
/// Compares with other instance to decide whether serialize this instance (eg. any field orp property is modified). Used to skip object serialization if not needed.
|
||||
/// </summary>
|
||||
/// <param name="otherObj">The instance of the object (always valid) to compare with to decide whether serialize this instance.</param>
|
||||
/// <returns>True if any field or property is modified compared to the other object instance, otherwise false.</returns>
|
||||
virtual bool ShouldSerialize(const void* otherObj) const { return true; }
|
||||
|
||||
/// <summary>
|
||||
/// Serializes object to the output stream compared to the values of the other object instance (eg. default class object). If other object is null then serialize all properties.
|
||||
/// </summary>
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace FlaxEngine
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.ColorConverter))]
|
||||
#endif
|
||||
partial struct Color
|
||||
partial struct Color : Json.ICustomValueEquals
|
||||
{
|
||||
/// <summary>
|
||||
/// The size of the <see cref="Color" /> type, in bytes.
|
||||
@@ -196,6 +196,13 @@ namespace FlaxEngine
|
||||
A = values[3];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Color)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object value)
|
||||
{
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace FlaxEngine
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Double2Converter))]
|
||||
#endif
|
||||
partial struct Double2 : IEquatable<Double2>, IFormattable
|
||||
partial struct Double2 : IEquatable<Double2>, IFormattable, Json.ICustomValueEquals
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2}";
|
||||
|
||||
@@ -1574,6 +1574,13 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Double2)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="Double2" /> is equal to this instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace FlaxEngine
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Double3Converter))]
|
||||
#endif
|
||||
partial struct Double3 : IEquatable<Double3>, IFormattable
|
||||
partial struct Double3 : IEquatable<Double3>, IFormattable, Json.ICustomValueEquals
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2}";
|
||||
|
||||
@@ -1872,6 +1872,13 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Double3)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="Double3" /> is equal to this instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace FlaxEngine
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Double4Converter))]
|
||||
#endif
|
||||
partial struct Double4 : IEquatable<Double4>, IFormattable
|
||||
partial struct Double4 : IEquatable<Double4>, IFormattable, Json.ICustomValueEquals
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";
|
||||
|
||||
@@ -1372,6 +1372,13 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Double4)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="Double4" /> is equal to this instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace FlaxEngine
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Float2Converter))]
|
||||
#endif
|
||||
partial struct Float2 : IEquatable<Float2>, IFormattable
|
||||
partial struct Float2 : IEquatable<Float2>, IFormattable, Json.ICustomValueEquals
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2}";
|
||||
|
||||
@@ -1650,6 +1650,13 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Float2)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="Float2" /> is equal to this instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace FlaxEngine
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Float3Converter))]
|
||||
#endif
|
||||
partial struct Float3 : IEquatable<Float3>, IFormattable
|
||||
partial struct Float3 : IEquatable<Float3>, IFormattable, Json.ICustomValueEquals
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2}";
|
||||
|
||||
@@ -1904,6 +1904,13 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Float3)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="Float3" /> is equal to this instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace FlaxEngine
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Float4Converter))]
|
||||
#endif
|
||||
partial struct Float4 : IEquatable<Float4>, IFormattable
|
||||
partial struct Float4 : IEquatable<Float4>, IFormattable, Json.ICustomValueEquals
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";
|
||||
|
||||
@@ -1412,6 +1412,13 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Float4)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="Float4" /> is equal to this instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "Math.h"
|
||||
#include "Vector2.h"
|
||||
#include "Vector3.h"
|
||||
#include "Vector4.h"
|
||||
|
||||
/// <summary>
|
||||
/// Half-precision 16 bit floating point number consisting of a sign bit, a 5 bit biased exponent, and a 10 bit mantissa
|
||||
@@ -248,6 +249,19 @@ public:
|
||||
explicit Half4(const Color& c);
|
||||
explicit Half4(const Rectangle& rect);
|
||||
|
||||
operator Float2() const
|
||||
{
|
||||
return ToFloat2();
|
||||
}
|
||||
operator Float3() const
|
||||
{
|
||||
return ToFloat3();
|
||||
}
|
||||
operator Float4() const
|
||||
{
|
||||
return ToFloat4();
|
||||
}
|
||||
|
||||
public:
|
||||
Float2 ToFloat2() const;
|
||||
Float3 ToFloat3() const;
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace FlaxEngine
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Int2Converter))]
|
||||
#endif
|
||||
partial struct Int2 : IEquatable<Int2>, IFormattable
|
||||
partial struct Int2 : IEquatable<Int2>, IFormattable, Json.ICustomValueEquals
|
||||
{
|
||||
private static readonly string _formatString = "X:{0} Y:{1}";
|
||||
|
||||
@@ -940,6 +940,13 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Int2)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="Int2" /> is equal to this instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace FlaxEngine
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Int3Converter))]
|
||||
#endif
|
||||
partial struct Int3 : IEquatable<Int3>, IFormattable
|
||||
partial struct Int3 : IEquatable<Int3>, IFormattable, Json.ICustomValueEquals
|
||||
{
|
||||
private static readonly string _formatString = "X:{0} Y:{1} Z:{2}";
|
||||
|
||||
@@ -1023,6 +1023,13 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Int3)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="Int3" /> is equal to this instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace FlaxEngine
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Int4Converter))]
|
||||
#endif
|
||||
partial struct Int4 : IEquatable<Int4>, IFormattable
|
||||
partial struct Int4 : IEquatable<Int4>, IFormattable, Json.ICustomValueEquals
|
||||
{
|
||||
private static readonly string _formatString = "X:{0} Y:{1} Z:{2} W:{3}";
|
||||
|
||||
@@ -881,6 +881,13 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Int4)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="Int4" /> is equal to this instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -41,16 +41,6 @@ FloatR10G10B10A2::FloatR10G10B10A2(const float* values)
|
||||
{
|
||||
}
|
||||
|
||||
FloatR10G10B10A2::operator Float3() const
|
||||
{
|
||||
return ToFloat3();
|
||||
}
|
||||
|
||||
FloatR10G10B10A2::operator Float4() const
|
||||
{
|
||||
return ToFloat4();
|
||||
}
|
||||
|
||||
Float3 FloatR10G10B10A2::ToFloat3() const
|
||||
{
|
||||
Float3 vectorOut;
|
||||
|
||||
@@ -40,9 +40,14 @@ struct FLAXENGINE_API FloatR10G10B10A2
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
operator Float3() const;
|
||||
operator Float4() const;
|
||||
operator Float3() const
|
||||
{
|
||||
return ToFloat3();
|
||||
}
|
||||
operator Float4() const
|
||||
{
|
||||
return ToFloat4();
|
||||
}
|
||||
|
||||
FloatR10G10B10A2& operator=(const FloatR10G10B10A2& other)
|
||||
{
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace FlaxEngine
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.QuaternionConverter))]
|
||||
#endif
|
||||
partial struct Quaternion : IEquatable<Quaternion>, IFormattable
|
||||
partial struct Quaternion : IEquatable<Quaternion>, IFormattable, Json.ICustomValueEquals
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";
|
||||
|
||||
@@ -1681,6 +1681,13 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Quaternion)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether one quaternion is near another quaternion.
|
||||
/// </summary>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user