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(); + if (_managed is MethodInfo methodInfo) + return methodInfo.Invoke(obj, parameters); + if (_managed != null) + throw new NotSupportedException(); + return _custom.Invoke(obj, parameters); + } } /// diff --git a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs index 71601ed23..329c55452 100644 --- a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs +++ b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs @@ -30,7 +30,7 @@ namespace FlaxEditor.Surface.Archetypes /// Represents single blend point. /// /// - protected class BlendPoint : Control + internal class BlendPoint : Control { private readonly BlendPointsEditor _editor; private readonly int _index; @@ -48,6 +48,11 @@ namespace FlaxEditor.Surface.Archetypes /// 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)