Merge remote-tracking branch 'origin/1.12' into 1.12
This commit is contained in:
1
.github/data/bt.sh
vendored
1
.github/data/bt.sh
vendored
@@ -9,6 +9,7 @@ gdb -q --batch \
|
||||
-ex 'handle SIGUSR1 nostop pass' \
|
||||
-ex 'handle SIGUSR2 nostop pass' \
|
||||
-ex 'handle SIGCHLD nostop pass' \
|
||||
-ex 'handle SIG34 nostop pass' \
|
||||
-ex 'set print thread-events off' \
|
||||
-return-child-result \
|
||||
-ex 'run' \
|
||||
|
||||
6
.github/workflows/build_android.yml
vendored
6
.github/workflows/build_android.yml
vendored
@@ -4,6 +4,7 @@ on: [push, pull_request]
|
||||
env:
|
||||
DOTNET_NOLOGO: true
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: false
|
||||
DOTNET_ROLL_FORWARD: 'minor'
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -19,7 +20,7 @@ jobs:
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
dotnet-version: 8.0.419
|
||||
- name: Setup .NET Workload
|
||||
run: |
|
||||
dotnet workload install android
|
||||
@@ -33,4 +34,7 @@ jobs:
|
||||
git lfs pull
|
||||
- name: Build
|
||||
run: |
|
||||
PowerShell "(Get-Content global.json).Replace('latestMajor', 'minor') | Set-Content global.json"
|
||||
PowerShell "(Get-Content Source/Tools/Flax.Build/global.json).Replace('latestMajor', 'minor') | Set-Content Source/Tools/Flax.Build/global.json"
|
||||
PowerShell "(Get-Content Source/Tools/Flax.Build/Flax.Build.csproj).Replace('LatestMajor', 'Minor') | Set-Content Source/Tools/Flax.Build/Flax.Build.csproj"
|
||||
.\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=8 -arch=ARM64 -platform=Android -configuration=Release -buildtargets=FlaxGame
|
||||
|
||||
11
.github/workflows/build_windows.yml
vendored
11
.github/workflows/build_windows.yml
vendored
@@ -4,6 +4,7 @@ on: [push, pull_request]
|
||||
env:
|
||||
DOTNET_NOLOGO: true
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: false
|
||||
DOTNET_ROLL_FORWARD: 'minor'
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -19,7 +20,7 @@ jobs:
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
dotnet-version: 8.0.419
|
||||
- name: Print .NET info
|
||||
run: |
|
||||
dotnet --info
|
||||
@@ -30,6 +31,9 @@ jobs:
|
||||
git lfs pull
|
||||
- name: Build
|
||||
run: |
|
||||
PowerShell "(Get-Content global.json).Replace('latestMajor', 'minor') | Set-Content global.json"
|
||||
PowerShell "(Get-Content Source/Tools/Flax.Build/global.json).Replace('latestMajor', 'minor') | Set-Content Source/Tools/Flax.Build/global.json"
|
||||
PowerShell "(Get-Content Source/Tools/Flax.Build/Flax.Build.csproj).Replace('LatestMajor', 'Minor') | Set-Content Source/Tools/Flax.Build/Flax.Build.csproj"
|
||||
.\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=8 -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxEditor
|
||||
|
||||
# Game
|
||||
@@ -44,7 +48,7 @@ jobs:
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
dotnet-version: 8.0.419
|
||||
- name: Print .NET info
|
||||
run: |
|
||||
dotnet --info
|
||||
@@ -55,4 +59,7 @@ jobs:
|
||||
git lfs pull
|
||||
- name: Build
|
||||
run: |
|
||||
PowerShell "(Get-Content global.json).Replace('latestMajor', 'minor') | Set-Content global.json"
|
||||
PowerShell "(Get-Content Source/Tools/Flax.Build/global.json).Replace('latestMajor', 'minor') | Set-Content Source/Tools/Flax.Build/global.json"
|
||||
PowerShell "(Get-Content Source/Tools/Flax.Build/Flax.Build.csproj).Replace('LatestMajor', 'Minor') | Set-Content Source/Tools/Flax.Build/Flax.Build.csproj"
|
||||
.\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=8 -arch=x64 -platform=Windows -configuration=Release -buildtargets=FlaxGame
|
||||
|
||||
11
.github/workflows/cd.yml
vendored
11
.github/workflows/cd.yml
vendored
@@ -7,6 +7,7 @@ on:
|
||||
env:
|
||||
DOTNET_NOLOGO: true
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: false
|
||||
DOTNET_ROLL_FORWARD: 'minor'
|
||||
GIT_LFS_PULL_OPTIONS: '-c lfs.concurrenttransfers=1 -c lfs.transfer.maxretries=2 -c http.version="HTTP/1.1" -c lfs.activitytimeout=60'
|
||||
|
||||
jobs:
|
||||
@@ -27,13 +28,16 @@ jobs:
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
dotnet-version: 8.0.419
|
||||
- name: Print .NET info
|
||||
run: |
|
||||
dotnet --info
|
||||
dotnet workload --info
|
||||
- name: Build
|
||||
run: |
|
||||
PowerShell "(Get-Content global.json).Replace('latestMajor', 'minor') | Set-Content global.json"
|
||||
PowerShell "(Get-Content Source/Tools/Flax.Build/global.json).Replace('latestMajor', 'minor') | Set-Content Source/Tools/Flax.Build/global.json"
|
||||
PowerShell "(Get-Content Source/Tools/Flax.Build/Flax.Build.csproj).Replace('LatestMajor', 'Minor') | Set-Content Source/Tools/Flax.Build/Flax.Build.csproj"
|
||||
.\PackageEditor.bat -arch=x64 -platform=Windows -deployOutput=Output -dotnet=8
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -60,13 +64,16 @@ jobs:
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
dotnet-version: 8.0.419
|
||||
- name: Print .NET info
|
||||
run: |
|
||||
dotnet --info
|
||||
dotnet workload --info
|
||||
- name: Build
|
||||
run: |
|
||||
PowerShell "(Get-Content global.json).Replace('latestMajor', 'minor') | Set-Content global.json"
|
||||
PowerShell "(Get-Content Source/Tools/Flax.Build/global.json).Replace('latestMajor', 'minor') | Set-Content Source/Tools/Flax.Build/global.json"
|
||||
PowerShell "(Get-Content Source/Tools/Flax.Build/Flax.Build.csproj).Replace('LatestMajor', 'Minor') | Set-Content Source/Tools/Flax.Build/Flax.Build.csproj"
|
||||
.\PackagePlatforms.bat -arch=x64 -platform=Windows -deployOutput=Output -dotnet=8
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
6
.github/workflows/tests.yml
vendored
6
.github/workflows/tests.yml
vendored
@@ -4,6 +4,7 @@ on: [push, pull_request]
|
||||
env:
|
||||
DOTNET_NOLOGO: true
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: false
|
||||
DOTNET_ROLL_FORWARD: 'minor'
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -56,7 +57,7 @@ jobs:
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
dotnet-version: 8.0.419
|
||||
- name: Print .NET info
|
||||
run: |
|
||||
dotnet --info
|
||||
@@ -67,6 +68,9 @@ jobs:
|
||||
git lfs pull
|
||||
- name: Build
|
||||
run: |
|
||||
PowerShell "(Get-Content global.json).Replace('latestMajor', 'minor') | Set-Content global.json"
|
||||
PowerShell "(Get-Content Source/Tools/Flax.Build/global.json).Replace('latestMajor', 'minor') | Set-Content Source/Tools/Flax.Build/global.json"
|
||||
PowerShell "(Get-Content Source/Tools/Flax.Build/Flax.Build.csproj).Replace('LatestMajor', 'Minor') | Set-Content Source/Tools/Flax.Build/Flax.Build.csproj"
|
||||
.\GenerateProjectFiles.bat -vs2022 -log -verbose -printSDKs -dotnet=8
|
||||
.\Development\Scripts\Windows\CallBuildTool.bat -build -log -dotnet=8 -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxTestsTarget
|
||||
dotnet msbuild Source\Tools\Flax.Build.Tests\Flax.Build.Tests.csproj /m /t:Restore,Build /p:Configuration=Debug /p:Platform=AnyCPU /nologo
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"Major": 1,
|
||||
"Minor": 12,
|
||||
"Revision": 0,
|
||||
"Build": 6908
|
||||
"Build": 6909
|
||||
},
|
||||
"Company": "Flax",
|
||||
"Copyright": "Copyright (c) 2012-2026 Wojciech Figat. All rights reserved.",
|
||||
@@ -14,6 +14,14 @@
|
||||
"UseCSharp": true,
|
||||
"UseLargeWorlds": false,
|
||||
"UseDotNet": true,
|
||||
"UseSDL": true
|
||||
"Windows": {
|
||||
"UseSDL": false,
|
||||
},
|
||||
"Mac": {
|
||||
"UseSDL": false,
|
||||
},
|
||||
"Linux": {
|
||||
"UseSDL": true,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,15 +175,13 @@ namespace FlaxEditor.Content
|
||||
}
|
||||
}
|
||||
|
||||
bool isExpanded = isAnyChildVisible;
|
||||
|
||||
if (isExpanded)
|
||||
if (!noFilter)
|
||||
{
|
||||
Expand(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Collapse(true);
|
||||
bool isExpanded = isAnyChildVisible;
|
||||
if (isExpanded)
|
||||
Expand(true);
|
||||
else
|
||||
Collapse(true);
|
||||
}
|
||||
|
||||
Visible = isThisVisible | isAnyChildVisible;
|
||||
|
||||
@@ -52,6 +52,7 @@ namespace FlaxEditor.CustomEditors
|
||||
private readonly List<CustomEditor> _children = new List<CustomEditor>();
|
||||
private ValueContainer _values;
|
||||
private bool _isSetBlocked;
|
||||
private bool _isRebuilding;
|
||||
private bool _skipChildrenRefresh;
|
||||
private bool _hasValueDirty;
|
||||
private bool _rebuildOnRefresh;
|
||||
@@ -178,7 +179,7 @@ namespace FlaxEditor.CustomEditors
|
||||
public void RebuildLayout()
|
||||
{
|
||||
// Skip rebuilding during init
|
||||
if (CurrentCustomEditor == this)
|
||||
if (CurrentCustomEditor == this || _isRebuilding)
|
||||
return;
|
||||
|
||||
// Special case for root objects to run normal layout build
|
||||
@@ -197,6 +198,7 @@ namespace FlaxEditor.CustomEditors
|
||||
_parent?.RebuildLayout();
|
||||
return;
|
||||
}
|
||||
_isRebuilding = true;
|
||||
var control = layout.ContainerControl;
|
||||
var parent = _parent;
|
||||
var parentScrollV = (_presenter?.Panel.Parent as Panel)?.VScrollBar?.Value ?? -1;
|
||||
@@ -216,6 +218,7 @@ namespace FlaxEditor.CustomEditors
|
||||
// Restore scroll value
|
||||
if (parentScrollV > -1 && _presenter != null && _presenter.Panel.Parent is Panel panel && panel.VScrollBar != null)
|
||||
panel.VScrollBar.Value = parentScrollV;
|
||||
_isRebuilding = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -61,6 +61,7 @@ public class Editor : EditorModule
|
||||
options.PrivateDependencies.Add("Renderer");
|
||||
options.PrivateDependencies.Add("TextureTool");
|
||||
options.PrivateDependencies.Add("Particles");
|
||||
options.PrivateDependencies.Add("Terrain");
|
||||
|
||||
var platformToolsRoot = Path.Combine(FolderPath, "Cooker", "Platform");
|
||||
var platformToolsRootExternal = Path.Combine(Globals.EngineRoot, "Source", "Platforms");
|
||||
|
||||
@@ -135,11 +135,7 @@ namespace FlaxEditor.GUI.Docking
|
||||
settings.MaximumSize = Float2.Zero; // Unlimited size
|
||||
settings.Fullscreen = false;
|
||||
settings.HasBorder = true;
|
||||
#if PLATFORM_SDL
|
||||
settings.SupportsTransparency = true;
|
||||
#else
|
||||
settings.SupportsTransparency = false;
|
||||
#endif
|
||||
settings.ActivateWhenFirstShown = true;
|
||||
settings.AllowInput = true;
|
||||
settings.AllowMinimize = true;
|
||||
@@ -211,6 +207,7 @@ namespace FlaxEditor.GUI.Docking
|
||||
{
|
||||
if (ChildPanelsCount > 0)
|
||||
return;
|
||||
|
||||
// Close window
|
||||
_window?.Close();
|
||||
}
|
||||
|
||||
@@ -269,8 +269,9 @@ namespace FlaxEditor.GUI.Docking
|
||||
if (_toDock == null)
|
||||
return;
|
||||
|
||||
if (_toDock.RootWindow.Window != _dragSourceWindow)
|
||||
_toDock.RootWindow.Window.MouseUp -= OnMouseUp;
|
||||
var window = _toDock.RootWindow?.Window;
|
||||
if (window != null && window != _dragSourceWindow)
|
||||
window.MouseUp -= OnMouseUp;
|
||||
|
||||
_dockHintDown?.Parent.RemoveChild(_dockHintDown);
|
||||
_dockHintUp?.Parent.RemoveChild(_dockHintUp);
|
||||
@@ -327,10 +328,10 @@ namespace FlaxEditor.GUI.Docking
|
||||
_toDock?.RootWindow.Window.BringToFront();
|
||||
//_toDock?.RootWindow.Window.Focus();
|
||||
|
||||
#if PLATFORM_SDL
|
||||
// Make the dragged window transparent when dock hints are visible
|
||||
_toMove.Window.Window.Opacity = _toDock == null ? 1.0f : DragWindowOpacity;
|
||||
#else
|
||||
|
||||
#if !PLATFORM_SDL
|
||||
// Bring the drop source always to the top
|
||||
if (_dragSourceWindow != null)
|
||||
_dragSourceWindow.BringToFront();
|
||||
|
||||
267
Source/Editor/Gizmo/DirectionGizmo.cs
Normal file
267
Source/Editor/Gizmo/DirectionGizmo.cs
Normal file
@@ -0,0 +1,267 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.Viewport;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.Gizmo;
|
||||
|
||||
[HideInEditor]
|
||||
internal class DirectionGizmo : ContainerControl
|
||||
{
|
||||
private IGizmoOwner _owner;
|
||||
private ViewportProjection _viewportProjection;
|
||||
private EditorViewport _viewport;
|
||||
private Vector3 _gizmoCenter;
|
||||
private float _axisLength = 75.0f;
|
||||
private float _textAxisLength = 95.0f;
|
||||
private float _spriteRadius = 12.0f;
|
||||
|
||||
private AxisData _xAxisData;
|
||||
private AxisData _yAxisData;
|
||||
private AxisData _zAxisData;
|
||||
private AxisData _negXAxisData;
|
||||
private AxisData _negYAxisData;
|
||||
private AxisData _negZAxisData;
|
||||
|
||||
private List<AxisData> _axisData = new List<AxisData>();
|
||||
private int _hoveredAxisIndex = -1;
|
||||
|
||||
private SpriteHandle _posHandle;
|
||||
private SpriteHandle _negHandle;
|
||||
|
||||
private FontReference _fontReference;
|
||||
|
||||
// Store sprite positions for hover detection
|
||||
private List<(Float2 position, AxisDirection direction)> _spritePositions = new List<(Float2, AxisDirection)>();
|
||||
|
||||
private struct ViewportProjection
|
||||
{
|
||||
private Matrix _viewProjection;
|
||||
private BoundingFrustum _frustum;
|
||||
private FlaxEngine.Viewport _viewport;
|
||||
private Vector3 _origin;
|
||||
|
||||
public void Init(EditorViewport editorViewport)
|
||||
{
|
||||
// Inline EditorViewport.ProjectPoint to save on calculation for large set of points
|
||||
_viewport = new FlaxEngine.Viewport(0, 0, editorViewport.Width, editorViewport.Height);
|
||||
_frustum = editorViewport.ViewFrustum;
|
||||
_viewProjection = _frustum.Matrix;
|
||||
_origin = editorViewport.Task.View.Origin;
|
||||
}
|
||||
|
||||
public void ProjectPoint(Vector3 worldSpaceLocation, out Float2 viewportSpaceLocation)
|
||||
{
|
||||
worldSpaceLocation -= _origin;
|
||||
_viewport.Project(ref worldSpaceLocation, ref _viewProjection, out var projected);
|
||||
viewportSpaceLocation = new Float2((float)projected.X, (float)projected.Y);
|
||||
}
|
||||
}
|
||||
|
||||
private struct AxisData
|
||||
{
|
||||
public Float2 Delta;
|
||||
public float Distance;
|
||||
public string Label;
|
||||
public Color AxisColor;
|
||||
public bool Negative;
|
||||
public AxisDirection Direction;
|
||||
}
|
||||
|
||||
private enum AxisDirection
|
||||
{
|
||||
PosX,
|
||||
PosY,
|
||||
PosZ,
|
||||
NegX,
|
||||
NegY,
|
||||
NegZ
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor of the Direction Gizmo
|
||||
/// </summary>
|
||||
/// <param name="owner">The owner of this object.</param>
|
||||
public DirectionGizmo(IGizmoOwner owner)
|
||||
{
|
||||
_owner = owner;
|
||||
_viewport = owner.Viewport;
|
||||
_viewportProjection.Init(owner.Viewport);
|
||||
|
||||
_xAxisData = new AxisData { Delta = new Float2(0, 0), Distance = 0, Label = "X", AxisColor = new Color(1.0f, 0.0f, 0.02745f, 1.0f), Negative = false, Direction = AxisDirection.PosX };
|
||||
_yAxisData = new AxisData { Delta = new Float2(0, 0), Distance = 0, Label = "Y", AxisColor = new Color(0.239215f, 1.0f, 0.047058f, 1.0f), Negative = false, Direction = AxisDirection.PosY };
|
||||
_zAxisData = new AxisData { Delta = new Float2(0, 0), Distance = 0, Label = "Z", AxisColor = new Color(0.0f, 0.0235294f, 1.0f, 1.0f), Negative = false, Direction = AxisDirection.PosZ };
|
||||
|
||||
_negXAxisData = new AxisData { Delta = new Float2(0, 0), Distance = 0, Label = "-X", AxisColor = new Color(1.0f, 0.0f, 0.02745f, 1.0f), Negative = true, Direction = AxisDirection.NegX };
|
||||
_negYAxisData = new AxisData { Delta = new Float2(0, 0), Distance = 0, Label = "-Y", AxisColor = new Color(0.239215f, 1.0f, 0.047058f, 1.0f), Negative = true, Direction = AxisDirection.NegY };
|
||||
_negZAxisData = new AxisData { Delta = new Float2(0, 0), Distance = 0, Label = "-Z", AxisColor = new Color(0.0f, 0.0235294f, 1.0f, 1.0f), Negative = true, Direction = AxisDirection.NegZ };
|
||||
_axisData.EnsureCapacity(6);
|
||||
_spritePositions.EnsureCapacity(6);
|
||||
|
||||
_posHandle = Editor.Instance.Icons.VisjectBoxClosed32;
|
||||
_negHandle = Editor.Instance.Icons.VisjectBoxOpen32;
|
||||
|
||||
_fontReference = new FontReference(Style.Current.FontSmall);
|
||||
_fontReference.Size = 8;
|
||||
}
|
||||
|
||||
private bool IsPointInSprite(Float2 point, Float2 spriteCenter)
|
||||
{
|
||||
Float2 delta = point - spriteCenter;
|
||||
float distanceSq = delta.LengthSquared;
|
||||
float radiusSq = _spriteRadius * _spriteRadius;
|
||||
return distanceSq <= radiusSq;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseMove(Float2 location)
|
||||
{
|
||||
_hoveredAxisIndex = -1;
|
||||
|
||||
// Check which axis is being hovered - check from closest to farthest for proper layering
|
||||
for (int i = _spritePositions.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (IsPointInSprite(location, _spritePositions[i].position))
|
||||
{
|
||||
_hoveredAxisIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
base.OnMouseMove(location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDown(Float2 location, MouseButton button)
|
||||
{
|
||||
if (base.OnMouseDown(location, button))
|
||||
return true;
|
||||
|
||||
// Check which axis is being clicked - check from closest to farthest for proper layering
|
||||
for (int i = _spritePositions.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (IsPointInSprite(location, _spritePositions[i].position))
|
||||
{
|
||||
OrientViewToAxis(_spritePositions[i].direction);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OrientViewToAxis(AxisDirection direction)
|
||||
{
|
||||
Quaternion orientation = direction switch
|
||||
{
|
||||
AxisDirection.PosX => Quaternion.Euler(0, 90, 0),
|
||||
AxisDirection.NegX => Quaternion.Euler(0, -90, 0),
|
||||
AxisDirection.PosY => Quaternion.Euler(-90, 0, 0),
|
||||
AxisDirection.NegY => Quaternion.Euler(90, 0, 0),
|
||||
AxisDirection.PosZ => Quaternion.Euler(0, 0, 0),
|
||||
AxisDirection.NegZ => Quaternion.Euler(0, 180, 0),
|
||||
_ => Quaternion.Identity
|
||||
};
|
||||
_viewport.OrientViewport(ref orientation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to Draw the gizmo.
|
||||
/// </summary>
|
||||
public override void DrawSelf()
|
||||
{
|
||||
base.DrawSelf();
|
||||
|
||||
var features = Render2D.Features;
|
||||
Render2D.Features = features & ~Render2D.RenderingFeatures.VertexSnapping;
|
||||
_viewportProjection.Init(_owner.Viewport);
|
||||
_gizmoCenter = _viewport.Task.View.WorldPosition + _viewport.Task.View.Direction * 1500;
|
||||
_viewportProjection.ProjectPoint(_gizmoCenter, out var gizmoCenterScreen);
|
||||
|
||||
var relativeCenter = Size * 0.5f;
|
||||
|
||||
// Project unit vectors
|
||||
_viewportProjection.ProjectPoint(_gizmoCenter + Vector3.Right, out var xProjected);
|
||||
_viewportProjection.ProjectPoint(_gizmoCenter + Vector3.Up, out var yProjected);
|
||||
_viewportProjection.ProjectPoint(_gizmoCenter + Vector3.Forward, out var zProjected);
|
||||
_viewportProjection.ProjectPoint(_gizmoCenter - Vector3.Right, out var negXProjected);
|
||||
_viewportProjection.ProjectPoint(_gizmoCenter - Vector3.Up, out var negYProjected);
|
||||
_viewportProjection.ProjectPoint(_gizmoCenter - Vector3.Forward, out var negZProjected);
|
||||
|
||||
// Normalize by viewport height to keep size independent of FOV and viewport dimensions
|
||||
float heightNormalization = _viewport.Height / 720.0f; // 720 = reference height
|
||||
if (_owner.Viewport.UseOrthographicProjection)
|
||||
heightNormalization /= _owner.Viewport.OrthographicScale * 0.5f; // Fix in ortho view to keep consistent size regardless of zoom level
|
||||
|
||||
Float2 xDelta = (xProjected - gizmoCenterScreen) / heightNormalization;
|
||||
Float2 yDelta = (yProjected - gizmoCenterScreen) / heightNormalization;
|
||||
Float2 zDelta = (zProjected - gizmoCenterScreen) / heightNormalization;
|
||||
Float2 negXDelta = (negXProjected - gizmoCenterScreen) / heightNormalization;
|
||||
Float2 negYDelta = (negYProjected - gizmoCenterScreen) / heightNormalization;
|
||||
Float2 negZDelta = (negZProjected - gizmoCenterScreen) / heightNormalization;
|
||||
|
||||
// Calculate distances from camera to determine draw order
|
||||
Vector3 cameraPosition = _viewport.Task.View.Position;
|
||||
float xDistance = (float)Vector3.Distance(cameraPosition, _gizmoCenter + Vector3.Right);
|
||||
float yDistance = (float)Vector3.Distance(cameraPosition, _gizmoCenter + Vector3.Up);
|
||||
float zDistance = (float)Vector3.Distance(cameraPosition, _gizmoCenter + Vector3.Forward);
|
||||
float negXDistance = (float)Vector3.Distance(cameraPosition, _gizmoCenter - Vector3.Right);
|
||||
float negYDistance = (float)Vector3.Distance(cameraPosition, _gizmoCenter - Vector3.Up);
|
||||
float negZDistance = (float)Vector3.Distance(cameraPosition, _gizmoCenter - Vector3.Forward);
|
||||
|
||||
_xAxisData.Delta = xDelta;
|
||||
_xAxisData.Distance = xDistance;
|
||||
_yAxisData.Delta = yDelta;
|
||||
_yAxisData.Distance = yDistance;
|
||||
_zAxisData.Delta = zDelta;
|
||||
_zAxisData.Distance = zDistance;
|
||||
_negXAxisData.Delta = negXDelta;
|
||||
_negXAxisData.Distance = negXDistance;
|
||||
_negYAxisData.Delta = negYDelta;
|
||||
_negYAxisData.Distance = negYDistance;
|
||||
_negZAxisData.Delta = negZDelta;
|
||||
_negZAxisData.Distance = negZDistance;
|
||||
|
||||
// Sort for correct draw order.
|
||||
_axisData.Clear();
|
||||
_axisData.AddRange([_xAxisData, _yAxisData, _zAxisData, _negXAxisData, _negYAxisData, _negZAxisData]);
|
||||
_axisData.Sort((a, b) => -a.Distance.CompareTo(b.Distance));
|
||||
|
||||
// Rebuild sprite positions list for hover detection
|
||||
_spritePositions.Clear();
|
||||
|
||||
Render2D.DrawSprite(_posHandle, new Rectangle(0, 0, Size), Color.Black.AlphaMultiplied(0.1f));
|
||||
|
||||
// Draw in order from farthest to closest
|
||||
for (int i = 0; i < _axisData.Count; i++)
|
||||
{
|
||||
var axis = _axisData[i];
|
||||
Float2 tipScreen = relativeCenter + axis.Delta * _axisLength;
|
||||
Float2 tipTextScreen = relativeCenter + axis.Delta * _textAxisLength;
|
||||
bool isHovered = _hoveredAxisIndex == i;
|
||||
|
||||
// Store sprite position for hover detection
|
||||
_spritePositions.Add((tipTextScreen, axis.Direction));
|
||||
|
||||
var axisColor = isHovered ? new Color(1.0f, 0.8980392f, 0.039215688f) : axis.AxisColor;
|
||||
var font = _fontReference.GetFont();
|
||||
if (!axis.Negative)
|
||||
{
|
||||
Render2D.DrawLine(relativeCenter, tipScreen, axisColor, 2.0f);
|
||||
Render2D.DrawSprite(_posHandle, new Rectangle(tipTextScreen - new Float2(_spriteRadius), new Float2(_spriteRadius * 2)), axisColor);
|
||||
Render2D.DrawText(font, axis.Label, isHovered ? Color.Gray : Color.Black, tipTextScreen - font.MeasureText(axis.Label) * 0.5f);
|
||||
}
|
||||
else
|
||||
{
|
||||
Render2D.DrawSprite(_posHandle, new Rectangle(tipTextScreen - new Float2(_spriteRadius), new Float2(_spriteRadius * 2)), axisColor.RGBMultiplied(0.65f));
|
||||
Render2D.DrawSprite(_negHandle, new Rectangle(tipTextScreen - new Float2(_spriteRadius), new Float2(_spriteRadius * 2)), axisColor);
|
||||
if (isHovered)
|
||||
Render2D.DrawText(font, axis.Label, Color.Black, tipTextScreen - font.MeasureText(axis.Label) * 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
Render2D.Features = features;
|
||||
}
|
||||
}
|
||||
@@ -710,6 +710,10 @@ namespace FlaxEditor.Options
|
||||
[EditorDisplay("Node Editors"), EditorOrder(4580)]
|
||||
public InputBinding NodesDistributeVertical = new InputBinding(KeyboardKeys.A, KeyboardKeys.Alt);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "Shift+F")]
|
||||
[EditorDisplay("Node Editors"), EditorOrder(4590)]
|
||||
public InputBinding FocusSelectedNodes = new InputBinding(KeyboardKeys.F, KeyboardKeys.Shift);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
29
Source/Editor/SceneGraph/Actors/RigidBodyNode.cs
Normal file
29
Source/Editor/SceneGraph/Actors/RigidBodyNode.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.SceneGraph.Actors
|
||||
{
|
||||
/// <summary>
|
||||
/// Scene tree node for <see cref="RigidBody"/> actor type.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public sealed class RigidBodyNode : ActorNode
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public RigidBodyNode(Actor actor)
|
||||
: base(actor)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void PostSpawn()
|
||||
{
|
||||
base.PostSpawn();
|
||||
|
||||
if (HasPrefabLink)
|
||||
return;
|
||||
Actor.StaticFlags = StaticFlags.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -324,13 +324,12 @@ namespace FlaxEditor.SceneGraph.GUI
|
||||
isExpanded = Editor.Instance.ProjectCache.IsExpandedActor(ref id);
|
||||
}
|
||||
|
||||
if (isExpanded)
|
||||
if (!noFilter)
|
||||
{
|
||||
Expand(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Collapse(true);
|
||||
if (isExpanded)
|
||||
Expand(true);
|
||||
else
|
||||
Collapse(true);
|
||||
}
|
||||
|
||||
Visible = isThisVisible | isAnyChildVisible;
|
||||
|
||||
@@ -74,6 +74,7 @@ namespace FlaxEditor.SceneGraph
|
||||
CustomNodesTypes.Add(typeof(NavMesh), typeof(ActorNode));
|
||||
CustomNodesTypes.Add(typeof(SpriteRender), typeof(SpriteRenderNode));
|
||||
CustomNodesTypes.Add(typeof(Joint), typeof(JointNode));
|
||||
CustomNodesTypes.Add(typeof(RigidBody), typeof(RigidBodyNode));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -52,6 +52,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
},
|
||||
new NodeArchetype
|
||||
{
|
||||
// [Deprecated]
|
||||
TypeID = 3,
|
||||
Title = "Pack Material Layer",
|
||||
Description = "Pack material properties",
|
||||
@@ -75,6 +76,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
},
|
||||
new NodeArchetype
|
||||
{
|
||||
// [Deprecated]
|
||||
TypeID = 4,
|
||||
Title = "Unpack Material Layer",
|
||||
Description = "Unpack material properties",
|
||||
@@ -120,6 +122,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
TypeID = 6,
|
||||
Title = "Pack Material Layer",
|
||||
Description = "Pack material properties",
|
||||
AlternativeTitles = new[] { "Make Material Layer", "Construct Material Layer", "Compose Material Layer" },
|
||||
Flags = NodeFlags.MaterialGraph,
|
||||
Size = new Float2(200, 280),
|
||||
Elements = new[]
|
||||
@@ -146,6 +149,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
TypeID = 7,
|
||||
Title = "Unpack Material Layer",
|
||||
Description = "Unpack material properties",
|
||||
AlternativeTitles = new[] { "Break Material Layer", "Deconstruct Material Layer", "Decompose Material Layer", "Split Material Layer" },
|
||||
Flags = NodeFlags.MaterialGraph,
|
||||
Size = new Float2(210, 280),
|
||||
Elements = new[]
|
||||
|
||||
@@ -342,6 +342,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
TypeID = 20,
|
||||
Title = "Pack Float2",
|
||||
Description = "Pack components to Float2",
|
||||
AlternativeTitles = new[] { "Make Float2", "Construct Float2", "Compose Float2" },
|
||||
Flags = NodeFlags.AllGraphs,
|
||||
Size = new Float2(150, 40),
|
||||
DefaultValues = new object[]
|
||||
@@ -361,6 +362,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
TypeID = 21,
|
||||
Title = "Pack Float3",
|
||||
Description = "Pack components to Float3",
|
||||
AlternativeTitles = new[] { "Make Float3", "Construct Float3", "Compose Float3" },
|
||||
Flags = NodeFlags.AllGraphs,
|
||||
Size = new Float2(150, 60),
|
||||
DefaultValues = new object[]
|
||||
@@ -382,6 +384,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
TypeID = 22,
|
||||
Title = "Pack Float4",
|
||||
Description = "Pack components to Float4",
|
||||
AlternativeTitles = new[] { "Make Float4", "Construct Float4", "Compose Float4" },
|
||||
Flags = NodeFlags.AllGraphs,
|
||||
Size = new Float2(150, 80),
|
||||
DefaultValues = new object[]
|
||||
@@ -405,6 +408,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
TypeID = 23,
|
||||
Title = "Pack Rotation",
|
||||
Description = "Pack components to Rotation",
|
||||
AlternativeTitles = new[] { "Make Rotation", "Construct Rotation", "Compose Rotation" },
|
||||
Flags = NodeFlags.AllGraphs,
|
||||
Size = new Float2(150, 60),
|
||||
DefaultValues = new object[]
|
||||
@@ -426,6 +430,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
TypeID = 24,
|
||||
Title = "Pack Transform",
|
||||
Description = "Pack components to Transform",
|
||||
AlternativeTitles = new[] { "Make Transform", "Construct Transform", "Compose Transform" },
|
||||
Flags = NodeFlags.AllGraphs,
|
||||
Size = new Float2(150, 80),
|
||||
Elements = new[]
|
||||
@@ -441,6 +446,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
TypeID = 25,
|
||||
Title = "Pack Box",
|
||||
Description = "Pack components to BoundingBox",
|
||||
AlternativeTitles = new[] { "Make Box", "Construct Box", "Compose Box" },
|
||||
Flags = NodeFlags.AllGraphs,
|
||||
Size = new Float2(150, 40),
|
||||
Elements = new[]
|
||||
@@ -454,6 +460,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
TypeID = 26,
|
||||
Title = "Pack Structure",
|
||||
AlternativeTitles = new[] { "Make Structure", "Construct Structure", "Compose Structure" },
|
||||
Create = (id, context, arch, groupArch) => new PackStructureNode(id, context, arch, groupArch),
|
||||
IsInputCompatible = PackStructureNode.IsInputCompatible,
|
||||
IsOutputCompatible = PackStructureNode.IsOutputCompatible,
|
||||
@@ -479,6 +486,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
TypeID = 30,
|
||||
Title = "Unpack Float2",
|
||||
Description = "Unpack components from Float2",
|
||||
AlternativeTitles = new[] { "Break Float2", "Deconstruct Float2", "Decompose Float2", "Split Float2" },
|
||||
Flags = NodeFlags.AllGraphs,
|
||||
Size = new Float2(150, 40),
|
||||
Elements = new[]
|
||||
@@ -493,6 +501,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
TypeID = 31,
|
||||
Title = "Unpack Float3",
|
||||
Description = "Unpack components from Float3",
|
||||
AlternativeTitles = new[] { "Break Float3", "Deconstruct Float3", "Decompose Float3", "Split Float3" },
|
||||
Flags = NodeFlags.AllGraphs,
|
||||
Size = new Float2(150, 60),
|
||||
Elements = new[]
|
||||
@@ -508,6 +517,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
TypeID = 32,
|
||||
Title = "Unpack Float4",
|
||||
Description = "Unpack components from Float4",
|
||||
AlternativeTitles = new[] { "Break Float4", "Deconstruct Float4", "Decompose Float4", "Split Float4" },
|
||||
Flags = NodeFlags.AllGraphs,
|
||||
Size = new Float2(150, 80),
|
||||
Elements = new[]
|
||||
@@ -524,6 +534,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
TypeID = 33,
|
||||
Title = "Unpack Rotation",
|
||||
Description = "Unpack components from Rotation",
|
||||
AlternativeTitles = new[] { "Break Rotation", "Deconstruct Rotation", "Decompose Rotation", "Split Rotation" },
|
||||
Flags = NodeFlags.AllGraphs,
|
||||
Size = new Float2(170, 60),
|
||||
Elements = new[]
|
||||
@@ -539,6 +550,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
TypeID = 34,
|
||||
Title = "Unpack Transform",
|
||||
Description = "Unpack components from Transform",
|
||||
AlternativeTitles = new[] { "Break Transform", "Deconstruct Transform", "Decompose Transform", "Split Transform" },
|
||||
Flags = NodeFlags.AllGraphs,
|
||||
Size = new Float2(170, 60),
|
||||
Elements = new[]
|
||||
@@ -554,6 +566,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
TypeID = 35,
|
||||
Title = "Unpack Box",
|
||||
Description = "Unpack components from BoundingBox",
|
||||
AlternativeTitles = new[] { "Break BoundingBox", "Deconstruct BoundingBox", "Decompose BoundingBox", "Split BoundingBox" },
|
||||
Flags = NodeFlags.AllGraphs,
|
||||
Size = new Float2(170, 40),
|
||||
Elements = new[]
|
||||
@@ -572,6 +585,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
IsOutputCompatible = UnpackStructureNode.IsOutputCompatible,
|
||||
GetInputOutputDescription = UnpackStructureNode.GetInputOutputDescription,
|
||||
Description = "Breaks the structure data to allow extracting components from it.",
|
||||
AlternativeTitles = new[] { "Break Structure", "Deconstruct Structure", "Decompose Structure", "Split Structure" },
|
||||
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI,
|
||||
Size = new Float2(180, 20),
|
||||
DefaultValues = new object[]
|
||||
|
||||
@@ -585,7 +585,7 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
|
||||
private void UpdateFilters()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_searchBox.Text) && _selectedBoxes[0] == null)
|
||||
if (string.IsNullOrEmpty(_searchBox.Text) && _selectedBoxes.Count == 0)
|
||||
{
|
||||
ResetView();
|
||||
Profiler.EndEvent();
|
||||
|
||||
@@ -34,11 +34,6 @@ namespace FlaxEditor.Surface.Elements
|
||||
/// </summary>
|
||||
public const float DefaultConnectionOffset = 24f;
|
||||
|
||||
/// <summary>
|
||||
/// Distance for the mouse to be considered above the connection
|
||||
/// </summary>
|
||||
public float MouseOverConnectionDistance => 100f / Surface.ViewScale;
|
||||
|
||||
/// <inheritdoc />
|
||||
public OutputBox(SurfaceNode parentNode, NodeElementArchetype archetype)
|
||||
: base(parentNode, archetype, archetype.Position + new Float2(parentNode.Archetype.Size.X, 0))
|
||||
@@ -109,12 +104,13 @@ namespace FlaxEditor.Surface.Elements
|
||||
/// </summary>
|
||||
/// <param name="targetBox">The other box.</param>
|
||||
/// <param name="mousePosition">The mouse position</param>
|
||||
public bool IntersectsConnection(Box targetBox, ref Float2 mousePosition)
|
||||
/// <param name="distance">Distance at which its an intersection</param>
|
||||
public bool IntersectsConnection(Box targetBox, ref Float2 mousePosition, float distance)
|
||||
{
|
||||
float connectionOffset = Mathf.Max(0f, DefaultConnectionOffset * (1 - Editor.Instance.Options.Options.Interface.ConnectionCurvature));
|
||||
Float2 start = new Float2(ConnectionOrigin.X + connectionOffset, ConnectionOrigin.Y);
|
||||
Float2 end = new Float2(targetBox.ConnectionOrigin.X - connectionOffset, targetBox.ConnectionOrigin.Y);
|
||||
return IntersectsConnection(ref start, ref end, ref mousePosition, MouseOverConnectionDistance);
|
||||
return IntersectsConnection(ref start, ref end, ref mousePosition, distance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -182,7 +178,7 @@ namespace FlaxEditor.Surface.Elements
|
||||
{
|
||||
// Draw all the connections
|
||||
var style = Surface.Style;
|
||||
var mouseOverDistance = MouseOverConnectionDistance;
|
||||
var mouseOverDistance = Surface.MouseOverConnectionDistance;
|
||||
var startPos = ConnectionOrigin;
|
||||
var startHighlight = ConnectionsHighlightIntensity;
|
||||
for (int i = 0; i < Connections.Count; i++)
|
||||
|
||||
@@ -574,13 +574,13 @@ namespace FlaxEditor.Surface
|
||||
var showSearch = () => editor.ContentFinding.ShowSearch(window);
|
||||
|
||||
// Toolstrip
|
||||
saveButton = toolStrip.AddButton(editor.Icons.Save64, window.Save).LinkTooltip("Save", ref inputOptions.Save);
|
||||
saveButton = toolStrip.AddButton(editor.Icons.Save64, window.Save).LinkTooltip("Save.", ref inputOptions.Save);
|
||||
toolStrip.AddSeparator();
|
||||
undoButton = toolStrip.AddButton(editor.Icons.Undo64, undo.PerformUndo).LinkTooltip("Undo", ref inputOptions.Undo);
|
||||
redoButton = toolStrip.AddButton(editor.Icons.Redo64, undo.PerformRedo).LinkTooltip("Redo", ref inputOptions.Redo);
|
||||
undoButton = toolStrip.AddButton(editor.Icons.Undo64, undo.PerformUndo).LinkTooltip("Undo.", ref inputOptions.Undo);
|
||||
redoButton = toolStrip.AddButton(editor.Icons.Redo64, undo.PerformRedo).LinkTooltip("Redo.", ref inputOptions.Redo);
|
||||
toolStrip.AddSeparator();
|
||||
toolStrip.AddButton(editor.Icons.Search64, showSearch).LinkTooltip("Open content search tool", ref inputOptions.Search);
|
||||
toolStrip.AddButton(editor.Icons.CenterView64, surface.ShowWholeGraph).LinkTooltip("Show whole graph");
|
||||
toolStrip.AddButton(editor.Icons.Search64, showSearch).LinkTooltip("Open content search tool.", ref inputOptions.Search);
|
||||
toolStrip.AddButton(editor.Icons.CenterView64, surface.ShowWholeGraph).LinkTooltip("Show whole graph.");
|
||||
var gridSnapButton = toolStrip.AddButton(editor.Icons.Grid32, surface.ToggleGridSnapping);
|
||||
gridSnapButton.LinkTooltip("Toggle grid snapping for nodes.");
|
||||
gridSnapButton.AutoCheck = true;
|
||||
|
||||
@@ -410,8 +410,11 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
menu.AddSeparator();
|
||||
|
||||
_cmFormatNodesMenu = menu.AddChildMenu("Format node(s)");
|
||||
_cmFormatNodesMenu.Enabled = CanEdit && HasNodesSelection;
|
||||
bool allNodesNoMove = SelectedNodes.All(n => n.Archetype.Flags.HasFlag(NodeFlags.NoMove));
|
||||
bool clickedNodeNoMove = ((SelectedNodes.Count == 1 && controlUnderMouse is SurfaceNode n && n.Archetype.Flags.HasFlag(NodeFlags.NoMove)));
|
||||
|
||||
_cmFormatNodesMenu = menu.AddChildMenu("Format nodes");
|
||||
_cmFormatNodesMenu.Enabled = CanEdit && HasNodesSelection && !(allNodesNoMove || clickedNodeNoMove);
|
||||
|
||||
_cmFormatNodesConnectionButton = _cmFormatNodesMenu.ContextMenu.AddButton("Auto format", Editor.Instance.Options.Options.Input.NodesAutoFormat, () => { FormatGraph(SelectedNodes); });
|
||||
_cmFormatNodesConnectionButton = _cmFormatNodesMenu.ContextMenu.AddButton("Straighten connections", Editor.Instance.Options.Options.Input.NodesStraightenConnections, () => { StraightenGraphConnections(SelectedNodes); });
|
||||
|
||||
@@ -213,6 +213,44 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw connection hints for lazy connect feature.
|
||||
/// </summary>
|
||||
protected virtual void DrawLazyConnect()
|
||||
{
|
||||
var style = FlaxEngine.GUI.Style.Current;
|
||||
|
||||
if (_lazyConnectStartNode != null)
|
||||
{
|
||||
Float2 upperLeft = _rootControl.PointToParent(_lazyConnectStartNode.UpperLeft);
|
||||
Rectangle startNodeOutline = new Rectangle(upperLeft + 1f, _lazyConnectStartNode.Size - 1f);
|
||||
startNodeOutline.Size *= ViewScale;
|
||||
Render2D.DrawRectangle(startNodeOutline.MakeExpanded(4f), style.BackgroundSelected, 4f);
|
||||
}
|
||||
|
||||
if (_lazyConnectEndNode != null)
|
||||
{
|
||||
Float2 upperLeft = _rootControl.PointToParent(_lazyConnectEndNode.UpperLeft);
|
||||
Rectangle startNodeOutline = new Rectangle(upperLeft + 1f, _lazyConnectEndNode.Size - 1f);
|
||||
startNodeOutline.Size *= ViewScale;
|
||||
Render2D.DrawRectangle(startNodeOutline.MakeExpanded(4f), style.BackgroundSelected, 4f);
|
||||
}
|
||||
|
||||
Rectangle startRect = new Rectangle(_rightMouseDownPos - 6f, new Float2(12f));
|
||||
Rectangle endRect = new Rectangle(_mousePos - 6f, new Float2(12f));
|
||||
|
||||
// Start and end shadows/ outlines
|
||||
Render2D.FillRectangle(startRect.MakeExpanded(2.5f), Color.Black);
|
||||
Render2D.FillRectangle(endRect.MakeExpanded(2.5f), Color.Black);
|
||||
|
||||
Render2D.DrawLine(_rightMouseDownPos, _mousePos, Color.Black, 7.5f);
|
||||
Render2D.DrawLine(_rightMouseDownPos, _mousePos, style.ForegroundGrey, 5f);
|
||||
|
||||
// Draw start and end boxes over the lines to hide ugly artifacts at the ends
|
||||
Render2D.FillRectangle(startRect, style.ForegroundGrey);
|
||||
Render2D.FillRectangle(endRect, style.ForegroundGrey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the contents of the surface (nodes, connections, comments, etc.).
|
||||
/// </summary>
|
||||
@@ -260,6 +298,9 @@ namespace FlaxEditor.Surface
|
||||
|
||||
DrawContents();
|
||||
|
||||
if (_isLazyConnecting)
|
||||
DrawLazyConnect();
|
||||
|
||||
//Render2D.DrawText(style.FontTitle, string.Format("Scale: {0}", _rootControl.Scale), rect, Enabled ? Color.Red : Color.Black);
|
||||
|
||||
// Draw border
|
||||
|
||||
@@ -39,6 +39,8 @@ namespace FlaxEditor.Surface
|
||||
if (nodes.Count <= 1)
|
||||
return;
|
||||
|
||||
List<MoveNodesAction> undoActions = new List<MoveNodesAction>();
|
||||
|
||||
var nodesToVisit = new HashSet<SurfaceNode>(nodes);
|
||||
|
||||
// While we haven't formatted every node
|
||||
@@ -73,18 +75,23 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
}
|
||||
|
||||
FormatConnectedGraph(connectedNodes);
|
||||
undoActions.AddRange(FormatConnectedGraph(connectedNodes));
|
||||
}
|
||||
|
||||
Undo?.AddAction(new MultiUndoAction(undoActions, "Format nodes"));
|
||||
MarkAsEdited(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a graph where all nodes are connected.
|
||||
/// </summary>
|
||||
/// <param name="nodes">List of connected nodes.</param>
|
||||
protected void FormatConnectedGraph(List<SurfaceNode> nodes)
|
||||
private List<MoveNodesAction> FormatConnectedGraph(List<SurfaceNode> nodes)
|
||||
{
|
||||
List<MoveNodesAction> undoActions = new List<MoveNodesAction>();
|
||||
|
||||
if (nodes.Count <= 1)
|
||||
return;
|
||||
return undoActions;
|
||||
|
||||
var boundingBox = GetNodesBounds(nodes);
|
||||
|
||||
@@ -140,7 +147,6 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
// Set the node positions
|
||||
var undoActions = new List<MoveNodesAction>();
|
||||
var topRightPosition = endNodes[0].Location;
|
||||
for (int i = 0; i < nodes.Count; i++)
|
||||
{
|
||||
@@ -155,16 +161,18 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
}
|
||||
|
||||
MarkAsEdited(false);
|
||||
Undo?.AddAction(new MultiUndoAction(undoActions, "Format nodes"));
|
||||
return undoActions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Straightens every connection between nodes in <paramref name="nodes"/>.
|
||||
/// </summary>
|
||||
/// <param name="nodes">List of nodes.</param>
|
||||
/// <returns>List of undo actions.</returns>
|
||||
public void StraightenGraphConnections(List<SurfaceNode> nodes)
|
||||
{
|
||||
{
|
||||
nodes = nodes.Where(n => !n.Archetype.Flags.HasFlag(NodeFlags.NoMove)).ToList();
|
||||
|
||||
if (nodes.Count <= 1)
|
||||
return;
|
||||
|
||||
@@ -350,8 +358,10 @@ namespace FlaxEditor.Surface
|
||||
/// <param name="nodes">List of nodes.</param>
|
||||
/// <param name="alignmentType">Alignemnt type.</param>
|
||||
public void AlignNodes(List<SurfaceNode> nodes, NodeAlignmentType alignmentType)
|
||||
{
|
||||
if(nodes.Count <= 1)
|
||||
{
|
||||
nodes = nodes.Where(n => !n.Archetype.Flags.HasFlag(NodeFlags.NoMove)).ToList();
|
||||
|
||||
if (nodes.Count <= 1)
|
||||
return;
|
||||
|
||||
var undoActions = new List<MoveNodesAction>();
|
||||
@@ -392,6 +402,8 @@ namespace FlaxEditor.Surface
|
||||
/// <param name="vertically">If false will be done horizontally, if true will be done vertically.</param>
|
||||
public void DistributeNodes(List<SurfaceNode> nodes, bool vertically)
|
||||
{
|
||||
nodes = nodes.Where(n => !n.Archetype.Flags.HasFlag(NodeFlags.NoMove)).ToList();
|
||||
|
||||
if(nodes.Count <= 1)
|
||||
return;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static FlaxEditor.Surface.Archetypes.Particles;
|
||||
using FlaxEditor.Options;
|
||||
using FlaxEditor.Surface.Elements;
|
||||
using FlaxEditor.Surface.Undo;
|
||||
@@ -23,12 +24,26 @@ namespace FlaxEditor.Surface
|
||||
/// </summary>
|
||||
public bool PanWithMiddleMouse = false;
|
||||
|
||||
/// <summary>
|
||||
/// Distance for the mouse to be considered above the connection.
|
||||
/// </summary>
|
||||
public float MouseOverConnectionDistance => 100f / ViewScale;
|
||||
|
||||
/// <summary>
|
||||
/// Distance of a node from which it is able to be slotted into an existing connection.
|
||||
/// </summary>
|
||||
public float SlotNodeIntoConnectionDistance => 250f / ViewScale;
|
||||
|
||||
private string _currentInputText = string.Empty;
|
||||
private Float2 _movingNodesDelta;
|
||||
private Float2 _gridRoundingDelta;
|
||||
private HashSet<SurfaceNode> _movingNodes;
|
||||
private HashSet<SurfaceNode> _temporarySelectedNodes;
|
||||
private readonly Stack<InputBracket> _inputBrackets = new Stack<InputBracket>();
|
||||
private bool _isLazyConnecting;
|
||||
private SurfaceNode _lazyConnectStartNode;
|
||||
private SurfaceNode _lazyConnectEndNode;
|
||||
private InputBinding _focusSelectedNodeBinding;
|
||||
|
||||
private class InputBracket
|
||||
{
|
||||
@@ -250,8 +265,13 @@ namespace FlaxEditor.Surface
|
||||
// Cache mouse location
|
||||
_mousePos = location;
|
||||
|
||||
if (_isLazyConnecting && GetControlUnderMouse() is SurfaceNode nodeUnderMouse && !(nodeUnderMouse is SurfaceComment || nodeUnderMouse is ParticleEmitterNode))
|
||||
_lazyConnectEndNode = nodeUnderMouse;
|
||||
else if (_isLazyConnecting && Nodes.Count > 0)
|
||||
_lazyConnectEndNode = GetClosestNodeAtLocation(location);
|
||||
|
||||
// Moving around surface with mouse
|
||||
if (_rightMouseDown)
|
||||
if (_rightMouseDown && !_isLazyConnecting)
|
||||
{
|
||||
// Calculate delta
|
||||
var delta = location - _rightMouseDownPos;
|
||||
@@ -321,6 +341,33 @@ namespace FlaxEditor.Surface
|
||||
|
||||
foreach (var node in _movingNodes)
|
||||
{
|
||||
// Allow ripping the node from its current connection
|
||||
if (RootWindow.GetKey(KeyboardKeys.Alt))
|
||||
{
|
||||
InputBox nodeConnectedInput = null;
|
||||
OutputBox nodeConnectedOuput = null;
|
||||
|
||||
var boxes = node.GetBoxes();
|
||||
foreach (var box in boxes)
|
||||
{
|
||||
if (!box.IsOutput && box.Connections.Count > 0)
|
||||
{
|
||||
nodeConnectedInput = (InputBox)box;
|
||||
continue;
|
||||
}
|
||||
if (box.IsOutput && box.Connections.Count > 0)
|
||||
{
|
||||
nodeConnectedOuput = (OutputBox)box;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (nodeConnectedInput != null && nodeConnectedOuput != null)
|
||||
TryConnect(nodeConnectedOuput.Connections[0], nodeConnectedInput.Connections[0]);
|
||||
|
||||
node.RemoveConnections();
|
||||
}
|
||||
|
||||
if (gridSnap)
|
||||
{
|
||||
Float2 unroundedLocation = node.Location;
|
||||
@@ -420,7 +467,7 @@ namespace FlaxEditor.Surface
|
||||
if (!handled && CanEdit && CanUseNodeType(7, 29))
|
||||
{
|
||||
var mousePos = _rootControl.PointFromParent(ref _mousePos);
|
||||
if (IntersectsConnection(mousePos, out InputBox inputBox, out OutputBox outputBox) && GetControlUnderMouse() == null)
|
||||
if (IntersectsConnection(mousePos, out InputBox inputBox, out OutputBox outputBox, MouseOverConnectionDistance) && GetControlUnderMouse() == null)
|
||||
{
|
||||
if (Undo != null)
|
||||
{
|
||||
@@ -515,11 +562,17 @@ namespace FlaxEditor.Surface
|
||||
_middleMouseDownPos = location;
|
||||
}
|
||||
|
||||
if (root.GetKey(KeyboardKeys.Alt) && button == MouseButton.Right)
|
||||
_isLazyConnecting = true;
|
||||
|
||||
// Check if any node is under the mouse
|
||||
SurfaceControl controlUnderMouse = GetControlUnderMouse();
|
||||
var cLocation = _rootControl.PointFromParent(ref location);
|
||||
if (controlUnderMouse != null)
|
||||
{
|
||||
if (controlUnderMouse is SurfaceNode node && _isLazyConnecting && !(controlUnderMouse is SurfaceComment || controlUnderMouse is ParticleEmitterNode))
|
||||
_lazyConnectStartNode = node;
|
||||
|
||||
// Check if mouse is over header and user is pressing mouse left button
|
||||
if (_leftMouseDown && controlUnderMouse.CanSelect(ref cLocation))
|
||||
{
|
||||
@@ -554,6 +607,9 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_isLazyConnecting && Nodes.Count > 0)
|
||||
_lazyConnectStartNode = GetClosestNodeAtLocation(location);
|
||||
|
||||
// Cache flags and state
|
||||
if (_leftMouseDown)
|
||||
{
|
||||
@@ -602,8 +658,71 @@ namespace FlaxEditor.Surface
|
||||
{
|
||||
if (_movingNodes != null && _movingNodes.Count > 0)
|
||||
{
|
||||
// Allow dropping a single node onto an existing connection and connect it
|
||||
if (_movingNodes.Count == 1)
|
||||
{
|
||||
var mousePos = _rootControl.PointFromParent(ref _mousePos);
|
||||
InputBox intersectedConnectionInputBox;
|
||||
OutputBox intersectedConnectionOutputBox;
|
||||
if (IntersectsConnection(mousePos, out intersectedConnectionInputBox, out intersectedConnectionOutputBox, SlotNodeIntoConnectionDistance))
|
||||
{
|
||||
SurfaceNode node = _movingNodes.First();
|
||||
InputBox nodeInputBox = (InputBox)node.GetBoxes().First(b => !b.IsOutput);
|
||||
OutputBox nodeOutputBox = (OutputBox)node.GetBoxes().First(b => b.IsOutput);
|
||||
TryConnect(intersectedConnectionOutputBox, nodeInputBox);
|
||||
TryConnect(nodeOutputBox, intersectedConnectionInputBox);
|
||||
|
||||
float intersectedConnectionNodesXDistance = intersectedConnectionInputBox.ParentNode.Left - intersectedConnectionOutputBox.ParentNode.Right;
|
||||
float paddedNodeWidth = node.Width + 2f;
|
||||
if (intersectedConnectionNodesXDistance < paddedNodeWidth)
|
||||
{
|
||||
List<SurfaceNode> visitedNodes = new List<SurfaceNode>{ node };
|
||||
List<SurfaceNode> movedNodes = new List<SurfaceNode>();
|
||||
Float2 locationDelta = new Float2(paddedNodeWidth, 0f);
|
||||
|
||||
MoveConnectedNodes(intersectedConnectionInputBox.ParentNode);
|
||||
|
||||
void MoveConnectedNodes(SurfaceNode node)
|
||||
{
|
||||
// Only move node if it is to the right of the node we have connected the moved node to
|
||||
if (node.Right > intersectedConnectionInputBox.ParentNode.Left + 15f && !node.Archetype.Flags.HasFlag(NodeFlags.NoMove))
|
||||
{
|
||||
node.Location += locationDelta;
|
||||
movedNodes.Add(node);
|
||||
}
|
||||
|
||||
visitedNodes.Add(node);
|
||||
|
||||
foreach (var box in node.GetBoxes())
|
||||
{
|
||||
if (!box.HasAnyConnection || box == intersectedConnectionInputBox)
|
||||
continue;
|
||||
|
||||
foreach (var connectedBox in box.Connections)
|
||||
{
|
||||
SurfaceNode nextNode = connectedBox.ParentNode;
|
||||
if (visitedNodes.Contains(nextNode))
|
||||
continue;
|
||||
|
||||
MoveConnectedNodes(nextNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Float2 nodeMoveOffset = new Float2(node.Width * 0.5f, 0f);
|
||||
node.Location += nodeMoveOffset;
|
||||
|
||||
var moveNodesAction = new MoveNodesAction(Context, movedNodes.Select(n => n.ID).ToArray(), locationDelta);
|
||||
var moveNodeAction = new MoveNodesAction(Context, [node.ID], nodeMoveOffset);
|
||||
var multiAction = new MultiUndoAction(moveNodeAction, moveNodesAction);
|
||||
|
||||
AddBatchedUndoAction(multiAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Undo != null && !_movingNodesDelta.IsZero && CanEdit)
|
||||
Undo.AddAction(new MoveNodesAction(Context, _movingNodes.Select(x => x.ID).ToArray(), _movingNodesDelta));
|
||||
AddBatchedUndoAction(new MoveNodesAction(Context, _movingNodes.Select(x => x.ID).ToArray(), _movingNodesDelta));
|
||||
_movingNodes.Clear();
|
||||
}
|
||||
_movingNodesDelta = Float2.Zero;
|
||||
@@ -630,12 +749,36 @@ namespace FlaxEditor.Surface
|
||||
{
|
||||
// Check if any control is under the mouse
|
||||
_cmStartPos = location;
|
||||
if (controlUnderMouse == null)
|
||||
if (controlUnderMouse == null && !_isLazyConnecting)
|
||||
{
|
||||
showPrimaryMenu = true;
|
||||
}
|
||||
}
|
||||
_mouseMoveAmount = 0;
|
||||
|
||||
if (_isLazyConnecting)
|
||||
{
|
||||
if (_lazyConnectStartNode != null && _lazyConnectEndNode != null && _lazyConnectStartNode != _lazyConnectEndNode)
|
||||
{
|
||||
// First check if there is a type matching input and output where input
|
||||
OutputBox startNodeOutput = (OutputBox)_lazyConnectStartNode.GetBoxes().FirstOrDefault(b => b.IsOutput, null);
|
||||
InputBox endNodeInput = null;
|
||||
|
||||
if (startNodeOutput != null)
|
||||
endNodeInput = (InputBox)_lazyConnectEndNode.GetBoxes().FirstOrDefault(b => !b.IsOutput && b.CurrentType == startNodeOutput.CurrentType && !b.HasAnyConnection && b.IsActive && b.CanConnectWith(startNodeOutput), null);
|
||||
|
||||
// Perform less strict checks (less ideal conditions for connection but still good) if the first checks failed
|
||||
if (endNodeInput == null)
|
||||
endNodeInput = (InputBox)_lazyConnectEndNode.GetBoxes().FirstOrDefault(b => !b.IsOutput && !b.HasAnyConnection && b.CanConnectWith(startNodeOutput), null);
|
||||
|
||||
if (startNodeOutput != null && endNodeInput != null)
|
||||
TryConnect(startNodeOutput, endNodeInput);
|
||||
}
|
||||
|
||||
_isLazyConnecting = false;
|
||||
_lazyConnectStartNode = null;
|
||||
_lazyConnectEndNode = null;
|
||||
}
|
||||
}
|
||||
if (_middleMouseDown && button == MouseButton.Middle)
|
||||
{
|
||||
@@ -651,7 +794,7 @@ namespace FlaxEditor.Surface
|
||||
{
|
||||
// Surface was not moved with MMB so try to remove connection underneath
|
||||
var mousePos = _rootControl.PointFromParent(ref location);
|
||||
if (IntersectsConnection(mousePos, out InputBox inputBox, out OutputBox outputBox))
|
||||
if (IntersectsConnection(mousePos, out InputBox inputBox, out OutputBox outputBox, MouseOverConnectionDistance))
|
||||
{
|
||||
var action = new EditNodeConnections(inputBox.ParentNode.Context, inputBox.ParentNode);
|
||||
inputBox.BreakConnection(outputBox);
|
||||
@@ -704,13 +847,21 @@ namespace FlaxEditor.Surface
|
||||
|
||||
private void MoveSelectedNodes(Float2 delta)
|
||||
{
|
||||
// TODO: undo
|
||||
List<MoveNodesAction> undoActions = new List<MoveNodesAction>();
|
||||
|
||||
delta /= _targetScale;
|
||||
OnGetNodesToMove();
|
||||
foreach (var node in _movingNodes)
|
||||
{
|
||||
node.Location += delta;
|
||||
if (Undo != null)
|
||||
undoActions.Add(new MoveNodesAction(Context, new[] { node.ID }, delta));
|
||||
}
|
||||
_isMovingSelection = false;
|
||||
MarkAsEdited(false);
|
||||
|
||||
if (undoActions.Count > 0)
|
||||
Undo?.AddAction(new MultiUndoAction(undoActions, "Moved "));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -837,6 +988,29 @@ namespace FlaxEditor.Surface
|
||||
return false;
|
||||
}
|
||||
|
||||
private SurfaceNode GetClosestNodeAtLocation(Float2 location)
|
||||
{
|
||||
SurfaceNode currentClosestNode = null;
|
||||
float currentClosestDistanceSquared = float.MaxValue;
|
||||
|
||||
foreach (var node in Nodes)
|
||||
{
|
||||
if (node is SurfaceComment || node is ParticleEmitterNode)
|
||||
continue;
|
||||
|
||||
Float2 nodeSurfaceLocation = _rootControl.PointToParent(node.Center);
|
||||
|
||||
float distanceSquared = Float2.DistanceSquared(location, nodeSurfaceLocation);
|
||||
if (distanceSquared < currentClosestDistanceSquared)
|
||||
{
|
||||
currentClosestNode = node;
|
||||
currentClosestDistanceSquared = distanceSquared;
|
||||
}
|
||||
}
|
||||
|
||||
return currentClosestNode;
|
||||
}
|
||||
|
||||
private void ResetInput()
|
||||
{
|
||||
InputText = "";
|
||||
@@ -845,7 +1019,8 @@ namespace FlaxEditor.Surface
|
||||
|
||||
private void CurrentInputTextChanged(string currentInputText)
|
||||
{
|
||||
if (string.IsNullOrEmpty(currentInputText))
|
||||
// Check if focus selected nodes binding is being pressed to prevent it triggering primary menu
|
||||
if (string.IsNullOrEmpty(currentInputText) || _focusSelectedNodeBinding.Process(RootWindow))
|
||||
return;
|
||||
if (IsPrimaryMenuOpened || !CanEdit)
|
||||
{
|
||||
@@ -1025,7 +1200,7 @@ namespace FlaxEditor.Surface
|
||||
return new Float2(xLocation, yLocation);
|
||||
}
|
||||
|
||||
private bool IntersectsConnection(Float2 mousePosition, out InputBox inputBox, out OutputBox outputBox)
|
||||
private bool IntersectsConnection(Float2 mousePosition, out InputBox inputBox, out OutputBox outputBox, float distance)
|
||||
{
|
||||
for (int i = 0; i < Nodes.Count; i++)
|
||||
{
|
||||
@@ -1035,7 +1210,7 @@ namespace FlaxEditor.Surface
|
||||
{
|
||||
for (int k = 0; k < ob.Connections.Count; k++)
|
||||
{
|
||||
if (ob.IntersectsConnection(ob.Connections[k], ref mousePosition))
|
||||
if (ob.IntersectsConnection(ob.Connections[k], ref mousePosition, distance))
|
||||
{
|
||||
outputBox = ob;
|
||||
inputBox = ob.Connections[k] as InputBox;
|
||||
|
||||
@@ -423,8 +423,9 @@ namespace FlaxEditor.Surface
|
||||
new InputActionsContainer.Binding(options => options.NodesAlignLeft, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Left); }),
|
||||
new InputActionsContainer.Binding(options => options.NodesAlignCenter, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Center); }),
|
||||
new InputActionsContainer.Binding(options => options.NodesAlignRight, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Right); }),
|
||||
new InputActionsContainer.Binding(options => options.NodesDistributeHorizontal, () => { DistributeNodes(SelectedNodes, false); }),
|
||||
new InputActionsContainer.Binding(options => options.NodesDistributeVertical, () => { DistributeNodes(SelectedNodes, true); }),
|
||||
new InputActionsContainer.Binding(options => options.NodesDistributeHorizontal, () => { DistributeNodes(SelectedNodes, false); }),
|
||||
new InputActionsContainer.Binding(options => options.NodesDistributeVertical, () => { DistributeNodes(SelectedNodes, true); }),
|
||||
new InputActionsContainer.Binding(options => options.FocusSelectedNodes, () => { FocusSelectionOrWholeGraph(); }),
|
||||
});
|
||||
|
||||
Context.ControlSpawned += OnSurfaceControlSpawned;
|
||||
@@ -436,7 +437,10 @@ namespace FlaxEditor.Surface
|
||||
DragHandlers.Add(_dragAssets = new DragAssets<DragDropEventArgs>(ValidateDragItem));
|
||||
DragHandlers.Add(_dragParameters = new DragNames<DragDropEventArgs>(SurfaceParameter.DragPrefix, ValidateDragParameter));
|
||||
|
||||
OnEditorOptionsChanged(Editor.Instance.Options.Options);
|
||||
|
||||
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
|
||||
Editor.Instance.Options.OptionsChanged += OnEditorOptionsChanged;
|
||||
}
|
||||
|
||||
private void OnScriptsReloadBegin()
|
||||
@@ -446,6 +450,11 @@ namespace FlaxEditor.Surface
|
||||
_cmPrimaryMenu = null;
|
||||
}
|
||||
|
||||
private void OnEditorOptionsChanged(EditorOptions options)
|
||||
{
|
||||
_focusSelectedNodeBinding = options.Input.FocusSelectedNodes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display name of the connection type used in the surface.
|
||||
/// </summary>
|
||||
@@ -648,6 +657,37 @@ namespace FlaxEditor.Surface
|
||||
ViewCenterPosition = areaRect.Center;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts the view to focus on the currently selected nodes, or the entire graph if no nodes are selected.
|
||||
/// </summary>
|
||||
public void FocusSelectionOrWholeGraph()
|
||||
{
|
||||
if (SelectedNodes.Count > 0)
|
||||
ShowSelection();
|
||||
else
|
||||
ShowWholeGraph();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the selected controls by changing the view scale and the position.
|
||||
/// </summary>
|
||||
public void ShowSelection()
|
||||
{
|
||||
var selection = SelectedControls;
|
||||
if (selection.Count == 0)
|
||||
return;
|
||||
|
||||
// Calculate the bounds of all selected controls
|
||||
Rectangle bounds = selection[0].Bounds;
|
||||
for (int i = 1; i < selection.Count; i++)
|
||||
bounds = Rectangle.Union(bounds, selection[i].Bounds);
|
||||
|
||||
// Add margin
|
||||
bounds = bounds.MakeExpanded(250.0f);
|
||||
|
||||
ShowArea(bounds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the given surface node by changing the view scale and the position and focuses the node.
|
||||
/// </summary>
|
||||
@@ -1071,6 +1111,7 @@ namespace FlaxEditor.Surface
|
||||
_cmPrimaryMenu?.Dispose();
|
||||
|
||||
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
|
||||
Editor.Instance.Options.OptionsChanged += OnEditorOptionsChanged;
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace FlaxEditor.Tools.Terrain
|
||||
if (!terrain.HasPatch(ref patchCoord) && _planeModel)
|
||||
{
|
||||
var planeSize = 100.0f;
|
||||
var patchSize = terrain.ChunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount;
|
||||
var patchSize = terrain.PatchSize;
|
||||
Matrix world = Matrix.RotationX(-Mathf.PiOverTwo) *
|
||||
Matrix.Scaling(patchSize / planeSize) *
|
||||
Matrix.Translation(patchSize * (0.5f + patchCoord.X), 0, patchSize * (0.5f + patchCoord.Y)) *
|
||||
|
||||
@@ -69,9 +69,9 @@ namespace FlaxEditor.Tools.Terrain.Paint
|
||||
var splatmapIndex = ActiveSplatmapIndex;
|
||||
var splatmapIndexOther = (splatmapIndex + 1) % 2;
|
||||
var chunkSize = terrain.ChunkSize;
|
||||
var heightmapSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1;
|
||||
var heightmapSize = terrain.HeightmapSize;
|
||||
var heightmapLength = heightmapSize * heightmapSize;
|
||||
var patchSize = chunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount;
|
||||
var patchSize = terrain.PatchSize;
|
||||
var tempBuffer = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes, splatmapIndex).ToPointer();
|
||||
var tempBufferOther = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes, (splatmapIndex + 1) % 2).ToPointer();
|
||||
var unitsPerVertexInv = 1.0f / FlaxEngine.Terrain.UnitsPerVertex;
|
||||
|
||||
@@ -70,9 +70,9 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
|
||||
|
||||
// Prepare
|
||||
var chunkSize = terrain.ChunkSize;
|
||||
var heightmapSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1;
|
||||
var heightmapSize = terrain.HeightmapSize;
|
||||
var heightmapLength = heightmapSize * heightmapSize;
|
||||
var patchSize = chunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount;
|
||||
var patchSize = terrain.PatchSize;
|
||||
var tempBuffer = (float*)gizmo.GetHeightmapTempBuffer(heightmapLength * sizeof(float)).ToPointer();
|
||||
var unitsPerVertexInv = 1.0f / FlaxEngine.Terrain.UnitsPerVertex;
|
||||
|
||||
|
||||
@@ -382,7 +382,8 @@ bool TerrainTools::ExportTerrain(Terrain* terrain, String outputFolder)
|
||||
const Int2 heightmapSize = size * Terrain::ChunksCountEdge * terrain->GetChunkSize() + 1;
|
||||
Array<float> heightmap;
|
||||
heightmap.Resize(heightmapSize.X * heightmapSize.Y);
|
||||
heightmap.SetAll(firstPatch->GetHeightmapData()[0]);
|
||||
if (const float* heightmapData = firstPatch->GetHeightmapData())
|
||||
heightmap.SetAll(heightmapData[0]);
|
||||
|
||||
// Fill heightmap with data from all patches
|
||||
const int32 rowSize = terrain->GetChunkSize() * Terrain::ChunksCountEdge + 1;
|
||||
@@ -392,8 +393,16 @@ bool TerrainTools::ExportTerrain(Terrain* terrain, String outputFolder)
|
||||
const Int2 pos(patch->GetX() - start.X, patch->GetZ() - start.Y);
|
||||
const float* src = patch->GetHeightmapData();
|
||||
float* dst = heightmap.Get() + pos.X * (rowSize - 1) + pos.Y * heightmapSize.X * (rowSize - 1);
|
||||
for (int32 row = 0; row < rowSize; row++)
|
||||
Platform::MemoryCopy(dst + row * heightmapSize.X, src + row * rowSize, rowSize * sizeof(float));
|
||||
if (src)
|
||||
{
|
||||
for (int32 row = 0; row < rowSize; row++)
|
||||
Platform::MemoryCopy(dst + row * heightmapSize.X, src + row * rowSize, rowSize * sizeof(float));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int32 row = 0; row < rowSize; row++)
|
||||
Platform::MemoryClear(dst + row * heightmapSize.X, rowSize * sizeof(float));
|
||||
}
|
||||
}
|
||||
|
||||
// Interpolate to 16-bit int
|
||||
|
||||
@@ -85,8 +85,7 @@ namespace FlaxEditor.Tools.Terrain.Undo
|
||||
{
|
||||
_terrain = terrain.ID;
|
||||
_patches = new List<PatchData>(4);
|
||||
var chunkSize = terrain.ChunkSize;
|
||||
var heightmapSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1;
|
||||
var heightmapSize = terrain.HeightmapSize;
|
||||
_heightmapLength = heightmapSize * heightmapSize;
|
||||
_heightmapDataSize = _heightmapLength * stride;
|
||||
|
||||
|
||||
@@ -1229,7 +1229,7 @@ namespace FlaxEditor.Viewport
|
||||
/// Orients the viewport.
|
||||
/// </summary>
|
||||
/// <param name="orientation">The orientation.</param>
|
||||
protected void OrientViewport(Quaternion orientation)
|
||||
public void OrientViewport(Quaternion orientation)
|
||||
{
|
||||
OrientViewport(ref orientation);
|
||||
}
|
||||
@@ -1238,7 +1238,7 @@ namespace FlaxEditor.Viewport
|
||||
/// Orients the viewport.
|
||||
/// </summary>
|
||||
/// <param name="orientation">The orientation.</param>
|
||||
protected virtual void OrientViewport(ref Quaternion orientation)
|
||||
public virtual void OrientViewport(ref Quaternion orientation)
|
||||
{
|
||||
if (ViewportCamera is FPSCamera fpsCamera)
|
||||
{
|
||||
|
||||
@@ -108,13 +108,14 @@ namespace FlaxEditor.Viewport
|
||||
private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32);
|
||||
private EditorSpritesRenderer _editorSpritesRenderer;
|
||||
private ViewportRubberBandSelector _rubberBandSelector;
|
||||
private DirectionGizmo _directionGizmo;
|
||||
|
||||
private bool _gameViewActive;
|
||||
private ViewFlags _preGameViewFlags;
|
||||
private ViewMode _preGameViewViewMode;
|
||||
private bool _gameViewWasGridShown;
|
||||
private bool _gameViewWasFpsCounterShown;
|
||||
private bool _gameViewWasNagivationShown;
|
||||
private bool _gameViewWasNavigationShown;
|
||||
|
||||
/// <summary>
|
||||
/// Drag and drop handlers
|
||||
@@ -225,6 +226,12 @@ namespace FlaxEditor.Viewport
|
||||
|
||||
// Add rubber band selector
|
||||
_rubberBandSelector = new ViewportRubberBandSelector(this);
|
||||
_directionGizmo = new DirectionGizmo(this);
|
||||
_directionGizmo.AnchorPreset = AnchorPresets.TopRight;
|
||||
_directionGizmo.Parent = this;
|
||||
_directionGizmo.LocalY += 25;
|
||||
_directionGizmo.LocalX -= 150;
|
||||
_directionGizmo.Size = new Float2(150, 150);
|
||||
|
||||
// Add grid
|
||||
Grid = new GridGizmo(this);
|
||||
@@ -244,6 +251,12 @@ namespace FlaxEditor.Viewport
|
||||
_showNavigationButton = ViewWidgetShowMenu.AddButton("Navigation", inputOptions.ToggleNavMeshVisibility, () => ShowNavigation = !ShowNavigation);
|
||||
_showNavigationButton.CloseMenuOnClick = false;
|
||||
|
||||
// Show direction gizmo widget
|
||||
var showDirectionGizmoButton = ViewWidgetShowMenu.AddButton("Direction Gizmo", () => _directionGizmo.Visible = !_directionGizmo.Visible);
|
||||
showDirectionGizmoButton.AutoCheck = true;
|
||||
showDirectionGizmoButton.CloseMenuOnClick = false;
|
||||
showDirectionGizmoButton.Checked = _directionGizmo.Visible;
|
||||
|
||||
// Game View
|
||||
ViewWidgetButtonMenu.AddSeparator();
|
||||
_toggleGameViewButton = ViewWidgetButtonMenu.AddButton("Game View", inputOptions.ToggleGameView, ToggleGameView);
|
||||
@@ -514,14 +527,14 @@ namespace FlaxEditor.Viewport
|
||||
_preGameViewViewMode = Task.ViewMode;
|
||||
_gameViewWasGridShown = Grid.Enabled;
|
||||
_gameViewWasFpsCounterShown = ShowFpsCounter;
|
||||
_gameViewWasNagivationShown = ShowNavigation;
|
||||
_gameViewWasNavigationShown = ShowNavigation;
|
||||
}
|
||||
|
||||
// Set flags & values
|
||||
Task.ViewFlags = _gameViewActive ? _preGameViewFlags : ViewFlags.DefaultGame;
|
||||
Task.ViewMode = _gameViewActive ? _preGameViewViewMode : ViewMode.Default;
|
||||
ShowFpsCounter = _gameViewActive ? _gameViewWasFpsCounterShown : false;
|
||||
ShowNavigation = _gameViewActive ? _gameViewWasNagivationShown : false;
|
||||
ShowNavigation = _gameViewActive ? _gameViewWasNavigationShown : false;
|
||||
Grid.Enabled = _gameViewActive ? _gameViewWasGridShown : false;
|
||||
|
||||
_gameViewActive = !_gameViewActive;
|
||||
@@ -647,7 +660,7 @@ namespace FlaxEditor.Viewport
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OrientViewport(ref Quaternion orientation)
|
||||
public override void OrientViewport(ref Quaternion orientation)
|
||||
{
|
||||
if (TransformGizmo.SelectedParents.Count != 0)
|
||||
FocusSelection(ref orientation);
|
||||
|
||||
@@ -681,7 +681,7 @@ namespace FlaxEditor.Viewport
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OrientViewport(ref Quaternion orientation)
|
||||
public override void OrientViewport(ref Quaternion orientation)
|
||||
{
|
||||
if (TransformGizmo.SelectedParents.Count != 0)
|
||||
FocusSelection(ref orientation);
|
||||
|
||||
@@ -303,8 +303,7 @@ namespace FlaxEditor.Viewport.Previews
|
||||
{
|
||||
_terrain = new Terrain();
|
||||
_terrain.Setup(1, 63);
|
||||
var chunkSize = _terrain.ChunkSize;
|
||||
var heightMapSize = chunkSize * Terrain.PatchEdgeChunksCount + 1;
|
||||
var heightMapSize = _terrain.HeightmapSize;
|
||||
var heightMapLength = heightMapSize * heightMapSize;
|
||||
var heightmap = new float[heightMapLength];
|
||||
var patchCoord = new Int2(0, 0);
|
||||
|
||||
@@ -431,6 +431,9 @@ namespace FlaxEditor.Windows.Assets
|
||||
_isWaitingForTimelineLoad = true;
|
||||
|
||||
base.OnItemReimported(item);
|
||||
|
||||
// Drop virtual asset state and get a new one from the reimported file
|
||||
LoadFromOriginal();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
{
|
||||
Parent = this
|
||||
};
|
||||
_toolstrip.AddButton(editor.Icons.Search64, () => Editor.Windows.ContentWin.Select(_item)).LinkTooltip("Show and select in Content Window");
|
||||
_toolstrip.AddButton(editor.Icons.Search64, () => Editor.Windows.ContentWin.Select(_item)).LinkTooltip("Show and select in Content Window.");
|
||||
|
||||
InputActions.Add(options => options.Save, Save);
|
||||
|
||||
@@ -527,6 +527,16 @@ namespace FlaxEditor.Windows.Assets
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the asset from the original location to reflect the state (eg. after original asset reimport).
|
||||
/// </summary>
|
||||
protected virtual void LoadFromOriginal()
|
||||
{
|
||||
_asset = LoadAsset();
|
||||
OnAssetLoaded();
|
||||
ClearEditedFlag();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override T LoadAsset()
|
||||
{
|
||||
|
||||
@@ -115,6 +115,7 @@ namespace FlaxEditor.Windows
|
||||
|
||||
var root = _root;
|
||||
root.LockChildrenRecursive();
|
||||
PerformLayout();
|
||||
|
||||
// Update tree
|
||||
var query = _foldersSearchBox.Text;
|
||||
|
||||
@@ -1126,6 +1126,8 @@ namespace FlaxEditor.Windows
|
||||
if (Editor.ContentDatabase.Find(_lastViewedFolderBeforeReload) is ContentFolder folder)
|
||||
_tree.Select(folder.Node);
|
||||
}
|
||||
|
||||
OnFoldersSearchBoxTextChanged();
|
||||
}
|
||||
|
||||
private void Refresh()
|
||||
|
||||
@@ -67,6 +67,7 @@ namespace FlaxEditor.Windows
|
||||
TooltipText = "Search the scene tree.\n\nYou can prefix your search with different search operators:\ns: -> Actor with script of type\na: -> Actor type\nc: -> Control type",
|
||||
};
|
||||
_searchBox.TextChanged += OnSearchBoxTextChanged;
|
||||
ScriptsBuilder.ScriptsReloadEnd += OnSearchBoxTextChanged;
|
||||
|
||||
// Scene tree panel
|
||||
_sceneTreePanel = new Panel
|
||||
@@ -111,7 +112,7 @@ namespace FlaxEditor.Windows
|
||||
InputActions.Add(options => options.LockFocusSelection, () => Editor.Windows.EditWin.Viewport.LockFocusSelection());
|
||||
InputActions.Add(options => options.Rename, RenameSelection);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnPlayBeginning()
|
||||
{
|
||||
@@ -124,6 +125,7 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
base.OnPlayBegin();
|
||||
_blockSceneTreeScroll = false;
|
||||
OnSearchBoxTextChanged();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -138,6 +140,7 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
base.OnPlayEnd();
|
||||
_blockSceneTreeScroll = true;
|
||||
OnSearchBoxTextChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -173,6 +176,7 @@ namespace FlaxEditor.Windows
|
||||
return;
|
||||
|
||||
_tree.LockChildrenRecursive();
|
||||
PerformLayout();
|
||||
|
||||
// Update tree
|
||||
var query = _searchBox.Text;
|
||||
@@ -586,6 +590,7 @@ namespace FlaxEditor.Windows
|
||||
_dragHandlers = null;
|
||||
_tree = null;
|
||||
_searchBox = null;
|
||||
ScriptsBuilder.ScriptsReloadEnd -= OnSearchBoxTextChanged;
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include "Loading/Tasks/LoadAssetTask.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/LogContext.h"
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
#include "Engine/Physics/Physics.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Profiler/ProfilerMemory.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MCore.h"
|
||||
@@ -703,6 +705,38 @@ void Asset::onUnload_MainThread()
|
||||
OnUnloaded(this);
|
||||
}
|
||||
|
||||
bool Asset::WaitForInitGraphics()
|
||||
{
|
||||
#define IS_GPU_NOT_READY() (GPUDevice::Instance == nullptr || GPUDevice::Instance->GetState() != GPUDevice::DeviceState::Ready)
|
||||
if (!IsInMainThread() && IS_GPU_NOT_READY())
|
||||
{
|
||||
PROFILE_CPU();
|
||||
ZoneColor(TracyWaitZoneColor);
|
||||
int32 timeout = 1000;
|
||||
while (IS_GPU_NOT_READY() && timeout-- > 0)
|
||||
Platform::Sleep(1);
|
||||
if (IS_GPU_NOT_READY())
|
||||
return true;
|
||||
}
|
||||
#undef IS_GPU_NOT_READY
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Asset::WaitForInitPhysics()
|
||||
{
|
||||
if (!IsInMainThread() && !Physics::DefaultScene)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
ZoneColor(TracyWaitZoneColor);
|
||||
int32 timeout = 1000;
|
||||
while (!Physics::DefaultScene && timeout-- > 0)
|
||||
Platform::Sleep(1);
|
||||
if (!Physics::DefaultScene)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
bool Asset::OnCheckSave(const StringView& path) const
|
||||
|
||||
@@ -285,6 +285,10 @@ protected:
|
||||
virtual void onRename(const StringView& newPath) = 0;
|
||||
#endif
|
||||
|
||||
// Utilities to ensure specific engine systems are initialized before loading asset (eg. assets can be loaded during engine startup).
|
||||
static bool WaitForInitGraphics();
|
||||
static bool WaitForInitPhysics();
|
||||
|
||||
public:
|
||||
// [ManagedScriptingObject]
|
||||
String ToString() const override;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "Engine/Content/Deprecated.h"
|
||||
#include "Engine/Content/Upgraders/ShaderAssetUpgrader.h"
|
||||
#include "Engine/Content/Factories/BinaryAssetFactory.h"
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
#include "Engine/Graphics/RenderTools.h"
|
||||
#include "Engine/Graphics/Materials/MaterialShader.h"
|
||||
#include "Engine/Graphics/Shaders/Cache/ShaderCacheManager.h"
|
||||
@@ -157,16 +156,8 @@ Asset::LoadResult Material::load()
|
||||
FlaxChunk* materialParamsChunk;
|
||||
|
||||
// Wait for the GPU Device to be ready (eg. case when loading material before GPU init)
|
||||
#define IS_GPU_NOT_READY() (GPUDevice::Instance == nullptr || GPUDevice::Instance->GetState() != GPUDevice::DeviceState::Ready)
|
||||
if (!IsInMainThread() && IS_GPU_NOT_READY())
|
||||
{
|
||||
int32 timeout = 1000;
|
||||
while (IS_GPU_NOT_READY() && timeout-- > 0)
|
||||
Platform::Sleep(1);
|
||||
if (IS_GPU_NOT_READY())
|
||||
return LoadResult::InvalidData;
|
||||
}
|
||||
#undef IS_GPU_NOT_READY
|
||||
if (WaitForInitGraphics())
|
||||
return LoadResult::CannotLoadData;
|
||||
|
||||
// If engine was compiled with shaders compiling service:
|
||||
// - Material should be changed in need to convert it to the newer version (via Visject Surface)
|
||||
|
||||
@@ -19,10 +19,10 @@ Variant MaterialBase::GetParameterValue(const StringView& name)
|
||||
if (!IsLoaded() && WaitForLoaded())
|
||||
return Variant::Null;
|
||||
const auto param = Params.Get(name);
|
||||
if (IsMaterialInstance() && param && !param->IsOverride() && ((MaterialInstance*)this)->GetBaseMaterial())
|
||||
return ((MaterialInstance*)this)->GetBaseMaterial()->GetParameterValue(name);
|
||||
if (param)
|
||||
{
|
||||
return param->GetValue();
|
||||
}
|
||||
LOG(Warning, "Missing material parameter '{0}' in material {1}", String(name), ToString());
|
||||
return Variant::Null;
|
||||
}
|
||||
|
||||
@@ -57,6 +57,8 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the material parameter value.
|
||||
/// </summary>
|
||||
/// <remarks>For material instances that inherit a base material, returned value might come from base material if the current one doesn't override it.</remarks>
|
||||
/// <param name="name">The parameter name.</param>
|
||||
/// <returns>The parameter value.</returns>
|
||||
API_FUNCTION() Variant GetParameterValue(const StringView& name);
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ namespace
|
||||
|
||||
ContentStorageService ContentStorageServiceInstance;
|
||||
|
||||
TimeSpan ContentStorageManager::UnusedStorageLifetime = TimeSpan::FromSeconds(0.5f);
|
||||
TimeSpan ContentStorageManager::UnusedDataChunksLifetime = TimeSpan::FromSeconds(10);
|
||||
|
||||
FlaxStorageReference ContentStorageManager::GetStorage(const StringView& path, bool loadIt)
|
||||
|
||||
@@ -15,7 +15,12 @@ class FLAXENGINE_API ContentStorageManager
|
||||
{
|
||||
public:
|
||||
/// <summary>
|
||||
/// Auto-release timeout for unused asset chunks.
|
||||
/// Auto-release timeout for unused asset files.
|
||||
/// </summary>
|
||||
static TimeSpan UnusedStorageLifetime;
|
||||
|
||||
/// <summary>
|
||||
/// Auto-release timeout for unused asset data chunks.
|
||||
/// </summary>
|
||||
static TimeSpan UnusedDataChunksLifetime;
|
||||
|
||||
|
||||
@@ -286,14 +286,14 @@ FlaxStorage::LockData FlaxStorage::LockSafe()
|
||||
|
||||
uint32 FlaxStorage::GetRefCount() const
|
||||
{
|
||||
return (uint32)Platform::AtomicRead((intptr*)&_refCount);
|
||||
return (uint32)Platform::AtomicRead(&_refCount);
|
||||
}
|
||||
|
||||
bool FlaxStorage::ShouldDispose() const
|
||||
{
|
||||
return Platform::AtomicRead((intptr*)&_refCount) == 0 &&
|
||||
Platform::AtomicRead((intptr*)&_chunksLock) == 0 &&
|
||||
Platform::GetTimeSeconds() - _lastRefLostTime >= 0.5; // TTL in seconds
|
||||
return Platform::AtomicRead(&_refCount) == 0 &&
|
||||
Platform::AtomicRead(&_chunksLock) == 0 &&
|
||||
Platform::GetTimeSeconds() - _lastRefLostTime >= ContentStorageManager::UnusedStorageLifetime.GetTotalSeconds();
|
||||
}
|
||||
|
||||
uint32 FlaxStorage::GetMemoryUsage() const
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "Engine/Animations/AnimEvent.h"
|
||||
#include "Engine/Level/Actors/EmptyActor.h"
|
||||
#include "Engine/Level/Actors/StaticModel.h"
|
||||
#include "Engine/Level/Actors/AnimatedModel.h"
|
||||
#include "Engine/Level/Prefabs/Prefab.h"
|
||||
#include "Engine/Level/Prefabs/PrefabManager.h"
|
||||
#include "Engine/Level/Scripts/ModelPrefab.h"
|
||||
@@ -82,6 +83,11 @@ bool ImportModel::TryGetImportOptions(const StringView& path, Options& options)
|
||||
|
||||
struct PrefabObject
|
||||
{
|
||||
enum
|
||||
{
|
||||
Model,
|
||||
SkinnedModel,
|
||||
} Type;
|
||||
int32 NodeIndex;
|
||||
String Name;
|
||||
String AssetPath;
|
||||
@@ -280,7 +286,7 @@ CreateAssetResult ImportModel::Import(CreateAssetContext& context)
|
||||
options.SplitObjects = false;
|
||||
options.ObjectIndex = -1;
|
||||
|
||||
// Import all of the objects recursive but use current model data to skip loading file again
|
||||
// Import all the objects recursive but use current model data to skip loading file again
|
||||
options.Cached = &cached;
|
||||
HashSet<String> objectNames;
|
||||
Function<bool(Options& splitOptions, const StringView& objectName, String& outputPath, MeshData* meshData)> splitImport = [&context, &autoImportOutput, &objectNames](Options& splitOptions, const StringView& objectName, String& outputPath, MeshData* meshData)
|
||||
@@ -335,12 +341,24 @@ CreateAssetResult ImportModel::Import(CreateAssetContext& context)
|
||||
auto& group = meshesByName[groupIndex];
|
||||
|
||||
// Cache object options (nested sub-object import removes the meshes)
|
||||
prefabObject.NodeIndex = group.First()->NodeIndex;
|
||||
prefabObject.Name = group.First()->Name;
|
||||
MeshData* firstMesh = group.First();
|
||||
prefabObject.NodeIndex = firstMesh->NodeIndex;
|
||||
prefabObject.Name = firstMesh->Name;
|
||||
|
||||
splitOptions.Type = ModelTool::ModelType::Model;
|
||||
// Detect model type
|
||||
if ((firstMesh->BlendIndices.HasItems() && firstMesh->BlendWeights.HasItems()) || firstMesh->BlendShapes.HasItems())
|
||||
{
|
||||
splitOptions.Type = ModelTool::ModelType::SkinnedModel;
|
||||
prefabObject.Type = PrefabObject::SkinnedModel;
|
||||
}
|
||||
else
|
||||
{
|
||||
splitOptions.Type = ModelTool::ModelType::Model;
|
||||
prefabObject.Type = PrefabObject::Model;
|
||||
}
|
||||
|
||||
splitOptions.ObjectIndex = groupIndex;
|
||||
if (!splitImport(splitOptions, group.GetKey(), prefabObject.AssetPath, group.First()))
|
||||
if (!splitImport(splitOptions, group.GetKey(), prefabObject.AssetPath, firstMesh))
|
||||
{
|
||||
prefabObjects.Add(prefabObject);
|
||||
}
|
||||
@@ -734,24 +752,38 @@ CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, const M
|
||||
nodeActors.Clear();
|
||||
for (const PrefabObject& e : prefabObjects)
|
||||
{
|
||||
if (e.NodeIndex == nodeIndex)
|
||||
if (e.NodeIndex != nodeIndex)
|
||||
continue;
|
||||
Actor* a = nullptr;
|
||||
switch (e.Type)
|
||||
{
|
||||
case PrefabObject::Model:
|
||||
{
|
||||
auto* actor = New<StaticModel>();
|
||||
actor->SetName(e.Name);
|
||||
if (auto* model = Content::LoadAsync<Model>(e.AssetPath))
|
||||
{
|
||||
actor->Model = model;
|
||||
}
|
||||
nodeActors.Add(actor);
|
||||
a = actor;
|
||||
break;
|
||||
}
|
||||
case PrefabObject::SkinnedModel:
|
||||
{
|
||||
auto* actor = New<AnimatedModel>();
|
||||
if (auto* skinnedModel = Content::LoadAsync<SkinnedModel>(e.AssetPath))
|
||||
actor->SkinnedModel = skinnedModel;
|
||||
a = actor;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
a->SetName(e.Name);
|
||||
nodeActors.Add(a);
|
||||
}
|
||||
Actor* nodeActor = nodeActors.Count() == 1 ? nodeActors[0] : New<EmptyActor>();
|
||||
if (nodeActors.Count() > 1)
|
||||
{
|
||||
for (Actor* e : nodeActors)
|
||||
{
|
||||
e->SetParent(nodeActor);
|
||||
}
|
||||
}
|
||||
if (nodeActors.Count() != 1)
|
||||
{
|
||||
|
||||
@@ -155,6 +155,7 @@ void Screen::SetCursorLock(CursorLockMode mode)
|
||||
bool inRelativeMode = Input::Mouse->IsRelative();
|
||||
if (mode == CursorLockMode::Clipped)
|
||||
win->StartClippingCursor(bounds);
|
||||
#if PLATFORM_SDL
|
||||
else if (mode == CursorLockMode::Locked)
|
||||
{
|
||||
// Use mouse clip region to restrict the cursor in one spot
|
||||
@@ -162,6 +163,10 @@ void Screen::SetCursorLock(CursorLockMode mode)
|
||||
}
|
||||
else if (CursorLock == CursorLockMode::Locked || CursorLock == CursorLockMode::Clipped)
|
||||
win->EndClippingCursor();
|
||||
#else
|
||||
else if (CursorLock == CursorLockMode::Clipped)
|
||||
win->EndClippingCursor();
|
||||
#endif
|
||||
|
||||
// Enable relative mode when cursor is restricted
|
||||
if (mode != CursorLockMode::None)
|
||||
|
||||
@@ -88,9 +88,8 @@ void TerrainMaterialShader::Bind(BindParameters& params)
|
||||
}
|
||||
|
||||
// Bind terrain textures
|
||||
const auto heightmap = drawCall.Terrain.Patch->Heightmap->GetTexture();
|
||||
const auto splatmap0 = drawCall.Terrain.Patch->Splatmap[0] ? drawCall.Terrain.Patch->Splatmap[0]->GetTexture() : nullptr;
|
||||
const auto splatmap1 = drawCall.Terrain.Patch->Splatmap[1] ? drawCall.Terrain.Patch->Splatmap[1]->GetTexture() : nullptr;
|
||||
GPUTexture* heightmap, *splatmap0, *splatmap1;
|
||||
drawCall.Terrain.Patch->GetTextures(heightmap, splatmap0, splatmap1);
|
||||
context->BindSR(0, heightmap);
|
||||
context->BindSR(1, splatmap0);
|
||||
context->BindSR(2, splatmap1);
|
||||
|
||||
@@ -936,7 +936,9 @@ void InputService::Update()
|
||||
break;
|
||||
}
|
||||
}
|
||||
#if PLATFORM_SDL
|
||||
WindowsManager::WindowsLocker.Unlock();
|
||||
#endif
|
||||
|
||||
// Send input events for the focused window
|
||||
for (const auto& e : InputEvents)
|
||||
@@ -990,6 +992,9 @@ void InputService::Update()
|
||||
break;
|
||||
}
|
||||
}
|
||||
#if !PLATFORM_SDL
|
||||
WindowsManager::WindowsLocker.Unlock();
|
||||
#endif
|
||||
|
||||
// Skip if game has no focus to handle the input
|
||||
if (!Engine::HasGameViewportFocus())
|
||||
|
||||
@@ -448,8 +448,7 @@ void SceneObjectsFactory::PrefabSyncData::InitNewObjects()
|
||||
void SceneObjectsFactory::SetupPrefabInstances(Context& context, const PrefabSyncData& data)
|
||||
{
|
||||
PROFILE_CPU_NAMED("SetupPrefabInstances");
|
||||
const int32 count = data.Data.Size();
|
||||
ASSERT(count <= data.SceneObjects.Count());
|
||||
const int32 count = Math::Min<int32>(data.Data.Size(), data.SceneObjects.Count());
|
||||
Dictionary<Guid, Guid> parentIdsLookup;
|
||||
for (int32 i = 0; i < count; i++)
|
||||
{
|
||||
|
||||
@@ -209,6 +209,13 @@ void Collider::CreateShape()
|
||||
// Create shape
|
||||
const bool isTrigger = _isTrigger && CanBeTrigger();
|
||||
_shape = PhysicsBackend::CreateShape(this, shape, Material, IsActiveInHierarchy(), isTrigger);
|
||||
if (!_shape)
|
||||
{
|
||||
LOG(Error, "Failed to create physics shape for actor '{}'", GetNamePath());
|
||||
if (shape.Type == CollisionShape::Types::ConvexMesh && Float3(shape.ConvexMesh.Scale).MinValue() <= 0)
|
||||
LOG(Warning, "Convex Mesh colliders cannot have negative scale");
|
||||
return;
|
||||
}
|
||||
PhysicsBackend::SetShapeContactOffset(_shape, _contactOffset);
|
||||
UpdateLayerBits();
|
||||
}
|
||||
@@ -293,18 +300,20 @@ void Collider::BeginPlay(SceneBeginData* data)
|
||||
if (_shape == nullptr)
|
||||
{
|
||||
CreateShape();
|
||||
|
||||
// Check if parent is a rigidbody
|
||||
const auto rigidBody = dynamic_cast<RigidBody*>(GetParent());
|
||||
if (rigidBody && CanAttach(rigidBody))
|
||||
if (_shape)
|
||||
{
|
||||
// Attach to the rigidbody
|
||||
Attach(rigidBody);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Be a static collider
|
||||
CreateStaticActor();
|
||||
// Check if parent is a rigidbody
|
||||
const auto rigidBody = dynamic_cast<RigidBody*>(GetParent());
|
||||
if (rigidBody && CanAttach(rigidBody))
|
||||
{
|
||||
// Attach to the rigidbody
|
||||
Attach(rigidBody);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Be a static collider
|
||||
CreateStaticActor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -257,6 +257,8 @@ Asset::LoadResult CollisionData::load()
|
||||
|
||||
CollisionData::LoadResult CollisionData::load(const SerializedOptions* options, byte* dataPtr, int32 dataSize)
|
||||
{
|
||||
if (WaitForInitPhysics())
|
||||
return LoadResult::CannotLoadData;
|
||||
PROFILE_MEM(Physics);
|
||||
|
||||
// Load options
|
||||
|
||||
@@ -1204,6 +1204,8 @@ void ScenePhysX::PreSimulateCloth(int32 i)
|
||||
PROFILE_MEM(PhysicsCloth);
|
||||
auto clothPhysX = ClothsList[i];
|
||||
auto& clothSettings = Cloths[clothPhysX];
|
||||
if (!clothSettings.Actor)
|
||||
return;
|
||||
|
||||
if (clothSettings.Actor->OnPreUpdate())
|
||||
{
|
||||
@@ -2686,10 +2688,13 @@ void* PhysicsBackend::CreateShape(PhysicsColliderActor* collider, const Collisio
|
||||
PxGeometryHolder geometryPhysX;
|
||||
GetShapeGeometry(geometry, geometryPhysX);
|
||||
PxShape* shapePhysX = PhysX->createShape(geometryPhysX.any(), materialsPhysX.Get(), materialsPhysX.Count(), true, shapeFlags);
|
||||
shapePhysX->userData = collider;
|
||||
if (shapePhysX)
|
||||
{
|
||||
shapePhysX->userData = collider;
|
||||
#if PHYSX_DEBUG_NAMING
|
||||
shapePhysX->setName("Shape");
|
||||
shapePhysX->setName("Shape");
|
||||
#endif
|
||||
}
|
||||
return shapePhysX;
|
||||
}
|
||||
|
||||
|
||||
@@ -178,6 +178,27 @@ void RenderAntiAliasingPass(RenderContext& renderContext, GPUTexture* input, GPU
|
||||
}
|
||||
}
|
||||
|
||||
void RenderLightBuffer(const SceneRenderTask* task, GPUContext* context, RenderContext& renderContext, GPUTexture* lightBuffer, const GPUTextureDescription& tempDesc)
|
||||
{
|
||||
context->ResetRenderTarget();
|
||||
auto colorGradingLUT = ColorGradingPass::Instance()->RenderLUT(renderContext);
|
||||
auto tempBuffer = RenderTargetPool::Get(tempDesc);
|
||||
RENDER_TARGET_POOL_SET_NAME(tempBuffer, "TempBuffer");
|
||||
EyeAdaptationPass::Instance()->Render(renderContext, lightBuffer);
|
||||
PostProcessingPass::Instance()->Render(renderContext, lightBuffer, tempBuffer, colorGradingLUT);
|
||||
context->ResetRenderTarget();
|
||||
if (renderContext.List->Settings.AntiAliasing.Mode == AntialiasingMode::TemporalAntialiasing)
|
||||
{
|
||||
TAA::Instance()->Render(renderContext, tempBuffer, lightBuffer->View());
|
||||
Swap(lightBuffer, tempBuffer);
|
||||
}
|
||||
RenderTargetPool::Release(lightBuffer);
|
||||
context->SetRenderTarget(task->GetOutputView());
|
||||
context->SetViewportAndScissors(task->GetOutputViewport());
|
||||
context->Draw(tempBuffer);
|
||||
RenderTargetPool::Release(tempBuffer);
|
||||
}
|
||||
|
||||
bool Renderer::IsReady()
|
||||
{
|
||||
// Warm up first (state getters initialize content loading so do it for all first)
|
||||
@@ -350,10 +371,12 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
|
||||
// Perform postFx volumes blending and query before rendering
|
||||
task->CollectPostFxVolumes(renderContext);
|
||||
renderContext.List->BlendSettings();
|
||||
auto aaMode = EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::AntiAliasing) ? renderContext.List->Settings.AntiAliasing.Mode : AntialiasingMode::None;
|
||||
if (aaMode == AntialiasingMode::TemporalAntialiasing && view.IsOrthographicProjection())
|
||||
aaMode = AntialiasingMode::None; // TODO: support TAA in ortho projection (see RenderView::Prepare to jitter projection matrix better)
|
||||
renderContext.List->Settings.AntiAliasing.Mode = aaMode;
|
||||
{
|
||||
auto aaMode = EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::AntiAliasing) ? renderContext.List->Settings.AntiAliasing.Mode : AntialiasingMode::None;
|
||||
if (aaMode == AntialiasingMode::TemporalAntialiasing && view.IsOrthographicProjection())
|
||||
aaMode = AntialiasingMode::None; // TODO: support TAA in ortho projection (see RenderView::Prepare to jitter projection matrix better)
|
||||
renderContext.List->Settings.AntiAliasing.Mode = aaMode;
|
||||
}
|
||||
|
||||
// Initialize setup
|
||||
RenderSetup& setup = renderContext.List->Setup;
|
||||
@@ -375,7 +398,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
|
||||
(ssrSettings.Intensity > ZeroTolerance && ssrSettings.TemporalEffect && EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::SSR)) ||
|
||||
renderContext.List->Settings.AntiAliasing.Mode == AntialiasingMode::TemporalAntialiasing;
|
||||
}
|
||||
setup.UseTemporalAAJitter = aaMode == AntialiasingMode::TemporalAntialiasing;
|
||||
setup.UseTemporalAAJitter = renderContext.List->Settings.AntiAliasing.Mode == AntialiasingMode::TemporalAntialiasing;
|
||||
setup.UseGlobalSurfaceAtlas = renderContext.View.Mode == ViewMode::GlobalSurfaceAtlas ||
|
||||
(EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::GI) && renderContext.List->Settings.GlobalIllumination.Mode == GlobalIlluminationMode::DDGI);
|
||||
setup.UseGlobalSDF = (graphicsSettings->EnableGlobalSDF && EnumHasAnyFlags(view.Flags, ViewFlags::GlobalSDF)) ||
|
||||
@@ -630,22 +653,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
|
||||
}
|
||||
if (renderContext.View.Mode == ViewMode::LightBuffer)
|
||||
{
|
||||
auto colorGradingLUT = ColorGradingPass::Instance()->RenderLUT(renderContext);
|
||||
auto tempBuffer = RenderTargetPool::Get(tempDesc);
|
||||
RENDER_TARGET_POOL_SET_NAME(tempBuffer, "TempBuffer");
|
||||
EyeAdaptationPass::Instance()->Render(renderContext, lightBuffer);
|
||||
PostProcessingPass::Instance()->Render(renderContext, lightBuffer, tempBuffer, colorGradingLUT);
|
||||
context->ResetRenderTarget();
|
||||
if (aaMode == AntialiasingMode::TemporalAntialiasing)
|
||||
{
|
||||
TAA::Instance()->Render(renderContext, tempBuffer, lightBuffer->View());
|
||||
Swap(lightBuffer, tempBuffer);
|
||||
}
|
||||
RenderTargetPool::Release(lightBuffer);
|
||||
context->SetRenderTarget(task->GetOutputView());
|
||||
context->SetViewportAndScissors(task->GetOutputViewport());
|
||||
context->Draw(tempBuffer);
|
||||
RenderTargetPool::Release(tempBuffer);
|
||||
RenderLightBuffer(task, context, renderContext, lightBuffer, tempDesc);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -656,11 +664,13 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
|
||||
ReflectionsPass::Instance()->Render(renderContext, *lightBuffer);
|
||||
if (renderContext.View.Mode == ViewMode::Reflections)
|
||||
{
|
||||
context->ResetRenderTarget();
|
||||
context->SetRenderTarget(task->GetOutputView());
|
||||
context->SetViewportAndScissors(task->GetOutputViewport());
|
||||
context->Draw(lightBuffer);
|
||||
RenderTargetPool::Release(lightBuffer);
|
||||
renderContext.List->Settings.ToneMapping.Mode = ToneMappingMode::Neutral;
|
||||
renderContext.List->Settings.Bloom.Enabled = false;
|
||||
renderContext.List->Settings.LensFlares.Intensity = 0.0f;
|
||||
renderContext.List->Settings.CameraArtifacts.GrainAmount = 0.0f;
|
||||
renderContext.List->Settings.CameraArtifacts.ChromaticDistortion = 0.0f;
|
||||
renderContext.List->Settings.CameraArtifacts.VignetteIntensity = 0.0f;
|
||||
RenderLightBuffer(task, context, renderContext, lightBuffer, tempDesc);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -716,7 +726,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
|
||||
renderContext.List->RunCustomPostFxPass(context, renderContext, PostProcessEffectLocation::BeforePostProcessingPass, frameBuffer, tempBuffer);
|
||||
|
||||
// Temporal Anti-Aliasing (goes before post processing)
|
||||
if (aaMode == AntialiasingMode::TemporalAntialiasing)
|
||||
if (renderContext.List->Settings.AntiAliasing.Mode == AntialiasingMode::TemporalAntialiasing)
|
||||
{
|
||||
TAA::Instance()->Render(renderContext, frameBuffer, tempBuffer->View());
|
||||
Swap(frameBuffer, tempBuffer);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#define RAPIDJSON_NEW(x) New<x>
|
||||
#define RAPIDJSON_DELETE(x) Delete(x)
|
||||
#define RAPIDJSON_NOMEMBERITERATORCLASS
|
||||
#define RAPIDJSON_PARSE_DEFAULT_FLAGS kParseTrailingCommasFlag
|
||||
//#define RAPIDJSON_MALLOC(size) ::malloc(size)
|
||||
//#define RAPIDJSON_REALLOC(ptr, new_size) ::realloc(ptr, new_size)
|
||||
//#define RAPIDJSON_FREE(ptr) ::free(ptr)
|
||||
|
||||
@@ -8,13 +8,22 @@ using Flax.Build.NativeCpp;
|
||||
/// </summary>
|
||||
public class Terrain : EngineModule
|
||||
{
|
||||
/// <summary>
|
||||
/// Enables terrain editing and changing at runtime. If your game doesn't use procedural terrain in game then disable this option to reduce build size.
|
||||
/// </summary>
|
||||
public static bool WithEditing = true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Setup(BuildOptions options)
|
||||
{
|
||||
base.Setup(options);
|
||||
|
||||
options.PrivateDependencies.Add("Physics");
|
||||
if (!WithEditing)
|
||||
{
|
||||
options.PublicDefinitions.Add("TERRAIN_EDITING=0");
|
||||
}
|
||||
|
||||
options.PrivateDependencies.Add("Physics");
|
||||
if (options.Target.IsEditor)
|
||||
{
|
||||
options.PrivateDependencies.Add("ContentImporters");
|
||||
|
||||
@@ -306,6 +306,16 @@ void Terrain::SetPhysicalMaterials(const Array<JsonAssetReference<PhysicalMateri
|
||||
}
|
||||
}
|
||||
|
||||
int32 Terrain::GetHeightmapSize() const
|
||||
{
|
||||
return GetChunkSize() * ChunksCountEdge + 1;
|
||||
}
|
||||
|
||||
float Terrain::GetPatchSize() const
|
||||
{
|
||||
return TERRAIN_UNITS_PER_VERTEX * ChunksCountEdge * GetChunkSize();
|
||||
}
|
||||
|
||||
TerrainPatch* Terrain::GetPatch(const Int2& patchCoord) const
|
||||
{
|
||||
return GetPatch(patchCoord.X, patchCoord.Y);
|
||||
|
||||
@@ -21,11 +21,14 @@ struct RenderView;
|
||||
// Amount of units per terrain geometry vertex (can be adjusted per terrain instance using non-uniform scale factor)
|
||||
#define TERRAIN_UNITS_PER_VERTEX 100.0f
|
||||
|
||||
// Enable/disable terrain editing and changing at runtime. If your game doesn't use procedural terrain in game then disable this option to reduce build size.
|
||||
#ifndef TERRAIN_EDITING
|
||||
// Enables terrain editing and changing at runtime. If your game doesn't use procedural terrain in game then disable this option to reduce build size.
|
||||
#define TERRAIN_EDITING 1
|
||||
#endif
|
||||
|
||||
// Enable/disable terrain heightmap samples modification and gather. Used by the editor to modify the terrain with the brushes.
|
||||
#define TERRAIN_UPDATING 1
|
||||
// [Deprecated in 1.12, use TERRAIN_EDITING instead]
|
||||
#define TERRAIN_UPDATING (TERRAIN_EDITING)
|
||||
|
||||
// Enable/disable terrain physics collision drawing
|
||||
#define TERRAIN_USE_PHYSICS_DEBUG (USE_EDITOR && 1)
|
||||
@@ -240,6 +243,18 @@ public:
|
||||
return static_cast<int32>(_chunkSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the heightmap texture size (square) used by a single patch (shared by all chunks within that patch).
|
||||
/// </summary>
|
||||
/// <remarks>ChunkSize * ChunksCountEdge + 1</remarks>
|
||||
API_PROPERTY() int32 GetHeightmapSize() const;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of the patch in world-units (square) without actor scale.
|
||||
/// </summary>
|
||||
/// <remarks>UnitsPerVertex * ChunksCountEdge * ChunkSize</remarks>
|
||||
API_PROPERTY() float GetPatchSize() const;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the terrain patches count. Each patch contains 16 chunks arranged into a 4x4 square.
|
||||
/// </summary>
|
||||
@@ -329,7 +344,6 @@ public:
|
||||
API_FUNCTION() void SetChunkOverrideMaterial(API_PARAM(Ref) const Int2& patchCoord, API_PARAM(Ref) const Int2& chunkCoord, MaterialBase* value);
|
||||
|
||||
#if TERRAIN_EDITING
|
||||
|
||||
/// <summary>
|
||||
/// Setups the terrain patch using the specified heightmap data.
|
||||
/// </summary>
|
||||
@@ -352,10 +366,6 @@ public:
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
API_FUNCTION() bool SetupPatchSplatMap(API_PARAM(Ref) const Int2& patchCoord, int32 index, int32 splatMapLength, const Color32* splatMap, bool forceUseVirtualStorage = false);
|
||||
|
||||
#endif
|
||||
|
||||
public:
|
||||
#if TERRAIN_EDITING
|
||||
/// <summary>
|
||||
/// Setups the terrain. Clears the existing data.
|
||||
/// </summary>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "Engine/Threading/Threading.h"
|
||||
#if TERRAIN_EDITING
|
||||
#include "Engine/Core/Math/Packed.h"
|
||||
#include "Engine/Core/Collections/ArrayExtensions.h"
|
||||
#include "Engine/Graphics/PixelFormatExtensions.h"
|
||||
#include "Engine/Graphics/RenderTools.h"
|
||||
#include "Engine/Graphics/RenderView.h"
|
||||
@@ -27,11 +28,6 @@
|
||||
#include "Editor/Editor.h"
|
||||
#include "Engine/ContentImporters/AssetsImportingManager.h"
|
||||
#endif
|
||||
#endif
|
||||
#if TERRAIN_EDITING || TERRAIN_UPDATING
|
||||
#include "Engine/Core/Collections/ArrayExtensions.h"
|
||||
#endif
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Debug/DebugDraw.h"
|
||||
#endif
|
||||
#if TERRAIN_USE_PHYSICS_DEBUG
|
||||
@@ -90,7 +86,7 @@ void TerrainPatch::Init(Terrain* terrain, int16 x, int16 z)
|
||||
Splatmap[i] = nullptr;
|
||||
}
|
||||
_heightfield = nullptr;
|
||||
#if TERRAIN_UPDATING
|
||||
#if TERRAIN_EDITING
|
||||
_cachedHeightMap.Resize(0);
|
||||
_cachedHolesMask.Resize(0);
|
||||
_wasHeightModified = false;
|
||||
@@ -114,7 +110,7 @@ void TerrainPatch::Init(Terrain* terrain, int16 x, int16 z)
|
||||
|
||||
TerrainPatch::~TerrainPatch()
|
||||
{
|
||||
#if TERRAIN_UPDATING
|
||||
#if TERRAIN_EDITING
|
||||
SAFE_DELETE(_dataHeightmap);
|
||||
for (int32 i = 0; i < TERRAIN_MAX_SPLATMAPS_COUNT; i++)
|
||||
{
|
||||
@@ -134,6 +130,13 @@ RawDataAsset* TerrainPatch::GetHeightfield() const
|
||||
return _heightfield.Get();
|
||||
}
|
||||
|
||||
void TerrainPatch::GetTextures(GPUTexture*& heightmap, GPUTexture*& splatmap0, GPUTexture*& splatmap1) const
|
||||
{
|
||||
heightmap = Heightmap->GetTexture();
|
||||
splatmap0 = Splatmap[0] ? Splatmap[0]->GetTexture() : nullptr;
|
||||
splatmap1 = Splatmap[1] ? Splatmap[1]->GetTexture() : nullptr;
|
||||
}
|
||||
|
||||
void TerrainPatch::RemoveLightmap()
|
||||
{
|
||||
for (auto& chunk : Chunks)
|
||||
@@ -178,7 +181,7 @@ void TerrainPatch::UpdateTransform()
|
||||
_collisionVertices.Resize(0);
|
||||
}
|
||||
|
||||
#if TERRAIN_EDITING || TERRAIN_UPDATING
|
||||
#if TERRAIN_EDITING
|
||||
|
||||
bool IsValidMaterial(const JsonAssetReference<PhysicalMaterial>& e)
|
||||
{
|
||||
@@ -217,7 +220,7 @@ struct TerrainDataUpdateInfo
|
||||
// When using physical materials, then get splatmaps data required for per-triangle material indices
|
||||
void GetSplatMaps()
|
||||
{
|
||||
#if TERRAIN_UPDATING
|
||||
#if TERRAIN_EDITING
|
||||
if (SplatMaps[0])
|
||||
return;
|
||||
if (UsePhysicalMaterials())
|
||||
@@ -1021,7 +1024,7 @@ bool TerrainPatch::SetupHeightMap(int32 heightMapLength, const float* heightMap,
|
||||
_terrain->UpdateBounds();
|
||||
_terrain->UpdateLayerBits();
|
||||
|
||||
#if TERRAIN_UPDATING
|
||||
#if TERRAIN_EDITING
|
||||
// Invalidate cache
|
||||
_cachedHeightMap.Resize(0);
|
||||
_cachedHolesMask.Resize(0);
|
||||
@@ -1169,7 +1172,7 @@ bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color3
|
||||
}
|
||||
#endif
|
||||
|
||||
#if TERRAIN_UPDATING
|
||||
#if TERRAIN_EDITING
|
||||
// Invalidate cache
|
||||
_cachedSplatMap[index].Resize(0);
|
||||
_wasSplatmapModified[index] = false;
|
||||
@@ -1191,7 +1194,7 @@ bool TerrainPatch::InitializeHeightMap()
|
||||
return SetupHeightMap(heightmap.Count(), heightmap.Get());
|
||||
}
|
||||
|
||||
#if TERRAIN_UPDATING
|
||||
#if TERRAIN_EDITING
|
||||
|
||||
float* TerrainPatch::GetHeightmapData()
|
||||
{
|
||||
@@ -2631,7 +2634,7 @@ void TerrainPatch::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
}
|
||||
stream.EndArray();
|
||||
|
||||
#if TERRAIN_UPDATING
|
||||
#if TERRAIN_EDITING
|
||||
SaveHeightData();
|
||||
SaveSplatData();
|
||||
#endif
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
struct RayCastHit;
|
||||
class TerrainMaterialShader;
|
||||
|
||||
#ifndef TERRAIN_EDITING
|
||||
#define TERRAIN_EDITING 1
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Represents single terrain patch made of 16 terrain chunks.
|
||||
/// </summary>
|
||||
@@ -34,7 +38,7 @@ private:
|
||||
void* _physicsHeightField;
|
||||
CriticalSection _collisionLocker;
|
||||
float _collisionScaleXZ;
|
||||
#if TERRAIN_UPDATING
|
||||
#if TERRAIN_EDITING
|
||||
Array<float> _cachedHeightMap;
|
||||
Array<byte> _cachedHolesMask;
|
||||
Array<Color32> _cachedSplatMap[TERRAIN_MAX_SPLATMAPS_COUNT];
|
||||
@@ -189,6 +193,8 @@ public:
|
||||
return _bounds;
|
||||
}
|
||||
|
||||
void GetTextures(GPUTexture*& heightmap, GPUTexture*& splatmap0, GPUTexture*& splatmap1) const;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Removes the lightmap data from the terrain patch.
|
||||
@@ -220,7 +226,7 @@ public:
|
||||
/// <param name="holesMask">The holes mask (optional). Normalized to 0-1 range values with holes mask per-vertex. Must match the heightmap dimensions.</param>
|
||||
/// <param name="forceUseVirtualStorage">If set to <c>true</c> patch will use virtual storage by force. Otherwise it can use normal texture asset storage on drive (valid only during Editor). Runtime-created terrain can only use virtual storage (in RAM).</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
API_FUNCTION() bool SetupHeightMap(int32 heightMapLength, API_PARAM(Ref) const float* heightMap, API_PARAM(Ref) const byte* holesMask = nullptr, bool forceUseVirtualStorage = false);
|
||||
API_FUNCTION() bool SetupHeightMap(int32 heightMapLength, const float* heightMap, const byte* holesMask = nullptr, bool forceUseVirtualStorage = false);
|
||||
|
||||
/// <summary>
|
||||
/// Setups the terrain patch layer weights using the specified splatmaps data.
|
||||
@@ -230,14 +236,12 @@ public:
|
||||
/// <param name="splatMap">The splat map. Each array item contains 4 layer weights.</param>
|
||||
/// <param name="forceUseVirtualStorage">If set to <c>true</c> patch will use virtual storage by force. Otherwise it can use normal texture asset storage on drive (valid only during Editor). Runtime-created terrain can only use virtual storage (in RAM).</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
API_FUNCTION() bool SetupSplatMap(int32 index, int32 splatMapLength, API_PARAM(Ref) const Color32* splatMap, bool forceUseVirtualStorage = false);
|
||||
#endif
|
||||
API_FUNCTION() bool SetupSplatMap(int32 index, int32 splatMapLength, const Color32* splatMap, bool forceUseVirtualStorage = false);
|
||||
|
||||
#if TERRAIN_UPDATING
|
||||
/// <summary>
|
||||
/// Gets the raw pointer to the heightmap data.
|
||||
/// Gets the raw pointer to the heightmap data. Array size is square of Terrain.HeightmapSize.
|
||||
/// </summary>
|
||||
/// <returns>The heightmap data.</returns>
|
||||
/// <returns>The heightmap data. Null if empty or failed to access it.</returns>
|
||||
API_FUNCTION() float* GetHeightmapData();
|
||||
|
||||
/// <summary>
|
||||
@@ -246,9 +250,9 @@ public:
|
||||
API_FUNCTION() void ClearHeightmapCache();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw pointer to the holes mask data.
|
||||
/// Gets the raw pointer to the holes mask data. Array size is square of Terrain.HeightmapSize.
|
||||
/// </summary>
|
||||
/// <returns>The holes mask data.</returns>
|
||||
/// <returns>The holes mask data. Null if empty/unused or failed to access it.</returns>
|
||||
API_FUNCTION() byte* GetHolesMaskData();
|
||||
|
||||
/// <summary>
|
||||
@@ -257,10 +261,10 @@ public:
|
||||
API_FUNCTION() void ClearHolesMaskCache();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw pointer to the splat map data.
|
||||
/// Gets the raw pointer to the splat map data. Array size is square of Terrain.HeightmapSize.
|
||||
/// </summary>
|
||||
/// <param name="index">The zero-based index of the splatmap texture.</param>
|
||||
/// <returns>The splat map data.</returns>
|
||||
/// <returns>The splat map data. Null if empty/unused or failed to access it.</returns>
|
||||
API_FUNCTION() Color32* GetSplatMapData(int32 index);
|
||||
|
||||
/// <summary>
|
||||
@@ -280,7 +284,7 @@ public:
|
||||
/// <param name="modifiedOffset">The offset from the first row and column of the heightmap data (offset destination x and z start position).</param>
|
||||
/// <param name="modifiedSize">The size of the heightmap to modify (x and z). Amount of samples in each direction.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
API_FUNCTION() bool ModifyHeightMap(API_PARAM(Ref) const float* samples, API_PARAM(Ref) const Int2& modifiedOffset, API_PARAM(Ref) const Int2& modifiedSize);
|
||||
API_FUNCTION() bool ModifyHeightMap(const float* samples, const Int2& modifiedOffset, const Int2& modifiedSize);
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the terrain patch holes mask with the given samples.
|
||||
@@ -289,7 +293,7 @@ public:
|
||||
/// <param name="modifiedOffset">The offset from the first row and column of the holes map data (offset destination x and z start position).</param>
|
||||
/// <param name="modifiedSize">The size of the holes map to modify (x and z). Amount of samples in each direction.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
API_FUNCTION() bool ModifyHolesMask(API_PARAM(Ref) const byte* samples, API_PARAM(Ref) const Int2& modifiedOffset, API_PARAM(Ref) const Int2& modifiedSize);
|
||||
API_FUNCTION() bool ModifyHolesMask(const byte* samples, const Int2& modifiedOffset, const Int2& modifiedSize);
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the terrain patch splat map (layers mask) with the given samples.
|
||||
@@ -299,7 +303,7 @@ public:
|
||||
/// <param name="modifiedOffset">The offset from the first row and column of the splat map data (offset destination x and z start position).</param>
|
||||
/// <param name="modifiedSize">The size of the splat map to modify (x and z). Amount of samples in each direction.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
API_FUNCTION() bool ModifySplatMap(int32 index, API_PARAM(Ref) const Color32* samples, API_PARAM(Ref) const Int2& modifiedOffset, API_PARAM(Ref) const Int2& modifiedSize);
|
||||
API_FUNCTION() bool ModifySplatMap(int32 index, const Color32* samples, const Int2& modifiedOffset, const Int2& modifiedSize);
|
||||
|
||||
private:
|
||||
bool UpdateHeightData(struct TerrainDataUpdateInfo& info, const Int2& modifiedOffset, const Int2& modifiedSize, bool wasHeightRangeChanged, bool wasHeightChanged);
|
||||
|
||||
@@ -588,6 +588,7 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj
|
||||
SERIALIZE(SloppyOptimization);
|
||||
SERIALIZE(LODTargetError);
|
||||
SERIALIZE(ImportMaterials);
|
||||
SERIALIZE(CreateEmptyMaterialSlots);
|
||||
SERIALIZE(ImportMaterialsAsInstances);
|
||||
SERIALIZE(InstanceToImportAs);
|
||||
SERIALIZE(ImportTextures);
|
||||
@@ -643,6 +644,7 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi
|
||||
DESERIALIZE(SloppyOptimization);
|
||||
DESERIALIZE(LODTargetError);
|
||||
DESERIALIZE(ImportMaterials);
|
||||
DESERIALIZE(CreateEmptyMaterialSlots);
|
||||
DESERIALIZE(ImportMaterialsAsInstances);
|
||||
DESERIALIZE(InstanceToImportAs);
|
||||
DESERIALIZE(ImportTextures);
|
||||
@@ -1019,7 +1021,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
|
||||
options.ImportTypes |= ImportDataTypes::Skeleton;
|
||||
break;
|
||||
case ModelType::Prefab:
|
||||
options.ImportTypes = ImportDataTypes::Geometry | ImportDataTypes::Nodes | ImportDataTypes::Animations;
|
||||
options.ImportTypes = ImportDataTypes::Geometry | ImportDataTypes::Nodes | ImportDataTypes::Skeleton | ImportDataTypes::Animations;
|
||||
if (options.ImportMaterials)
|
||||
options.ImportTypes |= ImportDataTypes::Materials;
|
||||
if (options.ImportTextures)
|
||||
@@ -1045,6 +1047,8 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
|
||||
{
|
||||
for (auto& mesh : lod.Meshes)
|
||||
{
|
||||
if (mesh->BlendShapes.IsEmpty())
|
||||
continue;
|
||||
for (int32 blendShapeIndex = mesh->BlendShapes.Count() - 1; blendShapeIndex >= 0; blendShapeIndex--)
|
||||
{
|
||||
auto& blendShape = mesh->BlendShapes[blendShapeIndex];
|
||||
@@ -1209,7 +1213,9 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
|
||||
for (int32 i = 0; i < meshesCount; i++)
|
||||
{
|
||||
const auto mesh = data.LODs[0].Meshes[i];
|
||||
if (mesh->BlendIndices.IsEmpty() || mesh->BlendWeights.IsEmpty())
|
||||
|
||||
// If imported mesh has skeleton but no indices or weights then need to setup those (except in Prefab mode when we conditionally import meshes based on type)
|
||||
if ((mesh->BlendIndices.IsEmpty() || mesh->BlendWeights.IsEmpty()) && data.Skeleton.Bones.HasItems() && (options.Type != ModelType::Prefab))
|
||||
{
|
||||
auto indices = Int4::Zero;
|
||||
auto weights = Float4::UnitX;
|
||||
@@ -1326,7 +1332,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
|
||||
auto& texture = data.Textures[i];
|
||||
|
||||
// Auto-import textures
|
||||
if (autoImportOutput.IsEmpty() || EnumHasNoneFlags(options.ImportTypes, ImportDataTypes::Textures) || texture.FilePath.IsEmpty())
|
||||
if (autoImportOutput.IsEmpty() || EnumHasNoneFlags(options.ImportTypes, ImportDataTypes::Textures) || texture.FilePath.IsEmpty() || options.CreateEmptyMaterialSlots)
|
||||
continue;
|
||||
String assetPath = GetAdditionalImportPath(autoImportOutput, importedFileNames, StringUtils::GetFileNameWithoutExtension(texture.FilePath));
|
||||
#if COMPILE_WITH_ASSETS_IMPORTER
|
||||
@@ -1384,6 +1390,10 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
|
||||
}
|
||||
}
|
||||
|
||||
// The rest of the steps this function performs become irrelevant when we're only creating slots.
|
||||
if (options.CreateEmptyMaterialSlots)
|
||||
continue;
|
||||
|
||||
if (options.ImportMaterialsAsInstances)
|
||||
{
|
||||
// Create material instance
|
||||
@@ -2021,12 +2031,11 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
|
||||
#undef REMAP_VERTEX_BUFFER
|
||||
|
||||
// Remap blend shapes
|
||||
dstMesh->BlendShapes.Resize(srcMesh->BlendShapes.Count());
|
||||
dstMesh->BlendShapes.EnsureCapacity(srcMesh->BlendShapes.Count(), false);
|
||||
for (int32 blendShapeIndex = 0; blendShapeIndex < srcMesh->BlendShapes.Count(); blendShapeIndex++)
|
||||
{
|
||||
const auto& srcBlendShape = srcMesh->BlendShapes[blendShapeIndex];
|
||||
auto& dstBlendShape = dstMesh->BlendShapes[blendShapeIndex];
|
||||
|
||||
BlendShape dstBlendShape;
|
||||
dstBlendShape.Name = srcBlendShape.Name;
|
||||
dstBlendShape.Weight = srcBlendShape.Weight;
|
||||
dstBlendShape.Vertices.EnsureCapacity(srcBlendShape.Vertices.Count());
|
||||
@@ -2035,17 +2044,12 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
|
||||
auto v = srcBlendShape.Vertices[i];
|
||||
v.VertexIndex = remap[v.VertexIndex];
|
||||
if (v.VertexIndex != ~0u)
|
||||
{
|
||||
dstBlendShape.Vertices.Add(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove empty blend shapes
|
||||
for (int32 blendShapeIndex = dstMesh->BlendShapes.Count() - 1; blendShapeIndex >= 0; blendShapeIndex--)
|
||||
{
|
||||
if (dstMesh->BlendShapes[blendShapeIndex].Vertices.IsEmpty())
|
||||
dstMesh->BlendShapes.RemoveAt(blendShapeIndex);
|
||||
// Add only valid blend shapes
|
||||
if (dstBlendShape.Vertices.HasItems())
|
||||
dstMesh->BlendShapes.Add(dstBlendShape);
|
||||
}
|
||||
|
||||
// Optimize generated LOD
|
||||
@@ -2092,6 +2096,8 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
|
||||
{
|
||||
for (auto& mesh : lod.Meshes)
|
||||
{
|
||||
if (mesh->BlendShapes.IsEmpty())
|
||||
continue;
|
||||
for (auto& blendShape : mesh->BlendShapes)
|
||||
{
|
||||
// Compute min/max for used vertex indices
|
||||
|
||||
@@ -311,16 +311,19 @@ public:
|
||||
public: // Materials
|
||||
|
||||
// If checked, the importer will create materials for model meshes as specified in the file.
|
||||
API_FIELD(Attributes="EditorOrder(400), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry))")
|
||||
API_FIELD(Attributes="EditorOrder(399), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry))")
|
||||
bool ImportMaterials = true;
|
||||
// If checked, the importer will create empty material slots for every material without importing materials nor textures.
|
||||
API_FIELD(Attributes="EditorOrder(400), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry))")
|
||||
bool CreateEmptyMaterialSlots = false;
|
||||
// If checked, the importer will create the model's materials as instances of a base material.
|
||||
API_FIELD(Attributes="EditorOrder(401), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterials)), VisibleIf(nameof(ShowGeometry))")
|
||||
API_FIELD(Attributes="EditorOrder(401), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterials)), VisibleIf(nameof(ShowGeometry)), VisibleIf(nameof(CreateEmptyMaterialSlots), true)")
|
||||
bool ImportMaterialsAsInstances = false;
|
||||
// The material used as the base material that will be instanced as the imported model's material.
|
||||
API_FIELD(Attributes="EditorOrder(402), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterialsAsInstances)), VisibleIf(nameof(ShowGeometry))")
|
||||
API_FIELD(Attributes="EditorOrder(402), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterialsAsInstances)), VisibleIf(nameof(ShowGeometry)), VisibleIf(nameof(CreateEmptyMaterialSlots), true)")
|
||||
AssetReference<MaterialBase> InstanceToImportAs;
|
||||
// If checked, the importer will import texture files used by the model and any embedded texture resources.
|
||||
API_FIELD(Attributes="EditorOrder(410), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry))")
|
||||
API_FIELD(Attributes="EditorOrder(410), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry)), VisibleIf(nameof(CreateEmptyMaterialSlots), true)")
|
||||
bool ImportTextures = true;
|
||||
// If checked, the importer will try to keep the model's current overridden material slots, instead of importing materials from the source file.
|
||||
API_FIELD(Attributes="EditorOrder(420), EditorDisplay(\"Materials\", \"Keep Overridden Materials\"), VisibleIf(nameof(ShowGeometry))")
|
||||
|
||||
@@ -414,6 +414,7 @@ bool TextureTool::UpdateTexture(GPUContext* context, GPUTexture* texture, int32
|
||||
Array<byte> tempData;
|
||||
if (textureFormat != dataFormat)
|
||||
{
|
||||
PROFILE_CPU_NAMED("ConvertTexture");
|
||||
auto dataSampler = PixelFormatSampler::Get(dataFormat);
|
||||
auto textureSampler = PixelFormatSampler::Get(textureFormat);
|
||||
if (!dataSampler || !textureSampler)
|
||||
|
||||
@@ -5,14 +5,14 @@ using System;
|
||||
namespace FlaxEngine.GUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Radial menu control that arranges child controls (of type Image) in a circle.
|
||||
/// Radial menu control that arranges child controls (of type <see cref="FlaxEngine.GUI.Image"/>) in a circle.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEngine.GUI.ContainerControl" />
|
||||
public class RadialMenu : ContainerControl
|
||||
{
|
||||
private bool _materialIsDirty = true;
|
||||
private float _angle;
|
||||
private float _selectedSegment;
|
||||
private int _selectedSegment;
|
||||
private int _highlightSegment = -1;
|
||||
private MaterialBase _material;
|
||||
private MaterialInstance _materialInstance;
|
||||
@@ -27,7 +27,7 @@ namespace FlaxEngine.GUI
|
||||
private bool ShowMatProp => _material != null;
|
||||
|
||||
/// <summary>
|
||||
/// The material to use for menu background drawing.
|
||||
/// The material used for menu background drawing.
|
||||
/// </summary>
|
||||
[EditorOrder(1)]
|
||||
public MaterialBase Material
|
||||
@@ -44,7 +44,7 @@ namespace FlaxEngine.GUI
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the edge offset.
|
||||
/// Gets or sets the offset of the outer edge from the bounds of the Control.
|
||||
/// </summary>
|
||||
[EditorOrder(2), Range(0, 1)]
|
||||
public float EdgeOffset
|
||||
@@ -59,7 +59,7 @@ namespace FlaxEngine.GUI
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the thickness.
|
||||
/// Gets or sets the thickness of the menu.
|
||||
/// </summary>
|
||||
[EditorOrder(3), Range(0, 1), VisibleIf(nameof(ShowMatProp))]
|
||||
public float Thickness
|
||||
@@ -74,7 +74,7 @@ namespace FlaxEngine.GUI
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets control background color (transparent color (alpha=0) means no background rendering).
|
||||
/// Gets or sets control background color (transparent color means no background rendering).
|
||||
/// </summary>
|
||||
[VisibleIf(nameof(ShowMatProp))]
|
||||
public new Color BackgroundColor
|
||||
@@ -88,7 +88,7 @@ namespace FlaxEngine.GUI
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the highlight.
|
||||
/// Gets or sets the color of the outer edge highlight.
|
||||
/// </summary>
|
||||
[VisibleIf(nameof(ShowMatProp))]
|
||||
public Color HighlightColor
|
||||
@@ -130,19 +130,43 @@ namespace FlaxEngine.GUI
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The selected callback
|
||||
/// The material instance of <see cref="Material"/> used to draw the menu.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public MaterialInstance MaterialInstance => _materialInstance;
|
||||
|
||||
/// <summary>
|
||||
/// The selected callback.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public Action<int> Selected;
|
||||
|
||||
/// <summary>
|
||||
/// The allow change selection when inside
|
||||
/// Invoked when the hovered segment is changed.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public Action<int> HoveredSelectionChanged;
|
||||
|
||||
/// <summary>
|
||||
/// The selected segment.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public int SelectedSegment => _selectedSegment;
|
||||
|
||||
/// <summary>
|
||||
/// Allows the selected to change when the mouse is moved in the empty center of the menu.
|
||||
/// </summary>
|
||||
[VisibleIf(nameof(ShowMatProp))]
|
||||
public bool AllowChangeSelectionWhenInside;
|
||||
|
||||
/// <summary>
|
||||
/// The center as button
|
||||
/// Allows the selected to change when the mouse is moved outside of the menu.
|
||||
/// </summary>
|
||||
[VisibleIf(nameof(ShowMatProp))]
|
||||
public bool AllowChangeSelectionWhenOutside;
|
||||
|
||||
/// <summary>
|
||||
/// Wether the center is a button.
|
||||
/// </summary>
|
||||
[VisibleIf(nameof(ShowMatProp))]
|
||||
public bool CenterAsButton;
|
||||
@@ -225,7 +249,7 @@ namespace FlaxEngine.GUI
|
||||
var min = ((1 - _edgeOffset) - _thickness) * USize * 0.5f;
|
||||
var max = (1 - _edgeOffset) * USize * 0.5f;
|
||||
var val = ((USize * 0.5f) - location).Length;
|
||||
if (Mathf.IsInRange(val, min, max) || val < min && AllowChangeSelectionWhenInside)
|
||||
if (Mathf.IsInRange(val, min, max) || val < min && AllowChangeSelectionWhenInside || val > max && AllowChangeSelectionWhenOutside)
|
||||
{
|
||||
UpdateAngle(ref location);
|
||||
}
|
||||
@@ -276,7 +300,7 @@ namespace FlaxEngine.GUI
|
||||
var min = ((1 - _edgeOffset) - _thickness) * USize * 0.5f;
|
||||
var max = (1 - _edgeOffset) * USize * 0.5f;
|
||||
var val = ((USize * 0.5f) - location).Length;
|
||||
if (Mathf.IsInRange(val, min, max) || val < min && AllowChangeSelectionWhenInside)
|
||||
if (Mathf.IsInRange(val, min, max) || val < min && AllowChangeSelectionWhenInside || val > max && AllowChangeSelectionWhenOutside)
|
||||
{
|
||||
UpdateAngle(ref location);
|
||||
}
|
||||
@@ -347,6 +371,28 @@ namespace FlaxEngine.GUI
|
||||
base.PerformLayout(force);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the current angle and selected segment of the radial menu based on the specified location inside of the control.
|
||||
/// </summary>
|
||||
/// <param name="location">The position used to determine the angle and segment selection within the radial menu.</param>
|
||||
public void UpdateAngle(ref Float2 location)
|
||||
{
|
||||
float previousSelectedSegment = _selectedSegment;
|
||||
|
||||
var size = new Float2(USize);
|
||||
var p = (size * 0.5f) - location;
|
||||
var sa = (1.0f / _segmentCount) * Mathf.TwoPi;
|
||||
_angle = Mathf.Atan2(p.X, p.Y);
|
||||
_angle = Mathf.Ceil((_angle - (sa * 0.5f)) / sa) * sa;
|
||||
_selectedSegment = Mathf.RoundToInt((_angle < 0 ? Mathf.TwoPi + _angle : _angle) / sa);
|
||||
if (float.IsNaN(_angle) || float.IsInfinity(_angle))
|
||||
_angle = 0;
|
||||
_materialInstance.SetParameterValue("RadialMenu_Rotation", -_angle + Mathf.Pi);
|
||||
|
||||
if (previousSelectedSegment != _selectedSegment)
|
||||
HoveredSelectionChanged?.Invoke((int)_selectedSegment);
|
||||
}
|
||||
|
||||
private void UpdateSelectionColor()
|
||||
{
|
||||
Color color;
|
||||
@@ -368,20 +414,6 @@ namespace FlaxEngine.GUI
|
||||
_materialInstance.SetParameterValue("RadialMenu_SelectionColor", color);
|
||||
}
|
||||
|
||||
private void UpdateAngle(ref Float2 location)
|
||||
{
|
||||
var size = new Float2(USize);
|
||||
var p = (size * 0.5f) - location;
|
||||
var sa = (1.0f / _segmentCount) * Mathf.TwoPi;
|
||||
_angle = Mathf.Atan2(p.X, p.Y);
|
||||
_angle = Mathf.Ceil((_angle - (sa * 0.5f)) / sa) * sa;
|
||||
_selectedSegment = _angle;
|
||||
_selectedSegment = Mathf.RoundToInt((_selectedSegment < 0 ? Mathf.TwoPi + _selectedSegment : _selectedSegment) / sa);
|
||||
if (float.IsNaN(_angle) || float.IsInfinity(_angle))
|
||||
_angle = 0;
|
||||
_materialInstance.SetParameterValue("RadialMenu_Rotation", -_angle + Mathf.Pi);
|
||||
}
|
||||
|
||||
private static Float2 Rotate2D(Float2 point, float angle)
|
||||
{
|
||||
return new Float2(Mathf.Cos(angle) * point.X + Mathf.Sin(angle) * point.Y,
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Source/Platforms/Windows/Binaries/ThirdParty/x64/FastXml_64.pdb
LFS
vendored
BIN
Source/Platforms/Windows/Binaries/ThirdParty/x64/FastXml_64.pdb
LFS
vendored
Binary file not shown.
BIN
Source/Platforms/Windows/Binaries/ThirdParty/x64/LowLevelAABB_64.pdb
LFS
vendored
BIN
Source/Platforms/Windows/Binaries/ThirdParty/x64/LowLevelAABB_64.pdb
LFS
vendored
Binary file not shown.
Binary file not shown.
BIN
Source/Platforms/Windows/Binaries/ThirdParty/x64/LowLevel_64.pdb
LFS
vendored
BIN
Source/Platforms/Windows/Binaries/ThirdParty/x64/LowLevel_64.pdb
LFS
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Source/Platforms/Windows/Binaries/ThirdParty/x64/PhysXTask_64.pdb
LFS
vendored
BIN
Source/Platforms/Windows/Binaries/ThirdParty/x64/PhysXTask_64.pdb
LFS
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Source/Platforms/Windows/Binaries/ThirdParty/x64/PhysX_static_64.lib
LFS
vendored
BIN
Source/Platforms/Windows/Binaries/ThirdParty/x64/PhysX_static_64.lib
LFS
vendored
Binary file not shown.
BIN
Source/Platforms/Windows/Binaries/ThirdParty/x64/PhysX_static_64.pdb
LFS
vendored
BIN
Source/Platforms/Windows/Binaries/ThirdParty/x64/PhysX_static_64.pdb
LFS
vendored
Binary file not shown.
BIN
Source/Platforms/Windows/Binaries/ThirdParty/x64/SceneQuery_64.pdb
LFS
vendored
BIN
Source/Platforms/Windows/Binaries/ThirdParty/x64/SceneQuery_64.pdb
LFS
vendored
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user