diff --git a/.github/data/Build Settings.json b/.github/data/Build Settings.json
new file mode 100644
index 000000000..84ee94f0c
--- /dev/null
+++ b/.github/data/Build Settings.json
@@ -0,0 +1,87 @@
+{
+ "ID": "2364031e4e327637c1ad88b415fa756e",
+ "TypeName": "FlaxEditor.Content.Settings.BuildSettings",
+ "EngineBuild": 6605,
+ "Data": {
+ "OutputName": "${PROJECT_NAME}",
+ "MaxAssetsPerPackage": 4096,
+ "MaxPackageSizeMB": 1024,
+ "ContentKey": 0,
+ "ForDistribution": false,
+ "SkipPackaging": true,
+ "AdditionalAssets": null,
+ "AdditionalScenes": null,
+ "AdditionalAssetFolders": null,
+ "ShadersNoOptimize": false,
+ "ShadersGenerateDebugData": false,
+ "SkipDefaultFonts": false,
+ "SkipDotnetPackaging": false,
+ "SkipUnusedDotnetLibsPackaging": true,
+ "Presets": [
+ {
+ "Name": "Development",
+ "Targets": [
+ {
+ "Name": "Windows",
+ "Output": "Output\\Windows",
+ "Platform": 2,
+ "Mode": 1,
+ "CustomDefines": null,
+ "PreBuildAction": null,
+ "PostBuildAction": null
+ },
+ {
+ "Name": "Linux",
+ "Output": "Output\\Linux",
+ "Platform": 6,
+ "Mode": 1,
+ "CustomDefines": null,
+ "PreBuildAction": null,
+ "PostBuildAction": null
+ },
+ {
+ "Name": "Mac",
+ "Output": "Output\\Mac",
+ "Platform": 13,
+ "Mode": 1,
+ "CustomDefines": null,
+ "PreBuildAction": null,
+ "PostBuildAction": null
+ },
+ {
+ "Name": "Android",
+ "Output": "Output\\Android",
+ "Platform": 9,
+ "Mode": 1,
+ "CustomDefines": null,
+ "PreBuildAction": null,
+ "PostBuildAction": null
+ },
+ {
+ "Name": "iOS",
+ "Output": "Output\\iOS",
+ "Platform": 14,
+ "Mode": 1,
+ "CustomDefines": null,
+ "PreBuildAction": null,
+ "PostBuildAction": null
+ }
+ ]
+ },
+ {
+ "Name": "Release",
+ "Targets": [
+ {
+ "Name": "Windows",
+ "Output": "Output\\Windows",
+ "Platform": 2,
+ "Mode": 2,
+ "CustomDefines": null,
+ "PreBuildAction": null,
+ "PostBuildAction": null
+ }
+ ]
+ }
+ ]
+}
+}
\ No newline at end of file
diff --git a/.github/data/Cook.ps1 b/.github/data/Cook.ps1
new file mode 100644
index 000000000..394e6d031
--- /dev/null
+++ b/.github/data/Cook.ps1
@@ -0,0 +1,5 @@
+Write-Output "Cooking Game"
+Start-Process -filepath "Binaries\Editor\Win64\Development\FlaxEditor.exe" -Wait -NoNewWindow -PassThru -ArgumentList '-std -headless -mute -null -project "FlaxSamples/MaterialsFeaturesTour" -build "Development.Windows"'
+
+Write-Output "Testing Game"
+Start-Process -filepath "FlaxSamples\MaterialsFeaturesTour\Output\Windows\MaterialsFeaturesTour.exe" -Wait -NoNewWindow -PassThru -ArgumentList '-std -headless -mute -null'
diff --git a/.github/data/ExitOnEsc.cs b/.github/data/ExitOnEsc.cs
new file mode 100644
index 000000000..a2abcdfe0
--- /dev/null
+++ b/.github/data/ExitOnEsc.cs
@@ -0,0 +1,11 @@
+using FlaxEngine;
+
+public class ExitOnEsc : Script
+{
+ ///
+ public override void OnUpdate()
+ {
+ // Exit as soon as game starts update loaded level
+ Engine.RequestExit();
+ }
+}
diff --git a/.github/workflows/build_ios.yml b/.github/workflows/build_ios.yml
index 5a0d285fe..0f27619f7 100644
--- a/.github/workflows/build_ios.yml
+++ b/.github/workflows/build_ios.yml
@@ -10,7 +10,7 @@ jobs:
# Game
game-windows:
name: Game (iOS, Release ARM64)
- runs-on: "macos-latest"
+ runs-on: "macos-14"
steps:
- name: Checkout repo
uses: actions/checkout@v3
diff --git a/.github/workflows/build_mac.yml b/.github/workflows/build_mac.yml
index a8ebb4c62..ca595ed32 100644
--- a/.github/workflows/build_mac.yml
+++ b/.github/workflows/build_mac.yml
@@ -10,7 +10,7 @@ jobs:
# Editor
editor-mac:
name: Editor (Mac, Development ARM64)
- runs-on: "macos-latest"
+ runs-on: "macos-14"
steps:
- name: Checkout repo
uses: actions/checkout@v3
@@ -35,7 +35,7 @@ jobs:
# Game
game-mac:
name: Game (Mac, Release ARM64)
- runs-on: "macos-latest"
+ runs-on: "macos-14"
steps:
- name: Checkout repo
uses: actions/checkout@v3
diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
index 4b4f9a827..428dea933 100644
--- a/.github/workflows/cd.yml
+++ b/.github/workflows/cd.yml
@@ -140,7 +140,7 @@ jobs:
# Mac
package-mac-editor:
name: Editor (Mac)
- runs-on: "macos-latest"
+ runs-on: "macos-14"
steps:
- name: Checkout repo
uses: actions/checkout@v3
@@ -168,7 +168,7 @@ jobs:
path: Output/FlaxEditorMac.zip
package-mac-game:
name: Game (Mac)
- runs-on: "macos-latest"
+ runs-on: "macos-14"
steps:
- name: Checkout repo
uses: actions/checkout@v3
diff --git a/.github/workflows/cooking.yml b/.github/workflows/cooking.yml
new file mode 100644
index 000000000..a2c4550cb
--- /dev/null
+++ b/.github/workflows/cooking.yml
@@ -0,0 +1,48 @@
+name: Cooker
+on: [push, pull_request]
+
+env:
+ DOTNET_NOLOGO: true
+ DOTNET_CLI_TELEMETRY_OPTOUT: false
+
+jobs:
+ # Cook on Mac
+ cook-mac:
+ name: Cook (Mac)
+ runs-on: "macos-14"
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v3
+ - name: Setup Vulkan
+ uses: ./.github/actions/vulkan
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: 8.0.x
+ - name: Setup .NET Workload
+ run: |
+ dotnet workload install ios
+ - name: Print .NET info
+ run: |
+ dotnet --info
+ dotnet workload --info
+ - name: Checkout LFS
+ run: |
+ git lfs version
+ git lfs pull
+ - name: Get Flax Samples
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 1
+ repository: FlaxEngine/FlaxSamples
+ path: FlaxSamples
+ - name: Patch Files
+ run: |
+ cp .github/data/ExitOnEsc.cs FlaxSamples/MaterialsFeaturesTour/Source/Game
+ cp ".github/data/Build Settings.json" "FlaxSamples/MaterialsFeaturesTour/Content/Settings"
+ - name: Build Editor
+ run: |
+ ./Development/Scripts/Mac/CallBuildTool.sh -build -log -printSDKs -dotnet=8 -arch=ARM64 -platform=Mac -configuration=Development -buildtargets=FlaxEditor
+ - name: Cook Game (iOS)
+ run: |
+ ./Binaries/Editor/Mac/Development/FlaxEditor -std -headless -mute -null -project "FlaxSamples/MaterialsFeaturesTour" -build "Development.iOS"
diff --git a/Flax.flaxproj b/Flax.flaxproj
index 33dc6e45b..15b925c0d 100644
--- a/Flax.flaxproj
+++ b/Flax.flaxproj
@@ -4,7 +4,7 @@
"Major": 1,
"Minor": 9,
"Revision": 0,
- "Build": 6605
+ "Build": 6606
},
"Company": "Flax",
"Copyright": "Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.",
diff --git a/Source/Editor/Content/Items/VisualScriptItem.cs b/Source/Editor/Content/Items/VisualScriptItem.cs
index 54133e6e2..c3953513f 100644
--- a/Source/Editor/Content/Items/VisualScriptItem.cs
+++ b/Source/Editor/Content/Items/VisualScriptItem.cs
@@ -112,6 +112,12 @@ namespace FlaxEditor.Content
throw new TargetException("Missing Visual Script asset.");
_type.Asset.SetScriptInstanceParameterValue(_parameter.Name, (Object)obj, value);
}
+
+ ///
+ public object Invoke(object obj, object[] parameters)
+ {
+ throw new NotSupportedException();
+ }
}
sealed class VisualScriptMethodInfo : IScriptMemberInfo
@@ -240,6 +246,14 @@ namespace FlaxEditor.Content
{
throw new NotSupportedException();
}
+
+ ///
+ public object Invoke(object obj, object[] parameters)
+ {
+ if (!_type.Asset)
+ throw new TargetException("Missing Visual Script asset.");
+ return _type.Asset.InvokeMethod(_index, obj, parameters);
+ }
}
///
diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs
index 0d2be75cc..6b7bd4601 100644
--- a/Source/Editor/CustomEditors/CustomEditor.cs
+++ b/Source/Editor/CustomEditors/CustomEditor.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using FlaxEditor.CustomEditors.GUI;
@@ -127,12 +128,41 @@ namespace FlaxEditor.CustomEditors
_isSetBlocked = true;
Initialize(layout);
+ ShowButtons();
Refresh();
_isSetBlocked = false;
CurrentCustomEditor = prev;
}
+ private void ShowButtons()
+ {
+ var values = Values;
+ if (values == null || values.HasDifferentTypes)
+ return;
+ var type = TypeUtils.GetObjectType(values[0]);
+ var methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
+ foreach (var method in methods)
+ {
+ if (!method.HasAttribute(typeof(ButtonAttribute)) ||
+ method.ParametersCount != 0)
+ continue;
+ var attribute = method.GetAttribute();
+ var text = string.IsNullOrEmpty(attribute.Text) ? Utilities.Utils.GetPropertyNameUI(method.Name) : attribute.Text;
+ var tooltip = string.IsNullOrEmpty(attribute.Tooltip) ? Editor.Instance.CodeDocs.GetTooltip(method) : attribute.Tooltip;
+ var button = _layout.Button(text, tooltip);
+ button.Button.Tag = method;
+ button.Button.ButtonClicked += OnButtonClicked;
+ }
+ }
+
+ private void OnButtonClicked(Button button)
+ {
+ var method = (ScriptMemberInfo)button.Tag;
+ var obj = method.IsStatic ? null : Values[0];
+ method.Invoke(obj);
+ }
+
internal static CustomEditor CurrentCustomEditor;
internal void OnChildCreated(CustomEditor child)
@@ -271,8 +301,16 @@ namespace FlaxEditor.CustomEditors
_valueToSet = null;
// Assign value
- for (int i = 0; i < _values.Count; i++)
- _values[i] = val;
+ if (val is IList l && l.Count == _values.Count)
+ {
+ for (int i = 0; i < _values.Count; i++)
+ _values[i] = l[i];
+ }
+ else
+ {
+ for (int i = 0; i < _values.Count; i++)
+ _values[i] = val;
+ }
}
finally
{
diff --git a/Source/Editor/CustomEditors/Dedicated/CurveObjectEditor.cs b/Source/Editor/CustomEditors/Dedicated/CurveObjectEditor.cs
index 019148b07..81d826a04 100644
--- a/Source/Editor/CustomEditors/Dedicated/CurveObjectEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/CurveObjectEditor.cs
@@ -2,6 +2,7 @@
using FlaxEditor.GUI;
using FlaxEngine;
+using FlaxEngine.GUI;
namespace FlaxEditor.CustomEditors.Dedicated
{
@@ -11,7 +12,9 @@ namespace FlaxEditor.CustomEditors.Dedicated
class BezierCurveObjectEditor : CustomEditor where T : struct
{
private bool _isSetting;
+ private int _firstTimeShow;
private BezierCurveEditor _curve;
+ private Splitter _splitter;
///
public override void Initialize(LayoutElementsContainer layout)
@@ -20,6 +23,14 @@ namespace FlaxEditor.CustomEditors.Dedicated
_curve = item.CustomControl;
_curve.Height = 120.0f;
_curve.Edited += OnCurveEdited;
+ _firstTimeShow = 4; // For some weird reason it needs several frames of warmup (probably due to sliders smoothing)
+ _splitter = new Splitter
+ {
+ Moved = OnSplitterMoved,
+ Parent = _curve,
+ AnchorPreset = AnchorPresets.HorizontalStretchBottom,
+ Bounds = new Rectangle(0, _curve.Height - Splitter.DefaultHeight, _curve.Width, Splitter.DefaultHeight),
+ };
}
private void OnCurveEdited()
@@ -32,6 +43,11 @@ namespace FlaxEditor.CustomEditors.Dedicated
_isSetting = false;
}
+ private void OnSplitterMoved(Float2 location)
+ {
+ _curve.Height = Mathf.Clamp(_splitter.PointToParent(location).Y, 50.0f, 1000.0f);
+ }
+
///
public override void Refresh()
{
@@ -44,12 +60,15 @@ namespace FlaxEditor.CustomEditors.Dedicated
_curve.SetKeyframes(value.Keyframes);
_isSetting = false;
}
+ if (_firstTimeShow-- > 0)
+ _curve.ShowWholeCurve();
}
///
protected override void Deinitialize()
{
_curve = null;
+ _splitter = null;
base.Deinitialize();
}
@@ -111,7 +130,9 @@ namespace FlaxEditor.CustomEditors.Dedicated
class LinearCurveObjectEditor : CustomEditor where T : struct
{
private bool _isSetting;
+ private int _firstTimeShow;
private LinearCurveEditor _curve;
+ private Splitter _splitter;
///
public override void Initialize(LayoutElementsContainer layout)
@@ -120,6 +141,14 @@ namespace FlaxEditor.CustomEditors.Dedicated
_curve = item.CustomControl;
_curve.Height = 120.0f;
_curve.Edited += OnCurveEdited;
+ _firstTimeShow = 4; // For some weird reason it needs several frames of warmup (probably due to sliders smoothing)
+ _splitter = new Splitter
+ {
+ Moved = OnSplitterMoved,
+ Parent = _curve,
+ AnchorPreset = AnchorPresets.HorizontalStretchBottom,
+ Bounds = new Rectangle(0, _curve.Height - Splitter.DefaultHeight, _curve.Width, Splitter.DefaultHeight),
+ };
}
private void OnCurveEdited()
@@ -132,6 +161,11 @@ namespace FlaxEditor.CustomEditors.Dedicated
_isSetting = false;
}
+ private void OnSplitterMoved(Float2 location)
+ {
+ _curve.Height = Mathf.Clamp(_splitter.PointToParent(location).Y, 50.0f, 1000.0f);
+ }
+
///
public override void Refresh()
{
@@ -144,12 +178,15 @@ namespace FlaxEditor.CustomEditors.Dedicated
_curve.SetKeyframes(value.Keyframes);
_isSetting = false;
}
+ if (_firstTimeShow-- > 0)
+ _curve.ShowWholeCurve();
}
///
protected override void Deinitialize()
{
_curve = null;
+ _splitter = null;
base.Deinitialize();
}
diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
index eb20560ba..c3330330d 100644
--- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
@@ -407,20 +407,13 @@ namespace FlaxEditor.CustomEditors.Dedicated
///
public class UIControlControlEditor : GenericEditor
{
- private Type _cachedType;
+ private ScriptType[] _valueTypes;
private bool _anchorDropDownClosed = true;
private Button _pivotRelativeButton;
///
public override void Initialize(LayoutElementsContainer layout)
{
- _cachedType = null;
- if (HasDifferentTypes)
- {
- // TODO: support stable editing multiple different control types (via generic way or for transform-only)
- return;
- }
-
// Set control type button
var space = layout.Space(20);
var buttonText = "Set Type";
@@ -445,12 +438,12 @@ namespace FlaxEditor.CustomEditors.Dedicated
}
// Add control type helper label
+ if (!Values.HasDifferentTypes)
{
- var type = Values[0].GetType();
- _cachedType = type;
var label = layout.AddPropertyItem("Type", "The type of the created control.");
- label.Label(type.FullName);
+ label.Label(Values[0].GetType().FullName);
}
+ _valueTypes = Values.ValuesTypes;
// Show control properties
base.Initialize(layout);
@@ -720,22 +713,20 @@ namespace FlaxEditor.CustomEditors.Dedicated
///
public override void Refresh()
{
- if (_cachedType != null)
+ // Automatic layout rebuild if control type gets changed
+ if (_valueTypes != null &&
+ !Values.HasNull &&
+ !Utils.ArraysEqual(_valueTypes, Values.ValuesTypes))
{
- // Automatic layout rebuild if control type gets changed
- var type = Values.HasNull ? null : Values[0].GetType();
- if (type != _cachedType)
- {
- RebuildLayout();
- return;
- }
+ RebuildLayout();
+ return;
+ }
- // Refresh anchors
- GetAnchorEquality(out bool xEq, out bool yEq, ValuesTypes);
- if (xEq != _cachedXEq || yEq != _cachedYEq)
- {
- RebuildLayout();
- }
+ // Refresh anchors
+ GetAnchorEquality(out bool xEq, out bool yEq, ValuesTypes);
+ if (xEq != _cachedXEq || yEq != _cachedYEq)
+ {
+ RebuildLayout();
}
base.Refresh();
diff --git a/Source/Editor/CustomEditors/Editors/QuaternionEditor.cs b/Source/Editor/CustomEditors/Editors/QuaternionEditor.cs
index 96f9d0646..0da35aa4f 100644
--- a/Source/Editor/CustomEditors/Editors/QuaternionEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/QuaternionEditor.cs
@@ -33,6 +33,15 @@ namespace FlaxEditor.CustomEditors.Editors
///
public override DisplayStyle Style => DisplayStyle.Inline;
+
+ ///
+ /// Whether to use the average for sliding different values.
+ ///
+ public virtual bool AllowSlidingForDifferentValues => true;
+
+ private ValueChanged _valueChanged;
+ private float _defaultSlidingSpeed;
+ private bool _slidingEnded = false;
///
public override void Initialize(LayoutElementsContainer layout)
@@ -46,18 +55,31 @@ namespace FlaxEditor.CustomEditors.Editors
XElement = grid.FloatValue();
XElement.ValueBox.Category = Utils.ValueCategory.Angle;
- XElement.ValueBox.ValueChanged += OnValueChanged;
- XElement.ValueBox.SlidingEnd += ClearToken;
+ XElement.ValueBox.ValueChanged += OnXValueChanged;
+ XElement.ValueBox.SlidingEnd += () =>
+ {
+ _slidingEnded = true;
+ ClearToken();
+ };
+ _defaultSlidingSpeed = XElement.ValueBox.SlideSpeed;
YElement = grid.FloatValue();
YElement.ValueBox.Category = Utils.ValueCategory.Angle;
- YElement.ValueBox.ValueChanged += OnValueChanged;
- YElement.ValueBox.SlidingEnd += ClearToken;
+ YElement.ValueBox.ValueChanged += OnYValueChanged;
+ YElement.ValueBox.SlidingEnd += () =>
+ {
+ _slidingEnded = true;
+ ClearToken();
+ };
ZElement = grid.FloatValue();
ZElement.ValueBox.Category = Utils.ValueCategory.Angle;
- ZElement.ValueBox.ValueChanged += OnValueChanged;
- ZElement.ValueBox.SlidingEnd += ClearToken;
+ ZElement.ValueBox.ValueChanged += OnZValueChanged;
+ ZElement.ValueBox.SlidingEnd += () =>
+ {
+ _slidingEnded = true;
+ ClearToken();
+ };
if (LinkedLabel != null)
{
@@ -69,33 +91,118 @@ namespace FlaxEditor.CustomEditors.Editors
};
}
}
-
- private void OnValueChanged()
+
+ private void OnXValueChanged()
{
if (IsSetBlocked)
return;
+ _valueChanged = ValueChanged.X;
+ OnValueChanged();
+ }
+ private void OnYValueChanged()
+ {
+ if (IsSetBlocked)
+ return;
+ _valueChanged = ValueChanged.Y;
+ OnValueChanged();
+ }
+
+ private void OnZValueChanged()
+ {
+ if (IsSetBlocked)
+ return;
+ _valueChanged = ValueChanged.Z;
+ OnValueChanged();
+ }
+
+ private void OnValueChanged()
+ {
var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding;
var token = isSliding ? this : null;
var useCachedAngles = isSliding && token == _cachedToken;
-
- float x = (useCachedAngles && !XElement.IsSliding) ? _cachedAngles.X : XElement.ValueBox.Value;
- float y = (useCachedAngles && !YElement.IsSliding) ? _cachedAngles.Y : YElement.ValueBox.Value;
- float z = (useCachedAngles && !ZElement.IsSliding) ? _cachedAngles.Z : ZElement.ValueBox.Value;
-
- x = Mathf.UnwindDegrees(x);
- y = Mathf.UnwindDegrees(y);
- z = Mathf.UnwindDegrees(z);
-
- if (!useCachedAngles)
+
+ if (HasDifferentValues && Values.Count > 1)
{
- _cachedAngles = new Float3(x, y, z);
+ var xValue = XElement.ValueBox.Value;
+ var yValue = YElement.ValueBox.Value;
+ var zValue = ZElement.ValueBox.Value;
+
+ xValue = Mathf.UnwindDegrees(xValue);
+ yValue = Mathf.UnwindDegrees(yValue);
+ zValue = Mathf.UnwindDegrees(zValue);
+
+ var value = new Float3(xValue, yValue, zValue);
+
+ _cachedToken = token;
+
+ var newObjects = new object[Values.Count];
+ // Handle Sliding
+ if (AllowSlidingForDifferentValues && (isSliding || _slidingEnded))
+ {
+ Float3 average = Float3.Zero;
+ for (int i = 0; i < Values.Count; i++)
+ {
+ var v = (Quaternion)Values[0];
+ var euler = v.EulerAngles;
+
+ average += euler;
+ }
+
+ average /= Values.Count;
+
+ var newValue = value - average;
+
+ for (int i = 0; i < Values.Count; i++)
+ {
+ var v = Values[i];
+ var val = (Quaternion)v;
+ Quaternion.Euler(_valueChanged == ValueChanged.X ? newValue.X : 0, _valueChanged == ValueChanged.Y ? newValue.Y : 0, _valueChanged == ValueChanged.Z ? newValue.Z : 0, out Quaternion qVal);
+ v = val * qVal;
+
+ newObjects[i] = v;
+ }
+
+ // Capture last sliding value
+ if (_slidingEnded)
+ _slidingEnded = false;
+ }
+ else
+ {
+ for (int i = 0; i < Values.Count; i++)
+ {
+ object v = Values[i];
+ var val = (Quaternion)v;
+ var euler = val.EulerAngles;
+ Quaternion.Euler(_valueChanged == ValueChanged.X ? xValue : euler.X, _valueChanged == ValueChanged.Y ? yValue : euler.Y, _valueChanged == ValueChanged.Z ? zValue : euler.Z, out Quaternion qVal);
+ v = val * qVal;
+
+ newObjects[i] = v;
+ }
+ }
+
+ SetValue(newObjects, token);
}
+ else
+ {
+ float x = (useCachedAngles && !XElement.IsSliding) ? _cachedAngles.X : XElement.ValueBox.Value;
+ float y = (useCachedAngles && !YElement.IsSliding) ? _cachedAngles.Y : YElement.ValueBox.Value;
+ float z = (useCachedAngles && !ZElement.IsSliding) ? _cachedAngles.Z : ZElement.ValueBox.Value;
- _cachedToken = token;
+ x = Mathf.UnwindDegrees(x);
+ y = Mathf.UnwindDegrees(y);
+ z = Mathf.UnwindDegrees(z);
- Quaternion.Euler(x, y, z, out Quaternion value);
- SetValue(value, token);
+ if (!useCachedAngles)
+ {
+ _cachedAngles = new Float3(x, y, z);
+ }
+
+ _cachedToken = token;
+
+ Quaternion.Euler(x, y, z, out Quaternion value);
+ SetValue(value, token);
+ }
}
///
@@ -112,7 +219,73 @@ namespace FlaxEditor.CustomEditors.Editors
if (HasDifferentValues)
{
- // TODO: support different values for ValueBox
+ // Get which values are different
+ bool xDifferent = false;
+ bool yDifferent = false;
+ bool zDifferent = false;
+ Float3 cachedFirstValue = Float3.Zero;
+ Float3 average = Float3.Zero;
+ for (int i = 0; i < Values.Count; i++)
+ {
+ var value = (Quaternion)Values[i];
+ var euler = value.EulerAngles;
+
+ average += euler;
+
+ if (i == 0)
+ {
+ cachedFirstValue = euler;
+ continue;
+ }
+
+ if (!Mathf.NearEqual(cachedFirstValue.X, value.X))
+ xDifferent = true;
+ if (!Mathf.NearEqual(cachedFirstValue.Y, value.Y))
+ yDifferent = true;
+ if (!Mathf.NearEqual(cachedFirstValue.Z, value.Z))
+ zDifferent = true;
+ }
+
+ average /= Values.Count;
+
+ if (!xDifferent)
+ {
+ XElement.ValueBox.Value = cachedFirstValue.X;
+ }
+ else
+ {
+ if (AllowSlidingForDifferentValues)
+ XElement.ValueBox.Value = average.X;
+ else
+ XElement.ValueBox.SlideSpeed = 0;
+ XElement.ValueBox.Text = "---";
+ }
+
+ if (!yDifferent)
+ {
+ YElement.ValueBox.Value = cachedFirstValue.Y;
+ }
+ else
+ {
+ if (AllowSlidingForDifferentValues)
+ YElement.ValueBox.Value = average.Y;
+ else
+ YElement.ValueBox.SlideSpeed = 0;
+ YElement.ValueBox.Text = "---";
+ }
+
+ if (!zDifferent)
+ {
+ ZElement.ValueBox.Value = cachedFirstValue.Z;
+ }
+ else
+ {
+ if (AllowSlidingForDifferentValues)
+ ZElement.ValueBox.Value = average.Z;
+ else
+ ZElement.ValueBox.SlideSpeed = 0;
+ ZElement.ValueBox.Text = "---";
+ }
}
else
{
@@ -121,6 +294,13 @@ namespace FlaxEditor.CustomEditors.Editors
XElement.ValueBox.Value = euler.X;
YElement.ValueBox.Value = euler.Y;
ZElement.ValueBox.Value = euler.Z;
+
+ if (!Mathf.NearEqual(XElement.ValueBox.SlideSpeed, _defaultSlidingSpeed))
+ XElement.ValueBox.SlideSpeed = _defaultSlidingSpeed;
+ if (!Mathf.NearEqual(YElement.ValueBox.SlideSpeed, _defaultSlidingSpeed))
+ YElement.ValueBox.SlideSpeed = _defaultSlidingSpeed;
+ if (!Mathf.NearEqual(ZElement.ValueBox.SlideSpeed, _defaultSlidingSpeed))
+ ZElement.ValueBox.SlideSpeed = _defaultSlidingSpeed;
}
}
}
diff --git a/Source/Editor/CustomEditors/Editors/TerrainLayerEditor.cs b/Source/Editor/CustomEditors/Editors/TerrainLayerEditor.cs
new file mode 100644
index 000000000..6e6f876b0
--- /dev/null
+++ b/Source/Editor/CustomEditors/Editors/TerrainLayerEditor.cs
@@ -0,0 +1,45 @@
+// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+
+using FlaxEditor.Content.Settings;
+using FlaxEditor.CustomEditors.Elements;
+using FlaxEditor.GUI;
+
+namespace FlaxEditor.CustomEditors.Editors
+{
+ ///
+ /// Custom editor for picking terrain layers. Instead of choosing bit mask or layer index it shows a combo box with simple layer picking by name.
+ ///
+ public sealed class TerrainLayerEditor : CustomEditor
+ {
+ private ComboBoxElement element;
+
+ ///
+ public override DisplayStyle Style => DisplayStyle.Inline;
+
+ ///
+ public override void Initialize(LayoutElementsContainer layout)
+ {
+ element = layout.ComboBox();
+ element.ComboBox.SetItems(LayersAndTagsSettings.GetCurrentTerrainLayers());
+ element.ComboBox.SelectedIndex = (int)Values[0];
+ element.ComboBox.SelectedIndexChanged += OnSelectedIndexChanged;
+ }
+
+ private void OnSelectedIndexChanged(ComboBox comboBox)
+ {
+ int value = comboBox.SelectedIndex;
+ if (value == -1)
+ value = 0;
+
+ SetValue(value);
+ }
+
+ ///
+ public override void Refresh()
+ {
+ base.Refresh();
+
+ element.ComboBox.SelectedIndex = (int)Values[0];
+ }
+ }
+}
diff --git a/Source/Editor/CustomEditors/Editors/Vector3Editor.cs b/Source/Editor/CustomEditors/Editors/Vector3Editor.cs
index 80fe7e205..9e01e8323 100644
--- a/Source/Editor/CustomEditors/Editors/Vector3Editor.cs
+++ b/Source/Editor/CustomEditors/Editors/Vector3Editor.cs
@@ -7,6 +7,27 @@ using FlaxEngine.GUI;
namespace FlaxEditor.CustomEditors.Editors
{
+ ///
+ /// The value changed by custom Vector3 editors.
+ ///
+ public enum ValueChanged
+ {
+ ///
+ /// X value changed.
+ ///
+ X = 0,
+
+ ///
+ /// Y value changed.
+ ///
+ Y = 1,
+
+ ///
+ /// Z value changed.
+ ///
+ Z = 2
+ }
+
///
/// Default implementation of the inspector used to edit Vector3 value type properties.
///
@@ -49,14 +70,14 @@ namespace FlaxEditor.CustomEditors.Editors
///
public bool LinkValues = false;
- private enum ValueChanged
- {
- X = 0,
- Y = 1,
- Z = 2
- }
+ ///
+ /// Whether to use the average for sliding different values.
+ ///
+ public virtual bool AllowSlidingForDifferentValues => true;
private ValueChanged _valueChanged;
+ private float _defaultSlidingSpeed;
+ private bool _slidingEnded = false;
///
public override void Initialize(LayoutElementsContainer layout)
@@ -83,19 +104,32 @@ namespace FlaxEditor.CustomEditors.Editors
XElement.SetLimits(limit);
XElement.SetCategory(category);
XElement.ValueBox.ValueChanged += OnXValueChanged;
- XElement.ValueBox.SlidingEnd += ClearToken;
+ XElement.ValueBox.SlidingEnd += () =>
+ {
+ _slidingEnded = true;
+ ClearToken();
+ };
+ _defaultSlidingSpeed = XElement.ValueBox.SlideSpeed;
YElement = grid.FloatValue();
YElement.SetLimits(limit);
YElement.SetCategory(category);
YElement.ValueBox.ValueChanged += OnYValueChanged;
- YElement.ValueBox.SlidingEnd += ClearToken;
+ YElement.ValueBox.SlidingEnd += () =>
+ {
+ _slidingEnded = true;
+ ClearToken();
+ };
ZElement = grid.FloatValue();
ZElement.SetLimits(limit);
ZElement.SetCategory(category);
ZElement.ValueBox.ValueChanged += OnZValueChanged;
- ZElement.ValueBox.SlidingEnd += ClearToken;
+ ZElement.ValueBox.SlidingEnd += () =>
+ {
+ _slidingEnded = true;
+ ClearToken();
+ };
if (LinkedLabel != null)
{
@@ -118,8 +152,7 @@ namespace FlaxEditor.CustomEditors.Editors
{
if (IsSetBlocked)
return;
- if (LinkValues)
- _valueChanged = ValueChanged.X;
+ _valueChanged = ValueChanged.X;
OnValueChanged();
}
@@ -127,8 +160,7 @@ namespace FlaxEditor.CustomEditors.Editors
{
if (IsSetBlocked)
return;
- if (LinkValues)
- _valueChanged = ValueChanged.Y;
+ _valueChanged = ValueChanged.Y;
OnValueChanged();
}
@@ -136,8 +168,7 @@ namespace FlaxEditor.CustomEditors.Editors
{
if (IsSetBlocked)
return;
- if (LinkValues)
- _valueChanged = ValueChanged.Z;
+ _valueChanged = ValueChanged.Z;
OnValueChanged();
}
@@ -191,14 +222,79 @@ namespace FlaxEditor.CustomEditors.Editors
var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding;
var token = isSliding ? this : null;
var value = new Float3(xValue, yValue, zValue);
- object v = Values[0];
- if (v is Vector3)
- v = (Vector3)value;
- else if (v is Float3)
- v = (Float3)value;
- else if (v is Double3)
- v = (Double3)value;
- SetValue(v, token);
+ if (HasDifferentValues && Values.Count > 1)
+ {
+ var newObjects = new object[Values.Count];
+ // Handle Sliding
+ if (AllowSlidingForDifferentValues && (isSliding || _slidingEnded))
+ {
+ // TODO: handle linked values
+ Float3 average = Float3.Zero;
+ for (int i = 0; i < Values.Count; i++)
+ {
+ var v = Values[i];
+ var castedValue = Float3.Zero;
+ if (v is Vector3 asVector3)
+ castedValue = asVector3;
+ else if (v is Float3 asFloat3)
+ castedValue = asFloat3;
+ else if (v is Double3 asDouble3)
+ castedValue = asDouble3;
+
+ average += castedValue;
+ }
+
+ average /= Values.Count;
+
+ var newValue = value - average;
+
+ for (int i = 0; i < Values.Count; i++)
+ {
+ var v = Values[i];
+ if (v is Vector3 asVector3)
+ v = asVector3 + new Vector3(_valueChanged == ValueChanged.X ? newValue.X : 0, _valueChanged == ValueChanged.Y ? newValue.Y : 0, _valueChanged == ValueChanged.Z ? newValue.Z : 0);
+ else if (v is Float3 asFloat3)
+ v = asFloat3 + new Float3(_valueChanged == ValueChanged.X ? newValue.X : 0, _valueChanged == ValueChanged.Y ? newValue.Y : 0, _valueChanged == ValueChanged.Z ? newValue.Z : 0);
+ else if (v is Double3 asDouble3)
+ v = asDouble3 + new Double3(_valueChanged == ValueChanged.X ? newValue.X : 0, _valueChanged == ValueChanged.Y ? newValue.Y : 0, _valueChanged == ValueChanged.Z ? newValue.Z : 0);
+
+ newObjects[i] = v;
+ }
+
+ // Capture last sliding value
+ if (_slidingEnded)
+ _slidingEnded = false;
+ }
+ else
+ {
+ // TODO: handle linked values
+ for (int i = 0; i < Values.Count; i++)
+ {
+ object v = Values[i];
+ if (v is Vector3 asVector3)
+ v = new Vector3(_valueChanged == ValueChanged.X ? xValue : asVector3.X, _valueChanged == ValueChanged.Y ? yValue : asVector3.Y, _valueChanged == ValueChanged.Z ? zValue : asVector3.Z);
+ else if (v is Float3 asFloat3)
+ v = new Float3(_valueChanged == ValueChanged.X ? xValue : asFloat3.X, _valueChanged == ValueChanged.Y ? yValue : asFloat3.Y, _valueChanged == ValueChanged.Z ? zValue : asFloat3.Z);
+ else if (v is Double3 asDouble3)
+ v = new Double3(_valueChanged == ValueChanged.X ? xValue : asDouble3.X, _valueChanged == ValueChanged.Y ? yValue : asDouble3.Y, _valueChanged == ValueChanged.Z ? zValue : asDouble3.Z);
+
+ newObjects[i] = v;
+ }
+ }
+
+ SetValue(newObjects, token);
+ }
+ else
+ {
+ object v = Values[0];
+ if (v is Vector3)
+ v = (Vector3)value;
+ else if (v is Float3)
+ v = (Float3)value;
+ else if (v is Double3)
+ v = (Double3)value;
+ SetValue(v, token);
+ }
}
private float GetRatio(float value, float initialValue)
@@ -218,7 +314,79 @@ namespace FlaxEditor.CustomEditors.Editors
if (HasDifferentValues)
{
- // TODO: support different values for ValueBox
+ // Get which values are different
+ bool xDifferent = false;
+ bool yDifferent = false;
+ bool zDifferent = false;
+ Float3 cachedFirstValue = Float3.Zero;
+ Float3 average = Float3.Zero;
+ for (int i = 0; i < Values.Count; i++)
+ {
+ var v = Values[i];
+ var value = Float3.Zero;
+ if (v is Vector3 asVector3)
+ value = asVector3;
+ else if (v is Float3 asFloat3)
+ value = asFloat3;
+ else if (v is Double3 asDouble3)
+ value = asDouble3;
+
+ average += value;
+
+ if (i == 0)
+ {
+ cachedFirstValue = value;
+ continue;
+ }
+
+ if (!Mathf.NearEqual(cachedFirstValue.X, value.X))
+ xDifferent = true;
+ if (!Mathf.NearEqual(cachedFirstValue.Y, value.Y))
+ yDifferent = true;
+ if (!Mathf.NearEqual(cachedFirstValue.Z, value.Z))
+ zDifferent = true;
+ }
+
+ average /= Values.Count;
+
+ if (!xDifferent)
+ {
+ XElement.ValueBox.Value = cachedFirstValue.X;
+ }
+ else
+ {
+ if (AllowSlidingForDifferentValues)
+ XElement.ValueBox.Value = average.X;
+ else
+ XElement.ValueBox.SlideSpeed = 0;
+ XElement.ValueBox.Text = "---";
+ }
+
+ if (!yDifferent)
+ {
+ YElement.ValueBox.Value = cachedFirstValue.Y;
+ }
+ else
+ {
+ if (AllowSlidingForDifferentValues)
+ YElement.ValueBox.Value = average.Y;
+ else
+ YElement.ValueBox.SlideSpeed = 0;
+ YElement.ValueBox.Text = "---";
+ }
+
+ if (!zDifferent)
+ {
+ ZElement.ValueBox.Value = cachedFirstValue.Z;
+ }
+ else
+ {
+ if (AllowSlidingForDifferentValues)
+ ZElement.ValueBox.Value = average.Z;
+ else
+ ZElement.ValueBox.SlideSpeed = 0;
+ ZElement.ValueBox.Text = "---";
+ }
}
else
{
@@ -232,6 +400,13 @@ namespace FlaxEditor.CustomEditors.Editors
XElement.ValueBox.Value = value.X;
YElement.ValueBox.Value = value.Y;
ZElement.ValueBox.Value = value.Z;
+
+ if (!Mathf.NearEqual(XElement.ValueBox.SlideSpeed, _defaultSlidingSpeed))
+ XElement.ValueBox.SlideSpeed = _defaultSlidingSpeed;
+ if (!Mathf.NearEqual(YElement.ValueBox.SlideSpeed, _defaultSlidingSpeed))
+ YElement.ValueBox.SlideSpeed = _defaultSlidingSpeed;
+ if (!Mathf.NearEqual(ZElement.ValueBox.SlideSpeed, _defaultSlidingSpeed))
+ ZElement.ValueBox.SlideSpeed = _defaultSlidingSpeed;
}
}
}
@@ -259,6 +434,15 @@ namespace FlaxEditor.CustomEditors.Editors
///
public override DisplayStyle Style => DisplayStyle.Inline;
+
+ ///
+ /// Whether to use the average for sliding different values.
+ ///
+ public virtual bool AllowSlidingForDifferentValues => true;
+
+ private ValueChanged _valueChanged;
+ private float _defaultSlidingSpeed;
+ private bool _slidingEnded = false;
///
public override void Initialize(LayoutElementsContainer layout)
@@ -284,20 +468,33 @@ namespace FlaxEditor.CustomEditors.Editors
XElement = grid.DoubleValue();
XElement.SetLimits(limit);
XElement.SetCategory(category);
- XElement.ValueBox.ValueChanged += OnValueChanged;
- XElement.ValueBox.SlidingEnd += ClearToken;
+ XElement.ValueBox.ValueChanged += OnXValueChanged;
+ XElement.ValueBox.SlidingEnd += () =>
+ {
+ _slidingEnded = true;
+ ClearToken();
+ };
+ _defaultSlidingSpeed = XElement.ValueBox.SlideSpeed;
YElement = grid.DoubleValue();
YElement.SetLimits(limit);
YElement.SetCategory(category);
- YElement.ValueBox.ValueChanged += OnValueChanged;
- YElement.ValueBox.SlidingEnd += ClearToken;
+ YElement.ValueBox.ValueChanged += OnYValueChanged;
+ YElement.ValueBox.SlidingEnd += () =>
+ {
+ _slidingEnded = true;
+ ClearToken();
+ };
ZElement = grid.DoubleValue();
ZElement.SetLimits(limit);
ZElement.SetCategory(category);
- ZElement.ValueBox.ValueChanged += OnValueChanged;
- ZElement.ValueBox.SlidingEnd += ClearToken;
+ ZElement.ValueBox.ValueChanged += OnZValueChanged;
+ ZElement.ValueBox.SlidingEnd += () =>
+ {
+ _slidingEnded = true;
+ ClearToken();
+ };
if (LinkedLabel != null)
{
@@ -316,22 +513,115 @@ namespace FlaxEditor.CustomEditors.Editors
}
}
+ private void OnXValueChanged()
+ {
+ if (IsSetBlocked)
+ return;
+ _valueChanged = ValueChanged.X;
+ OnValueChanged();
+ }
+
+ private void OnYValueChanged()
+ {
+ if (IsSetBlocked)
+ return;
+ _valueChanged = ValueChanged.Y;
+ OnValueChanged();
+ }
+
+ private void OnZValueChanged()
+ {
+ if (IsSetBlocked)
+ return;
+ _valueChanged = ValueChanged.Z;
+ OnValueChanged();
+ }
+
private void OnValueChanged()
{
if (IsSetBlocked || Values == null)
return;
+ var xValue = XElement.ValueBox.Value;
+ var yValue = YElement.ValueBox.Value;
+ var zValue = ZElement.ValueBox.Value;
+
var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding;
var token = isSliding ? this : null;
- var value = new Double3(XElement.ValueBox.Value, YElement.ValueBox.Value, ZElement.ValueBox.Value);
- object v = Values[0];
- if (v is Vector3)
- v = (Vector3)value;
- else if (v is Float3)
- v = (Float3)value;
- else if (v is Double3)
- v = (Double3)value;
- SetValue(v, token);
+ var value = new Double3(xValue, yValue, zValue);
+ if (HasDifferentValues && Values.Count > 1)
+ {
+ var newObjects = new object[Values.Count];
+ // Handle Sliding
+ if (AllowSlidingForDifferentValues && (isSliding || _slidingEnded))
+ {
+ // TODO: handle linked values
+ Double3 average = Double3.Zero;
+ for (int i = 0; i < Values.Count; i++)
+ {
+ var v = Values[i];
+ var castedValue = Double3.Zero;
+ if (v is Vector3 asVector3)
+ castedValue = asVector3;
+ else if (v is Float3 asFloat3)
+ castedValue = asFloat3;
+ else if (v is Double3 asDouble3)
+ castedValue = asDouble3;
+
+ average += castedValue;
+ }
+
+ average /= Values.Count;
+
+ var newValue = value - average;
+
+ for (int i = 0; i < Values.Count; i++)
+ {
+ var v = Values[i];
+ if (v is Vector3 asVector3)
+ v = asVector3 + new Vector3(_valueChanged == ValueChanged.X ? newValue.X : 0, _valueChanged == ValueChanged.Y ? newValue.Y : 0, _valueChanged == ValueChanged.Z ? newValue.Z : 0);
+ else if (v is Float3 asFloat3)
+ v = asFloat3 + new Float3(_valueChanged == ValueChanged.X ? (float)newValue.X : 0, _valueChanged == ValueChanged.Y ? (float)newValue.Y : 0, _valueChanged == ValueChanged.Z ? (float)newValue.Z : 0);
+ else if (v is Double3 asDouble3)
+ v = asDouble3 + new Double3(_valueChanged == ValueChanged.X ? newValue.X : 0, _valueChanged == ValueChanged.Y ? newValue.Y : 0, _valueChanged == ValueChanged.Z ? newValue.Z : 0);
+
+ newObjects[i] = v;
+ }
+
+ // Capture last sliding value
+ if (_slidingEnded)
+ _slidingEnded = false;
+ }
+ else
+ {
+ // TODO: handle linked values
+ for (int i = 0; i < Values.Count; i++)
+ {
+ object v = Values[i];
+ if (v is Vector3 asVector3)
+ v = new Vector3(_valueChanged == ValueChanged.X ? xValue : asVector3.X, _valueChanged == ValueChanged.Y ? yValue : asVector3.Y, _valueChanged == ValueChanged.Z ? zValue : asVector3.Z);
+ else if (v is Float3 asFloat3)
+ v = new Float3(_valueChanged == ValueChanged.X ? (float)xValue : asFloat3.X, _valueChanged == ValueChanged.Y ? (float)yValue : asFloat3.Y, _valueChanged == ValueChanged.Z ? (float)zValue : asFloat3.Z);
+ else if (v is Double3 asDouble3)
+ v = new Double3(_valueChanged == ValueChanged.X ? xValue : asDouble3.X, _valueChanged == ValueChanged.Y ? yValue : asDouble3.Y, _valueChanged == ValueChanged.Z ? zValue : asDouble3.Z);
+
+ newObjects[i] = v;
+ }
+ }
+
+ SetValue(newObjects, token);
+ }
+ else
+ {
+ object v = Values[0];
+ if (v is Vector3)
+ v = (Vector3)value;
+ else if (v is Float3)
+ v = (Float3)value;
+ else if (v is Double3)
+ v = (Double3)value;
+ SetValue(v, token);
+ }
}
///
@@ -341,7 +631,79 @@ namespace FlaxEditor.CustomEditors.Editors
if (HasDifferentValues)
{
- // TODO: support different values for ValueBox
+ // Get which values are different
+ bool xDifferent = false;
+ bool yDifferent = false;
+ bool zDifferent = false;
+ Double3 cachedFirstValue = Double3.Zero;
+ Double3 average = Double3.Zero;
+ for (int i = 0; i < Values.Count; i++)
+ {
+ var v = Values[i];
+ var value = Double3.Zero;
+ if (v is Vector3 asVector3)
+ value = asVector3;
+ else if (v is Float3 asFloat3)
+ value = asFloat3;
+ else if (v is Double3 asDouble3)
+ value = asDouble3;
+
+ average += value;
+
+ if (i == 0)
+ {
+ cachedFirstValue = value;
+ continue;
+ }
+
+ if (!Mathd.NearEqual(cachedFirstValue.X, value.X))
+ xDifferent = true;
+ if (!Mathd.NearEqual(cachedFirstValue.Y, value.Y))
+ yDifferent = true;
+ if (!Mathd.NearEqual(cachedFirstValue.Z, value.Z))
+ zDifferent = true;
+ }
+
+ average /= Values.Count;
+
+ if (!xDifferent)
+ {
+ XElement.ValueBox.Value = cachedFirstValue.X;
+ }
+ else
+ {
+ if (AllowSlidingForDifferentValues)
+ XElement.ValueBox.Value = average.X;
+ else
+ XElement.ValueBox.SlideSpeed = 0;
+ XElement.ValueBox.Text = "---";
+ }
+
+ if (!yDifferent)
+ {
+ YElement.ValueBox.Value = cachedFirstValue.Y;
+ }
+ else
+ {
+ if (AllowSlidingForDifferentValues)
+ YElement.ValueBox.Value = average.Y;
+ else
+ YElement.ValueBox.SlideSpeed = 0;
+ YElement.ValueBox.Text = "---";
+ }
+
+ if (!zDifferent)
+ {
+ ZElement.ValueBox.Value = cachedFirstValue.Z;
+ }
+ else
+ {
+ if (AllowSlidingForDifferentValues)
+ ZElement.ValueBox.Value = average.Z;
+ else
+ ZElement.ValueBox.SlideSpeed = 0;
+ ZElement.ValueBox.Text = "---";
+ }
}
else
{
@@ -355,6 +717,19 @@ namespace FlaxEditor.CustomEditors.Editors
XElement.ValueBox.Value = value.X;
YElement.ValueBox.Value = value.Y;
ZElement.ValueBox.Value = value.Z;
+
+ if (!Mathf.NearEqual(XElement.ValueBox.SlideSpeed, _defaultSlidingSpeed))
+ {
+ XElement.ValueBox.SlideSpeed = _defaultSlidingSpeed;
+ }
+ if (!Mathf.NearEqual(YElement.ValueBox.SlideSpeed, _defaultSlidingSpeed))
+ {
+ YElement.ValueBox.SlideSpeed = _defaultSlidingSpeed;
+ }
+ if (!Mathf.NearEqual(ZElement.ValueBox.SlideSpeed, _defaultSlidingSpeed))
+ {
+ ZElement.ValueBox.SlideSpeed = _defaultSlidingSpeed;
+ }
}
}
}
diff --git a/Source/Editor/CustomEditors/Values/ValueContainer.cs b/Source/Editor/CustomEditors/Values/ValueContainer.cs
index c583b96c3..601d20bb9 100644
--- a/Source/Editor/CustomEditors/Values/ValueContainer.cs
+++ b/Source/Editor/CustomEditors/Values/ValueContainer.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
+using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
@@ -385,10 +386,21 @@ namespace FlaxEditor.CustomEditors
if (instanceValues.Count != Count)
throw new ArgumentException();
- for (int i = 0; i < Count; i++)
+ if (value is IList l && l.Count == Count)
{
- Info.SetValue(instanceValues[i], value);
- this[i] = Info.GetValue(instanceValues[i]);
+ for (int i = 0; i < Count; i++)
+ {
+ Info.SetValue(instanceValues[i], l[i]);
+ this[i] = Info.GetValue(instanceValues[i]);
+ }
+ }
+ else
+ {
+ for (int i = 0; i < Count; i++)
+ {
+ Info.SetValue(instanceValues[i], value);
+ this[i] = Info.GetValue(instanceValues[i]);
+ }
}
}
diff --git a/Source/Editor/Editor.cpp b/Source/Editor/Editor.cpp
index 8fd646da9..885f41535 100644
--- a/Source/Editor/Editor.cpp
+++ b/Source/Editor/Editor.cpp
@@ -673,6 +673,7 @@ bool Editor::Init()
Managed = New();
// Show splash screen
+ if (!CommandLine::Options.Headless.IsTrue())
{
PROFILE_CPU_NAMED("Splash");
if (EditorImpl::Splash == nullptr)
diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs
index d99a55a08..795f89d55 100644
--- a/Source/Editor/Editor.cs
+++ b/Source/Editor/Editor.cs
@@ -49,9 +49,9 @@ namespace FlaxEditor
}
private readonly List _modules = new List(16);
- private bool _isAfterInit, _areModulesInited, _areModulesAfterInitEnd, _isHeadlessMode;
+ private bool _isAfterInit, _areModulesInited, _areModulesAfterInitEnd, _isHeadlessMode, _autoExit;
private string _projectToOpen;
- private float _lastAutoSaveTimer;
+ private float _lastAutoSaveTimer, _autoExitTimeout = 0.1f;
private Button _saveNowButton;
private Button _cancelSaveButton;
private bool _autoSaveNow;
@@ -258,10 +258,11 @@ namespace FlaxEditor
Instance = this;
}
- internal void Init(bool isHeadless, bool skipCompile, bool newProject, Guid startupScene)
+ internal void Init(StartupFlags flags, Guid startupScene)
{
Log("Setting up C# Editor...");
- _isHeadlessMode = isHeadless;
+ _isHeadlessMode = flags.HasFlag(StartupFlags.Headless);
+ _autoExit = flags.HasFlag(StartupFlags.Exit);
_startupSceneCmdLine = startupScene;
Profiler.BeginEvent("Projects");
@@ -297,11 +298,11 @@ namespace FlaxEditor
StateMachine = new EditorStateMachine(this);
Undo = new EditorUndo(this);
- if (newProject)
+ if (flags.HasFlag(StartupFlags.NewProject))
InitProject();
EnsureState();
Log("Editor init");
- if (isHeadless)
+ if (_isHeadlessMode)
Log("Running in headless mode");
// Note: we don't sort modules before Init (optimized)
@@ -357,7 +358,7 @@ namespace FlaxEditor
InitializationStart?.Invoke();
// Start Editor initialization ending phrase (will wait for scripts compilation result)
- StateMachine.LoadingState.StartInitEnding(skipCompile);
+ StateMachine.LoadingState.StartInitEnding(flags.HasFlag(StartupFlags.SkipCompile));
}
internal void RegisterModule(EditorModule module)
@@ -486,6 +487,15 @@ namespace FlaxEditor
{
StateMachine.Update();
UpdateAutoSave();
+ if (_autoExit && StateMachine.CurrentState == StateMachine.EditingSceneState)
+ {
+ _autoExitTimeout -= Time.UnscaledGameTime;
+ if (_autoExitTimeout < 0.0f)
+ {
+ Log("Auto exit");
+ Engine.RequestExit(0);
+ }
+ }
if (!StateMachine.IsPlayMode)
{
diff --git a/Source/Editor/GUI/CurveEditor.Contents.cs b/Source/Editor/GUI/CurveEditor.Contents.cs
index bdb1c650b..25954abfa 100644
--- a/Source/Editor/GUI/CurveEditor.Contents.cs
+++ b/Source/Editor/GUI/CurveEditor.Contents.cs
@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
-using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;
@@ -29,6 +28,7 @@ namespace FlaxEditor.GUI
internal Float2 _mousePos = Float2.Minimum;
internal bool _isMovingSelection;
internal bool _isMovingTangent;
+ internal bool _movedView;
internal bool _movedKeyframes;
private TangentPoint _movingTangent;
private Float2 _movingSelectionStart;
@@ -190,31 +190,28 @@ namespace FlaxEditor.GUI
// Moving view
if (_rightMouseDown)
{
- var delta = location - _movingViewLastPos;
+ var movingViewPos = Parent.PointToParent(PointToParent(location));
+ var delta = movingViewPos - _movingViewLastPos;
if (_editor.CustomViewPanning != null)
delta = _editor.CustomViewPanning(delta);
- delta *= GetUseModeMask(_editor.EnablePanning) * _editor.ViewScale;
+ delta *= GetUseModeMask(_editor.EnablePanning);
if (delta.LengthSquared > 0.01f)
{
_editor._mainPanel.ViewOffset += delta;
- _movingViewLastPos = location;
+ _movingViewLastPos = movingViewPos;
+ _movedView = true;
if (_editor.CustomViewPanning != null)
{
- Cursor = CursorType.SizeAll;
+ if (Cursor == CursorType.Default)
+ Cursor = CursorType.SizeAll;
}
else
{
switch (_editor.EnablePanning)
{
- case UseMode.Vertical:
- Cursor = CursorType.SizeNS;
- break;
- case UseMode.Horizontal:
- Cursor = CursorType.SizeWE;
- break;
- case UseMode.On:
- Cursor = CursorType.SizeAll;
- break;
+ case UseMode.Vertical: Cursor = CursorType.SizeNS; break;
+ case UseMode.Horizontal: Cursor = CursorType.SizeWE; break;
+ case UseMode.On: Cursor = CursorType.SizeAll; break;
}
}
}
@@ -234,11 +231,10 @@ namespace FlaxEditor.GUI
else if (_isMovingTangent)
{
var viewRect = _editor._mainPanel.GetClientArea();
- var direction = _movingTangent.IsIn ? -1.0f : 1.0f;
var k = _editor.GetKeyframe(_movingTangent.Index);
var kv = _editor.GetKeyframeValue(k);
var value = _editor.Accessor.GetCurveValue(ref kv, _movingTangent.Component);
- _movingTangent.TangentValue = direction * (PointToKeyframes(location, ref viewRect).Y - value);
+ _movingTangent.TangentValue = PointToKeyframes(location, ref viewRect).Y - value;
_editor.UpdateTangents();
Cursor = CursorType.SizeNS;
_movedKeyframes = true;
@@ -299,7 +295,8 @@ namespace FlaxEditor.GUI
{
_rightMouseDown = true;
_rightMouseDownPos = location;
- _movingViewLastPos = location;
+ _movedView = false;
+ _movingViewLastPos = Parent.PointToParent(PointToParent(location));
}
// Check if any node is under the mouse
@@ -444,7 +441,7 @@ namespace FlaxEditor.GUI
Cursor = CursorType.Default;
// Check if no move has been made at all
- if (Float2.Distance(ref location, ref _rightMouseDownPos) < 2.0f)
+ if (!_movedView)
{
var selectionCount = _editor.SelectionCount;
var point = GetChildAt(location) as KeyframePoint;
@@ -512,6 +509,27 @@ namespace FlaxEditor.GUI
return true;
}
+ ///
+ public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
+ {
+ if (base.OnMouseDoubleClick(location, button))
+ return true;
+
+ // Add keyframe on double click
+ var child = GetChildAt(location);
+ if (child is not KeyframePoint &&
+ child is not TangentPoint &&
+ _editor.KeyframesCount < _editor.MaxKeyframes)
+ {
+ var viewRect = _editor._mainPanel.GetClientArea();
+ var pos = PointToKeyframes(location, ref viewRect);
+ _editor.AddKeyframe(pos);
+ return true;
+ }
+
+ return false;
+ }
+
///
public override bool OnMouseWheel(Float2 location, float delta)
{
@@ -519,10 +537,29 @@ namespace FlaxEditor.GUI
return true;
// Zoom in/out
- if (_editor.EnableZoom != UseMode.Off && IsMouseOver && !_leftMouseDown && RootWindow.GetKey(KeyboardKeys.Control))
+ var zoom = RootWindow.GetKey(KeyboardKeys.Control);
+ var zoomAlt = RootWindow.GetKey(KeyboardKeys.Shift);
+ if (_editor.EnableZoom != UseMode.Off && IsMouseOver && !_leftMouseDown && (zoom || zoomAlt))
{
- // TODO: preserve the view center point for easier zooming
- _editor.ViewScale += GetUseModeMask(_editor.EnableZoom) * (delta * 0.1f);
+ // Cache mouse location in curve-space
+ var viewRect = _editor._mainPanel.GetClientArea();
+ var locationInKeyframes = PointToKeyframes(location, ref viewRect);
+ var locationInEditorBefore = _editor.PointFromKeyframes(locationInKeyframes, ref viewRect);
+
+ // Scale relative to the curve size
+ var scale = new Float2(delta * 0.1f);
+ _editor._mainPanel.GetDesireClientArea(out var mainPanelArea);
+ var curveScale = mainPanelArea.Size / _editor._contents.Size;
+ scale *= curveScale;
+ if (zoomAlt)
+ scale.X = 0; // Scale Y axis only
+ scale *= GetUseModeMask(_editor.EnableZoom); // Mask scale depending on allowed usage
+ _editor.ViewScale += scale;
+
+ // Zoom towards the mouse position
+ var locationInEditorAfter = _editor.PointFromKeyframes(locationInKeyframes, ref viewRect);
+ var locationInEditorDelta = locationInEditorAfter - locationInEditorBefore;
+ _editor.ViewOffset -= locationInEditorDelta;
return true;
}
diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs
index 3499ef5d3..ed0885afd 100644
--- a/Source/Editor/GUI/CurveEditor.cs
+++ b/Source/Editor/GUI/CurveEditor.cs
@@ -671,8 +671,22 @@ namespace FlaxEditor.GUI
///
public override void ShowWholeCurve()
{
- ViewScale = ApplyUseModeMask(EnableZoom, _mainPanel.Size / _contents.Size, ViewScale);
- ViewOffset = ApplyUseModeMask(EnablePanning, -_mainPanel.ControlsBounds.Location, ViewOffset);
+ _mainPanel.GetDesireClientArea(out var mainPanelArea);
+ ViewScale = ApplyUseModeMask(EnableZoom, mainPanelArea.Size / _contents.Size, ViewScale);
+ Float2 minPos = Float2.Maximum;
+ foreach (var point in _points)
+ {
+ var pos = point.PointToParent(point.Location);
+ Float2.Min(ref minPos, ref pos, out minPos);
+ }
+ var minPosPoint = _contents.PointToParent(ref minPos);
+ var scroll = new Float2(_mainPanel.HScrollBar?.TargetValue ?? 0, _mainPanel.VScrollBar?.TargetValue ?? 0);
+ scroll = ApplyUseModeMask(EnablePanning, minPosPoint, scroll);
+ if (_mainPanel.HScrollBar != null)
+ _mainPanel.HScrollBar.TargetValue = scroll.X;
+ if (_mainPanel.VScrollBar != null)
+ _mainPanel.VScrollBar.TargetValue = scroll.Y;
+
UpdateKeyframes();
}
@@ -923,6 +937,11 @@ namespace FlaxEditor.GUI
KeyframesEditorUtils.Paste(this);
return true;
}
+ else if (options.FocusSelection.Process(this))
+ {
+ ShowWholeCurve();
+ return true;
+ }
return false;
}
@@ -1384,9 +1403,7 @@ namespace FlaxEditor.GUI
// Calculate bounds
var bounds = _points[0].Bounds;
for (var i = 1; i < _points.Count; i++)
- {
bounds = Rectangle.Union(bounds, _points[i].Bounds);
- }
// Adjust contents bounds to fill the curve area
if (EnablePanning != UseMode.Off || !ShowCollapsed)
@@ -1632,6 +1649,7 @@ namespace FlaxEditor.GUI
var o = _keyframes[p.Index - 1];
var oValue = Accessor.GetCurveValue(ref o.Value, p.Component);
var slope = (value - oValue) / (k.Time - o.Time);
+ slope = -slope;
Accessor.SetCurveValue(slope, ref k.TangentIn, p.Component);
}
@@ -2116,9 +2134,7 @@ namespace FlaxEditor.GUI
// Calculate bounds
var bounds = _points[0].Bounds;
for (int i = 1; i < _points.Count; i++)
- {
bounds = Rectangle.Union(bounds, _points[i].Bounds);
- }
// Adjust contents bounds to fill the curve area
if (EnablePanning != UseMode.Off || !ShowCollapsed)
@@ -2184,12 +2200,12 @@ namespace FlaxEditor.GUI
var tangent = t.TangentValue;
var direction = t.IsIn ? -1.0f : 1.0f;
- var offset = 30.0f * direction;
+ var offset = 30.0f;
var location = GetKeyframePoint(ref k, selectedComponent);
t.Size = KeyframesSize / ViewScale;
t.Location = new Float2
(
- location.X * UnitsPerSecond - t.Width * 0.5f + offset,
+ location.X * UnitsPerSecond - t.Width * 0.5f + offset * direction,
location.Y * -UnitsPerSecond - t.Height * 0.5f + curveContentAreaBounds.Height - offset * tangent
);
@@ -2265,14 +2281,13 @@ namespace FlaxEditor.GUI
var startTangent = Accessor.GetCurveValue(ref startK.TangentOut, component);
var endTangent = Accessor.GetCurveValue(ref endK.TangentIn, component);
- var offset = (end.X - start.X) * 0.5f;
-
+ var tangentScale = (endK.Time - startK.Time) / 3.0f;
var p1 = PointFromKeyframes(start, ref viewRect);
- var p2 = PointFromKeyframes(start + new Float2(offset, startTangent * offset), ref viewRect);
- var p3 = PointFromKeyframes(end - new Float2(offset, endTangent * offset), ref viewRect);
+ var p2 = PointFromKeyframes(start + new Float2(0, startTangent * tangentScale), ref viewRect);
+ var p3 = PointFromKeyframes(end + new Float2(0, endTangent * tangentScale), ref viewRect);
var p4 = PointFromKeyframes(end, ref viewRect);
- Render2D.DrawBezier(p1, p2, p3, p4, color);
+ Render2D.DrawSpline(p1, p2, p3, p4, color);
}
}
}
diff --git a/Source/Editor/GUI/Splitter.cs b/Source/Editor/GUI/Splitter.cs
new file mode 100644
index 000000000..10a67706e
--- /dev/null
+++ b/Source/Editor/GUI/Splitter.cs
@@ -0,0 +1,86 @@
+// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+
+using System;
+using FlaxEngine;
+using FlaxEngine.GUI;
+
+namespace FlaxEditor.GUI
+{
+ sealed class Splitter : Control
+ {
+ private bool _clicked;
+
+ public Action Moved;
+ public const float DefaultHeight = 5.0f;
+
+ public override void Draw()
+ {
+ var style = Style.Current;
+ if (IsMouseOver || _clicked)
+ Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), _clicked ? style.BackgroundSelected : style.BackgroundHighlighted);
+ }
+
+ public override void OnEndMouseCapture()
+ {
+ base.OnEndMouseCapture();
+
+ _clicked = false;
+ }
+
+ public override void Defocus()
+ {
+ base.Defocus();
+
+ _clicked = false;
+ }
+
+ public override void OnMouseEnter(Float2 location)
+ {
+ base.OnMouseEnter(location);
+
+ Cursor = CursorType.SizeNS;
+ }
+
+ public override void OnMouseLeave()
+ {
+ Cursor = CursorType.Default;
+
+ base.OnMouseLeave();
+ }
+
+ public override bool OnMouseDown(Float2 location, MouseButton button)
+ {
+ if (button == MouseButton.Left)
+ {
+ _clicked = true;
+ Focus();
+ StartMouseCapture();
+ return true;
+ }
+
+ return base.OnMouseDown(location, button);
+ }
+
+ public override void OnMouseMove(Float2 location)
+ {
+ base.OnMouseMove(location);
+
+ if (_clicked)
+ {
+ Moved(location);
+ }
+ }
+
+ public override bool OnMouseUp(Float2 location, MouseButton button)
+ {
+ if (button == MouseButton.Left && _clicked)
+ {
+ _clicked = false;
+ EndMouseCapture();
+ return true;
+ }
+
+ return base.OnMouseUp(location, button);
+ }
+ }
+}
diff --git a/Source/Editor/GUI/Timeline/GUI/Background.cs b/Source/Editor/GUI/Timeline/GUI/Background.cs
index 7bda8f4c0..57b09324e 100644
--- a/Source/Editor/GUI/Timeline/GUI/Background.cs
+++ b/Source/Editor/GUI/Timeline/GUI/Background.cs
@@ -230,7 +230,7 @@ namespace FlaxEditor.GUI.Timeline.GUI
continue;
// Draw all ticks
- int l = Mathf.Clamp(smallestTick + level, 0, _tickSteps.Length - 1);
+ int l = Mathf.Clamp(smallestTick + level, 0, _tickSteps.Length - 2);
var lStep = _tickSteps[l];
var lNextStep = _tickSteps[l + 1];
int startTick = Mathf.FloorToInt(min / lStep);
diff --git a/Source/Editor/GUI/Timeline/Timeline.cs b/Source/Editor/GUI/Timeline/Timeline.cs
index 556c4dcdb..8dabba251 100644
--- a/Source/Editor/GUI/Timeline/Timeline.cs
+++ b/Source/Editor/GUI/Timeline/Timeline.cs
@@ -1446,6 +1446,17 @@ namespace FlaxEditor.GUI.Timeline
{
GetTracks(SelectedTracks[i], tracks);
}
+
+ // Find the lowest track position for selection
+ int lowestTrackLocation = Tracks.Count - 1;
+ for (int i = 0; i < tracks.Count; i++)
+ {
+ var trackToDelete = tracks[i];
+ if (trackToDelete.TrackIndex < lowestTrackLocation)
+ {
+ lowestTrackLocation = trackToDelete.TrackIndex;
+ }
+ }
SelectedTracks.Clear();
if (withUndo && Undo != null && Undo.Enabled)
{
@@ -1468,6 +1479,18 @@ namespace FlaxEditor.GUI.Timeline
}
OnTracksChanged();
MarkAsEdited();
+
+ // Select track above deleted tracks unless track is first track
+ if (Tracks.Count > 0)
+ {
+ if (lowestTrackLocation - 1 >= 0)
+ Select(Tracks[lowestTrackLocation - 1]);
+ else
+ Select(Tracks[0]);
+
+ SelectedTracks[0].Focus();
+ }
+
}
///
@@ -1655,6 +1678,14 @@ namespace FlaxEditor.GUI.Timeline
}
OnTracksChanged();
MarkAsEdited();
+
+ // Deselect and select new clones.
+ Deselect();
+ foreach (var clone in clones)
+ {
+ Select(clone, true);
+ }
+
SelectedTracks[0].Focus();
}
diff --git a/Source/Editor/GUI/Timeline/Tracks/CurvePropertyTrack.cs b/Source/Editor/GUI/Timeline/Tracks/CurvePropertyTrack.cs
index 3196879a1..db5fe90e5 100644
--- a/Source/Editor/GUI/Timeline/Tracks/CurvePropertyTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/CurvePropertyTrack.cs
@@ -19,87 +19,6 @@ namespace FlaxEditor.GUI.Timeline.Tracks
///
public abstract class CurvePropertyTrackBase : MemberTrack, IKeyframesEditorContext
{
- private sealed class Splitter : Control
- {
- private bool _clicked;
- internal CurvePropertyTrackBase _track;
-
- public override void Draw()
- {
- var style = Style.Current;
- if (IsMouseOver || _clicked)
- Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), _clicked ? style.BackgroundSelected : style.BackgroundHighlighted);
- }
-
- public override void OnEndMouseCapture()
- {
- base.OnEndMouseCapture();
-
- _clicked = false;
- }
-
- public override void Defocus()
- {
- base.Defocus();
-
- _clicked = false;
- }
-
- public override void OnMouseEnter(Float2 location)
- {
- base.OnMouseEnter(location);
-
- Cursor = CursorType.SizeNS;
- }
-
- public override void OnMouseLeave()
- {
- Cursor = CursorType.Default;
-
- base.OnMouseLeave();
- }
-
- public override bool OnMouseDown(Float2 location, MouseButton button)
- {
- if (button == MouseButton.Left)
- {
- _clicked = true;
- Focus();
- StartMouseCapture();
- return true;
- }
-
- return base.OnMouseDown(location, button);
- }
-
- public override void OnMouseMove(Float2 location)
- {
- base.OnMouseMove(location);
-
- if (_clicked)
- {
- var height = Mathf.Clamp(PointToParent(location).Y, 40.0f, 1000.0f);
- if (!Mathf.NearEqual(height, _track._expandedHeight))
- {
- _track.Height = _track._expandedHeight = height;
- _track.Timeline.ArrangeTracks();
- }
- }
- }
-
- public override bool OnMouseUp(Float2 location, MouseButton button)
- {
- if (button == MouseButton.Left && _clicked)
- {
- _clicked = false;
- EndMouseCapture();
- return true;
- }
-
- return base.OnMouseUp(location, button);
- }
- }
-
private byte[] _curveEditingStartData;
private float _expandedHeight = 120.0f;
private Splitter _splitter;
@@ -251,12 +170,21 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{
_splitter = new Splitter
{
- _track = this,
+ Moved = OnSplitterMoved,
Parent = Curve,
};
}
- var splitterHeight = 5.0f;
- _splitter.Bounds = new Rectangle(0, Curve.Height - splitterHeight, Curve.Width, splitterHeight);
+ _splitter.Bounds = new Rectangle(0, Curve.Height - Splitter.DefaultHeight, Curve.Width, Splitter.DefaultHeight);
+ }
+ }
+
+ private void OnSplitterMoved(Float2 location)
+ {
+ var height = Mathf.Clamp(PointToParent(location).Y, 40.0f, 1000.0f);
+ if (!Mathf.NearEqual(height, _expandedHeight))
+ {
+ Height = _expandedHeight = height;
+ Timeline.ArrangeTracks();
}
}
diff --git a/Source/Editor/GUI/Timeline/Tracks/ParticleEmitterTrack.cs b/Source/Editor/GUI/Timeline/Tracks/ParticleEmitterTrack.cs
index 82a33f57b..1c25a08c2 100644
--- a/Source/Editor/GUI/Timeline/Tracks/ParticleEmitterTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/ParticleEmitterTrack.cs
@@ -109,6 +109,19 @@ namespace FlaxEditor.GUI.Timeline.Tracks
MaxMediaCount = 1;
}
+ ///
+ public override void OnDuplicated(Track clone)
+ {
+ base.OnDuplicated(clone);
+
+ // Clone overriden parameters
+ if (clone is ParticleEmitterTrack cloneTrack)
+ {
+ foreach (var e in ParametersOverrides)
+ cloneTrack.ParametersOverrides.Add(e.Key, e.Value);
+ }
+ }
+
///
public override void OnDestroy()
{
diff --git a/Source/Editor/GUI/Timeline/Undo/AddRemoveTrackAction.cs b/Source/Editor/GUI/Timeline/Undo/AddRemoveTrackAction.cs
index dfdb911b3..51a832eeb 100644
--- a/Source/Editor/GUI/Timeline/Undo/AddRemoveTrackAction.cs
+++ b/Source/Editor/GUI/Timeline/Undo/AddRemoveTrackAction.cs
@@ -61,6 +61,9 @@ namespace FlaxEditor.GUI.Timeline.Undo
_timeline.AddTrack(track, false);
track.TrackIndex = _order;
_timeline.OnTracksOrderChanged();
+ _timeline.Focus();
+ _timeline.Select(track);
+ track.Focus();
}
private void Remove()
@@ -68,10 +71,11 @@ namespace FlaxEditor.GUI.Timeline.Undo
var track = _timeline.FindTrack(_name);
if (track == null)
{
- Editor.LogWarning($"Cannot remove track {_name}. It doesn't already exists.");
+ Editor.LogWarning($"Cannot remove track {_name}. It doesn't exist.");
return;
}
_timeline.Delete(track, false);
+ _timeline.Focus();
}
public string ActionString => _isAdd ? "Add track" : "Remove track";
diff --git a/Source/Editor/Managed/ManagedEditor.cpp b/Source/Editor/Managed/ManagedEditor.cpp
index d0b060ffe..9442aa244 100644
--- a/Source/Editor/Managed/ManagedEditor.cpp
+++ b/Source/Editor/Managed/ManagedEditor.cpp
@@ -176,7 +176,7 @@ ManagedEditor::~ManagedEditor()
void ManagedEditor::Init()
{
// Note: editor modules should perform quite fast init, any longer things should be done in async during 'editor splash screen time
- void* args[4];
+ void* args[2];
MClass* mclass = GetClass();
if (mclass == nullptr)
{
@@ -193,18 +193,22 @@ void ManagedEditor::Init()
LOG(Fatal, "Failed to create editor instance.");
}
MObject* exception = nullptr;
- bool isHeadless = CommandLine::Options.Headless.IsTrue();
- bool skipCompile = CommandLine::Options.SkipCompile.IsTrue();
- bool newProject = CommandLine::Options.NewProject.IsTrue();
- args[0] = &isHeadless;
- args[1] = &skipCompile;
- args[2] = &newProject;
+ StartupFlags flags = StartupFlags::None;
+ if (CommandLine::Options.Headless.IsTrue())
+ flags |= StartupFlags::Headless;
+ if (CommandLine::Options.SkipCompile.IsTrue())
+ flags |= StartupFlags::SkipCompile;
+ if (CommandLine::Options.NewProject.IsTrue())
+ flags |= StartupFlags::NewProject;
+ if (CommandLine::Options.Exit.IsTrue())
+ flags |= StartupFlags::Exit;
+ args[0] = &flags;
Guid sceneId;
if (!CommandLine::Options.Play.HasValue() || (CommandLine::Options.Play.HasValue() && Guid::Parse(CommandLine::Options.Play.GetValue(), sceneId)))
{
sceneId = Guid::Empty;
}
- args[3] = &sceneId;
+ args[1] = &sceneId;
initMethod->Invoke(instance, args, &exception);
if (exception)
{
@@ -219,7 +223,7 @@ void ManagedEditor::Init()
WasExitCalled = false;
// Load scripts if auto-load on startup is disabled
- if (!ManagedEditorOptions.ForceScriptCompilationOnStartup || skipCompile)
+ if (!ManagedEditorOptions.ForceScriptCompilationOnStartup || EnumHasAllFlags(flags, StartupFlags::SkipCompile))
{
LOG(Info, "Loading managed assemblies (due to disabled compilation on startup)");
Scripting::Load();
diff --git a/Source/Editor/Managed/ManagedEditor.h b/Source/Editor/Managed/ManagedEditor.h
index eb1cceef8..4722db997 100644
--- a/Source/Editor/Managed/ManagedEditor.h
+++ b/Source/Editor/Managed/ManagedEditor.h
@@ -22,6 +22,15 @@ API_CLASS(Namespace="FlaxEditor", Name="Editor", NoSpawn, NoConstructor) class M
DECLARE_SCRIPTING_TYPE_NO_SPAWN(ManagedEditor);
static Guid ObjectID;
+ API_ENUM(Attributes="Flags", Internal) enum class StartupFlags
+ {
+ None = 0,
+ Headless = 1,
+ SkipCompile = 2,
+ NewProject = 4,
+ Exit = 8,
+ };
+
struct InternalOptions
{
byte AutoReloadScriptsOnMainWindowFocus = 1;
@@ -258,3 +267,5 @@ public:
// [ScriptingObject]
void DestroyManaged() override;
};
+
+DECLARE_ENUM_OPERATORS(ManagedEditor::StartupFlags);
diff --git a/Source/Editor/Modules/ProjectCacheModule.cs b/Source/Editor/Modules/ProjectCacheModule.cs
index b0ebb6981..3e2cd6806 100644
--- a/Source/Editor/Modules/ProjectCacheModule.cs
+++ b/Source/Editor/Modules/ProjectCacheModule.cs
@@ -274,11 +274,7 @@ namespace FlaxEditor.Modules
private void Load()
{
if (!File.Exists(_cachePath))
- {
- Editor.LogWarning("Missing editor cache file.");
return;
- }
-
_lastSaveTime = DateTime.UtcNow;
try
diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs
index b9f3e246b..fa7a04d9b 100644
--- a/Source/Editor/Modules/WindowsModule.cs
+++ b/Source/Editor/Modules/WindowsModule.cs
@@ -950,7 +950,10 @@ namespace FlaxEditor.Modules
MainWindow = null;
// Capture project icon screenshot (not in play mode and if editor was used for some time)
- if (!Editor.StateMachine.IsPlayMode && Time.TimeSinceStartup >= 5.0f && GPUDevice.Instance?.RendererType != RendererType.Null)
+ if (!Editor.StateMachine.IsPlayMode &&
+ Time.TimeSinceStartup >= 5.0f &&
+ !Editor.IsHeadlessMode &&
+ GPUDevice.Instance?.RendererType != RendererType.Null)
{
Editor.Log("Capture project icon screenshot");
_projectIconScreenshotTimeout = Time.TimeSinceStartup + 0.8f; // wait 800ms for a screenshot task
diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs
index ec46ccdd9..1654df920 100644
--- a/Source/Editor/Options/InterfaceOptions.cs
+++ b/Source/Editor/Options/InterfaceOptions.cs
@@ -449,6 +449,13 @@ namespace FlaxEditor.Options
[EditorDisplay("Visject", "Grid Snapping Size"), EditorOrder(551), Tooltip("Defines the size of the grid for nodes snapping."), VisibleIf(nameof(SurfaceGridSnapping))]
public float SurfaceGridSnappingSize { get; set; } = 20.0f;
+ ///
+ /// Gets or sets a value that indicates if a warning should be displayed when deleting a Visject parameter that is used in a graph.
+ ///
+ [DefaultValue(true)]
+ [EditorDisplay("Visject", "Warn when deleting used parameter"), EditorOrder(552)]
+ public bool WarnOnDeletingUsedVisjectParameter { get; set; } = true;
+
private static FontAsset DefaultFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.PrimaryFont);
private static FontAsset ConsoleFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont);
diff --git a/Source/Editor/SceneGraph/Actors/SceneNode.cs b/Source/Editor/SceneGraph/Actors/SceneNode.cs
index f0938492f..bb5998ef4 100644
--- a/Source/Editor/SceneGraph/Actors/SceneNode.cs
+++ b/Source/Editor/SceneGraph/Actors/SceneNode.cs
@@ -81,6 +81,7 @@ namespace FlaxEditor.SceneGraph.Actors
if (Level.ScenesCount > 1)
contextMenu.AddButton("Unload all but this scene", OnUnloadAllButSelectedScene).LinkTooltip("Unloads all of the active scenes except for the selected scene.").Enabled = Editor.Instance.StateMachine.CurrentState.CanChangeScene;
+ contextMenu.MaximumItemsInViewCount += 3;
base.OnContextMenu(contextMenu, window);
}
diff --git a/Source/Editor/Scripting/ScriptType.Interfaces.cs b/Source/Editor/Scripting/ScriptType.Interfaces.cs
index 01a4755ec..34294fd60 100644
--- a/Source/Editor/Scripting/ScriptType.Interfaces.cs
+++ b/Source/Editor/Scripting/ScriptType.Interfaces.cs
@@ -293,6 +293,14 @@ namespace FlaxEditor.Scripting
/// The object whose member value will be modified.
/// The new member value.
void SetValue(object obj, object value);
+
+ ///
+ /// Invokes the method on a specific object (null if static) using the provided parameters.
+ ///
+ /// The instance of the object to invoke its method. Use null for static methods.
+ /// List of parameters to provide.
+ /// The value returned by the method.
+ object Invoke(object obj, object[] parameters);
}
///
diff --git a/Source/Editor/Scripting/ScriptType.cs b/Source/Editor/Scripting/ScriptType.cs
index ec3775b95..66c7a9b06 100644
--- a/Source/Editor/Scripting/ScriptType.cs
+++ b/Source/Editor/Scripting/ScriptType.cs
@@ -691,6 +691,23 @@ namespace FlaxEditor.Scripting
else
_custom.SetValue(obj, value);
}
+
+ ///
+ /// Invokes the method on a specific object (null if static) using the provided parameters.
+ ///
+ /// The instance of the object to invoke its method. Use null for static methods.
+ /// List of parameters to provide.
+ /// The value returned by the method.
+ public object Invoke(object obj = null, object[] parameters = null)
+ {
+ if (parameters == null)
+ parameters = Array.Empty
public int Index => _index;
+ ///
+ /// Flag that indicates that user is moving this point with a mouse.
+ ///
+ public bool IsMouseDown => _isMouseDown;
+
///
/// Initializes a new instance of the class.
///
@@ -211,6 +216,11 @@ namespace FlaxEditor.Surface.Archetypes
///
public int PointsCount => (_node.Values.Length - 4) / 2; // 4 node values + 2 per blend point
+ ///
+ /// BLend points array.
+ ///
+ internal IReadOnlyList BlendPoints => _blendPoints;
+
///
/// Initializes a new instance of the class.
///
@@ -374,6 +384,12 @@ namespace FlaxEditor.Surface.Archetypes
/// The blend point control position.
public Float2 BlendSpacePosToBlendPointPos(Float2 pos)
{
+ if (_rangeX.IsZero)
+ {
+ var data0 = (Float4)_node.Values[0];
+ _rangeX = new Float2(data0.X, data0.Y);
+ _rangeY = _is2D ? new Float2(data0.Z, data0.W) : Float2.Zero;
+ }
GetPointsArea(out var pointsArea);
if (_is2D)
{
@@ -389,7 +405,7 @@ namespace FlaxEditor.Surface.Archetypes
pointsArea.Center.Y
);
}
- return pos - new Float2(BlendPoint.DefaultSize * 0.5f);
+ return pos;
}
///
@@ -424,7 +440,7 @@ namespace FlaxEditor.Surface.Archetypes
}
// Update blend point
- _blendPoints[i].Location = BlendSpacePosToBlendPointPos(location);
+ _blendPoints[i].Location = BlendSpacePosToBlendPointPos(location) - BlendPoint.DefaultSize * 0.5f;
var asset = Editor.Instance.ContentDatabase.FindAsset(animId);
var tooltip = asset?.ShortName ?? string.Empty;
tooltip += "\nX: " + location.X;
@@ -532,81 +548,18 @@ namespace FlaxEditor.Surface.Archetypes
SetAsset((int)b.Tag, Guid.Empty);
}
- private void DrawAxis(bool vertical, Float2 start, Float2 end, ref Color gridColor, ref Color labelColor, Font labelFont, float value, bool isLast)
- {
- // Draw line
- Render2D.DrawLine(start, end, gridColor);
-
- // Draw label
- var labelWidth = 50.0f;
- var labelHeight = 10.0f;
- var labelMargin = 2.0f;
- string label = Utils.RoundTo2DecimalPlaces(value).ToString(System.Globalization.CultureInfo.InvariantCulture);
- var hAlign = TextAlignment.Near;
- Rectangle labelRect;
- if (vertical)
- {
- labelRect = new Rectangle(start.X + labelMargin * 2, start.Y, labelWidth, labelHeight);
- if (isLast)
- return; // Don't overlap with the first horizontal label
- }
- else
- {
- labelRect = new Rectangle(start.X + labelMargin, start.Y - labelHeight - labelMargin, labelWidth, labelHeight);
- if (isLast)
- {
- labelRect.X = start.X - labelMargin - labelRect.Width;
- hAlign = TextAlignment.Far;
- }
- }
- Render2D.DrawText(labelFont, label, labelRect, labelColor, hAlign, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f);
- }
-
///
public override void Draw()
{
var style = Style.Current;
var rect = new Rectangle(Float2.Zero, Size);
var containsFocus = ContainsFocus;
- GetPointsArea(out var pointsArea);
- var data0 = (Float4)_node.Values[0];
- var rangeX = new Float2(data0.X, data0.Y);
// Background
- Render2D.DrawRectangle(rect, IsMouseOver ? style.TextBoxBackgroundSelected : style.TextBoxBackground);
- //Render2D.DrawRectangle(pointsArea, Color.Red);
+ _node.DrawEditorBackground(ref rect);
// Grid
- int splits = 10;
- var gridColor = style.TextBoxBackgroundSelected * 1.1f;
- var labelColor = style.ForegroundDisabled;
- var labelFont = style.FontSmall;
- //var blendArea = BlendAreaRect;
- var blendArea = pointsArea;
-
- for (int i = 0; i <= splits; i++)
- {
- float alpha = (float)i / splits;
- float x = blendArea.Left + blendArea.Width * alpha;
- float value = Mathf.Lerp(rangeX.X, rangeX.Y, alpha);
- DrawAxis(false, new Float2(x, rect.Height - 2), new Float2(x, 1), ref gridColor, ref labelColor, labelFont, value, i == splits);
- }
- if (_is2D)
- {
- var rangeY = new Float2(data0.Z, data0.W);
- for (int i = 0; i <= splits; i++)
- {
- float alpha = (float)i / splits;
- float y = blendArea.Top + blendArea.Height * alpha;
- float value = Mathf.Lerp(rangeY.X, rangeY.Y, alpha);
- DrawAxis(true, new Float2(1, y), new Float2(rect.Width - 2, y), ref gridColor, ref labelColor, labelFont, value, i == splits);
- }
- }
- else
- {
- float y = blendArea.Center.Y;
- Render2D.DrawLine(new Float2(1, y), new Float2(rect.Width - 2, y), gridColor);
- }
+ _node.DrawEditorGrid(ref rect);
base.Draw();
@@ -808,6 +761,87 @@ namespace FlaxEditor.Surface.Archetypes
_editor.SetAsset(SelectedAnimationIndex, Guid.Empty);
}
+ private void DrawAxis(bool vertical, Float2 start, Float2 end, ref Color gridColor, ref Color labelColor, Font labelFont, float value, bool isLast)
+ {
+ // Draw line
+ Render2D.DrawLine(start, end, gridColor);
+
+ // Draw label
+ var labelWidth = 50.0f;
+ var labelHeight = 10.0f;
+ var labelMargin = 2.0f;
+ string label = Utils.RoundTo2DecimalPlaces(value).ToString(System.Globalization.CultureInfo.InvariantCulture);
+ var hAlign = TextAlignment.Near;
+ Rectangle labelRect;
+ if (vertical)
+ {
+ labelRect = new Rectangle(start.X + labelMargin * 2, start.Y, labelWidth, labelHeight);
+ if (isLast)
+ return; // Don't overlap with the first horizontal label
+ }
+ else
+ {
+ labelRect = new Rectangle(start.X + labelMargin, start.Y - labelHeight - labelMargin, labelWidth, labelHeight);
+ if (isLast)
+ {
+ labelRect.X = start.X - labelMargin - labelRect.Width;
+ hAlign = TextAlignment.Far;
+ }
+ }
+ Render2D.DrawText(labelFont, label, labelRect, labelColor, hAlign, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f);
+ }
+
+ ///
+ /// Custom drawing logic for blend space background.
+ ///
+ public virtual void DrawEditorBackground(ref Rectangle rect)
+ {
+ var style = Style.Current;
+ Render2D.FillRectangle(rect, style.Background.AlphaMultiplied(0.5f));
+ Render2D.DrawRectangle(rect, IsMouseOver ? style.TextBoxBackgroundSelected : style.TextBoxBackground);
+ }
+
+ ///
+ /// Custom drawing logic for blend space grid.
+ ///
+ public virtual void DrawEditorGrid(ref Rectangle rect)
+ {
+ var style = Style.Current;
+ _editor.GetPointsArea(out var pointsArea);
+ var data0 = (Float4)Values[0];
+ var rangeX = new Float2(data0.X, data0.Y);
+ int splits = 10;
+ var gridColor = style.TextBoxBackgroundSelected * 1.1f;
+ var labelColor = style.ForegroundDisabled;
+ var labelFont = style.FontSmall;
+ //var blendArea = BlendAreaRect;
+ var blendArea = pointsArea;
+
+ for (int i = 0; i <= splits; i++)
+ {
+ float alpha = (float)i / splits;
+ float x = blendArea.Left + blendArea.Width * alpha;
+ float value = Mathf.Lerp(rangeX.X, rangeX.Y, alpha);
+ DrawAxis(false, new Float2(x, rect.Height - 2), new Float2(x, 1), ref gridColor, ref labelColor, labelFont, value, i == splits);
+ }
+ if (_editor.Is2D)
+ {
+ var rangeY = new Float2(data0.Z, data0.W);
+ for (int i = 0; i <= splits; i++)
+ {
+ float alpha = (float)i / splits;
+ float y = blendArea.Top + blendArea.Height * alpha;
+ float value = Mathf.Lerp(rangeY.X, rangeY.Y, 1.0f - alpha);
+ DrawAxis(true, new Float2(1, y), new Float2(rect.Width - 2, y), ref gridColor, ref labelColor, labelFont, value, i == splits);
+ }
+ }
+ else
+ {
+ float y = blendArea.Center.Y;
+ Render2D.DrawLine(new Float2(1, y), new Float2(rect.Width - 2, y), gridColor);
+ }
+ }
+
///
/// Updates the editor UI.
///
@@ -983,6 +1017,10 @@ namespace FlaxEditor.Surface.Archetypes
private readonly FloatValueBox _animationX;
private readonly Label _animationYLabel;
private readonly FloatValueBox _animationY;
+ private Float2[] _triangles;
+ private Color[] _triangleColors;
+ private Float2[] _selectedTriangles;
+ private Color[] _selectedColors;
///
public MultiBlend2D(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
@@ -1051,6 +1089,143 @@ namespace FlaxEditor.Surface.Archetypes
}
}
+ private void ClearTriangles()
+ {
+ // Remove cache
+ _triangles = null;
+ _triangleColors = null;
+ _selectedTriangles = null;
+ _selectedColors = null;
+ }
+
+ private void CacheTriangles()
+ {
+ // Get locations of blend point vertices
+ int pointsCount = _editor.PointsCount;
+ int count = 0, j = 0;
+ for (int i = 0; i < pointsCount; i++)
+ {
+ var animId = (Guid)Values[5 + i * 2];
+ if (animId != Guid.Empty)
+ count++;
+ }
+ var vertices = new Float2[count];
+ for (int i = 0; i < pointsCount; i++)
+ {
+ var animId = (Guid)Values[5 + i * 2];
+ if (animId != Guid.Empty)
+ {
+ var dataA = (Float4)Values[4 + i * 2];
+ vertices[j++] = new Float2(dataA.X, dataA.Y);
+ }
+ }
+
+ // Triangulate
+ _triangles = FlaxEngine.Utilities.Delaunay2D.Triangulate(vertices);
+ _triangleColors = null;
+
+ // Fix incorrect triangles (mirror logic in AnimGraphBase::onNodeLoaded)
+ if (_triangles == null || _triangles.Length == 0)
+ {
+ // Insert dummy triangles to have something working (eg. blend points are on the same axis)
+ var triangles = new List();
+ int verticesLeft = vertices.Length;
+ while (verticesLeft >= 3)
+ {
+ verticesLeft -= 3;
+ triangles.Add(vertices[verticesLeft + 0]);
+ triangles.Add(vertices[verticesLeft + 1]);
+ triangles.Add(vertices[verticesLeft + 2]);
+ }
+ if (verticesLeft == 1)
+ {
+ triangles.Add(vertices[0]);
+ triangles.Add(vertices[0]);
+ triangles.Add(vertices[0]);
+ }
+ else if (verticesLeft == 2)
+ {
+ triangles.Add(vertices[0]);
+ triangles.Add(vertices[1]);
+ triangles.Add(vertices[0]);
+ }
+ _triangles = triangles.ToArray();
+ }
+
+ // Project to the blend space for drawing
+ for (int i = 0; i < _triangles.Length; i++)
+ _triangles[i] = _editor.BlendSpacePosToBlendPointPos(_triangles[i]);
+
+ // Check if anything is selected
+ var selectedIndex = _selectedAnimation.SelectedIndex;
+ if (selectedIndex != -1)
+ {
+ // Find triangles that contain selected point
+ var dataA = (Float4)Values[4 + selectedIndex * 2];
+ var pos = _editor.BlendSpacePosToBlendPointPos(new Float2(dataA.X, dataA.Y));
+ var selectedTriangles = new List();
+ var selectedColors = new List();
+ var style = Style.Current;
+ var triangleColor = style.TextBoxBackgroundSelected.AlphaMultiplied(0.6f);
+ var selectedTriangleColor = style.BackgroundSelected.AlphaMultiplied(0.6f);
+ _triangleColors = new Color[_triangles.Length];
+ for (int i = 0; i < _triangles.Length; i += 3)
+ {
+ var is0 = Float2.NearEqual(ref _triangles[i + 0], ref pos);
+ var is1 = Float2.NearEqual(ref _triangles[i + 1], ref pos);
+ var is2 = Float2.NearEqual(ref _triangles[i + 2], ref pos);
+ if (is0 || is1 || is2)
+ {
+ selectedTriangles.Add(_triangles[i + 0]);
+ selectedTriangles.Add(_triangles[i + 1]);
+ selectedTriangles.Add(_triangles[i + 2]);
+ selectedColors.Add(is0 ? Color.White : Color.Transparent);
+ selectedColors.Add(is1 ? Color.White : Color.Transparent);
+ selectedColors.Add(is2 ? Color.White : Color.Transparent);
+ }
+ _triangleColors[i + 0] = is0 ? selectedTriangleColor : triangleColor;
+ _triangleColors[i + 1] = is1 ? selectedTriangleColor : triangleColor;
+ _triangleColors[i + 2] = is2 ? selectedTriangleColor : triangleColor;
+ }
+ _selectedTriangles = selectedTriangles.ToArray();
+ _selectedColors = selectedColors.ToArray();
+ }
+ }
+
+ ///
+ public override void DrawEditorBackground(ref Rectangle rect)
+ {
+ base.DrawEditorBackground(ref rect);
+
+ // Draw triangulated multi blend space
+ var style = Style.Current;
+ if (_triangles == null)
+ CacheTriangles();
+ if (_triangleColors != null && (ContainsFocus || IsMouseOver))
+ Render2D.FillTriangles(_triangles, _triangleColors);
+ else
+ Render2D.FillTriangles(_triangles, style.TextBoxBackgroundSelected.AlphaMultiplied(0.6f));
+ Render2D.DrawTriangles(_triangles, style.Foreground);
+ }
+
+ ///
+ public override void DrawEditorGrid(ref Rectangle rect)
+ {
+ base.DrawEditorGrid(ref rect);
+
+ // Highlight selected blend point
+ var style = Style.Current;
+ var selectedIndex = _selectedAnimation.SelectedIndex;
+ if (selectedIndex != -1 && (ContainsFocus || IsMouseOver))
+ {
+ var point = _editor.BlendPoints[selectedIndex];
+ var highlightColor = point.IsMouseDown ? style.SelectionBorder : style.BackgroundSelected;
+ Render2D.PushTint(ref highlightColor);
+ Render2D.DrawTriangles(_selectedTriangles, _selectedColors);
+ Render2D.PopTint();
+ }
+ }
+
///
public override void UpdateUI(int selectedIndex, bool isValid, ref Float4 data0, ref Guid data1)
{
@@ -1075,6 +1250,23 @@ namespace FlaxEditor.Surface.Archetypes
_animationX.Enabled = isValid;
_animationYLabel.Enabled = isValid;
_animationY.Enabled = isValid;
+ ClearTriangles();
+ }
+
+ ///
+ public override void OnValuesChanged()
+ {
+ base.OnValuesChanged();
+
+ ClearTriangles();
+ }
+
+ ///
+ public override void OnLoaded(SurfaceNodeActions action)
+ {
+ base.OnLoaded(action);
+
+ ClearTriangles();
}
}
}
diff --git a/Source/Editor/Surface/Archetypes/Material.cs b/Source/Editor/Surface/Archetypes/Material.cs
index f1da861c1..9da0ec067 100644
--- a/Source/Editor/Surface/Archetypes/Material.cs
+++ b/Source/Editor/Surface/Archetypes/Material.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
+using FlaxEditor.Content.Settings;
using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
using FlaxEditor.Windows.Assets;
@@ -590,7 +591,7 @@ namespace FlaxEditor.Surface.Archetypes
},
Elements = new[]
{
- NodeElementArchetype.Factory.ComboBox(0, 0, 70.0f, 0, FlaxEditor.Tools.Terrain.PaintTerrainGizmoMode.TerrainLayerNames),
+ NodeElementArchetype.Factory.ComboBox(0, 0, 70.0f, 0, LayersAndTagsSettings.GetCurrentTerrainLayers()),
NodeElementArchetype.Factory.Output(0, "", typeof(float), 0),
}
},
diff --git a/Source/Editor/Surface/Archetypes/Parameters.cs b/Source/Editor/Surface/Archetypes/Parameters.cs
index a75be6bb5..b6b80e222 100644
--- a/Source/Editor/Surface/Archetypes/Parameters.cs
+++ b/Source/Editor/Surface/Archetypes/Parameters.cs
@@ -425,6 +425,12 @@ namespace FlaxEditor.Surface.Archetypes
UpdateCombo();
}
+ ///
+ public bool IsParamUsed(SurfaceParameter param)
+ {
+ return (Guid)Values[0] == param.ID;
+ }
+
///
public override void OnLoaded(SurfaceNodeActions action)
{
@@ -937,13 +943,17 @@ namespace FlaxEditor.Surface.Archetypes
{
// Deselect if that parameter is selected
if ((Guid)Values[0] == param.ID)
- {
_combobox.SelectedIndex = -1;
- }
UpdateCombo();
}
+ ///
+ public bool IsParamUsed(SurfaceParameter param)
+ {
+ return (Guid)Values[0] == param.ID;
+ }
+
///
public override void OnLoaded(SurfaceNodeActions action)
{
diff --git a/Source/Editor/Surface/IParametersDependantNode.cs b/Source/Editor/Surface/IParametersDependantNode.cs
index 9683abd70..4b9507032 100644
--- a/Source/Editor/Surface/IParametersDependantNode.cs
+++ b/Source/Editor/Surface/IParametersDependantNode.cs
@@ -33,5 +33,12 @@ namespace FlaxEditor.Surface
///
/// The parameter.
void OnParamDeleted(SurfaceParameter param);
+
+ ///
+ /// Get if the parameter is referenced in a graph. Referenced in this case means in a graph and at least one node in-/output connected to another node.
+ ///
+ /// The parameter.
+ /// If the parameter is referenced.
+ bool IsParamUsed(SurfaceParameter param);
}
}
diff --git a/Source/Editor/Surface/NodeFactory.cs b/Source/Editor/Surface/NodeFactory.cs
index ee4553f2f..484b5d89b 100644
--- a/Source/Editor/Surface/NodeFactory.cs
+++ b/Source/Editor/Surface/NodeFactory.cs
@@ -39,6 +39,7 @@ namespace FlaxEditor.Surface
typeof(TooltipAttribute),
typeof(HideInEditorAttribute),
typeof(NoAnimateAttribute),
+ typeof(ButtonAttribute),
};
///
diff --git a/Source/Editor/Surface/VisjectSurface.Paramaters.cs b/Source/Editor/Surface/VisjectSurface.Parameters.cs
similarity index 88%
rename from Source/Editor/Surface/VisjectSurface.Paramaters.cs
rename to Source/Editor/Surface/VisjectSurface.Parameters.cs
index d6cf6ca0b..b31404ec5 100644
--- a/Source/Editor/Surface/VisjectSurface.Paramaters.cs
+++ b/Source/Editor/Surface/VisjectSurface.Parameters.cs
@@ -84,5 +84,16 @@ namespace FlaxEditor.Surface
}
MarkAsEdited();
}
+
+ ///
+ public bool IsParamUsed(SurfaceParameter param)
+ {
+ for (int i = 0; i < Nodes.Count; i++)
+ {
+ if (Nodes[i] is IParametersDependantNode node && node.IsParamUsed(param))
+ return true;
+ }
+ return false;
+ }
}
}
diff --git a/Source/Editor/Surface/VisjectSurfaceWindow.cs b/Source/Editor/Surface/VisjectSurfaceWindow.cs
index 16e4f303b..6d7633b73 100644
--- a/Source/Editor/Surface/VisjectSurfaceWindow.cs
+++ b/Source/Editor/Surface/VisjectSurfaceWindow.cs
@@ -776,6 +776,15 @@ namespace FlaxEditor.Surface
private void DeleteParameter(int index)
{
var window = (IVisjectSurfaceWindow)Values[0];
+ SurfaceParameter param = window.VisjectSurface.Parameters[index];
+
+ if (Editor.Instance.Options.Options.Interface.WarnOnDeletingUsedVisjectParameter && window.VisjectSurface.IsParamUsed(param))
+ {
+ string msg = $"Delete parameter {param.Name}?\nParameter is being used in a graph.\n\nYou can disable this warning in the editor settings.";
+ if (MessageBox.Show(msg, "Delete parameter", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) != DialogResult.OK)
+ return;
+ }
+
var action = new AddRemoveParamAction
{
Window = window,
diff --git a/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs b/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs
index 5581eeb66..9cf550f5c 100644
--- a/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs
+++ b/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs
@@ -60,7 +60,7 @@ namespace FlaxEditor.Tools.Terrain.Paint
///
/// The layer to paint with it.
///
- [EditorOrder(10), Tooltip("The layer to paint with it. Terrain material can access per-layer blend weight to perform materials or textures blending.")]
+ [EditorOrder(10), Tooltip("The layer to paint on. Terrain material can access a per-layer blend weight to perform material or texture blending."), CustomEditorAlias("FlaxEditor.CustomEditors.Editors.TerrainLayerEditor")]
public Layers Layer = Layers.Layer0;
///
diff --git a/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs b/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs
index 0be0fe41c..f9cf12fc1 100644
--- a/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs
+++ b/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs
@@ -21,21 +21,6 @@ namespace FlaxEditor.Tools.Terrain
[HideInEditor]
public class PaintTerrainGizmoMode : EditorGizmoMode
{
- ///
- /// The terrain layer names.
- ///
- public static readonly string[] TerrainLayerNames =
- {
- "Layer 0",
- "Layer 1",
- "Layer 2",
- "Layer 3",
- "Layer 4",
- "Layer 5",
- "Layer 6",
- "Layer 7",
- };
-
private struct SplatmapData
{
public IntPtr DataPtr;
diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs
index 09a059251..c4ff4acf2 100644
--- a/Source/Editor/Utilities/Utils.cs
+++ b/Source/Editor/Utilities/Utils.cs
@@ -250,6 +250,8 @@ namespace FlaxEditor.Utilities
internal static Int2 DrawCurveTicks(DrawCurveTick drawTick, float[] tickSteps, ref float[] tickStrengths, float min, float max, float pixelRange, float minDistanceBetweenTicks = 20, float maxDistanceBetweenTicks = 60)
{
+ if (pixelRange <= Mathf.Epsilon || maxDistanceBetweenTicks <= minDistanceBetweenTicks)
+ return Int2.Zero;
if (tickStrengths == null || tickStrengths.Length != tickSteps.Length)
tickStrengths = new float[tickSteps.Length];
@@ -286,7 +288,7 @@ namespace FlaxEditor.Utilities
continue;
// Draw all ticks
- int l = Mathf.Clamp(smallestTick + level, 0, tickSteps.Length - 1);
+ int l = Mathf.Clamp(smallestTick + level, 0, tickSteps.Length - 2);
var lStep = tickSteps[l];
var lNextStep = tickSteps[l + 1];
int startTick = Mathf.FloorToInt(min / lStep);
diff --git a/Source/Editor/Viewport/Previews/PrefabPreview.cs b/Source/Editor/Viewport/Previews/PrefabPreview.cs
index 1673c1723..258be7645 100644
--- a/Source/Editor/Viewport/Previews/PrefabPreview.cs
+++ b/Source/Editor/Viewport/Previews/PrefabPreview.cs
@@ -79,6 +79,13 @@ namespace FlaxEditor.Viewport.Previews
_uiControlLinked.Control.Parent = null;
_uiControlLinked = null;
}
+ foreach (var child in _uiParentLink.Children.ToArray())
+ {
+ if (child is CanvasRootControl canvasRoot)
+ {
+ canvasRoot.Canvas.EditorOverride(null, null);
+ }
+ }
// Remove for the preview
Task.RemoveCustomActor(_instance);
diff --git a/Source/Editor/Windows/Assets/MaterialWindow.cs b/Source/Editor/Windows/Assets/MaterialWindow.cs
index ad72c92d9..794ad699a 100644
--- a/Source/Editor/Windows/Assets/MaterialWindow.cs
+++ b/Source/Editor/Windows/Assets/MaterialWindow.cs
@@ -54,24 +54,24 @@ namespace FlaxEditor.Windows.Assets
[EditorOrder(10), EditorDisplay("General"), Tooltip("Material domain type.")]
public MaterialDomain Domain;
- [EditorOrder(20), EditorDisplay("General"), Tooltip("Defines how material inputs and properties are combined to result the final surface color.")]
+ [EditorOrder(20), VisibleIf(nameof(IsStandard)), EditorDisplay("General"), Tooltip("Defines how material inputs and properties are combined to result the final surface color.")]
public MaterialShadingModel ShadingModel;
- [EditorOrder(30), EditorDisplay("General"), Tooltip("Determinates how materials' color should be blended with the background colors.")]
+ [EditorOrder(30), VisibleIf(nameof(IsStandard)), EditorDisplay("General"), Tooltip("Determinates how materials' color should be blended with the background colors.")]
public MaterialBlendMode BlendMode;
// Rendering
- [EditorOrder(100), DefaultValue(CullMode.Normal), EditorDisplay("Rendering"), Tooltip("Defines the primitives culling mode used during geometry rendering.")]
+ [EditorOrder(100), DefaultValue(CullMode.Normal), VisibleIf(nameof(IsStandard)), EditorDisplay("Rendering"), Tooltip("Defines the primitives culling mode used during geometry rendering.")]
public CullMode CullMode;
- [EditorOrder(110), DefaultValue(false), EditorDisplay("Rendering"), Tooltip("If checked, geometry will be rendered in wireframe mode without solid triangles fill.")]
+ [EditorOrder(110), DefaultValue(false), VisibleIf(nameof(IsStandardOrGUI)), EditorDisplay("Rendering"), Tooltip("If checked, geometry will be rendered in wireframe mode without solid triangles fill.")]
public bool Wireframe;
- [EditorOrder(120), DefaultValue(true), EditorDisplay("Rendering"), Tooltip("Enables performing depth test during material rendering.")]
+ [EditorOrder(120), DefaultValue(true), VisibleIf(nameof(IsStandard)), EditorDisplay("Rendering"), Tooltip("Enables performing depth test during material rendering.")]
public bool DepthTest;
- [EditorOrder(130), DefaultValue(true), EditorDisplay("Rendering"), Tooltip("Enable writing to the depth buffer during material rendering.")]
+ [EditorOrder(130), DefaultValue(true), VisibleIf(nameof(IsStandard)), EditorDisplay("Rendering"), Tooltip("Enable writing to the depth buffer during material rendering.")]
public bool DepthWrite;
// Transparency
@@ -111,13 +111,13 @@ namespace FlaxEditor.Windows.Assets
// Misc
- [EditorOrder(400), DefaultValue(false), EditorDisplay("Misc"), Tooltip("If checked, material input normal will be assumed as world-space rather than tangent-space.")]
+ [EditorOrder(400), DefaultValue(false), VisibleIf(nameof(IsStandard)), EditorDisplay("Misc"), Tooltip("If checked, material input normal will be assumed as world-space rather than tangent-space.")]
public bool InputWorldSpaceNormal;
- [EditorOrder(410), DefaultValue(false), EditorDisplay("Misc", "Dithered LOD Transition"), Tooltip("If checked, material uses dithered model LOD transition for smoother LODs switching.")]
+ [EditorOrder(410), DefaultValue(false), VisibleIf(nameof(IsStandard)), EditorDisplay("Misc", "Dithered LOD Transition"), Tooltip("If checked, material uses dithered model LOD transition for smoother LODs switching.")]
public bool DitheredLODTransition;
- [EditorOrder(420), DefaultValue(0.3f), EditorDisplay("Misc"), Tooltip("Controls mask values clipping point."), Limit(0.0f, 1.0f, 0.01f)]
+ [EditorOrder(420), DefaultValue(0.3f), VisibleIf(nameof(IsStandard)), EditorDisplay("Misc"), Tooltip("Controls mask values clipping point."), Limit(0.0f, 1.0f, 0.01f)]
public float MaskThreshold;
[EditorOrder(430), DefaultValue(MaterialDecalBlendingMode.Translucent), VisibleIf(nameof(IsDecal)), EditorDisplay("Misc"), Tooltip("The decal material blending mode.")]
@@ -144,7 +144,9 @@ namespace FlaxEditor.Windows.Assets
private bool IsPostProcess => Domain == MaterialDomain.PostProcess;
private bool IsDecal => Domain == MaterialDomain.Decal;
+ private bool IsGUI => Domain == MaterialDomain.GUI;
private bool IsStandard => Domain == MaterialDomain.Surface || Domain == MaterialDomain.Terrain || Domain == MaterialDomain.Particle || Domain == MaterialDomain.Deformable;
+ private bool IsStandardOrGUI => IsStandard || IsGUI;
///
/// Gathers parameters from the specified material.
diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs
index 2cbe56a27..a107a72f5 100644
--- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs
+++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs
@@ -52,12 +52,13 @@ namespace FlaxEditor.Windows
contextMenu.AddSeparator();
// Basic editing options
-
+ var firstSelection = hasSthSelected ? Editor.SceneEditing.Selection[0] as ActorNode : null;
b = contextMenu.AddButton("Rename", inputOptions.Rename, Rename);
- b = contextMenu.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate);
b.Enabled = hasSthSelected;
+ b = contextMenu.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate);
+ b.Enabled = hasSthSelected && (firstSelection != null ? firstSelection.CanDuplicate : true);
- if (isSingleActorSelected)
+ if (isSingleActorSelected && firstSelection?.Actor is not Scene)
{
var convertMenu = contextMenu.AddChildMenu("Convert");
convertMenu.ContextMenu.AutoSort = true;
@@ -117,31 +118,31 @@ namespace FlaxEditor.Windows
}
}
b = contextMenu.AddButton("Delete", inputOptions.Delete, Editor.SceneEditing.Delete);
- b.Enabled = hasSthSelected;
+ b.Enabled = hasSthSelected && (firstSelection != null ? firstSelection.CanDelete : true);
contextMenu.AddSeparator();
b = contextMenu.AddButton("Copy", inputOptions.Copy, Editor.SceneEditing.Copy);
+ b.Enabled = hasSthSelected && (firstSelection != null ? firstSelection.CanCopyPaste : true);
- b.Enabled = hasSthSelected;
contextMenu.AddButton("Paste", inputOptions.Paste, Editor.SceneEditing.Paste);
b = contextMenu.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut);
- b.Enabled = canEditScene;
+ b.Enabled = canEditScene && hasSthSelected && (firstSelection != null ? firstSelection.CanCopyPaste : true);
// Create option
contextMenu.AddSeparator();
b = contextMenu.AddButton("Parent to new Actor", inputOptions.GroupSelectedActors, Editor.SceneEditing.CreateParentForSelectedActors);
- b.Enabled = canEditScene && hasSthSelected;
+ b.Enabled = canEditScene && hasSthSelected && firstSelection?.Actor is not Scene;
b = contextMenu.AddButton("Create Prefab", Editor.Prefabs.CreatePrefab);
b.Enabled = isSingleActorSelected &&
- ((ActorNode)Editor.SceneEditing.Selection[0]).CanCreatePrefab &&
+ (firstSelection != null ? firstSelection.CanCreatePrefab : false) &&
Editor.Windows.ContentWin.CurrentViewFolder.CanHaveAssets;
- bool hasPrefabLink = canEditScene && isSingleActorSelected && (Editor.SceneEditing.Selection[0] as ActorNode).HasPrefabLink;
+ bool hasPrefabLink = canEditScene && isSingleActorSelected && (firstSelection != null ? firstSelection.HasPrefabLink : false);
if (hasPrefabLink)
{
contextMenu.AddButton("Select Prefab", Editor.Prefabs.SelectPrefab);
diff --git a/Source/Engine/Animations/AnimationUtils.h b/Source/Engine/Animations/AnimationUtils.h
index d801443ab..4f697a2d0 100644
--- a/Source/Engine/Animations/AnimationUtils.h
+++ b/Source/Engine/Animations/AnimationUtils.h
@@ -66,27 +66,23 @@ namespace AnimationUtils
}
template
- FORCE_INLINE static void GetTangent(const T& a, const T& b, float length, T& result)
+ FORCE_INLINE static void GetTangent(const T& value, const T& tangent, float tangentScale, T& result)
{
- const float oneThird = 1.0f / 3.0f;
- result = a + b * (length * oneThird);
+ result = value + tangent * tangentScale;
}
template<>
- FORCE_INLINE void GetTangent(const Quaternion& a, const Quaternion& b, float length, Quaternion& result)
+ FORCE_INLINE void GetTangent(const Quaternion& value, const Quaternion& tangent, float tangentScale, Quaternion& result)
{
- const float oneThird = 1.0f / 3.0f;
- Quaternion::Slerp(a, b, oneThird, result);
+ Quaternion::Slerp(value, tangent, 1.0f / 3.0f, result);
}
template<>
- FORCE_INLINE void GetTangent(const Transform& a, const Transform& b, float length, Transform& result)
+ FORCE_INLINE void GetTangent(const Transform& value, const Transform& tangent, float tangentScale, Transform& result)
{
- const float oneThird = 1.0f / 3.0f;
- const float oneThirdLength = length * oneThird;
- result.Translation = a.Translation + b.Translation * oneThirdLength;
- Quaternion::Slerp(a.Orientation, b.Orientation, oneThird, result.Orientation);
- result.Scale = a.Scale + (b.Scale - a.Scale) * oneThirdLength;
+ GetTangent(value.Translation, tangent.Translation, tangentScale, result.Translation);
+ GetTangent(value.Orientation, tangent.Orientation, tangentScale, result.Orientation);
+ GetTangent(value.Scale, tangent.Scale, tangentScale, result.Scale);
}
template
diff --git a/Source/Engine/Animations/Curve.cs b/Source/Engine/Animations/Curve.cs
index a0d911141..4c47dbb6e 100644
--- a/Source/Engine/Animations/Curve.cs
+++ b/Source/Engine/Animations/Curve.cs
@@ -1,8 +1,10 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+using FlaxEngine.Interop;
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace FlaxEngine
@@ -24,9 +26,9 @@ namespace FlaxEngine
///
/// The value.
/// The tangent.
- /// The length divided by 3.
+ /// The tangent scale factor.
/// The result.
- void GetTangent(ref U value, ref U tangent, float lengthThird, out U result);
+ void GetTangent(ref U value, ref U tangent, float tangentScale, out U result);
///
/// Calculates the linear interpolation at the specified alpha.
@@ -67,7 +69,7 @@ namespace FlaxEngine
IKeyframeAccess,
IKeyframeAccess
{
- public void GetTangent(ref bool value, ref bool tangent, float lengthThird, out bool result)
+ public void GetTangent(ref bool value, ref bool tangent, float tangentScale, out bool result)
{
result = value;
}
@@ -82,9 +84,9 @@ namespace FlaxEngine
result = p0;
}
- public void GetTangent(ref int value, ref int tangent, float lengthThird, out int result)
+ public void GetTangent(ref int value, ref int tangent, float tangentScale, out int result)
{
- result = value + (int)(tangent * lengthThird);
+ result = value + (int)(tangent * tangentScale);
}
public void Linear(ref int a, ref int b, float alpha, out int result)
@@ -102,9 +104,9 @@ namespace FlaxEngine
result = Mathf.Lerp(p012, p123, alpha);
}
- public void GetTangent(ref double value, ref double tangent, float lengthThird, out double result)
+ public void GetTangent(ref double value, ref double tangent, float tangentScale, out double result)
{
- result = value + tangent * lengthThird;
+ result = value + tangent * tangentScale;
}
public void Linear(ref double a, ref double b, float alpha, out double result)
@@ -122,9 +124,9 @@ namespace FlaxEngine
result = Mathf.Lerp(p012, p123, alpha);
}
- public void GetTangent(ref float value, ref float tangent, float lengthThird, out float result)
+ public void GetTangent(ref float value, ref float tangent, float tangentScale, out float result)
{
- result = value + tangent * lengthThird;
+ result = value + tangent * tangentScale;
}
public void Linear(ref float a, ref float b, float alpha, out float result)
@@ -142,9 +144,9 @@ namespace FlaxEngine
result = Mathf.Lerp(p012, p123, alpha);
}
- public void GetTangent(ref Vector2 value, ref Vector2 tangent, float lengthThird, out Vector2 result)
+ public void GetTangent(ref Vector2 value, ref Vector2 tangent, float tangentScale, out Vector2 result)
{
- result = value + tangent * lengthThird;
+ result = value + tangent * tangentScale;
}
public void Linear(ref Vector2 a, ref Vector2 b, float alpha, out Vector2 result)
@@ -162,9 +164,9 @@ namespace FlaxEngine
Vector2.Lerp(ref p012, ref p123, alpha, out result);
}
- public void GetTangent(ref Vector3 value, ref Vector3 tangent, float lengthThird, out Vector3 result)
+ public void GetTangent(ref Vector3 value, ref Vector3 tangent, float tangentScale, out Vector3 result)
{
- result = value + tangent * lengthThird;
+ result = value + tangent * tangentScale;
}
public void Linear(ref Vector3 a, ref Vector3 b, float alpha, out Vector3 result)
@@ -182,9 +184,9 @@ namespace FlaxEngine
Vector3.Lerp(ref p012, ref p123, alpha, out result);
}
- public void GetTangent(ref Vector4 value, ref Vector4 tangent, float lengthThird, out Vector4 result)
+ public void GetTangent(ref Vector4 value, ref Vector4 tangent, float tangentScale, out Vector4 result)
{
- result = value + tangent * lengthThird;
+ result = value + tangent * tangentScale;
}
public void Linear(ref Vector4 a, ref Vector4 b, float alpha, out Vector4 result)
@@ -202,9 +204,9 @@ namespace FlaxEngine
Vector4.Lerp(ref p012, ref p123, alpha, out result);
}
- public void GetTangent(ref Float2 value, ref Float2 tangent, float lengthThird, out Float2 result)
+ public void GetTangent(ref Float2 value, ref Float2 tangent, float tangentScale, out Float2 result)
{
- result = value + tangent * lengthThird;
+ result = value + tangent * tangentScale;
}
public void Linear(ref Float2 a, ref Float2 b, float alpha, out Float2 result)
@@ -222,9 +224,9 @@ namespace FlaxEngine
Float2.Lerp(ref p012, ref p123, alpha, out result);
}
- public void GetTangent(ref Float3 value, ref Float3 tangent, float lengthThird, out Float3 result)
+ public void GetTangent(ref Float3 value, ref Float3 tangent, float tangentScale, out Float3 result)
{
- result = value + tangent * lengthThird;
+ result = value + tangent * tangentScale;
}
public void Linear(ref Float3 a, ref Float3 b, float alpha, out Float3 result)
@@ -242,9 +244,9 @@ namespace FlaxEngine
Float3.Lerp(ref p012, ref p123, alpha, out result);
}
- public void GetTangent(ref Float4 value, ref Float4 tangent, float lengthThird, out Float4 result)
+ public void GetTangent(ref Float4 value, ref Float4 tangent, float tangentScale, out Float4 result)
{
- result = value + tangent * lengthThird;
+ result = value + tangent * tangentScale;
}
public void Linear(ref Float4 a, ref Float4 b, float alpha, out Float4 result)
@@ -262,9 +264,9 @@ namespace FlaxEngine
Float4.Lerp(ref p012, ref p123, alpha, out result);
}
- public void GetTangent(ref Double2 value, ref Double2 tangent, float lengthThird, out Double2 result)
+ public void GetTangent(ref Double2 value, ref Double2 tangent, float tangentScale, out Double2 result)
{
- result = value + tangent * lengthThird;
+ result = value + tangent * tangentScale;
}
public void Linear(ref Double2 a, ref Double2 b, float alpha, out Double2 result)
@@ -282,9 +284,9 @@ namespace FlaxEngine
Double2.Lerp(ref p012, ref p123, alpha, out result);
}
- public void GetTangent(ref Double3 value, ref Double3 tangent, float lengthThird, out Double3 result)
+ public void GetTangent(ref Double3 value, ref Double3 tangent, float tangentScale, out Double3 result)
{
- result = value + tangent * lengthThird;
+ result = value + tangent * tangentScale;
}
public void Linear(ref Double3 a, ref Double3 b, float alpha, out Double3 result)
@@ -302,9 +304,9 @@ namespace FlaxEngine
Double3.Lerp(ref p012, ref p123, alpha, out result);
}
- public void GetTangent(ref Double4 value, ref Double4 tangent, float lengthThird, out Double4 result)
+ public void GetTangent(ref Double4 value, ref Double4 tangent, float tangentScale, out Double4 result)
{
- result = value + tangent * lengthThird;
+ result = value + tangent * tangentScale;
}
public void Linear(ref Double4 a, ref Double4 b, float alpha, out Double4 result)
@@ -322,7 +324,7 @@ namespace FlaxEngine
Double4.Lerp(ref p012, ref p123, alpha, out result);
}
- public void GetTangent(ref Quaternion value, ref Quaternion tangent, float lengthThird, out Quaternion result)
+ public void GetTangent(ref Quaternion value, ref Quaternion tangent, float tangentScale, out Quaternion result)
{
Quaternion.Slerp(ref value, ref tangent, 1.0f / 3.0f, out result);
}
@@ -342,9 +344,9 @@ namespace FlaxEngine
Quaternion.Slerp(ref p012, ref p123, alpha, out result);
}
- public void GetTangent(ref Color32 value, ref Color32 tangent, float lengthThird, out Color32 result)
+ public void GetTangent(ref Color32 value, ref Color32 tangent, float tangentScale, out Color32 result)
{
- result = value + tangent * lengthThird;
+ result = value + tangent * tangentScale;
}
public void Linear(ref Color32 a, ref Color32 b, float alpha, out Color32 result)
@@ -362,9 +364,9 @@ namespace FlaxEngine
Color32.Lerp(ref p012, ref p123, alpha, out result);
}
- public void GetTangent(ref Color value, ref Color tangent, float lengthThird, out Color result)
+ public void GetTangent(ref Color value, ref Color tangent, float tangentScale, out Color result)
{
- result = value + tangent * lengthThird;
+ result = value + tangent * tangentScale;
}
public void Linear(ref Color a, ref Color b, float alpha, out Color result)
@@ -454,6 +456,40 @@ namespace FlaxEngine
time = end;
}
}
+
+ ///
+ /// Raw memory copy (used by scripting bindings - see MarshalAs tag).
+ ///
+ /// The keyframes array.
+ /// The raw keyframes data.
+ protected static unsafe byte[] MarshalKeyframes(Keyframe[] keyframes)
+ {
+ if (keyframes == null || keyframes.Length == 0)
+ return null;
+ var keyframeSize = Unsafe.SizeOf();
+ var result = new byte[keyframes.Length * keyframeSize];
+ fixed (byte* resultPtr = result)
+ {
+ var keyframesHandle = ManagedHandle.Alloc(keyframes, GCHandleType.Pinned);
+ var keyframesPtr = Marshal.UnsafeAddrOfPinnedArrayElement(keyframes, 0);
+ Buffer.MemoryCopy((void*)keyframesPtr, resultPtr, (uint)result.Length, (uint)result.Length);
+ keyframesHandle.Free();
+ }
+ return result;
+ }
+
+ ///
+ /// Raw memory copy (used by scripting bindings - see MarshalAs tag).
+ ///
+ /// The raw keyframes data.
+ /// The keyframes array.
+ protected static unsafe Keyframe[] MarshalKeyframes(byte[] raw) where Keyframe : struct
+ {
+ if (raw == null || raw.Length == 0)
+ return null;
+ fixed (byte* rawPtr = raw)
+ return MemoryMarshal.Cast(new Span(rawPtr, raw.Length)).ToArray();
+ }
}
///
@@ -709,6 +745,30 @@ namespace FlaxEngine
leftKey = Mathf.Max(0, start - 1);
rightKey = Mathf.Min(start, Keyframes.Length - 1);
}
+
+ ///
+ /// Raw memory copy (used by scripting bindings - see MarshalAs tag).
+ ///
+ /// The curve to copy.
+ /// The raw keyframes data.
+ public static unsafe implicit operator byte[](LinearCurve curve)
+ {
+ if (curve == null)
+ return null;
+ return MarshalKeyframes(curve.Keyframes);
+ }
+
+ ///
+ /// Raw memory copy (used by scripting bindings - see MarshalAs tag).
+ ///
+ /// The raw keyframes data.
+ /// The curve.
+ public static unsafe implicit operator LinearCurve(byte[] raw)
+ {
+ if (raw == null || raw.Length == 0)
+ return null;
+ return new LinearCurve(MarshalKeyframes(raw));
+ }
}
///
@@ -860,9 +920,9 @@ namespace FlaxEngine
// Evaluate the key at the curve
result.Time = leftKey.Time + length * t;
- float lengthThird = length / 3.0f;
- _accessor.GetTangent(ref leftKey.Value, ref leftKey.TangentOut, lengthThird, out var leftTangent);
- _accessor.GetTangent(ref rightKey.Value, ref rightKey.TangentIn, lengthThird, out var rightTangent);
+ float tangentScale = length / 3.0f;
+ _accessor.GetTangent(ref leftKey.Value, ref leftKey.TangentOut, tangentScale, out var leftTangent);
+ _accessor.GetTangent(ref rightKey.Value, ref rightKey.TangentIn, tangentScale, out var rightTangent);
_accessor.Bezier(ref leftKey.Value, ref leftTangent, ref rightTangent, ref rightKey.Value, t, out result.Value);
result.TangentIn = leftKey.TangentOut;
result.TangentOut = rightKey.TangentIn;
@@ -895,9 +955,9 @@ namespace FlaxEngine
float t = Mathf.NearEqual(length, 0.0f) ? 0.0f : (time - leftKey.Time) / length;
// Evaluate the value at the curve
- float lengthThird = length / 3.0f;
- _accessor.GetTangent(ref leftKey.Value, ref leftKey.TangentOut, lengthThird, out var leftTangent);
- _accessor.GetTangent(ref rightKey.Value, ref rightKey.TangentIn, lengthThird, out var rightTangent);
+ float tangentScale = length / 3.0f;
+ _accessor.GetTangent(ref leftKey.Value, ref leftKey.TangentOut, tangentScale, out var leftTangent);
+ _accessor.GetTangent(ref rightKey.Value, ref rightKey.TangentIn, tangentScale, out var rightTangent);
_accessor.Bezier(ref leftKey.Value, ref leftTangent, ref rightTangent, ref rightKey.Value, t, out result);
}
@@ -1000,5 +1060,29 @@ namespace FlaxEngine
leftKey = Mathf.Max(0, start - 1);
rightKey = Mathf.Min(start, Keyframes.Length - 1);
}
+
+ ///
+ /// Raw memory copy (used by scripting bindings - see MarshalAs tag).
+ ///
+ /// The curve to copy.
+ /// The raw keyframes data.
+ public static unsafe implicit operator byte[](BezierCurve curve)
+ {
+ if (curve == null)
+ return null;
+ return MarshalKeyframes(curve.Keyframes);
+ }
+
+ ///
+ /// Raw memory copy (used by scripting bindings - see MarshalAs tag).
+ ///
+ /// The raw keyframes data.
+ /// The curve.
+ public static unsafe implicit operator BezierCurve(byte[] raw)
+ {
+ if (raw == null || raw.Length == 0)
+ return null;
+ return new BezierCurve(MarshalKeyframes(raw));
+ }
}
}
diff --git a/Source/Engine/Animations/Curve.h b/Source/Engine/Animations/Curve.h
index 40dfc8add..e3e0be33e 100644
--- a/Source/Engine/Animations/Curve.h
+++ b/Source/Engine/Animations/Curve.h
@@ -247,16 +247,18 @@ public:
static void Interpolate(const BezierCurveKeyframe& a, const BezierCurveKeyframe& b, float alpha, float length, T& result)
{
T leftTangent, rightTangent;
- AnimationUtils::GetTangent(a.Value, a.TangentOut, length, leftTangent);
- AnimationUtils::GetTangent(b.Value, b.TangentIn, length, rightTangent);
+ const float tangentScale = length / 3.0f;
+ AnimationUtils::GetTangent(a.Value, a.TangentOut, tangentScale, leftTangent);
+ AnimationUtils::GetTangent(b.Value, b.TangentIn, tangentScale, rightTangent);
AnimationUtils::Bezier(a.Value, leftTangent, rightTangent, b.Value, alpha, result);
}
static void InterpolateFirstDerivative(const BezierCurveKeyframe& a, const BezierCurveKeyframe& b, float alpha, float length, T& result)
{
T leftTangent, rightTangent;
- AnimationUtils::GetTangent(a.Value, a.TangentOut, length, leftTangent);
- AnimationUtils::GetTangent(b.Value, b.TangentIn, length, rightTangent);
+ const float tangentScale = length / 3.0f;
+ AnimationUtils::GetTangent(a.Value, a.TangentOut, tangentScale, leftTangent);
+ AnimationUtils::GetTangent(b.Value, b.TangentIn, tangentScale, rightTangent);
AnimationUtils::BezierFirstDerivative(a.Value, leftTangent, rightTangent, b.Value, alpha, result);
}
@@ -264,8 +266,9 @@ public:
{
result.Time = a.Time + length * alpha;
T leftTangent, rightTangent;
- AnimationUtils::GetTangent(a.Value, a.TangentOut, length, leftTangent);
- AnimationUtils::GetTangent(b.Value, b.TangentIn, length, rightTangent);
+ const float tangentScale = length / 3.0f;
+ AnimationUtils::GetTangent(a.Value, a.TangentOut, tangentScale, leftTangent);
+ AnimationUtils::GetTangent(b.Value, b.TangentIn, tangentScale, rightTangent);
AnimationUtils::Bezier(a.Value, leftTangent, rightTangent, b.Value, alpha, result.Value);
result.TangentIn = a.TangentOut;
result.TangentOut = b.TangentIn;
@@ -498,7 +501,7 @@ protected:
/// An animation spline represented by a set of keyframes, each representing an endpoint of a curve.
///
template>
-class Curve : public CurveBase
+API_CLASS(InBuild, Template, MarshalAs=Span) class Curve : public CurveBase
{
public:
typedef CurveBase Base;
@@ -760,28 +763,42 @@ public:
}
return true;
}
+
+ // Raw memory copy (used by scripting bindings - see MarshalAs tag).
+ Curve& operator=(const Span& raw)
+ {
+ ASSERT((raw.Length() % sizeof(KeyFrame)) == 0);
+ const int32 count = raw.Length() / sizeof(KeyFrame);
+ _keyframes.Resize(count, false);
+ Platform::MemoryCopy(_keyframes.Get(), raw.Get(), sizeof(KeyFrame) * count);
+ return *this;
+ }
+ operator Span()
+ {
+ return Span((const byte*)_keyframes.Get(), _keyframes.Count() * sizeof(KeyFrame));
+ }
};
///
/// An animation spline represented by a set of keyframes, each representing a value point.
///
template
-using StepCurve = Curve>;
+API_TYPEDEF() using StepCurve = Curve>;
///
/// An animation spline represented by a set of keyframes, each representing an endpoint of a linear curve.
///
template
-using LinearCurve = Curve>;
+API_TYPEDEF() using LinearCurve = Curve>;
///
/// An animation spline represented by a set of keyframes, each representing an endpoint of a cubic hermite curve.
///
template
-using HermiteCurve = Curve>;
+API_TYPEDEF() using HermiteCurve = Curve>;
///
/// An animation spline represented by a set of keyframes, each representing an endpoint of Bezier curve.
///
template
-using BezierCurve = Curve>;
+API_TYPEDEF() using BezierCurve = Curve>;
diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp
index 4fd1718ab..b410d62f7 100644
--- a/Source/Engine/Content/Assets/VisualScript.cpp
+++ b/Source/Engine/Content/Assets/VisualScript.cpp
@@ -2263,6 +2263,14 @@ void VisualScript::GetMethodSignature(int32 index, String& name, byte& flags, St
}
}
+Variant VisualScript::InvokeMethod(int32 index, const Variant& instance, Span parameters) const
+{
+ auto& method = _methods[index];
+ Variant result;
+ VisualScriptingModule.InvokeMethod((void*)&method, instance, parameters, result);
+ return result;
+}
+
Span VisualScript::GetMetaData(int32 typeID)
{
auto meta = Graph.Meta.GetEntry(typeID);
diff --git a/Source/Engine/Content/Assets/VisualScript.h b/Source/Engine/Content/Assets/VisualScript.h
index 1cd8e9749..c07e3883a 100644
--- a/Source/Engine/Content/Assets/VisualScript.h
+++ b/Source/Engine/Content/Assets/VisualScript.h
@@ -267,6 +267,9 @@ public:
// Gets the signature data of the method.
API_FUNCTION() void GetMethodSignature(int32 index, API_PARAM(Out) String& name, API_PARAM(Out) byte& flags, API_PARAM(Out) String& returnTypeName, API_PARAM(Out) Array& paramNames, API_PARAM(Out) Array& paramTypeNames, API_PARAM(Out) Array& paramOuts);
+ // Invokes the method.
+ API_FUNCTION() Variant InvokeMethod(int32 index, const Variant& instance, Span parameters) const;
+
// Gets the metadata of the script surface.
API_FUNCTION() Span GetMetaData(int32 typeID);
diff --git a/Source/Engine/Core/Config/BuildSettings.cs b/Source/Engine/Core/Config/BuildSettings.cs
index 8526690b1..e5838087c 100644
--- a/Source/Engine/Core/Config/BuildSettings.cs
+++ b/Source/Engine/Core/Config/BuildSettings.cs
@@ -21,8 +21,8 @@ namespace FlaxEditor.Content.Settings
{
new BuildTarget
{
- Name = "Windows 64bit",
- Output = "Output\\Win64",
+ Name = "Windows",
+ Output = "Output\\Windows",
Platform = BuildPlatform.Windows64,
Mode = BuildConfiguration.Development,
},
@@ -35,8 +35,8 @@ namespace FlaxEditor.Content.Settings
{
new BuildTarget
{
- Name = "Windows 64bit",
- Output = "Output\\Win64",
+ Name = "Windows",
+ Output = "Output\\Windows",
Platform = BuildPlatform.Windows64,
Mode = BuildConfiguration.Release,
},
diff --git a/Source/Engine/Core/Config/LayersAndTagsSettings.cs b/Source/Engine/Core/Config/LayersAndTagsSettings.cs
index 9346efee7..4086ec657 100644
--- a/Source/Engine/Core/Config/LayersAndTagsSettings.cs
+++ b/Source/Engine/Core/Config/LayersAndTagsSettings.cs
@@ -16,7 +16,7 @@ namespace FlaxEditor.Content.Settings
public List Tags = new List();
///
- /// The layers names.
+ /// The layer names.
///
[EditorOrder(10), EditorDisplay("Layers", EditorDisplayAttribute.InlineStyle), Collection(CanResize = false, Display = CollectionAttribute.DisplayType.Inline)]
public string[] Layers = new string[32];
@@ -30,6 +30,31 @@ namespace FlaxEditor.Content.Settings
return GetCurrentLayers(out int _);
}
+ ///
+ /// The layer names.
+ ///
+ [EditorOrder(10), EditorDisplay("Terrain Layers", EditorDisplayAttribute.InlineStyle), Collection(CanResize = false, Display = CollectionAttribute.DisplayType.Inline)]
+ public string[] TerrainLayers = new string[8];
+
+ ///
+ /// Gets the current terrain layer names. Returns "Layer" + index for layers without a name.
+ ///
+ /// The layer names.
+ public static string[] GetCurrentTerrainLayers()
+ {
+#if FLAX_TESTS
+ return System.Array.Empty();
+#else
+ string[] layerNames = GameSettings.Load().TerrainLayers;
+ for (int i = 0; i < layerNames.Length; i++)
+ {
+ if (string.IsNullOrEmpty(layerNames[i]))
+ layerNames[i] = $"Layer {i}";
+ }
+ return layerNames;
+#endif
+ }
+
[LibraryImport("FlaxEngine", EntryPoint = "LayersAndTagsSettingsInternal_GetCurrentLayers", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.Interop.StringMarshaller))]
[return: MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = "layerCount")]
internal static partial string[] GetCurrentLayers(out int layerCount);
diff --git a/Source/Engine/Core/Log.cpp b/Source/Engine/Core/Log.cpp
index 38d591c51..20689f628 100644
--- a/Source/Engine/Core/Log.cpp
+++ b/Source/Engine/Core/Log.cpp
@@ -16,10 +16,14 @@
#include
#define LOG_ENABLE_FILE (!PLATFORM_SWITCH)
+#define LOG_ENABLE_WINDOWS_SINGLE_NEW_LINE_CHAR (PLATFORM_WINDOWS && PLATFORM_DESKTOP && (USE_EDITOR || !BUILD_RELEASE))
namespace
{
bool LogAfterInit = false, IsDuringLog = false;
+#if LOG_ENABLE_WINDOWS_SINGLE_NEW_LINE_CHAR
+ bool IsWindowsSingleNewLineChar = false;
+#endif
int LogTotalErrorsCnt = 0;
FileWriteStream* LogFile = nullptr;
CriticalSection LogLocker;
@@ -86,6 +90,11 @@ bool Log::Logger::Init()
}
LogTotalErrorsCnt = 0;
LogAfterInit = true;
+#if LOG_ENABLE_WINDOWS_SINGLE_NEW_LINE_CHAR
+ String envVar;
+ Platform::GetEnvironmentVariable(TEXT("GITHUB_ACTION"), envVar);
+ IsWindowsSingleNewLineChar = envVar.HasChars();
+#endif
// Write BOM (UTF-16 (LE); BOM: FF FE)
byte bom[] = { 0xFF, 0xFE };
@@ -127,6 +136,11 @@ void Log::Logger::Write(const StringView& msg)
printf("%s", ansi.Get());
#else
std::wcout.write(ptr, length);
+#if LOG_ENABLE_WINDOWS_SINGLE_NEW_LINE_CHAR
+ if (IsWindowsSingleNewLineChar)
+ std::wcout.write(TEXT("\n"), 1); // Github Actions show logs with duplicated new-line characters so skip \r
+ else
+#endif
std::wcout.write(TEXT(PLATFORM_LINE_TERMINATOR), ARRAY_COUNT(PLATFORM_LINE_TERMINATOR) - 1);
#endif
}
diff --git a/Source/Engine/Core/Math/BoundingBox.cpp b/Source/Engine/Core/Math/BoundingBox.cpp
index 915014be8..6486d0188 100644
--- a/Source/Engine/Core/Math/BoundingBox.cpp
+++ b/Source/Engine/Core/Math/BoundingBox.cpp
@@ -124,9 +124,9 @@ void BoundingBox::Transform(const BoundingBox& box, const Matrix& matrix, Boundi
const auto ya = up * box.Minimum.Y;
const auto yb = up * box.Maximum.Y;
- const auto backward = matrix.GetBackward();
- const auto za = backward * box.Minimum.Z;
- const auto zb = backward * box.Maximum.Z;
+ const auto forward = matrix.GetForward();
+ const auto za = forward * box.Minimum.Z;
+ const auto zb = forward * box.Maximum.Z;
const auto translation = matrix.GetTranslation();
const auto min = Vector3::Min(xa, xb) + Vector3::Min(ya, yb) + Vector3::Min(za, zb) + translation;
@@ -146,9 +146,9 @@ void BoundingBox::Transform(const BoundingBox& box, const ::Transform& transform
const auto ya = up * box.Minimum.Y;
const auto yb = up * box.Maximum.Y;
- const auto backward = Float3::Transform(Float3::Backward, transform.Orientation);
- const auto za = backward * box.Minimum.Z;
- const auto zb = backward * box.Maximum.Z;
+ const auto forward = Float3::Transform(Float3::Forward, transform.Orientation);
+ const auto za = forward * box.Minimum.Z;
+ const auto zb = forward * box.Maximum.Z;
const auto min = Vector3::Min(xa, xb) + Vector3::Min(ya, yb) + Vector3::Min(za, zb) + transform.Translation;
const auto max = Vector3::Max(xa, xb) + Vector3::Max(ya, yb) + Vector3::Max(za, zb) + transform.Translation;
diff --git a/Source/Engine/Core/Math/BoundingBox.cs b/Source/Engine/Core/Math/BoundingBox.cs
index 7f871a8a1..e161ed300 100644
--- a/Source/Engine/Core/Math/BoundingBox.cs
+++ b/Source/Engine/Core/Math/BoundingBox.cs
@@ -474,9 +474,9 @@ namespace FlaxEngine
var ya = up * box.Minimum.Y;
var yb = up * box.Maximum.Y;
- Double3 backward = transform.Backward;
- var za = backward * box.Minimum.Z;
- var zb = backward * box.Maximum.Z;
+ Double3 forward = transform.Forward;
+ var za = forward * box.Minimum.Z;
+ var zb = forward * box.Maximum.Z;
var translation = transform.TranslationVector;
var min = Vector3.Min(xa, xb) + Vector3.Min(ya, yb) + Vector3.Min(za, zb) + translation;
@@ -514,9 +514,9 @@ namespace FlaxEngine
var ya = up * box.Minimum.Y;
var yb = up * box.Maximum.Y;
- Double3 backward = transform.Backward;
- var za = backward * box.Minimum.Z;
- var zb = backward * box.Maximum.Z;
+ Double3 forward = transform.Forward;
+ var za = forward * box.Minimum.Z;
+ var zb = forward * box.Maximum.Z;
var min = Vector3.Min(xa, xb) + Vector3.Min(ya, yb) + Vector3.Min(za, zb) + transform.Translation;
var max = Vector3.Max(xa, xb) + Vector3.Max(ya, yb) + Vector3.Max(za, zb) + transform.Translation;
diff --git a/Source/Engine/Core/Math/Matrix.cpp b/Source/Engine/Core/Math/Matrix.cpp
index 5d7bab6b6..872b554a6 100644
--- a/Source/Engine/Core/Math/Matrix.cpp
+++ b/Source/Engine/Core/Math/Matrix.cpp
@@ -136,12 +136,12 @@ void Matrix::Decompose(Float3& scale, Matrix3x3& rotation, Float3& translation)
const auto right = Float3::Cross(up, at);
rotation.SetRight(right);
rotation.SetUp(up);
- rotation.SetBackward(at);
+ rotation.SetForward(at);
// In case of reflexions
scale.X = Float3::Dot(right, GetRight()) > 0.0f ? scale.X : -scale.X;
scale.Y = Float3::Dot(up, GetUp()) > 0.0f ? scale.Y : -scale.Y;
- scale.Z = Float3::Dot(at, GetBackward()) > 0.0f ? scale.Z : -scale.Z;
+ scale.Z = Float3::Dot(at, GetForward()) > 0.0f ? scale.Z : -scale.Z;
}
void Matrix::Decompose(Float3& scale, Matrix& rotation, Float3& translation) const
diff --git a/Source/Engine/Core/Math/Matrix.cs b/Source/Engine/Core/Math/Matrix.cs
index c065379d3..71edef415 100644
--- a/Source/Engine/Core/Math/Matrix.cs
+++ b/Source/Engine/Core/Math/Matrix.cs
@@ -215,23 +215,9 @@ namespace FlaxEngine
}
///
- /// Gets or sets the forward of the matrix; that is -M31, -M32, and -M33.
+ /// Gets or sets the forward of the matrix; that is M31, M32, and M33.
///
public Float3 Forward
- {
- get => new Float3(-M31, -M32, -M33);
- set
- {
- M31 = -value.X;
- M32 = -value.Y;
- M33 = -value.Z;
- }
- }
-
- ///
- /// Gets or sets the backward of the matrix; that is M31, M32, and M33.
- ///
- public Float3 Backward
{
get => new Float3(M31, M32, M33);
set
@@ -242,6 +228,20 @@ namespace FlaxEngine
}
}
+ ///
+ /// Gets or sets the backward of the matrix; that is -M31, -M32, and -M33.
+ ///
+ public Float3 Backward
+ {
+ get => new Float3(-M31, -M32, -M33);
+ set
+ {
+ M31 = -value.X;
+ M32 = -value.Y;
+ M33 = -value.Z;
+ }
+ }
+
///
/// Initializes a new instance of the struct.
///
diff --git a/Source/Engine/Core/Math/Matrix.h b/Source/Engine/Core/Math/Matrix.h
index 0eddc173b..e71da993a 100644
--- a/Source/Engine/Core/Math/Matrix.h
+++ b/Source/Engine/Core/Math/Matrix.h
@@ -210,31 +210,31 @@ public:
// Gets the forward Float3 of the matrix; that is -M31, -M32, and -M33.
Float3 GetForward() const
{
- return -Float3(M31, M32, M33);
+ return Float3(M31, M32, M33);
}
// Sets the forward Float3 of the matrix; that is -M31, -M32, and -M33.
void SetForward(const Float3& value)
- {
- M31 = -value.X;
- M32 = -value.Y;
- M33 = -value.Z;
- }
-
- // Gets the backward Float3 of the matrix; that is M31, M32, and M33.
- Float3 GetBackward() const
- {
- return Float3(M31, M32, M33);
- }
-
- // Sets the backward Float3 of the matrix; that is M31, M32, and M33.
- void SetBackward(const Float3& value)
{
M31 = value.X;
M32 = value.Y;
M33 = value.Z;
}
+ // Gets the backward Float3 of the matrix; that is -M31, -M32, and -M33.
+ Float3 GetBackward() const
+ {
+ return Float3(-M31, -M32, -M33);
+ }
+
+ // Sets the backward Float3 of the matrix; that is -M31, -M32, and -M33.
+ void SetBackward(const Float3& value)
+ {
+ M31 = -value.X;
+ M32 = -value.Y;
+ M33 = -value.Z;
+ }
+
// Gets the first row in the matrix; that is M11, M12, M13, and M14.
Float4 GetRow1() const
{
diff --git a/Source/Engine/Core/Math/Matrix3x3.cpp b/Source/Engine/Core/Math/Matrix3x3.cpp
index 0f0f520e7..b319a274a 100644
--- a/Source/Engine/Core/Math/Matrix3x3.cpp
+++ b/Source/Engine/Core/Math/Matrix3x3.cpp
@@ -225,7 +225,7 @@ void Matrix3x3::Decompose(Float3& scale, Matrix3x3& rotation) const
const auto right = Float3::Cross(up, at);
rotation.SetRight(right);
rotation.SetUp(up);
- rotation.SetBackward(at);
+ rotation.SetForward(at);
// In case of reflexions
scale.X = Float3::Dot(right, GetRight()) > 0.0f ? scale.X : -scale.X;
diff --git a/Source/Engine/Core/Math/Matrix3x3.cs b/Source/Engine/Core/Math/Matrix3x3.cs
index b19d80490..970ec6cec 100644
--- a/Source/Engine/Core/Math/Matrix3x3.cs
+++ b/Source/Engine/Core/Math/Matrix3x3.cs
@@ -303,9 +303,6 @@ namespace FlaxEngine
///
/// Gets a value indicating whether this instance is an identity Matrix3x3.
///
- ///
- /// true if this instance is an identity Matrix3x3; otherwise, false.
- ///
public bool IsIdentity => Equals(Identity);
///
@@ -566,19 +563,19 @@ namespace FlaxEngine
///
public bool DecomposeUniformScale(out float scale, out Quaternion rotation)
{
- //Scaling is the length of the rows. ( just take one row since this is a uniform matrix)
+ // Scaling is the length of the rows. ( just take one row since this is a uniform matrix)
scale = (float)Math.Sqrt((M11 * M11) + (M12 * M12) + (M13 * M13));
var invScale = 1f / scale;
- //If any of the scaling factors are zero, then the rotation matrix can not exist.
+ // If any of the scaling factors are zero, then the rotation matrix can not exist
if (Math.Abs(scale) < Mathf.Epsilon)
{
rotation = Quaternion.Identity;
return false;
}
- //The rotation is the left over matrix after dividing out the scaling.
- Matrix3x3 rotationmatrix = new Matrix3x3
+ // The rotation is the leftover matrix after dividing out the scaling
+ var rotationMatrix = new Matrix3x3
{
M11 = M11 * invScale,
M12 = M12 * invScale,
@@ -590,8 +587,7 @@ namespace FlaxEngine
M32 = M32 * invScale,
M33 = M33 * invScale
};
-
- Quaternion.RotationMatrix(ref rotationmatrix, out rotation);
+ Quaternion.RotationMatrix(ref rotationMatrix, out rotation);
return true;
}
diff --git a/Source/Engine/Core/Math/Matrix3x3.h b/Source/Engine/Core/Math/Matrix3x3.h
index f5f8cd998..cb5ae04a2 100644
--- a/Source/Engine/Core/Math/Matrix3x3.h
+++ b/Source/Engine/Core/Math/Matrix3x3.h
@@ -175,34 +175,34 @@ public:
M13 = -value.Z;
}
- // Gets the forward Float3 of the matrix; that is -M31, -M32, and -M33.
+ // Gets the forward Float3 of the matrix; that is M31, M32, and M33.
Float3 GetForward() const
{
return -Float3(M31, M32, M33);
}
- // Sets the forward Float3 of the matrix; that is -M31, -M32, and -M33.
+ // Sets the forward Float3 of the matrix; that is M31, M32, and M33.
void SetForward(const Float3& value)
- {
- M31 = -value.X;
- M32 = -value.Y;
- M33 = -value.Z;
- }
-
- // Gets the backward Float3 of the matrix; that is M31, M32, and M33.
- Float3 GetBackward() const
- {
- return Float3(M31, M32, M33);
- }
-
- // Sets the backward Float3 of the matrix; that is M31, M32, and M33.
- void SetBackward(const Float3& value)
{
M31 = value.X;
M32 = value.Y;
M33 = value.Z;
}
+ // Gets the backward Float3 of the matrix; that is -M31, -M32, and -M33.
+ Float3 GetBackward() const
+ {
+ return Float3(-M31, -M32, -M33);
+ }
+
+ // Sets the backward Float3 of the matrix; that is -M31, -M32, and -M33.
+ void SetBackward(const Float3& value)
+ {
+ M31 = -value.X;
+ M32 = -value.Y;
+ M33 = -value.Z;
+ }
+
// Gets the first row in the matrix; that is M11, M12 and M13.
Float3 GetRow1() const
{
diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp
index fc921df0d..9035ef71f 100644
--- a/Source/Engine/Core/Types/Variant.cpp
+++ b/Source/Engine/Core/Types/Variant.cpp
@@ -1827,6 +1827,7 @@ Variant::operator Float4() const
return Float4(*(Float3*)AsData, 0.0f);
case VariantType::Float4:
case VariantType::Color:
+ case VariantType::Quaternion:
return *(Float4*)AsData;
case VariantType::Double2:
return Float4(AsDouble2(), 0.0f, 0.0f);
diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp
index 077bf9b80..447e6cabc 100644
--- a/Source/Engine/Debug/DebugDraw.cpp
+++ b/Source/Engine/Debug/DebugDraw.cpp
@@ -94,6 +94,13 @@ struct DebugLine
float TimeLeft;
};
+struct DebugGeometryBuffer
+{
+ GPUBuffer* Buffer;
+ float TimeLeft;
+ Matrix Transform;
+};
+
struct DebugTriangle
{
Float3 V0;
@@ -122,12 +129,9 @@ struct DebugText3D
float TimeLeft;
};
-PACK_STRUCT(struct Vertex {
- Float3 Position;
- Color32 Color;
- });
+typedef DebugDraw::Vertex Vertex;
-GPU_CB_STRUCT(Data {
+GPU_CB_STRUCT(ShaderData {
Matrix ViewProjection;
Float2 Padding;
float ClipPosZBias;
@@ -231,6 +235,7 @@ void TeleportList(const Float3& delta, Array& list)
struct DebugDrawData
{
+ Array GeometryBuffers;
Array DefaultLines;
Array OneFrameLines;
Array DefaultTriangles;
@@ -244,7 +249,7 @@ struct DebugDrawData
inline int32 Count() const
{
- return LinesCount() + TrianglesCount() + TextCount();
+ return LinesCount() + TrianglesCount() + TextCount() + GeometryBuffers.Count();
}
inline int32 LinesCount() const
@@ -280,6 +285,7 @@ struct DebugDrawData
inline void Update(float deltaTime)
{
+ UpdateList(deltaTime, GeometryBuffers);
UpdateList(deltaTime, DefaultLines);
UpdateList(deltaTime, DefaultTriangles);
UpdateList(deltaTime, DefaultWireTriangles);
@@ -784,7 +790,7 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
// Update constant buffer
const auto cb = DebugDrawShader->GetShader()->GetCB(0);
- Data data;
+ ShaderData data;
Matrix vp;
Matrix::Multiply(view.View, view.Projection, vp);
Matrix::Transpose(vp, data.ViewProjection);
@@ -830,6 +836,22 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
context->Draw(depthTestTriangles.StartVertex, depthTestTriangles.VertexCount);
}
+ // Geometries
+ for (auto& geometry : Context->DebugDrawDepthTest.GeometryBuffers)
+ {
+ auto tmp = data;
+ Matrix mvp;
+ Matrix::Multiply(geometry.Transform, vp, mvp);
+ Matrix::Transpose(mvp, tmp.ViewProjection);
+ context->UpdateCB(cb, &tmp);
+ auto state = data.EnableDepthTest ? &DebugDrawPsLinesDepthTest : &DebugDrawPsLinesDefault;
+ context->SetState(state->Get(enableDepthWrite, true));
+ context->BindVB(ToSpan(&geometry.Buffer, 1));
+ context->Draw(0, geometry.Buffer->GetElementsCount());
+ }
+ if (Context->DebugDrawDepthTest.GeometryBuffers.HasItems())
+ context->UpdateCB(cb, &data);
+
if (data.EnableDepthTest)
context->UnBindSR(0);
}
@@ -862,6 +884,19 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
context->BindVB(ToSpan(&vb, 1));
context->Draw(defaultTriangles.StartVertex, defaultTriangles.VertexCount);
}
+
+ // Geometries
+ for (auto& geometry : Context->DebugDrawDefault.GeometryBuffers)
+ {
+ auto tmp = data;
+ Matrix mvp;
+ Matrix::Multiply(geometry.Transform, vp, mvp);
+ Matrix::Transpose(mvp, tmp.ViewProjection);
+ context->UpdateCB(cb, &tmp);
+ context->SetState(DebugDrawPsLinesDefault.Get(false, false));
+ context->BindVB(ToSpan(&geometry.Buffer, 1));
+ context->Draw(0, geometry.Buffer->GetElementsCount());
+ }
}
// Text
@@ -1088,6 +1123,24 @@ void DebugDraw::DrawLines(const Span& lines, const Matrix& transform, co
}
}
+void DebugDraw::DrawLines(GPUBuffer* lines, const Matrix& transform, float duration, bool depthTest)
+{
+ if (lines == nullptr || lines->GetSize() == 0)
+ return;
+ if (lines->GetSize() % (sizeof(Vertex) * 2) != 0)
+ {
+ DebugLog::ThrowException("Cannot draw debug lines with uneven amount of items in array");
+ return;
+ }
+
+ // Draw lines
+ auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
+ auto& geometry = debugDrawData.GeometryBuffers.AddOne();
+ geometry.Buffer = lines;
+ geometry.TimeLeft = duration;
+ geometry.Transform = transform * Matrix::Translation(-Context->Origin);
+}
+
void DebugDraw::DrawLines(const Array& lines, const Matrix& transform, const Color& color, float duration, bool depthTest)
{
DrawLines(Span(lines.Get(), lines.Count()), transform, color, duration, depthTest);
@@ -2147,6 +2200,7 @@ void DebugDraw::DrawText(const StringView& text, const Transform& transform, con
void DebugDraw::Clear(void* context)
{
- DebugDraw::UpdateContext(context, MAX_float);
+ UpdateContext(context, MAX_float);
}
+
#endif
diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h
index bb5cf2e50..6cfceca6d 100644
--- a/Source/Engine/Debug/DebugDraw.h
+++ b/Source/Engine/Debug/DebugDraw.h
@@ -6,6 +6,8 @@
#include "Engine/Scripting/ScriptingType.h"
#include "Engine/Core/Math/Color.h"
+#include "Engine/Core/Math/Color32.h"
+#include "Engine/Core/Math/Vector3.h"
#include "Engine/Core/Types/Span.h"
struct RenderView;
@@ -14,6 +16,7 @@ class Light;
struct RenderContext;
class GPUTextureView;
class GPUContext;
+class GPUBuffer;
class RenderTask;
class SceneRenderTask;
class Actor;
@@ -26,6 +29,14 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
{
DECLARE_SCRIPTING_TYPE_NO_SPAWN(DebugDraw);
+ ///
+ /// Vertex data for debug shapes.
+ ///
+ PACK_STRUCT(struct Vertex {
+ Float3 Position;
+ Color32 Color;
+ });
+
#if USE_EDITOR
///
/// Allocates the context for Debug Drawing. Can be use to redirect debug shapes collecting to a separate container (instead of global state).
@@ -175,6 +186,15 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
/// If set to true depth test will be performed, otherwise depth will be ignored.
API_FUNCTION() static void DrawLines(const Span& lines, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true);
+ ///
+ /// Draws the lines using the provided vertex buffer that contains pairs of Vertex elements. Line positions are located one after another (e.g. l0.start, l0.end, l1.start, l1.end,...).
+ ///
+ /// The GPU buffer with vertices for lines (must have multiple of 2 elements).
+ /// The custom matrix used to transform all line vertices.
+ /// The duration (in seconds). Use 0 to draw it only once.
+ /// If set to true depth test will be performed, otherwise depth will be ignored.
+ API_FUNCTION() static void DrawLines(GPUBuffer* lines, const Matrix& transform, float duration = 0.0f, bool depthTest = true);
+
///
/// Draws the lines. Line positions are located one after another (e.g. l0.start, l0.end, l1.start, l1.end,...).
///
@@ -691,9 +711,9 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
API_FUNCTION() static void DrawText(const StringView& text, const Transform& transform, const Color& color = Color::White, int32 size = 32, float duration = 0.0f);
///
- /// Clear all debug draw displayed on sceen.
+ /// Clears all debug shapes displayed on screen.
///
- ///
+ /// The context.
API_FUNCTION() static void Clear(void* context = nullptr);
};
diff --git a/Source/Engine/Engine/CommandLine.cpp b/Source/Engine/Engine/CommandLine.cpp
index e2de230ef..efcd73524 100644
--- a/Source/Engine/Engine/CommandLine.cpp
+++ b/Source/Engine/Engine/CommandLine.cpp
@@ -154,6 +154,7 @@ bool CommandLine::Parse(const Char* cmdLine)
PARSE_ARG_SWITCH("-build ", Build);
PARSE_BOOL_SWITCH("-skipcompile ", SkipCompile);
PARSE_BOOL_SWITCH("-shaderdebug ", ShaderDebug);
+ PARSE_BOOL_SWITCH("-exit ", Exit);
PARSE_ARG_OPT_SWITCH("-play ", Play);
#endif
#if USE_EDITOR || !BUILD_RELEASE
diff --git a/Source/Engine/Engine/CommandLine.h b/Source/Engine/Engine/CommandLine.h
index 347e9bebd..4a3bab4c0 100644
--- a/Source/Engine/Engine/CommandLine.h
+++ b/Source/Engine/Engine/CommandLine.h
@@ -168,6 +168,11 @@ public:
///
Nullable ShaderDebug;
+ ///
+ /// -exit (exits the editor after startup and performing all queued actions). Usefull when invoking editor from CL/CD.
+ ///
+ Nullable Exit;
+
///
/// -play !guid! ( Scene to play, can be empty to use default )
///
diff --git a/Source/Engine/Graphics/Async/GPUTasksContext.cpp b/Source/Engine/Graphics/Async/GPUTasksContext.cpp
index 11582fe78..e0ee8cae1 100644
--- a/Source/Engine/Graphics/Async/GPUTasksContext.cpp
+++ b/Source/Engine/Graphics/Async/GPUTasksContext.cpp
@@ -5,6 +5,7 @@
#include "Engine/Core/Log.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Threading/Threading.h"
+#include "Engine/Engine/Globals.h"
#define GPU_TASKS_USE_DEDICATED_CONTEXT 0
@@ -36,7 +37,8 @@ GPUTasksContext::~GPUTasksContext()
auto task = tasks[i];
if (task->GetSyncPoint() <= _currentSyncPoint && task->GetState() != TaskState::Finished)
{
- LOG(Warning, "{0} has been canceled before a sync", task->ToString());
+ if (!Globals::IsRequestingExit)
+ LOG(Warning, "{0} has been canceled before a sync", task->ToString());
task->CancelSync();
}
}
@@ -60,7 +62,8 @@ void GPUTasksContext::OnCancelSync(GPUTask* task)
_tasksDone.Remove(task);
- LOG(Warning, "{0} has been canceled before a sync", task->ToString());
+ if (!Globals::IsRequestingExit)
+ LOG(Warning, "{0} has been canceled before a sync", task->ToString());
}
void GPUTasksContext::OnFrameBegin()
diff --git a/Source/Engine/Graphics/GPUBuffer.cpp b/Source/Engine/Graphics/GPUBuffer.cpp
index 32681b4ce..6a0221b14 100644
--- a/Source/Engine/Graphics/GPUBuffer.cpp
+++ b/Source/Engine/Graphics/GPUBuffer.cpp
@@ -5,6 +5,7 @@
#include "GPUResourceProperty.h"
#include "GPUBufferDescription.h"
#include "PixelFormatExtensions.h"
+#include "RenderTask.h"
#include "Async/Tasks/GPUCopyResourceTask.h"
#include "Engine/Core/Utilities.h"
#include "Engine/Core/Types/String.h"
@@ -358,6 +359,16 @@ void GPUBuffer::SetData(const void* data, uint32 size)
Log::ArgumentOutOfRangeException(TEXT("Buffer.SetData"));
return;
}
+
+ if (_desc.Usage == GPUResourceUsage::Default && GPUDevice::Instance->IsRendering())
+ {
+ // Upload using the context (will use internal staging buffer inside command buffer)
+ RenderContext::GPULocker.Lock();
+ GPUDevice::Instance->GetMainContext()->UpdateBuffer(this, data, size);
+ RenderContext::GPULocker.Unlock();
+ return;
+ }
+
void* mapped = Map(GPUResourceMapMode::Write);
if (!mapped)
return;
diff --git a/Source/Engine/Graphics/GPUBuffer.h b/Source/Engine/Graphics/GPUBuffer.h
index cbdc39c4f..d40b7744d 100644
--- a/Source/Engine/Graphics/GPUBuffer.h
+++ b/Source/Engine/Graphics/GPUBuffer.h
@@ -74,8 +74,7 @@ public:
///
API_PROPERTY() FORCE_INLINE uint32 GetElementsCount() const
{
- ASSERT(_desc.Stride > 0);
- return _desc.Size / _desc.Stride;
+ return _desc.Stride > 0 ? _desc.Size / _desc.Stride : 0;
}
///
diff --git a/Source/Engine/Graphics/Materials/MaterialParams.cpp b/Source/Engine/Graphics/Materials/MaterialParams.cpp
index ee5b690f0..30064222b 100644
--- a/Source/Engine/Graphics/Materials/MaterialParams.cpp
+++ b/Source/Engine/Graphics/Materials/MaterialParams.cpp
@@ -282,35 +282,35 @@ void MaterialParameter::Bind(BindMeta& meta) const
switch (_type)
{
case MaterialParameterType::Bool:
- ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)_offset + sizeof(bool));
+ ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(bool)));
*((int32*)(meta.Constants.Get() + _offset)) = _asBool;
break;
case MaterialParameterType::Integer:
- ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)_offset + sizeof(int32));
+ ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(int32)));
*((int32*)(meta.Constants.Get() + _offset)) = _asInteger;
break;
case MaterialParameterType::Float:
- ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)_offset + sizeof(float));
+ ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(float)));
*((float*)(meta.Constants.Get() + _offset)) = _asFloat;
break;
case MaterialParameterType::Vector2:
- ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)_offset + sizeof(Float2));
+ ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(Float2)));
*((Float2*)(meta.Constants.Get() + _offset)) = _asVector2;
break;
case MaterialParameterType::Vector3:
- ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)_offset + sizeof(Float3));
+ ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(Float3)));
*((Float3*)(meta.Constants.Get() + _offset)) = _asVector3;
break;
case MaterialParameterType::Vector4:
- ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)_offset + sizeof(Float4));
+ ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(Float4)));
*((Float4*)(meta.Constants.Get() + _offset)) = *(Float4*)&AsData;
break;
case MaterialParameterType::Color:
- ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)_offset + sizeof(Float4));
+ ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(Float4)));
*((Color*)(meta.Constants.Get() + _offset)) = _asColor;
break;
case MaterialParameterType::Matrix:
- ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)_offset + sizeof(Matrix));
+ ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(Matrix)));
Matrix::Transpose(*(Matrix*)&AsData, *(Matrix*)(meta.Constants.Get() + _offset));
break;
case MaterialParameterType::NormalMap:
@@ -409,44 +409,44 @@ void MaterialParameter::Bind(BindMeta& meta) const
switch (e->Value.Type.Type)
{
case VariantType::Bool:
- ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)_offset + sizeof(bool));
+ ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(bool)));
*((bool*)(meta.Constants.Get() + _offset)) = e->Value.AsBool;
break;
case VariantType::Int:
- ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)_offset + sizeof(int32));
+ ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(int32)));
*((int32*)(meta.Constants.Get() + _offset)) = e->Value.AsInt;
break;
case VariantType::Uint:
- ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)_offset + sizeof(uint32));
+ ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(uint32)));
*((uint32*)(meta.Constants.Get() + _offset)) = e->Value.AsUint;
break;
case VariantType::Float:
- ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)_offset + sizeof(float));
+ ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(float)));
*((float*)(meta.Constants.Get() + _offset)) = e->Value.AsFloat;
break;
case VariantType::Float2:
- ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)_offset + sizeof(Float2));
+ ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(Float2)));
*((Float2*)(meta.Constants.Get() + _offset)) = e->Value.AsFloat2();
break;
case VariantType::Float3:
- ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)_offset + sizeof(Float3));
+ ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(Float3)));
*((Float3*)(meta.Constants.Get() + _offset)) = e->Value.AsFloat3();
break;
case VariantType::Float4:
case VariantType::Color:
- ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)_offset + sizeof(Float4));
+ ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(Float4)));
*((Float4*)(meta.Constants.Get() + _offset)) = e->Value.AsFloat4();
break;
case VariantType::Double2:
- ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)_offset + sizeof(Float2));
+ ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(Float2)));
*((Float2*)(meta.Constants.Get() + _offset)) = (Float2)e->Value.AsDouble2();
break;
case VariantType::Double3:
- ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)_offset + sizeof(Float3));
+ ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(Float3)));
*((Float3*)(meta.Constants.Get() + _offset)) = (Float3)e->Value.AsDouble3();
break;
case VariantType::Double4:
- ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)_offset + sizeof(Float4));
+ ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(Float4)));
*((Float4*)(meta.Constants.Get() + _offset)) = (Float4)e->Value.AsDouble4();
break;
default: ;
diff --git a/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp b/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp
index f60742ccc..33e2bb454 100644
--- a/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp
+++ b/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp
@@ -85,7 +85,7 @@ void ParticleMaterialShader::Bind(BindParameters& params)
{
const StringView name(param.GetName().Get() + 9, param.GetName().Length() - 9);
const int32 offset = drawCall.Particle.Particles->Layout->FindAttributeOffset(name);
- ASSERT_LOW_LAYER(bindMeta.Constants.Get() && bindMeta.Constants.Length() >= (int32)param.GetBindOffset() + sizeof(int32));
+ ASSERT_LOW_LAYER(bindMeta.Constants.Get() && bindMeta.Constants.Length() >= (int32)(param.GetBindOffset() + sizeof(int32)));
*((int32*)(bindMeta.Constants.Get() + param.GetBindOffset())) = offset;
}
}
diff --git a/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.cpp b/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.cpp
index 5b9bb27a6..cc04498df 100644
--- a/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.cpp
+++ b/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.cpp
@@ -68,7 +68,7 @@ void VolumeParticleMaterialShader::Bind(BindParameters& params)
{
const StringView name(param.GetName().Get() + 9, param.GetName().Length() - 9);
const int32 offset = drawCall.Particle.Particles->Layout->FindAttributeOffset(name);
- ASSERT_LOW_LAYER(bindMeta.Constants.Get() && bindMeta.Constants.Length() >= (int32)param.GetBindOffset() + sizeof(int32));
+ ASSERT_LOW_LAYER(bindMeta.Constants.Get() && bindMeta.Constants.Length() >= (int32)(param.GetBindOffset() + sizeof(int32)));
*((int32*)(bindMeta.Constants.Get() + param.GetBindOffset())) = offset;
}
}
diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp
index 029012fa8..0ff1392ec 100644
--- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp
+++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp
@@ -285,7 +285,10 @@ void GPUContextDX11::SetBlendFactor(const Float4& value)
void GPUContextDX11::SetStencilRef(uint32 value)
{
if (CurrentStencilRef != value)
+ {
+ CurrentStencilRef = value;
_context->OMSetDepthStencilState(CurrentDepthStencilState, CurrentStencilRef);
+ }
}
void GPUContextDX11::ResetSR()
diff --git a/Source/Engine/Level/Actors/Spline.cpp b/Source/Engine/Level/Actors/Spline.cpp
index 01b4248ec..bc46c3da1 100644
--- a/Source/Engine/Level/Actors/Spline.cpp
+++ b/Source/Engine/Level/Actors/Spline.cpp
@@ -158,10 +158,10 @@ float Spline::GetSplineLength() const
const auto& b = Curve[i];
Vector3 prevPoint = a.Value.Translation * scale;
- const float length = Math::Abs(b.Time - a.Time);
+ const float tangentScale = Math::Abs(b.Time - a.Time) / 3.0f;
Vector3 leftTangent, rightTangent;
- AnimationUtils::GetTangent(a.Value.Translation, a.TangentOut.Translation, length, leftTangent);
- AnimationUtils::GetTangent(b.Value.Translation, b.TangentIn.Translation, length, rightTangent);
+ AnimationUtils::GetTangent(a.Value.Translation, a.TangentOut.Translation, tangentScale, leftTangent);
+ AnimationUtils::GetTangent(b.Value.Translation, b.TangentIn.Translation, tangentScale, rightTangent);
for (int32 slice = 1; slice < slices; slice++)
{
@@ -189,10 +189,10 @@ float Spline::GetSplineSegmentLength(int32 index) const
const Vector3 scale = _transform.Scale;
Vector3 prevPoint = a.Value.Translation * scale;
{
- const float length = Math::Abs(b.Time - a.Time);
+ const float tangentScale = Math::Abs(b.Time - a.Time) / 3.0f;
Vector3 leftTangent, rightTangent;
- AnimationUtils::GetTangent(a.Value.Translation, a.TangentOut.Translation, length, leftTangent);
- AnimationUtils::GetTangent(b.Value.Translation, b.TangentIn.Translation, length, rightTangent);
+ AnimationUtils::GetTangent(a.Value.Translation, a.TangentOut.Translation, tangentScale, leftTangent);
+ AnimationUtils::GetTangent(b.Value.Translation, b.TangentIn.Translation, tangentScale, rightTangent);
for (int32 slice = 1; slice < slices; slice++)
{
@@ -478,9 +478,7 @@ void Spline::GetKeyframes(MArray* data)
void Spline::SetKeyframes(MArray* data)
{
- const int32 count = MCore::Array::GetLength(data);
- Curve.GetKeyframes().Resize(count, false);
- Platform::MemoryCopy(Curve.GetKeyframes().Get(), MCore::Array::GetAddress(data), sizeof(Keyframe) * count);
+ Curve = Span((const byte*)MCore::Array::GetAddress(data), MCore::Array::GetLength(data));
UpdateSpline();
}
diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp
index 29c14f035..71b618384 100644
--- a/Source/Engine/Level/Actors/SplineModel.cpp
+++ b/Source/Engine/Level/Actors/SplineModel.cpp
@@ -184,9 +184,9 @@ void SplineModel::OnSplineUpdated()
auto& instance = _instances[segment];
const auto& start = keyframes[segment];
const auto& end = keyframes[segment + 1];
- const float length = end.Time - start.Time;
- AnimationUtils::GetTangent(start.Value, start.TangentOut, length, leftTangent);
- AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent);
+ const float tangentScale = (end.Time - start.Time) / 3.0f;
+ AnimationUtils::GetTangent(start.Value, start.TangentOut, tangentScale, leftTangent);
+ AnimationUtils::GetTangent(end.Value, end.TangentIn, tangentScale, rightTangent);
// Find maximum scale over the segment spline and collect the segment positions for bounds
segmentPoints.Clear();
@@ -256,9 +256,9 @@ void SplineModel::UpdateDeformationBuffer()
auto& instance = _instances[segment];
const auto& start = keyframes[segment];
const auto& end = keyframes[segment + 1];
- const float length = end.Time - start.Time;
- AnimationUtils::GetTangent(start.Value, start.TangentOut, length, leftTangent);
- AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent);
+ const float tangentScale = (end.Time - start.Time) / 3.0f;
+ AnimationUtils::GetTangent(start.Value, start.TangentOut, tangentScale, leftTangent);
+ AnimationUtils::GetTangent(end.Value, end.TangentIn, tangentScale, rightTangent);
for (int32 chunk = 0; chunk < chunksPerSegment; chunk++)
{
const float alpha = (chunk == chunksPerSegment - 1) ? 1.0f : ((float)chunk * chunksPerSegmentInv);
@@ -291,10 +291,10 @@ void SplineModel::UpdateDeformationBuffer()
{
const auto& start = keyframes[segments - 1];
const auto& end = keyframes[segments];
- const float length = end.Time - start.Time;
+ const float tangentScale = (end.Time - start.Time) / 3.0f;
const float alpha = 1.0f - ZeroTolerance; // Offset to prevent zero derivative at the end of the curve
- AnimationUtils::GetTangent(start.Value, start.TangentOut, length, leftTangent);
- AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent);
+ AnimationUtils::GetTangent(start.Value, start.TangentOut, tangentScale, leftTangent);
+ AnimationUtils::GetTangent(end.Value, end.TangentIn, tangentScale, rightTangent);
AnimationUtils::Bezier(start.Value, leftTangent, rightTangent, end.Value, alpha, transform);
Vector3 direction;
AnimationUtils::BezierFirstDerivative(start.Value.Translation, leftTangent.Translation, rightTangent.Translation, end.Value.Translation, alpha, direction);
diff --git a/Source/Engine/Level/Scene/SceneRendering.cpp b/Source/Engine/Level/Scene/SceneRendering.cpp
index 3dbbd113f..4e2a84661 100644
--- a/Source/Engine/Level/Scene/SceneRendering.cpp
+++ b/Source/Engine/Level/Scene/SceneRendering.cpp
@@ -90,6 +90,7 @@ void SceneRendering::Draw(RenderContextBatch& renderContextBatch, DrawCategory c
// Draw physics shapes
if (EnumHasAnyFlags(view.Flags, ViewFlags::PhysicsDebug) || view.Mode == ViewMode::PhysicsColliders)
{
+ PROFILE_CPU_NAMED("PhysicsDebug");
const PhysicsDebugCallback* physicsDebugData = PhysicsDebug.Get();
for (int32 i = 0; i < PhysicsDebug.Count(); i++)
{
@@ -100,6 +101,7 @@ void SceneRendering::Draw(RenderContextBatch& renderContextBatch, DrawCategory c
// Draw light shapes
if (EnumHasAnyFlags(view.Flags, ViewFlags::LightsDebug))
{
+ PROFILE_CPU_NAMED("LightsDebug");
const LightsDebugCallback* lightsDebugData = LightsDebug.Get();
for (int32 i = 0; i < LightsDebug.Count(); i++)
{
diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp
index 5a6b28be9..83d9eee68 100644
--- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp
+++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp
@@ -456,6 +456,7 @@ void ParticleEmitterGPUGenerator::PrepareGraph(ParticleEmitterGraphGPU* graph)
mp.AsFloat3 = param->Value.AsFloat3();
break;
case VariantType::Float4:
+ case VariantType::Quaternion:
mp.Type = MaterialParameterType::Vector4;
*(Float4*)&mp.AsData = param->Value.AsFloat4();
break;
diff --git a/Source/Engine/Physics/Colliders/SplineCollider.cpp b/Source/Engine/Physics/Colliders/SplineCollider.cpp
index 94e35aae5..226b6fa27 100644
--- a/Source/Engine/Physics/Colliders/SplineCollider.cpp
+++ b/Source/Engine/Physics/Colliders/SplineCollider.cpp
@@ -214,9 +214,9 @@ void SplineCollider::GetGeometry(CollisionShape& collision)
auto offsetIndices = segment * collisionIndices.Count();
const auto& start = keyframes[segment];
const auto& end = keyframes[segment + 1];
- const float length = end.Time - start.Time;
- AnimationUtils::GetTangent(start.Value, start.TangentOut, length, leftTangent);
- AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent);
+ const float tangentScale = (end.Time - start.Time) / 3.0f;
+ AnimationUtils::GetTangent(start.Value, start.TangentOut, tangentScale, leftTangent);
+ AnimationUtils::GetTangent(end.Value, end.TangentIn, tangentScale, rightTangent);
// Vertex buffer is deformed along the spline
auto srcVertices = collisionVertices.Get();
diff --git a/Source/Engine/Platform/Mac/MacPlatform.cpp b/Source/Engine/Platform/Mac/MacPlatform.cpp
index 84279e194..9014c7bba 100644
--- a/Source/Engine/Platform/Mac/MacPlatform.cpp
+++ b/Source/Engine/Platform/Mac/MacPlatform.cpp
@@ -468,12 +468,12 @@ int32 MacPlatform::CreateProcess(CreateProcessSettings& settings)
if (settings.WaitForEnd)
{
id outputObserver = nil;
+ id outputObserverError = nil;
if (captureStdOut)
{
- NSPipe *stdoutPipe = [NSPipe pipe];
+ NSPipe* stdoutPipe = [NSPipe pipe];
[task setStandardOutput:stdoutPipe];
-
outputObserver = [[NSNotificationCenter defaultCenter]
addObserverForName: NSFileHandleDataAvailableNotification
object: [stdoutPipe fileHandleForReading]
@@ -497,8 +497,34 @@ int32 MacPlatform::CreateProcess(CreateProcessSettings& settings)
}
}
];
-
[[stdoutPipe fileHandleForReading] waitForDataInBackgroundAndNotify];
+
+ NSPipe *stderrPipe = [NSPipe pipe];
+ [task setStandardError:stderrPipe];
+ outputObserverError = [[NSNotificationCenter defaultCenter]
+ addObserverForName: NSFileHandleDataAvailableNotification
+ object: [stderrPipe fileHandleForReading]
+ queue: nil
+ usingBlock:^(NSNotification* notification)
+ {
+ NSData* data = [stderrPipe fileHandleForReading].availableData;
+ if (data.length)
+ {
+ String line((const char*)data.bytes, data.length);
+ if (settings.SaveOutput)
+ settings.Output.Add(line.Get(), line.Length());
+ if (settings.LogOutput)
+ {
+ StringView lineView(line);
+ if (line[line.Length() - 1] == '\n')
+ lineView = StringView(line.Get(), line.Length() - 1);
+ Log::Logger::Write(LogType::Error, lineView);
+ }
+ [[stderrPipe fileHandleForReading] waitForDataInBackgroundAndNotify];
+ }
+ }
+ ];
+ [[stderrPipe fileHandleForReading] waitForDataInBackgroundAndNotify];
}
String exception;
diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp
index 877b6bb16..942cb9e0b 100644
--- a/Source/Engine/Render2D/Render2D.cpp
+++ b/Source/Engine/Render2D/Render2D.cpp
@@ -301,12 +301,12 @@ void WriteTri(const Float2& p0, const Float2& p1, const Float2& p2, const Float2
IBIndex += 3;
}
-void WriteTri(const Float2& p0, const Float2& p1, const Float2& p2, const Color& color0, const Color& color1, const Color& color2)
+FORCE_INLINE void WriteTri(const Float2& p0, const Float2& p1, const Float2& p2, const Color& color0, const Color& color1, const Color& color2)
{
WriteTri(p0, p1, p2, Float2::Zero, Float2::Zero, Float2::Zero, color0, color1, color2);
}
-void WriteTri(const Float2& p0, const Float2& p1, const Float2& p2, const Float2& uv0, const Float2& uv1, const Float2& uv2)
+FORCE_INLINE void WriteTri(const Float2& p0, const Float2& p1, const Float2& p2, const Float2& uv0, const Float2& uv1, const Float2& uv2)
{
WriteTri(p0, p1, p2, uv0, uv1, uv2, Color::Black, Color::Black, Color::Black);
}
@@ -1816,8 +1816,8 @@ void DrawLines(const Float2* points, int32 pointsCount, const Color& color1, con
// Ending cap
{
- ApplyTransform(points[0], p1t);
- ApplyTransform(points[1], p2t);
+ ApplyTransform(points[pointsCount - 2], p1t);
+ //ApplyTransform(points[pointsCount - 1], p2t);
const Float2 capDirection = thicknessHalf * Float2::Normalize(p2t - p1t);
@@ -1882,19 +1882,46 @@ void Render2D::DrawBezier(const Float2& p1, const Float2& p2, const Float2& p3,
const Float2 d3 = p4 - p3;
const float len = d1.Length() + d2.Length() + d3.Length();
const int32 segmentCount = Math::Clamp(Math::CeilToInt(len * 0.05f), 1, 100);
- const float segmentCountInv = 1.0f / segmentCount;
+ const float segmentCountInv = 1.0f / (float)segmentCount;
// Draw segmented curve
- Float2 p;
- AnimationUtils::Bezier(p1, p2, p3, p4, 0, p);
Lines2.Clear();
- Lines2.Add(p);
- for (int32 i = 1; i <= segmentCount; i++)
+ Lines2.Add(p1);
+ for (int32 i = 1; i < segmentCount; i++)
{
- const float t = i * segmentCountInv;
+ const float t = (float)i * segmentCountInv;
+ Float2 p;
AnimationUtils::Bezier(p1, p2, p3, p4, t, p);
Lines2.Add(p);
}
+ Lines2.Add(p4);
+ DrawLines(Lines2.Get(), Lines2.Count(), color, color, thickness);
+}
+
+void Render2D::DrawSpline(const Float2& p1, const Float2& p2, const Float2& p3, const Float2& p4, const Color& color, float thickness)
+{
+ RENDER2D_CHECK_RENDERING_STATE;
+
+ // Find amount of segments to use
+ const Float2 d1 = p2 - p1;
+ const Float2 d2 = p3 - p2;
+ const Float2 d3 = p4 - p3;
+ const float len = d1.Length() + d2.Length() + d3.Length();
+ const int32 segmentCount = Math::Clamp(Math::CeilToInt(len * 0.05f), 1, 100);
+ const float segmentCountInv = 1.0f / (float)segmentCount;
+
+ // Draw segmented curve
+ Lines2.Clear();
+ Lines2.Add(p1);
+ for (int32 i = 1; i < segmentCount; i++)
+ {
+ const float t = (float)i * segmentCountInv;
+ Float2 p;
+ p.X = Math::Lerp(p1.X, p4.X, t);
+ AnimationUtils::Bezier(p1.Y, p2.Y, p3.Y, p4.Y, t, p.Y);
+ Lines2.Add(p);
+ }
+ Lines2.Add(p4);
DrawLines(Lines2.Get(), Lines2.Count(), color, color, thickness);
}
@@ -1935,9 +1962,56 @@ void Render2D::DrawBlur(const Rectangle& rect, float blurStrength)
WriteRect(rect, Color::White);
}
+void Render2D::DrawTriangles(const Span& vertices, const Color& color, float thickness)
+{
+ RENDER2D_CHECK_RENDERING_STATE;
+ CHECK(vertices.Length() % 3 == 0);
+
+ Float2 points[2];
+ for (int32 i = 0; i < vertices.Length(); i += 3)
+ {
+#if 0
+ // TODO: fix this
+ DrawLines(&vertices.Get()[i], 3, color, color, thickness);
+#else
+ points[0] = vertices.Get()[i + 0];
+ points[1] = vertices.Get()[i + 1];
+ DrawLines(points, 2, color, color, thickness);
+ points[0] = vertices.Get()[i + 2];
+ DrawLines(points, 2, color, color, thickness);
+ points[1] = vertices.Get()[i + 0];
+ DrawLines(points, 2, color, color, thickness);
+#endif
+ }
+}
+
+void Render2D::DrawTriangles(const Span& vertices, const Span& colors, float thickness)
+{
+ RENDER2D_CHECK_RENDERING_STATE;
+ CHECK(vertices.Length() % 3 == 0);
+
+ Float2 points[2];
+ Color cols[2];
+ for (int32 i = 0; i < vertices.Length(); i += 3)
+ {
+ points[0] = vertices.Get()[i + 0];
+ points[1] = vertices.Get()[i + 1];
+ cols[0] = colors.Get()[i + 0];
+ cols[1] = colors.Get()[i + 1];
+ DrawLines(points, 2, cols[0], cols[1], thickness);
+ points[0] = vertices.Get()[i + 2];
+ cols[0] = colors.Get()[i + 2];
+ DrawLines(points, 2, cols[0], cols[1], thickness);
+ points[1] = vertices.Get()[i + 0];
+ cols[1] = colors.Get()[i + 0];
+ DrawLines(points, 2, cols[0], cols[1], thickness);
+ }
+}
+
void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span& vertices, const Span& uvs)
{
RENDER2D_CHECK_RENDERING_STATE;
+ CHECK(vertices.Length() % 3 == 0);
CHECK(vertices.Length() == uvs.Length());
Render2DDrawCall& drawCall = DrawCalls.AddOne();
@@ -1952,14 +2026,24 @@ void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span& vertices
void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span& vertices, const Span& uvs, const Color& color)
{
- Color colors[3] = { (Color)color, (Color)color, (Color)color };
- Span spancolor(colors, 3);
- DrawTexturedTriangles(t, vertices, uvs, spancolor);
+ RENDER2D_CHECK_RENDERING_STATE;
+ CHECK(vertices.Length() % 3 == 0);
+ CHECK(vertices.Length() == uvs.Length());
+
+ Render2DDrawCall& drawCall = DrawCalls.AddOne();
+ drawCall.Type = DrawCallType::FillTexture;
+ drawCall.StartIB = IBIndex;
+ drawCall.CountIB = vertices.Length();
+ drawCall.AsTexture.Ptr = t;
+
+ for (int32 i = 0; i < vertices.Length(); i += 3)
+ WriteTri(vertices[i], vertices[i + 1], vertices[i + 2], uvs[i], uvs[i + 1], uvs[i + 2], color, color, color);
}
void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span& vertices, const Span& uvs, const Span& colors)
{
RENDER2D_CHECK_RENDERING_STATE;
+ CHECK(vertices.Length() % 3 == 0);
CHECK(vertices.Length() == uvs.Length());
CHECK(vertices.Length() == colors.Length());
@@ -1994,6 +2078,19 @@ void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span& indices,
}
}
+void Render2D::FillTriangles(const Span& vertices, const Color& color)
+{
+ RENDER2D_CHECK_RENDERING_STATE;
+
+ Render2DDrawCall& drawCall = DrawCalls.AddOne();
+ drawCall.Type = NeedAlphaWithTint(color) ? DrawCallType::FillRect : DrawCallType::FillRectNoAlpha;
+ drawCall.StartIB = IBIndex;
+ drawCall.CountIB = vertices.Length();
+
+ for (int32 i = 0; i < vertices.Length(); i += 3)
+ WriteTri(vertices[i], vertices[i + 1], vertices[i + 2], color, color, color);
+}
+
void Render2D::FillTriangles(const Span& vertices, const Span& colors, bool useAlpha)
{
CHECK(vertices.Length() == colors.Length());
diff --git a/Source/Engine/Render2D/Render2D.h b/Source/Engine/Render2D/Render2D.h
index 5eda59e7e..886c9e664 100644
--- a/Source/Engine/Render2D/Render2D.h
+++ b/Source/Engine/Render2D/Render2D.h
@@ -389,6 +389,17 @@ public:
/// The line thickness.
API_FUNCTION() static void DrawBezier(const Float2& p1, const Float2& p2, const Float2& p3, const Float2& p4, const Color& color, float thickness = 1.0f);
+ ///
+ /// Draws a spline curve (Bezier but X axis represents uniform time).
+ ///
+ /// The start point.
+ /// The first control point.
+ /// The second control point.
+ /// The end point.
+ /// The line color
+ /// The line thickness.
+ API_FUNCTION() static void DrawSpline(const Float2& p1, const Float2& p2, const Float2& p3, const Float2& p4, const Color& color, float thickness = 1.0f);
+
///
/// Draws the GUI material.
///
@@ -404,6 +415,22 @@ public:
/// The blur strength defines how blurry the background is. Larger numbers increase blur, resulting in a larger runtime cost on the GPU.
API_FUNCTION() static void DrawBlur(const Rectangle& rect, float blurStrength);
+ ///
+ /// Draws vertices array.
+ ///
+ /// The vertices array.
+ /// The color.
+ /// The line thickness.
+ API_FUNCTION() static void DrawTriangles(const Span& vertices, const Color& color, float thickness = 1.0f);
+
+ ///
+ /// Draws vertices array.
+ ///
+ /// The vertices array.
+ /// The colors array.
+ /// The line thickness.
+ API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& colors, float thickness = 1.0f);
+
///
/// Draws vertices array.
///
@@ -440,13 +467,20 @@ public:
/// The colors array.
API_FUNCTION() static void DrawTexturedTriangles(GPUTexture* t, const Span& indices, const Span& vertices, const Span& uvs, const Span& colors);
+ ///
+ /// Draws vertices array.
+ ///
+ /// The vertices array.
+ /// The color.
+ API_FUNCTION() static void FillTriangles(const Span& vertices, const Color& color);
+
///
/// Draws vertices array.
///
/// The vertices array.
/// The colors array.
/// If true alpha blending will be enabled.
- API_FUNCTION() static void FillTriangles(const Span& vertices, const Span& colors, bool useAlpha);
+ API_FUNCTION() static void FillTriangles(const Span& vertices, const Span& colors, bool useAlpha = true);
///
/// Fills a triangular area.
diff --git a/Source/Engine/Renderer/PostProcessingPass.cpp b/Source/Engine/Renderer/PostProcessingPass.cpp
index 8ff789223..99831b430 100644
--- a/Source/Engine/Renderer/PostProcessingPass.cpp
+++ b/Source/Engine/Renderer/PostProcessingPass.cpp
@@ -271,7 +271,7 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input,
// Calculate star texture rotation matrix
Float3 camX = renderContext.View.View.GetRight();
- Float3 camZ = renderContext.View.View.GetForward();
+ Float3 camZ = renderContext.View.View.GetBackward();
float camRot = Float3::Dot(camX, Float3::Forward) + Float3::Dot(camZ, Float3::Up);
float camRotCos = Math::Cos(camRot) * 0.8f;
float camRotSin = Math::Sin(camRot) * 0.8f;
diff --git a/Source/Engine/Scripting/Attributes/Editor/ButtonAttribute.cs b/Source/Engine/Scripting/Attributes/Editor/ButtonAttribute.cs
new file mode 100644
index 000000000..75ee9f5f0
--- /dev/null
+++ b/Source/Engine/Scripting/Attributes/Editor/ButtonAttribute.cs
@@ -0,0 +1,42 @@
+// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+
+using System;
+
+namespace FlaxEngine
+{
+ ///
+ /// Displays the method in the properties panel where user can click and invoke this method.
+ ///
+ /// Supported on both static and member methods that are parameterless.
+ [AttributeUsage(AttributeTargets.Method)]
+ public sealed class ButtonAttribute : Attribute
+ {
+ ///
+ /// The button text. Empty value will use method name (auto-formatted).
+ ///
+ public string Text;
+
+ ///
+ /// The button tooltip text. Empty value will use method documentation.
+ ///
+ public string Tooltip;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ButtonAttribute()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The button text.
+ /// The button tooltip.
+ public ButtonAttribute(string text, string tooltip = null)
+ {
+ Text = text;
+ Tooltip = tooltip;
+ }
+ }
+}
diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp
index 96dc35d49..5ff02cf36 100644
--- a/Source/Engine/Terrain/TerrainPatch.cpp
+++ b/Source/Engine/Terrain/TerrainPatch.cpp
@@ -33,6 +33,11 @@
#if USE_EDITOR
#include "Engine/Debug/DebugDraw.h"
#endif
+#if TERRAIN_USE_PHYSICS_DEBUG
+#include "Engine/Graphics/GPUDevice.h"
+#include "Engine/Graphics/DynamicBuffer.h"
+#include "Engine/Engine/Units.h"
+#endif
#include "Engine/Content/Content.h"
#include "Engine/Content/Assets/RawDataAsset.h"
@@ -94,7 +99,8 @@ void TerrainPatch::Init(Terrain* terrain, int16 x, int16 z)
}
#endif
#if TERRAIN_USE_PHYSICS_DEBUG
- _debugLines.Resize(0);
+ SAFE_DELETE(_debugLines);
+ _debugLinesDirty = true;
#endif
#if USE_EDITOR
_collisionTriangles.Resize(0);
@@ -1822,7 +1828,7 @@ bool TerrainPatch::UpdateHeightData(TerrainDataUpdateInfo& info, const Int2& mod
// Invalidate cache
#if TERRAIN_USE_PHYSICS_DEBUG
- _debugLines.Resize(0);
+ _debugLinesDirty = true;
#endif
#if USE_EDITOR
_collisionTriangles.Resize(0);
@@ -1940,7 +1946,7 @@ bool TerrainPatch::UpdateCollision()
{
// Invalidate cache
#if TERRAIN_USE_PHYSICS_DEBUG
- _debugLines.Resize(0);
+ _debugLinesDirty = true;
#endif
#if USE_EDITOR
_collisionTriangles.Resize(0);
@@ -2082,7 +2088,7 @@ void TerrainPatch::UpdatePostManualDeserialization()
{
// Invalidate cache
#if TERRAIN_USE_PHYSICS_DEBUG
- _debugLines.Resize(0);
+ _debugLinesDirty = true;
#endif
#if USE_EDITOR
_collisionTriangles.Resize(0);
@@ -2211,7 +2217,8 @@ void TerrainPatch::DestroyCollision()
_physicsShape = nullptr;
_physicsHeightField = nullptr;
#if TERRAIN_USE_PHYSICS_DEBUG
- _debugLines.Resize(0);
+ _debugLinesDirty = true;
+ SAFE_DELETE(_debugLines);
#endif
#if USE_EDITOR
_collisionTriangles.Resize(0);
@@ -2224,15 +2231,26 @@ void TerrainPatch::DestroyCollision()
void TerrainPatch::CacheDebugLines()
{
PROFILE_CPU();
- ASSERT(_debugLines.IsEmpty() && _physicsHeightField);
+ ASSERT(_physicsHeightField);
+ _debugLinesDirty = false;
+ if (!_debugLines)
+ _debugLines = GPUDevice::Instance->CreateBuffer(TEXT("Terrain.DebugLines"));
int32 rows, cols;
PhysicsBackend::GetHeightFieldSize(_physicsHeightField, rows, cols);
+ const int32 count = (rows - 1) * (cols - 1) * 6 + (cols + rows - 2) * 2;
+ typedef DebugDraw::Vertex Vertex;
+ if (_debugLines->GetElementsCount() != count)
+ {
+ if (_debugLines->Init(GPUBufferDescription::Vertex(sizeof(Vertex), count)))
+ return;
+ }
+ Array debugLines;
+ debugLines.Resize(count);
+ auto* data = debugLines.Get();
+ const Color32 color(Color::GreenYellow * 0.8f);
- _debugLines.Resize((rows - 1) * (cols - 1) * 6 + (cols + rows - 2) * 2);
- Vector3* data = _debugLines.Get();
-
-#define GET_VERTEX(x, y) const Vector3 v##x##y((float)(row + (x)), PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, row + (x), col + (y)) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)(col + (y)))
+#define GET_VERTEX(x, y) const Vertex v##x##y = { Float3((float)(row + (x)), PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, row + (x), col + (y)) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)(col + (y))), color }
for (int32 row = 0; row < rows - 1; row++)
{
@@ -2243,7 +2261,7 @@ void TerrainPatch::CacheDebugLines()
if (sample.MaterialIndex0 == (uint8)PhysicsBackend::HeightFieldMaterial::Hole)
{
for (int32 i = 0; i < 6; i++)
- *data++ = Vector3::Zero;
+ *data++ = Vertex { Float3::Zero, Color32::Black };
continue;
}
@@ -2284,18 +2302,16 @@ void TerrainPatch::CacheDebugLines()
}
#undef GET_VERTEX
+
+ _debugLines->SetData(debugLines.Get(), _debugLines->GetSize());
}
void TerrainPatch::DrawPhysicsDebug(RenderView& view)
{
+#if COMPILE_WITH_DEBUG_DRAW
const BoundingBox bounds(_bounds.Minimum - view.Origin, _bounds.Maximum - view.Origin);
if (!_physicsShape || !view.CullingFrustum.Intersects(bounds))
return;
-
- const Transform terrainTransform = _terrain->_transform;
- const Transform localTransform(Vector3(0, _yOffset, 0), Quaternion::Identity, Vector3(_collisionScaleXZ, _yHeight, _collisionScaleXZ));
- const Matrix world = localTransform.GetWorld() * terrainTransform.GetWorld();
-
if (view.Mode == ViewMode::PhysicsColliders)
{
DEBUG_DRAW_TRIANGLES(GetCollisionTriangles(), Color::DarkOliveGreen, 0, true);
@@ -2304,13 +2320,17 @@ void TerrainPatch::DrawPhysicsDebug(RenderView& view)
{
BoundingSphere sphere;
BoundingSphere::FromBox(bounds, sphere);
- if (Vector3::Distance(sphere.Center, view.Position) - sphere.Radius < 4000.0f)
+ if (Vector3::Distance(sphere.Center, view.Position) - sphere.Radius < METERS_TO_UNITS(500))
{
- if (_debugLines.IsEmpty())
+ if (!_debugLines || _debugLinesDirty)
CacheDebugLines();
- DEBUG_DRAW_LINES(_debugLines, world, Color::GreenYellow * 0.8f, 0, true);
+ const Transform terrainTransform = _terrain->_transform;
+ const Transform localTransform(Vector3(0, _yOffset, 0), Quaternion::Identity, Vector3(_collisionScaleXZ, _yHeight, _collisionScaleXZ));
+ const Matrix world = localTransform.GetWorld() * terrainTransform.GetWorld();
+ DebugDraw::DrawLines(_debugLines, world);
}
}
+#endif
}
#endif
diff --git a/Source/Engine/Terrain/TerrainPatch.h b/Source/Engine/Terrain/TerrainPatch.h
index 05a7693fe..5eedf6e6d 100644
--- a/Source/Engine/Terrain/TerrainPatch.h
+++ b/Source/Engine/Terrain/TerrainPatch.h
@@ -40,12 +40,13 @@ private:
Array _cachedSplatMap[TERRAIN_MAX_SPLATMAPS_COUNT];
bool _wasHeightModified;
bool _wasSplatmapModified[TERRAIN_MAX_SPLATMAPS_COUNT];
+#if TERRAIN_USE_PHYSICS_DEBUG
+ bool _debugLinesDirty = true;
+ class GPUBuffer* _debugLines = nullptr;
+#endif
TextureBase::InitData* _dataHeightmap = nullptr;
TextureBase::InitData* _dataSplatmap[TERRAIN_MAX_SPLATMAPS_COUNT] = {};
#endif
-#if TERRAIN_USE_PHYSICS_DEBUG
- Array _debugLines; // TODO: large-worlds
-#endif
#if USE_EDITOR
Array _collisionTriangles; // TODO: large-worlds
#endif
diff --git a/Source/Engine/UI/GUI/Panels/Panel.cs b/Source/Engine/UI/GUI/Panels/Panel.cs
index 6549f94da..8923ff6df 100644
--- a/Source/Engine/UI/GUI/Panels/Panel.cs
+++ b/Source/Engine/UI/GUI/Panels/Panel.cs
@@ -263,16 +263,21 @@ namespace FlaxEngine.GUI
///
protected override void SetViewOffset(ref Float2 value)
{
+ // Update scroll bars but with locked layout
bool wasLocked = _isLayoutLocked;
+ int layoutUpdateLock = _layoutUpdateLock;
_isLayoutLocked = true;
-
+ _layoutUpdateLock = 999;
if (HScrollBar != null)
- HScrollBar.Value = -value.X;
+ HScrollBar.TargetValue = -value.X;
if (VScrollBar != null)
- VScrollBar.Value = -value.Y;
-
+ VScrollBar.TargetValue = -value.Y;
+ _layoutUpdateLock = layoutUpdateLock;
_isLayoutLocked = wasLocked;
+
base.SetViewOffset(ref value);
+
+ PerformLayout();
}
///
@@ -553,7 +558,12 @@ namespace FlaxEngine.GUI
if (vScrollEnabled)
{
- VScrollBar.SetScrollRange(scrollBounds.Top, Mathf.Max(Mathf.Max(0, scrollBounds.Top), scrollBounds.Height - height));
+ float max;
+ if (scrollBounds.Top < 0)
+ max = Mathf.Max(scrollBounds.Bottom, scrollBounds.Top + scrollBounds.Height - height);
+ else
+ max = Mathf.Max(scrollBounds.Top, scrollBounds.Height - height);
+ VScrollBar.SetScrollRange(scrollBounds.Top, max);
}
VScrollBar.Bounds = new Rectangle(Width - _scrollBarsSize, 0, _scrollBarsSize, Height);
}
@@ -580,7 +590,12 @@ namespace FlaxEngine.GUI
if (hScrollEnabled)
{
- HScrollBar.SetScrollRange(scrollBounds.Left, Mathf.Max(Mathf.Max(0, scrollBounds.Left), scrollBounds.Width - width));
+ float max;
+ if (scrollBounds.Left < 0)
+ max = Mathf.Max(scrollBounds.Right, scrollBounds.Left + scrollBounds.Width - width);
+ else
+ max = Mathf.Max(scrollBounds.Left, scrollBounds.Width - width);
+ HScrollBar.SetScrollRange(scrollBounds.Left, max);
}
HScrollBar.Bounds = new Rectangle(0, Height - _scrollBarsSize, Width - (VScrollBar != null && VScrollBar.Visible ? VScrollBar.Width : 0), _scrollBarsSize);
}
@@ -596,17 +611,29 @@ namespace FlaxEngine.GUI
// Calculate scroll area bounds
var totalMin = Float2.Zero;
var totalMax = Float2.Zero;
+ var hasTotal = false;
for (int i = 0; i < _children.Count; i++)
{
var c = _children[i];
if (c.Visible && c.IsScrollable)
{
- var min = Float2.Zero;
- var max = c.Size;
- Matrix3x3.Transform2D(ref min, ref c._cachedTransform, out min);
- Matrix3x3.Transform2D(ref max, ref c._cachedTransform, out max);
- Float2.Min(ref min, ref totalMin, out totalMin);
- Float2.Max(ref max, ref totalMax, out totalMax);
+ var upperLeft = Float2.Zero;
+ var bottomRight = c.Size;
+ Matrix3x3.Transform2D(ref upperLeft, ref c._cachedTransform, out upperLeft);
+ Matrix3x3.Transform2D(ref bottomRight, ref c._cachedTransform, out bottomRight);
+ Float2.Min(ref upperLeft, ref bottomRight, out var min);
+ Float2.Max(ref upperLeft, ref bottomRight, out var max);
+ if (hasTotal)
+ {
+ Float2.Min(ref min, ref totalMin, out totalMin);
+ Float2.Max(ref max, ref totalMax, out totalMax);
+ }
+ else
+ {
+ totalMin = min;
+ totalMax = max;
+ hasTotal = true;
+ }
}
}
diff --git a/Source/Engine/UI/GUI/Panels/ScrollBar.cs b/Source/Engine/UI/GUI/Panels/ScrollBar.cs
index 712c3e606..a0d961fc4 100644
--- a/Source/Engine/UI/GUI/Panels/ScrollBar.cs
+++ b/Source/Engine/UI/GUI/Panels/ScrollBar.cs
@@ -348,6 +348,8 @@ namespace FlaxEngine.GUI
// https://easings.net/#easeOutSine
var easedProgress = Mathf.Sin((progress * Mathf.Pi) / 2);
+ if (progress >= 1.0f)
+ easedProgress = 1.0f;
value = Mathf.Lerp(_startValue, _targetValue, easedProgress);
_scrollAnimationProgress = progress;
diff --git a/Source/Engine/Utilities/Delaunay2D.h b/Source/Engine/Utilities/Delaunay2D.h
index 766824bb9..3f1e25a7d 100644
--- a/Source/Engine/Utilities/Delaunay2D.h
+++ b/Source/Engine/Utilities/Delaunay2D.h
@@ -7,10 +7,11 @@
#include "Engine/Core/Collections/Array.h"
///
-/// Helper class with Delaunay triangulation algorithm implementation,
+/// Helper class with Delaunay triangulation algorithm implementation (2D space).
///
-class Delaunay2D
+API_CLASS(Internal, Static, Namespace="FlaxEngine.Utilities") class Delaunay2D
{
+ DECLARE_SCRIPTING_TYPE_MINIMAL(Delaunay2D);
public:
struct Triangle
{
@@ -31,14 +32,35 @@ public:
}
};
- template
- static void Triangulate(const TVertexArray& vertices, TTrianglesArray& triangles)
+ ///
+ /// Triangulates input vertices array into the list of triangle vertices.
+ ///
+ /// Input list of vertices.
+ /// Result list of triangles. Each triangle is made out of sequence of 3 vertices. Empty if no valid triangle built.
+ API_FUNCTION() static Array Triangulate(const Array& vertices)
+ {
+ Array triangles;
+ Triangulate(vertices, triangles);
+ Array result;
+ result.Resize(triangles.Count() * 3);
+ int32 c = 0;
+ for (const Triangle& t : triangles)
+ {
+ result.Get()[c++] = vertices[t.Indices[0]];
+ result.Get()[c++] = vertices[t.Indices[1]];
+ result.Get()[c++] = vertices[t.Indices[2]];
+ }
+ return result;
+ }
+
+ template
+ static void Triangulate(const Array& vertices, TrianglesArray& triangles)
{
// Skip if no change to produce any triangles
- if (vertices.Count() < 3)
+ if (vertices.Count() < 3)
return;
- TVertexArray points = vertices;
+ Array points = vertices;
Array polygon;
Rectangle rect;
@@ -142,8 +164,7 @@ private:
}
};
- template
- static bool CircumCircleContains(const TVertexArray& vertices, const Triangle& triangle, int vertexIndex)
+ static bool CircumCircleContains(const Array& vertices, const Triangle& triangle, int32 vertexIndex)
{
Float2 p1 = vertices[triangle.Indices[0]];
Float2 p2 = vertices[triangle.Indices[1]];
@@ -157,25 +178,20 @@ private:
(ab * (p3.Y - p2.Y) + cd * (p1.Y - p3.Y) + ef * (p2.Y - p1.Y)) / (p1.X * (p3.Y - p2.Y) + p2.X * (p1.Y - p3.Y) + p3.X * (p2.Y - p1.Y)),
(ab * (p3.X - p2.X) + cd * (p1.X - p3.X) + ef * (p2.X - p1.X)) / (p1.Y * (p3.X - p2.X) + p2.Y * (p1.X - p3.X) + p3.Y * (p2.X - p1.X)));
- circum *= 0.5;
+ circum *= 0.5f;
float r = Float2::DistanceSquared(p1, circum);
float d = Float2::DistanceSquared(vertices[vertexIndex], circum);
return d <= r;
}
- template
- static bool EdgeCompare(const TVertexArray& vertices, const Edge& a, const Edge& b)
+ static bool EdgeCompare(const Array& vertices, const Edge& a, const Edge& b)
{
- if (Float2::Distance(vertices[a.Indices[0]], vertices[b.Indices[0]]) < ZeroTolerance && Vector2::Distance(vertices[a.Indices[1]], vertices[b.Indices[1]]) < ZeroTolerance)
- {
+ if (Float2::Distance(vertices[a.Indices[0]], vertices[b.Indices[0]]) < ZeroTolerance &&
+ Float2::Distance(vertices[a.Indices[1]], vertices[b.Indices[1]]) < ZeroTolerance)
return true;
- }
-
- if (Float2::Distance(vertices[a.Indices[0]], vertices[b.Indices[1]]) < ZeroTolerance && Vector2::Distance(vertices[a.Indices[1]], vertices[b.Indices[0]]) < ZeroTolerance)
- {
+ if (Float2::Distance(vertices[a.Indices[0]], vertices[b.Indices[1]]) < ZeroTolerance &&
+ Float2::Distance(vertices[a.Indices[1]], vertices[b.Indices[0]]) < ZeroTolerance)
return true;
- }
-
return false;
}
};
diff --git a/Source/Engine/Visject/ShaderGraph.cpp b/Source/Engine/Visject/ShaderGraph.cpp
index 718e9cba9..9bdc5709d 100644
--- a/Source/Engine/Visject/ShaderGraph.cpp
+++ b/Source/Engine/Visject/ShaderGraph.cpp
@@ -92,9 +92,13 @@ void ShaderGenerator::ProcessGroupConstants(Box* box, Node* node, Value& value)
value = Value(cv.W);
break;
}
+ // Rotation
case 8:
{
- value = Value::Zero;
+ const float pitch = (float)node->Values[0];
+ const float yaw = (float)node->Values[1];
+ const float roll = (float)node->Values[2];
+ value = Value(Quaternion::Euler(pitch, yaw, roll));
break;
}
// PI
diff --git a/Source/Engine/Visject/ShaderGraphValue.cpp b/Source/Engine/Visject/ShaderGraphValue.cpp
index 2aeebbd2b..cdff3d1e6 100644
--- a/Source/Engine/Visject/ShaderGraphValue.cpp
+++ b/Source/Engine/Visject/ShaderGraphValue.cpp
@@ -460,6 +460,7 @@ ShaderGraphValue ShaderGraphValue::Cast(const ShaderGraphValue& v, VariantType::
case VariantType::Types::Float4:
case VariantType::Types::Double4:
case VariantType::Types::Color:
+ case VariantType::Types::Quaternion:
switch (v.Type)
{
case VariantType::Types::Bool:
@@ -485,16 +486,6 @@ ShaderGraphValue ShaderGraphValue::Cast(const ShaderGraphValue& v, VariantType::
break;
}
break;
- case VariantType::Types::Quaternion:
- switch (v.Type)
- {
- case VariantType::Types::Color:
- case VariantType::Types::Float4:
- case VariantType::Types::Double4:
- format = TEXT("{}");
- break;
- }
- break;
}
if (format == nullptr)
{
diff --git a/Source/Engine/Visject/VisjectGraph.cpp b/Source/Engine/Visject/VisjectGraph.cpp
index 199747b67..7e02f0aa5 100644
--- a/Source/Engine/Visject/VisjectGraph.cpp
+++ b/Source/Engine/Visject/VisjectGraph.cpp
@@ -79,6 +79,7 @@ void VisjectExecutor::ProcessGroupConstants(Box* box, Node* node, Value& value)
value = cv.W;
break;
}
+ // Rotation
case 8:
{
const float pitch = (float)node->Values[0];
diff --git a/Source/ThirdParty/WinPixEventRuntime/LICENSE.txt b/Source/ThirdParty/WinPixEventRuntime/LICENSE.txt
new file mode 100644
index 000000000..63447fd8b
--- /dev/null
+++ b/Source/ThirdParty/WinPixEventRuntime/LICENSE.txt
@@ -0,0 +1,21 @@
+Copyright (c) Microsoft Corporation.
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/Source/ThirdParty/WinPixEventRuntime/WinPixEventRuntime.Build.cs b/Source/ThirdParty/WinPixEventRuntime/WinPixEventRuntime.Build.cs
new file mode 100644
index 000000000..3b86a55b6
--- /dev/null
+++ b/Source/ThirdParty/WinPixEventRuntime/WinPixEventRuntime.Build.cs
@@ -0,0 +1,21 @@
+// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+
+using Flax.Build;
+
+///
+/// https://github.com/microsoft/PixEvents
+///
+public class WinPixEventRuntime : HeaderOnlyModule
+{
+ ///
+ public override void Init()
+ {
+ base.Init();
+
+ LicenseType = LicenseTypes.MIT;
+ LicenseFilePath = "LICENSE.txt";
+
+ // Merge third-party modules into engine binary
+ BinaryModuleName = "FlaxEngine";
+ }
+}
diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs
index 551417ebf..5d03d0489 100644
--- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs
+++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs
@@ -20,12 +20,12 @@ namespace Flax.Build.Bindings
private static readonly Dictionary CSharpAdditionalCodeCache = new Dictionary();
#if USE_NETCORE
private static readonly TypeInfo CSharpEventBindReturn = new TypeInfo("void");
- private static readonly List CSharpEventBindParams = new List() { new FunctionInfo.ParameterInfo() { Name = "bind", Type = new TypeInfo("bool") } };
+ private static readonly List CSharpEventBindParams = new List { new FunctionInfo.ParameterInfo { Name = "bind", Type = new TypeInfo("bool") } };
#endif
public static event Action GenerateCSharpTypeInternals;
- internal static readonly Dictionary CSharpNativeToManagedBasicTypes = new Dictionary()
+ internal static readonly Dictionary CSharpNativeToManagedBasicTypes = new Dictionary
{
// Language types
{ "bool", "bool" },
@@ -46,7 +46,7 @@ namespace Flax.Build.Bindings
{ "double", "double" },
};
- internal static readonly Dictionary CSharpNativeToManagedDefault = new Dictionary()
+ internal static readonly Dictionary CSharpNativeToManagedDefault = new Dictionary
{
// Engine types
{ "String", "string" },
@@ -632,11 +632,11 @@ namespace Flax.Build.Bindings
else if (returnValueType == "object[]")
returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemObjectArrayMarshaller))";
else if (functionInfo.ReturnType.Type == "Array" || functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "DataContainer" || functionInfo.ReturnType.Type == "BytesContainer" || returnNativeType == "Array")
- returnMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = nameof(__returnCount))";
+ returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = nameof(__returnCount))";
else if (functionInfo.ReturnType.Type == "Dictionary")
- returnMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.DictionaryMarshaller<,>), ConstantElementCount = 0)";
+ returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.DictionaryMarshaller<,>), ConstantElementCount = 0)";
else if (returnValueType == "byte[]")
- returnMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = \"__returnCount\")";
+ returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = \"__returnCount\")";
else if (returnValueType == "bool[]")
{
// Boolean arrays does not support custom marshalling for some unknown reason
@@ -691,11 +691,15 @@ namespace Flax.Build.Bindings
parameterMarshalType += ", In"; // The usage of 'LibraryImportAttribute' does not follow recommendations. It is recommended to use explicit '[In]' and '[Out]' attributes on array parameters.
}
else if (parameterInfo.Type.Type == "Dictionary")
- parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.DictionaryMarshaller<,>), ConstantElementCount = 0)";
+ parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.DictionaryMarshaller<,>), ConstantElementCount = 0)";
else if (nativeType == "bool")
parameterMarshalType = "MarshalAs(UnmanagedType.U1)";
else if (nativeType == "char")
parameterMarshalType = "MarshalAs(UnmanagedType.I2)";
+ else if (nativeType.EndsWith("[]"))
+ {
+ parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>))";
+ }
if (!string.IsNullOrEmpty(parameterMarshalType))
contents.Append($"[{parameterMarshalType}] ");
@@ -730,7 +734,7 @@ namespace Flax.Build.Bindings
if (parameterInfo.Type.Type == "Array" || parameterInfo.Type.Type == "Span" || parameterInfo.Type.Type == "DataContainer" || parameterInfo.Type.Type == "BytesContainer")
parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = \"{parameterInfo.Name}Count\")";
else if (parameterInfo.Type.Type == "Dictionary")
- parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.DictionaryMarshaller<,>), ConstantElementCount = 0)";
+ parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.DictionaryMarshaller<,>), ConstantElementCount = 0)";
}
if (nativeType == "System.Type")
parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemTypeMarshaller))";
diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
index 86bd3dcd4..c621adcd3 100644
--- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
+++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
@@ -980,6 +980,10 @@ namespace Flax.Build.Bindings
UseReferenceForResult = UsePassByReference(buildData, functionInfo.ReturnType, caller),
CustomParameters = new List(),
};
+ var returnType = functionInfo.ReturnType;
+ var returnApiType = FindApiTypeInfo(buildData, returnType, caller);
+ if (returnApiType != null && returnApiType.MarshalAs != null)
+ returnType = returnApiType.MarshalAs;
bool returnTypeIsContainer = false;
var returnValueConvert = GenerateCppWrapperNativeToManaged(buildData, functionInfo.ReturnType, caller, out var returnValueType, functionInfo);
@@ -999,7 +1003,7 @@ namespace Flax.Build.Bindings
});
}
#if USE_NETCORE
- else if (functionInfo.ReturnType.Type == "Array" || functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "DataContainer" || functionInfo.ReturnType.Type == "BitArray" || functionInfo.ReturnType.Type == "BytesContainer")
+ else if (returnType.Type == "Array" || returnType.Type == "Span" || returnType.Type == "DataContainer" || returnType.Type == "BitArray" || returnType.Type == "BytesContainer")
{
returnTypeIsContainer = true;
functionInfo.Glue.CustomParameters.Add(new FunctionInfo.ParameterInfo
@@ -1173,7 +1177,7 @@ namespace Flax.Build.Bindings
{
callBegin += "*__resultAsRef = ";
}
- else if (!functionInfo.ReturnType.IsVoid)
+ else if (!returnType.IsVoid)
{
if (useInlinedReturn)
callBegin += "return ";
@@ -1186,7 +1190,7 @@ namespace Flax.Build.Bindings
if (returnTypeIsContainer)
{
callReturnCount = indent;
- if (functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "BytesContainer")
+ if (returnType.Type == "Span" || returnType.Type == "BytesContainer")
callReturnCount += "*__returnCount = {0}.Length();";
else
callReturnCount += "*__returnCount = {0}.Count();";
@@ -1278,7 +1282,8 @@ namespace Flax.Build.Bindings
#if USE_NETCORE
if (!string.IsNullOrEmpty(callReturnCount))
{
- contents.Append(indent).Append("const auto& __callTemp = ").Append(string.Format(callFormat, call, callParams)).Append(";").AppendLine();
+ var tempVar = returnTypeIsContainer && returnType != functionInfo.ReturnType ? $"{returnType} __callTemp = " : "const auto& __callTemp = ";
+ contents.Append(indent).Append(tempVar).Append(string.Format(callFormat, call, callParams)).Append(";").AppendLine();
call = "__callTemp";
contents.Append(string.Format(callReturnCount, call));
contents.AppendLine();
@@ -1357,7 +1362,7 @@ namespace Flax.Build.Bindings
}
}
- if (!useInlinedReturn && !functionInfo.Glue.UseReferenceForResult && !functionInfo.ReturnType.IsVoid)
+ if (!useInlinedReturn && !functionInfo.Glue.UseReferenceForResult && !returnType.IsVoid)
{
contents.Append(indent).Append("return __result;").AppendLine();
}
@@ -1823,6 +1828,10 @@ namespace Flax.Build.Bindings
// Add includes to properly compile bindings (eg. SoftObjectReference)
GenerateCppAddFileReference(buildData, caller, typeInfo, apiTypeInfo);
+ // TODO: find a better way to reference other include files for types that have separate serialization header
+ if (typeInfo.Type.EndsWith("Curve") && typeInfo.GenericArgs != null)
+ CppIncludeFilesList.Add("Engine/Animations/CurveSerialization.h");
+
return false;
}
@@ -3132,13 +3141,12 @@ namespace Flax.Build.Bindings
// Includes
header.Clear();
CppReferencesFiles.Remove(null);
- CppIncludeFilesList.Clear();
foreach (var fileInfo in CppReferencesFiles)
CppIncludeFilesList.Add(fileInfo.Name);
CppIncludeFilesList.AddRange(CppIncludeFiles);
CppIncludeFilesList.Sort();
if (CppIncludeFilesList.Remove("Engine/Serialization/Serialization.h"))
- CppIncludeFilesList.Add("Engine/Serialization/Serialization.h");
+ CppIncludeFilesList.Add("Engine/Serialization/Serialization.h"); // Include serialization header as the last one to properly handle specialization of custom types serialization
foreach (var path in CppIncludeFilesList)
header.AppendFormat("#include \"{0}\"", path).AppendLine();
contents.Insert(headerPos, header.ToString());
diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs
index d85f1f342..a6ffe9a98 100644
--- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs
+++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs
@@ -188,6 +188,13 @@ namespace Flax.Build.Bindings
token = context.Tokenizer.NextToken();
if (token.Type == TokenType.Multiply)
tag.Value += token.Value;
+ else if (token.Type == TokenType.LeftAngleBracket)
+ {
+ context.Tokenizer.SkipUntil(TokenType.RightAngleBracket, out var s);
+ tag.Value += '<';
+ tag.Value += s;
+ tag.Value += '>';
+ }
else
context.Tokenizer.PreviousToken();
parameters.Add(tag);
@@ -273,7 +280,6 @@ namespace Flax.Build.Bindings
type.GenericArgs.Add(argType);
token = context.Tokenizer.NextToken();
} while (token.Type != TokenType.RightAngleBracket);
-
token = context.Tokenizer.NextToken();
}
@@ -405,7 +411,7 @@ namespace Flax.Build.Bindings
currentParam.Attributes += ", Optional";
currentParam.Name = $"namelessArg{parameters.Count}";
}
-
+
if (currentParam.IsOut && (currentParam.Type.IsPtr || currentParam.Type.IsRef) && currentParam.Type.Type.EndsWith("*"))
{
// Pointer to value passed as output pointer
@@ -545,15 +551,19 @@ namespace Flax.Build.Bindings
}
if (token.Type == TokenType.LeftAngleBracket)
{
- var genericType = context.Tokenizer.ExpectToken(TokenType.Identifier);
- token = context.Tokenizer.ExpectToken(TokenType.RightAngleBracket);
- inheritType.GenericArgs = new List
+ inheritType.GenericArgs = new List();
+ while (true)
{
- new TypeInfo
- {
- Type = genericType.Value,
- }
- };
+ token = context.Tokenizer.NextToken();
+ if (token.Type == TokenType.RightAngleBracket)
+ break;
+ if (token.Type == TokenType.Comma)
+ continue;
+ if (token.Type == TokenType.Identifier)
+ inheritType.GenericArgs.Add(new TypeInfo { Type = token.Value });
+ else
+ throw new ParseException(ref context, "Incorrect inheritance");
+ }
// TODO: find better way to resolve this (custom base type attribute?)
if (inheritType.Type == "ShaderAssetTypeBase")
@@ -1590,16 +1600,30 @@ namespace Flax.Build.Bindings
// Read parameters from the tag
var tagParams = ParseTagParameters(ref context);
- // Read 'typedef' keyword
+ // Read 'typedef' or 'using' keyword
var token = context.Tokenizer.NextToken();
- if (token.Value != "typedef")
- throw new ParseException(ref context, $"Invalid {ApiTokens.Typedef} usage (expected 'typedef' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}').");
+ var isUsing = token.Value == "using";
+ if (token.Value != "typedef" && !isUsing)
+ throw new ParseException(ref context, $"Invalid {ApiTokens.Typedef} usage (expected 'typedef' or 'using' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}').");
- // Read type definition
- desc.Type = ParseType(ref context);
+ if (isUsing)
+ {
+ // Read name
+ desc.Name = ParseName(ref context);
- // Read name
- desc.Name = ParseName(ref context);
+ context.Tokenizer.ExpectToken(TokenType.Equal);
+
+ // Read type definition
+ desc.Type = ParseType(ref context);
+ }
+ else
+ {
+ // Read type definition
+ desc.Type = ParseType(ref context);
+
+ // Read name
+ desc.Name = ParseName(ref context);
+ }
// Read ';'
context.Tokenizer.ExpectToken(TokenType.SemiColon);
diff --git a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs
index 9258e7827..e1d6a0281 100644
--- a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs
+++ b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs
@@ -182,11 +182,40 @@ namespace Flax.Build.Bindings
public static TypeInfo FromString(string text)
{
- var result = new TypeInfo(text);
+ var result = new TypeInfo(text.Trim());
+ if (result.Type.StartsWith("const"))
+ {
+ // Const
+ result.IsConst = true;
+ result.Type = result.Type.Substring(5).Trim();
+ }
if (result.Type.EndsWith('*'))
{
+ // Pointer
result.IsPtr = true;
- result.Type = result.Type.Substring(0, result.Type.Length - 1);
+ result.Type = result.Type.Substring(0, result.Type.Length - 1).Trim();
+ }
+ if (result.Type.EndsWith('&'))
+ {
+ // Reference
+ result.IsRef = true;
+ result.Type = result.Type.Substring(0, result.Type.Length - 1).Trim();
+ if (result.Type.EndsWith('&'))
+ {
+ // Move reference
+ result.IsMoveRef = true;
+ result.Type = result.Type.Substring(0, result.Type.Length - 1).Trim();
+ }
+ }
+ var idx = result.Type.IndexOf('<');
+ if (idx != -1)
+ {
+ // Generic
+ result.GenericArgs = new List();
+ var generics = result.Type.Substring(idx + 1, result.Type.Length - idx - 2);
+ foreach (var generic in generics.Split(','))
+ result.GenericArgs.Add(FromString(generic));
+ result.Type = result.Type.Substring(0, idx);
}
return result;
}
diff --git a/Source/Tools/Flax.Build/Build/Toolchain.cs b/Source/Tools/Flax.Build/Build/Toolchain.cs
index 6d90dc850..07bd01a88 100644
--- a/Source/Tools/Flax.Build/Build/Toolchain.cs
+++ b/Source/Tools/Flax.Build/Build/Toolchain.cs
@@ -73,6 +73,11 @@ namespace Flax.Build
///
public abstract TargetCompiler Compiler { get; }
+ ///
+ /// Gets the main native files compiler path.
+ ///
+ public virtual string NativeCompilerPath { get; }
+
///
/// Initializes a new instance of the class.
///
diff --git a/Source/Tools/Flax.Build/Deploy/Deployer.cs b/Source/Tools/Flax.Build/Deploy/Deployer.cs
index aed145a26..c9809a97d 100644
--- a/Source/Tools/Flax.Build/Deploy/Deployer.cs
+++ b/Source/Tools/Flax.Build/Deploy/Deployer.cs
@@ -93,6 +93,22 @@ namespace Flax.Deploy
BuildPlatform(platform, architectures);
}
}
+
+ if (Platform.BuildTargetPlatform == TargetPlatform.Windows)
+ {
+ Log.Info("Compressing game debug symbols files...");
+ var gamePackageZipPath = Path.Combine(Deployer.PackageOutputPath, "GameDebugSymbols.zip");
+ Utilities.FileDelete(gamePackageZipPath);
+ using (var zip = new Ionic.Zip.ZipFile())
+ {
+ zip.AddDirectory(Path.Combine(Deployer.PackageOutputPath, "GameDebugSymbols"));
+ zip.CompressionLevel = Ionic.Zlib.CompressionLevel.BestCompression;
+ zip.Comment = string.Format("Flax Game {0}.{1}.{2}\nDate: {3}", Deployer.VersionMajor, Deployer.VersionMinor, Deployer.VersionBuild, DateTime.UtcNow);
+ zip.Save(gamePackageZipPath);
+ }
+ Log.Info("Compressed game debug symbols package size: " + Utilities.GetFileSize(gamePackageZipPath));
+ }
+ Utilities.DirectoryDelete(Path.Combine(Deployer.PackageOutputPath, "GameDebugSymbols"));
}
if (Configuration.DeployEditor)
diff --git a/Source/Tools/Flax.Build/Deploy/Deployment.Platforms.cs b/Source/Tools/Flax.Build/Deploy/Deployment.Platforms.cs
index 805cfdbe9..ef3b991d7 100644
--- a/Source/Tools/Flax.Build/Deploy/Deployment.Platforms.cs
+++ b/Source/Tools/Flax.Build/Deploy/Deployment.Platforms.cs
@@ -28,6 +28,18 @@ namespace Flax.Deploy
string dst = Path.Combine(Deployer.PackageOutputPath, platformName);
Utilities.DirectoryDelete(dst);
+ // Deploy debug files for crashes debugging
+ foreach (var configuration in new[] { TargetConfiguration.Debug, TargetConfiguration.Development, TargetConfiguration.Release })
+ {
+ if (platform == TargetPlatform.Windows)
+ {
+ var dstDebug = Path.Combine(Deployer.PackageOutputPath, $"GameDebugSymbols/{platform}/{configuration}");
+ Directory.CreateDirectory(dstDebug);
+ var binaries = Path.Combine(src, "Binaries", "Game", "x64", configuration.ToString());
+ DeployFiles(binaries, dstDebug, "*.pdb");
+ }
+ }
+
// Deploy files
{
DeployFolder(platformsRoot, Deployer.PackageOutputPath, platformName);
diff --git a/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs b/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs
index b03dcbb90..88f3abf44 100644
--- a/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs
+++ b/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs
@@ -267,6 +267,9 @@ namespace Flax.Build.Platforms
///
public override TargetCompiler Compiler => TargetCompiler.Clang;
+ ///
+ public override string NativeCompilerPath => ClangPath;
+
///
public override void LogInfo()
{
diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs
index 91c3b44cd..d51a59836 100644
--- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs
+++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs
@@ -358,6 +358,9 @@ namespace Flax.Build.Platforms
///
public override TargetCompiler Compiler => TargetCompiler.MSVC;
+ ///
+ public override string NativeCompilerPath => _compilerPath;
+
///
public override void LogInfo()
{
diff --git a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs
index 27092b219..268bb87e1 100644
--- a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs
+++ b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs
@@ -542,6 +542,8 @@ namespace Flax.Build.Projects.VisualStudioCode
var configuration = TargetConfiguration.Development;
var architecture = TargetArchitecture.x64;
+ var compilerPath = string.Empty;
+ var cppVersion = NativeCpp.CppVersion.Cpp14;
var includePaths = new HashSet();
var preprocessorDefinitions = new HashSet();
foreach (var e in mainProject.Defines)
@@ -563,6 +565,8 @@ namespace Flax.Build.Projects.VisualStudioCode
module.Key.SetupEnvironment(targetBuildOptions);
}
+ cppVersion = targetBuildOptions.CompileEnv.CppVersion;
+ compilerPath = toolchain.NativeCompilerPath;
foreach (var e in targetBuildOptions.CompileEnv.PreprocessorDefinitions)
preprocessorDefinitions.Add(e);
foreach (var e in targetBuildOptions.CompileEnv.IncludePaths)
@@ -570,6 +574,28 @@ namespace Flax.Build.Projects.VisualStudioCode
}
}
+ if (compilerPath.Length != 0)
+ json.AddField("compilerPath", compilerPath);
+
+ switch (cppVersion)
+ {
+ case NativeCpp.CppVersion.Cpp14:
+ json.AddField("cStandard", "c11");
+ json.AddField("cppStandard", "c++14");
+ break;
+ case NativeCpp.CppVersion.Cpp17:
+ case NativeCpp.CppVersion.Latest:
+ json.AddField("cStandard", "c17");
+ json.AddField("cppStandard", "c++17");
+ break;
+ case NativeCpp.CppVersion.Cpp20:
+ json.AddField("cStandard", "c17");
+ json.AddField("cppStandard", "c++20");
+ break;
+ default:
+ throw new Exception($"Visual Code project generator does not support C++ standard {cppVersion}.");
+ }
+
json.BeginArray("includePath");
{
foreach (var path in includePaths)