Merge remote-tracking branch 'origin/master' into 1.13

# Conflicts:
#	Content/Shaders/DepthOfField.flax
#	Source/Engine/Content/Asset.cpp
This commit is contained in:
2026-06-30 10:06:55 +02:00
64 changed files with 1222 additions and 423 deletions
+126
View File
@@ -0,0 +1,126 @@
# AGENTS.md
## Repo Purpose
Flax Engine is a modern 3D game engine written in C++ and C#.
This repository contains the engine, editor, tooling, shaders, tests, assets, and platform-specific sources, excluding NDA-protected platform support.
## High-Level Structure
- `Source/Engine/`: engine runtime code and managed/runtime integration.
- `Source/Editor/`: editor code in both C++ and C#.
- `Source/Tools/`: build system and developer tooling, including `Flax.Build`.
- `Source/Platforms/`: platform-specific code, dependencies, and binaries.
- `Source/ThirdParty/`: vendored third-party code. Avoid changes here unless the task explicitly requires it.
- `Source/Engine/Tests/`: native and managed engine tests.
- `Source/Tools/Flax.Build.Tests/`: .NET tests for the build tool.
- `Content/`: engine/editor assets.
- `Development/Scripts/`: helper scripts for project generation and builds.
## Working Assumptions
- Generated solutions and project files are not committed. On a clean clone, generate them first.
- Git LFS is required. The Windows build scripts explicitly check for LFS-populated files.
- On Windows, the main entry points are `GenerateProjectFiles.bat` and `Development\Scripts\Windows\CallBuildTool.bat`.
- Prefer edits in `Source/Engine`, `Source/Editor`, `Source/Tools`, or docs. Do not modify `Binaries/`, `Cache/`, or generated project files unless the task explicitly targets generated output.
## Windows Setup And Build
Use these commands from the repo root.
Generate project files:
```powershell
.\GenerateProjectFiles.bat -vs2022 -log -verbose -printSDKs -dotnet=8
```
Alternative default generation:
```powershell
.\GenerateProjectFiles.bat
```
Build the editor target:
```powershell
.\Development\Scripts\Windows\CallBuildTool.bat -build -log -dotnet=8 -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxEditor
```
Run the editor:
```powershell
.\Binaries\Editor\Win64\Development\FlaxEditor.exe
```
Visual Studio workflow after generation:
- Open `Flax.sln`.
- Use solution configuration `Editor.Development` and platform `Win64`.
- Set `Flax` or `FlaxEngine` as the startup project.
## Tests And Validation
The checked-in CI workflow in `.github/workflows/tests.yml` is the most reliable source for current validation commands.
Build native tests:
```powershell
.\Development\Scripts\Windows\CallBuildTool.bat -build -log -dotnet=8 -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxTestsTarget
```
Run native tests:
```powershell
.\Binaries\Editor\Win64\Development\FlaxTests.exe -headless
```
Build Flax.Build tests:
```powershell
dotnet msbuild Source\Tools\Flax.Build.Tests\Flax.Build.Tests.csproj /m /t:Restore,Build /p:Configuration=Debug /p:Platform=AnyCPU /nologo
```
Run Flax.Build tests:
```powershell
dotnet test -f net8.0 Binaries\Tests\Flax.Build.Tests.dll
```
Run managed engine tests after copying runtime dependencies:
```powershell
xcopy /y Binaries\Editor\Win64\Development\FlaxEngine.CSharp.dll Binaries\Tests
xcopy /y Binaries\Editor\Win64\Development\FlaxEngine.CSharp.runtimeconfig.json Binaries\Tests
xcopy /y Binaries\Editor\Win64\Development\Newtonsoft.Json.dll Binaries\Tests
dotnet test -f net8.0 Binaries\Tests\FlaxEngine.CSharp.dll
```
If a change is localized, prefer the narrowest possible target build and only run the relevant tests for that area.
## Style And Conventions
- The root `Source/.editorconfig` sets CRLF line endings, UTF-8, final newline, and spaces for indentation.
- Use 4 spaces for C++, C#, shaders, and Python. Use 2 spaces for XAML and MSBuild files.
- Keep the existing copyright header in source files.
- Public APIs in both C++ headers and C# commonly use XML-style documentation comments such as `/// <summary>`.
- Preserve local file style instead of mass-normalizing. The C# codebase mixes block namespaces and file-scoped namespaces.
- Follow existing naming patterns: PascalCase for types and public members; private C# fields often use `_camelCase`.
- In C++ headers, prefer forward declarations where practical and keep includes minimal.
## Build System Notes
- `FlaxEditor` is the main standalone editor target.
- `FlaxGame` is the standalone game target.
- `FlaxTestsTarget` builds the native test executable.
- `Flax.Build` supports project generation switches such as `-vs2022`, `-vs2026`, `-vscode`, and `-rider`.
- `GenerateProjectFiles.bat` also builds C# bindings for `FlaxEditor` on Windows.
## Agent Guidance
- Treat `.github/workflows/tests.yml` as the source of truth for CI-backed validation.
- Do not assume generated artifacts already exist in the repo.
- Avoid broad style-only rewrites.
- Avoid touching `Source/ThirdParty/` unless explicitly requested.
- If a change affects developer workflow or build/test steps, update the relevant root docs as part of the task.
- No dedicated repo-wide formatter or linter command is checked in. Prefer build and test validation over inventing formatting steps.
- PVS-Studio is mentioned in `README.md`, but it is not wired here as a standard local validation command.
+1 -1
View File
@@ -384,7 +384,7 @@ namespace FlaxEditor.Content.GUI
/// Selects the specified item.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="additive">If set to <c>true</c> item will be added to the current selection. Otherwise selection will be cleared before.</param>
/// <param name="additive">If set to <c>true</c> item will be added to the current selection. Otherwise, selection will be cleared before.</param>
public void Select(ContentItem item, bool additive = false)
{
if (item == null)
@@ -21,6 +21,7 @@ namespace FlaxEditor.Content.Import
{
private TreeNode _rootNode;
private CustomEditorPresenter _settingsEditor;
private Tree _tree;
/// <summary>
/// Gets the entries count.
@@ -106,11 +107,11 @@ namespace FlaxEditor.Content.Import
_settingsEditor.Panel.Parent = splitPanel.Panel2;
// Setup tree
var tree = new Tree(true)
_tree = new Tree(true)
{
Parent = splitPanel.Panel1
};
tree.RightClick += OnTreeRightClick;
_tree.RightClick += OnTreeRightClick;
_rootNode = new TreeNode(false);
for (int i = 0; i < entries.Count; i++)
{
@@ -124,12 +125,12 @@ namespace FlaxEditor.Content.Import
}
_rootNode.Expand();
_rootNode.ChildrenIndent = 0;
_rootNode.Parent = tree;
tree.Margin = new Margin(0.0f, 0.0f, -14.0f, 2.0f); // Hide root node
tree.SelectedChanged += OnSelectedChanged;
_rootNode.Parent = _tree;
_tree.Margin = new Margin(0.0f, 0.0f, -16.0f, 2.0f); // Hide root node
_tree.SelectedChanged += OnSelectedChanged;
// Select the first item
tree.Select(_rootNode.Children[0] as TreeNode);
_tree.Select(_rootNode.Children[0] as TreeNode);
_dialogSize = new Float2(TotalWidth, EditorHeight + splitPanel.Offsets.Height);
}
@@ -257,5 +258,12 @@ namespace FlaxEditor.Content.Import
settings.MinimumSize = new Float2(300, 400);
settings.HasSizingFrame = true;
}
/// <inheritdoc />
public override void Focus()
{
base.Focus();
_tree.SelectedNode?.Focus();
}
}
}
+1 -1
View File
@@ -239,7 +239,7 @@ String CookingData::GetGameBinariesPath() const
archDir = TEXT("ARM64");
break;
default:
CRASH;
CRASH;
return String::Empty;
}
@@ -69,8 +69,6 @@ bool ValidateStep::Perform(CookingData& data)
return true;
}
// TODO: validate version
AssetInfo info;
if (!Content::GetAssetInfo(gameSettings->FirstScene, info))
{
@@ -79,9 +77,5 @@ bool ValidateStep::Perform(CookingData& data)
}
}
// TODO: validate more game config
// TODO: validate all input scenes?
return false;
}
+1 -1
View File
@@ -654,7 +654,7 @@ Window* Editor::CreateMainWindow()
PROFILE_MEM(Editor);
Window* window = Managed->GetMainWindow();
#if PLATFORM_LINUX || (PLATFORM_MAC && PLATFORM_SDL)
#if PLATFORM_LINUX || PLATFORM_MAC
// Set window icon
const String iconPath = Globals::BinariesFolder / TEXT("Logo.png");
if (FileSystem::FileExists(iconPath))
@@ -1,8 +1,8 @@
#if PLATFORM_WINDOWS || PLATFORM_SDL
#if PLATFORM_WINDOWS || PLATFORM_SDL || PLATFORM_MAC
#define USE_IS_FOREGROUND
#else
#endif
#if PLATFORM_SDL
#if PLATFORM_SDL || PLATFORM_MAC
#define USE_SDL_WORKAROUNDS
#endif
// Copyright (c) Wojciech Figat. All rights reserved.
@@ -52,8 +52,67 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// </summary>
public Script Script
{
get => FlaxEngine.Object.TryFind<Script>(ref ScriptID);
set => ScriptID = value?.ID ?? Guid.Empty;
get
{
if (Flags.HasFlag(TrackFlags.PrefabObject))
{
// TODO: reuse cached script to improve perf
foreach (var window in Editor.Instance.Windows.Windows)
{
if (window is Windows.Assets.PrefabWindow prefabWindow && prefabWindow.Graph.MainActor)
{
var script = FindScriptWithPrefabObjectID(prefabWindow.Graph.MainActor, ref ScriptID);
if (script != null)
return script;
}
}
return null;
}
return FlaxEngine.Object.TryFind<Script>(ref ScriptID);
}
set
{
if (value != null)
{
if (value.HasPrefabLink && !value.HasScene)
{
// Track with prefab object reference assigned in Editor
ScriptID = value.PrefabObjectID;
Flags |= TrackFlags.PrefabObject;
}
else
{
ScriptID = value.ID;
Flags &= ~TrackFlags.PrefabObject;
}
}
else
{
ScriptID = Guid.Empty;
Flags &= ~TrackFlags.PrefabObject;
}
}
}
private static Script FindScriptWithPrefabObjectID(Actor actor, ref Guid id)
{
if (actor == null)
return null;
var scripts = actor.Scripts;
for (int i = 0; i < scripts.Length; i++)
{
var script = scripts[i];
if (script && script.PrefabObjectID == id)
return script;
}
for (int i = 0; i < actor.ChildrenCount; i++)
{
var e = FindScriptWithPrefabObjectID(actor.GetChild(i), ref id);
if (e != null)
return e;
}
return null;
}
/// <inheritdoc />
+12 -3
View File
@@ -138,7 +138,8 @@ namespace FlaxEditor.GUI.Tree
/// Selects single tree node.
/// </summary>
/// <param name="node">Node to select.</param>
public void Select(TreeNode node)
/// <param name="additive">If set to <c>true</c> item will be added to the current selection. Otherwise, selection will be cleared before.</param>
public void Select(TreeNode node, bool additive = false)
{
if (node == null)
throw new ArgumentNullException();
@@ -151,8 +152,16 @@ namespace FlaxEditor.GUI.Tree
var prev = new List<TreeNode>(Selection);
// Update selection
Selection.Clear();
Selection.Add(node);
if (additive)
{
if (!Selection.Contains(node))
Selection.Add(node);
}
else
{
Selection.Clear();
Selection.Add(node);
}
// Ensure that node can be visible (all it's parents are expanded)
node.ExpandAllParents();
+2 -1
View File
@@ -113,7 +113,8 @@ namespace FlaxEditor.Gizmo
if (cb != IntPtr.Zero)
{
var data = new Data();
Matrix.Multiply(ref renderContext.View.View, ref renderContext.View.Projection, out var viewProjection);
renderContext.View.GetOverlayProjection(out var projection);
Matrix.Multiply(ref renderContext.View.View, ref projection, out var viewProjection);
Matrix.Transpose(ref viewProjection, out data.ViewProjectionMatrix);
data.ViewPos = renderContext.View.WorldPosition;
data.GridColor = options.Viewport.ViewportGridColor;
@@ -1261,7 +1261,6 @@ namespace FlaxEditor.Modules
private void OnImportFileDone(string path)
{
// Check if already has that element
var item = Find(path);
if (item is BinaryAssetItem binaryAssetItem)
{
@@ -1284,9 +1283,6 @@ namespace FlaxEditor.Modules
binaryAssetItem.OnReimport(ref assetInfo.ID);
}
}
// Refresh content view (not the best design because window could also track this event but it gives better performance)
Editor.Windows.ContentWin?.RefreshView();
}
}
@@ -252,6 +252,8 @@ namespace FlaxEditor.Modules
/// <param name="settings">Import settings to override. Use null to skip this value.</param>
private void Import(string inputPath, string outputPath, bool isInBuilt, bool skipSettingsDialog = false, object settings = null)
{
inputPath = StringUtils.NormalizePath(inputPath);
outputPath = StringUtils.NormalizePath(outputPath);
lock (_requests)
{
_requests.Add(new Request
@@ -311,7 +313,9 @@ namespace FlaxEditor.Modules
}
_importBatchDone++;
Profiler.BeginEvent("ImportFileEnd");
ImportFileEnd?.Invoke(entry, failed);
Profiler.EndEvent();
}
}
else
@@ -532,6 +532,7 @@ namespace FlaxEditor.Modules.SourceCodeEditing
{
// Invalidate cached types
All.ClearTypes();
AllWithStd.ClearTypes();
VisualScriptPropertyTypes.ClearTypes();
Actors.ClearTypes();
Scripts.ClearTypes();
+9 -3
View File
@@ -189,12 +189,18 @@ namespace FlaxEditor.Options
/// <summary>
/// Determined automatically based on the system and any known compatibility issues with native decorations.
/// </summary>
#if PLATFORM_MAC && !PLATFORM_SDL
[HideInEditor]
#endif
Auto,
/// <summary>
/// Automatically choose most compatible window decorations for child windows, prefer custom decorations on main window.
/// </summary>
[EditorDisplay(Name = "Auto (Child Only)")]
#if PLATFORM_MAC && !PLATFORM_SDL
[HideInEditor]
#endif
AutoChildOnly,
/// <summary>
@@ -307,18 +313,18 @@ namespace FlaxEditor.Options
[EditorDisplay("Interface"), EditorOrder(322)]
public bool ScrollToScriptOnAdd { get; set; } = true;
#if PLATFORM_SDL
#if PLATFORM_SDL || PLATFORM_MAC
/// <summary>
/// Gets or sets a value indicating whether use native window title bar decorations in child windows. Editor restart required.
/// </summary>
#if PLATFORM_WINDOWS
#if PLATFORM_WINDOWS || PLATFORM_MAC
[DefaultValue(WindowDecorationsType.ClientSide)]
#else
[DefaultValue(WindowDecorationsType.AutoChildOnly)]
#endif
[EditorDisplay("Tabs & Windows"), EditorOrder(70), Tooltip("Determines whether use native window title bar decorations. Editor restart required.")]
public WindowDecorationsType WindowDecorations { get; set; } =
#if PLATFORM_WINDOWS
#if PLATFORM_WINDOWS || PLATFORM_MAC
WindowDecorationsType.ClientSide;
#else
WindowDecorationsType.AutoChildOnly;
+7
View File
@@ -136,6 +136,13 @@ namespace FlaxEditor.Options
[DefaultValue(50.0f), Limit(25.0f, 500.0f, 5.0f)]
[EditorDisplay("Defaults"), EditorOrder(220), Tooltip("The default editor viewport grid scale.")]
public float ViewportGridScale { get; set; } = 50.0f;
/// <summary>
/// Gets or sets the use persistence over defaults setting
/// </summary>
[DefaultValue(true)]
[EditorDisplay("Defaults"), EditorOrder(230), Tooltip("Allow persistence setting from last session to override default settings")]
public bool UsePersistenceOverDefaults { get; set; } = true;
/// <summary>
/// Gets or sets the view distance you can see the grid.
+8 -2
View File
@@ -651,10 +651,16 @@ namespace FlaxEditor.Surface.Archetypes
foreach (var e in values)
{
_combobox.AddItem(e.Key);
tooltips[i++] = "Type: " + CustomEditorsUtil.GetTypeNameUI(e.Value.GetType()) + ", default value: " + e.Value;
var value = e.Value;
if (value == null)
{
tooltips[i++] = "null";
continue;
}
tooltips[i++] = "Type: " + CustomEditorsUtil.GetTypeNameUI(value.GetType()) + ", default value: " + value;
if (toSelect == e.Key)
{
type = e.Value.GetType();
type = value.GetType();
}
}
_combobox.Tooltips = tooltips;
+258 -107
View File
@@ -1,24 +1,260 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Surface
{
/// <summary>
/// Visject Surface node control that cna be resized.
/// Visject Surface node control that can be resized.
/// </summary>
/// <seealso cref="SurfaceNode" />
[HideInEditor]
public class ResizableSurfaceNode : SurfaceNode
{
private Float2 _startResizingSize;
private Float2 _startResizingCornerOffset;
/// <summary>
/// Helper class for <see cref="ResizableSurfaceNode"/> that handles mouse interactions resizing the node itself.
/// </summary>
public class ResizeBorder : ContainerControl
{
/// <summary>
/// Distance to each of the 4 node edges that the cursor has to be so the user can resize along the direction of the edge.
/// </summary>
private const float BorderWidth = 15f;
private readonly VisjectSurface _surface;
private Float2 _lastSurfaceMouseLoc;
private Float2 startResizingSize;
private Float2 noClampedSize;
/// <summary>
/// Whether to ignore the surface index in parent when updating the cursor type. Set to <code>false</code> for nodes that have order like <see cref="SurfaceComment"/>.
/// </summary>
internal bool IgnoreSurfaceIndex = true;
/// <summary>
/// The resizable node that this <see cref="ResizeBorder"/> controls.
/// </summary>
public readonly ResizableSurfaceNode ResizableNode;
/// <summary>
/// True if the mouse is at the border of the resizable node and not further away from the border than <see cref="BorderWidth"/>.
/// </summary>
public bool IsMouseOverResizeBorder { get; private set; }
/// <summary>
/// True if <see cref="ResizableNode"/> is being resized.
/// </summary>
public bool IsResizing { get; private set; }
/// <summary>
/// The direction in which to resize the node. Should be either -1, 0 or 1 on both axes.
/// </summary>
public Float2 ResizeDirection { get; private set; }
/// <summary>
/// The type of cursor to show to hint to the user that they can resize the node in a given direction.
/// </summary>
public CursorType CursorType
{
get
{
if ((ResizeDirection.X == 1 && ResizeDirection.Y == 0) || (ResizeDirection.X == -1 && ResizeDirection.Y == 0))
return CursorType.SizeWE;
if ((ResizeDirection.X == 0 && ResizeDirection.Y == 1) || (ResizeDirection.X == 0 && ResizeDirection.Y == -1))
return CursorType.SizeNS;
if ((ResizeDirection.X == -1 && ResizeDirection.Y == -1) || (ResizeDirection.X == 1 && ResizeDirection.Y == 1))
return CursorType.SizeNWSE;
if ((ResizeDirection.X == 1 && ResizeDirection.Y == -1) || (ResizeDirection.X == -1 && ResizeDirection.Y == 1))
return CursorType.SizeNESW;
return CursorType.Default;
}
}
/// <summary>
/// Creates a new instance of <see cref="ResizeBorder"/>.
/// </summary>
/// <param name="surface">The surface.</param>
/// <param name="resizableNode">The <see cref="ResizableSurfaceNode "/> this controls.</param>
public ResizeBorder(VisjectSurface surface, ResizableSurfaceNode resizableNode)
{
_surface = surface;
ResizableNode = resizableNode;
}
/// <summary>
/// Updates location and size to match the resizable node with the additional padding.
/// </summary>
/// <param name="nodeSize">The node size.</param>
/// <param name="nodeLocation">The node location.</param>
public void MatchResizableNode(Float2 nodeSize, Float2 nodeLocation)
{
Size = nodeSize + new Float2(BorderWidth * 2);
Location = nodeLocation - new Float2(BorderWidth);
}
private void UpdateResizeFlags(Float2 mouseLocation)
{
var borderRect = Bounds with { Location = Float2.Zero };
bool onBorder = borderRect.Contains(mouseLocation);
// Check this the way we do because some resizable nodes (like comments) have an implementation of `Control.ContainsPoint`
// that does not check for the size you would think they have based on their visual appearance
bool inNode = borderRect.MakeExpanded(-BorderWidth * 2f).Contains(mouseLocation);
Float2 rawResizeDirection = (mouseLocation - borderRect.Center);
var nodeHalfSizeNoBorder = ResizableNode.Size * 0.5f - BorderWidth;
ResizeDirection = new Float2(Mathf.Abs(rawResizeDirection.X) >= nodeHalfSizeNoBorder.X ? Mathf.Sign(rawResizeDirection.X) : 0,
Mathf.Abs(rawResizeDirection.Y) >= nodeHalfSizeNoBorder.Y ? Mathf.Sign(rawResizeDirection.Y) : 0);
IsMouseOverResizeBorder = onBorder && !inNode;
}
private Float2 GetControlDelta(Control control, Float2 start, Float2 end)
{
var pointOrigin = control.Parent ?? control;
var startPos = pointOrigin.PointFromParent(ResizableNode, start);
var endPos = pointOrigin.PointFromParent(ResizableNode, end);
return endPos - startPos;
}
private void EndResizing()
{
EndMouseCapture();
IsResizing = false;
if (startResizingSize != ResizableNode.Size)
{
var emptySize = ResizableNode.CalculateNodeSize(0, 0);
ResizableNode.SizeValue = ResizableNode.Size - emptySize;
_surface.MarkAsEdited(false);
}
}
/// <inheritdoc />
public override void OnMouseMove(Float2 location)
{
if (!IsResizing)
{
UpdateResizeFlags(location);
}
else if (_surface.CanEdit)
{
var resizeAxisAbs = ResizeDirection.Absolute;
var resizeAxisPos = Float2.Clamp(ResizeDirection, Float2.Zero, Float2.One);
var resizeAxisNeg = Float2.Clamp(-ResizeDirection, Float2.Zero, Float2.One);
var currentSurfaceMouseLoc = _surface.PointFromScreen(Input.MouseScreenPosition);
var delta = currentSurfaceMouseLoc - _lastSurfaceMouseLoc;
// TODO: Snapping
delta *= resizeAxisAbs;
var moveLocation = currentSurfaceMouseLoc;
var uiControlDelta = GetControlDelta(this, _lastSurfaceMouseLoc, moveLocation);
// This ensures that the node is not resized below min size, but also keeps the resize behavior like expected (which just Max() -ing the value does not)
var emptySize = ResizableNode.CalculateNodeSize(0, 0);
var minSize = ResizableNode._sizeMin;
noClampedSize = noClampedSize + uiControlDelta * resizeAxisPos - uiControlDelta * resizeAxisNeg;
if (noClampedSize.X < minSize.X && noClampedSize.X < ResizableNode.Size.X)
resizeAxisAbs.X = resizeAxisPos.X = resizeAxisNeg.X = 0f;
if (noClampedSize.Y < minSize.Y && noClampedSize.Y < ResizableNode.Size.Y)
resizeAxisAbs.Y = resizeAxisPos.Y = resizeAxisNeg.Y = 0f;
ResizableNode.Size += uiControlDelta * resizeAxisPos - uiControlDelta * resizeAxisNeg;
ResizableNode.Location += uiControlDelta * resizeAxisNeg;
ResizableNode.SizeValue = ResizableNode.Size - emptySize;
ResizableNode.CalculateNodeSize(ResizableNode.Size.X, ResizableNode.Size.Y);
MatchResizableNode(ResizableNode.Size, ResizableNode.Location);
_lastSurfaceMouseLoc = currentSurfaceMouseLoc;
}
// Update the cursor shape
if ((_surface.resizeableNodeIndexInParent <= IndexInParent || IgnoreSurfaceIndex) && !_surface.IsConnecting && _surface.CanEdit)
{
if (!IgnoreSurfaceIndex)
_surface.resizeableNodeIndexInParent = IndexInParent;
if (IsMouseOverResizeBorder)
Cursor = CursorType;
else
Cursor = CursorType.Default;
}
base.OnMouseMove(location);
}
/// <inheritdoc />
public override void OnMouseEnter(Float2 location)
{
Cursor = CursorType.Default;
base.OnMouseEnter(location);
}
/// <inheritdoc />
public override void OnMouseLeave()
{
Cursor = CursorType.Default;
IsMouseOverResizeBorder = false;
_surface.resizeableNodeIndexInParent = -1; // Will get updated by MouseMove again to match current index
base.OnMouseLeave();
}
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && IsMouseOverResizeBorder && !IsResizing)
{
// Start resizing
_lastSurfaceMouseLoc = _surface.PointFromScreen(Input.MouseScreenPosition);
noClampedSize = ResizableNode.Size;
IsResizing = true;
startResizingSize = ResizableNode.Size;
StartMouseCapture();
return true;
}
return base.OnMouseDown(location, button);
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && IsResizing)
{
Cursor = CursorType.Default;
EndResizing();
return true;
}
return base.OnMouseUp(location, button);
}
/// <inheritdoc />
public override void OnLostFocus()
{
if (IsResizing)
EndResizing();
base.OnLostFocus();
}
/// <inheritdoc />
public override void OnEndMouseCapture()
{
if (IsResizing)
EndResizing();
base.OnEndMouseCapture();
}
}
/// <summary>
/// Indicates whether the node is currently being resized.
/// Represents the border control used for resizing the associated element.
/// </summary>
protected bool _isResizing;
public ResizeBorder ResizeBorderControl;
/// <summary>
/// Index of the Float2 value in the node values list to store node size.
@@ -30,11 +266,6 @@ namespace FlaxEditor.Surface
/// </summary>
protected Float2 _sizeMin = new Float2(240, 160);
/// <summary>
/// Node resizing rectangle bounds.
/// </summary>
protected Rectangle _resizeButtonRect;
private Float2 SizeValue
{
get => (Float2)Values[_sizeValueIndex];
@@ -45,22 +276,31 @@ namespace FlaxEditor.Surface
public ResizableSurfaceNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
ResizeBorderControl = new ResizeBorder(Surface, this)
{
Parent = Surface.SurfaceRoot,
};
Parent = ResizeBorderControl;
ResizeBorderControl.MatchResizableNode(Size, Location);
}
/// <inheritdoc />
public override bool CanSelect(ref Float2 location)
protected override void OnLocationChanged()
{
return base.CanSelect(ref location) && !_resizeButtonRect.MakeOffsetted(Location).Contains(ref location);
ResizeBorderControl.MatchResizableNode(Size, Location);
base.OnLocationChanged();
}
/// <inheritdoc />
public override void OnSurfaceLoaded(SurfaceNodeActions action)
{
// Reapply the curve node size
// Reapply the node size
var size = SizeValue;
if (Surface != null && Surface.GridSnappingEnabled)
size = Surface.SnapToGrid(size, true);
Resize(size.X, size.Y);
ResizeBorderControl.MatchResizableNode(Size, Location);
base.OnSurfaceLoaded(action);
}
@@ -85,104 +325,15 @@ namespace FlaxEditor.Surface
{
base.Draw();
if (Surface.CanEdit)
{
var style = Style.Current;
if (_isResizing)
{
Render2D.FillRectangle(_resizeButtonRect, style.Selection);
Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder);
}
Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
}
if (Surface.CanEdit && !Surface.IsConnecting && (ResizeBorderControl.IsResizing || ResizeBorderControl.IsMouseOverResizeBorder))
Render2D.DrawRectangle(new Rectangle(Float2.Zero, Size), Style.Current.Foreground, 0.5f);
}
/// <inheritdoc />
public override void OnLostFocus()
public override void OnDestroy()
{
if (_isResizing)
EndResizing();
base.OnLostFocus();
}
/// <inheritdoc />
public override void OnEndMouseCapture()
{
if (_isResizing)
EndResizing();
base.OnEndMouseCapture();
}
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (base.OnMouseDown(location, button))
return true;
if (button == MouseButton.Left && _resizeButtonRect.Contains(ref location) && Surface.CanEdit)
{
// Start resizing
_isResizing = true;
_startResizingSize = Size;
_startResizingCornerOffset = Size - location;
StartMouseCapture();
Cursor = CursorType.SizeNWSE;
return true;
}
return false;
}
/// <inheritdoc />
public override void OnMouseMove(Float2 location)
{
if (_isResizing)
{
var emptySize = CalculateNodeSize(0, 0);
var size = Float2.Max(location - emptySize + _startResizingCornerOffset, _sizeMin);
Resize(size.X, size.Y);
}
else
{
base.OnMouseMove(location);
}
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && _isResizing)
{
EndResizing();
return true;
}
return base.OnMouseUp(location, button);
}
/// <inheritdoc />
protected override void UpdateRectangles()
{
base.UpdateRectangles();
const float buttonMargin = Constants.NodeCloseButtonMargin;
const float buttonSize = Constants.NodeCloseButtonSize;
_resizeButtonRect = new Rectangle(_closeButtonRect.Left, Height - buttonSize - buttonMargin - 4, buttonSize, buttonSize);
}
private void EndResizing()
{
Cursor = CursorType.Default;
EndMouseCapture();
_isResizing = false;
if (_startResizingSize != Size)
{
var emptySize = CalculateNodeSize(0, 0);
SizeValue = Size - emptySize;
Surface.MarkAsEdited(false);
}
ResizeBorderControl.Parent = null;
base.OnDestroy();
}
}
}
+19 -22
View File
@@ -65,6 +65,8 @@ namespace FlaxEditor.Surface
EndEditOnClick = false, // We have to handle this ourselves, otherwise the textbox instantly loses focus when double-clicking the header
HorizontalAlignment = TextAlignment.Center,
};
ResizeBorderControl.IgnoreSurfaceIndex = false;
}
/// <inheritdoc />
@@ -86,7 +88,7 @@ namespace FlaxEditor.Surface
}
else if (OrderValue != -1)
{
IndexInParent = OrderValue;
ResizeBorderControl.IndexInParent = OrderValue;
}
}
@@ -99,8 +101,8 @@ namespace FlaxEditor.Surface
Color = ColorValue = Color.FromHSV(new Random().NextFloat(0, 360), 0.7f, 0.25f, 0.8f);
if (OrderValue == -1)
OrderValue = Context.CommentCount - 1;
IndexInParent = OrderValue;
OrderValue = Context.CommentCount;
ResizeBorderControl.IndexInParent = OrderValue;
}
/// <inheritdoc />
@@ -130,7 +132,6 @@ namespace FlaxEditor.Surface
_headerRect = new Rectangle(0, 0, Width, headerSize);
_closeButtonRect = new Rectangle(Width - buttonSize * 0.75f - buttonMargin, buttonMargin, buttonSize * 0.75f, buttonSize * 0.75f);
_colorButtonRect = new Rectangle(_closeButtonRect.Left - buttonSize - buttonMargin, buttonMargin, buttonSize, buttonSize);
_resizeButtonRect = new Rectangle(_closeButtonRect.Left, Height - buttonSize - buttonMargin, buttonSize, buttonSize);
_renameTextBox.Width = Width;
_renameTextBox.Height = headerSize;
}
@@ -188,13 +189,9 @@ namespace FlaxEditor.Surface
// Color button
Render2D.DrawSprite(style.Settings, _colorButtonRect, _colorButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
// Resize button
if (_isResizing)
{
Render2D.FillRectangle(_resizeButtonRect, style.Selection);
Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder);
}
Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
// Resize
if ((ResizeBorderControl.IsResizing || ResizeBorderControl.IsMouseOverResizeBorder) && !Surface.IsConnecting)
Render2D.DrawRectangle(new Rectangle(Float2.Zero, Size), Style.Current.Foreground, 0.5f);
}
// Selection outline
@@ -229,7 +226,7 @@ namespace FlaxEditor.Surface
/// <inheritdoc />
public override bool ContainsPoint(ref Float2 location, bool precise)
{
return _headerRect.Contains(ref location) || _resizeButtonRect.Contains(ref location);
return _headerRect.Contains(ref location);
}
/// <inheritdoc />
@@ -334,25 +331,25 @@ namespace FlaxEditor.Surface
{
cmOrder.ContextMenu.AddButton("Bring Forward", () =>
{
if (IndexInParent < Context.CommentCount - 1)
IndexInParent++;
OrderValue = IndexInParent;
if (ResizeBorderControl.IndexInParent < Context.CommentCount - 1)
ResizeBorderControl.IndexInParent++;
OrderValue = ResizeBorderControl.IndexInParent;
});
cmOrder.ContextMenu.AddButton("Bring to Front", () =>
{
IndexInParent = Context.CommentCount - 1;
OrderValue = IndexInParent;
ResizeBorderControl.IndexInParent = Context.CommentCount - 1;
OrderValue = ResizeBorderControl.IndexInParent;
});
cmOrder.ContextMenu.AddButton("Send Backward", () =>
{
if (IndexInParent > 0)
IndexInParent--;
OrderValue = IndexInParent;
if (ResizeBorderControl.IndexInParent > 0)
ResizeBorderControl.IndexInParent--;
OrderValue = ResizeBorderControl.IndexInParent;
});
cmOrder.ContextMenu.AddButton("Send to Back", () =>
{
IndexInParent = 0;
OrderValue = IndexInParent;
ResizeBorderControl.IndexInParent = 0;
OrderValue = ResizeBorderControl.IndexInParent;
});
}
}
+3 -3
View File
@@ -46,10 +46,10 @@ namespace FlaxEditor.Surface
{
var child = _children[i];
if (child is SurfaceComment && child.Visible)
if (child is ResizableSurfaceNode.ResizeBorder border && border.ResizableNode is SurfaceComment comment2 && comment2.Visible)
{
Render2D.PushTransform(ref child._cachedTransform);
child.Draw();
Render2D.PushTransform(ref comment2._cachedTransform);
comment2.Draw();
Render2D.PopTransform();
}
}
+6 -4
View File
@@ -62,6 +62,7 @@ namespace FlaxEditor.Surface
private int _selectedConnectionIndex;
internal int _isUpdatingBoxTypes;
internal int resizeableNodeIndexInParent = -1;
/// <summary>
/// True if surface supports implicit casting of the FlaxEngine.Object types into Boolean value (as simple validate check).
@@ -855,10 +856,11 @@ namespace FlaxEditor.Surface
int lowestCommentOrder = int.MaxValue;
for (int i = 0; i < selection.Count; i++)
{
if (selection[i] is not SurfaceComment || selection[i].IndexInParent >= lowestCommentOrder)
continue;
hasCommentsSelected = true;
lowestCommentOrder = selection[i].IndexInParent;
if (selection[i] is ResizableSurfaceNode node && node is SurfaceComment && node.ResizeBorderControl.IndexInParent < lowestCommentOrder)
{
hasCommentsSelected = true;
lowestCommentOrder = node.ResizeBorderControl.IndexInParent;
}
}
return _context.CreateComment(ref surfaceArea, string.IsNullOrEmpty(text) ? "Comment" : text, new Color(1.0f, 1.0f, 1.0f, 0.2f), hasCommentsSelected ? lowestCommentOrder : -1);
@@ -80,8 +80,9 @@ namespace FlaxEditor.Surface
var result = new List<SurfaceComment>();
for (int i = 0; i < RootControl.Children.Count; i++)
{
if (RootControl.Children[i] is SurfaceComment comment)
result.Add(comment);
var child = RootControl.Children[i];
if (child is ResizableSurfaceNode.ResizeBorder border && border.ResizableNode is SurfaceComment comment2)
result.Add(comment2);
}
return result;
}
@@ -101,7 +102,8 @@ namespace FlaxEditor.Surface
int count = 0;
for (int i = 0; i < RootControl.Children.Count; i++)
{
if (RootControl.Children[i] is SurfaceComment)
var child = RootControl.Children[i];
if (child is ResizableSurfaceNode.ResizeBorder border && border.ResizableNode is SurfaceComment)
count++;
}
return count;
@@ -39,8 +39,16 @@ namespace FlaxEditor.Surface
{
"Newtonsoft.Json.",
"System.Array",
"System.ComponentModel.",
"System.Linq.Expressions.",
"System.Reflection.",
"System.Runtime.CompilerServices.",
"System.Runtime.InteropServices.",
"System.Runtime.Intrinsics.",
"System.Security.",
"System.Text.",
"System.Xml.",
"MS.",
};
private static NodesCache _nodesCache = new NodesCache(IterateNodesCache);
+6
View File
@@ -1293,6 +1293,12 @@ namespace FlaxEditor.Utilities
};
#elif PLATFORM_WINDOWS
return !Editor.Instance.Options.Options.Interface.UseNativeWindowSystem;
#elif PLATFORM_MAC
return Editor.Instance.Options.Options.Interface.WindowDecorations switch
{
Options.InterfaceOptions.WindowDecorationsType.ClientSide => true,
_ => false
};
#else
return false;
#endif
@@ -157,6 +157,7 @@ namespace FlaxEditor.Windows.Assets
private void Setter(object instance, int index, object value)
{
CheckForNullValue(ref value, _proxy.DefaultValues[_name].GetType());
if (_isDefault)
_proxy.DefaultValues[_name] = value;
else
@@ -251,6 +252,8 @@ namespace FlaxEditor.Windows.Assets
typeof(Rectangle),
typeof(Matrix),
typeof(string),
typeof(Texture),
typeof(CubeTexture),
};
public override void Initialize(LayoutElementsContainer layout)
@@ -272,7 +275,6 @@ namespace FlaxEditor.Windows.Assets
{
var name = e.Key;
var value = _proxy.Asset.GetValue(name);
var valueContainer = new VariableValueContainer(_proxy, name, value, false);
var propertyLabel = new PropertyNameLabel(name)
{
Tag = name,
@@ -280,7 +282,15 @@ namespace FlaxEditor.Windows.Assets
string tooltip = null;
if (_proxy.DefaultValues.TryGetValue(name, out var defaultValue))
tooltip = "Default value: " + defaultValue;
layout.Object(propertyLabel, valueContainer, null, tooltip);
var property = layout.AddPropertyItem(propertyLabel, tooltip);
if (value == null)
{
property.Label("null").Label.TextColor = Color.Red;
continue;
}
var valueContainer = new VariableValueContainer(_proxy, name, value, false);
valueContainer.SetDefaultValue(defaultValue);
property.Object(valueContainer);
}
}
else
@@ -289,19 +299,37 @@ namespace FlaxEditor.Windows.Assets
{
var name = e.Key;
var value = e.Value;
var valueContainer = new VariableValueContainer(_proxy, name, value, true);
var propertyLabel = new ClickablePropertyNameLabel(name)
{
Tag = name,
};
propertyLabel.MouseLeftDoubleClick += (label, location) => StartParameterRenaming(name, label);
propertyLabel.SetupContextMenu += OnPropertyLabelSetupContextMenu;
layout.Object(propertyLabel, valueContainer, null, "Type: " + CustomEditorsUtil.GetTypeNameUI(value.GetType()));
var tooltip = value != null ? "Type: " + CustomEditorsUtil.GetTypeNameUI(value.GetType()) : string.Empty;
var property = layout.AddPropertyItem(propertyLabel, tooltip);
if (value == null)
{
property.Label("null").Label.TextColor = Color.Red;
continue;
}
var valueContainer = new VariableValueContainer(_proxy, name, value, true);
property.Object(valueContainer);
}
if (_proxy.DefaultValues.Count == 0)
{
var emptyLabel = layout.Label("Empty", TextAlignment.Center).Label;
emptyLabel.TextColor = emptyLabel.TextColorHighlighted = FlaxEngine.GUI.Style.Current.ForegroundDisabled;
}
// TODO: improve the UI
layout.Space(40);
var addParamType = layout.ComboBox().ComboBox;
var addPanel = layout.HorizontalPanel();
addPanel.Panel.Size = new Float2(0, TextBox.DefaultHeight);
addPanel.Panel.Margin = Margin.Zero;
addPanel.Panel.Spacing = Utilities.Constants.UIMargin;
addPanel.Label("New value type:");
var addParamType = addPanel.ComboBox().ComboBox;
object lastValue = null;
foreach (var e in _proxy.DefaultValues)
lastValue = e.Value;
@@ -314,7 +342,7 @@ namespace FlaxEditor.Windows.Assets
addParamType.Items = allowedTypes;
addParamType.SelectedIndex = index;
_addParamType = addParamType;
var addParamButton = layout.Button("Add").Button;
var addParamButton = addPanel.Button("Add").Button;
addParamButton.Clicked += OnAddParamButtonClicked;
}
}
@@ -344,6 +372,7 @@ namespace FlaxEditor.Windows.Assets
Name = Utilities.Utils.IncrementNameNumber("New parameter", x => OnParameterRenameValidate(null, x)),
DefaultValue = TypeUtils.GetDefaultValue(new ScriptType(type)),
};
CheckForNullValue(ref action.DefaultValue, type);
_proxy.Window.Undo.AddAction(action);
action.Do();
}
@@ -387,6 +416,26 @@ namespace FlaxEditor.Windows.Assets
}
}
private static void CheckForNullValue(ref object value, Type type)
{
if (value == null)
{
// Default values are invalid as Variant type is used in C++ to properly bind the value
if (typeof(CubeTexture).IsAssignableFrom(type))
{
// Default cube texture
value = FlaxEngine.Content.LoadAsyncInternal<CubeTexture>(EditorAssets.DefaultSkyCubeTexture);
}
else if (typeof(Texture).IsAssignableFrom(type))
{
// Default texture
value = FlaxEngine.Content.LoadAsyncInternal<Texture>("Engine/Textures/BlackTexture");
}
else
throw new Exception("Null values are not allowed in Gameplay Globals");
}
}
private CustomEditorPresenter _propertiesEditor;
private PropertiesProxy _proxy;
private ToolStripButton _saveButton;
@@ -81,28 +81,10 @@ namespace FlaxEditor.Windows
_navigationUndo.Push(source);
}
// Show folder contents and select tree node
if (!_showAllContentInTree)
RefreshView(target);
_tree.Select(target);
target.ExpandAllParents();
// Clear redo list
_navigationRedo.Clear();
// Set valid sizes for stacks
//RedoList.SetSize(32);
//UndoList.SetSize(32);
// Update search
if (!_showAllContentInTree)
UpdateItemsSearch();
// Unlock navigation
_navigationUnlocked = true;
// Update UI
UpdateUI();
DoNavigate(target);
}
}
@@ -123,25 +105,7 @@ namespace FlaxEditor.Windows
// Add to Redo list
_navigationRedo.Push(SelectedNode);
// Select node
if (!_showAllContentInTree)
RefreshView(node);
_tree.Select(node);
node.ExpandAllParents();
// Set valid sizes for stacks
//RedoList.SetSize(32);
//UndoList.SetSize(32);
// Update search
if (!_showAllContentInTree)
UpdateItemsSearch();
// Unlock navigation
_navigationUnlocked = true;
// Update UI
UpdateUI();
DoNavigate(node);
if (!_showAllContentInTree)
_view.SelectFirstItem();
}
@@ -164,25 +128,7 @@ namespace FlaxEditor.Windows
// Add to Undo list
_navigationUndo.Push(SelectedNode);
// Select node
if (!_showAllContentInTree)
RefreshView(node);
_tree.Select(node);
node.ExpandAllParents();
// Set valid sizes for stacks
//RedoList.SetSize(32);
//UndoList.SetSize(32);
// Update search
if (!_showAllContentInTree)
UpdateItemsSearch();
// Unlock navigation
_navigationUnlocked = true;
// Update UI
UpdateUI();
DoNavigate(node);
if (!_showAllContentInTree)
_view.SelectFirstItem();
}
@@ -214,6 +160,32 @@ namespace FlaxEditor.Windows
UpdateUI();
}
private void DoNavigate(ContentFolderTreeNode node)
{
// Select node
if (!_showAllContentInTree)
RefreshView(node);
_tree.Select(node);
node.ExpandAllParents();
// Set valid sizes for stacks
//RedoList.SetSize(32);
//UndoList.SetSize(32);
// Update search
if (!_showAllContentInTree)
UpdateItemsSearch();
// Unlock navigation
_navigationUnlocked = true;
UpdateUI();
// Clear auto-select cache for new/imported files
_newFilesCache?.Clear();
_newFilesCacheSize = 0;
}
private void UpdateNavigationBar()
{
if (_navigationBar == null)
+94 -40
View File
@@ -67,6 +67,8 @@ namespace FlaxEditor.Windows
private readonly Stack<ContentFolderTreeNode> _navigationRedo = new Stack<ContentFolderTreeNode>(32);
private NewItem _newElement;
private List<string> _newFilesCache;
private int _newFilesCacheSize;
/// <summary>
/// Gets the toolstrip.
@@ -598,21 +600,9 @@ namespace FlaxEditor.Windows
// Disable scrolling in proper view
_renameInTree = _showAllContentInTree;
if (_renameInTree)
{
if (_contentTreePanel.VScrollBar != null)
_contentTreePanel.VScrollBar.ThumbEnabled = false;
if (_contentTreePanel.HScrollBar != null)
_contentTreePanel.HScrollBar.ThumbEnabled = false;
ScrollingOnTreeView(false);
}
else
{
if (_contentViewPanel.VScrollBar != null)
_contentViewPanel.VScrollBar.ThumbEnabled = false;
if (_contentViewPanel.HScrollBar != null)
_contentViewPanel.HScrollBar.ThumbEnabled = false;
ScrollingOnContentView(false);
}
// Show rename popup
RenamePopup popup;
@@ -664,21 +654,9 @@ namespace FlaxEditor.Windows
{
// Restore scrolling in proper view
if (_renameInTree)
{
if (_contentTreePanel.VScrollBar != null)
_contentTreePanel.VScrollBar.ThumbEnabled = true;
if (_contentTreePanel.HScrollBar != null)
_contentTreePanel.HScrollBar.ThumbEnabled = true;
ScrollingOnTreeView(true);
}
else
{
if (_contentViewPanel.VScrollBar != null)
_contentViewPanel.VScrollBar.ThumbEnabled = true;
if (_contentViewPanel.HScrollBar != null)
_contentViewPanel.HScrollBar.ThumbEnabled = true;
ScrollingOnContentView(true);
}
_renameInTree = false;
// Check if was creating new element
@@ -704,7 +682,6 @@ namespace FlaxEditor.Windows
// Check if can rename this item
if (!item.CanRename)
{
// Cannot
MessageBox.Show("Cannot rename this item.", "Cannot rename", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
@@ -718,7 +695,6 @@ namespace FlaxEditor.Windows
// Check if name is valid
if (!Editor.ContentEditing.IsValidAssetName(item, newShortName, out string hint))
{
// Invalid name
MessageBox.Show("Given asset name is invalid. " + hint, "Invalid name", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
@@ -740,6 +716,7 @@ namespace FlaxEditor.Windows
// Note: we create `_newElement` and then rename it to create new asset
var itemFolder = item.ParentFolder;
Action<ContentItem> endEvent = null;
bool lazyCreation = false;
if (_newElement == item)
{
try
@@ -750,6 +727,9 @@ namespace FlaxEditor.Windows
var proxy = _newElement.Proxy;
Editor.Log(string.Format("Creating asset {0} in {1}", proxy.Name, newPath));
proxy.Create(newPath, _newElement.Argument);
// When creating item with options dialog deffer processing
lazyCreation = !File.Exists(newPath);
}
catch (Exception ex)
{
@@ -773,6 +753,16 @@ namespace FlaxEditor.Windows
if (_newElement.Proxy is ScriptProxy && Editor.Instance.Options.Options.General.AutoReloadScriptsOnMainWindowFocus)
ScriptsBuilder.MarkWorkspaceDirty();
// Cache new file to be auto-selected after actual creation
_newFilesCache?.Clear();
_newFilesCacheSize = 0;
if (lazyCreation)
{
_newFilesCache ??= new List<string>();
_newFilesCache.Add(newPath);
_newFilesCacheSize = 1;
}
// Destroy mock control
_newElement.ParentFolder = null;
_newElement.Dispose();
@@ -789,7 +779,8 @@ namespace FlaxEditor.Windows
var newItem = itemFolder.FindChild(newPath);
if (newItem == null)
{
Editor.LogWarning("Failed to find the created new item.");
if (!lazyCreation)
Editor.LogWarning("Failed to find the created new item.");
return;
}
@@ -1143,11 +1134,12 @@ namespace FlaxEditor.Windows
}
/// <summary>
/// Selects the specified item in the content view.
/// Selects the specified item in the content view. Does nothing if the current view doesn't show the folder containing that item.
/// </summary>
/// <param name="item">The item to select.</param>
/// <param name="fastScroll">True of scroll to the item quickly without smoothing.</param>
public void Select(ContentItem item, bool fastScroll = false)
/// <param name="additive">True of select item in additive mode with existing selection preservation, otherwise current selection will be cleared.</param>
public void Select(ContentItem item, bool fastScroll = false, bool additive = false)
{
if (item == null)
throw new ArgumentNullException();
@@ -1169,7 +1161,7 @@ namespace FlaxEditor.Windows
targetNode.ExpandAllParents();
if (item is ContentFolder)
{
_tree.Select(targetNode);
_tree.Select(targetNode, additive);
_contentTreePanel.ScrollViewTo(targetNode, fastScroll);
targetNode.Focus();
}
@@ -1178,13 +1170,13 @@ namespace FlaxEditor.Windows
var itemNode = FindTreeItemNode(targetNode, item);
if (itemNode != null)
{
_tree.Select(itemNode);
_tree.Select(itemNode, additive);
_contentTreePanel.ScrollViewTo(itemNode, fastScroll);
itemNode.Focus();
}
else
{
_tree.Select(targetNode);
_tree.Select(targetNode, additive);
}
}
}
@@ -1195,7 +1187,7 @@ namespace FlaxEditor.Windows
Navigate(parent.Node);
// Select and scroll to cover in view
_view.Select(item);
_view.Select(item, additive);
_contentViewPanel.ScrollViewTo(item, fastScroll);
// Focus
@@ -1574,7 +1566,7 @@ namespace FlaxEditor.Windows
/// <inheritdoc />
public override void OnInit()
{
// Content database events
// Content events
Editor.ContentDatabase.WorkspaceModified += () => _isWorkspaceDirty = true;
Editor.ContentDatabase.ItemAdded += OnContentDatabaseItemAdded;
Editor.ContentDatabase.ItemRemoved += OnContentDatabaseItemRemoved;
@@ -1594,6 +1586,9 @@ namespace FlaxEditor.Windows
else if (_root != null)
ShowRoot();
};
Editor.ContentImporting.ImportFileBegin += OnImportFileBegin;
Editor.ContentImporting.ImportFileEnd += OnImportFileEnd;
Editor.ContentImporting.ImportingQueueBegin += OnImportingQueueBegin;
LoadExpandedFolders();
Refresh();
@@ -1631,6 +1626,64 @@ namespace FlaxEditor.Windows
OnFoldersSearchBoxTextChanged();
}
private void OnImportFileBegin(IFileEntryAction entry)
{
// Add to auto-select cache
_newFilesCache ??= new List<string>();
_newFilesCache.Add(entry.ResultUrl);
_newFilesCacheSize++;
}
private void OnImportFileEnd(IFileEntryAction entry, bool failed)
{
if (failed)
return;
if (!Platform.IsInMainThread)
{
FlaxEngine.Scripting.InvokeOnUpdate(() => OnImportFileEnd(entry, false));
return;
}
// Refresh view (gives faster response than waiting for filesystem event)
//RefreshView(); // TODO: is this still needed?
// Auto-select pending items
if (_newFilesCache != null && _newFilesCache.Contains(entry.ResultUrl))
{
var item = EnsureItem(entry.ResultUrl);
if (item != null)
{
bool additive = _newFilesCache.Count != _newFilesCacheSize;
Select(item, true, additive);
}
_newFilesCache.Remove(entry.ResultUrl);
}
}
private void OnImportingQueueBegin()
{
// Clear cache to auto-select all imported files
_newFilesCache?.Clear();
_newFilesCacheSize = 0;
}
private ContentItem EnsureItem(string path)
{
var item = Editor.ContentDatabase.Find(path);
if (item == null)
{
// Cannot find the item (eg. just created file, content database event not yet handled) so refresh to take effect quickly
var parentPath = Path.GetDirectoryName(path);
var parentItem = Editor.ContentDatabase.Find(parentPath);
if (parentItem != null)
{
Editor.ContentDatabase.RefreshFolder(parentItem, false);
item = Editor.ContentDatabase.Find(path);
}
}
return item;
}
private void Refresh()
{
// Setup content root node
@@ -1774,16 +1827,11 @@ namespace FlaxEditor.Windows
return base.OnMouseUp(location, button);
}
/// <inheritdoc />
protected override void PerformLayoutBeforeChildren()
{
base.PerformLayoutBeforeChildren();
}
/// <inheritdoc />
protected override void PerformLayoutAfterChildren()
{
base.PerformLayoutAfterChildren();
UpdateNavigationBarBounds();
}
@@ -1846,12 +1894,18 @@ namespace FlaxEditor.Windows
_treeHeaderPanel = null;
_treeOnlyPanel = null;
_contentItemsSearchPanel = null;
_newFilesCache = null;
Editor.Options.OptionsChanged -= OnOptionsChanged;
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd;
if (Editor?.ContentDatabase != null)
{
Editor.ContentDatabase.ItemAdded -= OnContentDatabaseItemAdded;
Editor.ContentImporting.ImportFileBegin -= OnImportFileBegin;
Editor.ContentImporting.ImportFileEnd -= OnImportFileEnd;
Editor.ContentImporting.ImportingQueueBegin -= OnImportingQueueBegin;
}
base.OnDestroy();
}
+24 -5
View File
@@ -433,25 +433,34 @@ namespace FlaxEditor.Windows
writer.WriteAttributeString("GridEnabled", Viewport.Grid.Enabled.ToString());
writer.WriteAttributeString("ShowFpsCounter", Viewport.ShowFpsCounter.ToString());
writer.WriteAttributeString("ShowNavigation", Viewport.ShowNavigation.ToString());
writer.WriteAttributeString("UseOrthographicProjection", Viewport.UseOrthographicProjection.ToString());
writer.WriteAttributeString("NearPlane", Viewport.NearPlane.ToString());
writer.WriteAttributeString("FarPlane", Viewport.FarPlane.ToString());
writer.WriteAttributeString("FieldOfView", Viewport.FieldOfView.ToString());
writer.WriteAttributeString("MovementSpeed", Viewport.MovementSpeed.ToString());
writer.WriteAttributeString("Brightness", Viewport.Brightness.ToString());
writer.WriteAttributeString("ViewportIconsScale", ViewportIconsRenderer.Scale.ToString());
writer.WriteAttributeString("ResolutionScale", Viewport.ResolutionScale.ToString());
writer.WriteAttributeString("OrthographicScale", Viewport.OrthographicScale.ToString());
writer.WriteAttributeString("UseOrthographicProjection", Viewport.UseOrthographicProjection.ToString());
writer.WriteAttributeString("ViewFlags", ((ulong)Viewport.Task.View.Flags).ToString());
writer.WriteAttributeString("DebugView", ((int)Viewport.Task.ViewMode).ToString());
writer.WriteAttributeString("LayerMask", Viewport.Task.ViewLayersMask.Mask.ToString());
}
/// <inheritdoc />
public override void OnLayoutDeserialize(XmlElement node)
{
if (!Editor.Options.Options.Viewport.UsePersistenceOverDefaults)
return;
if (bool.TryParse(node.GetAttribute("GridEnabled"), out bool value1))
Viewport.Grid.Enabled = value1;
if (bool.TryParse(node.GetAttribute("ShowFpsCounter"), out value1))
Viewport.ShowFpsCounter = value1;
if (bool.TryParse(node.GetAttribute("ShowNavigation"), out value1))
Viewport.ShowNavigation = value1;
if (bool.TryParse(node.GetAttribute("UseOrthographicProjection"), out value1))
Viewport.UseOrthographicProjection = value1;
if (float.TryParse(node.GetAttribute("NearPlane"), out float value2))
Viewport.NearPlane = value2;
if (float.TryParse(node.GetAttribute("FarPlane"), out value2))
@@ -460,18 +469,28 @@ namespace FlaxEditor.Windows
Viewport.FieldOfView = value2;
if (float.TryParse(node.GetAttribute("MovementSpeed"), out value2))
Viewport.MovementSpeed = value2;
if (float.TryParse(node.GetAttribute("Brightness"), out value2))
Viewport.Brightness = value2;
if (float.TryParse(node.GetAttribute("ResolutionScale"), out value2))
Viewport.ResolutionScale = value2;
if (float.TryParse(node.GetAttribute("ViewportIconsScale"), out value2))
ViewportIconsRenderer.Scale = value2;
if (float.TryParse(node.GetAttribute("OrthographicScale"), out value2))
Viewport.OrthographicScale = value2;
if (bool.TryParse(node.GetAttribute("UseOrthographicProjection"), out value1))
Viewport.UseOrthographicProjection = value1;
if (ulong.TryParse(node.GetAttribute("ViewFlags"), out ulong value3))
Viewport.Task.ViewFlags = (ViewFlags)value3;
// Reset view flags if opening with different engine version (ViewFlags enum could be modified)
if (int.TryParse(node.GetAttribute("DebugView"), out int value4))
Viewport.Task.ViewMode = (ViewMode)value4;
if (uint.TryParse(node.GetAttribute("LayerMask"), out uint value5))
Viewport.Task.ViewLayersMask = new LayersMask(value5);
// Reset view flags and view mode if opening with different engine version
// (ViewFlags and ViewMode enums could be modified)
if (Editor.LastProjectOpenedEngineBuild != Globals.EngineBuildNumber)
{
Viewport.Task.ViewFlags = ViewFlags.DefaultEditor;
Viewport.Task.ViewMode = ViewMode.Default;
}
}
/// <inheritdoc />
+2 -1
View File
@@ -821,7 +821,8 @@ namespace FlaxEditor.Windows
{
base.Draw();
if (Camera.MainCamera == null)
var mainRenderTask = MainRenderTask.Instance;
if (Camera.MainCamera == null && (mainRenderTask == null || !mainRenderTask.IsCustomRendering))
{
var style = Style.Current;
Render2D.DrawText(style.FontLarge, "No camera", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center);
+1 -1
View File
@@ -433,7 +433,7 @@ void Asset::Reload()
// Cancel any still-running loading task (e.g. if WaitForLoaded timed out)
Platform::AtomicStore(&_loadingTask, 0);
if (IsLoaded())
if (IsLoaded() || LastLoadFailed())
{
// Unload current data
unload(true);
+19 -8
View File
@@ -85,21 +85,31 @@ CreateAssetResult ImportAudio::Import(CreateAssetContext& context, AudioDecoder&
LOG(Info, "Audio: {0}kHz, channels: {1}, Bit depth: {2}, Length: {3}s", info.SampleRate / 1000.0f, info.NumChannels, info.BitDepth, info.GetLength());
// Load the whole audio data
uint32 bytesPerSample = info.BitDepth / 8;
uint32 bufferSize = info.NumSamples * bytesPerSample;
DataContainer<byte> sampleBuffer;
sampleBuffer.Link(audioData.Get());
sampleBuffer.Link(audioData.Get(), info.NumSamples * (info.BitDepth / 8));
if (!Math::IsOne(options.Volume))
{
// Scale PCM signal
Array<float> pcm;
pcm.Resize(info.NumSamples);
AudioTool::ConvertToFloat(sampleBuffer.Get(), info.BitDepth, pcm.Get(), info.NumSamples);
for (float& e : pcm)
e *= options.Volume;
sampleBuffer.Allocate(info.NumSamples * sizeof(int32));
AudioTool::ConvertFromFloat(pcm.Get(), (int32*)sampleBuffer.Get(), info.NumSamples);
info.BitDepth = 32;
}
// Convert bit depth if need to
uint32 outputBitDepth = (uint32)options.BitDepth;
if (outputBitDepth != info.BitDepth)
{
DataContainer<byte> sampleBufferPrev = MoveTemp(sampleBuffer);
const uint32 outBufferSize = info.NumSamples * (outputBitDepth / 8);
sampleBuffer.Allocate(outBufferSize);
AudioTool::ConvertBitDepth(audioData.Get(), info.BitDepth, sampleBuffer.Get(), outputBitDepth, info.NumSamples);
AudioTool::ConvertBitDepth(sampleBufferPrev.Get(), info.BitDepth, sampleBuffer.Get(), outputBitDepth, info.NumSamples);
info.BitDepth = outputBitDepth;
bytesPerSample = info.BitDepth / 8;
bufferSize = outBufferSize;
}
// Base
@@ -157,13 +167,14 @@ CreateAssetResult ImportAudio::Import(CreateAssetContext& context, AudioDecoder&
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
WRITE_DATA(0, sampleBuffer.Get(), bufferSize);
WRITE_DATA(0, sampleBuffer.Get(), sampleBuffer.Length());
}
else
{
// Split audio data into a several chunks (uniform data spread)
const uint32 minChunkSize = 1 * 1024 * 1024; // 1 MB
const uint32 dataAlignment = info.NumChannels * bytesPerSample * ASSET_FILE_DATA_CHUNKS; // Ensure to never split samples in-between (eg. 24-bit that uses 3 bytes)
const uint32 bufferSize = sampleBuffer.Length();
const uint32 dataAlignment = info.NumChannels * (info.BitDepth / 8) * ASSET_FILE_DATA_CHUNKS; // Ensure to never split samples in-between (eg. 24-bit that uses 3 bytes)
const uint32 chunkSize = Math::AlignUp(Math::Max(minChunkSize, bufferSize / ASSET_FILE_DATA_CHUNKS), dataAlignment);
const int32 chunksCount = Math::CeilToInt((float)bufferSize / (float)chunkSize);
ASSERT(chunksCount > 0 && chunksCount <= ASSET_FILE_DATA_CHUNKS);
+46 -45
View File
@@ -264,29 +264,29 @@ namespace FlaxEngine
}
/// <summary>
/// Creates a new frustum relaying on perspective camera parameters
/// Creates a new frustum based on a perspective camera parameters.
/// </summary>
/// <param name="cameraPos">The camera pos.</param>
/// <param name="lookDir">The look dir.</param>
/// <param name="upDir">Up dir.</param>
/// <param name="fov">The fov.</param>
/// <param name="znear">The Z near.</param>
/// <param name="zfar">The Z far.</param>
/// <param name="aspect">The aspect.</param>
/// <returns>The bounding frustum calculated from perspective camera</returns>
public static BoundingFrustum FromCamera(Vector3 cameraPos, Vector3 lookDir, Vector3 upDir, float fov, float znear, float zfar, float aspect)
/// <param name="cameraPos">The camera position.</param>
/// <param name="lookDir">The look direction.</param>
/// <param name="upDir">Up direction.</param>
/// <param name="fov">The fov in radians.</param>
/// <param name="zNear">The Z near plane.</param>
/// <param name="zFar">The Z far plane.</param>
/// <param name="aspectRatio">The aspect ratio.</param>
/// <returns>The bounding frustum calculated from the perspective camera</returns>
public static BoundingFrustum FromCamera(Vector3 cameraPos, Vector3 lookDir, Vector3 upDir, float fov, float zNear, float zFar, float aspectRatio)
{
//http://knol.google.com/k/view-frustum
lookDir = Vector3.Normalize(lookDir);
upDir = Vector3.Normalize(upDir);
Vector3 nearCenter = cameraPos + lookDir * znear;
Vector3 farCenter = cameraPos + lookDir * zfar;
var nearHalfHeight = (float)(znear * Math.Tan(fov / 2f));
var farHalfHeight = (float)(zfar * Math.Tan(fov / 2f));
float nearHalfWidth = nearHalfHeight * aspect;
float farHalfWidth = farHalfHeight * aspect;
Vector3 nearCenter = cameraPos + lookDir * zNear;
Vector3 farCenter = cameraPos + lookDir * zFar;
var nearHalfHeight = (float)(zNear * Math.Tan(fov / 2f));
var farHalfHeight = (float)(zFar * Math.Tan(fov / 2f));
float nearHalfWidth = nearHalfHeight * aspectRatio;
float farHalfWidth = farHalfHeight * aspectRatio;
Vector3 rightDir = Vector3.Normalize(Vector3.Cross(upDir, lookDir));
Vector3 near1 = nearCenter - nearHalfHeight * upDir + nearHalfWidth * rightDir;
@@ -315,20 +315,21 @@ namespace FlaxEngine
result.pTop.Normalize();
result.pBottom.Normalize();
result.pMatrix = Matrix.LookAt(cameraPos, cameraPos + lookDir * 10, upDir) * Matrix.PerspectiveFov(fov, aspect, znear, zfar);
result.pMatrix = Matrix.LookAt(cameraPos, cameraPos + lookDir * 10, upDir) * Matrix.PerspectiveFov(fov, aspectRatio, zNear, zFar);
return result;
}
/// <summary>
/// Returns the 8 corners of the frustum, element0 is Near1 (near right down corner)
/// , element1 is Near2 (near right top corner)
/// , element2 is Near3 (near Left top corner)
/// , element3 is Near4 (near Left down corner)
/// , element4 is Far1 (far right down corner)
/// , element5 is Far2 (far right top corner)
/// , element6 is Far3 (far left top corner)
/// , element7 is Far4 (far left down corner)
/// Returns the 8 corners of the frustum:
/// <para>[0] is Near1 (Near right down corner)</para>
/// <para>[1] is Near2 (Near right top corner)</para>
/// <para>[2] is Near3 (Near left top corner)</para>
/// <para>[3] is Near4 (Near left down corner)</para>
/// <para>[4] is Far1 (Far right down corner)</para>
/// <para>[5] is Far2 (Far right top corner)</para>
/// <para>[6] is Far3 (Far left top corner)</para>
/// <para>[7] is Far4 (Far left down corner)</para>
/// </summary>
/// <returns>The 8 corners of the frustum</returns>
public Vector3[] GetCorners()
@@ -339,16 +340,16 @@ namespace FlaxEngine
}
/// <summary>
/// Returns the 8 corners of the frustum, element0 is Near1 (near right down corner)
/// , element1 is Near2 (near right top corner)
/// , element2 is Near3 (near Left top corner)
/// , element3 is Near4 (near Left down corner)
/// , element4 is Far1 (far right down corner)
/// , element5 is Far2 (far right top corner)
/// , element6 is Far3 (far left top corner)
/// , element7 is Far4 (far left down corner)
/// Populates the array with the 8 corners of the frustum:
/// <para>[0] is Near1 (Near right down corner)</para>
/// <para>[1] is Near2 (Near right top corner)</para>
/// <para>[2] is Near3 (Near left top corner)</para>
/// <para>[3] is Near4 (Near left down corner)</para>
/// <para>[4] is Far1 (Far right down corner)</para>
/// <para>[5] is Far2 (Far right top corner)</para>
/// <para>[6] is Far3 (Far left top corner)</para>
/// <para>[7] is Far4 (Far left down corner)</para>
/// </summary>
/// <returns>The 8 corners of the frustum</returns>
public void GetCorners(Vector3[] corners)
{
corners[0] = Get3PlanesInterPoint(ref pNear, ref pBottom, ref pRight); //Near1
@@ -362,7 +363,7 @@ namespace FlaxEngine
}
/// <summary>
/// Checks whether a point lay inside, intersects or lay outside the frustum.
/// Checks whether a point lays inside, intersects or lays outside the frustum.
/// </summary>
/// <param name="point">The point.</param>
/// <returns>Type of the containment</returns>
@@ -669,15 +670,15 @@ namespace FlaxEngine
{
if (Contains(ray.Position) != ContainmentType.Disjoint)
{
Real nearstPlaneDistance = Real.MaxValue;
Real nearestPlaneDistance = Real.MaxValue;
for (var i = 0; i < 6; i++)
{
Plane plane = GetPlane(i);
if (CollisionsHelper.RayIntersectsPlane(ref ray, ref plane, out Real distance) && (distance < nearstPlaneDistance))
nearstPlaneDistance = distance;
if (CollisionsHelper.RayIntersectsPlane(ref ray, ref plane, out Real distance) && (distance < nearestPlaneDistance))
nearestPlaneDistance = distance;
}
inDistance = nearstPlaneDistance;
inDistance = nearestPlaneDistance;
outDistance = null;
return true;
}
@@ -711,9 +712,9 @@ namespace FlaxEngine
}
/// <summary>
/// Get the distance which when added to camera position along the lookat direction will do the effect of zoom to extents (zoom to fit) operation, so all the passed points will fit in the current view.
/// if the returned value is positive, the camera will move toward the lookat direction (ZoomIn).
/// if the returned value is negative, the camera will move in the reverse direction of the lookat direction (ZoomOut).
/// Get the distance which when added to camera position along the look-at direction will do the effect of zoom to extents (zoom to fit) operation, so all the passed points will fit in the current view.
/// if the returned value is positive, the camera will move toward the look-at direction (ZoomIn).
/// if the returned value is negative, the camera will move in the reverse direction of the look-at direction (ZoomOut).
/// </summary>
/// <param name="points">The points.</param>
/// <returns>The zoom to fit distance</returns>
@@ -740,9 +741,9 @@ namespace FlaxEngine
}
/// <summary>
/// Get the distance which when added to camera position along the lookat direction will do the effect of zoom to extents (zoom to fit) operation, so all the passed points will fit in the current view.
/// if the returned value is positive, the camera will move toward the lookat direction (ZoomIn).
/// if the returned value is negative, the camera will move in the reverse direction of the lookat direction (ZoomOut).
/// Get the distance which when added to camera position along the look-at direction will do the effect of zoom to extents (zoom to fit) operation, so all the passed points will fit in the current view.
/// if the returned value is positive, the camera will move toward the look-at direction (ZoomIn).
/// if the returned value is negative, the camera will move in the reverse direction of the look-at direction (ZoomOut).
/// </summary>
/// <param name="boundingBox">The bounding box.</param>
/// <returns>The zoom to fit distance</returns>
+49 -5
View File
@@ -357,7 +357,7 @@ struct DebugDrawContext
Vector3 Origin = Vector3::Zero;
DebugDrawData DebugDrawDefault;
DebugDrawData DebugDrawDepthTest;
Float3 LastViewPos = Float3::Zero;
Vector3 LastViewPosition = Vector3::Zero;
Matrix LastViewProjection = Matrix::Identity;
BoundingFrustum LastViewFrustum;
@@ -790,9 +790,14 @@ bool DebugDraw::CanClear(void* context)
#endif
Vector3 DebugDraw::GetViewPos()
Vector3 DebugDraw::GetViewPosition()
{
return Context->LastViewPos;
return Context->LastViewPosition;
}
Vector3 DebugDraw::GetViewOrigin()
{
return Context->Origin;
}
BoundingFrustum DebugDraw::GetViewFrustum()
@@ -802,7 +807,7 @@ BoundingFrustum DebugDraw::GetViewFrustum()
void DebugDraw::SetView(const RenderView& view)
{
Context->LastViewPos = view.Position;
Context->LastViewPosition = view.WorldPosition;
Context->LastViewProjection = view.Projection;
Context->LastViewFrustum = view.Frustum;
}
@@ -1423,7 +1428,8 @@ void DebugDraw::DrawWireSphere(const BoundingSphere& sphere, const Color& color,
int32 index;
const Float3 centerF = sphere.Center - Context->Origin;
const float radiusF = (float)sphere.Radius;
const float screenRadiusSquared = RenderTools::ComputeBoundsScreenRadiusSquared(centerF, radiusF, Context->LastViewPos, Context->LastViewProjection);
const Float3 drawPos = Context->LastViewPosition - Context->Origin;
const float screenRadiusSquared = RenderTools::ComputeBoundsScreenRadiusSquared(centerF, radiusF, drawPos, Context->LastViewProjection);
if (screenRadiusSquared > DEBUG_DRAW_SPHERE_LOD0_SCREEN_SIZE * DEBUG_DRAW_SPHERE_LOD0_SCREEN_SIZE * 0.25f)
index = 0;
else if (screenRadiusSquared > DEBUG_DRAW_SPHERE_LOD1_SCREEN_SIZE * DEBUG_DRAW_SPHERE_LOD1_SCREEN_SIZE * 0.25f)
@@ -1523,6 +1529,44 @@ void DebugDraw::DrawCircle(const Vector3& position, const Float3& normal, float
}
}
void DebugDraw::DrawPoint(const Vector3& position, float radius, const Color& color, float duration, bool depthTest)
{
Float3 normal = (Float3)(Context->LastViewPosition - position);
if (normal.Length() < ZeroTolerance)
normal = Float3::Up;
normal.Normalize();
// Create matrix transform for unit circle points
Matrix world, scale, matrix;
Float3 right, up;
if (Float3::Dot(normal, Float3::Up) > 0.99f)
right = Float3::Right;
else if (Float3::Dot(normal, Float3::Down) > 0.99f)
right = Float3::Left;
else
Float3::Cross(normal, Float3::Up, right);
Float3::Cross(right, normal, up);
Matrix::Scaling(radius, scale);
const Float3 positionF = position - Context->Origin;
Matrix::CreateWorld(positionF, normal, up, world);
Matrix::Multiply(scale, world, matrix);
// Build a filled disc as a triangle fan from the center over the transformed unit circle points
PROFILE_MEM(EngineDebug);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
auto& debugDrawList = duration > 0 ? debugDrawData.DefaultTriangles : debugDrawData.OneFrameTriangles;
for (int32 i = 0; i < DEBUG_DRAW_CIRCLE_VERTICES; i += 2)
{
DebugTriangle t;
t.Color = Color32(color);
t.TimeLeft = duration;
t.V0 = positionF;
t.V1 = Float3::Transform(CircleCache[i], matrix);
t.V2 = Float3::Transform(CircleCache[i + 1], matrix);
debugDrawList.Add(t);
}
}
void DebugDraw::DrawWireTriangle(const Vector3& v0, const Vector3& v1, const Vector3& v2, const Color& color, float duration, bool depthTest)
{
DrawLine(v0, v1, color, duration, depthTest);
+15 -2
View File
@@ -74,8 +74,10 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
API_FUNCTION() static bool CanClear(void* context = nullptr);
#endif
// Gets the last view position when rendering the current context. Can be used for custom culling or LODing when drawing more complex shapes.
static Vector3 GetViewPos();
// Gets the last view position (world-space) when rendering the current context. Can be used for custom culling or LODing when drawing more complex shapes.
static Vector3 GetViewPosition();
// Gets the last view origin (world-space) when rendering the current context. Can be used for custom culling or LODing when drawing more complex shapes.
static Vector3 GetViewOrigin();
// Gets the last view frustum when rendering the current context. Can be used for custom culling or LODing when drawing more complex shapes.
static BoundingFrustum GetViewFrustum();
@@ -265,6 +267,16 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
/// <param name="duration">The duration (in seconds). Use 0 to draw it only once.</param>
/// <param name="depthTest">If set to <c>true</c> depth test will be performed, otherwise depth will be ignored.</param>
API_FUNCTION() static void DrawCircle(const Vector3& position, const Float3& normal, float radius, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true);
/// <summary>
/// Draws the point facing camera.
/// </summary>
/// <param name="position">The center position.</param>
/// <param name="radius">The radius.</param>
/// <param name="color">The color.</param>
/// <param name="duration">The duration (in seconds). Use 0 to draw it only once.</param>
/// <param name="depthTest">If set to <c>true</c> depth test will be performed, otherwise depth will be ignored.</param>
API_FUNCTION() static void DrawPoint(const Vector3& position, float radius, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true);
/// <summary>
/// Draws the wireframe triangle.
@@ -778,6 +790,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
#define DEBUG_DRAW_LINES(lines, transform, color, duration, depthTest) DebugDraw::DrawLines(lines, transform, color, duration, depthTest)
#define DEBUG_DRAW_BEZIER(p1, p2, p3, p4, color, duration, depthTest) DebugDraw::DrawBezier(p1, p2, p3, p4, color, duration, depthTest)
#define DEBUG_DRAW_CIRCLE(position, normal, radius, color, duration, depthTest) DebugDraw::DrawCircle(position, normal, radius, color, duration, depthTest)
#define DEBUG_DRAW_POINT(position, radius, color, duration, depthTest) DebugDraw::DrawPoint(position, radius, color, duration, depthTest)
#define DEBUG_DRAW_TRIANGLE(v0, v1, v2, color, duration, depthTest) DebugDraw::DrawTriangle(v0, v1, v2, color, duration, depthTest)
#define DEBUG_DRAW_TRIANGLES(vertices, color, duration, depthTest) DebugDraw::DrawTriangles(vertices, color, duration, depthTest)
#define DEBUG_DRAW_TRIANGLES_EX(vertices, indices, color, duration, depthTest) DebugDraw::DrawTriangles(vertices, indices, color, duration, depthTest)
+12
View File
@@ -149,6 +149,18 @@ bool GameplayGlobals::Save(const StringView& path)
return false;
}
void GameplayGlobals::GetReferences(Array<Guid>& assets, Array<String>& files) const
{
BinaryAsset::GetReferences(assets, files);
for (auto& e : Variables)
{
auto asset = (Asset*)e.Value.DefaultValue;
if (asset)
assets.Add(asset->GetID());
}
}
#endif
void GameplayGlobals::InitAsVirtual()
+1
View File
@@ -85,6 +85,7 @@ public:
void InitAsVirtual() override;
#if USE_EDITOR
bool Save(const StringView& path = StringView::Empty) override;
void GetReferences(Array<Guid>& assets, Array<String>& files) const override;
#endif
protected:
@@ -472,7 +472,24 @@ void MaterialParameter::Bind(BindMeta& meta) const
ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(Int4)));
*((Int4*)(meta.Constants.Get() + _offset)) = (Int4)e->Value.AsInt4();
break;
default: ;
case VariantType::Asset:
{
auto texture = Cast<TextureBase>(e->Value.AsAsset);
meta.Context->BindSR(_registerIndex, texture ? texture->GetTexture() : nullptr);
break;
}
case VariantType::Object:
{
// GPU Texture bind must match dimensions of the value archetype (eg. 2d or cube texture)
auto gpuTexture = Cast<GPUTexture>(e->Value.AsObject);
meta.Context->BindSR(_registerIndex, gpuTexture);
break;
}
default:
#if !BUILD_RELEASE
LOG(Warning, "Invalid Gameplay Global '{}' ({}) value type '{}' to bind to material", _name, _asAsset->GetPath(), e->Value.Type.ToString());
#endif
break;
}
}
}
+1 -2
View File
@@ -70,8 +70,7 @@ namespace
if (accessor.AllocateBuffer(MeshBufferType::Vertex0, vertexCount, vb0layout))
return true;
auto positionStream = accessor.Position();
ASSERT(positionStream.IsLinear(PixelFormat::R32G32B32_Float));
positionStream.SetLinear(vertices);
positionStream.Set(Span<Float3>(vertices, vertexCount));
}
// Vertex Buffer 1 (general purpose components)
@@ -44,7 +44,7 @@ namespace
// Index Buffer
{
if (accessor.AllocateBuffer(MeshBufferType::Index, triangleCount, indexFormat))
if (accessor.AllocateBuffer(MeshBufferType::Index, triangleCount * 3, indexFormat))
return true;
auto indexStream = accessor.Index();
ASSERT(indexStream.IsLinear(indexFormat));
@@ -73,8 +73,7 @@ namespace
return true;
auto positionStream = accessor.Position();
ASSERT(positionStream.IsLinear(PixelFormat::R32G32B32_Float));
positionStream.SetLinear(vertices);
positionStream.Set(Span<Float3>(vertices, vertexCount));
if (normals)
{
auto normalStream = accessor.Normal();
+9
View File
@@ -41,6 +41,15 @@ namespace FlaxEngine
NonJitteredProjection = Projection;
}
/// <summary>
/// Gets projection matrix for overlay geometry rendered after temporal anti-aliasing has been resolved.
/// </summary>
/// <param name="projection">Projection matrix valid for rendering before or after (matches current TAA jitter stage).</param>
public void GetOverlayProjection(out Matrix projection)
{
projection = IsTaaResolved ? NonJitteredProjection : Projection;
}
/// <summary>
/// Initializes render view data.
/// </summary>
+2 -2
View File
@@ -1516,8 +1516,8 @@ MaterialBase* AnimatedModel::GetMaterial(int32 entryIndex)
SkinnedModel->WaitForLoaded();
else
return nullptr;
CHECK_RETURN(entryIndex >= 0 && entryIndex < Entries.Count(), nullptr);
MaterialBase* material = Entries[entryIndex].Material.Get();
CHECK_RETURN(entryIndex >= 0 && entryIndex < SkinnedModel->MaterialSlots.Count(), nullptr);
MaterialBase* material = entryIndex < Entries.Count() ? Entries[entryIndex].Material.Get() : nullptr;
if (!material)
{
material = SkinnedModel->MaterialSlots[entryIndex].Material.Get();
@@ -39,6 +39,9 @@ void ModelInstanceActor::SetMaterial(int32 entryIndex, MaterialBase* material)
WaitForModelLoad();
if (Entries.Count() == 0 && !material)
return;
const int32 slotsCount = GetMaterialSlots().Length();
if (Entries.Count() != slotsCount)
Entries.Setup(slotsCount);
CHECK(entryIndex >= 0 && entryIndex < Entries.Count());
if (Entries[entryIndex].Material == material)
return;
@@ -50,6 +53,10 @@ void ModelInstanceActor::SetMaterial(int32 entryIndex, MaterialBase* material)
MaterialInstance* ModelInstanceActor::CreateAndSetVirtualMaterialInstance(int32 entryIndex)
{
WaitForModelLoad();
const int32 slotsCount = GetMaterialSlots().Length();
CHECK_RETURN(entryIndex >= 0 && entryIndex < slotsCount, nullptr);
if (Entries.Count() != slotsCount)
Entries.Setup(slotsCount);
MaterialBase* material = GetMaterial(entryIndex);
CHECK_RETURN(material && !material->WaitForLoaded(), nullptr);
MaterialInstance* result = material->CreateVirtualInstance();
+5 -3
View File
@@ -496,7 +496,7 @@ namespace
FORCE_INLINE float NodeSizeByDistance(const Vector3& nodePosition, bool scaleByDistance)
{
if (scaleByDistance)
return (float)(Vector3::Distance(DebugDraw::GetViewPos(), nodePosition) / 100);
return (float)(Vector3::Distance(DebugDraw::GetViewPosition(), nodePosition) / 100);
return 5.0f;
}
@@ -507,7 +507,7 @@ namespace
return;
Spline::Keyframe* prev = spline->Curve.GetKeyframes().Get();
Vector3 prevPos = transform.LocalToWorld(prev->Value.Translation);
Real distance = Vector3::Distance(prevPos, DebugDraw::GetViewPos());
Real distance = Vector3::Distance(prevPos, DebugDraw::GetViewPosition());
if (distance < METERS_TO_UNITS(800)) // 800m
{
// Bezier curve
@@ -540,7 +540,9 @@ namespace
void Spline::OnDebugDraw()
{
if (DebugDraw::GetViewFrustum().Intersects(_sphere))
BoundingSphere sphere(_sphere);
sphere.Center -= DebugDraw::GetViewOrigin();
if (DebugDraw::GetViewFrustum().Intersects(sphere))
DrawSpline(this, GetSplineColor().AlphaMultiplied(0.7f), _transform, true);
// Base
+2 -2
View File
@@ -356,8 +356,8 @@ MaterialBase* SplineModel::GetMaterial(int32 entryIndex)
Model->WaitForLoaded();
else
return nullptr;
CHECK_RETURN(entryIndex >= 0 && entryIndex < Entries.Count(), nullptr);
MaterialBase* material = Entries[entryIndex].Material.Get();
CHECK_RETURN(entryIndex >= 0 && entryIndex < Model->MaterialSlots.Count(), nullptr);
MaterialBase* material = entryIndex < Entries.Count() ? Entries[entryIndex].Material.Get() : nullptr;
if (!material)
{
material = Model->MaterialSlots[entryIndex].Material.Get();
+4 -4
View File
@@ -154,7 +154,7 @@ MaterialBase* StaticModel::GetMaterial(int32 meshIndex, int32 lodIndex) const
Math::IsInRange(meshIndex, 0, model->LODs[lodIndex].Meshes.Count()));
const auto& mesh = model->LODs[lodIndex].Meshes[meshIndex];
const auto materialSlotIndex = mesh.GetMaterialSlotIndex();
MaterialBase* material = Entries[materialSlotIndex].Material.Get();
MaterialBase* material = materialSlotIndex < Entries.Count() ? Entries[materialSlotIndex].Material.Get() : nullptr;
return material ? material : model->MaterialSlots[materialSlotIndex].Material.Get();
}
@@ -607,9 +607,9 @@ MaterialBase* StaticModel::GetMaterial(int32 entryIndex)
{
if (!Model || Model->WaitForLoaded())
return nullptr;
CHECK_RETURN(entryIndex >= 0 && entryIndex < Entries.Count(), nullptr);
MaterialBase* material = Entries[entryIndex].Material.Get();
if (!material && entryIndex < Model->MaterialSlots.Count())
CHECK_RETURN(entryIndex >= 0 && entryIndex < Model->MaterialSlots.Count(), nullptr);
MaterialBase* material = entryIndex < Entries.Count() ? Entries[entryIndex].Material.Get() : nullptr;
if (!material)
{
material = Model->MaterialSlots[entryIndex].Material.Get();
if (!material)
@@ -5,7 +5,7 @@
#include "ParticleEmitterGraph.GPU.h"
#include "Engine/Graphics/Materials/MaterialInfo.h"
bool ParticleEmitterGPUGenerator::loadTexture(Node* caller, Box* box, const SerializedMaterialParam& texture, Value& result)
bool ParticleEmitterGPUGenerator::loadTexture(Node* caller, Box* box, const SerializedMaterialParam& texture, const Value& textureValue, Value& result)
{
ASSERT(caller && box && texture.ID.IsValid());
@@ -22,7 +22,8 @@ bool ParticleEmitterGPUGenerator::loadTexture(Node* caller, Box* box, const Seri
&& texture.Type != MaterialParameterType::GPUTextureVolume
&& texture.Type != MaterialParameterType::GPUTextureCube
&& texture.Type != MaterialParameterType::GPUTextureArray
&& texture.Type != MaterialParameterType::CubeTexture)
&& texture.Type != MaterialParameterType::CubeTexture
&& textureValue.Type != VariantType::Object)
{
result = Value::Zero;
OnError(caller, box, TEXT("No parameter for texture load or invalid type."));
@@ -41,7 +42,8 @@ bool ParticleEmitterGPUGenerator::loadTexture(Node* caller, Box* box, const Seri
// Load texture
const Char* format = TEXT("{0}.Load({1})");
const String sampledValue = String::Format(format, texture.ShaderName, location.Value);
auto& shaderName = textureValue.Type == VariantType::Object ? textureValue.Value : texture.ShaderName;
const String sampledValue = String::Format(format, shaderName, location.Value);
result = writeLocal(VariantType::Float4, sampledValue, parent);
return false;
@@ -300,7 +302,7 @@ void ParticleEmitterGPUGenerator::ProcessGroupTextures(Box* box, Node* node, Val
const auto copy = *textureParam;
// Load texture
loadTexture(node, box, copy, value);
loadTexture(node, box, copy, texture, value);
break;
}
// Sample Global SDF
@@ -127,7 +127,7 @@ private:
Parameter* findGraphParam(const Guid& id);
bool sampleSceneTexture(Node* caller, Box* box, const SerializedMaterialParam& texture, Value& result);
bool loadTexture(Node* caller, Box* box, const SerializedMaterialParam& texture, Value& result);
bool loadTexture(Node* caller, Box* box, const SerializedMaterialParam& texture, const Value& textureValue, Value& result);
void sampleSceneDepth(Node* caller, Value& value, Box* box);
void linearizeSceneDepth(Node* caller, const Value& depth, Value& value);
+141 -15
View File
@@ -11,10 +11,12 @@
#include "Engine/Platform/Base/DragDropHelper.h"
#endif
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Color32.h"
#include "Engine/Input/Input.h"
#include "Engine/Input/Mouse.h"
#include "Engine/Input/Keyboard.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include <Cocoa/Cocoa.h>
#include <AppKit/AppKit.h>
#include <QuartzCore/CAMetalLayer.h>
@@ -200,6 +202,19 @@ Float2 GetMousePosition(MacWindow* window, NSEvent* event)
return Float2(point.x, frame.size.height - point.y) * MacPlatform::ScreenScale - GetWindowTitleSize(window);
}
NSRect GetFrameRectForClientBounds(MacWindow* macWindow, NSWindow* window, const Rectangle& clientArea)
{
const float screenScale = MacPlatform::ScreenScale;
NSRect rect = NSMakeRect(0, 0, clientArea.Size.X / screenScale, clientArea.Size.Y / screenScale);
rect = [window frameRectForContentRect:rect];
Float2 pos = AppleUtils::PosToCoca(clientArea.Location) / screenScale;
Float2 titleSize = GetWindowTitleSize(macWindow);
rect.origin.x = pos.X + titleSize.X;
rect.origin.y = pos.Y - rect.size.height + titleSize.Y;
return rect;
}
class MacDropData : public IGuiData
{
public:
@@ -310,6 +325,12 @@ NSDragOperation GetDragDropOperation(DragDropEffect dragDropEffect)
Window->OnLostFocus();
}
- (void)windowDidMove:(NSNotification*)notification
{
if (IsWindowInvalid(Window)) return;
Window->SyncWindowState();
}
- (void)windowWillClose:(NSNotification*)notification
{
[self setDelegate: nil];
@@ -518,6 +539,28 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event);
mousePos = Window->ClientToScreen(mousePos);
if ([event clickCount] == 1 && !Input::Mouse->IsRelative())
{
WindowHitCodes hit = WindowHitCodes::Client;
bool handled = false;
Window->OnHitTest(mousePos, hit, handled);
if (hit == WindowHitCodes::Caption)
{
bool consumed = false;
Window->OnLeftButtonHit(hit, consumed);
if (!consumed)
{
[(NSWindow*)Window->GetNativePtr() performWindowDragWithEvent:event];
Window->SyncWindowState();
}
return;
}
}
MouseButton mouseButton = MouseButton::Left;
if ([event clickCount] == 2 && !Input::Mouse->IsRelative())
Input::Mouse->OnMouseDoubleClick(mousePos, mouseButton, Window);
@@ -835,15 +878,28 @@ MacWindow::MacWindow(const CreateWindowSettings& settings)
MacWindow::~MacWindow()
{
NSWindow* window = (NSWindow*)_window;
[window close];
[window release];
if (NSWindow* window = (NSWindow*)_window)
{
[window close];
[window release];
}
_window = nullptr;
_view = nullptr;
}
void MacWindow::SyncWindowState()
{
NSWindow* window = (NSWindow*)_window;
if (window)
{
_minimized = window.miniaturized;
_maximized = window.zoomed;
}
}
void MacWindow::CheckForResize(float width, float height)
{
SyncWindowState();
const Float2 clientSize(width, height);
if (clientSize != _clientSize)
{
@@ -940,7 +996,7 @@ void MacWindow::Hide()
[window orderOut:nil];
// Transfer focus back to the parent when hiding popup
if (_settings.Parent && wasKey)
if (_settings.Parent && wasKey && _settings.Type != WindowType::Popup && _settings.Type != WindowType::Tooltip)
{
NSWindow* parent = (NSWindow*)_settings.Parent->GetNativePtr();
[parent makeKeyAndOrderFront:nil];
@@ -951,6 +1007,27 @@ void MacWindow::Hide()
}
}
void MacWindow::Close(ClosingReason reason)
{
const BOOL wasKey = _window && [(NSWindow*)_window isKeyWindow];
WindowBase::Close(reason);
// Closing can be cancelled by managed Window.Closing handlers.
if (!IsClosed())
return;
if (NSWindow* window = (NSWindow*)_window)
{
[window close];
}
if (_settings.Parent && wasKey && _settings.Type != WindowType::Popup && _settings.Type != WindowType::Tooltip)
{
NSWindow* parent = (NSWindow*)_settings.Parent->GetNativePtr();
[parent makeKeyAndOrderFront:nil];
}
}
void MacWindow::Minimize()
{
if (!_settings.AllowMinimize)
@@ -967,17 +1044,43 @@ void MacWindow::Maximize()
if (!_settings.AllowMaximize)
return;
NSWindow* window = (NSWindow*)_window;
if (!window)
return;
if (!window.zoomed)
{
if (!_maximized)
{
_restoreClientBounds = GetClientBounds();
_hasRestoreClientBounds = true;
}
[window zoom:nil];
}
SyncWindowState();
}
void MacWindow::Restore()
{
NSWindow* window = (NSWindow*)_window;
if (!window)
return;
if (window.miniaturized)
{
[window deminiaturize:nil];
SyncWindowState();
}
else if (_maximized && _hasRestoreClientBounds)
{
const Rectangle restoreClientBounds = _restoreClientBounds;
_hasRestoreClientBounds = false;
NSRect restoreFrame = GetFrameRectForClientBounds(this, window, restoreClientBounds);
[window setFrame:restoreFrame display:YES animate:YES];
_maximized = false;
}
else if (window.zoomed)
{
[window zoom:nil];
SyncWindowState();
}
}
bool MacWindow::IsForegroundWindow() const
@@ -1001,17 +1104,7 @@ void MacWindow::SetClientBounds(const Rectangle& clientArea)
NSWindow* window = (NSWindow*)_window;
if (!window)
return;
const float screenScale = MacPlatform::ScreenScale;
NSRect oldRect = [window frame];
NSRect newRect = NSMakeRect(0, 0, clientArea.Size.X / screenScale, clientArea.Size.Y / screenScale);
newRect = [window frameRectForContentRect:newRect];
Float2 pos = AppleUtils::PosToCoca(clientArea.Location) / screenScale;
Float2 titleSize = GetWindowTitleSize(this);
newRect.origin.x = pos.X + titleSize.X;
newRect.origin.y = pos.Y - newRect.size.height + titleSize.Y;
NSRect newRect = GetFrameRectForClientBounds(this, window, clientArea);
[window setFrame:newRect display:YES];
}
@@ -1213,4 +1306,37 @@ void MacWindow::SetCursor(CursorType type)
}
}
void MacWindow::SetIcon(TextureData& icon)
{
// Get pixels
Array<Color32> colorData;
icon.GetPixels(colorData);
// Convert to Cocoa image
NSImage* image = [[NSImage alloc] initWithSize:NSMakeSize(icon.Width, icon.Height)];
if (image == nil)
return;
NSBitmapImageRep* rep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
pixelsWide:icon.Width
pixelsHigh:icon.Height
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSDeviceRGBColorSpace
bytesPerRow:icon.Width * 4
bitsPerPixel:32];
if (rep == nil)
return;
// Copy the pixels
Platform::MemoryCopy([rep bitmapData], colorData.Get(), colorData.Count() * sizeof(Color32));
// Add the image representation
[image addRepresentation:rep];
// Set app icon
[NSApp setApplicationIconImage:image];
}
#endif
+6 -1
View File
@@ -17,13 +17,16 @@ private:
void* _window = nullptr;
void* _view = nullptr;
bool _isMouseOver = false;
bool _hasRestoreClientBounds = false;
Rectangle _restoreClientBounds;
Float2 _mouseTrackPos = Float2::Minimum;
String _dragText;
public:
MacWindow(const CreateWindowSettings& settings);
~MacWindow();
~MacWindow() override;
void SyncWindowState();
void CheckForResize(float width, float height);
void SetIsMouseOver(bool value);
const String& GetDragText() const
@@ -37,6 +40,7 @@ public:
void OnUpdate(float dt) override;
void Show() override;
void Hide() override;
void Close(ClosingReason reason) override;
void Minimize() override;
void Maximize() override;
void Restore() override;
@@ -59,6 +63,7 @@ public:
void StartTrackingMouse(bool useMouseScreenOffset) override;
void EndTrackingMouse() override;
void SetCursor(CursorType type) override;
void SetIcon(TextureData& icon) override;
};
#endif
+1 -1
View File
@@ -36,7 +36,7 @@ public class Platform : EngineModule
{
options.OutputFiles.Add("dbghelp.lib");
options.DelayLoadLibraries.Add("dbghelp.dll");
options.DependencyFiles.Add(Path.Combine(options.DepsFolder, "dbghelp.dll"));
options.OptionalDependencyFiles.Add(Path.Combine(options.DepsFolder, "dbghelp.dll"));
}
if (options.Target.IsEditor)
{
+9 -2
View File
@@ -247,6 +247,13 @@ FORCE_INLINE void ApplyTransform(const Float2& value, Float2& result)
Matrix3x3::Transform2DPoint(value, TransformCached, result);
}
FORCE_INLINE float GetTransformScale()
{
const Float2 axisX(TransformCached.M11, TransformCached.M12);
const Float2 axisY(TransformCached.M21, TransformCached.M22);
return (axisX.Length() + axisY.Length()) * 0.5f;
}
void ApplyTransform(const Rectangle& value, RotatedRectangle& result)
{
const RotatedRectangle rotated(value);
@@ -1464,7 +1471,7 @@ void Render2D::DrawRectangle(const Rectangle& rect, const Color& color1, const C
const auto& mask = ClipLayersStack.Peek().Mask;
float thick = thickness;
thickness *= (TransformCached.M11 + TransformCached.M22 + TransformCached.M33) * 0.3333333f;
thickness *= GetTransformScale();
// When lines thickness is very large, don't use corner caps and place line ends to not overlap
if (thickness > 4.0f)
@@ -1840,7 +1847,7 @@ void DrawLines(const Float2* points, int32 pointsCount, const Color& color1, con
ASSERT(points && pointsCount >= 2);
const auto& mask = ClipLayersStack.Peek().Mask;
thickness *= (TransformCached.M11 + TransformCached.M22 + TransformCached.M33) * 0.3333333f;
thickness *= GetTransformScale();
Render2DDrawCall& drawCall = DrawCalls.AddOne();
drawCall.StartIB = IBIndex;
+1 -21
View File
@@ -21,27 +21,7 @@
String AudioTool::Options::ToString() const
{
return String::Format(TEXT("Format:{}, DisableStreaming:{}, Is3D:{}, Quality:{}, BitDepth:{}"), ScriptingEnum::ToString(Format), DisableStreaming, Is3D, Quality, (int32)BitDepth);
}
void AudioTool::Options::Serialize(SerializeStream& stream, const void* otherObj)
{
SERIALIZE_GET_OTHER_OBJ(AudioTool::Options);
SERIALIZE(Format);
SERIALIZE(DisableStreaming);
SERIALIZE(Is3D);
SERIALIZE(Quality);
SERIALIZE(BitDepth);
}
void AudioTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
{
DESERIALIZE(Format);
DESERIALIZE(DisableStreaming);
DESERIALIZE(Is3D);
DESERIALIZE(Quality);
DESERIALIZE(BitDepth);
return String::Format(TEXT("Volume: {}, Format:{}, DisableStreaming:{}, Is3D:{}, Quality:{}, BitDepth:{}"), Volume, ScriptingEnum::ToString(Format), DisableStreaming, Is3D, Quality, (int32)BitDepth);
}
#endif
+7 -4
View File
@@ -42,6 +42,13 @@ public:
API_STRUCT(Attributes="HideInEditor") struct FLAXENGINE_API Options : public ISerializable
{
DECLARE_SCRIPTING_TYPE_MINIMAL(Options);
API_AUTO_SERIALIZATION();
/// <summary>
/// The audio volume. Can be used to scale source audio data at import time.
/// </summary>
API_FIELD(Attributes="EditorOrder(5), Limit(0, 10, 0.01f)")
float Volume = 1;
/// <summary>
/// The audio data format to import the audio clip as.
@@ -74,10 +81,6 @@ public:
BitDepth BitDepth = BitDepth::_16;
String ToString() const;
// [ISerializable]
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
};
#endif
@@ -475,7 +475,11 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
uv = MaterialValue::Cast(tryGetValue(uvBox, getUVs), VariantType::Float2).Value;
else
uv = TEXT("input.TexCoord.xy");
value = writeLocal(VariantType::Float3, String::Format(TEXT("GetWorldPos({1}, {0}.rgb)"), depthSample->Value, uv), node);
const auto layer = GetRootLayer();
if (layer && layer->Domain == MaterialDomain::PostProcess)
value = writeLocal(VariantType::Float3, String::Format(TEXT("GetWorldPos({1}, {0})"), depthSample->Value, uv), node);
else // TODO: reimpl GetWorldPos() for other domains (see 'Content/Editor/MaterialTemplates/PostProcess.shader'), can be via matrix inverse in a shader
value = ShaderGraphValue::InitForZero(VariantType::Float3);
break;
}
case MaterialSceneTextures::SceneStencil:
+12 -7
View File
@@ -228,19 +228,27 @@ namespace FlaxEngine.GUI
float progressNormalized = Mathf.InverseLerp(_minimum, _maximum, _current);
if (progressNormalized > 0.001f)
{
Rectangle barRect = new Rectangle(0, 0, Width * progressNormalized, Height);
Rectangle rect = new Rectangle(0, 0, Width, Height);
BarMargin.ShrinkRectangle(ref rect);
if (rect.Width <= 0.0f || rect.Height <= 0.0f)
return;
Rectangle barRect = rect;
switch (Origin)
{
case BarOrigin.HorizontalLeft:
barRect.Width *= progressNormalized;
break;
case BarOrigin.HorizontalRight:
barRect = new Rectangle(Width - Width * progressNormalized, 0, Width * progressNormalized, Height);
barRect.Width *= progressNormalized;
barRect.X = rect.Right - barRect.Width;
break;
case BarOrigin.VerticalTop:
barRect = new Rectangle(0, 0, Width, Height * progressNormalized);
barRect.Height *= progressNormalized;
break;
case BarOrigin.VerticalBottom:
barRect = new Rectangle(0, Height - Height * progressNormalized, Width, Height * progressNormalized);
barRect.Height *= progressNormalized;
barRect.Y = rect.Bottom - barRect.Height;
break;
default: break;
}
@@ -248,15 +256,12 @@ namespace FlaxEngine.GUI
switch (Method)
{
case BarMethod.Stretch:
BarMargin.ShrinkRectangle(ref barRect);
if (BarBrush != null)
BarBrush.Draw(barRect, BarColor);
else
Render2D.FillRectangle(barRect, BarColor);
break;
case BarMethod.Clip:
var rect = new Rectangle(0, 0, Width, Height);
BarMargin.ShrinkRectangle(ref rect);
Render2D.PushClip(ref barRect);
if (BarBrush != null)
BarBrush.Draw(rect, BarColor);
+1 -1
View File
@@ -90,7 +90,7 @@ namespace FlaxEngine
Matrix.Multiply(ref worldMatrix, ref renderContext.View.View, out Matrix viewMatrix);
Matrix projectionMatrix = renderContext.View.Projection;
if (worldSpace && (Canvas.RenderLocation == PostProcessEffectLocation.Default || Canvas.RenderLocation == PostProcessEffectLocation.AfterAntiAliasingPass))
projectionMatrix = renderContext.View.NonJitteredProjection; // Fix TAA jittering when rendering UI in world after TAA resolve
renderContext.View.GetOverlayProjection(out projectionMatrix); // Fix TAA jittering when rendering UI in world after TAA resolve
Matrix.Multiply(ref viewMatrix, ref projectionMatrix, out Matrix viewProjectionMatrix);
// Pick a depth buffer
+33
View File
@@ -5,6 +5,8 @@
#include "ShaderGraph.h"
#include "GraphUtilities.h"
#include "ShaderGraphUtilities.h"
#include "Engine/Content/Assets/Texture.h"
#include "Engine/Content/Assets/CubeTexture.h"
#include "Engine/Engine/GameplayGlobals.h"
const Char* ShaderGenerator::_mathFunctions[] =
@@ -742,6 +744,37 @@ void ShaderGenerator::ProcessGroupTools(Box* box, Node* node, Value& value)
// Get param value
value.Type = variable.DefaultValue.Type.Type;
value.Value = param->ShaderName;
switch (variable.DefaultValue.Type.Type)
{
case VariantType::Bool:
case VariantType::Int:
case VariantType::Uint:
case VariantType::Float:
case VariantType::Float2:
case VariantType::Float3:
case VariantType::Float4:
case VariantType::Color:
case VariantType::Double2:
case VariantType::Double3:
case VariantType::Double4:
case VariantType::Int2:
case VariantType::Int3:
case VariantType::Int4:
// POD value types
break;
case VariantType::Asset:
if (Texture::GetStaticType().Fullname == variable.DefaultValue.Type.TypeName ||
CubeTexture::GetStaticType().Fullname == variable.DefaultValue.Type.TypeName)
{
// Texture or Cube Texture
value.Type = VariantType::Object;
break;
}
default:
LOG(Warning, "Invalid Gameplay Global '{}' ({}) value type '{}' to bind to material", name, asset->GetPath(), variable.DefaultValue.Type.ToString());
value = Value::Zero;
break;
}
break;
}
// Platform Switch
@@ -7,6 +7,8 @@
#include "Engine/Core/Types/StringBuilder.h"
#include "Engine/Core/Math/Vector4.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/Assets/Texture.h"
#include "Engine/Content/Assets/CubeTexture.h"
#include "Engine/Engine/GameplayGlobals.h"
#include "Engine/Graphics/Config.h"
#include "Engine/Renderer/GlobalSignDistanceFieldPass.h"
@@ -172,6 +174,26 @@ const Char* ShaderGraphUtilities::GenerateShaderResources(TextWriterUnicode& wri
case MaterialParameterType::GPUTextureVolume:
format = TEXT("Texture3D {0} : register(t{1});");
break;
case MaterialParameterType::GameplayGlobal:
{
auto asset = Content::LoadAsync<GameplayGlobals>(param.AsGuid);
if (!asset || asset->WaitForLoaded())
break;
GameplayGlobals::Variable variable;
if (!asset->Variables.TryGet(param.Name, variable))
break;
if (Texture::GetStaticType().Fullname == variable.DefaultValue.Type.TypeName)
{
// Texture
format = TEXT("Texture2D {0} : register(t{1});");
}
else if (CubeTexture::GetStaticType().Fullname == variable.DefaultValue.Type.TypeName)
{
// Cube Texture
format = TEXT("TextureCube {0} : register(t{1});");
}
break;
}
case MaterialParameterType::GlobalSDF:
format = TEXT("Texture3D<snorm float> {0}_Tex : register(t{1});\nTexture3D<snorm float> {0}_Mip : register(t{2});");
zeroOffset = false;
+1 -2
View File
@@ -71,8 +71,7 @@ public class FlaxEditor : EngineTarget
break;
case TargetPlatform.Mac:
options.OutputFolder = Path.Combine(options.WorkingDirectory, "Binaries", "Editor", "Mac", options.Configuration.ToString());
if (EngineConfiguration.WithSDL(options))
options.DependencyFiles.Add(Path.Combine(Globals.EngineRoot, "Source", "Logo.png"));
options.DependencyFiles.Add(Path.Combine(Globals.EngineRoot, "Source", "Platforms", "Mac", "Logo.png"));
break;
default: throw new InvalidPlatformException(options.Platform.Target, "Not supported Editor platform.");
}
Binary file not shown.
+4 -1
View File
@@ -37,7 +37,6 @@
#include <stdint.h>
#include <limits>
#include <initializer_list>
#include <iterator>
#include "core.h"
@@ -464,10 +463,12 @@ FMT_INLINE void assume(bool condition) {
#endif
}
#if FMT_USE_ITERATOR
// An approximation of iterator_t for pre-C++20 systems.
template <typename T>
using iterator_t = decltype(std::begin(std::declval<T&>()));
template <typename T> using sentinel_t = decltype(std::end(std::declval<T&>()));
#endif
#if FMT_USE_STRING
// A workaround for std::string not having mutable data() until C++17.
@@ -3407,6 +3408,7 @@ auto join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel> {
return {begin, end, sep};
}
#if FMT_USE_ITERATOR
/**
\rst
Returns a view that formats `range` with elements separated by `sep`.
@@ -3428,6 +3430,7 @@ auto join(Range&& range, string_view sep)
-> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>> {
return join(std::begin(range), std::end(range), sep);
}
#endif
#if FMT_USE_STRING
/**
@@ -357,8 +357,7 @@ namespace Flax.Deploy
DeployFile(src, dst, "MoltenVK_icd.json");
DeployFiles(src, dst, "*.dll");
DeployFiles(src, dst, "*.dylib");
if (EngineConfiguration.UseSDL && MacConfiguration.UseSDL)
DeployFile(src, dst, "Logo.png");
DeployFile(src, dst, "Logo.png");
// Optimize package size
Utilities.Run("strip", "FlaxEditor", null, dst, Utilities.RunOptions.None);
@@ -134,6 +134,8 @@ namespace Flax.Deps.Dependencies
case TargetPlatform.Windows:
{
cmakeArgs = ".. -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL " + cmakeArgs;
if (architecture == TargetArchitecture.ARM64)
cmakeArgs += " -DBASISU_SSE=OFF";
RunCmake(buildDir, platform, architecture, cmakeArgs);
BuildCmake(buildDir, configuration, options:Utilities.RunOptions.ConsoleLogOutput);
CopyLib(platform, Path.Combine(buildDir, configuration), depsFolder, "basisu_encoder");
@@ -48,6 +48,9 @@ namespace Flax.Deps.Dependencies
}
}
/// <inheritdoc />
public override bool BuildByDefault => false;
/// <inheritdoc />
public override void Build(BuildOptions options)
{
@@ -82,9 +82,19 @@ namespace Flax.Deps.Dependencies
CloneGitRepoFast(root, "https://github.com/FlaxEngine/glslang.git");
// Setup the external sources
// Requires distutils (pip install setuptools)
bool canRetry = true;
RETRY:
if (Utilities.Run(BuildPlatform != TargetPlatform.Mac ? "python" : "python3", "update_glslang_sources.py", null, root, Utilities.RunOptions.ConsoleLogOutput) != 0)
{
if (canRetry)
{
// Requires distutils (pip install setuptools)
canRetry = false;
Utilities.Run("pip", "install setuptools", null, root, Utilities.RunOptions.ConsoleLogOutput);
goto RETRY;
}
throw new Exception("Failed to update glslang sources, make sure setuptools python package is installed.");
}
foreach (var platform in options.Platforms)
{