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

This commit is contained in:
Wojtek Figat
2024-12-10 11:07:31 +01:00
105 changed files with 2570 additions and 653 deletions

87
.github/data/Build Settings.json vendored Normal file
View File

@@ -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
}
]
}
]
}
}

5
.github/data/Cook.ps1 vendored Normal file
View File

@@ -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'

11
.github/data/ExitOnEsc.cs vendored Normal file
View File

@@ -0,0 +1,11 @@
using FlaxEngine;
public class ExitOnEsc : Script
{
/// <inheritdoc />
public override void OnUpdate()
{
// Exit as soon as game starts update loaded level
Engine.RequestExit();
}
}

View File

@@ -10,7 +10,7 @@ jobs:
# Game # Game
game-windows: game-windows:
name: Game (iOS, Release ARM64) name: Game (iOS, Release ARM64)
runs-on: "macos-latest" runs-on: "macos-14"
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v3 uses: actions/checkout@v3

View File

@@ -10,7 +10,7 @@ jobs:
# Editor # Editor
editor-mac: editor-mac:
name: Editor (Mac, Development ARM64) name: Editor (Mac, Development ARM64)
runs-on: "macos-latest" runs-on: "macos-14"
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v3 uses: actions/checkout@v3
@@ -35,7 +35,7 @@ jobs:
# Game # Game
game-mac: game-mac:
name: Game (Mac, Release ARM64) name: Game (Mac, Release ARM64)
runs-on: "macos-latest" runs-on: "macos-14"
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v3 uses: actions/checkout@v3

View File

@@ -140,7 +140,7 @@ jobs:
# Mac # Mac
package-mac-editor: package-mac-editor:
name: Editor (Mac) name: Editor (Mac)
runs-on: "macos-latest" runs-on: "macos-14"
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v3 uses: actions/checkout@v3
@@ -168,7 +168,7 @@ jobs:
path: Output/FlaxEditorMac.zip path: Output/FlaxEditorMac.zip
package-mac-game: package-mac-game:
name: Game (Mac) name: Game (Mac)
runs-on: "macos-latest" runs-on: "macos-14"
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v3 uses: actions/checkout@v3

48
.github/workflows/cooking.yml vendored Normal file
View File

@@ -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"

View File

@@ -4,7 +4,7 @@
"Major": 1, "Major": 1,
"Minor": 9, "Minor": 9,
"Revision": 0, "Revision": 0,
"Build": 6605 "Build": 6606
}, },
"Company": "Flax", "Company": "Flax",
"Copyright": "Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.", "Copyright": "Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.",

View File

@@ -112,6 +112,12 @@ namespace FlaxEditor.Content
throw new TargetException("Missing Visual Script asset."); throw new TargetException("Missing Visual Script asset.");
_type.Asset.SetScriptInstanceParameterValue(_parameter.Name, (Object)obj, value); _type.Asset.SetScriptInstanceParameterValue(_parameter.Name, (Object)obj, value);
} }
/// <inheritdoc />
public object Invoke(object obj, object[] parameters)
{
throw new NotSupportedException();
}
} }
sealed class VisualScriptMethodInfo : IScriptMemberInfo sealed class VisualScriptMethodInfo : IScriptMemberInfo
@@ -240,6 +246,14 @@ namespace FlaxEditor.Content
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }
/// <inheritdoc />
public object Invoke(object obj, object[] parameters)
{
if (!_type.Asset)
throw new TargetException("Missing Visual Script asset.");
return _type.Asset.InvokeMethod(_index, obj, parameters);
}
} }
/// <summary> /// <summary>

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using FlaxEditor.CustomEditors.GUI; using FlaxEditor.CustomEditors.GUI;
@@ -127,12 +128,41 @@ namespace FlaxEditor.CustomEditors
_isSetBlocked = true; _isSetBlocked = true;
Initialize(layout); Initialize(layout);
ShowButtons();
Refresh(); Refresh();
_isSetBlocked = false; _isSetBlocked = false;
CurrentCustomEditor = prev; 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<ButtonAttribute>();
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 static CustomEditor CurrentCustomEditor;
internal void OnChildCreated(CustomEditor child) internal void OnChildCreated(CustomEditor child)
@@ -271,8 +301,16 @@ namespace FlaxEditor.CustomEditors
_valueToSet = null; _valueToSet = null;
// Assign value // Assign value
for (int i = 0; i < _values.Count; i++) if (val is IList l && l.Count == _values.Count)
_values[i] = val; {
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 finally
{ {

View File

@@ -2,6 +2,7 @@
using FlaxEditor.GUI; using FlaxEditor.GUI;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.CustomEditors.Dedicated namespace FlaxEditor.CustomEditors.Dedicated
{ {
@@ -11,7 +12,9 @@ namespace FlaxEditor.CustomEditors.Dedicated
class BezierCurveObjectEditor<T> : CustomEditor where T : struct class BezierCurveObjectEditor<T> : CustomEditor where T : struct
{ {
private bool _isSetting; private bool _isSetting;
private int _firstTimeShow;
private BezierCurveEditor<T> _curve; private BezierCurveEditor<T> _curve;
private Splitter _splitter;
/// <inheritdoc /> /// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout) public override void Initialize(LayoutElementsContainer layout)
@@ -20,6 +23,14 @@ namespace FlaxEditor.CustomEditors.Dedicated
_curve = item.CustomControl; _curve = item.CustomControl;
_curve.Height = 120.0f; _curve.Height = 120.0f;
_curve.Edited += OnCurveEdited; _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() private void OnCurveEdited()
@@ -32,6 +43,11 @@ namespace FlaxEditor.CustomEditors.Dedicated
_isSetting = false; _isSetting = false;
} }
private void OnSplitterMoved(Float2 location)
{
_curve.Height = Mathf.Clamp(_splitter.PointToParent(location).Y, 50.0f, 1000.0f);
}
/// <inheritdoc /> /// <inheritdoc />
public override void Refresh() public override void Refresh()
{ {
@@ -44,12 +60,15 @@ namespace FlaxEditor.CustomEditors.Dedicated
_curve.SetKeyframes(value.Keyframes); _curve.SetKeyframes(value.Keyframes);
_isSetting = false; _isSetting = false;
} }
if (_firstTimeShow-- > 0)
_curve.ShowWholeCurve();
} }
/// <inheritdoc /> /// <inheritdoc />
protected override void Deinitialize() protected override void Deinitialize()
{ {
_curve = null; _curve = null;
_splitter = null;
base.Deinitialize(); base.Deinitialize();
} }
@@ -111,7 +130,9 @@ namespace FlaxEditor.CustomEditors.Dedicated
class LinearCurveObjectEditor<T> : CustomEditor where T : struct class LinearCurveObjectEditor<T> : CustomEditor where T : struct
{ {
private bool _isSetting; private bool _isSetting;
private int _firstTimeShow;
private LinearCurveEditor<T> _curve; private LinearCurveEditor<T> _curve;
private Splitter _splitter;
/// <inheritdoc /> /// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout) public override void Initialize(LayoutElementsContainer layout)
@@ -120,6 +141,14 @@ namespace FlaxEditor.CustomEditors.Dedicated
_curve = item.CustomControl; _curve = item.CustomControl;
_curve.Height = 120.0f; _curve.Height = 120.0f;
_curve.Edited += OnCurveEdited; _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() private void OnCurveEdited()
@@ -132,6 +161,11 @@ namespace FlaxEditor.CustomEditors.Dedicated
_isSetting = false; _isSetting = false;
} }
private void OnSplitterMoved(Float2 location)
{
_curve.Height = Mathf.Clamp(_splitter.PointToParent(location).Y, 50.0f, 1000.0f);
}
/// <inheritdoc /> /// <inheritdoc />
public override void Refresh() public override void Refresh()
{ {
@@ -144,12 +178,15 @@ namespace FlaxEditor.CustomEditors.Dedicated
_curve.SetKeyframes(value.Keyframes); _curve.SetKeyframes(value.Keyframes);
_isSetting = false; _isSetting = false;
} }
if (_firstTimeShow-- > 0)
_curve.ShowWholeCurve();
} }
/// <inheritdoc /> /// <inheritdoc />
protected override void Deinitialize() protected override void Deinitialize()
{ {
_curve = null; _curve = null;
_splitter = null;
base.Deinitialize(); base.Deinitialize();
} }

View File

@@ -407,20 +407,13 @@ namespace FlaxEditor.CustomEditors.Dedicated
/// <seealso cref="FlaxEditor.CustomEditors.Editors.GenericEditor" /> /// <seealso cref="FlaxEditor.CustomEditors.Editors.GenericEditor" />
public class UIControlControlEditor : GenericEditor public class UIControlControlEditor : GenericEditor
{ {
private Type _cachedType; private ScriptType[] _valueTypes;
private bool _anchorDropDownClosed = true; private bool _anchorDropDownClosed = true;
private Button _pivotRelativeButton; private Button _pivotRelativeButton;
/// <inheritdoc /> /// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout) 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 // Set control type button
var space = layout.Space(20); var space = layout.Space(20);
var buttonText = "Set Type"; var buttonText = "Set Type";
@@ -445,12 +438,12 @@ namespace FlaxEditor.CustomEditors.Dedicated
} }
// Add control type helper label // 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."); 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 // Show control properties
base.Initialize(layout); base.Initialize(layout);
@@ -720,22 +713,20 @@ namespace FlaxEditor.CustomEditors.Dedicated
/// <inheritdoc /> /// <inheritdoc />
public override void Refresh() 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 RebuildLayout();
var type = Values.HasNull ? null : Values[0].GetType(); return;
if (type != _cachedType) }
{
RebuildLayout();
return;
}
// Refresh anchors // Refresh anchors
GetAnchorEquality(out bool xEq, out bool yEq, ValuesTypes); GetAnchorEquality(out bool xEq, out bool yEq, ValuesTypes);
if (xEq != _cachedXEq || yEq != _cachedYEq) if (xEq != _cachedXEq || yEq != _cachedYEq)
{ {
RebuildLayout(); RebuildLayout();
}
} }
base.Refresh(); base.Refresh();

View File

@@ -34,6 +34,15 @@ namespace FlaxEditor.CustomEditors.Editors
/// <inheritdoc /> /// <inheritdoc />
public override DisplayStyle Style => DisplayStyle.Inline; public override DisplayStyle Style => DisplayStyle.Inline;
/// <summary>
/// Whether to use the average for sliding different values.
/// </summary>
public virtual bool AllowSlidingForDifferentValues => true;
private ValueChanged _valueChanged;
private float _defaultSlidingSpeed;
private bool _slidingEnded = false;
/// <inheritdoc /> /// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout) public override void Initialize(LayoutElementsContainer layout)
{ {
@@ -46,18 +55,31 @@ namespace FlaxEditor.CustomEditors.Editors
XElement = grid.FloatValue(); XElement = grid.FloatValue();
XElement.ValueBox.Category = Utils.ValueCategory.Angle; XElement.ValueBox.Category = Utils.ValueCategory.Angle;
XElement.ValueBox.ValueChanged += OnValueChanged; XElement.ValueBox.ValueChanged += OnXValueChanged;
XElement.ValueBox.SlidingEnd += ClearToken; XElement.ValueBox.SlidingEnd += () =>
{
_slidingEnded = true;
ClearToken();
};
_defaultSlidingSpeed = XElement.ValueBox.SlideSpeed;
YElement = grid.FloatValue(); YElement = grid.FloatValue();
YElement.ValueBox.Category = Utils.ValueCategory.Angle; YElement.ValueBox.Category = Utils.ValueCategory.Angle;
YElement.ValueBox.ValueChanged += OnValueChanged; YElement.ValueBox.ValueChanged += OnYValueChanged;
YElement.ValueBox.SlidingEnd += ClearToken; YElement.ValueBox.SlidingEnd += () =>
{
_slidingEnded = true;
ClearToken();
};
ZElement = grid.FloatValue(); ZElement = grid.FloatValue();
ZElement.ValueBox.Category = Utils.ValueCategory.Angle; ZElement.ValueBox.Category = Utils.ValueCategory.Angle;
ZElement.ValueBox.ValueChanged += OnValueChanged; ZElement.ValueBox.ValueChanged += OnZValueChanged;
ZElement.ValueBox.SlidingEnd += ClearToken; ZElement.ValueBox.SlidingEnd += () =>
{
_slidingEnded = true;
ClearToken();
};
if (LinkedLabel != null) if (LinkedLabel != null)
{ {
@@ -70,32 +92,117 @@ namespace FlaxEditor.CustomEditors.Editors
} }
} }
private void OnValueChanged() private void OnXValueChanged()
{ {
if (IsSetBlocked) if (IsSetBlocked)
return; 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 isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding;
var token = isSliding ? this : null; var token = isSliding ? this : null;
var useCachedAngles = isSliding && token == _cachedToken; var useCachedAngles = isSliding && token == _cachedToken;
float x = (useCachedAngles && !XElement.IsSliding) ? _cachedAngles.X : XElement.ValueBox.Value; if (HasDifferentValues && Values.Count > 1)
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)
{ {
_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); if (!useCachedAngles)
SetValue(value, token); {
_cachedAngles = new Float3(x, y, z);
}
_cachedToken = token;
Quaternion.Euler(x, y, z, out Quaternion value);
SetValue(value, token);
}
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -112,7 +219,73 @@ namespace FlaxEditor.CustomEditors.Editors
if (HasDifferentValues) if (HasDifferentValues)
{ {
// TODO: support different values for ValueBox<T> // 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 else
{ {
@@ -121,6 +294,13 @@ namespace FlaxEditor.CustomEditors.Editors
XElement.ValueBox.Value = euler.X; XElement.ValueBox.Value = euler.X;
YElement.ValueBox.Value = euler.Y; YElement.ValueBox.Value = euler.Y;
ZElement.ValueBox.Value = euler.Z; 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;
} }
} }
} }

View File

@@ -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
{
/// <summary>
/// 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.
/// </summary>
public sealed class TerrainLayerEditor : CustomEditor
{
private ComboBoxElement element;
/// <inheritdoc />
public override DisplayStyle Style => DisplayStyle.Inline;
/// <inheritdoc />
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);
}
/// <inheritdoc />
public override void Refresh()
{
base.Refresh();
element.ComboBox.SelectedIndex = (int)Values[0];
}
}
}

View File

@@ -7,6 +7,27 @@ using FlaxEngine.GUI;
namespace FlaxEditor.CustomEditors.Editors namespace FlaxEditor.CustomEditors.Editors
{ {
/// <summary>
/// The value changed by custom Vector3 editors.
/// </summary>
public enum ValueChanged
{
/// <summary>
/// X value changed.
/// </summary>
X = 0,
/// <summary>
/// Y value changed.
/// </summary>
Y = 1,
/// <summary>
/// Z value changed.
/// </summary>
Z = 2
}
/// <summary> /// <summary>
/// Default implementation of the inspector used to edit Vector3 value type properties. /// Default implementation of the inspector used to edit Vector3 value type properties.
/// </summary> /// </summary>
@@ -49,14 +70,14 @@ namespace FlaxEditor.CustomEditors.Editors
/// </summary> /// </summary>
public bool LinkValues = false; public bool LinkValues = false;
private enum ValueChanged /// <summary>
{ /// Whether to use the average for sliding different values.
X = 0, /// </summary>
Y = 1, public virtual bool AllowSlidingForDifferentValues => true;
Z = 2
}
private ValueChanged _valueChanged; private ValueChanged _valueChanged;
private float _defaultSlidingSpeed;
private bool _slidingEnded = false;
/// <inheritdoc /> /// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout) public override void Initialize(LayoutElementsContainer layout)
@@ -83,19 +104,32 @@ namespace FlaxEditor.CustomEditors.Editors
XElement.SetLimits(limit); XElement.SetLimits(limit);
XElement.SetCategory(category); XElement.SetCategory(category);
XElement.ValueBox.ValueChanged += OnXValueChanged; XElement.ValueBox.ValueChanged += OnXValueChanged;
XElement.ValueBox.SlidingEnd += ClearToken; XElement.ValueBox.SlidingEnd += () =>
{
_slidingEnded = true;
ClearToken();
};
_defaultSlidingSpeed = XElement.ValueBox.SlideSpeed;
YElement = grid.FloatValue(); YElement = grid.FloatValue();
YElement.SetLimits(limit); YElement.SetLimits(limit);
YElement.SetCategory(category); YElement.SetCategory(category);
YElement.ValueBox.ValueChanged += OnYValueChanged; YElement.ValueBox.ValueChanged += OnYValueChanged;
YElement.ValueBox.SlidingEnd += ClearToken; YElement.ValueBox.SlidingEnd += () =>
{
_slidingEnded = true;
ClearToken();
};
ZElement = grid.FloatValue(); ZElement = grid.FloatValue();
ZElement.SetLimits(limit); ZElement.SetLimits(limit);
ZElement.SetCategory(category); ZElement.SetCategory(category);
ZElement.ValueBox.ValueChanged += OnZValueChanged; ZElement.ValueBox.ValueChanged += OnZValueChanged;
ZElement.ValueBox.SlidingEnd += ClearToken; ZElement.ValueBox.SlidingEnd += () =>
{
_slidingEnded = true;
ClearToken();
};
if (LinkedLabel != null) if (LinkedLabel != null)
{ {
@@ -118,8 +152,7 @@ namespace FlaxEditor.CustomEditors.Editors
{ {
if (IsSetBlocked) if (IsSetBlocked)
return; return;
if (LinkValues) _valueChanged = ValueChanged.X;
_valueChanged = ValueChanged.X;
OnValueChanged(); OnValueChanged();
} }
@@ -127,8 +160,7 @@ namespace FlaxEditor.CustomEditors.Editors
{ {
if (IsSetBlocked) if (IsSetBlocked)
return; return;
if (LinkValues) _valueChanged = ValueChanged.Y;
_valueChanged = ValueChanged.Y;
OnValueChanged(); OnValueChanged();
} }
@@ -136,8 +168,7 @@ namespace FlaxEditor.CustomEditors.Editors
{ {
if (IsSetBlocked) if (IsSetBlocked)
return; return;
if (LinkValues) _valueChanged = ValueChanged.Z;
_valueChanged = ValueChanged.Z;
OnValueChanged(); OnValueChanged();
} }
@@ -191,14 +222,79 @@ namespace FlaxEditor.CustomEditors.Editors
var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding; var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding;
var token = isSliding ? this : null; var token = isSliding ? this : null;
var value = new Float3(xValue, yValue, zValue); var value = new Float3(xValue, yValue, zValue);
object v = Values[0]; if (HasDifferentValues && Values.Count > 1)
if (v is Vector3) {
v = (Vector3)value; var newObjects = new object[Values.Count];
else if (v is Float3) // Handle Sliding
v = (Float3)value; if (AllowSlidingForDifferentValues && (isSliding || _slidingEnded))
else if (v is Double3) {
v = (Double3)value; // TODO: handle linked values
SetValue(v, token); 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) private float GetRatio(float value, float initialValue)
@@ -218,7 +314,79 @@ namespace FlaxEditor.CustomEditors.Editors
if (HasDifferentValues) if (HasDifferentValues)
{ {
// TODO: support different values for ValueBox<T> // 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 else
{ {
@@ -232,6 +400,13 @@ namespace FlaxEditor.CustomEditors.Editors
XElement.ValueBox.Value = value.X; XElement.ValueBox.Value = value.X;
YElement.ValueBox.Value = value.Y; YElement.ValueBox.Value = value.Y;
ZElement.ValueBox.Value = value.Z; 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;
} }
} }
} }
@@ -260,6 +435,15 @@ namespace FlaxEditor.CustomEditors.Editors
/// <inheritdoc /> /// <inheritdoc />
public override DisplayStyle Style => DisplayStyle.Inline; public override DisplayStyle Style => DisplayStyle.Inline;
/// <summary>
/// Whether to use the average for sliding different values.
/// </summary>
public virtual bool AllowSlidingForDifferentValues => true;
private ValueChanged _valueChanged;
private float _defaultSlidingSpeed;
private bool _slidingEnded = false;
/// <inheritdoc /> /// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout) public override void Initialize(LayoutElementsContainer layout)
{ {
@@ -284,20 +468,33 @@ namespace FlaxEditor.CustomEditors.Editors
XElement = grid.DoubleValue(); XElement = grid.DoubleValue();
XElement.SetLimits(limit); XElement.SetLimits(limit);
XElement.SetCategory(category); XElement.SetCategory(category);
XElement.ValueBox.ValueChanged += OnValueChanged; XElement.ValueBox.ValueChanged += OnXValueChanged;
XElement.ValueBox.SlidingEnd += ClearToken; XElement.ValueBox.SlidingEnd += () =>
{
_slidingEnded = true;
ClearToken();
};
_defaultSlidingSpeed = XElement.ValueBox.SlideSpeed;
YElement = grid.DoubleValue(); YElement = grid.DoubleValue();
YElement.SetLimits(limit); YElement.SetLimits(limit);
YElement.SetCategory(category); YElement.SetCategory(category);
YElement.ValueBox.ValueChanged += OnValueChanged; YElement.ValueBox.ValueChanged += OnYValueChanged;
YElement.ValueBox.SlidingEnd += ClearToken; YElement.ValueBox.SlidingEnd += () =>
{
_slidingEnded = true;
ClearToken();
};
ZElement = grid.DoubleValue(); ZElement = grid.DoubleValue();
ZElement.SetLimits(limit); ZElement.SetLimits(limit);
ZElement.SetCategory(category); ZElement.SetCategory(category);
ZElement.ValueBox.ValueChanged += OnValueChanged; ZElement.ValueBox.ValueChanged += OnZValueChanged;
ZElement.ValueBox.SlidingEnd += ClearToken; ZElement.ValueBox.SlidingEnd += () =>
{
_slidingEnded = true;
ClearToken();
};
if (LinkedLabel != null) 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() private void OnValueChanged()
{ {
if (IsSetBlocked || Values == null) if (IsSetBlocked || Values == null)
return; 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 isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding;
var token = isSliding ? this : null; var token = isSliding ? this : null;
var value = new Double3(XElement.ValueBox.Value, YElement.ValueBox.Value, ZElement.ValueBox.Value); var value = new Double3(xValue, yValue, zValue);
object v = Values[0]; if (HasDifferentValues && Values.Count > 1)
if (v is Vector3) {
v = (Vector3)value; var newObjects = new object[Values.Count];
else if (v is Float3) // Handle Sliding
v = (Float3)value; if (AllowSlidingForDifferentValues && (isSliding || _slidingEnded))
else if (v is Double3) {
v = (Double3)value; // TODO: handle linked values
SetValue(v, token); 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);
}
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -341,7 +631,79 @@ namespace FlaxEditor.CustomEditors.Editors
if (HasDifferentValues) if (HasDifferentValues)
{ {
// TODO: support different values for ValueBox<T> // 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 else
{ {
@@ -355,6 +717,19 @@ namespace FlaxEditor.CustomEditors.Editors
XElement.ValueBox.Value = value.X; XElement.ValueBox.Value = value.X;
YElement.ValueBox.Value = value.Y; YElement.ValueBox.Value = value.Y;
ZElement.ValueBox.Value = value.Z; 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;
}
} }
} }
} }

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
@@ -385,10 +386,21 @@ namespace FlaxEditor.CustomEditors
if (instanceValues.Count != Count) if (instanceValues.Count != Count)
throw new ArgumentException(); throw new ArgumentException();
for (int i = 0; i < Count; i++) if (value is IList l && l.Count == Count)
{ {
Info.SetValue(instanceValues[i], value); for (int i = 0; i < Count; i++)
this[i] = Info.GetValue(instanceValues[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]);
}
} }
} }

View File

@@ -673,6 +673,7 @@ bool Editor::Init()
Managed = New<ManagedEditor>(); Managed = New<ManagedEditor>();
// Show splash screen // Show splash screen
if (!CommandLine::Options.Headless.IsTrue())
{ {
PROFILE_CPU_NAMED("Splash"); PROFILE_CPU_NAMED("Splash");
if (EditorImpl::Splash == nullptr) if (EditorImpl::Splash == nullptr)

View File

@@ -49,9 +49,9 @@ namespace FlaxEditor
} }
private readonly List<EditorModule> _modules = new List<EditorModule>(16); private readonly List<EditorModule> _modules = new List<EditorModule>(16);
private bool _isAfterInit, _areModulesInited, _areModulesAfterInitEnd, _isHeadlessMode; private bool _isAfterInit, _areModulesInited, _areModulesAfterInitEnd, _isHeadlessMode, _autoExit;
private string _projectToOpen; private string _projectToOpen;
private float _lastAutoSaveTimer; private float _lastAutoSaveTimer, _autoExitTimeout = 0.1f;
private Button _saveNowButton; private Button _saveNowButton;
private Button _cancelSaveButton; private Button _cancelSaveButton;
private bool _autoSaveNow; private bool _autoSaveNow;
@@ -258,10 +258,11 @@ namespace FlaxEditor
Instance = this; 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..."); Log("Setting up C# Editor...");
_isHeadlessMode = isHeadless; _isHeadlessMode = flags.HasFlag(StartupFlags.Headless);
_autoExit = flags.HasFlag(StartupFlags.Exit);
_startupSceneCmdLine = startupScene; _startupSceneCmdLine = startupScene;
Profiler.BeginEvent("Projects"); Profiler.BeginEvent("Projects");
@@ -297,11 +298,11 @@ namespace FlaxEditor
StateMachine = new EditorStateMachine(this); StateMachine = new EditorStateMachine(this);
Undo = new EditorUndo(this); Undo = new EditorUndo(this);
if (newProject) if (flags.HasFlag(StartupFlags.NewProject))
InitProject(); InitProject();
EnsureState<LoadingState>(); EnsureState<LoadingState>();
Log("Editor init"); Log("Editor init");
if (isHeadless) if (_isHeadlessMode)
Log("Running in headless mode"); Log("Running in headless mode");
// Note: we don't sort modules before Init (optimized) // Note: we don't sort modules before Init (optimized)
@@ -357,7 +358,7 @@ namespace FlaxEditor
InitializationStart?.Invoke(); InitializationStart?.Invoke();
// Start Editor initialization ending phrase (will wait for scripts compilation result) // 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) internal void RegisterModule(EditorModule module)
@@ -486,6 +487,15 @@ namespace FlaxEditor
{ {
StateMachine.Update(); StateMachine.Update();
UpdateAutoSave(); UpdateAutoSave();
if (_autoExit && StateMachine.CurrentState == StateMachine.EditingSceneState)
{
_autoExitTimeout -= Time.UnscaledGameTime;
if (_autoExitTimeout < 0.0f)
{
Log("Auto exit");
Engine.RequestExit(0);
}
}
if (!StateMachine.IsPlayMode) if (!StateMachine.IsPlayMode)
{ {

View File

@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Text; using System.Text;
using FlaxEditor.Scripting;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using FlaxEngine.Json; using FlaxEngine.Json;
@@ -29,6 +28,7 @@ namespace FlaxEditor.GUI
internal Float2 _mousePos = Float2.Minimum; internal Float2 _mousePos = Float2.Minimum;
internal bool _isMovingSelection; internal bool _isMovingSelection;
internal bool _isMovingTangent; internal bool _isMovingTangent;
internal bool _movedView;
internal bool _movedKeyframes; internal bool _movedKeyframes;
private TangentPoint _movingTangent; private TangentPoint _movingTangent;
private Float2 _movingSelectionStart; private Float2 _movingSelectionStart;
@@ -190,31 +190,28 @@ namespace FlaxEditor.GUI
// Moving view // Moving view
if (_rightMouseDown) if (_rightMouseDown)
{ {
var delta = location - _movingViewLastPos; var movingViewPos = Parent.PointToParent(PointToParent(location));
var delta = movingViewPos - _movingViewLastPos;
if (_editor.CustomViewPanning != null) if (_editor.CustomViewPanning != null)
delta = _editor.CustomViewPanning(delta); delta = _editor.CustomViewPanning(delta);
delta *= GetUseModeMask(_editor.EnablePanning) * _editor.ViewScale; delta *= GetUseModeMask(_editor.EnablePanning);
if (delta.LengthSquared > 0.01f) if (delta.LengthSquared > 0.01f)
{ {
_editor._mainPanel.ViewOffset += delta; _editor._mainPanel.ViewOffset += delta;
_movingViewLastPos = location; _movingViewLastPos = movingViewPos;
_movedView = true;
if (_editor.CustomViewPanning != null) if (_editor.CustomViewPanning != null)
{ {
Cursor = CursorType.SizeAll; if (Cursor == CursorType.Default)
Cursor = CursorType.SizeAll;
} }
else else
{ {
switch (_editor.EnablePanning) switch (_editor.EnablePanning)
{ {
case UseMode.Vertical: case UseMode.Vertical: Cursor = CursorType.SizeNS; break;
Cursor = CursorType.SizeNS; case UseMode.Horizontal: Cursor = CursorType.SizeWE; break;
break; case UseMode.On: Cursor = CursorType.SizeAll; 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) else if (_isMovingTangent)
{ {
var viewRect = _editor._mainPanel.GetClientArea(); var viewRect = _editor._mainPanel.GetClientArea();
var direction = _movingTangent.IsIn ? -1.0f : 1.0f;
var k = _editor.GetKeyframe(_movingTangent.Index); var k = _editor.GetKeyframe(_movingTangent.Index);
var kv = _editor.GetKeyframeValue(k); var kv = _editor.GetKeyframeValue(k);
var value = _editor.Accessor.GetCurveValue(ref kv, _movingTangent.Component); 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(); _editor.UpdateTangents();
Cursor = CursorType.SizeNS; Cursor = CursorType.SizeNS;
_movedKeyframes = true; _movedKeyframes = true;
@@ -299,7 +295,8 @@ namespace FlaxEditor.GUI
{ {
_rightMouseDown = true; _rightMouseDown = true;
_rightMouseDownPos = location; _rightMouseDownPos = location;
_movingViewLastPos = location; _movedView = false;
_movingViewLastPos = Parent.PointToParent(PointToParent(location));
} }
// Check if any node is under the mouse // Check if any node is under the mouse
@@ -444,7 +441,7 @@ namespace FlaxEditor.GUI
Cursor = CursorType.Default; Cursor = CursorType.Default;
// Check if no move has been made at all // 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 selectionCount = _editor.SelectionCount;
var point = GetChildAt(location) as KeyframePoint; var point = GetChildAt(location) as KeyframePoint;
@@ -512,6 +509,27 @@ namespace FlaxEditor.GUI
return true; return true;
} }
/// <inheritdoc />
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;
}
/// <inheritdoc /> /// <inheritdoc />
public override bool OnMouseWheel(Float2 location, float delta) public override bool OnMouseWheel(Float2 location, float delta)
{ {
@@ -519,10 +537,29 @@ namespace FlaxEditor.GUI
return true; return true;
// Zoom in/out // 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 // Cache mouse location in curve-space
_editor.ViewScale += GetUseModeMask(_editor.EnableZoom) * (delta * 0.1f); 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; return true;
} }

View File

@@ -671,8 +671,22 @@ namespace FlaxEditor.GUI
/// <inheritdoc /> /// <inheritdoc />
public override void ShowWholeCurve() public override void ShowWholeCurve()
{ {
ViewScale = ApplyUseModeMask(EnableZoom, _mainPanel.Size / _contents.Size, ViewScale); _mainPanel.GetDesireClientArea(out var mainPanelArea);
ViewOffset = ApplyUseModeMask(EnablePanning, -_mainPanel.ControlsBounds.Location, ViewOffset); 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(); UpdateKeyframes();
} }
@@ -923,6 +937,11 @@ namespace FlaxEditor.GUI
KeyframesEditorUtils.Paste(this); KeyframesEditorUtils.Paste(this);
return true; return true;
} }
else if (options.FocusSelection.Process(this))
{
ShowWholeCurve();
return true;
}
return false; return false;
} }
@@ -1384,9 +1403,7 @@ namespace FlaxEditor.GUI
// Calculate bounds // Calculate bounds
var bounds = _points[0].Bounds; var bounds = _points[0].Bounds;
for (var i = 1; i < _points.Count; i++) for (var i = 1; i < _points.Count; i++)
{
bounds = Rectangle.Union(bounds, _points[i].Bounds); bounds = Rectangle.Union(bounds, _points[i].Bounds);
}
// Adjust contents bounds to fill the curve area // Adjust contents bounds to fill the curve area
if (EnablePanning != UseMode.Off || !ShowCollapsed) if (EnablePanning != UseMode.Off || !ShowCollapsed)
@@ -1632,6 +1649,7 @@ namespace FlaxEditor.GUI
var o = _keyframes[p.Index - 1]; var o = _keyframes[p.Index - 1];
var oValue = Accessor.GetCurveValue(ref o.Value, p.Component); var oValue = Accessor.GetCurveValue(ref o.Value, p.Component);
var slope = (value - oValue) / (k.Time - o.Time); var slope = (value - oValue) / (k.Time - o.Time);
slope = -slope;
Accessor.SetCurveValue(slope, ref k.TangentIn, p.Component); Accessor.SetCurveValue(slope, ref k.TangentIn, p.Component);
} }
@@ -2116,9 +2134,7 @@ namespace FlaxEditor.GUI
// Calculate bounds // Calculate bounds
var bounds = _points[0].Bounds; var bounds = _points[0].Bounds;
for (int i = 1; i < _points.Count; i++) for (int i = 1; i < _points.Count; i++)
{
bounds = Rectangle.Union(bounds, _points[i].Bounds); bounds = Rectangle.Union(bounds, _points[i].Bounds);
}
// Adjust contents bounds to fill the curve area // Adjust contents bounds to fill the curve area
if (EnablePanning != UseMode.Off || !ShowCollapsed) if (EnablePanning != UseMode.Off || !ShowCollapsed)
@@ -2184,12 +2200,12 @@ namespace FlaxEditor.GUI
var tangent = t.TangentValue; var tangent = t.TangentValue;
var direction = t.IsIn ? -1.0f : 1.0f; var direction = t.IsIn ? -1.0f : 1.0f;
var offset = 30.0f * direction; var offset = 30.0f;
var location = GetKeyframePoint(ref k, selectedComponent); var location = GetKeyframePoint(ref k, selectedComponent);
t.Size = KeyframesSize / ViewScale; t.Size = KeyframesSize / ViewScale;
t.Location = new Float2 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 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 startTangent = Accessor.GetCurveValue(ref startK.TangentOut, component);
var endTangent = Accessor.GetCurveValue(ref endK.TangentIn, 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 p1 = PointFromKeyframes(start, ref viewRect);
var p2 = PointFromKeyframes(start + new Float2(offset, startTangent * offset), ref viewRect); var p2 = PointFromKeyframes(start + new Float2(0, startTangent * tangentScale), ref viewRect);
var p3 = PointFromKeyframes(end - new Float2(offset, endTangent * offset), ref viewRect); var p3 = PointFromKeyframes(end + new Float2(0, endTangent * tangentScale), ref viewRect);
var p4 = PointFromKeyframes(end, ref viewRect); var p4 = PointFromKeyframes(end, ref viewRect);
Render2D.DrawBezier(p1, p2, p3, p4, color); Render2D.DrawSpline(p1, p2, p3, p4, color);
} }
} }
} }

View File

@@ -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<Float2> 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);
}
}
}

View File

@@ -230,7 +230,7 @@ namespace FlaxEditor.GUI.Timeline.GUI
continue; continue;
// Draw all ticks // 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 lStep = _tickSteps[l];
var lNextStep = _tickSteps[l + 1]; var lNextStep = _tickSteps[l + 1];
int startTick = Mathf.FloorToInt(min / lStep); int startTick = Mathf.FloorToInt(min / lStep);

View File

@@ -1446,6 +1446,17 @@ namespace FlaxEditor.GUI.Timeline
{ {
GetTracks(SelectedTracks[i], tracks); 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(); SelectedTracks.Clear();
if (withUndo && Undo != null && Undo.Enabled) if (withUndo && Undo != null && Undo.Enabled)
{ {
@@ -1468,6 +1479,18 @@ namespace FlaxEditor.GUI.Timeline
} }
OnTracksChanged(); OnTracksChanged();
MarkAsEdited(); 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();
}
} }
/// <summary> /// <summary>
@@ -1655,6 +1678,14 @@ namespace FlaxEditor.GUI.Timeline
} }
OnTracksChanged(); OnTracksChanged();
MarkAsEdited(); MarkAsEdited();
// Deselect and select new clones.
Deselect();
foreach (var clone in clones)
{
Select(clone, true);
}
SelectedTracks[0].Focus(); SelectedTracks[0].Focus();
} }

View File

@@ -19,87 +19,6 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// <seealso cref="MemberTrack" /> /// <seealso cref="MemberTrack" />
public abstract class CurvePropertyTrackBase : MemberTrack, IKeyframesEditorContext 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 byte[] _curveEditingStartData;
private float _expandedHeight = 120.0f; private float _expandedHeight = 120.0f;
private Splitter _splitter; private Splitter _splitter;
@@ -251,12 +170,21 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{ {
_splitter = new Splitter _splitter = new Splitter
{ {
_track = this, Moved = OnSplitterMoved,
Parent = Curve, Parent = Curve,
}; };
} }
var splitterHeight = 5.0f; _splitter.Bounds = new Rectangle(0, Curve.Height - Splitter.DefaultHeight, Curve.Width, Splitter.DefaultHeight);
_splitter.Bounds = new Rectangle(0, Curve.Height - splitterHeight, Curve.Width, splitterHeight); }
}
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();
} }
} }

View File

@@ -109,6 +109,19 @@ namespace FlaxEditor.GUI.Timeline.Tracks
MaxMediaCount = 1; MaxMediaCount = 1;
} }
/// <inheritdoc />
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);
}
}
/// <inheritdoc /> /// <inheritdoc />
public override void OnDestroy() public override void OnDestroy()
{ {

View File

@@ -61,6 +61,9 @@ namespace FlaxEditor.GUI.Timeline.Undo
_timeline.AddTrack(track, false); _timeline.AddTrack(track, false);
track.TrackIndex = _order; track.TrackIndex = _order;
_timeline.OnTracksOrderChanged(); _timeline.OnTracksOrderChanged();
_timeline.Focus();
_timeline.Select(track);
track.Focus();
} }
private void Remove() private void Remove()
@@ -68,10 +71,11 @@ namespace FlaxEditor.GUI.Timeline.Undo
var track = _timeline.FindTrack(_name); var track = _timeline.FindTrack(_name);
if (track == null) 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; return;
} }
_timeline.Delete(track, false); _timeline.Delete(track, false);
_timeline.Focus();
} }
public string ActionString => _isAdd ? "Add track" : "Remove track"; public string ActionString => _isAdd ? "Add track" : "Remove track";

View File

@@ -176,7 +176,7 @@ ManagedEditor::~ManagedEditor()
void ManagedEditor::Init() void ManagedEditor::Init()
{ {
// Note: editor modules should perform quite fast init, any longer things should be done in async during 'editor splash screen time // 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(); MClass* mclass = GetClass();
if (mclass == nullptr) if (mclass == nullptr)
{ {
@@ -193,18 +193,22 @@ void ManagedEditor::Init()
LOG(Fatal, "Failed to create editor instance."); LOG(Fatal, "Failed to create editor instance.");
} }
MObject* exception = nullptr; MObject* exception = nullptr;
bool isHeadless = CommandLine::Options.Headless.IsTrue(); StartupFlags flags = StartupFlags::None;
bool skipCompile = CommandLine::Options.SkipCompile.IsTrue(); if (CommandLine::Options.Headless.IsTrue())
bool newProject = CommandLine::Options.NewProject.IsTrue(); flags |= StartupFlags::Headless;
args[0] = &isHeadless; if (CommandLine::Options.SkipCompile.IsTrue())
args[1] = &skipCompile; flags |= StartupFlags::SkipCompile;
args[2] = &newProject; if (CommandLine::Options.NewProject.IsTrue())
flags |= StartupFlags::NewProject;
if (CommandLine::Options.Exit.IsTrue())
flags |= StartupFlags::Exit;
args[0] = &flags;
Guid sceneId; Guid sceneId;
if (!CommandLine::Options.Play.HasValue() || (CommandLine::Options.Play.HasValue() && Guid::Parse(CommandLine::Options.Play.GetValue(), sceneId))) if (!CommandLine::Options.Play.HasValue() || (CommandLine::Options.Play.HasValue() && Guid::Parse(CommandLine::Options.Play.GetValue(), sceneId)))
{ {
sceneId = Guid::Empty; sceneId = Guid::Empty;
} }
args[3] = &sceneId; args[1] = &sceneId;
initMethod->Invoke(instance, args, &exception); initMethod->Invoke(instance, args, &exception);
if (exception) if (exception)
{ {
@@ -219,7 +223,7 @@ void ManagedEditor::Init()
WasExitCalled = false; WasExitCalled = false;
// Load scripts if auto-load on startup is disabled // 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)"); LOG(Info, "Loading managed assemblies (due to disabled compilation on startup)");
Scripting::Load(); Scripting::Load();

View File

@@ -22,6 +22,15 @@ API_CLASS(Namespace="FlaxEditor", Name="Editor", NoSpawn, NoConstructor) class M
DECLARE_SCRIPTING_TYPE_NO_SPAWN(ManagedEditor); DECLARE_SCRIPTING_TYPE_NO_SPAWN(ManagedEditor);
static Guid ObjectID; static Guid ObjectID;
API_ENUM(Attributes="Flags", Internal) enum class StartupFlags
{
None = 0,
Headless = 1,
SkipCompile = 2,
NewProject = 4,
Exit = 8,
};
struct InternalOptions struct InternalOptions
{ {
byte AutoReloadScriptsOnMainWindowFocus = 1; byte AutoReloadScriptsOnMainWindowFocus = 1;
@@ -258,3 +267,5 @@ public:
// [ScriptingObject] // [ScriptingObject]
void DestroyManaged() override; void DestroyManaged() override;
}; };
DECLARE_ENUM_OPERATORS(ManagedEditor::StartupFlags);

View File

@@ -274,11 +274,7 @@ namespace FlaxEditor.Modules
private void Load() private void Load()
{ {
if (!File.Exists(_cachePath)) if (!File.Exists(_cachePath))
{
Editor.LogWarning("Missing editor cache file.");
return; return;
}
_lastSaveTime = DateTime.UtcNow; _lastSaveTime = DateTime.UtcNow;
try try

View File

@@ -950,7 +950,10 @@ namespace FlaxEditor.Modules
MainWindow = null; MainWindow = null;
// Capture project icon screenshot (not in play mode and if editor was used for some time) // 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"); Editor.Log("Capture project icon screenshot");
_projectIconScreenshotTimeout = Time.TimeSinceStartup + 0.8f; // wait 800ms for a screenshot task _projectIconScreenshotTimeout = Time.TimeSinceStartup + 0.8f; // wait 800ms for a screenshot task

View File

@@ -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))] [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; public float SurfaceGridSnappingSize { get; set; } = 20.0f;
/// <summary>
/// Gets or sets a value that indicates if a warning should be displayed when deleting a Visject parameter that is used in a graph.
/// </summary>
[DefaultValue(true)]
[EditorDisplay("Visject", "Warn when deleting used parameter"), EditorOrder(552)]
public bool WarnOnDeletingUsedVisjectParameter { get; set; } = true;
private static FontAsset DefaultFont => FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.PrimaryFont); private static FontAsset DefaultFont => FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.PrimaryFont);
private static FontAsset ConsoleFont => FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.InconsolataRegularFont); private static FontAsset ConsoleFont => FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.InconsolataRegularFont);

View File

@@ -81,6 +81,7 @@ namespace FlaxEditor.SceneGraph.Actors
if (Level.ScenesCount > 1) 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.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); base.OnContextMenu(contextMenu, window);
} }

View File

@@ -293,6 +293,14 @@ namespace FlaxEditor.Scripting
/// <param name="obj">The object whose member value will be modified.</param> /// <param name="obj">The object whose member value will be modified.</param>
/// <param name="value">The new member value.</param> /// <param name="value">The new member value.</param>
void SetValue(object obj, object value); void SetValue(object obj, object value);
/// <summary>
/// Invokes the method on a specific object (null if static) using the provided parameters.
/// </summary>
/// <param name="obj">The instance of the object to invoke its method. Use null for static methods.</param>
/// <param name="parameters">List of parameters to provide.</param>
/// <returns>The value returned by the method.</returns>
object Invoke(object obj, object[] parameters);
} }
/// <summary> /// <summary>

View File

@@ -691,6 +691,23 @@ namespace FlaxEditor.Scripting
else else
_custom.SetValue(obj, value); _custom.SetValue(obj, value);
} }
/// <summary>
/// Invokes the method on a specific object (null if static) using the provided parameters.
/// </summary>
/// <param name="obj">The instance of the object to invoke its method. Use null for static methods.</param>
/// <param name="parameters">List of parameters to provide.</param>
/// <returns>The value returned by the method.</returns>
public object Invoke(object obj = null, object[] parameters = null)
{
if (parameters == null)
parameters = Array.Empty<object>();
if (_managed is MethodInfo methodInfo)
return methodInfo.Invoke(obj, parameters);
if (_managed != null)
throw new NotSupportedException();
return _custom.Invoke(obj, parameters);
}
} }
/// <summary> /// <summary>

View File

@@ -30,7 +30,7 @@ namespace FlaxEditor.Surface.Archetypes
/// Represents single blend point. /// Represents single blend point.
/// </summary> /// </summary>
/// <seealso cref="FlaxEngine.GUI.Control" /> /// <seealso cref="FlaxEngine.GUI.Control" />
protected class BlendPoint : Control internal class BlendPoint : Control
{ {
private readonly BlendPointsEditor _editor; private readonly BlendPointsEditor _editor;
private readonly int _index; private readonly int _index;
@@ -48,6 +48,11 @@ namespace FlaxEditor.Surface.Archetypes
/// </summary> /// </summary>
public int Index => _index; public int Index => _index;
/// <summary>
/// Flag that indicates that user is moving this point with a mouse.
/// </summary>
public bool IsMouseDown => _isMouseDown;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BlendPoint"/> class. /// Initializes a new instance of the <see cref="BlendPoint"/> class.
/// </summary> /// </summary>
@@ -211,6 +216,11 @@ namespace FlaxEditor.Surface.Archetypes
/// </summary> /// </summary>
public int PointsCount => (_node.Values.Length - 4) / 2; // 4 node values + 2 per blend point public int PointsCount => (_node.Values.Length - 4) / 2; // 4 node values + 2 per blend point
/// <summary>
/// BLend points array.
/// </summary>
internal IReadOnlyList<BlendPoint> BlendPoints => _blendPoints;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BlendPointsEditor"/> class. /// Initializes a new instance of the <see cref="BlendPointsEditor"/> class.
/// </summary> /// </summary>
@@ -374,6 +384,12 @@ namespace FlaxEditor.Surface.Archetypes
/// <returns>The blend point control position.</returns> /// <returns>The blend point control position.</returns>
public Float2 BlendSpacePosToBlendPointPos(Float2 pos) 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); GetPointsArea(out var pointsArea);
if (_is2D) if (_is2D)
{ {
@@ -389,7 +405,7 @@ namespace FlaxEditor.Surface.Archetypes
pointsArea.Center.Y pointsArea.Center.Y
); );
} }
return pos - new Float2(BlendPoint.DefaultSize * 0.5f); return pos;
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -424,7 +440,7 @@ namespace FlaxEditor.Surface.Archetypes
} }
// Update blend point // 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 asset = Editor.Instance.ContentDatabase.FindAsset(animId);
var tooltip = asset?.ShortName ?? string.Empty; var tooltip = asset?.ShortName ?? string.Empty;
tooltip += "\nX: " + location.X; tooltip += "\nX: " + location.X;
@@ -532,81 +548,18 @@ namespace FlaxEditor.Surface.Archetypes
SetAsset((int)b.Tag, Guid.Empty); 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);
}
/// <inheritdoc /> /// <inheritdoc />
public override void Draw() public override void Draw()
{ {
var style = Style.Current; var style = Style.Current;
var rect = new Rectangle(Float2.Zero, Size); var rect = new Rectangle(Float2.Zero, Size);
var containsFocus = ContainsFocus; var containsFocus = ContainsFocus;
GetPointsArea(out var pointsArea);
var data0 = (Float4)_node.Values[0];
var rangeX = new Float2(data0.X, data0.Y);
// Background // Background
Render2D.DrawRectangle(rect, IsMouseOver ? style.TextBoxBackgroundSelected : style.TextBoxBackground); _node.DrawEditorBackground(ref rect);
//Render2D.DrawRectangle(pointsArea, Color.Red);
// Grid // Grid
int splits = 10; _node.DrawEditorGrid(ref rect);
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);
}
base.Draw(); base.Draw();
@@ -808,6 +761,87 @@ namespace FlaxEditor.Surface.Archetypes
_editor.SetAsset(SelectedAnimationIndex, Guid.Empty); _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);
}
/// <summary>
/// Custom drawing logic for blend space background.
/// </summary>
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);
}
/// <summary>
/// Custom drawing logic for blend space grid.
/// </summary>
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);
}
}
/// <summary> /// <summary>
/// Updates the editor UI. /// Updates the editor UI.
/// </summary> /// </summary>
@@ -983,6 +1017,10 @@ namespace FlaxEditor.Surface.Archetypes
private readonly FloatValueBox _animationX; private readonly FloatValueBox _animationX;
private readonly Label _animationYLabel; private readonly Label _animationYLabel;
private readonly FloatValueBox _animationY; private readonly FloatValueBox _animationY;
private Float2[] _triangles;
private Color[] _triangleColors;
private Float2[] _selectedTriangles;
private Color[] _selectedColors;
/// <inheritdoc /> /// <inheritdoc />
public MultiBlend2D(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) 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<Float2>();
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<Float2>();
var selectedColors = new List<Color>();
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();
}
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
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();
}
}
/// <inheritdoc /> /// <inheritdoc />
public override void UpdateUI(int selectedIndex, bool isValid, ref Float4 data0, ref Guid data1) 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; _animationX.Enabled = isValid;
_animationYLabel.Enabled = isValid; _animationYLabel.Enabled = isValid;
_animationY.Enabled = isValid; _animationY.Enabled = isValid;
ClearTriangles();
}
/// <inheritdoc />
public override void OnValuesChanged()
{
base.OnValuesChanged();
ClearTriangles();
}
/// <inheritdoc />
public override void OnLoaded(SurfaceNodeActions action)
{
base.OnLoaded(action);
ClearTriangles();
} }
} }
} }

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System; using System;
using FlaxEditor.Content.Settings;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements; using FlaxEditor.Surface.Elements;
using FlaxEditor.Windows.Assets; using FlaxEditor.Windows.Assets;
@@ -590,7 +591,7 @@ namespace FlaxEditor.Surface.Archetypes
}, },
Elements = new[] 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), NodeElementArchetype.Factory.Output(0, "", typeof(float), 0),
} }
}, },

View File

@@ -425,6 +425,12 @@ namespace FlaxEditor.Surface.Archetypes
UpdateCombo(); UpdateCombo();
} }
/// <inheritdoc />
public bool IsParamUsed(SurfaceParameter param)
{
return (Guid)Values[0] == param.ID;
}
/// <inheritdoc /> /// <inheritdoc />
public override void OnLoaded(SurfaceNodeActions action) public override void OnLoaded(SurfaceNodeActions action)
{ {
@@ -937,13 +943,17 @@ namespace FlaxEditor.Surface.Archetypes
{ {
// Deselect if that parameter is selected // Deselect if that parameter is selected
if ((Guid)Values[0] == param.ID) if ((Guid)Values[0] == param.ID)
{
_combobox.SelectedIndex = -1; _combobox.SelectedIndex = -1;
}
UpdateCombo(); UpdateCombo();
} }
/// <inheritdoc />
public bool IsParamUsed(SurfaceParameter param)
{
return (Guid)Values[0] == param.ID;
}
/// <inheritdoc /> /// <inheritdoc />
public override void OnLoaded(SurfaceNodeActions action) public override void OnLoaded(SurfaceNodeActions action)
{ {

View File

@@ -33,5 +33,12 @@ namespace FlaxEditor.Surface
/// </summary> /// </summary>
/// <param name="param">The parameter.</param> /// <param name="param">The parameter.</param>
void OnParamDeleted(SurfaceParameter param); void OnParamDeleted(SurfaceParameter param);
/// <summary>
/// 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.
/// </summary>
/// <param name="param">The parameter.</param>
/// <returns>If the parameter is referenced.</returns>
bool IsParamUsed(SurfaceParameter param);
} }
} }

View File

@@ -39,6 +39,7 @@ namespace FlaxEditor.Surface
typeof(TooltipAttribute), typeof(TooltipAttribute),
typeof(HideInEditorAttribute), typeof(HideInEditorAttribute),
typeof(NoAnimateAttribute), typeof(NoAnimateAttribute),
typeof(ButtonAttribute),
}; };
/// <summary> /// <summary>

View File

@@ -84,5 +84,16 @@ namespace FlaxEditor.Surface
} }
MarkAsEdited(); MarkAsEdited();
} }
/// <inheritdoc />
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;
}
} }
} }

View File

@@ -776,6 +776,15 @@ namespace FlaxEditor.Surface
private void DeleteParameter(int index) private void DeleteParameter(int index)
{ {
var window = (IVisjectSurfaceWindow)Values[0]; 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 var action = new AddRemoveParamAction
{ {
Window = window, Window = window,

View File

@@ -60,7 +60,7 @@ namespace FlaxEditor.Tools.Terrain.Paint
/// <summary> /// <summary>
/// The layer to paint with it. /// The layer to paint with it.
/// </summary> /// </summary>
[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; public Layers Layer = Layers.Layer0;
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -21,21 +21,6 @@ namespace FlaxEditor.Tools.Terrain
[HideInEditor] [HideInEditor]
public class PaintTerrainGizmoMode : EditorGizmoMode public class PaintTerrainGizmoMode : EditorGizmoMode
{ {
/// <summary>
/// The terrain layer names.
/// </summary>
public static readonly string[] TerrainLayerNames =
{
"Layer 0",
"Layer 1",
"Layer 2",
"Layer 3",
"Layer 4",
"Layer 5",
"Layer 6",
"Layer 7",
};
private struct SplatmapData private struct SplatmapData
{ {
public IntPtr DataPtr; public IntPtr DataPtr;

View File

@@ -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) 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) if (tickStrengths == null || tickStrengths.Length != tickSteps.Length)
tickStrengths = new float[tickSteps.Length]; tickStrengths = new float[tickSteps.Length];
@@ -286,7 +288,7 @@ namespace FlaxEditor.Utilities
continue; continue;
// Draw all ticks // 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 lStep = tickSteps[l];
var lNextStep = tickSteps[l + 1]; var lNextStep = tickSteps[l + 1];
int startTick = Mathf.FloorToInt(min / lStep); int startTick = Mathf.FloorToInt(min / lStep);

View File

@@ -79,6 +79,13 @@ namespace FlaxEditor.Viewport.Previews
_uiControlLinked.Control.Parent = null; _uiControlLinked.Control.Parent = null;
_uiControlLinked = null; _uiControlLinked = null;
} }
foreach (var child in _uiParentLink.Children.ToArray())
{
if (child is CanvasRootControl canvasRoot)
{
canvasRoot.Canvas.EditorOverride(null, null);
}
}
// Remove for the preview // Remove for the preview
Task.RemoveCustomActor(_instance); Task.RemoveCustomActor(_instance);

View File

@@ -54,24 +54,24 @@ namespace FlaxEditor.Windows.Assets
[EditorOrder(10), EditorDisplay("General"), Tooltip("Material domain type.")] [EditorOrder(10), EditorDisplay("General"), Tooltip("Material domain type.")]
public MaterialDomain Domain; 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; 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; public MaterialBlendMode BlendMode;
// Rendering // 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; 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; 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; 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; public bool DepthWrite;
// Transparency // Transparency
@@ -111,13 +111,13 @@ namespace FlaxEditor.Windows.Assets
// Misc // 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; 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; 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; public float MaskThreshold;
[EditorOrder(430), DefaultValue(MaterialDecalBlendingMode.Translucent), VisibleIf(nameof(IsDecal)), EditorDisplay("Misc"), Tooltip("The decal material blending mode.")] [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 IsPostProcess => Domain == MaterialDomain.PostProcess;
private bool IsDecal => Domain == MaterialDomain.Decal; 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 IsStandard => Domain == MaterialDomain.Surface || Domain == MaterialDomain.Terrain || Domain == MaterialDomain.Particle || Domain == MaterialDomain.Deformable;
private bool IsStandardOrGUI => IsStandard || IsGUI;
/// <summary> /// <summary>
/// Gathers parameters from the specified material. /// Gathers parameters from the specified material.

View File

@@ -52,12 +52,13 @@ namespace FlaxEditor.Windows
contextMenu.AddSeparator(); contextMenu.AddSeparator();
// Basic editing options // Basic editing options
var firstSelection = hasSthSelected ? Editor.SceneEditing.Selection[0] as ActorNode : null;
b = contextMenu.AddButton("Rename", inputOptions.Rename, Rename); b = contextMenu.AddButton("Rename", inputOptions.Rename, Rename);
b = contextMenu.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate);
b.Enabled = hasSthSelected; 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"); var convertMenu = contextMenu.AddChildMenu("Convert");
convertMenu.ContextMenu.AutoSort = true; convertMenu.ContextMenu.AutoSort = true;
@@ -117,31 +118,31 @@ namespace FlaxEditor.Windows
} }
} }
b = contextMenu.AddButton("Delete", inputOptions.Delete, Editor.SceneEditing.Delete); b = contextMenu.AddButton("Delete", inputOptions.Delete, Editor.SceneEditing.Delete);
b.Enabled = hasSthSelected; b.Enabled = hasSthSelected && (firstSelection != null ? firstSelection.CanDelete : true);
contextMenu.AddSeparator(); contextMenu.AddSeparator();
b = contextMenu.AddButton("Copy", inputOptions.Copy, Editor.SceneEditing.Copy); 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); contextMenu.AddButton("Paste", inputOptions.Paste, Editor.SceneEditing.Paste);
b = contextMenu.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut); b = contextMenu.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut);
b.Enabled = canEditScene; b.Enabled = canEditScene && hasSthSelected && (firstSelection != null ? firstSelection.CanCopyPaste : true);
// Create option // Create option
contextMenu.AddSeparator(); contextMenu.AddSeparator();
b = contextMenu.AddButton("Parent to new Actor", inputOptions.GroupSelectedActors, Editor.SceneEditing.CreateParentForSelectedActors); 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 = contextMenu.AddButton("Create Prefab", Editor.Prefabs.CreatePrefab);
b.Enabled = isSingleActorSelected && b.Enabled = isSingleActorSelected &&
((ActorNode)Editor.SceneEditing.Selection[0]).CanCreatePrefab && (firstSelection != null ? firstSelection.CanCreatePrefab : false) &&
Editor.Windows.ContentWin.CurrentViewFolder.CanHaveAssets; 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) if (hasPrefabLink)
{ {
contextMenu.AddButton("Select Prefab", Editor.Prefabs.SelectPrefab); contextMenu.AddButton("Select Prefab", Editor.Prefabs.SelectPrefab);

View File

@@ -66,27 +66,23 @@ namespace AnimationUtils
} }
template<class T> template<class T>
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 = value + tangent * tangentScale;
result = a + b * (length * oneThird);
} }
template<> template<>
FORCE_INLINE void GetTangent<Quaternion>(const Quaternion& a, const Quaternion& b, float length, Quaternion& result) FORCE_INLINE void GetTangent<Quaternion>(const Quaternion& value, const Quaternion& tangent, float tangentScale, Quaternion& result)
{ {
const float oneThird = 1.0f / 3.0f; Quaternion::Slerp(value, tangent, 1.0f / 3.0f, result);
Quaternion::Slerp(a, b, oneThird, result);
} }
template<> template<>
FORCE_INLINE void GetTangent<Transform>(const Transform& a, const Transform& b, float length, Transform& result) FORCE_INLINE void GetTangent<Transform>(const Transform& value, const Transform& tangent, float tangentScale, Transform& result)
{ {
const float oneThird = 1.0f / 3.0f; GetTangent(value.Translation, tangent.Translation, tangentScale, result.Translation);
const float oneThirdLength = length * oneThird; GetTangent(value.Orientation, tangent.Orientation, tangentScale, result.Orientation);
result.Translation = a.Translation + b.Translation * oneThirdLength; GetTangent(value.Scale, tangent.Scale, tangentScale, result.Scale);
Quaternion::Slerp(a.Orientation, b.Orientation, oneThird, result.Orientation);
result.Scale = a.Scale + (b.Scale - a.Scale) * oneThirdLength;
} }
template<class T> template<class T>

View File

@@ -1,8 +1,10 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using FlaxEngine.Interop;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace FlaxEngine namespace FlaxEngine
@@ -24,9 +26,9 @@ namespace FlaxEngine
/// </summary> /// </summary>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <param name="tangent">The tangent.</param> /// <param name="tangent">The tangent.</param>
/// <param name="lengthThird">The length divided by 3.</param> /// <param name="tangentScale">The tangent scale factor.</param>
/// <param name="result">The result.</param> /// <param name="result">The result.</param>
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);
/// <summary> /// <summary>
/// Calculates the linear interpolation at the specified alpha. /// Calculates the linear interpolation at the specified alpha.
@@ -67,7 +69,7 @@ namespace FlaxEngine
IKeyframeAccess<Color32>, IKeyframeAccess<Color32>,
IKeyframeAccess<Color> IKeyframeAccess<Color>
{ {
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; result = value;
} }
@@ -82,9 +84,9 @@ namespace FlaxEngine
result = p0; 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) 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); 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) 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); 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) 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); 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) 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); 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) 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); 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) 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); 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) 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); 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) 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); 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) 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); 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) 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); 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) 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); 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) 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); 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); 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); 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) 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); 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) public void Linear(ref Color a, ref Color b, float alpha, out Color result)
@@ -454,6 +456,40 @@ namespace FlaxEngine
time = end; time = end;
} }
} }
/// <summary>
/// Raw memory copy (used by scripting bindings - see MarshalAs tag).
/// </summary>
/// <param name="keyframes">The keyframes array.</param>
/// <returns>The raw keyframes data.</returns>
protected static unsafe byte[] MarshalKeyframes<Keyframe>(Keyframe[] keyframes)
{
if (keyframes == null || keyframes.Length == 0)
return null;
var keyframeSize = Unsafe.SizeOf<Keyframe>();
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;
}
/// <summary>
/// Raw memory copy (used by scripting bindings - see MarshalAs tag).
/// </summary>
/// <param name="raw">The raw keyframes data.</param>
/// <returns>The keyframes array.</returns>
protected static unsafe Keyframe[] MarshalKeyframes<Keyframe>(byte[] raw) where Keyframe : struct
{
if (raw == null || raw.Length == 0)
return null;
fixed (byte* rawPtr = raw)
return MemoryMarshal.Cast<byte, Keyframe>(new Span<byte>(rawPtr, raw.Length)).ToArray();
}
} }
/// <summary> /// <summary>
@@ -709,6 +745,30 @@ namespace FlaxEngine
leftKey = Mathf.Max(0, start - 1); leftKey = Mathf.Max(0, start - 1);
rightKey = Mathf.Min(start, Keyframes.Length - 1); rightKey = Mathf.Min(start, Keyframes.Length - 1);
} }
/// <summary>
/// Raw memory copy (used by scripting bindings - see MarshalAs tag).
/// </summary>
/// <param name="curve">The curve to copy.</param>
/// <returns>The raw keyframes data.</returns>
public static unsafe implicit operator byte[](LinearCurve<T> curve)
{
if (curve == null)
return null;
return MarshalKeyframes<Keyframe>(curve.Keyframes);
}
/// <summary>
/// Raw memory copy (used by scripting bindings - see MarshalAs tag).
/// </summary>
/// <param name="raw">The raw keyframes data.</param>
/// <returns>The curve.</returns>
public static unsafe implicit operator LinearCurve<T>(byte[] raw)
{
if (raw == null || raw.Length == 0)
return null;
return new LinearCurve<T>(MarshalKeyframes<Keyframe>(raw));
}
} }
/// <summary> /// <summary>
@@ -860,9 +920,9 @@ namespace FlaxEngine
// Evaluate the key at the curve // Evaluate the key at the curve
result.Time = leftKey.Time + length * t; result.Time = leftKey.Time + length * t;
float lengthThird = length / 3.0f; float tangentScale = length / 3.0f;
_accessor.GetTangent(ref leftKey.Value, ref leftKey.TangentOut, lengthThird, out var leftTangent); _accessor.GetTangent(ref leftKey.Value, ref leftKey.TangentOut, tangentScale, out var leftTangent);
_accessor.GetTangent(ref rightKey.Value, ref rightKey.TangentIn, lengthThird, out var rightTangent); _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); _accessor.Bezier(ref leftKey.Value, ref leftTangent, ref rightTangent, ref rightKey.Value, t, out result.Value);
result.TangentIn = leftKey.TangentOut; result.TangentIn = leftKey.TangentOut;
result.TangentOut = rightKey.TangentIn; result.TangentOut = rightKey.TangentIn;
@@ -895,9 +955,9 @@ namespace FlaxEngine
float t = Mathf.NearEqual(length, 0.0f) ? 0.0f : (time - leftKey.Time) / length; float t = Mathf.NearEqual(length, 0.0f) ? 0.0f : (time - leftKey.Time) / length;
// Evaluate the value at the curve // Evaluate the value at the curve
float lengthThird = length / 3.0f; float tangentScale = length / 3.0f;
_accessor.GetTangent(ref leftKey.Value, ref leftKey.TangentOut, lengthThird, out var leftTangent); _accessor.GetTangent(ref leftKey.Value, ref leftKey.TangentOut, tangentScale, out var leftTangent);
_accessor.GetTangent(ref rightKey.Value, ref rightKey.TangentIn, lengthThird, out var rightTangent); _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); _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); leftKey = Mathf.Max(0, start - 1);
rightKey = Mathf.Min(start, Keyframes.Length - 1); rightKey = Mathf.Min(start, Keyframes.Length - 1);
} }
/// <summary>
/// Raw memory copy (used by scripting bindings - see MarshalAs tag).
/// </summary>
/// <param name="curve">The curve to copy.</param>
/// <returns>The raw keyframes data.</returns>
public static unsafe implicit operator byte[](BezierCurve<T> curve)
{
if (curve == null)
return null;
return MarshalKeyframes<Keyframe>(curve.Keyframes);
}
/// <summary>
/// Raw memory copy (used by scripting bindings - see MarshalAs tag).
/// </summary>
/// <param name="raw">The raw keyframes data.</param>
/// <returns>The curve.</returns>
public static unsafe implicit operator BezierCurve<T>(byte[] raw)
{
if (raw == null || raw.Length == 0)
return null;
return new BezierCurve<T>(MarshalKeyframes<Keyframe>(raw));
}
} }
} }

View File

@@ -247,16 +247,18 @@ public:
static void Interpolate(const BezierCurveKeyframe& a, const BezierCurveKeyframe& b, float alpha, float length, T& result) static void Interpolate(const BezierCurveKeyframe& a, const BezierCurveKeyframe& b, float alpha, float length, T& result)
{ {
T leftTangent, rightTangent; T leftTangent, rightTangent;
AnimationUtils::GetTangent(a.Value, a.TangentOut, length, leftTangent); const float tangentScale = length / 3.0f;
AnimationUtils::GetTangent(b.Value, b.TangentIn, length, rightTangent); 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); 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) static void InterpolateFirstDerivative(const BezierCurveKeyframe& a, const BezierCurveKeyframe& b, float alpha, float length, T& result)
{ {
T leftTangent, rightTangent; T leftTangent, rightTangent;
AnimationUtils::GetTangent(a.Value, a.TangentOut, length, leftTangent); const float tangentScale = length / 3.0f;
AnimationUtils::GetTangent(b.Value, b.TangentIn, length, rightTangent); 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); AnimationUtils::BezierFirstDerivative(a.Value, leftTangent, rightTangent, b.Value, alpha, result);
} }
@@ -264,8 +266,9 @@ public:
{ {
result.Time = a.Time + length * alpha; result.Time = a.Time + length * alpha;
T leftTangent, rightTangent; T leftTangent, rightTangent;
AnimationUtils::GetTangent(a.Value, a.TangentOut, length, leftTangent); const float tangentScale = length / 3.0f;
AnimationUtils::GetTangent(b.Value, b.TangentIn, length, rightTangent); 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); AnimationUtils::Bezier(a.Value, leftTangent, rightTangent, b.Value, alpha, result.Value);
result.TangentIn = a.TangentOut; result.TangentIn = a.TangentOut;
result.TangentOut = b.TangentIn; 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. /// An animation spline represented by a set of keyframes, each representing an endpoint of a curve.
/// </summary> /// </summary>
template<class T, typename KeyFrame = LinearCurveKeyframe<T>> template<class T, typename KeyFrame = LinearCurveKeyframe<T>>
class Curve : public CurveBase<T, KeyFrame> API_CLASS(InBuild, Template, MarshalAs=Span<byte>) class Curve : public CurveBase<T, KeyFrame>
{ {
public: public:
typedef CurveBase<T, KeyFrame> Base; typedef CurveBase<T, KeyFrame> Base;
@@ -760,28 +763,42 @@ public:
} }
return true; return true;
} }
// Raw memory copy (used by scripting bindings - see MarshalAs tag).
Curve& operator=(const Span<byte>& 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<byte>()
{
return Span<byte>((const byte*)_keyframes.Get(), _keyframes.Count() * sizeof(KeyFrame));
}
}; };
/// <summary> /// <summary>
/// An animation spline represented by a set of keyframes, each representing a value point. /// An animation spline represented by a set of keyframes, each representing a value point.
/// </summary> /// </summary>
template<typename T> template<typename T>
using StepCurve = Curve<T, StepCurveKeyframe<T>>; API_TYPEDEF() using StepCurve = Curve<T, StepCurveKeyframe<T>>;
/// <summary> /// <summary>
/// An animation spline represented by a set of keyframes, each representing an endpoint of a linear curve. /// An animation spline represented by a set of keyframes, each representing an endpoint of a linear curve.
/// </summary> /// </summary>
template<typename T> template<typename T>
using LinearCurve = Curve<T, LinearCurveKeyframe<T>>; API_TYPEDEF() using LinearCurve = Curve<T, LinearCurveKeyframe<T>>;
/// <summary> /// <summary>
/// An animation spline represented by a set of keyframes, each representing an endpoint of a cubic hermite curve. /// An animation spline represented by a set of keyframes, each representing an endpoint of a cubic hermite curve.
/// </summary> /// </summary>
template<typename T> template<typename T>
using HermiteCurve = Curve<T, HermiteCurveKeyframe<T>>; API_TYPEDEF() using HermiteCurve = Curve<T, HermiteCurveKeyframe<T>>;
/// <summary> /// <summary>
/// An animation spline represented by a set of keyframes, each representing an endpoint of Bezier curve. /// An animation spline represented by a set of keyframes, each representing an endpoint of Bezier curve.
/// </summary> /// </summary>
template<typename T> template<typename T>
using BezierCurve = Curve<T, BezierCurveKeyframe<T>>; API_TYPEDEF() using BezierCurve = Curve<T, BezierCurveKeyframe<T>>;

View File

@@ -2263,6 +2263,14 @@ void VisualScript::GetMethodSignature(int32 index, String& name, byte& flags, St
} }
} }
Variant VisualScript::InvokeMethod(int32 index, const Variant& instance, Span<Variant> parameters) const
{
auto& method = _methods[index];
Variant result;
VisualScriptingModule.InvokeMethod((void*)&method, instance, parameters, result);
return result;
}
Span<byte> VisualScript::GetMetaData(int32 typeID) Span<byte> VisualScript::GetMetaData(int32 typeID)
{ {
auto meta = Graph.Meta.GetEntry(typeID); auto meta = Graph.Meta.GetEntry(typeID);

View File

@@ -267,6 +267,9 @@ public:
// Gets the signature data of the method. // 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<String>& paramNames, API_PARAM(Out) Array<String>& paramTypeNames, API_PARAM(Out) Array<bool>& paramOuts); 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<String>& paramNames, API_PARAM(Out) Array<String>& paramTypeNames, API_PARAM(Out) Array<bool>& paramOuts);
// Invokes the method.
API_FUNCTION() Variant InvokeMethod(int32 index, const Variant& instance, Span<Variant> parameters) const;
// Gets the metadata of the script surface. // Gets the metadata of the script surface.
API_FUNCTION() Span<byte> GetMetaData(int32 typeID); API_FUNCTION() Span<byte> GetMetaData(int32 typeID);

View File

@@ -21,8 +21,8 @@ namespace FlaxEditor.Content.Settings
{ {
new BuildTarget new BuildTarget
{ {
Name = "Windows 64bit", Name = "Windows",
Output = "Output\\Win64", Output = "Output\\Windows",
Platform = BuildPlatform.Windows64, Platform = BuildPlatform.Windows64,
Mode = BuildConfiguration.Development, Mode = BuildConfiguration.Development,
}, },
@@ -35,8 +35,8 @@ namespace FlaxEditor.Content.Settings
{ {
new BuildTarget new BuildTarget
{ {
Name = "Windows 64bit", Name = "Windows",
Output = "Output\\Win64", Output = "Output\\Windows",
Platform = BuildPlatform.Windows64, Platform = BuildPlatform.Windows64,
Mode = BuildConfiguration.Release, Mode = BuildConfiguration.Release,
}, },

View File

@@ -16,7 +16,7 @@ namespace FlaxEditor.Content.Settings
public List<string> Tags = new List<string>(); public List<string> Tags = new List<string>();
/// <summary> /// <summary>
/// The layers names. /// The layer names.
/// </summary> /// </summary>
[EditorOrder(10), EditorDisplay("Layers", EditorDisplayAttribute.InlineStyle), Collection(CanResize = false, Display = CollectionAttribute.DisplayType.Inline)] [EditorOrder(10), EditorDisplay("Layers", EditorDisplayAttribute.InlineStyle), Collection(CanResize = false, Display = CollectionAttribute.DisplayType.Inline)]
public string[] Layers = new string[32]; public string[] Layers = new string[32];
@@ -30,6 +30,31 @@ namespace FlaxEditor.Content.Settings
return GetCurrentLayers(out int _); return GetCurrentLayers(out int _);
} }
/// <summary>
/// The layer names.
/// </summary>
[EditorOrder(10), EditorDisplay("Terrain Layers", EditorDisplayAttribute.InlineStyle), Collection(CanResize = false, Display = CollectionAttribute.DisplayType.Inline)]
public string[] TerrainLayers = new string[8];
/// <summary>
/// Gets the current terrain layer names. Returns "Layer" + index for layers without a name.
/// </summary>
/// <returns>The layer names.</returns>
public static string[] GetCurrentTerrainLayers()
{
#if FLAX_TESTS
return System.Array.Empty<string>();
#else
string[] layerNames = GameSettings.Load<LayersAndTagsSettings>().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))] [LibraryImport("FlaxEngine", EntryPoint = "LayersAndTagsSettingsInternal_GetCurrentLayers", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.Interop.StringMarshaller))]
[return: MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = "layerCount")] [return: MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = "layerCount")]
internal static partial string[] GetCurrentLayers(out int layerCount); internal static partial string[] GetCurrentLayers(out int layerCount);

View File

@@ -16,10 +16,14 @@
#include <iostream> #include <iostream>
#define LOG_ENABLE_FILE (!PLATFORM_SWITCH) #define LOG_ENABLE_FILE (!PLATFORM_SWITCH)
#define LOG_ENABLE_WINDOWS_SINGLE_NEW_LINE_CHAR (PLATFORM_WINDOWS && PLATFORM_DESKTOP && (USE_EDITOR || !BUILD_RELEASE))
namespace namespace
{ {
bool LogAfterInit = false, IsDuringLog = false; bool LogAfterInit = false, IsDuringLog = false;
#if LOG_ENABLE_WINDOWS_SINGLE_NEW_LINE_CHAR
bool IsWindowsSingleNewLineChar = false;
#endif
int LogTotalErrorsCnt = 0; int LogTotalErrorsCnt = 0;
FileWriteStream* LogFile = nullptr; FileWriteStream* LogFile = nullptr;
CriticalSection LogLocker; CriticalSection LogLocker;
@@ -86,6 +90,11 @@ bool Log::Logger::Init()
} }
LogTotalErrorsCnt = 0; LogTotalErrorsCnt = 0;
LogAfterInit = true; 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) // Write BOM (UTF-16 (LE); BOM: FF FE)
byte bom[] = { 0xFF, 0xFE }; byte bom[] = { 0xFF, 0xFE };
@@ -127,6 +136,11 @@ void Log::Logger::Write(const StringView& msg)
printf("%s", ansi.Get()); printf("%s", ansi.Get());
#else #else
std::wcout.write(ptr, length); 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); std::wcout.write(TEXT(PLATFORM_LINE_TERMINATOR), ARRAY_COUNT(PLATFORM_LINE_TERMINATOR) - 1);
#endif #endif
} }

View File

@@ -124,9 +124,9 @@ void BoundingBox::Transform(const BoundingBox& box, const Matrix& matrix, Boundi
const auto ya = up * box.Minimum.Y; const auto ya = up * box.Minimum.Y;
const auto yb = up * box.Maximum.Y; const auto yb = up * box.Maximum.Y;
const auto backward = matrix.GetBackward(); const auto forward = matrix.GetForward();
const auto za = backward * box.Minimum.Z; const auto za = forward * box.Minimum.Z;
const auto zb = backward * box.Maximum.Z; const auto zb = forward * box.Maximum.Z;
const auto translation = matrix.GetTranslation(); const auto translation = matrix.GetTranslation();
const auto min = Vector3::Min(xa, xb) + Vector3::Min(ya, yb) + Vector3::Min(za, zb) + translation; 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 ya = up * box.Minimum.Y;
const auto yb = up * box.Maximum.Y; const auto yb = up * box.Maximum.Y;
const auto backward = Float3::Transform(Float3::Backward, transform.Orientation); const auto forward = Float3::Transform(Float3::Forward, transform.Orientation);
const auto za = backward * box.Minimum.Z; const auto za = forward * box.Minimum.Z;
const auto zb = backward * box.Maximum.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 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; const auto max = Vector3::Max(xa, xb) + Vector3::Max(ya, yb) + Vector3::Max(za, zb) + transform.Translation;

View File

@@ -474,9 +474,9 @@ namespace FlaxEngine
var ya = up * box.Minimum.Y; var ya = up * box.Minimum.Y;
var yb = up * box.Maximum.Y; var yb = up * box.Maximum.Y;
Double3 backward = transform.Backward; Double3 forward = transform.Forward;
var za = backward * box.Minimum.Z; var za = forward * box.Minimum.Z;
var zb = backward * box.Maximum.Z; var zb = forward * box.Maximum.Z;
var translation = transform.TranslationVector; var translation = transform.TranslationVector;
var min = Vector3.Min(xa, xb) + Vector3.Min(ya, yb) + Vector3.Min(za, zb) + translation; 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 ya = up * box.Minimum.Y;
var yb = up * box.Maximum.Y; var yb = up * box.Maximum.Y;
Double3 backward = transform.Backward; Double3 forward = transform.Forward;
var za = backward * box.Minimum.Z; var za = forward * box.Minimum.Z;
var zb = backward * box.Maximum.Z; var zb = forward * box.Maximum.Z;
var min = Vector3.Min(xa, xb) + Vector3.Min(ya, yb) + Vector3.Min(za, zb) + transform.Translation; 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; var max = Vector3.Max(xa, xb) + Vector3.Max(ya, yb) + Vector3.Max(za, zb) + transform.Translation;

View File

@@ -136,12 +136,12 @@ void Matrix::Decompose(Float3& scale, Matrix3x3& rotation, Float3& translation)
const auto right = Float3::Cross(up, at); const auto right = Float3::Cross(up, at);
rotation.SetRight(right); rotation.SetRight(right);
rotation.SetUp(up); rotation.SetUp(up);
rotation.SetBackward(at); rotation.SetForward(at);
// In case of reflexions // In case of reflexions
scale.X = Float3::Dot(right, GetRight()) > 0.0f ? scale.X : -scale.X; 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.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 void Matrix::Decompose(Float3& scale, Matrix& rotation, Float3& translation) const

View File

@@ -215,23 +215,9 @@ namespace FlaxEngine
} }
/// <summary> /// <summary>
/// Gets or sets the forward <see cref="Float3" /> of the matrix; that is -M31, -M32, and -M33. /// Gets or sets the forward <see cref="Float3" /> of the matrix; that is M31, M32, and M33.
/// </summary> /// </summary>
public Float3 Forward public Float3 Forward
{
get => new Float3(-M31, -M32, -M33);
set
{
M31 = -value.X;
M32 = -value.Y;
M33 = -value.Z;
}
}
/// <summary>
/// Gets or sets the backward <see cref="Float3" /> of the matrix; that is M31, M32, and M33.
/// </summary>
public Float3 Backward
{ {
get => new Float3(M31, M32, M33); get => new Float3(M31, M32, M33);
set set
@@ -242,6 +228,20 @@ namespace FlaxEngine
} }
} }
/// <summary>
/// Gets or sets the backward <see cref="Float3" /> of the matrix; that is -M31, -M32, and -M33.
/// </summary>
public Float3 Backward
{
get => new Float3(-M31, -M32, -M33);
set
{
M31 = -value.X;
M32 = -value.Y;
M33 = -value.Z;
}
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Matrix" /> struct. /// Initializes a new instance of the <see cref="Matrix" /> struct.
/// </summary> /// </summary>

View File

@@ -210,31 +210,31 @@ public:
// 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 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. // Sets the forward Float3 of the matrix; that is -M31, -M32, and -M33.
void SetForward(const Float3& value) 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; M31 = value.X;
M32 = value.Y; M32 = value.Y;
M33 = value.Z; 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. // Gets the first row in the matrix; that is M11, M12, M13, and M14.
Float4 GetRow1() const Float4 GetRow1() const
{ {

View File

@@ -225,7 +225,7 @@ void Matrix3x3::Decompose(Float3& scale, Matrix3x3& rotation) const
const auto right = Float3::Cross(up, at); const auto right = Float3::Cross(up, at);
rotation.SetRight(right); rotation.SetRight(right);
rotation.SetUp(up); rotation.SetUp(up);
rotation.SetBackward(at); rotation.SetForward(at);
// In case of reflexions // In case of reflexions
scale.X = Float3::Dot(right, GetRight()) > 0.0f ? scale.X : -scale.X; scale.X = Float3::Dot(right, GetRight()) > 0.0f ? scale.X : -scale.X;

View File

@@ -303,9 +303,6 @@ namespace FlaxEngine
/// <summary> /// <summary>
/// Gets a value indicating whether this instance is an identity Matrix3x3. /// Gets a value indicating whether this instance is an identity Matrix3x3.
/// </summary> /// </summary>
/// <value>
/// <c>true</c> if this instance is an identity Matrix3x3; otherwise, <c>false</c>.
/// </value>
public bool IsIdentity => Equals(Identity); public bool IsIdentity => Equals(Identity);
/// <summary> /// <summary>
@@ -566,19 +563,19 @@ namespace FlaxEngine
/// </remarks> /// </remarks>
public bool DecomposeUniformScale(out float scale, out Quaternion rotation) 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)); scale = (float)Math.Sqrt((M11 * M11) + (M12 * M12) + (M13 * M13));
var invScale = 1f / scale; 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) if (Math.Abs(scale) < Mathf.Epsilon)
{ {
rotation = Quaternion.Identity; rotation = Quaternion.Identity;
return false; return false;
} }
//The rotation is the left over matrix after dividing out the scaling. // The rotation is the leftover matrix after dividing out the scaling
Matrix3x3 rotationmatrix = new Matrix3x3 var rotationMatrix = new Matrix3x3
{ {
M11 = M11 * invScale, M11 = M11 * invScale,
M12 = M12 * invScale, M12 = M12 * invScale,
@@ -590,8 +587,7 @@ namespace FlaxEngine
M32 = M32 * invScale, M32 = M32 * invScale,
M33 = M33 * invScale M33 = M33 * invScale
}; };
Quaternion.RotationMatrix(ref rotationMatrix, out rotation);
Quaternion.RotationMatrix(ref rotationmatrix, out rotation);
return true; return true;
} }

View File

@@ -175,34 +175,34 @@ public:
M13 = -value.Z; 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 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. // Sets the forward Float3 of the matrix; that is M31, M32, and M33.
void SetForward(const Float3& value) 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; M31 = value.X;
M32 = value.Y; M32 = value.Y;
M33 = value.Z; 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. // Gets the first row in the matrix; that is M11, M12 and M13.
Float3 GetRow1() const Float3 GetRow1() const
{ {

View File

@@ -1827,6 +1827,7 @@ Variant::operator Float4() const
return Float4(*(Float3*)AsData, 0.0f); return Float4(*(Float3*)AsData, 0.0f);
case VariantType::Float4: case VariantType::Float4:
case VariantType::Color: case VariantType::Color:
case VariantType::Quaternion:
return *(Float4*)AsData; return *(Float4*)AsData;
case VariantType::Double2: case VariantType::Double2:
return Float4(AsDouble2(), 0.0f, 0.0f); return Float4(AsDouble2(), 0.0f, 0.0f);

View File

@@ -94,6 +94,13 @@ struct DebugLine
float TimeLeft; float TimeLeft;
}; };
struct DebugGeometryBuffer
{
GPUBuffer* Buffer;
float TimeLeft;
Matrix Transform;
};
struct DebugTriangle struct DebugTriangle
{ {
Float3 V0; Float3 V0;
@@ -122,12 +129,9 @@ struct DebugText3D
float TimeLeft; float TimeLeft;
}; };
PACK_STRUCT(struct Vertex { typedef DebugDraw::Vertex Vertex;
Float3 Position;
Color32 Color;
});
GPU_CB_STRUCT(Data { GPU_CB_STRUCT(ShaderData {
Matrix ViewProjection; Matrix ViewProjection;
Float2 Padding; Float2 Padding;
float ClipPosZBias; float ClipPosZBias;
@@ -231,6 +235,7 @@ void TeleportList(const Float3& delta, Array<DebugText3D>& list)
struct DebugDrawData struct DebugDrawData
{ {
Array<DebugGeometryBuffer> GeometryBuffers;
Array<DebugLine> DefaultLines; Array<DebugLine> DefaultLines;
Array<Vertex> OneFrameLines; Array<Vertex> OneFrameLines;
Array<DebugTriangle> DefaultTriangles; Array<DebugTriangle> DefaultTriangles;
@@ -244,7 +249,7 @@ struct DebugDrawData
inline int32 Count() const inline int32 Count() const
{ {
return LinesCount() + TrianglesCount() + TextCount(); return LinesCount() + TrianglesCount() + TextCount() + GeometryBuffers.Count();
} }
inline int32 LinesCount() const inline int32 LinesCount() const
@@ -280,6 +285,7 @@ struct DebugDrawData
inline void Update(float deltaTime) inline void Update(float deltaTime)
{ {
UpdateList(deltaTime, GeometryBuffers);
UpdateList(deltaTime, DefaultLines); UpdateList(deltaTime, DefaultLines);
UpdateList(deltaTime, DefaultTriangles); UpdateList(deltaTime, DefaultTriangles);
UpdateList(deltaTime, DefaultWireTriangles); UpdateList(deltaTime, DefaultWireTriangles);
@@ -784,7 +790,7 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
// Update constant buffer // Update constant buffer
const auto cb = DebugDrawShader->GetShader()->GetCB(0); const auto cb = DebugDrawShader->GetShader()->GetCB(0);
Data data; ShaderData data;
Matrix vp; Matrix vp;
Matrix::Multiply(view.View, view.Projection, vp); Matrix::Multiply(view.View, view.Projection, vp);
Matrix::Transpose(vp, data.ViewProjection); Matrix::Transpose(vp, data.ViewProjection);
@@ -830,6 +836,22 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
context->Draw(depthTestTriangles.StartVertex, depthTestTriangles.VertexCount); 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) if (data.EnableDepthTest)
context->UnBindSR(0); context->UnBindSR(0);
} }
@@ -862,6 +884,19 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
context->BindVB(ToSpan(&vb, 1)); context->BindVB(ToSpan(&vb, 1));
context->Draw(defaultTriangles.StartVertex, defaultTriangles.VertexCount); 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 // Text
@@ -1088,6 +1123,24 @@ void DebugDraw::DrawLines(const Span<Float3>& 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<Float3>& lines, const Matrix& transform, const Color& color, float duration, bool depthTest) void DebugDraw::DrawLines(const Array<Float3>& lines, const Matrix& transform, const Color& color, float duration, bool depthTest)
{ {
DrawLines(Span<Float3>(lines.Get(), lines.Count()), transform, color, duration, depthTest); DrawLines(Span<Float3>(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) void DebugDraw::Clear(void* context)
{ {
DebugDraw::UpdateContext(context, MAX_float); UpdateContext(context, MAX_float);
} }
#endif #endif

View File

@@ -6,6 +6,8 @@
#include "Engine/Scripting/ScriptingType.h" #include "Engine/Scripting/ScriptingType.h"
#include "Engine/Core/Math/Color.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" #include "Engine/Core/Types/Span.h"
struct RenderView; struct RenderView;
@@ -14,6 +16,7 @@ class Light;
struct RenderContext; struct RenderContext;
class GPUTextureView; class GPUTextureView;
class GPUContext; class GPUContext;
class GPUBuffer;
class RenderTask; class RenderTask;
class SceneRenderTask; class SceneRenderTask;
class Actor; class Actor;
@@ -26,6 +29,14 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
{ {
DECLARE_SCRIPTING_TYPE_NO_SPAWN(DebugDraw); DECLARE_SCRIPTING_TYPE_NO_SPAWN(DebugDraw);
/// <summary>
/// Vertex data for debug shapes.
/// </summary>
PACK_STRUCT(struct Vertex {
Float3 Position;
Color32 Color;
});
#if USE_EDITOR #if USE_EDITOR
/// <summary> /// <summary>
/// Allocates the context for Debug Drawing. Can be use to redirect debug shapes collecting to a separate container (instead of global state). /// 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
/// <param name="depthTest">If set to <c>true</c> depth test will be performed, otherwise depth will be ignored.</param> /// <param name="depthTest">If set to <c>true</c> depth test will be performed, otherwise depth will be ignored.</param>
API_FUNCTION() static void DrawLines(const Span<Float3>& lines, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); API_FUNCTION() static void DrawLines(const Span<Float3>& lines, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true);
/// <summary>
/// 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,...).
/// </summary>
/// <param name="lines">The GPU buffer with vertices for lines (must have multiple of 2 elements).</param>
/// <param name="transform">The custom matrix used to transform all line vertices.</param>
/// <param name="duration">The duration (in seconds). Use 0 to draw it only once.</param>
/// <param name="depthTest">If set to <c>true</c> depth test will be performed, otherwise depth will be ignored.</param>
API_FUNCTION() static void DrawLines(GPUBuffer* lines, const Matrix& transform, float duration = 0.0f, bool depthTest = true);
/// <summary> /// <summary>
/// Draws the lines. Line positions are located one after another (e.g. l0.start, l0.end, l1.start, l1.end,...). /// Draws the lines. Line positions are located one after another (e.g. l0.start, l0.end, l1.start, l1.end,...).
/// </summary> /// </summary>
@@ -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); API_FUNCTION() static void DrawText(const StringView& text, const Transform& transform, const Color& color = Color::White, int32 size = 32, float duration = 0.0f);
/// <summary> /// <summary>
/// Clear all debug draw displayed on sceen. /// Clears all debug shapes displayed on screen.
/// </summary> /// </summary>
/// <returns></returns> /// <param name="context">The context.</param>
API_FUNCTION() static void Clear(void* context = nullptr); API_FUNCTION() static void Clear(void* context = nullptr);
}; };

View File

@@ -154,6 +154,7 @@ bool CommandLine::Parse(const Char* cmdLine)
PARSE_ARG_SWITCH("-build ", Build); PARSE_ARG_SWITCH("-build ", Build);
PARSE_BOOL_SWITCH("-skipcompile ", SkipCompile); PARSE_BOOL_SWITCH("-skipcompile ", SkipCompile);
PARSE_BOOL_SWITCH("-shaderdebug ", ShaderDebug); PARSE_BOOL_SWITCH("-shaderdebug ", ShaderDebug);
PARSE_BOOL_SWITCH("-exit ", Exit);
PARSE_ARG_OPT_SWITCH("-play ", Play); PARSE_ARG_OPT_SWITCH("-play ", Play);
#endif #endif
#if USE_EDITOR || !BUILD_RELEASE #if USE_EDITOR || !BUILD_RELEASE

View File

@@ -168,6 +168,11 @@ public:
/// </summary> /// </summary>
Nullable<bool> ShaderDebug; Nullable<bool> ShaderDebug;
/// <summary>
/// -exit (exits the editor after startup and performing all queued actions). Usefull when invoking editor from CL/CD.
/// </summary>
Nullable<bool> Exit;
/// <summary> /// <summary>
/// -play !guid! ( Scene to play, can be empty to use default ) /// -play !guid! ( Scene to play, can be empty to use default )
/// </summary> /// </summary>

View File

@@ -5,6 +5,7 @@
#include "Engine/Core/Log.h" #include "Engine/Core/Log.h"
#include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPUDevice.h"
#include "Engine/Threading/Threading.h" #include "Engine/Threading/Threading.h"
#include "Engine/Engine/Globals.h"
#define GPU_TASKS_USE_DEDICATED_CONTEXT 0 #define GPU_TASKS_USE_DEDICATED_CONTEXT 0
@@ -36,7 +37,8 @@ GPUTasksContext::~GPUTasksContext()
auto task = tasks[i]; auto task = tasks[i];
if (task->GetSyncPoint() <= _currentSyncPoint && task->GetState() != TaskState::Finished) 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(); task->CancelSync();
} }
} }
@@ -60,7 +62,8 @@ void GPUTasksContext::OnCancelSync(GPUTask* task)
_tasksDone.Remove(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() void GPUTasksContext::OnFrameBegin()

View File

@@ -5,6 +5,7 @@
#include "GPUResourceProperty.h" #include "GPUResourceProperty.h"
#include "GPUBufferDescription.h" #include "GPUBufferDescription.h"
#include "PixelFormatExtensions.h" #include "PixelFormatExtensions.h"
#include "RenderTask.h"
#include "Async/Tasks/GPUCopyResourceTask.h" #include "Async/Tasks/GPUCopyResourceTask.h"
#include "Engine/Core/Utilities.h" #include "Engine/Core/Utilities.h"
#include "Engine/Core/Types/String.h" #include "Engine/Core/Types/String.h"
@@ -358,6 +359,16 @@ void GPUBuffer::SetData(const void* data, uint32 size)
Log::ArgumentOutOfRangeException(TEXT("Buffer.SetData")); Log::ArgumentOutOfRangeException(TEXT("Buffer.SetData"));
return; 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); void* mapped = Map(GPUResourceMapMode::Write);
if (!mapped) if (!mapped)
return; return;

View File

@@ -74,8 +74,7 @@ public:
/// </summary> /// </summary>
API_PROPERTY() FORCE_INLINE uint32 GetElementsCount() const API_PROPERTY() FORCE_INLINE uint32 GetElementsCount() const
{ {
ASSERT(_desc.Stride > 0); return _desc.Stride > 0 ? _desc.Size / _desc.Stride : 0;
return _desc.Size / _desc.Stride;
} }
/// <summary> /// <summary>

View File

@@ -282,35 +282,35 @@ void MaterialParameter::Bind(BindMeta& meta) const
switch (_type) switch (_type)
{ {
case MaterialParameterType::Bool: 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; *((int32*)(meta.Constants.Get() + _offset)) = _asBool;
break; break;
case MaterialParameterType::Integer: 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; *((int32*)(meta.Constants.Get() + _offset)) = _asInteger;
break; break;
case MaterialParameterType::Float: 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; *((float*)(meta.Constants.Get() + _offset)) = _asFloat;
break; break;
case MaterialParameterType::Vector2: 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; *((Float2*)(meta.Constants.Get() + _offset)) = _asVector2;
break; break;
case MaterialParameterType::Vector3: 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; *((Float3*)(meta.Constants.Get() + _offset)) = _asVector3;
break; break;
case MaterialParameterType::Vector4: 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; *((Float4*)(meta.Constants.Get() + _offset)) = *(Float4*)&AsData;
break; break;
case MaterialParameterType::Color: 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; *((Color*)(meta.Constants.Get() + _offset)) = _asColor;
break; break;
case MaterialParameterType::Matrix: 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)); Matrix::Transpose(*(Matrix*)&AsData, *(Matrix*)(meta.Constants.Get() + _offset));
break; break;
case MaterialParameterType::NormalMap: case MaterialParameterType::NormalMap:
@@ -409,44 +409,44 @@ void MaterialParameter::Bind(BindMeta& meta) const
switch (e->Value.Type.Type) switch (e->Value.Type.Type)
{ {
case VariantType::Bool: 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; *((bool*)(meta.Constants.Get() + _offset)) = e->Value.AsBool;
break; break;
case VariantType::Int: 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; *((int32*)(meta.Constants.Get() + _offset)) = e->Value.AsInt;
break; break;
case VariantType::Uint: 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; *((uint32*)(meta.Constants.Get() + _offset)) = e->Value.AsUint;
break; break;
case VariantType::Float: 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; *((float*)(meta.Constants.Get() + _offset)) = e->Value.AsFloat;
break; break;
case VariantType::Float2: 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(); *((Float2*)(meta.Constants.Get() + _offset)) = e->Value.AsFloat2();
break; break;
case VariantType::Float3: 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(); *((Float3*)(meta.Constants.Get() + _offset)) = e->Value.AsFloat3();
break; break;
case VariantType::Float4: case VariantType::Float4:
case VariantType::Color: 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(); *((Float4*)(meta.Constants.Get() + _offset)) = e->Value.AsFloat4();
break; break;
case VariantType::Double2: 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(); *((Float2*)(meta.Constants.Get() + _offset)) = (Float2)e->Value.AsDouble2();
break; break;
case VariantType::Double3: 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(); *((Float3*)(meta.Constants.Get() + _offset)) = (Float3)e->Value.AsDouble3();
break; break;
case VariantType::Double4: 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(); *((Float4*)(meta.Constants.Get() + _offset)) = (Float4)e->Value.AsDouble4();
break; break;
default: ; default: ;

View File

@@ -85,7 +85,7 @@ void ParticleMaterialShader::Bind(BindParameters& params)
{ {
const StringView name(param.GetName().Get() + 9, param.GetName().Length() - 9); const StringView name(param.GetName().Get() + 9, param.GetName().Length() - 9);
const int32 offset = drawCall.Particle.Particles->Layout->FindAttributeOffset(name); 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; *((int32*)(bindMeta.Constants.Get() + param.GetBindOffset())) = offset;
} }
} }

View File

@@ -68,7 +68,7 @@ void VolumeParticleMaterialShader::Bind(BindParameters& params)
{ {
const StringView name(param.GetName().Get() + 9, param.GetName().Length() - 9); const StringView name(param.GetName().Get() + 9, param.GetName().Length() - 9);
const int32 offset = drawCall.Particle.Particles->Layout->FindAttributeOffset(name); 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; *((int32*)(bindMeta.Constants.Get() + param.GetBindOffset())) = offset;
} }
} }

View File

@@ -285,7 +285,10 @@ void GPUContextDX11::SetBlendFactor(const Float4& value)
void GPUContextDX11::SetStencilRef(uint32 value) void GPUContextDX11::SetStencilRef(uint32 value)
{ {
if (CurrentStencilRef != value) if (CurrentStencilRef != value)
{
CurrentStencilRef = value;
_context->OMSetDepthStencilState(CurrentDepthStencilState, CurrentStencilRef); _context->OMSetDepthStencilState(CurrentDepthStencilState, CurrentStencilRef);
}
} }
void GPUContextDX11::ResetSR() void GPUContextDX11::ResetSR()

View File

@@ -158,10 +158,10 @@ float Spline::GetSplineLength() const
const auto& b = Curve[i]; const auto& b = Curve[i];
Vector3 prevPoint = a.Value.Translation * 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; Vector3 leftTangent, rightTangent;
AnimationUtils::GetTangent(a.Value.Translation, a.TangentOut.Translation, length, leftTangent); AnimationUtils::GetTangent(a.Value.Translation, a.TangentOut.Translation, tangentScale, leftTangent);
AnimationUtils::GetTangent(b.Value.Translation, b.TangentIn.Translation, length, rightTangent); AnimationUtils::GetTangent(b.Value.Translation, b.TangentIn.Translation, tangentScale, rightTangent);
for (int32 slice = 1; slice < slices; slice++) for (int32 slice = 1; slice < slices; slice++)
{ {
@@ -189,10 +189,10 @@ float Spline::GetSplineSegmentLength(int32 index) const
const Vector3 scale = _transform.Scale; const Vector3 scale = _transform.Scale;
Vector3 prevPoint = a.Value.Translation * 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; Vector3 leftTangent, rightTangent;
AnimationUtils::GetTangent(a.Value.Translation, a.TangentOut.Translation, length, leftTangent); AnimationUtils::GetTangent(a.Value.Translation, a.TangentOut.Translation, tangentScale, leftTangent);
AnimationUtils::GetTangent(b.Value.Translation, b.TangentIn.Translation, length, rightTangent); AnimationUtils::GetTangent(b.Value.Translation, b.TangentIn.Translation, tangentScale, rightTangent);
for (int32 slice = 1; slice < slices; slice++) for (int32 slice = 1; slice < slices; slice++)
{ {
@@ -478,9 +478,7 @@ void Spline::GetKeyframes(MArray* data)
void Spline::SetKeyframes(MArray* data) void Spline::SetKeyframes(MArray* data)
{ {
const int32 count = MCore::Array::GetLength(data); Curve = Span<byte>((const byte*)MCore::Array::GetAddress(data), MCore::Array::GetLength(data));
Curve.GetKeyframes().Resize(count, false);
Platform::MemoryCopy(Curve.GetKeyframes().Get(), MCore::Array::GetAddress(data), sizeof(Keyframe) * count);
UpdateSpline(); UpdateSpline();
} }

View File

@@ -184,9 +184,9 @@ void SplineModel::OnSplineUpdated()
auto& instance = _instances[segment]; auto& instance = _instances[segment];
const auto& start = keyframes[segment]; const auto& start = keyframes[segment];
const auto& end = keyframes[segment + 1]; const auto& end = keyframes[segment + 1];
const float length = end.Time - start.Time; const float tangentScale = (end.Time - start.Time) / 3.0f;
AnimationUtils::GetTangent(start.Value, start.TangentOut, length, leftTangent); AnimationUtils::GetTangent(start.Value, start.TangentOut, tangentScale, leftTangent);
AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent); AnimationUtils::GetTangent(end.Value, end.TangentIn, tangentScale, rightTangent);
// Find maximum scale over the segment spline and collect the segment positions for bounds // Find maximum scale over the segment spline and collect the segment positions for bounds
segmentPoints.Clear(); segmentPoints.Clear();
@@ -256,9 +256,9 @@ void SplineModel::UpdateDeformationBuffer()
auto& instance = _instances[segment]; auto& instance = _instances[segment];
const auto& start = keyframes[segment]; const auto& start = keyframes[segment];
const auto& end = keyframes[segment + 1]; const auto& end = keyframes[segment + 1];
const float length = end.Time - start.Time; const float tangentScale = (end.Time - start.Time) / 3.0f;
AnimationUtils::GetTangent(start.Value, start.TangentOut, length, leftTangent); AnimationUtils::GetTangent(start.Value, start.TangentOut, tangentScale, leftTangent);
AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent); AnimationUtils::GetTangent(end.Value, end.TangentIn, tangentScale, rightTangent);
for (int32 chunk = 0; chunk < chunksPerSegment; chunk++) for (int32 chunk = 0; chunk < chunksPerSegment; chunk++)
{ {
const float alpha = (chunk == chunksPerSegment - 1) ? 1.0f : ((float)chunk * chunksPerSegmentInv); 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& start = keyframes[segments - 1];
const auto& end = keyframes[segments]; 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 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(start.Value, start.TangentOut, tangentScale, leftTangent);
AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent); AnimationUtils::GetTangent(end.Value, end.TangentIn, tangentScale, rightTangent);
AnimationUtils::Bezier(start.Value, leftTangent, rightTangent, end.Value, alpha, transform); AnimationUtils::Bezier(start.Value, leftTangent, rightTangent, end.Value, alpha, transform);
Vector3 direction; Vector3 direction;
AnimationUtils::BezierFirstDerivative(start.Value.Translation, leftTangent.Translation, rightTangent.Translation, end.Value.Translation, alpha, direction); AnimationUtils::BezierFirstDerivative(start.Value.Translation, leftTangent.Translation, rightTangent.Translation, end.Value.Translation, alpha, direction);

View File

@@ -90,6 +90,7 @@ void SceneRendering::Draw(RenderContextBatch& renderContextBatch, DrawCategory c
// Draw physics shapes // Draw physics shapes
if (EnumHasAnyFlags(view.Flags, ViewFlags::PhysicsDebug) || view.Mode == ViewMode::PhysicsColliders) if (EnumHasAnyFlags(view.Flags, ViewFlags::PhysicsDebug) || view.Mode == ViewMode::PhysicsColliders)
{ {
PROFILE_CPU_NAMED("PhysicsDebug");
const PhysicsDebugCallback* physicsDebugData = PhysicsDebug.Get(); const PhysicsDebugCallback* physicsDebugData = PhysicsDebug.Get();
for (int32 i = 0; i < PhysicsDebug.Count(); i++) for (int32 i = 0; i < PhysicsDebug.Count(); i++)
{ {
@@ -100,6 +101,7 @@ void SceneRendering::Draw(RenderContextBatch& renderContextBatch, DrawCategory c
// Draw light shapes // Draw light shapes
if (EnumHasAnyFlags(view.Flags, ViewFlags::LightsDebug)) if (EnumHasAnyFlags(view.Flags, ViewFlags::LightsDebug))
{ {
PROFILE_CPU_NAMED("LightsDebug");
const LightsDebugCallback* lightsDebugData = LightsDebug.Get(); const LightsDebugCallback* lightsDebugData = LightsDebug.Get();
for (int32 i = 0; i < LightsDebug.Count(); i++) for (int32 i = 0; i < LightsDebug.Count(); i++)
{ {

View File

@@ -456,6 +456,7 @@ void ParticleEmitterGPUGenerator::PrepareGraph(ParticleEmitterGraphGPU* graph)
mp.AsFloat3 = param->Value.AsFloat3(); mp.AsFloat3 = param->Value.AsFloat3();
break; break;
case VariantType::Float4: case VariantType::Float4:
case VariantType::Quaternion:
mp.Type = MaterialParameterType::Vector4; mp.Type = MaterialParameterType::Vector4;
*(Float4*)&mp.AsData = param->Value.AsFloat4(); *(Float4*)&mp.AsData = param->Value.AsFloat4();
break; break;

View File

@@ -214,9 +214,9 @@ void SplineCollider::GetGeometry(CollisionShape& collision)
auto offsetIndices = segment * collisionIndices.Count(); auto offsetIndices = segment * collisionIndices.Count();
const auto& start = keyframes[segment]; const auto& start = keyframes[segment];
const auto& end = keyframes[segment + 1]; const auto& end = keyframes[segment + 1];
const float length = end.Time - start.Time; const float tangentScale = (end.Time - start.Time) / 3.0f;
AnimationUtils::GetTangent(start.Value, start.TangentOut, length, leftTangent); AnimationUtils::GetTangent(start.Value, start.TangentOut, tangentScale, leftTangent);
AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent); AnimationUtils::GetTangent(end.Value, end.TangentIn, tangentScale, rightTangent);
// Vertex buffer is deformed along the spline // Vertex buffer is deformed along the spline
auto srcVertices = collisionVertices.Get(); auto srcVertices = collisionVertices.Get();

View File

@@ -468,12 +468,12 @@ int32 MacPlatform::CreateProcess(CreateProcessSettings& settings)
if (settings.WaitForEnd) if (settings.WaitForEnd)
{ {
id<NSObject> outputObserver = nil; id<NSObject> outputObserver = nil;
id<NSObject> outputObserverError = nil;
if (captureStdOut) if (captureStdOut)
{ {
NSPipe *stdoutPipe = [NSPipe pipe]; NSPipe* stdoutPipe = [NSPipe pipe];
[task setStandardOutput:stdoutPipe]; [task setStandardOutput:stdoutPipe];
outputObserver = [[NSNotificationCenter defaultCenter] outputObserver = [[NSNotificationCenter defaultCenter]
addObserverForName: NSFileHandleDataAvailableNotification addObserverForName: NSFileHandleDataAvailableNotification
object: [stdoutPipe fileHandleForReading] object: [stdoutPipe fileHandleForReading]
@@ -497,8 +497,34 @@ int32 MacPlatform::CreateProcess(CreateProcessSettings& settings)
} }
} }
]; ];
[[stdoutPipe fileHandleForReading] waitForDataInBackgroundAndNotify]; [[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; String exception;

View File

@@ -301,12 +301,12 @@ void WriteTri(const Float2& p0, const Float2& p1, const Float2& p2, const Float2
IBIndex += 3; 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); 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); 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 // Ending cap
{ {
ApplyTransform(points[0], p1t); ApplyTransform(points[pointsCount - 2], p1t);
ApplyTransform(points[1], p2t); //ApplyTransform(points[pointsCount - 1], p2t);
const Float2 capDirection = thicknessHalf * Float2::Normalize(p2t - p1t); 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 Float2 d3 = p4 - p3;
const float len = d1.Length() + d2.Length() + d3.Length(); const float len = d1.Length() + d2.Length() + d3.Length();
const int32 segmentCount = Math::Clamp(Math::CeilToInt(len * 0.05f), 1, 100); 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 // Draw segmented curve
Float2 p;
AnimationUtils::Bezier(p1, p2, p3, p4, 0, p);
Lines2.Clear(); Lines2.Clear();
Lines2.Add(p); Lines2.Add(p1);
for (int32 i = 1; i <= segmentCount; i++) 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); AnimationUtils::Bezier(p1, p2, p3, p4, t, p);
Lines2.Add(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); DrawLines(Lines2.Get(), Lines2.Count(), color, color, thickness);
} }
@@ -1935,9 +1962,56 @@ void Render2D::DrawBlur(const Rectangle& rect, float blurStrength)
WriteRect(rect, Color::White); WriteRect(rect, Color::White);
} }
void Render2D::DrawTriangles(const Span<Float2>& 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<Float2>& vertices, const Span<Color>& 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<Float2>& vertices, const Span<Float2>& uvs) void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span<Float2>& vertices, const Span<Float2>& uvs)
{ {
RENDER2D_CHECK_RENDERING_STATE; RENDER2D_CHECK_RENDERING_STATE;
CHECK(vertices.Length() % 3 == 0);
CHECK(vertices.Length() == uvs.Length()); CHECK(vertices.Length() == uvs.Length());
Render2DDrawCall& drawCall = DrawCalls.AddOne(); Render2DDrawCall& drawCall = DrawCalls.AddOne();
@@ -1952,14 +2026,24 @@ void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span<Float2>& vertices
void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span<Float2>& vertices, const Span<Float2>& uvs, const Color& color) void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span<Float2>& vertices, const Span<Float2>& uvs, const Color& color)
{ {
Color colors[3] = { (Color)color, (Color)color, (Color)color }; RENDER2D_CHECK_RENDERING_STATE;
Span<Color> spancolor(colors, 3); CHECK(vertices.Length() % 3 == 0);
DrawTexturedTriangles(t, vertices, uvs, spancolor); 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<Float2>& vertices, const Span<Float2>& uvs, const Span<Color>& colors) void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span<Float2>& vertices, const Span<Float2>& uvs, const Span<Color>& colors)
{ {
RENDER2D_CHECK_RENDERING_STATE; RENDER2D_CHECK_RENDERING_STATE;
CHECK(vertices.Length() % 3 == 0);
CHECK(vertices.Length() == uvs.Length()); CHECK(vertices.Length() == uvs.Length());
CHECK(vertices.Length() == colors.Length()); CHECK(vertices.Length() == colors.Length());
@@ -1994,6 +2078,19 @@ void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span<uint16>& indices,
} }
} }
void Render2D::FillTriangles(const Span<Float2>& 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<Float2>& vertices, const Span<Color>& colors, bool useAlpha) void Render2D::FillTriangles(const Span<Float2>& vertices, const Span<Color>& colors, bool useAlpha)
{ {
CHECK(vertices.Length() == colors.Length()); CHECK(vertices.Length() == colors.Length());

View File

@@ -389,6 +389,17 @@ public:
/// <param name="thickness">The line thickness.</param> /// <param name="thickness">The line thickness.</param>
API_FUNCTION() static void DrawBezier(const Float2& p1, const Float2& p2, const Float2& p3, const Float2& p4, const Color& color, float thickness = 1.0f); API_FUNCTION() static void DrawBezier(const Float2& p1, const Float2& p2, const Float2& p3, const Float2& p4, const Color& color, float thickness = 1.0f);
/// <summary>
/// Draws a spline curve (Bezier but X axis represents uniform time).
/// </summary>
/// <param name="p1">The start point.</param>
/// <param name="p2">The first control point.</param>
/// <param name="p3">The second control point.</param>
/// <param name="p4">The end point.</param>
/// <param name="color">The line color</param>
/// <param name="thickness">The line thickness.</param>
API_FUNCTION() static void DrawSpline(const Float2& p1, const Float2& p2, const Float2& p3, const Float2& p4, const Color& color, float thickness = 1.0f);
/// <summary> /// <summary>
/// Draws the GUI material. /// Draws the GUI material.
/// </summary> /// </summary>
@@ -404,6 +415,22 @@ public:
/// <param name="blurStrength">The blur strength defines how blurry the background is. Larger numbers increase blur, resulting in a larger runtime cost on the GPU.</param> /// <param name="blurStrength">The blur strength defines how blurry the background is. Larger numbers increase blur, resulting in a larger runtime cost on the GPU.</param>
API_FUNCTION() static void DrawBlur(const Rectangle& rect, float blurStrength); API_FUNCTION() static void DrawBlur(const Rectangle& rect, float blurStrength);
/// <summary>
/// Draws vertices array.
/// </summary>
/// <param name="vertices">The vertices array.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The line thickness.</param>
API_FUNCTION() static void DrawTriangles(const Span<Float2>& vertices, const Color& color, float thickness = 1.0f);
/// <summary>
/// Draws vertices array.
/// </summary>
/// <param name="vertices">The vertices array.</param>
/// <param name="colors">The colors array.</param>
/// <param name="thickness">The line thickness.</param>
API_FUNCTION() static void DrawTriangles(const Span<Float2>& vertices, const Span<Color>& colors, float thickness = 1.0f);
/// <summary> /// <summary>
/// Draws vertices array. /// Draws vertices array.
/// </summary> /// </summary>
@@ -440,13 +467,20 @@ public:
/// <param name="colors">The colors array.</param> /// <param name="colors">The colors array.</param>
API_FUNCTION() static void DrawTexturedTriangles(GPUTexture* t, const Span<uint16>& indices, const Span<Float2>& vertices, const Span<Float2>& uvs, const Span<Color>& colors); API_FUNCTION() static void DrawTexturedTriangles(GPUTexture* t, const Span<uint16>& indices, const Span<Float2>& vertices, const Span<Float2>& uvs, const Span<Color>& colors);
/// <summary>
/// Draws vertices array.
/// </summary>
/// <param name="vertices">The vertices array.</param>
/// <param name="color">The color.</param>
API_FUNCTION() static void FillTriangles(const Span<Float2>& vertices, const Color& color);
/// <summary> /// <summary>
/// Draws vertices array. /// Draws vertices array.
/// </summary> /// </summary>
/// <param name="vertices">The vertices array.</param> /// <param name="vertices">The vertices array.</param>
/// <param name="colors">The colors array.</param> /// <param name="colors">The colors array.</param>
/// <param name="useAlpha">If true alpha blending will be enabled.</param> /// <param name="useAlpha">If true alpha blending will be enabled.</param>
API_FUNCTION() static void FillTriangles(const Span<Float2>& vertices, const Span<Color>& colors, bool useAlpha); API_FUNCTION() static void FillTriangles(const Span<Float2>& vertices, const Span<Color>& colors, bool useAlpha = true);
/// <summary> /// <summary>
/// Fills a triangular area. /// Fills a triangular area.

View File

@@ -271,7 +271,7 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input,
// Calculate star texture rotation matrix // Calculate star texture rotation matrix
Float3 camX = renderContext.View.View.GetRight(); 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 camRot = Float3::Dot(camX, Float3::Forward) + Float3::Dot(camZ, Float3::Up);
float camRotCos = Math::Cos(camRot) * 0.8f; float camRotCos = Math::Cos(camRot) * 0.8f;
float camRotSin = Math::Sin(camRot) * 0.8f; float camRotSin = Math::Sin(camRot) * 0.8f;

View File

@@ -0,0 +1,42 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
namespace FlaxEngine
{
/// <summary>
/// Displays the method in the properties panel where user can click and invoke this method.
/// </summary>
/// <remarks>Supported on both static and member methods that are parameterless.</remarks>
[AttributeUsage(AttributeTargets.Method)]
public sealed class ButtonAttribute : Attribute
{
/// <summary>
/// The button text. Empty value will use method name (auto-formatted).
/// </summary>
public string Text;
/// <summary>
/// The button tooltip text. Empty value will use method documentation.
/// </summary>
public string Tooltip;
/// <summary>
/// Initializes a new instance of the <see cref="ButtonAttribute"/> class.
/// </summary>
public ButtonAttribute()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ButtonAttribute"/> class.
/// </summary>
/// <param name="text">The button text.</param>
/// <param name="tooltip">The button tooltip.</param>
public ButtonAttribute(string text, string tooltip = null)
{
Text = text;
Tooltip = tooltip;
}
}
}

View File

@@ -33,6 +33,11 @@
#if USE_EDITOR #if USE_EDITOR
#include "Engine/Debug/DebugDraw.h" #include "Engine/Debug/DebugDraw.h"
#endif #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/Content.h"
#include "Engine/Content/Assets/RawDataAsset.h" #include "Engine/Content/Assets/RawDataAsset.h"
@@ -94,7 +99,8 @@ void TerrainPatch::Init(Terrain* terrain, int16 x, int16 z)
} }
#endif #endif
#if TERRAIN_USE_PHYSICS_DEBUG #if TERRAIN_USE_PHYSICS_DEBUG
_debugLines.Resize(0); SAFE_DELETE(_debugLines);
_debugLinesDirty = true;
#endif #endif
#if USE_EDITOR #if USE_EDITOR
_collisionTriangles.Resize(0); _collisionTriangles.Resize(0);
@@ -1822,7 +1828,7 @@ bool TerrainPatch::UpdateHeightData(TerrainDataUpdateInfo& info, const Int2& mod
// Invalidate cache // Invalidate cache
#if TERRAIN_USE_PHYSICS_DEBUG #if TERRAIN_USE_PHYSICS_DEBUG
_debugLines.Resize(0); _debugLinesDirty = true;
#endif #endif
#if USE_EDITOR #if USE_EDITOR
_collisionTriangles.Resize(0); _collisionTriangles.Resize(0);
@@ -1940,7 +1946,7 @@ bool TerrainPatch::UpdateCollision()
{ {
// Invalidate cache // Invalidate cache
#if TERRAIN_USE_PHYSICS_DEBUG #if TERRAIN_USE_PHYSICS_DEBUG
_debugLines.Resize(0); _debugLinesDirty = true;
#endif #endif
#if USE_EDITOR #if USE_EDITOR
_collisionTriangles.Resize(0); _collisionTriangles.Resize(0);
@@ -2082,7 +2088,7 @@ void TerrainPatch::UpdatePostManualDeserialization()
{ {
// Invalidate cache // Invalidate cache
#if TERRAIN_USE_PHYSICS_DEBUG #if TERRAIN_USE_PHYSICS_DEBUG
_debugLines.Resize(0); _debugLinesDirty = true;
#endif #endif
#if USE_EDITOR #if USE_EDITOR
_collisionTriangles.Resize(0); _collisionTriangles.Resize(0);
@@ -2211,7 +2217,8 @@ void TerrainPatch::DestroyCollision()
_physicsShape = nullptr; _physicsShape = nullptr;
_physicsHeightField = nullptr; _physicsHeightField = nullptr;
#if TERRAIN_USE_PHYSICS_DEBUG #if TERRAIN_USE_PHYSICS_DEBUG
_debugLines.Resize(0); _debugLinesDirty = true;
SAFE_DELETE(_debugLines);
#endif #endif
#if USE_EDITOR #if USE_EDITOR
_collisionTriangles.Resize(0); _collisionTriangles.Resize(0);
@@ -2224,15 +2231,26 @@ void TerrainPatch::DestroyCollision()
void TerrainPatch::CacheDebugLines() void TerrainPatch::CacheDebugLines()
{ {
PROFILE_CPU(); PROFILE_CPU();
ASSERT(_debugLines.IsEmpty() && _physicsHeightField); ASSERT(_physicsHeightField);
_debugLinesDirty = false;
if (!_debugLines)
_debugLines = GPUDevice::Instance->CreateBuffer(TEXT("Terrain.DebugLines"));
int32 rows, cols; int32 rows, cols;
PhysicsBackend::GetHeightFieldSize(_physicsHeightField, 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<Vertex> 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); #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 }
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)))
for (int32 row = 0; row < rows - 1; row++) for (int32 row = 0; row < rows - 1; row++)
{ {
@@ -2243,7 +2261,7 @@ void TerrainPatch::CacheDebugLines()
if (sample.MaterialIndex0 == (uint8)PhysicsBackend::HeightFieldMaterial::Hole) if (sample.MaterialIndex0 == (uint8)PhysicsBackend::HeightFieldMaterial::Hole)
{ {
for (int32 i = 0; i < 6; i++) for (int32 i = 0; i < 6; i++)
*data++ = Vector3::Zero; *data++ = Vertex { Float3::Zero, Color32::Black };
continue; continue;
} }
@@ -2284,18 +2302,16 @@ void TerrainPatch::CacheDebugLines()
} }
#undef GET_VERTEX #undef GET_VERTEX
_debugLines->SetData(debugLines.Get(), _debugLines->GetSize());
} }
void TerrainPatch::DrawPhysicsDebug(RenderView& view) void TerrainPatch::DrawPhysicsDebug(RenderView& view)
{ {
#if COMPILE_WITH_DEBUG_DRAW
const BoundingBox bounds(_bounds.Minimum - view.Origin, _bounds.Maximum - view.Origin); const BoundingBox bounds(_bounds.Minimum - view.Origin, _bounds.Maximum - view.Origin);
if (!_physicsShape || !view.CullingFrustum.Intersects(bounds)) if (!_physicsShape || !view.CullingFrustum.Intersects(bounds))
return; 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) if (view.Mode == ViewMode::PhysicsColliders)
{ {
DEBUG_DRAW_TRIANGLES(GetCollisionTriangles(), Color::DarkOliveGreen, 0, true); DEBUG_DRAW_TRIANGLES(GetCollisionTriangles(), Color::DarkOliveGreen, 0, true);
@@ -2304,13 +2320,17 @@ void TerrainPatch::DrawPhysicsDebug(RenderView& view)
{ {
BoundingSphere sphere; BoundingSphere sphere;
BoundingSphere::FromBox(bounds, 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(); 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 #endif

View File

@@ -40,12 +40,13 @@ private:
Array<Color32> _cachedSplatMap[TERRAIN_MAX_SPLATMAPS_COUNT]; Array<Color32> _cachedSplatMap[TERRAIN_MAX_SPLATMAPS_COUNT];
bool _wasHeightModified; bool _wasHeightModified;
bool _wasSplatmapModified[TERRAIN_MAX_SPLATMAPS_COUNT]; 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* _dataHeightmap = nullptr;
TextureBase::InitData* _dataSplatmap[TERRAIN_MAX_SPLATMAPS_COUNT] = {}; TextureBase::InitData* _dataSplatmap[TERRAIN_MAX_SPLATMAPS_COUNT] = {};
#endif #endif
#if TERRAIN_USE_PHYSICS_DEBUG
Array<Vector3> _debugLines; // TODO: large-worlds
#endif
#if USE_EDITOR #if USE_EDITOR
Array<Vector3> _collisionTriangles; // TODO: large-worlds Array<Vector3> _collisionTriangles; // TODO: large-worlds
#endif #endif

View File

@@ -263,16 +263,21 @@ namespace FlaxEngine.GUI
/// <inheritdoc /> /// <inheritdoc />
protected override void SetViewOffset(ref Float2 value) protected override void SetViewOffset(ref Float2 value)
{ {
// Update scroll bars but with locked layout
bool wasLocked = _isLayoutLocked; bool wasLocked = _isLayoutLocked;
int layoutUpdateLock = _layoutUpdateLock;
_isLayoutLocked = true; _isLayoutLocked = true;
_layoutUpdateLock = 999;
if (HScrollBar != null) if (HScrollBar != null)
HScrollBar.Value = -value.X; HScrollBar.TargetValue = -value.X;
if (VScrollBar != null) if (VScrollBar != null)
VScrollBar.Value = -value.Y; VScrollBar.TargetValue = -value.Y;
_layoutUpdateLock = layoutUpdateLock;
_isLayoutLocked = wasLocked; _isLayoutLocked = wasLocked;
base.SetViewOffset(ref value); base.SetViewOffset(ref value);
PerformLayout();
} }
/// <summary> /// <summary>
@@ -553,7 +558,12 @@ namespace FlaxEngine.GUI
if (vScrollEnabled) 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); VScrollBar.Bounds = new Rectangle(Width - _scrollBarsSize, 0, _scrollBarsSize, Height);
} }
@@ -580,7 +590,12 @@ namespace FlaxEngine.GUI
if (hScrollEnabled) 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); 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 // Calculate scroll area bounds
var totalMin = Float2.Zero; var totalMin = Float2.Zero;
var totalMax = Float2.Zero; var totalMax = Float2.Zero;
var hasTotal = false;
for (int i = 0; i < _children.Count; i++) for (int i = 0; i < _children.Count; i++)
{ {
var c = _children[i]; var c = _children[i];
if (c.Visible && c.IsScrollable) if (c.Visible && c.IsScrollable)
{ {
var min = Float2.Zero; var upperLeft = Float2.Zero;
var max = c.Size; var bottomRight = c.Size;
Matrix3x3.Transform2D(ref min, ref c._cachedTransform, out min); Matrix3x3.Transform2D(ref upperLeft, ref c._cachedTransform, out upperLeft);
Matrix3x3.Transform2D(ref max, ref c._cachedTransform, out max); Matrix3x3.Transform2D(ref bottomRight, ref c._cachedTransform, out bottomRight);
Float2.Min(ref min, ref totalMin, out totalMin); Float2.Min(ref upperLeft, ref bottomRight, out var min);
Float2.Max(ref max, ref totalMax, out totalMax); 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;
}
} }
} }

View File

@@ -348,6 +348,8 @@ namespace FlaxEngine.GUI
// https://easings.net/#easeOutSine // https://easings.net/#easeOutSine
var easedProgress = Mathf.Sin((progress * Mathf.Pi) / 2); var easedProgress = Mathf.Sin((progress * Mathf.Pi) / 2);
if (progress >= 1.0f)
easedProgress = 1.0f;
value = Mathf.Lerp(_startValue, _targetValue, easedProgress); value = Mathf.Lerp(_startValue, _targetValue, easedProgress);
_scrollAnimationProgress = progress; _scrollAnimationProgress = progress;

View File

@@ -7,10 +7,11 @@
#include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/Array.h"
/// <summary> /// <summary>
/// Helper class with Delaunay triangulation algorithm implementation, /// Helper class with Delaunay triangulation algorithm implementation (2D space).
/// </summary> /// </summary>
class Delaunay2D API_CLASS(Internal, Static, Namespace="FlaxEngine.Utilities") class Delaunay2D
{ {
DECLARE_SCRIPTING_TYPE_MINIMAL(Delaunay2D);
public: public:
struct Triangle struct Triangle
{ {
@@ -31,14 +32,35 @@ public:
} }
}; };
template<typename TVertexArray, typename TTrianglesArray> /// <summary>
static void Triangulate(const TVertexArray& vertices, TTrianglesArray& triangles) /// Triangulates input vertices array into the list of triangle vertices.
/// </summary>
/// <param name="vertices">Input list of vertices.</param>
/// <returns>Result list of triangles. Each triangle is made out of sequence of 3 vertices. Empty if no valid triangle built.</returns>
API_FUNCTION() static Array<Float2> Triangulate(const Array<Float2>& vertices)
{
Array<Triangle> triangles;
Triangulate(vertices, triangles);
Array<Float2> 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<typename TrianglesArray = Triangle>
static void Triangulate(const Array<Float2>& vertices, TrianglesArray& triangles)
{ {
// Skip if no change to produce any triangles // Skip if no change to produce any triangles
if (vertices.Count() < 3) if (vertices.Count() < 3)
return; return;
TVertexArray points = vertices; Array<Float2> points = vertices;
Array<Edge> polygon; Array<Edge> polygon;
Rectangle rect; Rectangle rect;
@@ -142,8 +164,7 @@ private:
} }
}; };
template<typename TVertexArray> static bool CircumCircleContains(const Array<Float2>& vertices, const Triangle& triangle, int32 vertexIndex)
static bool CircumCircleContains(const TVertexArray& vertices, const Triangle& triangle, int vertexIndex)
{ {
Float2 p1 = vertices[triangle.Indices[0]]; Float2 p1 = vertices[triangle.Indices[0]];
Float2 p2 = vertices[triangle.Indices[1]]; 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.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))); (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 r = Float2::DistanceSquared(p1, circum);
float d = Float2::DistanceSquared(vertices[vertexIndex], circum); float d = Float2::DistanceSquared(vertices[vertexIndex], circum);
return d <= r; return d <= r;
} }
template<typename TVertexArray> static bool EdgeCompare(const Array<Float2>& vertices, const Edge& a, const Edge& b)
static bool EdgeCompare(const TVertexArray& 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; return true;
} if (Float2::Distance(vertices[a.Indices[0]], vertices[b.Indices[1]]) < ZeroTolerance &&
Float2::Distance(vertices[a.Indices[1]], vertices[b.Indices[0]]) < ZeroTolerance)
if (Float2::Distance(vertices[a.Indices[0]], vertices[b.Indices[1]]) < ZeroTolerance && Vector2::Distance(vertices[a.Indices[1]], vertices[b.Indices[0]]) < ZeroTolerance)
{
return true; return true;
}
return false; return false;
} }
}; };

View File

@@ -92,9 +92,13 @@ void ShaderGenerator::ProcessGroupConstants(Box* box, Node* node, Value& value)
value = Value(cv.W); value = Value(cv.W);
break; break;
} }
// Rotation
case 8: 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; break;
} }
// PI // PI

View File

@@ -460,6 +460,7 @@ ShaderGraphValue ShaderGraphValue::Cast(const ShaderGraphValue& v, VariantType::
case VariantType::Types::Float4: case VariantType::Types::Float4:
case VariantType::Types::Double4: case VariantType::Types::Double4:
case VariantType::Types::Color: case VariantType::Types::Color:
case VariantType::Types::Quaternion:
switch (v.Type) switch (v.Type)
{ {
case VariantType::Types::Bool: case VariantType::Types::Bool:
@@ -485,16 +486,6 @@ ShaderGraphValue ShaderGraphValue::Cast(const ShaderGraphValue& v, VariantType::
break; break;
} }
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) if (format == nullptr)
{ {

View File

@@ -79,6 +79,7 @@ void VisjectExecutor::ProcessGroupConstants(Box* box, Node* node, Value& value)
value = cv.W; value = cv.W;
break; break;
} }
// Rotation
case 8: case 8:
{ {
const float pitch = (float)node->Values[0]; const float pitch = (float)node->Values[0];

View File

@@ -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.

View File

@@ -0,0 +1,21 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using Flax.Build;
/// <summary>
/// https://github.com/microsoft/PixEvents
/// </summary>
public class WinPixEventRuntime : HeaderOnlyModule
{
/// <inheritdoc />
public override void Init()
{
base.Init();
LicenseType = LicenseTypes.MIT;
LicenseFilePath = "LICENSE.txt";
// Merge third-party modules into engine binary
BinaryModuleName = "FlaxEngine";
}
}

View File

@@ -20,12 +20,12 @@ namespace Flax.Build.Bindings
private static readonly Dictionary<string, string> CSharpAdditionalCodeCache = new Dictionary<string, string>(); private static readonly Dictionary<string, string> CSharpAdditionalCodeCache = new Dictionary<string, string>();
#if USE_NETCORE #if USE_NETCORE
private static readonly TypeInfo CSharpEventBindReturn = new TypeInfo("void"); private static readonly TypeInfo CSharpEventBindReturn = new TypeInfo("void");
private static readonly List<FunctionInfo.ParameterInfo> CSharpEventBindParams = new List<FunctionInfo.ParameterInfo>() { new FunctionInfo.ParameterInfo() { Name = "bind", Type = new TypeInfo("bool") } }; private static readonly List<FunctionInfo.ParameterInfo> CSharpEventBindParams = new List<FunctionInfo.ParameterInfo> { new FunctionInfo.ParameterInfo { Name = "bind", Type = new TypeInfo("bool") } };
#endif #endif
public static event Action<BuildData, ApiTypeInfo, StringBuilder, string> GenerateCSharpTypeInternals; public static event Action<BuildData, ApiTypeInfo, StringBuilder, string> GenerateCSharpTypeInternals;
internal static readonly Dictionary<string, string> CSharpNativeToManagedBasicTypes = new Dictionary<string, string>() internal static readonly Dictionary<string, string> CSharpNativeToManagedBasicTypes = new Dictionary<string, string>
{ {
// Language types // Language types
{ "bool", "bool" }, { "bool", "bool" },
@@ -46,7 +46,7 @@ namespace Flax.Build.Bindings
{ "double", "double" }, { "double", "double" },
}; };
internal static readonly Dictionary<string, string> CSharpNativeToManagedDefault = new Dictionary<string, string>() internal static readonly Dictionary<string, string> CSharpNativeToManagedDefault = new Dictionary<string, string>
{ {
// Engine types // Engine types
{ "String", "string" }, { "String", "string" },
@@ -632,11 +632,11 @@ namespace Flax.Build.Bindings
else if (returnValueType == "object[]") else if (returnValueType == "object[]")
returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemObjectArrayMarshaller))"; 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") 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") 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[]") else if (returnValueType == "byte[]")
returnMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = \"__returnCount\")"; returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = \"__returnCount\")";
else if (returnValueType == "bool[]") else if (returnValueType == "bool[]")
{ {
// Boolean arrays does not support custom marshalling for some unknown reason // 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. 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") 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") else if (nativeType == "bool")
parameterMarshalType = "MarshalAs(UnmanagedType.U1)"; parameterMarshalType = "MarshalAs(UnmanagedType.U1)";
else if (nativeType == "char") else if (nativeType == "char")
parameterMarshalType = "MarshalAs(UnmanagedType.I2)"; parameterMarshalType = "MarshalAs(UnmanagedType.I2)";
else if (nativeType.EndsWith("[]"))
{
parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>))";
}
if (!string.IsNullOrEmpty(parameterMarshalType)) if (!string.IsNullOrEmpty(parameterMarshalType))
contents.Append($"[{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") 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\")"; parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = \"{parameterInfo.Name}Count\")";
else if (parameterInfo.Type.Type == "Dictionary") 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") if (nativeType == "System.Type")
parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemTypeMarshaller))"; parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemTypeMarshaller))";

View File

@@ -980,6 +980,10 @@ namespace Flax.Build.Bindings
UseReferenceForResult = UsePassByReference(buildData, functionInfo.ReturnType, caller), UseReferenceForResult = UsePassByReference(buildData, functionInfo.ReturnType, caller),
CustomParameters = new List<FunctionInfo.ParameterInfo>(), CustomParameters = new List<FunctionInfo.ParameterInfo>(),
}; };
var returnType = functionInfo.ReturnType;
var returnApiType = FindApiTypeInfo(buildData, returnType, caller);
if (returnApiType != null && returnApiType.MarshalAs != null)
returnType = returnApiType.MarshalAs;
bool returnTypeIsContainer = false; bool returnTypeIsContainer = false;
var returnValueConvert = GenerateCppWrapperNativeToManaged(buildData, functionInfo.ReturnType, caller, out var returnValueType, functionInfo); var returnValueConvert = GenerateCppWrapperNativeToManaged(buildData, functionInfo.ReturnType, caller, out var returnValueType, functionInfo);
@@ -999,7 +1003,7 @@ namespace Flax.Build.Bindings
}); });
} }
#if USE_NETCORE #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; returnTypeIsContainer = true;
functionInfo.Glue.CustomParameters.Add(new FunctionInfo.ParameterInfo functionInfo.Glue.CustomParameters.Add(new FunctionInfo.ParameterInfo
@@ -1173,7 +1177,7 @@ namespace Flax.Build.Bindings
{ {
callBegin += "*__resultAsRef = "; callBegin += "*__resultAsRef = ";
} }
else if (!functionInfo.ReturnType.IsVoid) else if (!returnType.IsVoid)
{ {
if (useInlinedReturn) if (useInlinedReturn)
callBegin += "return "; callBegin += "return ";
@@ -1186,7 +1190,7 @@ namespace Flax.Build.Bindings
if (returnTypeIsContainer) if (returnTypeIsContainer)
{ {
callReturnCount = indent; callReturnCount = indent;
if (functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "BytesContainer") if (returnType.Type == "Span" || returnType.Type == "BytesContainer")
callReturnCount += "*__returnCount = {0}.Length();"; callReturnCount += "*__returnCount = {0}.Length();";
else else
callReturnCount += "*__returnCount = {0}.Count();"; callReturnCount += "*__returnCount = {0}.Count();";
@@ -1278,7 +1282,8 @@ namespace Flax.Build.Bindings
#if USE_NETCORE #if USE_NETCORE
if (!string.IsNullOrEmpty(callReturnCount)) 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"; call = "__callTemp";
contents.Append(string.Format(callReturnCount, call)); contents.Append(string.Format(callReturnCount, call));
contents.AppendLine(); 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(); contents.Append(indent).Append("return __result;").AppendLine();
} }
@@ -1823,6 +1828,10 @@ namespace Flax.Build.Bindings
// Add includes to properly compile bindings (eg. SoftObjectReference<class Texture>) // Add includes to properly compile bindings (eg. SoftObjectReference<class Texture>)
GenerateCppAddFileReference(buildData, caller, typeInfo, apiTypeInfo); 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; return false;
} }
@@ -3132,13 +3141,12 @@ namespace Flax.Build.Bindings
// Includes // Includes
header.Clear(); header.Clear();
CppReferencesFiles.Remove(null); CppReferencesFiles.Remove(null);
CppIncludeFilesList.Clear();
foreach (var fileInfo in CppReferencesFiles) foreach (var fileInfo in CppReferencesFiles)
CppIncludeFilesList.Add(fileInfo.Name); CppIncludeFilesList.Add(fileInfo.Name);
CppIncludeFilesList.AddRange(CppIncludeFiles); CppIncludeFilesList.AddRange(CppIncludeFiles);
CppIncludeFilesList.Sort(); CppIncludeFilesList.Sort();
if (CppIncludeFilesList.Remove("Engine/Serialization/Serialization.h")) 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) foreach (var path in CppIncludeFilesList)
header.AppendFormat("#include \"{0}\"", path).AppendLine(); header.AppendFormat("#include \"{0}\"", path).AppendLine();
contents.Insert(headerPos, header.ToString()); contents.Insert(headerPos, header.ToString());

View File

@@ -188,6 +188,13 @@ namespace Flax.Build.Bindings
token = context.Tokenizer.NextToken(); token = context.Tokenizer.NextToken();
if (token.Type == TokenType.Multiply) if (token.Type == TokenType.Multiply)
tag.Value += token.Value; 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 else
context.Tokenizer.PreviousToken(); context.Tokenizer.PreviousToken();
parameters.Add(tag); parameters.Add(tag);
@@ -273,7 +280,6 @@ namespace Flax.Build.Bindings
type.GenericArgs.Add(argType); type.GenericArgs.Add(argType);
token = context.Tokenizer.NextToken(); token = context.Tokenizer.NextToken();
} while (token.Type != TokenType.RightAngleBracket); } while (token.Type != TokenType.RightAngleBracket);
token = context.Tokenizer.NextToken(); token = context.Tokenizer.NextToken();
} }
@@ -545,15 +551,19 @@ namespace Flax.Build.Bindings
} }
if (token.Type == TokenType.LeftAngleBracket) if (token.Type == TokenType.LeftAngleBracket)
{ {
var genericType = context.Tokenizer.ExpectToken(TokenType.Identifier); inheritType.GenericArgs = new List<TypeInfo>();
token = context.Tokenizer.ExpectToken(TokenType.RightAngleBracket); while (true)
inheritType.GenericArgs = new List<TypeInfo>
{ {
new TypeInfo token = context.Tokenizer.NextToken();
{ if (token.Type == TokenType.RightAngleBracket)
Type = genericType.Value, 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?) // TODO: find better way to resolve this (custom base type attribute?)
if (inheritType.Type == "ShaderAssetTypeBase") if (inheritType.Type == "ShaderAssetTypeBase")
@@ -1590,16 +1600,30 @@ namespace Flax.Build.Bindings
// Read parameters from the tag // Read parameters from the tag
var tagParams = ParseTagParameters(ref context); var tagParams = ParseTagParameters(ref context);
// Read 'typedef' keyword // Read 'typedef' or 'using' keyword
var token = context.Tokenizer.NextToken(); var token = context.Tokenizer.NextToken();
if (token.Value != "typedef") var isUsing = token.Value == "using";
throw new ParseException(ref context, $"Invalid {ApiTokens.Typedef} usage (expected 'typedef' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}')."); 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 if (isUsing)
desc.Type = ParseType(ref context); {
// Read name
desc.Name = ParseName(ref context);
// Read name context.Tokenizer.ExpectToken(TokenType.Equal);
desc.Name = ParseName(ref context);
// 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 ';' // Read ';'
context.Tokenizer.ExpectToken(TokenType.SemiColon); context.Tokenizer.ExpectToken(TokenType.SemiColon);

View File

@@ -182,11 +182,40 @@ namespace Flax.Build.Bindings
public static TypeInfo FromString(string text) 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('*')) if (result.Type.EndsWith('*'))
{ {
// Pointer
result.IsPtr = true; 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<TypeInfo>();
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; return result;
} }

View File

@@ -73,6 +73,11 @@ namespace Flax.Build
/// </summary> /// </summary>
public abstract TargetCompiler Compiler { get; } public abstract TargetCompiler Compiler { get; }
/// <summary>
/// Gets the main native files compiler path.
/// </summary>
public virtual string NativeCompilerPath { get; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Toolchain"/> class. /// Initializes a new instance of the <see cref="Toolchain"/> class.
/// </summary> /// </summary>

Some files were not shown because too many files have changed in this diff Show More