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

# Conflicts:
#	Flax.flaxproj
This commit is contained in:
Wojtek Figat
2024-03-19 20:23:34 +01:00
82 changed files with 2434 additions and 1379 deletions

BIN
Content/Shaders/DebugDraw.flax (Stored with Git LFS)

Binary file not shown.

View File

@@ -4,7 +4,7 @@
"Major": 1, "Major": 1,
"Minor": 8, "Minor": 8,
"Revision": 0, "Revision": 0,
"Build": 65045 "Build": 65046
}, },
"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

@@ -46,10 +46,13 @@ namespace FlaxEditor.CustomEditors.Editors
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground; var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor); XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor);
XElement.ValueBox.BorderSelectedColor = AxisColorX; XElement.ValueBox.BorderSelectedColor = AxisColorX;
XElement.ValueBox.Category = Utils.ValueCategory.Distance;
YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor); YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor);
YElement.ValueBox.BorderSelectedColor = AxisColorY; YElement.ValueBox.BorderSelectedColor = AxisColorY;
YElement.ValueBox.Category = Utils.ValueCategory.Distance;
ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor); ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor);
ZElement.ValueBox.BorderSelectedColor = AxisColorZ; ZElement.ValueBox.BorderSelectedColor = AxisColorZ;
ZElement.ValueBox.Category = Utils.ValueCategory.Distance;
} }
} }
@@ -68,10 +71,13 @@ namespace FlaxEditor.CustomEditors.Editors
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground; var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor); XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor);
XElement.ValueBox.BorderSelectedColor = AxisColorX; XElement.ValueBox.BorderSelectedColor = AxisColorX;
XElement.ValueBox.Category = Utils.ValueCategory.Angle;
YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor); YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor);
YElement.ValueBox.BorderSelectedColor = AxisColorY; YElement.ValueBox.BorderSelectedColor = AxisColorY;
YElement.ValueBox.Category = Utils.ValueCategory.Angle;
ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor); ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor);
ZElement.ValueBox.BorderSelectedColor = AxisColorZ; ZElement.ValueBox.BorderSelectedColor = AxisColorZ;
ZElement.ValueBox.Category = Utils.ValueCategory.Angle;
} }
} }

View File

@@ -21,32 +21,28 @@ namespace FlaxEditor.CustomEditors.Editors
/// <inheritdoc /> /// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout) public override void Initialize(LayoutElementsContainer layout)
{ {
_element = null; var doubleValue = layout.DoubleValue();
doubleValue.ValueBox.ValueChanged += OnValueChanged;
// Try get limit attribute for value min/max range setting and slider speed doubleValue.ValueBox.SlidingEnd += ClearToken;
_element = doubleValue;
var attributes = Values.GetAttributes(); var attributes = Values.GetAttributes();
if (attributes != null) if (attributes != null)
{ {
var limit = attributes.FirstOrDefault(x => x is LimitAttribute); var limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute);
if (limit != null) doubleValue.SetLimits(limit);
var valueCategory = ((ValueCategoryAttribute)attributes.FirstOrDefault(x => x is ValueCategoryAttribute))?.Category ?? Utils.ValueCategory.None;
if (valueCategory != Utils.ValueCategory.None)
{ {
// Use double value editor with limit doubleValue.SetCategory(valueCategory);
var doubleValue = layout.DoubleValue(); LinkedLabel.SetupContextMenu += (label, menu, editor) =>
doubleValue.SetLimits((LimitAttribute)limit); {
doubleValue.ValueBox.ValueChanged += OnValueChanged; menu.AddSeparator();
doubleValue.ValueBox.SlidingEnd += ClearToken; var mb = menu.AddButton("Show formatted", bt => { doubleValue.SetCategory(bt.Checked ? valueCategory : Utils.ValueCategory.None); });
_element = doubleValue; mb.AutoCheck = true;
return; mb.Checked = doubleValue.ValueBox.Category != Utils.ValueCategory.None;
};
} }
} }
if (_element == null)
{
// Use double value editor
var doubleValue = layout.DoubleValue();
doubleValue.ValueBox.ValueChanged += OnValueChanged;
doubleValue.ValueBox.SlidingEnd += ClearToken;
_element = doubleValue;
}
} }
private void OnValueChanged() private void OnValueChanged()

View File

@@ -4,6 +4,7 @@ using System;
using System.Linq; using System.Linq;
using FlaxEditor.CustomEditors.Elements; using FlaxEditor.CustomEditors.Elements;
using FlaxEngine; using FlaxEngine;
using Utils = FlaxEngine.Utils;
namespace FlaxEditor.CustomEditors.Editors namespace FlaxEditor.CustomEditors.Editors
{ {
@@ -27,41 +28,39 @@ namespace FlaxEditor.CustomEditors.Editors
public override void Initialize(LayoutElementsContainer layout) public override void Initialize(LayoutElementsContainer layout)
{ {
_element = null; _element = null;
// Try get limit attribute for value min/max range setting and slider speed
var attributes = Values.GetAttributes(); var attributes = Values.GetAttributes();
var range = (RangeAttribute)attributes?.FirstOrDefault(x => x is RangeAttribute);
if (range != null)
{
// Use slider
var slider = layout.Slider();
slider.Slider.SetLimits(range);
slider.Slider.ValueChanged += OnValueChanged;
slider.Slider.SlidingEnd += ClearToken;
_element = slider;
return;
}
var floatValue = layout.FloatValue();
floatValue.ValueBox.ValueChanged += OnValueChanged;
floatValue.ValueBox.SlidingEnd += ClearToken;
_element = floatValue;
if (attributes != null) if (attributes != null)
{ {
var range = attributes.FirstOrDefault(x => x is RangeAttribute); var limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute);
if (range != null) floatValue.SetLimits(limit);
var valueCategory = ((ValueCategoryAttribute)attributes.FirstOrDefault(x => x is ValueCategoryAttribute))?.Category ?? Utils.ValueCategory.None;
if (valueCategory != Utils.ValueCategory.None)
{ {
// Use slider floatValue.SetCategory(valueCategory);
var slider = layout.Slider(); LinkedLabel.SetupContextMenu += (label, menu, editor) =>
slider.SetLimits((RangeAttribute)range); {
slider.Slider.ValueChanged += OnValueChanged; menu.AddSeparator();
slider.Slider.SlidingEnd += ClearToken; var mb = menu.AddButton("Show formatted", bt => { floatValue.SetCategory(bt.Checked ? valueCategory : Utils.ValueCategory.None); });
_element = slider; mb.AutoCheck = true;
return; mb.Checked = floatValue.ValueBox.Category != Utils.ValueCategory.None;
};
} }
var limit = attributes.FirstOrDefault(x => x is LimitAttribute);
if (limit != null)
{
// Use float value editor with limit
var floatValue = layout.FloatValue();
floatValue.SetLimits((LimitAttribute)limit);
floatValue.ValueBox.ValueChanged += OnValueChanged;
floatValue.ValueBox.SlidingEnd += ClearToken;
_element = floatValue;
return;
}
}
if (_element == null)
{
// Use float value editor
var floatValue = layout.FloatValue();
floatValue.ValueBox.ValueChanged += OnValueChanged;
floatValue.ValueBox.SlidingEnd += ClearToken;
_element = floatValue;
} }
} }

View File

@@ -45,14 +45,17 @@ namespace FlaxEditor.CustomEditors.Editors
gridControl.SlotsVertically = 1; gridControl.SlotsVertically = 1;
XElement = grid.FloatValue(); XElement = grid.FloatValue();
XElement.ValueBox.Category = Utils.ValueCategory.Angle;
XElement.ValueBox.ValueChanged += OnValueChanged; XElement.ValueBox.ValueChanged += OnValueChanged;
XElement.ValueBox.SlidingEnd += ClearToken; XElement.ValueBox.SlidingEnd += ClearToken;
YElement = grid.FloatValue(); YElement = grid.FloatValue();
YElement.ValueBox.Category = Utils.ValueCategory.Angle;
YElement.ValueBox.ValueChanged += OnValueChanged; YElement.ValueBox.ValueChanged += OnValueChanged;
YElement.ValueBox.SlidingEnd += ClearToken; YElement.ValueBox.SlidingEnd += ClearToken;
ZElement = grid.FloatValue(); ZElement = grid.FloatValue();
ZElement.ValueBox.Category = Utils.ValueCategory.Angle;
ZElement.ValueBox.ValueChanged += OnValueChanged; ZElement.ValueBox.ValueChanged += OnValueChanged;
ZElement.ValueBox.SlidingEnd += ClearToken; ZElement.ValueBox.SlidingEnd += ClearToken;

View File

@@ -70,25 +70,44 @@ namespace FlaxEditor.CustomEditors.Editors
LimitAttribute limit = null; LimitAttribute limit = null;
var attributes = Values.GetAttributes(); var attributes = Values.GetAttributes();
var category = Utils.ValueCategory.None;
if (attributes != null) if (attributes != null)
{ {
limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute); limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute);
var categoryAttribute = (ValueCategoryAttribute)attributes.FirstOrDefault(x => x is ValueCategoryAttribute);
if (categoryAttribute != null)
category = categoryAttribute.Category;
} }
XElement = grid.FloatValue(); XElement = grid.FloatValue();
XElement.SetLimits(limit); XElement.SetLimits(limit);
XElement.SetCategory(category);
XElement.ValueBox.ValueChanged += OnXValueChanged; XElement.ValueBox.ValueChanged += OnXValueChanged;
XElement.ValueBox.SlidingEnd += ClearToken; XElement.ValueBox.SlidingEnd += ClearToken;
YElement = grid.FloatValue(); YElement = grid.FloatValue();
YElement.SetLimits(limit); YElement.SetLimits(limit);
YElement.SetCategory(category);
YElement.ValueBox.ValueChanged += OnYValueChanged; YElement.ValueBox.ValueChanged += OnYValueChanged;
YElement.ValueBox.SlidingEnd += ClearToken; YElement.ValueBox.SlidingEnd += ClearToken;
ZElement = grid.FloatValue(); ZElement = grid.FloatValue();
ZElement.SetLimits(limit); ZElement.SetLimits(limit);
ZElement.SetCategory(category);
ZElement.ValueBox.ValueChanged += OnZValueChanged; ZElement.ValueBox.ValueChanged += OnZValueChanged;
ZElement.ValueBox.SlidingEnd += ClearToken; ZElement.ValueBox.SlidingEnd += ClearToken;
LinkedLabel.SetupContextMenu += (label, menu, editor) =>
{
menu.AddSeparator();
var mb = menu.AddButton("Show formatted", bt =>
{
XElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None);
YElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None);
ZElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None);
});
mb.AutoCheck = true;
mb.Checked = XElement.ValueBox.Category != Utils.ValueCategory.None;
};
} }
private void OnXValueChanged() private void OnXValueChanged()
@@ -248,26 +267,45 @@ namespace FlaxEditor.CustomEditors.Editors
gridControl.SlotsVertically = 1; gridControl.SlotsVertically = 1;
LimitAttribute limit = null; LimitAttribute limit = null;
Utils.ValueCategory category = Utils.ValueCategory.None;
var attributes = Values.GetAttributes(); var attributes = Values.GetAttributes();
if (attributes != null) if (attributes != null)
{ {
limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute); limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute);
var categoryAttribute = (ValueCategoryAttribute)attributes.FirstOrDefault(x => x is ValueCategoryAttribute);
if (categoryAttribute != null)
category = categoryAttribute.Category;
} }
XElement = grid.DoubleValue(); XElement = grid.DoubleValue();
XElement.SetLimits(limit); XElement.SetLimits(limit);
XElement.SetCategory(category);
XElement.ValueBox.ValueChanged += OnValueChanged; XElement.ValueBox.ValueChanged += OnValueChanged;
XElement.ValueBox.SlidingEnd += ClearToken; XElement.ValueBox.SlidingEnd += ClearToken;
YElement = grid.DoubleValue(); YElement = grid.DoubleValue();
YElement.SetLimits(limit); YElement.SetLimits(limit);
YElement.SetCategory(category);
YElement.ValueBox.ValueChanged += OnValueChanged; YElement.ValueBox.ValueChanged += OnValueChanged;
YElement.ValueBox.SlidingEnd += ClearToken; YElement.ValueBox.SlidingEnd += ClearToken;
ZElement = grid.DoubleValue(); ZElement = grid.DoubleValue();
ZElement.SetLimits(limit); ZElement.SetLimits(limit);
ZElement.SetCategory(category);
ZElement.ValueBox.ValueChanged += OnValueChanged; ZElement.ValueBox.ValueChanged += OnValueChanged;
ZElement.ValueBox.SlidingEnd += ClearToken; ZElement.ValueBox.SlidingEnd += ClearToken;
LinkedLabel.SetupContextMenu += (label, menu, editor) =>
{
menu.AddSeparator();
var mb = menu.AddButton("Show formatted", bt =>
{
XElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None);
YElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None);
ZElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None);
});
mb.AutoCheck = true;
mb.Checked = XElement.ValueBox.Category != Utils.ValueCategory.None;
};
} }
private void OnValueChanged() private void OnValueChanged()

View File

@@ -51,6 +51,15 @@ namespace FlaxEditor.CustomEditors.Elements
} }
} }
/// <summary>
/// Sets the editor value category.
/// </summary>
/// <param name="category">The category.</param>
public void SetCategory(Utils.ValueCategory category)
{
ValueBox.Category = category;
}
/// <summary> /// <summary>
/// Sets the editor limits from member <see cref="LimitAttribute"/>. /// Sets the editor limits from member <see cref="LimitAttribute"/>.
/// </summary> /// </summary>

View File

@@ -51,6 +51,15 @@ namespace FlaxEditor.CustomEditors.Elements
} }
} }
/// <summary>
/// Sets the editor value category.
/// </summary>
/// <param name="category">The category.</param>
public void SetCategory(Utils.ValueCategory category)
{
ValueBox.Category = category;
}
/// <summary> /// <summary>
/// Sets the editor limits from member <see cref="LimitAttribute"/>. /// Sets the editor limits from member <see cref="LimitAttribute"/>.
/// </summary> /// </summary>

View File

@@ -290,7 +290,6 @@ namespace FlaxEditor
StateMachine = new EditorStateMachine(this); StateMachine = new EditorStateMachine(this);
Undo = new EditorUndo(this); Undo = new EditorUndo(this);
UIControl.FallbackParentGetDelegate += OnUIControlFallbackParentGet;
if (newProject) if (newProject)
InitProject(); InitProject();
@@ -355,27 +354,6 @@ namespace FlaxEditor
StateMachine.LoadingState.StartInitEnding(skipCompile); StateMachine.LoadingState.StartInitEnding(skipCompile);
} }
private ContainerControl OnUIControlFallbackParentGet(UIControl control)
{
// Check if prefab root control is this UIControl
var loadingPreview = Viewport.Previews.PrefabPreview.LoadingPreview;
var activePreviews = Viewport.Previews.PrefabPreview.ActivePreviews;
if (activePreviews != null)
{
foreach (var preview in activePreviews)
{
if (preview == loadingPreview ||
(preview.Instance != null && (preview.Instance == control || preview.Instance.HasActorInHierarchy(control))))
{
// Link it to the prefab preview to see it in the editor
preview.customControlLinked = control;
return preview;
}
}
}
return null;
}
internal void RegisterModule(EditorModule module) internal void RegisterModule(EditorModule module)
{ {
Log("Register Editor module " + module); Log("Register Editor module " + module);

View File

@@ -431,7 +431,6 @@ namespace FlaxEditor.GUI
/// </summary> /// </summary>
protected CurveEditor() protected CurveEditor()
{ {
_tickStrengths = new float[TickSteps.Length];
Accessor.GetDefaultValue(out DefaultValue); Accessor.GetDefaultValue(out DefaultValue);
var style = Style.Current; var style = Style.Current;
@@ -780,75 +779,31 @@ namespace FlaxEditor.GUI
return _mainPanel.PointToParent(point); return _mainPanel.PointToParent(point);
} }
private void DrawAxis(Float2 axis, ref Rectangle viewRect, float min, float max, float pixelRange) private void DrawAxis(Float2 axis, Rectangle viewRect, float min, float max, float pixelRange)
{ {
int minDistanceBetweenTicks = 20; Utilities.Utils.DrawCurveTicks((float tick, float strength) =>
int maxDistanceBetweenTicks = 60;
var range = max - min;
// Find the strength for each modulo number tick marker
int smallestTick = 0;
int biggestTick = TickSteps.Length - 1;
for (int i = TickSteps.Length - 1; i >= 0; i--)
{ {
// Calculate how far apart these modulo tick steps are spaced var p = PointFromKeyframes(axis * tick, ref viewRect);
float tickSpacing = TickSteps[i] * pixelRange / range;
// Calculate the strength of the tick markers based on the spacing // Draw line
_tickStrengths[i] = Mathf.Saturate((tickSpacing - minDistanceBetweenTicks) / (maxDistanceBetweenTicks - minDistanceBetweenTicks)); var lineRect = new Rectangle
(
viewRect.Location + (p - 0.5f) * axis,
Float2.Lerp(viewRect.Size, Float2.One, axis)
);
Render2D.FillRectangle(lineRect, _linesColor.AlphaMultiplied(strength));
// Beyond threshold the ticks don't get any bigger or fatter // Draw label
if (_tickStrengths[i] >= 1) string label = tick.ToString(CultureInfo.InvariantCulture);
biggestTick = i; var labelRect = new Rectangle
(
// Do not show small tick markers viewRect.X + 4.0f + (p.X * axis.X),
if (tickSpacing <= minDistanceBetweenTicks) viewRect.Y - LabelsSize + (p.Y * axis.Y) + (viewRect.Size.Y * axis.X),
{ 50,
smallestTick = i; LabelsSize
break; );
} Render2D.DrawText(_labelsFont, label, labelRect, _labelsColor.AlphaMultiplied(strength), TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f);
} }, TickSteps, ref _tickStrengths, min, max, pixelRange);
// Draw all tick levels
int tickLevels = biggestTick - smallestTick + 1;
for (int level = 0; level < tickLevels; level++)
{
float strength = _tickStrengths[smallestTick + level];
if (strength <= Mathf.Epsilon)
continue;
// Draw all ticks
int l = Mathf.Clamp(smallestTick + level, 0, TickSteps.Length - 1);
int startTick = Mathf.FloorToInt(min / TickSteps[l]);
int endTick = Mathf.CeilToInt(max / TickSteps[l]);
for (int i = startTick; i <= endTick; i++)
{
if (l < biggestTick && (i % Mathf.RoundToInt(TickSteps[l + 1] / TickSteps[l]) == 0))
continue;
var tick = i * TickSteps[l];
var p = PointFromKeyframes(axis * tick, ref viewRect);
// Draw line
var lineRect = new Rectangle
(
viewRect.Location + (p - 0.5f) * axis,
Float2.Lerp(viewRect.Size, Float2.One, axis)
);
Render2D.FillRectangle(lineRect, _linesColor.AlphaMultiplied(strength));
// Draw label
string label = tick.ToString(CultureInfo.InvariantCulture);
var labelRect = new Rectangle
(
viewRect.X + 4.0f + (p.X * axis.X),
viewRect.Y - LabelsSize + (p.Y * axis.Y) + (viewRect.Size.Y * axis.X),
50,
LabelsSize
);
Render2D.DrawText(_labelsFont, label, labelRect, _labelsColor.AlphaMultiplied(strength), TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f);
}
}
} }
/// <summary> /// <summary>
@@ -890,9 +845,9 @@ namespace FlaxEditor.GUI
Render2D.PushClip(ref viewRect); Render2D.PushClip(ref viewRect);
if ((ShowAxes & UseMode.Vertical) == UseMode.Vertical) if ((ShowAxes & UseMode.Vertical) == UseMode.Vertical)
DrawAxis(Float2.UnitX, ref viewRect, min.X, max.X, pixelRange.X); DrawAxis(Float2.UnitX, viewRect, min.X, max.X, pixelRange.X);
if ((ShowAxes & UseMode.Horizontal) == UseMode.Horizontal) if ((ShowAxes & UseMode.Horizontal) == UseMode.Horizontal)
DrawAxis(Float2.UnitY, ref viewRect, min.Y, max.Y, pixelRange.Y); DrawAxis(Float2.UnitY, viewRect, min.Y, max.Y, pixelRange.Y);
Render2D.PopClip(); Render2D.PopClip();
} }

View File

@@ -3,6 +3,7 @@
using System; using System;
using FlaxEditor.Utilities; using FlaxEditor.Utilities;
using FlaxEngine; using FlaxEngine;
using Utils = FlaxEngine.Utils;
namespace FlaxEditor.GUI.Input namespace FlaxEditor.GUI.Input
{ {
@@ -13,6 +14,8 @@ namespace FlaxEditor.GUI.Input
[HideInEditor] [HideInEditor]
public class DoubleValueBox : ValueBox<double> public class DoubleValueBox : ValueBox<double>
{ {
private Utils.ValueCategory _category = Utils.ValueCategory.None;
/// <inheritdoc /> /// <inheritdoc />
public override double Value public override double Value
{ {
@@ -129,10 +132,25 @@ namespace FlaxEditor.GUI.Input
Value = Value; Value = Value;
} }
/// <summary>
/// Gets or sets the category of the value. This can be none for just a number or a more specific one like a distance.
/// </summary>
public Utils.ValueCategory Category
{
get => _category;
set
{
if (_category == value)
return;
_category = value;
UpdateText();
}
}
/// <inheritdoc /> /// <inheritdoc />
protected sealed override void UpdateText() protected sealed override void UpdateText()
{ {
SetText(Utilities.Utils.FormatFloat(_value)); SetText(Utilities.Utils.FormatFloat(_value, Category));
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -1,9 +1,9 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System; using System;
using System.Globalization;
using FlaxEditor.Utilities; using FlaxEditor.Utilities;
using FlaxEngine; using FlaxEngine;
using Utils = FlaxEngine.Utils;
namespace FlaxEditor.GUI.Input namespace FlaxEditor.GUI.Input
{ {
@@ -14,6 +14,8 @@ namespace FlaxEditor.GUI.Input
[HideInEditor] [HideInEditor]
public class FloatValueBox : ValueBox<float> public class FloatValueBox : ValueBox<float>
{ {
private Utils.ValueCategory _category = Utils.ValueCategory.None;
/// <inheritdoc /> /// <inheritdoc />
public override float Value public override float Value
{ {
@@ -137,10 +139,25 @@ namespace FlaxEditor.GUI.Input
Value = Value; Value = Value;
} }
/// <summary>
/// Gets or sets the category of the value. This can be none for just a number or a more specific one like a distance.
/// </summary>
public Utils.ValueCategory Category
{
get => _category;
set
{
if (_category == value)
return;
_category = value;
UpdateText();
}
}
/// <inheritdoc /> /// <inheritdoc />
protected sealed override void UpdateText() protected sealed override void UpdateText()
{ {
SetText(Utilities.Utils.FormatFloat(_value)); SetText(Utilities.Utils.FormatFloat(_value, Category));
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -28,7 +28,6 @@ namespace FlaxEditor.GUI.Timeline.GUI
{ {
_timeline = timeline; _timeline = timeline;
_tickSteps = Utilities.Utils.CurveTickSteps; _tickSteps = Utilities.Utils.CurveTickSteps;
_tickStrengths = new float[_tickSteps.Length];
} }
private void UpdateSelectionRectangle() private void UpdateSelectionRectangle()
@@ -173,55 +172,20 @@ namespace FlaxEditor.GUI.Timeline.GUI
var rightFrame = Mathf.Ceil((right - Timeline.StartOffset) / zoom) * _timeline.FramesPerSecond; var rightFrame = Mathf.Ceil((right - Timeline.StartOffset) / zoom) * _timeline.FramesPerSecond;
var min = leftFrame; var min = leftFrame;
var max = rightFrame; var max = rightFrame;
int smallestTick = 0;
int biggestTick = _tickSteps.Length - 1;
for (int i = _tickSteps.Length - 1; i >= 0; i--)
{
// Calculate how far apart these modulo tick steps are spaced
float tickSpacing = _tickSteps[i] * _timeline.Zoom;
// Calculate the strength of the tick markers based on the spacing
_tickStrengths[i] = Mathf.Saturate((tickSpacing - minDistanceBetweenTicks) / (maxDistanceBetweenTicks - minDistanceBetweenTicks));
// Beyond threshold the ticks don't get any bigger or fatter
if (_tickStrengths[i] >= 1)
biggestTick = i;
// Do not show small tick markers
if (tickSpacing <= minDistanceBetweenTicks)
{
smallestTick = i;
break;
}
}
int tickLevels = biggestTick - smallestTick + 1;
// Draw vertical lines for time axis // Draw vertical lines for time axis
for (int level = 0; level < tickLevels; level++) var pixelsInRange = _timeline.Zoom;
var pixelRange = pixelsInRange * (max - min);
var tickRange = Utilities.Utils.DrawCurveTicks((float tick, float strength) =>
{ {
float strength = _tickStrengths[smallestTick + level]; var time = tick / _timeline.FramesPerSecond;
if (strength <= Mathf.Epsilon) var x = time * zoom + Timeline.StartOffset;
continue; var lineColor = style.ForegroundDisabled.RGBMultiplied(0.7f).AlphaMultiplied(strength);
Render2D.FillRectangle(new Rectangle(x - 0.5f, 0, 1.0f, height), lineColor);
// Draw all ticks }, _tickSteps, ref _tickStrengths, min, max, pixelRange, minDistanceBetweenTicks, maxDistanceBetweenTicks);
int l = Mathf.Clamp(smallestTick + level, 0, _tickSteps.Length - 1); var smallestTick = tickRange.X;
var lStep = _tickSteps[l]; var biggestTick = tickRange.Y;
var lNextStep = _tickSteps[l + 1]; var tickLevels = biggestTick - smallestTick + 1;
int startTick = Mathf.FloorToInt(min / lStep);
int endTick = Mathf.CeilToInt(max / lStep);
Color lineColor = style.ForegroundDisabled.RGBMultiplied(0.7f).AlphaMultiplied(strength);
for (int i = startTick; i <= endTick; i++)
{
if (l < biggestTick && (i % Mathf.RoundToInt(lNextStep / lStep) == 0))
continue;
var tick = i * lStep;
var time = tick / _timeline.FramesPerSecond;
var x = time * zoom + Timeline.StartOffset;
// Draw line
Render2D.FillRectangle(new Rectangle(x - 0.5f, 0, 1.0f, height), lineColor);
}
}
// Draw selection rectangle // Draw selection rectangle
if (_isSelecting) if (_isSelecting)

View File

@@ -161,5 +161,20 @@ namespace FlaxEditor.Gizmo
} }
throw new ArgumentException("Not added mode to activate."); throw new ArgumentException("Not added mode to activate.");
} }
/// <summary>
/// Gets the gizmo of a given type or returns null if not added.
/// </summary>
/// <typeparam name="T">Type of the gizmo.</typeparam>
/// <returns>Found gizmo or null.</returns>
public T Get<T>() where T : GizmoBase
{
foreach (var e in this)
{
if (e is T asT)
return asT;
}
return null;
}
} }
} }

View File

@@ -21,7 +21,7 @@ namespace FlaxEditor.Gizmo
{ {
Order = -100; Order = -100;
UseSingleTarget = true; UseSingleTarget = true;
Location = PostProcessEffectLocation.BeforeForwardPass; Location = PostProcessEffectLocation.AfterAntiAliasingPass;
} }
~Renderer() ~Renderer()
@@ -46,7 +46,8 @@ namespace FlaxEditor.Gizmo
var plane = new Plane(Vector3.Zero, Vector3.UnitY); var plane = new Plane(Vector3.Zero, Vector3.UnitY);
var dst = CollisionsHelper.DistancePlanePoint(ref plane, ref viewPos); var dst = CollisionsHelper.DistancePlanePoint(ref plane, ref viewPos);
float space = Editor.Instance.Options.Options.Viewport.ViewportGridScale, size; var options = Editor.Instance.Options.Options;
float space = options.Viewport.ViewportGridScale, size;
if (dst <= 500.0f) if (dst <= 500.0f)
{ {
size = 8000; size = 8000;
@@ -62,8 +63,12 @@ namespace FlaxEditor.Gizmo
size = 100000; size = 100000;
} }
Color color = Color.Gray * 0.7f; float bigLineIntensity = 0.8f;
Color bigColor = Color.Gray * bigLineIntensity;
Color color = bigColor * 0.8f;
int count = (int)(size / space); int count = (int)(size / space);
int midLine = count / 2;
int bigLinesMod = count / 8;
Vector3 start = new Vector3(0, 0, size * -0.5f); Vector3 start = new Vector3(0, 0, size * -0.5f);
Vector3 end = new Vector3(0, 0, size * 0.5f); Vector3 end = new Vector3(0, 0, size * 0.5f);
@@ -71,7 +76,12 @@ namespace FlaxEditor.Gizmo
for (int i = 0; i <= count; i++) for (int i = 0; i <= count; i++)
{ {
start.X = end.X = i * space + start.Z; start.X = end.X = i * space + start.Z;
DebugDraw.DrawLine(start, end, color); Color lineColor = color;
if (i == midLine)
lineColor = Color.Blue * bigLineIntensity;
else if (i % bigLinesMod == 0)
lineColor = bigColor;
DebugDraw.DrawLine(start, end, lineColor);
} }
start = new Vector3(size * -0.5f, 0, 0); start = new Vector3(size * -0.5f, 0, 0);
@@ -80,7 +90,12 @@ namespace FlaxEditor.Gizmo
for (int i = 0; i <= count; i++) for (int i = 0; i <= count; i++)
{ {
start.Z = end.Z = i * space + start.X; start.Z = end.Z = i * space + start.X;
DebugDraw.DrawLine(start, end, color); Color lineColor = color;
if (i == midLine)
lineColor = Color.Red * bigLineIntensity;
else if (i % bigLinesMod == 0)
lineColor = bigColor;
DebugDraw.DrawLine(start, end, lineColor);
} }
DebugDraw.Draw(ref renderContext, input.View(), null, true); DebugDraw.Draw(ref renderContext, input.View(), null, true);

View File

@@ -117,5 +117,10 @@ namespace FlaxEditor.Gizmo
/// </summary> /// </summary>
/// <param name="actor">The new actor to spawn.</param> /// <param name="actor">The new actor to spawn.</param>
void Spawn(Actor actor); void Spawn(Actor actor);
/// <summary>
/// Opens the context menu at the current mouse location (using current selection).
/// </summary>
void OpenContextMenu();
} }
} }

View File

@@ -42,6 +42,11 @@ namespace FlaxEditor.Gizmo
/// </summary> /// </summary>
public Action Duplicate; public Action Duplicate;
/// <summary>
/// Gets the array of selected objects.
/// </summary>
public List<SceneGraphNode> Selection => _selection;
/// <summary> /// <summary>
/// Gets the array of selected parent objects (as actors). /// Gets the array of selected parent objects (as actors).
/// </summary> /// </summary>

View File

@@ -0,0 +1,891 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Gizmo;
using FlaxEditor.SceneGraph;
using FlaxEditor.SceneGraph.Actors;
using FlaxEditor.Viewport.Cameras;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor
{
/// <summary>
/// UI editor camera.
/// </summary>
[HideInEditor]
internal sealed class UIEditorCamera : ViewportCamera
{
public UIEditorRoot UIEditor;
public void ShowActors(IEnumerable<Actor> actors)
{
var root = UIEditor.UIRoot;
if (root == null)
return;
// Calculate bounds of all selected objects
var areaRect = Rectangle.Empty;
foreach (var actor in actors)
{
Rectangle bounds;
if (actor is UIControl uiControl && uiControl.HasControl && uiControl.IsActive)
{
var control = uiControl.Control;
bounds = control.EditorBounds;
var ul = control.PointToParent(root, bounds.UpperLeft);
var ur = control.PointToParent(root, bounds.UpperRight);
var bl = control.PointToParent(root, bounds.BottomLeft);
var br = control.PointToParent(root, bounds.BottomRight);
var min = Float2.Min(Float2.Min(ul, ur), Float2.Min(bl, br));
var max = Float2.Max(Float2.Max(ul, ur), Float2.Max(bl, br));
bounds = new Rectangle(min, Float2.Max(max - min, Float2.Zero));
}
else if (actor is UICanvas uiCanvas && uiCanvas.IsActive && uiCanvas.GUI.Parent == root)
{
bounds = uiCanvas.GUI.Bounds;
}
else
continue;
if (areaRect == Rectangle.Empty)
areaRect = bounds;
else
areaRect = Rectangle.Union(areaRect, bounds);
}
if (areaRect == Rectangle.Empty)
return;
// Add margin
areaRect = areaRect.MakeExpanded(100.0f);
// Show bounds
UIEditor.ViewScale = (UIEditor.Size / areaRect.Size).MinValue * 0.95f;
UIEditor.ViewCenterPosition = areaRect.Center;
}
public override void FocusSelection(GizmosCollection gizmos, ref Quaternion orientation)
{
ShowActors(gizmos.Get<TransformGizmo>().Selection, ref orientation);
}
public override void ShowActor(Actor actor)
{
ShowActors(new[] { actor });
}
public override void ShowActors(List<SceneGraphNode> selection, ref Quaternion orientation)
{
ShowActors(selection.ConvertAll(x => (Actor)x.EditableObject));
}
public override void UpdateView(float dt, ref Vector3 moveDelta, ref Float2 mouseDelta, out bool centerMouse)
{
centerMouse = false;
}
}
/// <summary>
/// Root control for UI Controls presentation in the game/prefab viewport.
/// </summary>
[HideInEditor]
internal class UIEditorRoot : InputsPassThrough
{
/// <summary>
/// View for the UI structure to be linked in for camera zoom and panning operations.
/// </summary>
private sealed class View : ContainerControl
{
public View(UIEditorRoot parent)
{
AutoFocus = false;
ClipChildren = false;
CullChildren = false;
Pivot = Float2.Zero;
Size = new Float2(1920, 1080);
Parent = parent;
}
public override bool RayCast(ref Float2 location, out Control hit)
{
// Ignore self
return RayCastChildren(ref location, out hit);
}
public override bool IntersectsContent(ref Float2 locationParent, out Float2 location)
{
location = PointFromParent(ref locationParent);
return true;
}
public override void DrawSelf()
{
var uiRoot = (UIEditorRoot)Parent;
if (!uiRoot.EnableBackground)
return;
// Draw canvas area
var bounds = new Rectangle(Float2.Zero, Size);
Render2D.FillRectangle(bounds, new Color(0, 0, 0, 0.2f));
}
}
/// <summary>
/// Cached placement of the widget used to size/edit control
/// </summary>
private struct Widget
{
public UIControl UIControl;
public Rectangle Bounds;
public Float2 ResizeAxis;
public CursorType Cursor;
}
private bool _mouseMovesControl, _mouseMovesView, _mouseMovesWidget;
private Float2 _mouseMovesPos, _moveSnapDelta;
private float _mouseMoveSum;
private UndoMultiBlock _undoBlock;
private View _view;
private float[] _gridTickSteps = Utilities.Utils.CurveTickSteps, _gridTickStrengths;
private List<Widget> _widgets;
private Widget _activeWidget;
/// <summary>
/// True if enable displaying UI editing background and grid elements.
/// </summary>
public virtual bool EnableBackground => false;
/// <summary>
/// True if enable selecting controls with mouse button.
/// </summary>
public virtual bool EnableSelecting => false;
/// <summary>
/// True if enable panning and zooming the view.
/// </summary>
public bool EnableCamera => _view != null && EnableBackground;
/// <summary>
/// Transform gizmo to use sync with (selection, snapping, transformation settings).
/// </summary>
public virtual TransformGizmo TransformGizmo => null;
/// <summary>
/// The root control for controls to be linked in.
/// </summary>
public readonly ContainerControl UIRoot;
internal Float2 ViewPosition
{
get => _view.Location / -ViewScale;
set => _view.Location = value * -ViewScale;
}
internal Float2 ViewCenterPosition
{
get => (_view.Location - Size * 0.5f) / -ViewScale;
set => _view.Location = Size * 0.5f + value * -ViewScale;
}
internal float ViewScale
{
get => _view?.Scale.X ?? 1;
set
{
if (_view == null)
return;
value = Mathf.Clamp(value, 0.1f, 4.0f);
_view.Scale = new Float2(value);
}
}
public UIEditorRoot(bool enableCamera = false)
{
AnchorPreset = AnchorPresets.StretchAll;
Offsets = Margin.Zero;
AutoFocus = false;
UIRoot = this;
CullChildren = false;
ClipChildren = true;
if (enableCamera)
{
_view = new View(this);
UIRoot = _view;
}
}
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (base.OnMouseDown(location, button))
return true;
var transformGizmo = TransformGizmo;
var owner = transformGizmo?.Owner;
if (_widgets != null && _widgets.Count != 0 && button == MouseButton.Left)
{
foreach (var widget in _widgets)
{
if (widget.Bounds.Contains(ref location))
{
// Initialize widget movement
_activeWidget = widget;
_mouseMovesWidget = true;
_mouseMovesPos = location;
Cursor = widget.Cursor;
StartUndo();
Focus();
StartMouseCapture();
return true;
}
}
}
if (EnableSelecting && owner != null && !_mouseMovesControl && button == MouseButton.Left)
{
// Raycast the control under the mouse
var mousePos = PointFromWindow(RootWindow.MousePosition);
if (RayCastControl(ref mousePos, out var hitControl))
{
var uiControlNode = FindUIControlNode(hitControl);
if (uiControlNode != null)
{
// Select node (with additive mode)
var selection = new List<SceneGraphNode>();
if (Root.GetKey(KeyboardKeys.Control))
{
// Add/remove from selection
selection.AddRange(transformGizmo.Selection);
if (transformGizmo.Selection.Contains(uiControlNode))
selection.Remove(uiControlNode);
else
selection.Add(uiControlNode);
}
else
{
// Select
selection.Add(uiControlNode);
}
owner.Select(selection);
// Initialize control movement
_mouseMovesControl = true;
_mouseMovesPos = location;
_mouseMoveSum = 0.0f;
_moveSnapDelta = Float2.Zero;
Focus();
StartMouseCapture();
return true;
}
}
// Allow deselecting if user clicks on nothing
else
{
owner.Select(null);
}
}
if (EnableCamera && (button == MouseButton.Right || button == MouseButton.Middle))
{
// Initialize surface movement
_mouseMovesView = true;
_mouseMovesPos = location;
_mouseMoveSum = 0.0f;
Focus();
StartMouseCapture();
return true;
}
return Focus(this);
}
public override void OnMouseMove(Float2 location)
{
base.OnMouseMove(location);
// Change cursor if mouse is over active control widget
bool cursorChanged = false;
if (_widgets != null && _widgets.Count != 0 && !_mouseMovesControl && !_mouseMovesWidget && !_mouseMovesView)
{
foreach (var widget in _widgets)
{
if (widget.Bounds.Contains(ref location))
{
Cursor = widget.Cursor;
cursorChanged = true;
}
else if (Cursor != CursorType.Default && !cursorChanged)
{
Cursor = CursorType.Default;
}
}
}
var transformGizmo = TransformGizmo;
if (_mouseMovesControl && transformGizmo != null)
{
// Calculate transform delta
var delta = location - _mouseMovesPos;
if (transformGizmo.TranslationSnapEnable || transformGizmo.Owner.UseSnapping)
{
_moveSnapDelta += delta;
delta = Float2.SnapToGrid(_moveSnapDelta, new Float2(transformGizmo.TranslationSnapValue * ViewScale));
_moveSnapDelta -= delta;
}
// Move selected controls
if (delta.LengthSquared > 0.0f)
{
StartUndo();
var moved = false;
var moveLocation = _mouseMovesPos + delta;
var selection = transformGizmo.Selection;
for (var i = 0; i < selection.Count; i++)
{
if (IsValidControl(selection[i], out var uiControl))
{
// Move control (handle any control transformations by moving in editor's local-space)
var control = uiControl.Control;
var localLocation = control.LocalLocation;
var uiControlDelta = GetControlDelta(control, ref _mouseMovesPos, ref moveLocation);
control.LocalLocation = localLocation + uiControlDelta;
// Don't move if layout doesn't allow it
if (control.Parent != null)
control.Parent.PerformLayout();
else
control.PerformLayout();
// Check if control was moved (parent container could block it)
if (localLocation != control.LocalLocation)
moved = true;
}
}
_mouseMovesPos = location;
_mouseMoveSum += delta.Length;
if (moved)
Cursor = CursorType.SizeAll;
}
}
if (_mouseMovesWidget && _activeWidget.UIControl)
{
// Calculate transform delta
var resizeAxisAbs = _activeWidget.ResizeAxis.Absolute;
var resizeAxisPos = Float2.Clamp(_activeWidget.ResizeAxis, Float2.Zero, Float2.One);
var resizeAxisNeg = Float2.Clamp(-_activeWidget.ResizeAxis, Float2.Zero, Float2.One);
var delta = location - _mouseMovesPos;
// TODO: scale/size snapping?
delta *= resizeAxisAbs;
// Resize control via widget
var moveLocation = _mouseMovesPos + delta;
var control = _activeWidget.UIControl.Control;
var uiControlDelta = GetControlDelta(control, ref _mouseMovesPos, ref moveLocation);
control.LocalLocation += uiControlDelta * resizeAxisNeg;
control.Size += uiControlDelta * resizeAxisPos - uiControlDelta * resizeAxisNeg;
// Don't move if layout doesn't allow it
if (control.Parent != null)
control.Parent.PerformLayout();
else
control.PerformLayout();
_mouseMovesPos = location;
}
if (_mouseMovesView)
{
// Move view
var delta = location - _mouseMovesPos;
if (delta.LengthSquared > 4.0f)
{
_mouseMovesPos = location;
_mouseMoveSum += delta.Length;
_view.Location += delta;
Cursor = CursorType.SizeAll;
}
}
}
public override bool OnMouseUp(Float2 location, MouseButton button)
{
EndMovingControls();
EndMovingWidget();
if (_mouseMovesView)
{
EndMovingView();
if (button == MouseButton.Right && _mouseMoveSum < 2.0f)
TransformGizmo.Owner.OpenContextMenu();
}
return base.OnMouseUp(location, button);
}
public override void OnMouseLeave()
{
EndMovingControls();
EndMovingView();
EndMovingWidget();
base.OnMouseLeave();
}
public override void OnLostFocus()
{
EndMovingControls();
EndMovingView();
EndMovingWidget();
base.OnLostFocus();
}
public override bool OnMouseWheel(Float2 location, float delta)
{
if (base.OnMouseWheel(location, delta))
return true;
if (EnableCamera && !_mouseMovesControl)
{
// Zoom view
var nextViewScale = ViewScale + delta * 0.1f;
if (delta > 0 && !_mouseMovesControl)
{
// Scale towards mouse when zooming in
var nextCenterPosition = ViewPosition + location / ViewScale;
ViewScale = nextViewScale;
ViewPosition = nextCenterPosition - (location / ViewScale);
}
else
{
// Scale while keeping center position when zooming out or when dragging view
var viewCenter = ViewCenterPosition;
ViewScale = nextViewScale;
ViewCenterPosition = viewCenter;
}
return true;
}
return false;
}
public override void Draw()
{
if (EnableBackground && _view != null)
{
// Draw background
Surface.VisjectSurface.DrawBackgroundDefault(Editor.Instance.UI.VisjectSurfaceBackground, Width, Height);
// Draw grid
var viewRect = GetClientArea();
var upperLeft = _view.PointFromParent(viewRect.Location);
var bottomRight = _view.PointFromParent(viewRect.Size);
var min = Float2.Min(upperLeft, bottomRight);
var max = Float2.Max(upperLeft, bottomRight);
var pixelRange = (max - min) * ViewScale;
Render2D.PushClip(ref viewRect);
DrawAxis(Float2.UnitX, viewRect, min.X, max.X, pixelRange.X);
DrawAxis(Float2.UnitY, viewRect, min.Y, max.Y, pixelRange.Y);
Render2D.PopClip();
}
base.Draw();
if (!_mouseMovesWidget)
{
// Clear widgets to collect them during drawing
_widgets?.Clear();
}
bool drawAnySelectedControl = false;
var transformGizmo = TransformGizmo;
var mousePos = PointFromWindow(RootWindow.MousePosition);
if (transformGizmo != null)
{
// Selected UI controls outline
var selection = transformGizmo.Selection;
for (var i = 0; i < selection.Count; i++)
{
if (IsValidControl(selection[i], out var controlActor))
{
DrawControl(controlActor, controlActor.Control, true, ref mousePos, ref drawAnySelectedControl, EnableSelecting);
}
}
}
if (EnableSelecting && !_mouseMovesControl && !_mouseMovesWidget && IsMouseOver)
{
// Highlight control under mouse for easier selecting (except if already selected)
if (RayCastControl(ref mousePos, out var hitControl) &&
(transformGizmo == null || !transformGizmo.Selection.Any(x => x.EditableObject is UIControl controlActor && controlActor.Control == hitControl)))
{
DrawControl(null, hitControl, false, ref mousePos, ref drawAnySelectedControl);
}
}
if (drawAnySelectedControl)
Render2D.PopTransform();
if (EnableBackground)
{
// Draw border
if (ContainsFocus)
{
Render2D.DrawRectangle(new Rectangle(1, 1, Width - 2, Height - 2), Editor.IsPlayMode ? Color.OrangeRed : Style.Current.BackgroundSelected);
}
}
}
public override void OnDestroy()
{
if (IsDisposing)
return;
EndMovingControls();
EndMovingView();
EndMovingWidget();
base.OnDestroy();
}
private Float2 GetControlDelta(Control control, ref Float2 start, ref Float2 end)
{
var pointOrigin = control.Parent ?? control;
var startPos = pointOrigin.PointFromParent(this, start);
var endPos = pointOrigin.PointFromParent(this, end);
return endPos - startPos;
}
private void DrawAxis(Float2 axis, Rectangle viewRect, float min, float max, float pixelRange)
{
var style = Style.Current;
var linesColor = style.ForegroundDisabled.RGBMultiplied(0.5f);
var labelsColor = style.ForegroundDisabled;
var labelsSize = 10.0f;
Utilities.Utils.DrawCurveTicks((float tick, float strength) =>
{
var p = _view.PointToParent(axis * tick);
// Draw line
var lineRect = new Rectangle
(
viewRect.Location + (p - 0.5f) * axis,
Float2.Lerp(viewRect.Size, Float2.One, axis)
);
Render2D.FillRectangle(lineRect, linesColor.AlphaMultiplied(strength));
// Draw label
string label = tick.ToString(System.Globalization.CultureInfo.InvariantCulture);
var labelRect = new Rectangle
(
viewRect.X + 4.0f + (p.X * axis.X),
viewRect.Y - labelsSize + (p.Y * axis.Y) + (viewRect.Size.Y * axis.X),
50,
labelsSize
);
Render2D.DrawText(style.FontSmall, label, labelRect, labelsColor.AlphaMultiplied(strength), TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f);
}, _gridTickSteps, ref _gridTickStrengths, min, max, pixelRange);
}
private void DrawControl(UIControl uiControl, Control control, bool selection, ref Float2 mousePos, ref bool drawAnySelectedControl, bool withWidgets = false)
{
if (!drawAnySelectedControl)
{
drawAnySelectedControl = true;
Render2D.PushTransform(ref _cachedTransform);
}
var options = Editor.Instance.Options.Options.Visual;
// Draw bounds
var bounds = control.EditorBounds;
var ul = control.PointToParent(this, bounds.UpperLeft);
var ur = control.PointToParent(this, bounds.UpperRight);
var bl = control.PointToParent(this, bounds.BottomLeft);
var br = control.PointToParent(this, bounds.BottomRight);
var color = selection ? options.SelectionOutlineColor0 : Style.Current.SelectionBorder;
#if false
// AABB
var min = Float2.Min(Float2.Min(ul, ur), Float2.Min(bl, br));
var max = Float2.Max(Float2.Max(ul, ur), Float2.Max(bl, br));
bounds = new Rectangle(min, Float2.Max(max - min, Float2.Zero));
Render2D.DrawRectangle(bounds, color, options.UISelectionOutlineSize);
#else
// OBB
Render2D.DrawLine(ul, ur, color, options.UISelectionOutlineSize);
Render2D.DrawLine(ur, br, color, options.UISelectionOutlineSize);
Render2D.DrawLine(br, bl, color, options.UISelectionOutlineSize);
Render2D.DrawLine(bl, ul, color, options.UISelectionOutlineSize);
#endif
if (withWidgets)
{
// Draw sizing widgets
if (_widgets == null)
_widgets = new List<Widget>();
var widgetSize = 8.0f;
var viewScale = ViewScale;
if (viewScale < 0.7f)
widgetSize *= viewScale;
var controlSize = control.Size.Absolute.MinValue / 50.0f;
if (controlSize < 1.0f)
widgetSize *= Mathf.Clamp(controlSize + 0.1f, 0.1f, 1.0f);
var cornerSize = new Float2(widgetSize);
DrawControlWidget(uiControl, ref ul, ref mousePos, ref cornerSize, new Float2(-1, -1), CursorType.SizeNWSE);
DrawControlWidget(uiControl, ref ur, ref mousePos, ref cornerSize, new Float2(1, -1), CursorType.SizeNESW);
DrawControlWidget(uiControl, ref bl, ref mousePos, ref cornerSize, new Float2(-1, 1), CursorType.SizeNESW);
DrawControlWidget(uiControl, ref br, ref mousePos, ref cornerSize, new Float2(1, 1), CursorType.SizeNWSE);
var edgeSizeV = new Float2(widgetSize * 2, widgetSize);
var edgeSizeH = new Float2(edgeSizeV.Y, edgeSizeV.X);
Float2.Lerp(ref ul, ref bl, 0.5f, out var el);
Float2.Lerp(ref ur, ref br, 0.5f, out var er);
Float2.Lerp(ref ul, ref ur, 0.5f, out var eu);
Float2.Lerp(ref bl, ref br, 0.5f, out var eb);
DrawControlWidget(uiControl, ref el, ref mousePos, ref edgeSizeH, new Float2(-1, 0), CursorType.SizeWE);
DrawControlWidget(uiControl, ref er, ref mousePos, ref edgeSizeH, new Float2(1, 0), CursorType.SizeWE);
DrawControlWidget(uiControl, ref eu, ref mousePos, ref edgeSizeV, new Float2(0, -1), CursorType.SizeNS);
DrawControlWidget(uiControl, ref eb, ref mousePos, ref edgeSizeV, new Float2(0, 1), CursorType.SizeNS);
// TODO: draw anchors
}
}
private void DrawControlWidget(UIControl uiControl, ref Float2 pos, ref Float2 mousePos, ref Float2 size, Float2 resizeAxis, CursorType cursor)
{
var style = Style.Current;
var rect = new Rectangle(pos - size * 0.5f, size);
if (rect.Contains(ref mousePos))
{
Render2D.FillRectangle(rect, style.Foreground);
}
else
{
Render2D.FillRectangle(rect, style.ForegroundGrey);
Render2D.DrawRectangle(rect, style.Foreground);
}
if (!_mouseMovesWidget && uiControl != null)
{
// Collect widget
_widgets.Add(new Widget
{
UIControl = uiControl,
Bounds = rect,
ResizeAxis = resizeAxis,
Cursor = cursor,
});
}
}
private bool IsValidControl(SceneGraphNode node, out UIControl uiControl)
{
uiControl = null;
if (node.EditableObject is UIControl controlActor)
uiControl = controlActor;
return uiControl != null &&
uiControl.Control != null &&
uiControl.Control.VisibleInHierarchy &&
uiControl.Control.RootWindow != null;
}
private bool RayCastControl(ref Float2 location, out Control hit)
{
#if false
// Raycast only controls with content (eg. skips transparent panels)
return RayCastChildren(ref location, out hit);
#else
// Find any control under mouse (hierarchical)
hit = GetChildAtRecursive(location);
if (hit is View || hit is CanvasContainer)
hit = null;
return hit != null;
#endif
}
private UIControlNode FindUIControlNode(Control control)
{
return FindUIControlNode(TransformGizmo.Owner.SceneGraphRoot, control);
}
private UIControlNode FindUIControlNode(SceneGraphNode node, Control control)
{
var result = node as UIControlNode;
if (result != null && ((UIControl)result.Actor).Control == control)
return result;
foreach (var e in node.ChildNodes)
{
result = FindUIControlNode(e, control);
if (result != null)
return result;
}
return null;
}
private void StartUndo()
{
var undo = TransformGizmo?.Owner?.Undo;
if (undo == null || _undoBlock != null)
return;
_undoBlock = new UndoMultiBlock(undo, TransformGizmo.Selection.ConvertAll(x => x.EditableObject), "Edit control");
}
private void EndUndo()
{
if (_undoBlock == null)
return;
_undoBlock.Dispose();
_undoBlock = null;
}
private void EndMovingControls()
{
if (!_mouseMovesControl)
return;
_mouseMovesControl = false;
EndMouseCapture();
Cursor = CursorType.Default;
EndUndo();
}
private void EndMovingView()
{
if (!_mouseMovesView)
return;
_mouseMovesView = false;
EndMouseCapture();
Cursor = CursorType.Default;
}
private void EndMovingWidget()
{
if (!_mouseMovesWidget)
return;
_mouseMovesWidget = false;
_activeWidget = new Widget();
EndMouseCapture();
Cursor = CursorType.Default;
EndUndo();
}
}
/// <summary>
/// Control that can optionally disable inputs to the children.
/// </summary>
[HideInEditor]
internal class InputsPassThrough : ContainerControl
{
private bool _isMouseOver;
/// <summary>
/// True if enable input events passing to the UI.
/// </summary>
public virtual bool EnableInputs => true;
public override bool RayCast(ref Float2 location, out Control hit)
{
return RayCastChildren(ref location, out hit);
}
public override bool ContainsPoint(ref Float2 location, bool precise = false)
{
if (precise)
return false;
return base.ContainsPoint(ref location, precise);
}
public override bool OnCharInput(char c)
{
if (!EnableInputs)
return false;
return base.OnCharInput(c);
}
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
{
if (!EnableInputs)
return DragDropEffect.None;
return base.OnDragDrop(ref location, data);
}
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
{
if (!EnableInputs)
return DragDropEffect.None;
return base.OnDragEnter(ref location, data);
}
public override void OnDragLeave()
{
if (!EnableInputs)
return;
base.OnDragLeave();
}
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
{
if (!EnableInputs)
return DragDropEffect.None;
return base.OnDragMove(ref location, data);
}
public override bool OnKeyDown(KeyboardKeys key)
{
if (!EnableInputs)
return false;
return base.OnKeyDown(key);
}
public override void OnKeyUp(KeyboardKeys key)
{
if (!EnableInputs)
return;
base.OnKeyUp(key);
}
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
{
if (!EnableInputs)
return false;
return base.OnMouseDoubleClick(location, button);
}
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (!EnableInputs)
return false;
return base.OnMouseDown(location, button);
}
public override bool IsMouseOver => _isMouseOver;
public override void OnMouseEnter(Float2 location)
{
_isMouseOver = true;
if (!EnableInputs)
return;
base.OnMouseEnter(location);
}
public override void OnMouseLeave()
{
_isMouseOver = false;
if (!EnableInputs)
return;
base.OnMouseLeave();
}
public override void OnMouseMove(Float2 location)
{
if (!EnableInputs)
return;
base.OnMouseMove(location);
}
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (!EnableInputs)
return false;
return base.OnMouseUp(location, button);
}
public override bool OnMouseWheel(Float2 location, float delta)
{
if (!EnableInputs)
return false;
return base.OnMouseWheel(location, delta);
}
}
}

View File

@@ -201,8 +201,8 @@ namespace FlaxEditor.Options
/// <returns>True if input has been processed, otherwise false.</returns> /// <returns>True if input has been processed, otherwise false.</returns>
public bool Process(Control control) public bool Process(Control control)
{ {
var root = control.Root; var root = control?.Root;
return root.GetKey(Key) && ProcessModifiers(control); return root != null && root.GetKey(Key) && ProcessModifiers(control);
} }
/// <summary> /// <summary>

View File

@@ -2,6 +2,7 @@
using System.ComponentModel; using System.ComponentModel;
using FlaxEditor.GUI.Docking; using FlaxEditor.GUI.Docking;
using FlaxEditor.Utilities;
using FlaxEngine; using FlaxEngine;
namespace FlaxEditor.Options namespace FlaxEditor.Options
@@ -116,6 +117,27 @@ namespace FlaxEditor.Options
BorderlessWindow, BorderlessWindow,
} }
/// <summary>
/// Options for formatting numerical values.
/// </summary>
public enum ValueFormattingType
{
/// <summary>
/// No formatting.
/// </summary>
None,
/// <summary>
/// Format using the base SI unit.
/// </summary>
BaseUnit,
/// <summary>
/// Format using a unit that matches the value best.
/// </summary>
AutoUnit,
}
/// <summary> /// <summary>
/// Gets or sets the Editor User Interface scale. Applied to all UI elements, windows and text. Can be used to scale the interface up on a bigger display. Editor restart required. /// Gets or sets the Editor User Interface scale. Applied to all UI elements, windows and text. Can be used to scale the interface up on a bigger display. Editor restart required.
/// </summary> /// </summary>
@@ -174,6 +196,20 @@ namespace FlaxEditor.Options
[EditorDisplay("Interface"), EditorOrder(280), Tooltip("Editor content window orientation.")] [EditorDisplay("Interface"), EditorOrder(280), Tooltip("Editor content window orientation.")]
public FlaxEngine.GUI.Orientation ContentWindowOrientation { get; set; } = FlaxEngine.GUI.Orientation.Horizontal; public FlaxEngine.GUI.Orientation ContentWindowOrientation { get; set; } = FlaxEngine.GUI.Orientation.Horizontal;
/// <summary>
/// Gets or sets the formatting option for numeric values in the editor.
/// </summary>
[DefaultValue(ValueFormattingType.None)]
[EditorDisplay("Interface"), EditorOrder(300)]
public ValueFormattingType ValueFormatting { get; set; }
/// <summary>
/// Gets or sets the option to put a space between numbers and units for unit formatting.
/// </summary>
[DefaultValue(false)]
[EditorDisplay("Interface"), EditorOrder(310)]
public bool SeparateValueAndUnit { get; set; }
/// <summary> /// <summary>
/// Gets or sets the timestamps prefix mode for output log messages. /// Gets or sets the timestamps prefix mode for output log messages.
/// </summary> /// </summary>

View File

@@ -200,6 +200,27 @@ namespace FlaxEditor.Options
EditorAssets.Cache.OnEditorOptionsChanged(Options); EditorAssets.Cache.OnEditorOptionsChanged(Options);
// Units formatting options
bool useUnitsFormatting = Options.Interface.ValueFormatting != InterfaceOptions.ValueFormattingType.None;
bool automaticUnitsFormatting = Options.Interface.ValueFormatting == InterfaceOptions.ValueFormattingType.AutoUnit;
bool separateValueAndUnit = Options.Interface.SeparateValueAndUnit;
if (useUnitsFormatting != Utilities.Units.UseUnitsFormatting ||
automaticUnitsFormatting != Utilities.Units.AutomaticUnitsFormatting ||
separateValueAndUnit != Utilities.Units.SeparateValueAndUnit)
{
Utilities.Units.UseUnitsFormatting = useUnitsFormatting;
Utilities.Units.AutomaticUnitsFormatting = automaticUnitsFormatting;
Utilities.Units.SeparateValueAndUnit = separateValueAndUnit;
// Refresh UI in property panels
Editor.Windows.PropertiesWin?.Presenter.BuildLayoutOnUpdate();
foreach (var window in Editor.Windows.Windows)
{
if (window is Windows.Assets.PrefabWindow prefabWindow)
prefabWindow.Presenter.BuildLayoutOnUpdate();
}
}
// Send event // Send event
OptionsChanged?.Invoke(Options); OptionsChanged?.Invoke(Options);
} }

View File

@@ -617,8 +617,9 @@ namespace FlaxEditor.Surface.Archetypes
public override void SetLocation(int index, Float2 location) public override void SetLocation(int index, Float2 location)
{ {
var dataA = (Float4)_node.Values[4 + index * 2]; var dataA = (Float4)_node.Values[4 + index * 2];
var ranges = (Float4)_node.Values[0];
dataA.X = location.X; dataA.X = Mathf.Clamp(location.X, ranges.X, ranges.Y);
_node.Values[4 + index * 2] = dataA; _node.Values[4 + index * 2] = dataA;
_node.Surface.MarkAsEdited(); _node.Surface.MarkAsEdited();
@@ -750,9 +751,10 @@ namespace FlaxEditor.Surface.Archetypes
public override void SetLocation(int index, Float2 location) public override void SetLocation(int index, Float2 location)
{ {
var dataA = (Float4)_node.Values[4 + index * 2]; var dataA = (Float4)_node.Values[4 + index * 2];
var ranges = (Float4)_node.Values[0];
dataA.X = location.X; dataA.X = Mathf.Clamp(location.X, ranges.X, ranges.Y);
dataA.Y = location.Y; dataA.Y = Mathf.Clamp(location.Y, ranges.Z, ranges.W);
_node.Values[4 + index * 2] = dataA; _node.Values[4 + index * 2] = dataA;
_node.Surface.MarkAsEdited(); _node.Surface.MarkAsEdited();

View File

@@ -64,7 +64,11 @@ namespace FlaxEditor.Surface
/// </summary> /// </summary>
protected virtual void DrawBackground() protected virtual void DrawBackground()
{ {
var background = Style.Background; DrawBackgroundDefault(Style.Background, Width, Height);
}
internal static void DrawBackgroundDefault(Texture background, float width, float height)
{
if (background && background.ResidentMipLevels > 0) if (background && background.ResidentMipLevels > 0)
{ {
var bSize = background.Size; var bSize = background.Size;
@@ -77,8 +81,8 @@ namespace FlaxEditor.Surface
if (pos.Y > 0) if (pos.Y > 0)
pos.Y -= bh; pos.Y -= bh;
int maxI = Mathf.CeilToInt(Width / bw + 1.0f); int maxI = Mathf.CeilToInt(width / bw + 1.0f);
int maxJ = Mathf.CeilToInt(Height / bh + 1.0f); int maxJ = Mathf.CeilToInt(height / bh + 1.0f);
for (int i = 0; i < maxI; i++) for (int i = 0; i < maxI; i++)
{ {

View File

@@ -121,6 +121,37 @@ namespace FlaxEditor.Utilities
["e"] = Math.E, ["e"] = Math.E,
["infinity"] = double.MaxValue, ["infinity"] = double.MaxValue,
["-infinity"] = -double.MaxValue, ["-infinity"] = -double.MaxValue,
["m"] = Units.Meters2Units,
["cm"] = Units.Meters2Units / 100,
["km"] = Units.Meters2Units * 1000,
["s"] = 1,
["ms"] = 0.001,
["min"] = 60,
["h"] = 3600,
["cm²"] = (Units.Meters2Units / 100) * (Units.Meters2Units / 100),
["cm³"] = (Units.Meters2Units / 100) * (Units.Meters2Units / 100) * (Units.Meters2Units / 100),
["dm²"] = (Units.Meters2Units / 10) * (Units.Meters2Units / 10),
["dm³"] = (Units.Meters2Units / 10) * (Units.Meters2Units / 10) * (Units.Meters2Units / 10),
["l"] = (Units.Meters2Units / 10) * (Units.Meters2Units / 10) * (Units.Meters2Units / 10),
["m²"] = Units.Meters2Units * Units.Meters2Units,
["m³"] = Units.Meters2Units * Units.Meters2Units * Units.Meters2Units,
["kg"] = 1,
["g"] = 0.001,
["n"] = Units.Meters2Units
};
/// <summary>
/// List known units which cannot be handled as a variable easily because they contain operator symbols (mostly a forward slash). The value is the factor to calculate game units.
/// </summary>
private static readonly IDictionary<string, double> UnitSymbols = new Dictionary<string, double>
{
["cm/s"] = Units.Meters2Units / 100,
["cm/s²"] = Units.Meters2Units / 100,
["m/s"] = Units.Meters2Units,
["m/s²"] = Units.Meters2Units,
["km/h"] = 1 / 3.6 * Units.Meters2Units,
// Nm is here because these values are compared case-sensitive, and we don't want to confuse nanometers and Newtonmeters
["Nm"] = Units.Meters2Units * Units.Meters2Units,
}; };
/// <summary> /// <summary>
@@ -156,7 +187,7 @@ namespace FlaxEditor.Utilities
if (Operators.ContainsKey(str)) if (Operators.ContainsKey(str))
return TokenType.Operator; return TokenType.Operator;
if (char.IsLetter(c)) if (char.IsLetter(c) || c == '²' || c == '³')
return TokenType.Variable; return TokenType.Variable;
throw new ParsingException("wrong character"); throw new ParsingException("wrong character");
@@ -170,7 +201,24 @@ namespace FlaxEditor.Utilities
public static IEnumerable<Token> Tokenize(string text) public static IEnumerable<Token> Tokenize(string text)
{ {
// Prepare text // Prepare text
text = text.Replace(',', '.'); text = text.Replace(',', '.').Replace("°", "");
foreach (var kv in UnitSymbols)
{
int idx;
do
{
idx = text.IndexOf(kv.Key, StringComparison.InvariantCulture);
if (idx > 0)
{
if (DetermineType(text[idx - 1]) != TokenType.Number)
throw new ParsingException($"unit found without a number: {kv.Key} at {idx} in {text}");
if (Mathf.Abs(kv.Value - 1) < Mathf.Epsilon)
text = text.Remove(idx, kv.Key.Length);
else
text = text.Replace(kv.Key, "*" + kv.Value);
}
} while (idx > 0);
}
// Necessary to correctly parse negative numbers // Necessary to correctly parse negative numbers
var previous = TokenType.WhiteSpace; var previous = TokenType.WhiteSpace;
@@ -240,6 +288,11 @@ namespace FlaxEditor.Utilities
} }
else if (type == TokenType.Variable) else if (type == TokenType.Variable)
{ {
if (previous == TokenType.Number)
{
previous = TokenType.Operator;
yield return new Token(TokenType.Operator, "*");
}
// Continue till the end of the variable // Continue till the end of the variable
while (i + 1 < text.Length && DetermineType(text[i + 1]) == TokenType.Variable) while (i + 1 < text.Length && DetermineType(text[i + 1]) == TokenType.Variable)
{ {
@@ -335,7 +388,7 @@ namespace FlaxEditor.Utilities
} }
else else
{ {
throw new ParsingException("unknown variable"); throw new ParsingException($"unknown variable : {token.Value}");
} }
} }
else else
@@ -372,6 +425,15 @@ namespace FlaxEditor.Utilities
} }
} }
// if stack has more than one item we're not finished with evaluating
// we assume the remaining values are all factors to be multiplied
if (stack.Count > 1)
{
var v1 = stack.Pop();
while (stack.Count > 0)
v1 *= stack.Pop();
return v1;
}
return stack.Pop(); return stack.Pop();
} }

View File

@@ -0,0 +1,41 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
namespace FlaxEditor.Utilities;
/// <summary>
/// Units display utilities for Editor.
/// </summary>
public class Units
{
/// <summary>
/// Factor of units per meter.
/// </summary>
public static readonly float Meters2Units = 100f;
/// <summary>
/// False to always show game units without any postfix.
/// </summary>
public static bool UseUnitsFormatting = true;
/// <summary>
/// Add a space between numbers and units for readability.
/// </summary>
public static bool SeparateValueAndUnit = true;
/// <summary>
/// If set to true, the distance unit is chosen on the magnitude, otherwise it's meters.
/// </summary>
public static bool AutomaticUnitsFormatting = true;
/// <summary>
/// Return the unit according to user settings.
/// </summary>
/// <param name="unit">The unit name.</param>
/// <returns>The formatted text.</returns>
public static string Unit(string unit)
{
if (SeparateValueAndUnit)
return $" {unit}";
return unit;
}
}

View File

@@ -243,6 +243,63 @@ namespace FlaxEditor.Utilities
500000, 1000000, 5000000, 10000000, 100000000 500000, 1000000, 5000000, 10000000, 100000000
}; };
internal delegate void DrawCurveTick(float tick, float strength);
internal static Int2 DrawCurveTicks(DrawCurveTick drawTick, float[] tickSteps, ref float[] tickStrengths, float min, float max, float pixelRange, float minDistanceBetweenTicks = 20, float maxDistanceBetweenTicks = 60)
{
if (tickStrengths == null || tickStrengths.Length != tickSteps.Length)
tickStrengths = new float[tickSteps.Length];
// Find the strength for each modulo number tick marker
var pixelsInRange = pixelRange / (max - min);
var smallestTick = 0;
var biggestTick = tickSteps.Length - 1;
for (int i = tickSteps.Length - 1; i >= 0; i--)
{
// Calculate how far apart these modulo tick steps are spaced
float tickSpacing = tickSteps[i] * pixelsInRange;
// Calculate the strength of the tick markers based on the spacing
tickStrengths[i] = Mathf.Saturate((tickSpacing - minDistanceBetweenTicks) / (maxDistanceBetweenTicks - minDistanceBetweenTicks));
// Beyond threshold the ticks don't get any bigger or fatter
if (tickStrengths[i] >= 1)
biggestTick = i;
// Do not show small tick markers
if (tickSpacing <= minDistanceBetweenTicks)
{
smallestTick = i;
break;
}
}
var tickLevels = biggestTick - smallestTick + 1;
// Draw all tick levels
for (int level = 0; level < tickLevels; level++)
{
float strength = tickStrengths[smallestTick + level];
if (strength <= Mathf.Epsilon)
continue;
// Draw all ticks
int l = Mathf.Clamp(smallestTick + level, 0, tickSteps.Length - 1);
var lStep = tickSteps[l];
var lNextStep = tickSteps[l + 1];
int startTick = Mathf.FloorToInt(min / lStep);
int endTick = Mathf.CeilToInt(max / lStep);
for (int i = startTick; i <= endTick; i++)
{
if (l < biggestTick && (i % Mathf.RoundToInt(lNextStep / lStep) == 0))
continue;
var tick = i * lStep;
drawTick(tick, strength);
}
}
return new Int2(smallestTick, biggestTick);
}
/// <summary> /// <summary>
/// Determines whether the specified path string contains any invalid character. /// Determines whether the specified path string contains any invalid character.
/// </summary> /// </summary>
@@ -1187,6 +1244,71 @@ namespace FlaxEditor.Utilities
return StringUtils.GetPathWithoutExtension(path); return StringUtils.GetPathWithoutExtension(path);
} }
private static string InternalFormat(double value, string format, FlaxEngine.Utils.ValueCategory category)
{
switch (category)
{
case FlaxEngine.Utils.ValueCategory.Distance:
if (!Units.AutomaticUnitsFormatting)
return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m");
var absValue = Mathf.Abs(value);
// in case a unit != cm this would be (value / Meters2Units * 100)
if (absValue < Units.Meters2Units)
return value.ToString(format, CultureInfo.InvariantCulture) + Units.Unit("cm");
if (absValue < Units.Meters2Units * 1000)
return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m");
return (value / 1000 / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("km");
case FlaxEngine.Utils.ValueCategory.Angle: return value.ToString(format, CultureInfo.InvariantCulture) + "°";
case FlaxEngine.Utils.ValueCategory.Time: return value.ToString(format, CultureInfo.InvariantCulture) + Units.Unit("s");
// some fonts have a symbol for that: "\u33A7"
case FlaxEngine.Utils.ValueCategory.Speed: return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m/s");
case FlaxEngine.Utils.ValueCategory.Acceleration: return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m/s²");
case FlaxEngine.Utils.ValueCategory.Area: return (value / Units.Meters2Units / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m²");
case FlaxEngine.Utils.ValueCategory.Volume: return (value / Units.Meters2Units / Units.Meters2Units / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m³");
case FlaxEngine.Utils.ValueCategory.Mass: return value.ToString(format, CultureInfo.InvariantCulture) + Units.Unit("kg");
case FlaxEngine.Utils.ValueCategory.Force: return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("N");
case FlaxEngine.Utils.ValueCategory.Torque: return (value / Units.Meters2Units / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("Nm");
case FlaxEngine.Utils.ValueCategory.None:
default: return FormatFloat(value);
}
}
/// <summary>
/// Format a float value either as-is, with a distance unit or with a degree sign.
/// </summary>
/// <param name="value">The value to format.</param>
/// <param name="category">The value type: none means just a number, distance will format in cm/m/km, angle with an appended degree sign.</param>
/// <returns>The formatted string.</returns>
public static string FormatFloat(float value, FlaxEngine.Utils.ValueCategory category)
{
if (float.IsPositiveInfinity(value) || value == float.MaxValue)
return "Infinity";
if (float.IsNegativeInfinity(value) || value == float.MinValue)
return "-Infinity";
if (!Units.UseUnitsFormatting || category == FlaxEngine.Utils.ValueCategory.None)
return FormatFloat(value);
const string format = "G7";
return InternalFormat(value, format, category);
}
/// <summary>
/// Format a double value either as-is, with a distance unit or with a degree sign
/// </summary>
/// <param name="value">The value to format.</param>
/// <param name="category">The value type: none means just a number, distance will format in cm/m/km, angle with an appended degree sign.</param>
/// <returns>The formatted string.</returns>
public static string FormatFloat(double value, FlaxEngine.Utils.ValueCategory category)
{
if (double.IsPositiveInfinity(value) || value == double.MaxValue)
return "Infinity";
if (double.IsNegativeInfinity(value) || value == double.MinValue)
return "-Infinity";
if (!Units.UseUnitsFormatting || category == FlaxEngine.Utils.ValueCategory.None)
return FormatFloat(value);
const string format = "G15";
return InternalFormat(value, format, category);
}
/// <summary> /// <summary>
/// Formats the floating point value (double precision) into the readable text representation. /// Formats the floating point value (double precision) into the readable text representation.
/// </summary> /// </summary>
@@ -1198,7 +1320,7 @@ namespace FlaxEditor.Utilities
return "Infinity"; return "Infinity";
if (float.IsNegativeInfinity(value) || value == float.MinValue) if (float.IsNegativeInfinity(value) || value == float.MinValue)
return "-Infinity"; return "-Infinity";
string str = value.ToString("r", CultureInfo.InvariantCulture); string str = value.ToString("R", CultureInfo.InvariantCulture);
return FormatFloat(str, value < 0); return FormatFloat(str, value < 0);
} }
@@ -1213,7 +1335,7 @@ namespace FlaxEditor.Utilities
return "Infinity"; return "Infinity";
if (double.IsNegativeInfinity(value) || value == double.MinValue) if (double.IsNegativeInfinity(value) || value == double.MinValue)
return "-Infinity"; return "-Infinity";
string str = value.ToString("r", CultureInfo.InvariantCulture); string str = value.ToString("R", CultureInfo.InvariantCulture);
return FormatFloat(str, value < 0); return FormatFloat(str, value < 0);
} }

View File

@@ -187,6 +187,8 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Scene
void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor* actor, Mesh::DrawInfo& draw) void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor* actor, Mesh::DrawInfo& draw)
{ {
if (!actor || !actor->IsActiveInHierarchy())
return;
auto& view = renderContext.View; auto& view = renderContext.View;
const BoundingFrustum frustum = view.Frustum; const BoundingFrustum frustum = view.Frustum;
Matrix m1, m2, world; Matrix m1, m2, world;
@@ -208,8 +210,7 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor
draw.DrawState = &drawState; draw.DrawState = &drawState;
draw.Deformation = nullptr; draw.Deformation = nullptr;
// Support custom icons through types, but not onces that were added through actors, // Support custom icons through types, but not ones that were added through actors, since they cant register while in prefab view anyway
// since they cant register while in prefab view anyway
if (ActorTypeToTexture.TryGet(actor->GetTypeHandle(), texture)) if (ActorTypeToTexture.TryGet(actor->GetTypeHandle(), texture))
{ {
// Use custom texture // Use custom texture

View File

@@ -6,9 +6,7 @@ using Real = System.Double;
using Real = System.Single; using Real = System.Single;
#endif #endif
using System.Collections.Generic;
using FlaxEditor.Gizmo; using FlaxEditor.Gizmo;
using FlaxEditor.SceneGraph;
using FlaxEngine; using FlaxEngine;
namespace FlaxEditor.Viewport.Cameras namespace FlaxEditor.Viewport.Cameras
@@ -85,86 +83,8 @@ namespace FlaxEditor.Viewport.Cameras
_moveStartTime = Time.UnscaledGameTime; _moveStartTime = Time.UnscaledGameTime;
} }
/// <summary> /// <inheritdoc />
/// Moves the viewport to visualize the actor. public override void ShowSphere(ref BoundingSphere sphere, ref Quaternion orientation)
/// </summary>
/// <param name="actor">The actor to preview.</param>
public void ShowActor(Actor actor)
{
Editor.GetActorEditorSphere(actor, out BoundingSphere sphere);
ShowSphere(ref sphere);
}
/// <summary>
/// Moves the viewport to visualize selected actors.
/// </summary>
/// <param name="actor">The actors to show.</param>
/// <param name="orientation">The used orientation.</param>
public void ShowActor(Actor actor, ref Quaternion orientation)
{
Editor.GetActorEditorSphere(actor, out BoundingSphere sphere);
ShowSphere(ref sphere, ref orientation);
}
/// <summary>
/// Moves the viewport to visualize selected actors.
/// </summary>
/// <param name="selection">The actors to show.</param>
public void ShowActors(List<SceneGraphNode> selection)
{
if (selection.Count == 0)
return;
BoundingSphere mergesSphere = BoundingSphere.Empty;
for (int i = 0; i < selection.Count; i++)
{
selection[i].GetEditorSphere(out var sphere);
BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere);
}
if (mergesSphere == BoundingSphere.Empty)
return;
ShowSphere(ref mergesSphere);
}
/// <summary>
/// Moves the viewport to visualize selected actors.
/// </summary>
/// <param name="selection">The actors to show.</param>
/// <param name="orientation">The used orientation.</param>
public void ShowActors(List<SceneGraphNode> selection, ref Quaternion orientation)
{
if (selection.Count == 0)
return;
BoundingSphere mergesSphere = BoundingSphere.Empty;
for (int i = 0; i < selection.Count; i++)
{
selection[i].GetEditorSphere(out var sphere);
BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere);
}
if (mergesSphere == BoundingSphere.Empty)
return;
ShowSphere(ref mergesSphere, ref orientation);
}
/// <summary>
/// Moves the camera to visualize given world area defined by the sphere.
/// </summary>
/// <param name="sphere">The sphere.</param>
public void ShowSphere(ref BoundingSphere sphere)
{
var q = new Quaternion(0.424461186f, -0.0940724313f, 0.0443938486f, 0.899451137f);
ShowSphere(ref sphere, ref q);
}
/// <summary>
/// Moves the camera to visualize given world area defined by the sphere.
/// </summary>
/// <param name="sphere">The sphere.</param>
/// <param name="orientation">The camera orientation.</param>
public void ShowSphere(ref BoundingSphere sphere, ref Quaternion orientation)
{ {
Vector3 position; Vector3 position;
if (Viewport.UseOrthographicProjection) if (Viewport.UseOrthographicProjection)

View File

@@ -6,6 +6,9 @@ using Real = System.Double;
using Real = System.Single; using Real = System.Single;
#endif #endif
using System.Collections.Generic;
using FlaxEditor.Gizmo;
using FlaxEditor.SceneGraph;
using FlaxEngine; using FlaxEngine;
namespace FlaxEditor.Viewport.Cameras namespace FlaxEditor.Viewport.Cameras
@@ -33,6 +36,90 @@ namespace FlaxEditor.Viewport.Cameras
/// </summary> /// </summary>
public virtual bool UseMovementSpeed => true; public virtual bool UseMovementSpeed => true;
/// <summary>
/// Focuses the viewport on the current selection of the gizmo.
/// </summary>
/// <param name="gizmos">The gizmo collection (from viewport).</param>
/// <param name="orientation">The target view orientation.</param>
public virtual void FocusSelection(GizmosCollection gizmos, ref Quaternion orientation)
{
var transformGizmo = gizmos.Get<TransformGizmo>();
if (transformGizmo == null || transformGizmo.SelectedParents.Count == 0)
return;
if (gizmos.Active != null)
{
var gizmoBounds = gizmos.Active.FocusBounds;
if (gizmoBounds != BoundingSphere.Empty)
{
ShowSphere(ref gizmoBounds, ref orientation);
return;
}
}
ShowActors(transformGizmo.SelectedParents, ref orientation);
}
/// <summary>
/// Moves the viewport to visualize the actor.
/// </summary>
/// <param name="actor">The actor to preview.</param>
public virtual void ShowActor(Actor actor)
{
Editor.GetActorEditorSphere(actor, out BoundingSphere sphere);
ShowSphere(ref sphere);
}
/// <summary>
/// Moves the viewport to visualize selected actors.
/// </summary>
/// <param name="selection">The actors to show.</param>
public void ShowActors(List<SceneGraphNode> selection)
{
var q = new Quaternion(0.424461186f, -0.0940724313f, 0.0443938486f, 0.899451137f);
ShowActors(selection, ref q);
}
/// <summary>
/// Moves the viewport to visualize selected actors.
/// </summary>
/// <param name="selection">The actors to show.</param>
/// <param name="orientation">The used orientation.</param>
public virtual void ShowActors(List<SceneGraphNode> selection, ref Quaternion orientation)
{
if (selection.Count == 0)
return;
BoundingSphere mergesSphere = BoundingSphere.Empty;
for (int i = 0; i < selection.Count; i++)
{
selection[i].GetEditorSphere(out var sphere);
BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere);
}
if (mergesSphere == BoundingSphere.Empty)
return;
ShowSphere(ref mergesSphere, ref orientation);
}
/// <summary>
/// Moves the camera to visualize given world area defined by the sphere.
/// </summary>
/// <param name="sphere">The sphere.</param>
public void ShowSphere(ref BoundingSphere sphere)
{
var q = new Quaternion(0.424461186f, -0.0940724313f, 0.0443938486f, 0.899451137f);
ShowSphere(ref sphere, ref q);
}
/// <summary>
/// Moves the camera to visualize given world area defined by the sphere.
/// </summary>
/// <param name="sphere">The sphere.</param>
/// <param name="orientation">The camera orientation.</param>
public virtual void ShowSphere(ref BoundingSphere sphere, ref Quaternion orientation)
{
SetArcBallView(orientation, sphere.Center, sphere.Radius);
}
/// <summary> /// <summary>
/// Sets view orientation and position to match the arc ball camera style view for the given target object bounds. /// Sets view orientation and position to match the arc ball camera style view for the given target object bounds.
/// </summary> /// </summary>

View File

@@ -1,9 +1,12 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using FlaxEditor.Gizmo; using FlaxEditor.Gizmo;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph;
using FlaxEditor.Viewport.Cameras; using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Viewport.Widgets;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
@@ -41,6 +44,7 @@ namespace FlaxEditor.Viewport
Gizmos[i].Update(deltaTime); Gizmos[i].Update(deltaTime);
} }
} }
/// <inheritdoc /> /// <inheritdoc />
public EditorViewport Viewport => this; public EditorViewport Viewport => this;
@@ -75,10 +79,10 @@ namespace FlaxEditor.Viewport
public Float2 MouseDelta => _mouseDelta * 1000; public Float2 MouseDelta => _mouseDelta * 1000;
/// <inheritdoc /> /// <inheritdoc />
public bool UseSnapping => Root.GetKey(KeyboardKeys.Control); public bool UseSnapping => Root?.GetKey(KeyboardKeys.Control) ?? false;
/// <inheritdoc /> /// <inheritdoc />
public bool UseDuplicate => Root.GetKey(KeyboardKeys.Shift); public bool UseDuplicate => Root?.GetKey(KeyboardKeys.Shift) ?? false;
/// <inheritdoc /> /// <inheritdoc />
public Undo Undo { get; } public Undo Undo { get; }
@@ -92,6 +96,9 @@ namespace FlaxEditor.Viewport
/// <inheritdoc /> /// <inheritdoc />
public abstract void Spawn(Actor actor); public abstract void Spawn(Actor actor);
/// <inheritdoc />
public abstract void OpenContextMenu();
/// <inheritdoc /> /// <inheritdoc />
protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false; protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false;
@@ -121,5 +128,284 @@ namespace FlaxEditor.Viewport
base.OnDestroy(); base.OnDestroy();
} }
internal static void AddGizmoViewportWidgets(EditorViewport viewport, TransformGizmo transformGizmo, bool useProjectCache = false)
{
var editor = Editor.Instance;
var inputOptions = editor.Options.Options.Input;
if (useProjectCache)
{
// Initialize snapping enabled from cached values
if (editor.ProjectCache.TryGetCustomData("TranslateSnapState", out var cachedState))
transformGizmo.TranslationSnapEnable = bool.Parse(cachedState);
if (editor.ProjectCache.TryGetCustomData("RotationSnapState", out cachedState))
transformGizmo.RotationSnapEnabled = bool.Parse(cachedState);
if (editor.ProjectCache.TryGetCustomData("ScaleSnapState", out cachedState))
transformGizmo.ScaleSnapEnabled = bool.Parse(cachedState);
if (editor.ProjectCache.TryGetCustomData("TranslateSnapValue", out cachedState))
transformGizmo.TranslationSnapValue = float.Parse(cachedState);
if (editor.ProjectCache.TryGetCustomData("RotationSnapValue", out cachedState))
transformGizmo.RotationSnapValue = float.Parse(cachedState);
if (editor.ProjectCache.TryGetCustomData("ScaleSnapValue", out cachedState))
transformGizmo.ScaleSnapValue = float.Parse(cachedState);
if (editor.ProjectCache.TryGetCustomData("TransformSpaceState", out cachedState) && Enum.TryParse(cachedState, out TransformGizmoBase.TransformSpace space))
transformGizmo.ActiveTransformSpace = space;
}
// Transform space widget
var transformSpaceWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var transformSpaceToggle = new ViewportWidgetButton(string.Empty, editor.Icons.Globe32, null, true)
{
Checked = transformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World,
TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})",
Parent = transformSpaceWidget
};
transformSpaceToggle.Toggled += _ =>
{
transformGizmo.ToggleTransformSpace();
if (useProjectCache)
editor.ProjectCache.SetCustomData("TransformSpaceState", transformGizmo.ActiveTransformSpace.ToString());
};
transformSpaceWidget.Parent = viewport;
// Scale snapping widget
var scaleSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var enableScaleSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.ScaleSnap32, null, true)
{
Checked = transformGizmo.ScaleSnapEnabled,
TooltipText = "Enable scale snapping",
Parent = scaleSnappingWidget
};
enableScaleSnapping.Toggled += _ =>
{
transformGizmo.ScaleSnapEnabled = !transformGizmo.ScaleSnapEnabled;
if (useProjectCache)
editor.ProjectCache.SetCustomData("ScaleSnapState", transformGizmo.ScaleSnapEnabled.ToString());
};
var scaleSnappingCM = new ContextMenu();
var scaleSnapping = new ViewportWidgetButton(transformGizmo.ScaleSnapValue.ToString(), SpriteHandle.Invalid, scaleSnappingCM)
{
TooltipText = "Scale snapping values"
};
for (int i = 0; i < ScaleSnapValues.Length; i++)
{
var v = ScaleSnapValues[i];
var button = scaleSnappingCM.AddButton(v.ToString());
button.Tag = v;
}
scaleSnappingCM.ButtonClicked += button =>
{
var v = (float)button.Tag;
transformGizmo.ScaleSnapValue = v;
scaleSnapping.Text = v.ToString();
if (useProjectCache)
editor.ProjectCache.SetCustomData("ScaleSnapValue", transformGizmo.ScaleSnapValue.ToString("N"));
};
scaleSnappingCM.VisibleChanged += control =>
{
if (control.Visible == false)
return;
var ccm = (ContextMenu)control;
foreach (var e in ccm.Items)
{
if (e is ContextMenuButton b)
{
var v = (float)b.Tag;
b.Icon = Mathf.Abs(transformGizmo.ScaleSnapValue - v) < 0.001f ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
}
}
};
scaleSnapping.Parent = scaleSnappingWidget;
scaleSnappingWidget.Parent = viewport;
// Rotation snapping widget
var rotateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var enableRotateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.RotateSnap32, null, true)
{
Checked = transformGizmo.RotationSnapEnabled,
TooltipText = "Enable rotation snapping",
Parent = rotateSnappingWidget
};
enableRotateSnapping.Toggled += _ =>
{
transformGizmo.RotationSnapEnabled = !transformGizmo.RotationSnapEnabled;
if (useProjectCache)
editor.ProjectCache.SetCustomData("RotationSnapState", transformGizmo.RotationSnapEnabled.ToString());
};
var rotateSnappingCM = new ContextMenu();
var rotateSnapping = new ViewportWidgetButton(transformGizmo.RotationSnapValue.ToString(), SpriteHandle.Invalid, rotateSnappingCM)
{
TooltipText = "Rotation snapping values"
};
for (int i = 0; i < RotateSnapValues.Length; i++)
{
var v = RotateSnapValues[i];
var button = rotateSnappingCM.AddButton(v.ToString());
button.Tag = v;
}
rotateSnappingCM.ButtonClicked += button =>
{
var v = (float)button.Tag;
transformGizmo.RotationSnapValue = v;
rotateSnapping.Text = v.ToString();
if (useProjectCache)
editor.ProjectCache.SetCustomData("RotationSnapValue", transformGizmo.RotationSnapValue.ToString("N"));
};
rotateSnappingCM.VisibleChanged += control =>
{
if (control.Visible == false)
return;
var ccm = (ContextMenu)control;
foreach (var e in ccm.Items)
{
if (e is ContextMenuButton b)
{
var v = (float)b.Tag;
b.Icon = Mathf.Abs(transformGizmo.RotationSnapValue - v) < 0.001f ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
}
}
};
rotateSnapping.Parent = rotateSnappingWidget;
rotateSnappingWidget.Parent = viewport;
// Translation snapping widget
var translateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var enableTranslateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.Grid32, null, true)
{
Checked = transformGizmo.TranslationSnapEnable,
TooltipText = "Enable position snapping",
Parent = translateSnappingWidget
};
enableTranslateSnapping.Toggled += _ =>
{
transformGizmo.TranslationSnapEnable = !transformGizmo.TranslationSnapEnable;
if (useProjectCache)
editor.ProjectCache.SetCustomData("TranslateSnapState", transformGizmo.TranslationSnapEnable.ToString());
};
var translateSnappingCM = new ContextMenu();
var translateSnapping = new ViewportWidgetButton(transformGizmo.TranslationSnapValue.ToString(), SpriteHandle.Invalid, translateSnappingCM)
{
TooltipText = "Position snapping values"
};
if (transformGizmo.TranslationSnapValue < 0.0f)
translateSnapping.Text = "Bounding Box";
for (int i = 0; i < TranslateSnapValues.Length; i++)
{
var v = TranslateSnapValues[i];
var button = translateSnappingCM.AddButton(v.ToString());
button.Tag = v;
}
var buttonBB = translateSnappingCM.AddButton("Bounding Box").LinkTooltip("Snaps the selection based on it's bounding volume");
buttonBB.Tag = -1.0f;
translateSnappingCM.ButtonClicked += button =>
{
var v = (float)button.Tag;
transformGizmo.TranslationSnapValue = v;
if (v < 0.0f)
translateSnapping.Text = "Bounding Box";
else
translateSnapping.Text = v.ToString();
if (useProjectCache)
editor.ProjectCache.SetCustomData("TranslateSnapValue", transformGizmo.TranslationSnapValue.ToString("N"));
};
translateSnappingCM.VisibleChanged += control =>
{
if (control.Visible == false)
return;
var ccm = (ContextMenu)control;
foreach (var e in ccm.Items)
{
if (e is ContextMenuButton b)
{
var v = (float)b.Tag;
b.Icon = Mathf.Abs(transformGizmo.TranslationSnapValue - v) < 0.001f ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
}
}
};
translateSnapping.Parent = translateSnappingWidget;
translateSnappingWidget.Parent = viewport;
// Gizmo mode widget
var gizmoMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var gizmoModeTranslate = new ViewportWidgetButton(string.Empty, editor.Icons.Translate32, null, true)
{
Tag = TransformGizmoBase.Mode.Translate,
TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})",
Checked = true,
Parent = gizmoMode
};
gizmoModeTranslate.Toggled += _ => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate;
var gizmoModeRotate = new ViewportWidgetButton(string.Empty, editor.Icons.Rotate32, null, true)
{
Tag = TransformGizmoBase.Mode.Rotate,
TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})",
Parent = gizmoMode
};
gizmoModeRotate.Toggled += _ => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate;
var gizmoModeScale = new ViewportWidgetButton(string.Empty, editor.Icons.Scale32, null, true)
{
Tag = TransformGizmoBase.Mode.Scale,
TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})",
Parent = gizmoMode
};
gizmoModeScale.Toggled += _ => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale;
gizmoMode.Parent = viewport;
transformGizmo.ModeChanged += () =>
{
var mode = transformGizmo.ActiveMode;
gizmoModeTranslate.Checked = mode == TransformGizmoBase.Mode.Translate;
gizmoModeRotate.Checked = mode == TransformGizmoBase.Mode.Rotate;
gizmoModeScale.Checked = mode == TransformGizmoBase.Mode.Scale;
};
// Setup input actions
viewport.InputActions.Add(options => options.TranslateMode, () => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate);
viewport.InputActions.Add(options => options.RotateMode, () => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate);
viewport.InputActions.Add(options => options.ScaleMode, () => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale);
viewport.InputActions.Add(options => options.ToggleTransformSpace, () =>
{
transformGizmo.ToggleTransformSpace();
if (useProjectCache)
editor.ProjectCache.SetCustomData("TransformSpaceState", transformGizmo.ActiveTransformSpace.ToString());
transformSpaceToggle.Checked = !transformSpaceToggle.Checked;
});
}
internal static readonly float[] TranslateSnapValues =
{
0.1f,
0.5f,
1.0f,
5.0f,
10.0f,
100.0f,
1000.0f,
};
internal static readonly float[] RotateSnapValues =
{
1.0f,
5.0f,
10.0f,
15.0f,
30.0f,
45.0f,
60.0f,
90.0f,
};
internal static readonly float[] ScaleSnapValues =
{
0.05f,
0.1f,
0.25f,
0.5f,
1.0f,
2.0f,
4.0f,
6.0f,
8.0f,
};
} }
} }

View File

@@ -10,7 +10,6 @@ using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Viewport.Widgets; using FlaxEditor.Viewport.Widgets;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using Newtonsoft.Json;
using JsonSerializer = FlaxEngine.Json.JsonSerializer; using JsonSerializer = FlaxEngine.Json.JsonSerializer;
namespace FlaxEditor.Viewport namespace FlaxEditor.Viewport
@@ -154,6 +153,7 @@ namespace FlaxEditor.Viewport
// Input // Input
internal bool _disableInputUpdate;
private bool _isControllingMouse, _isViewportControllingMouse, _wasVirtualMouseRightDown, _isVirtualMouseRightDown; private bool _isControllingMouse, _isViewportControllingMouse, _wasVirtualMouseRightDown, _isVirtualMouseRightDown;
private int _deltaFilteringStep; private int _deltaFilteringStep;
private Float2 _startPos; private Float2 _startPos;
@@ -704,9 +704,9 @@ namespace FlaxEditor.Viewport
// Camera Viewpoints // Camera Viewpoints
{ {
var cameraView = cameraCM.AddChildMenu("Viewpoints").ContextMenu; var cameraView = cameraCM.AddChildMenu("Viewpoints").ContextMenu;
for (int i = 0; i < EditorViewportCameraViewpointValues.Length; i++) for (int i = 0; i < CameraViewpointValues.Length; i++)
{ {
var co = EditorViewportCameraViewpointValues[i]; var co = CameraViewpointValues[i];
var button = cameraView.AddButton(co.Name); var button = cameraView.AddButton(co.Name);
button.Tag = co.Orientation; button.Tag = co.Orientation;
} }
@@ -899,9 +899,9 @@ namespace FlaxEditor.Viewport
viewFlags.AddButton("Reset flags", () => Task.ViewFlags = ViewFlags.DefaultEditor).Icon = Editor.Instance.Icons.Rotate32; viewFlags.AddButton("Reset flags", () => Task.ViewFlags = ViewFlags.DefaultEditor).Icon = Editor.Instance.Icons.Rotate32;
viewFlags.AddButton("Disable flags", () => Task.ViewFlags = ViewFlags.None).Icon = Editor.Instance.Icons.Rotate32; viewFlags.AddButton("Disable flags", () => Task.ViewFlags = ViewFlags.None).Icon = Editor.Instance.Icons.Rotate32;
viewFlags.AddSeparator(); viewFlags.AddSeparator();
for (int i = 0; i < EditorViewportViewFlagsValues.Length; i++) for (int i = 0; i < ViewFlagsValues.Length; i++)
{ {
var v = EditorViewportViewFlagsValues[i]; var v = ViewFlagsValues[i];
var button = viewFlags.AddButton(v.Name); var button = viewFlags.AddButton(v.Name);
button.CloseMenuOnClick = false; button.CloseMenuOnClick = false;
button.Tag = v.Mode; button.Tag = v.Mode;
@@ -933,9 +933,9 @@ namespace FlaxEditor.Viewport
} }
}); });
debugView.AddSeparator(); debugView.AddSeparator();
for (int i = 0; i < EditorViewportViewModeValues.Length; i++) for (int i = 0; i < ViewModeValues.Length; i++)
{ {
ref var v = ref EditorViewportViewModeValues[i]; ref var v = ref ViewModeValues[i];
if (v.Options != null) if (v.Options != null)
{ {
var childMenu = debugView.AddChildMenu(v.Name).ContextMenu; var childMenu = debugView.AddChildMenu(v.Name).ContextMenu;
@@ -989,12 +989,12 @@ namespace FlaxEditor.Viewport
#endregion View mode widget #endregion View mode widget
} }
InputActions.Add(options => options.ViewpointTop, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Top").Orientation))); InputActions.Add(options => options.ViewpointTop, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Top").Orientation)));
InputActions.Add(options => options.ViewpointBottom, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Bottom").Orientation))); InputActions.Add(options => options.ViewpointBottom, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Bottom").Orientation)));
InputActions.Add(options => options.ViewpointFront, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Front").Orientation))); InputActions.Add(options => options.ViewpointFront, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Front").Orientation)));
InputActions.Add(options => options.ViewpointBack, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Back").Orientation))); InputActions.Add(options => options.ViewpointBack, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Back").Orientation)));
InputActions.Add(options => options.ViewpointRight, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Right").Orientation))); InputActions.Add(options => options.ViewpointRight, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Right").Orientation)));
InputActions.Add(options => options.ViewpointLeft, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Left").Orientation))); InputActions.Add(options => options.ViewpointLeft, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Left").Orientation)));
InputActions.Add(options => options.CameraToggleRotation, () => _isVirtualMouseRightDown = !_isVirtualMouseRightDown); InputActions.Add(options => options.CameraToggleRotation, () => _isVirtualMouseRightDown = !_isVirtualMouseRightDown);
InputActions.Add(options => options.CameraIncreaseMoveSpeed, () => AdjustCameraMoveSpeed(1)); InputActions.Add(options => options.CameraIncreaseMoveSpeed, () => AdjustCameraMoveSpeed(1));
InputActions.Add(options => options.CameraDecreaseMoveSpeed, () => AdjustCameraMoveSpeed(-1)); InputActions.Add(options => options.CameraDecreaseMoveSpeed, () => AdjustCameraMoveSpeed(-1));
@@ -1496,6 +1496,9 @@ namespace FlaxEditor.Viewport
{ {
base.Update(deltaTime); base.Update(deltaTime);
if (_disableInputUpdate)
return;
// Update camera // Update camera
bool useMovementSpeed = false; bool useMovementSpeed = false;
if (_camera != null) if (_camera != null)
@@ -1534,7 +1537,7 @@ namespace FlaxEditor.Viewport
} }
bool useMouse = IsControllingMouse || (Mathf.IsInRange(_viewMousePos.X, 0, Width) && Mathf.IsInRange(_viewMousePos.Y, 0, Height)); bool useMouse = IsControllingMouse || (Mathf.IsInRange(_viewMousePos.X, 0, Width) && Mathf.IsInRange(_viewMousePos.Y, 0, Height));
_prevInput = _input; _prevInput = _input;
var hit = GetChildAt(_viewMousePos, c => c.Visible && !(c is CanvasRootControl)); var hit = GetChildAt(_viewMousePos, c => c.Visible && !(c is CanvasRootControl) && !(c is UIEditorRoot));
if (canUseInput && ContainsFocus && hit == null) if (canUseInput && ContainsFocus && hit == null)
_input.Gather(win.Window, useMouse); _input.Gather(win.Window, useMouse);
else else
@@ -1867,7 +1870,7 @@ namespace FlaxEditor.Viewport
} }
} }
private readonly CameraViewpoint[] EditorViewportCameraViewpointValues = private readonly CameraViewpoint[] CameraViewpointValues =
{ {
new CameraViewpoint("Front", new Float3(0, 180, 0)), new CameraViewpoint("Front", new Float3(0, 180, 0)),
new CameraViewpoint("Back", new Float3(0, 0, 0)), new CameraViewpoint("Back", new Float3(0, 0, 0)),
@@ -1898,7 +1901,7 @@ namespace FlaxEditor.Viewport
} }
} }
private static readonly ViewModeOptions[] EditorViewportViewModeValues = private static readonly ViewModeOptions[] ViewModeValues =
{ {
new ViewModeOptions(ViewMode.Default, "Default"), new ViewModeOptions(ViewMode.Default, "Default"),
new ViewModeOptions(ViewMode.Unlit, "Unlit"), new ViewModeOptions(ViewMode.Unlit, "Unlit"),
@@ -1970,7 +1973,7 @@ namespace FlaxEditor.Viewport
} }
} }
private static readonly ViewFlagOptions[] EditorViewportViewFlagsValues = private static readonly ViewFlagOptions[] ViewFlagsValues =
{ {
new ViewFlagOptions(ViewFlags.AntiAliasing, "Anti Aliasing"), new ViewFlagOptions(ViewFlags.AntiAliasing, "Anti Aliasing"),
new ViewFlagOptions(ViewFlags.Shadows, "Shadows"), new ViewFlagOptions(ViewFlags.Shadows, "Shadows"),
@@ -2005,16 +2008,13 @@ namespace FlaxEditor.Viewport
{ {
if (cm.Visible == false) if (cm.Visible == false)
return; return;
var ccm = (ContextMenu)cm; var ccm = (ContextMenu)cm;
foreach (var e in ccm.Items) foreach (var e in ccm.Items)
{ {
if (e is ContextMenuButton b && b.Tag != null) if (e is ContextMenuButton b && b.Tag != null)
{ {
var v = (ViewFlags)b.Tag; var v = (ViewFlags)b.Tag;
b.Icon = (Task.View.Flags & v) != 0 b.Icon = (Task.View.Flags & v) != 0 ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
? Style.Current.CheckBoxTick
: SpriteHandle.Invalid;
} }
} }
} }
@@ -2023,7 +2023,6 @@ namespace FlaxEditor.Viewport
{ {
if (cm.Visible == false) if (cm.Visible == false)
return; return;
var ccm = (ContextMenu)cm; var ccm = (ContextMenu)cm;
var layersMask = Task.ViewLayersMask; var layersMask = Task.ViewLayersMask;
foreach (var e in ccm.Items) foreach (var e in ccm.Items)

View File

@@ -2,15 +2,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Content; using FlaxEditor.Content;
using FlaxEditor.Gizmo; using FlaxEditor.Gizmo;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Viewport.Modes; using FlaxEditor.Viewport.Modes;
using FlaxEditor.Viewport.Widgets;
using FlaxEditor.Windows; using FlaxEditor.Windows;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
@@ -28,13 +25,6 @@ namespace FlaxEditor.Viewport
private readonly ContextMenuButton _showGridButton; private readonly ContextMenuButton _showGridButton;
private readonly ContextMenuButton _showNavigationButton; private readonly ContextMenuButton _showNavigationButton;
private readonly ViewportWidgetButton _gizmoModeTranslate;
private readonly ViewportWidgetButton _gizmoModeRotate;
private readonly ViewportWidgetButton _gizmoModeScale;
private readonly ViewportWidgetButton _translateSnapping;
private readonly ViewportWidgetButton _rotateSnapping;
private readonly ViewportWidgetButton _scaleSnapping;
private SelectionOutline _customSelectionOutline; private SelectionOutline _customSelectionOutline;
@@ -196,7 +186,6 @@ namespace FlaxEditor.Viewport
{ {
_editor = editor; _editor = editor;
DragHandlers = new ViewportDragHandlers(this, this, ValidateDragItem, ValidateDragActorType, ValidateDragScriptItem); DragHandlers = new ViewportDragHandlers(this, this, ValidateDragItem, ValidateDragActorType, ValidateDragScriptItem);
var inputOptions = editor.Options.Options.Input;
// Prepare rendering task // Prepare rendering task
Task.ActorsSource = ActorsSources.Scenes; Task.ActorsSource = ActorsSources.Scenes;
@@ -222,8 +211,7 @@ namespace FlaxEditor.Viewport
// Add transformation gizmo // Add transformation gizmo
TransformGizmo = new TransformGizmo(this); TransformGizmo = new TransformGizmo(this);
TransformGizmo.ApplyTransformation += ApplyTransform; TransformGizmo.ApplyTransformation += ApplyTransform;
TransformGizmo.ModeChanged += OnGizmoModeChanged; TransformGizmo.Duplicate += _editor.SceneEditing.Duplicate;
TransformGizmo.Duplicate += Editor.Instance.SceneEditing.Duplicate;
Gizmos.Active = TransformGizmo; Gizmos.Active = TransformGizmo;
// Add grid // Add grid
@@ -232,144 +220,8 @@ namespace FlaxEditor.Viewport
editor.SceneEditing.SelectionChanged += OnSelectionChanged; editor.SceneEditing.SelectionChanged += OnSelectionChanged;
// Initialize snapping enabled from cached values // Gizmo widgets
if (_editor.ProjectCache.TryGetCustomData("TranslateSnapState", out var cachedState)) AddGizmoViewportWidgets(this, TransformGizmo);
TransformGizmo.TranslationSnapEnable = bool.Parse(cachedState);
if (_editor.ProjectCache.TryGetCustomData("RotationSnapState", out cachedState))
TransformGizmo.RotationSnapEnabled = bool.Parse(cachedState);
if (_editor.ProjectCache.TryGetCustomData("ScaleSnapState", out cachedState))
TransformGizmo.ScaleSnapEnabled = bool.Parse(cachedState);
if (_editor.ProjectCache.TryGetCustomData("TranslateSnapValue", out cachedState))
TransformGizmo.TranslationSnapValue = float.Parse(cachedState);
if (_editor.ProjectCache.TryGetCustomData("RotationSnapValue", out cachedState))
TransformGizmo.RotationSnapValue = float.Parse(cachedState);
if (_editor.ProjectCache.TryGetCustomData("ScaleSnapValue", out cachedState))
TransformGizmo.ScaleSnapValue = float.Parse(cachedState);
if (_editor.ProjectCache.TryGetCustomData("TransformSpaceState", out cachedState) && Enum.TryParse(cachedState, out TransformGizmoBase.TransformSpace space))
TransformGizmo.ActiveTransformSpace = space;
// Transform space widget
var transformSpaceWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var transformSpaceToggle = new ViewportWidgetButton(string.Empty, editor.Icons.Globe32, null, true)
{
Checked = TransformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World,
TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})",
Parent = transformSpaceWidget
};
transformSpaceToggle.Toggled += OnTransformSpaceToggle;
transformSpaceWidget.Parent = this;
// Scale snapping widget
var scaleSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var enableScaleSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.ScaleSnap32, null, true)
{
Checked = TransformGizmo.ScaleSnapEnabled,
TooltipText = "Enable scale snapping",
Parent = scaleSnappingWidget
};
enableScaleSnapping.Toggled += OnScaleSnappingToggle;
var scaleSnappingCM = new ContextMenu();
_scaleSnapping = new ViewportWidgetButton(TransformGizmo.ScaleSnapValue.ToString(), SpriteHandle.Invalid, scaleSnappingCM)
{
TooltipText = "Scale snapping values"
};
for (int i = 0; i < EditorViewportScaleSnapValues.Length; i++)
{
var v = EditorViewportScaleSnapValues[i];
var button = scaleSnappingCM.AddButton(v.ToString());
button.Tag = v;
}
scaleSnappingCM.ButtonClicked += OnWidgetScaleSnapClick;
scaleSnappingCM.VisibleChanged += OnWidgetScaleSnapShowHide;
_scaleSnapping.Parent = scaleSnappingWidget;
scaleSnappingWidget.Parent = this;
// Rotation snapping widget
var rotateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var enableRotateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.RotateSnap32, null, true)
{
Checked = TransformGizmo.RotationSnapEnabled,
TooltipText = "Enable rotation snapping",
Parent = rotateSnappingWidget
};
enableRotateSnapping.Toggled += OnRotateSnappingToggle;
var rotateSnappingCM = new ContextMenu();
_rotateSnapping = new ViewportWidgetButton(TransformGizmo.RotationSnapValue.ToString(), SpriteHandle.Invalid, rotateSnappingCM)
{
TooltipText = "Rotation snapping values"
};
for (int i = 0; i < EditorViewportRotateSnapValues.Length; i++)
{
var v = EditorViewportRotateSnapValues[i];
var button = rotateSnappingCM.AddButton(v.ToString());
button.Tag = v;
}
rotateSnappingCM.ButtonClicked += OnWidgetRotateSnapClick;
rotateSnappingCM.VisibleChanged += OnWidgetRotateSnapShowHide;
_rotateSnapping.Parent = rotateSnappingWidget;
rotateSnappingWidget.Parent = this;
// Translation snapping widget
var translateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var enableTranslateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.Grid32, null, true)
{
Checked = TransformGizmo.TranslationSnapEnable,
TooltipText = "Enable position snapping",
Parent = translateSnappingWidget
};
enableTranslateSnapping.Toggled += OnTranslateSnappingToggle;
var translateSnappingCM = new ContextMenu();
_translateSnapping = new ViewportWidgetButton(TransformGizmo.TranslationSnapValue.ToString(), SpriteHandle.Invalid, translateSnappingCM)
{
TooltipText = "Position snapping values"
};
if (TransformGizmo.TranslationSnapValue < 0.0f)
_translateSnapping.Text = "Bounding Box";
for (int i = 0; i < EditorViewportTranslateSnapValues.Length; i++)
{
var v = EditorViewportTranslateSnapValues[i];
var button = translateSnappingCM.AddButton(v.ToString());
button.Tag = v;
}
var buttonBB = translateSnappingCM.AddButton("Bounding Box").LinkTooltip("Snaps the selection based on it's bounding volume");
buttonBB.Tag = -1.0f;
translateSnappingCM.ButtonClicked += OnWidgetTranslateSnapClick;
translateSnappingCM.VisibleChanged += OnWidgetTranslateSnapShowHide;
_translateSnapping.Parent = translateSnappingWidget;
translateSnappingWidget.Parent = this;
// Gizmo mode widget
var gizmoMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
_gizmoModeTranslate = new ViewportWidgetButton(string.Empty, editor.Icons.Translate32, null, true)
{
Tag = TransformGizmoBase.Mode.Translate,
TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})",
Checked = true,
Parent = gizmoMode
};
_gizmoModeTranslate.Toggled += OnGizmoModeToggle;
_gizmoModeRotate = new ViewportWidgetButton(string.Empty, editor.Icons.Rotate32, null, true)
{
Tag = TransformGizmoBase.Mode.Rotate,
TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})",
Parent = gizmoMode
};
_gizmoModeRotate.Toggled += OnGizmoModeToggle;
_gizmoModeScale = new ViewportWidgetButton(string.Empty, editor.Icons.Scale32, null, true)
{
Tag = TransformGizmoBase.Mode.Scale,
TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})",
Parent = gizmoMode
};
_gizmoModeScale.Toggled += OnGizmoModeToggle;
gizmoMode.Parent = this;
// Show grid widget // Show grid widget
_showGridButton = ViewWidgetShowMenu.AddButton("Grid", () => Grid.Enabled = !Grid.Enabled); _showGridButton = ViewWidgetShowMenu.AddButton("Grid", () => Grid.Enabled = !Grid.Enabled);
@@ -400,14 +252,6 @@ namespace FlaxEditor.Viewport
} }
// Setup input actions // Setup input actions
InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate);
InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate);
InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale);
InputActions.Add(options => options.ToggleTransformSpace, () =>
{
OnTransformSpaceToggle(transformSpaceToggle);
transformSpaceToggle.Checked = !transformSpaceToggle.Checked;
});
InputActions.Add(options => options.LockFocusSelection, LockFocusSelection); InputActions.Add(options => options.LockFocusSelection, LockFocusSelection);
InputActions.Add(options => options.FocusSelection, FocusSelection); InputActions.Add(options => options.FocusSelection, FocusSelection);
InputActions.Add(options => options.RotateSelection, RotateSelection); InputActions.Add(options => options.RotateSelection, RotateSelection);
@@ -486,7 +330,7 @@ namespace FlaxEditor.Viewport
}; };
// Spawn // Spawn
Editor.Instance.SceneEditing.Spawn(actor, parent); _editor.SceneEditing.Spawn(actor, parent);
} }
private void OnBegin(RenderTask task, GPUContext context) private void OnBegin(RenderTask task, GPUContext context)
@@ -552,7 +396,7 @@ namespace FlaxEditor.Viewport
var task = renderContext.Task; var task = renderContext.Task;
// Render editor primitives, gizmo and debug shapes in debug view modes // Render editor primitives, gizmo and debug shapes in debug view modes
// Note: can use Output buffer as both input and output because EditorPrimitives is using a intermediate buffers // Note: can use Output buffer as both input and output because EditorPrimitives is using an intermediate buffer
if (EditorPrimitives && EditorPrimitives.CanRender()) if (EditorPrimitives && EditorPrimitives.CanRender())
{ {
EditorPrimitives.Render(context, ref renderContext, task.Output, task.Output); EditorPrimitives.Render(context, ref renderContext, task.Output, task.Output);
@@ -581,161 +425,6 @@ namespace FlaxEditor.Viewport
} }
} }
private void OnGizmoModeToggle(ViewportWidgetButton button)
{
TransformGizmo.ActiveMode = (TransformGizmoBase.Mode)(int)button.Tag;
}
private void OnTranslateSnappingToggle(ViewportWidgetButton button)
{
TransformGizmo.TranslationSnapEnable = !TransformGizmo.TranslationSnapEnable;
_editor.ProjectCache.SetCustomData("TranslateSnapState", TransformGizmo.TranslationSnapEnable.ToString());
}
private void OnRotateSnappingToggle(ViewportWidgetButton button)
{
TransformGizmo.RotationSnapEnabled = !TransformGizmo.RotationSnapEnabled;
_editor.ProjectCache.SetCustomData("RotationSnapState", TransformGizmo.RotationSnapEnabled.ToString());
}
private void OnScaleSnappingToggle(ViewportWidgetButton button)
{
TransformGizmo.ScaleSnapEnabled = !TransformGizmo.ScaleSnapEnabled;
_editor.ProjectCache.SetCustomData("ScaleSnapState", TransformGizmo.ScaleSnapEnabled.ToString());
}
private void OnTransformSpaceToggle(ViewportWidgetButton button)
{
TransformGizmo.ToggleTransformSpace();
_editor.ProjectCache.SetCustomData("TransformSpaceState", TransformGizmo.ActiveTransformSpace.ToString());
}
private void OnGizmoModeChanged()
{
// Update all viewport widgets status
var mode = TransformGizmo.ActiveMode;
_gizmoModeTranslate.Checked = mode == TransformGizmoBase.Mode.Translate;
_gizmoModeRotate.Checked = mode == TransformGizmoBase.Mode.Rotate;
_gizmoModeScale.Checked = mode == TransformGizmoBase.Mode.Scale;
}
private static readonly float[] EditorViewportScaleSnapValues =
{
0.05f,
0.1f,
0.25f,
0.5f,
1.0f,
2.0f,
4.0f,
6.0f,
8.0f,
};
private void OnWidgetScaleSnapClick(ContextMenuButton button)
{
var v = (float)button.Tag;
TransformGizmo.ScaleSnapValue = v;
_scaleSnapping.Text = v.ToString();
_editor.ProjectCache.SetCustomData("ScaleSnapValue", TransformGizmo.ScaleSnapValue.ToString("N"));
}
private void OnWidgetScaleSnapShowHide(Control control)
{
if (control.Visible == false)
return;
var ccm = (ContextMenu)control;
foreach (var e in ccm.Items)
{
if (e is ContextMenuButton b)
{
var v = (float)b.Tag;
b.Icon = Mathf.Abs(TransformGizmo.ScaleSnapValue - v) < 0.001f
? Style.Current.CheckBoxTick
: SpriteHandle.Invalid;
}
}
}
private static readonly float[] EditorViewportRotateSnapValues =
{
1.0f,
5.0f,
10.0f,
15.0f,
30.0f,
45.0f,
60.0f,
90.0f,
};
private void OnWidgetRotateSnapClick(ContextMenuButton button)
{
var v = (float)button.Tag;
TransformGizmo.RotationSnapValue = v;
_rotateSnapping.Text = v.ToString();
_editor.ProjectCache.SetCustomData("RotationSnapValue", TransformGizmo.RotationSnapValue.ToString("N"));
}
private void OnWidgetRotateSnapShowHide(Control control)
{
if (control.Visible == false)
return;
var ccm = (ContextMenu)control;
foreach (var e in ccm.Items)
{
if (e is ContextMenuButton b)
{
var v = (float)b.Tag;
b.Icon = Mathf.Abs(TransformGizmo.RotationSnapValue - v) < 0.001f
? Style.Current.CheckBoxTick
: SpriteHandle.Invalid;
}
}
}
private static readonly float[] EditorViewportTranslateSnapValues =
{
0.1f,
0.5f,
1.0f,
5.0f,
10.0f,
100.0f,
1000.0f,
};
private void OnWidgetTranslateSnapClick(ContextMenuButton button)
{
var v = (float)button.Tag;
TransformGizmo.TranslationSnapValue = v;
if (v < 0.0f)
_translateSnapping.Text = "Bounding Box";
else
_translateSnapping.Text = v.ToString();
_editor.ProjectCache.SetCustomData("TranslateSnapValue", TransformGizmo.TranslationSnapValue.ToString("N"));
}
private void OnWidgetTranslateSnapShowHide(Control control)
{
if (control.Visible == false)
return;
var ccm = (ContextMenu)control;
foreach (var e in ccm.Items)
{
if (e is ContextMenuButton b)
{
var v = (float)b.Tag;
b.Icon = Mathf.Abs(TransformGizmo.TranslationSnapValue - v) < 0.001f
? Style.Current.CheckBoxTick
: SpriteHandle.Invalid;
}
}
}
private void OnSelectionChanged() private void OnSelectionChanged()
{ {
var selection = _editor.SceneEditing.Selection; var selection = _editor.SceneEditing.Selection;
@@ -761,7 +450,7 @@ namespace FlaxEditor.Viewport
Vector3 gizmoPosition = TransformGizmo.Position; Vector3 gizmoPosition = TransformGizmo.Position;
// Rotate selected objects // Rotate selected objects
bool isPlayMode = Editor.Instance.StateMachine.IsPlayMode; bool isPlayMode = _editor.StateMachine.IsPlayMode;
TransformGizmo.StartTransforming(); TransformGizmo.StartTransforming();
for (int i = 0; i < selection.Count; i++) for (int i = 0; i < selection.Count; i++)
{ {
@@ -819,14 +508,7 @@ namespace FlaxEditor.Viewport
/// <param name="orientation">The target view orientation.</param> /// <param name="orientation">The target view orientation.</param>
public void FocusSelection(ref Quaternion orientation) public void FocusSelection(ref Quaternion orientation)
{ {
if (TransformGizmo.SelectedParents.Count == 0) ViewportCamera.FocusSelection(Gizmos, ref orientation);
return;
var gizmoBounds = Gizmos.Active.FocusBounds;
if (gizmoBounds != BoundingSphere.Empty)
((FPSCamera)ViewportCamera).ShowSphere(ref gizmoBounds, ref orientation);
else
((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orientation);
} }
/// <summary> /// <summary>
@@ -843,7 +525,7 @@ namespace FlaxEditor.Viewport
Vector3 gizmoPosition = TransformGizmo.Position; Vector3 gizmoPosition = TransformGizmo.Position;
// Transform selected objects // Transform selected objects
bool isPlayMode = Editor.Instance.StateMachine.IsPlayMode; bool isPlayMode = _editor.StateMachine.IsPlayMode;
for (int i = 0; i < selection.Count; i++) for (int i = 0; i < selection.Count; i++)
{ {
var obj = selection[i]; var obj = selection[i];
@@ -985,7 +667,14 @@ namespace FlaxEditor.Viewport
{ {
var parent = actor.Parent ?? Level.GetScene(0); var parent = actor.Parent ?? Level.GetScene(0);
actor.Name = Utilities.Utils.IncrementNameNumber(actor.Name, x => parent.GetChild(x) == null); actor.Name = Utilities.Utils.IncrementNameNumber(actor.Name, x => parent.GetChild(x) == null);
Editor.Instance.SceneEditing.Spawn(actor); _editor.SceneEditing.Spawn(actor);
}
/// <inheritdoc />
public override void OpenContextMenu()
{
var mouse = PointFromWindow(Root.MousePosition);
_editor.Windows.SceneWin.ShowContextMenu(this, mouse);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -2,14 +2,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Content; using FlaxEditor.Content;
using FlaxEditor.Gizmo; using FlaxEditor.Gizmo;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
using FlaxEditor.Viewport.Cameras; using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Viewport.Previews; using FlaxEditor.Viewport.Previews;
using FlaxEditor.Viewport.Widgets;
using FlaxEditor.Windows.Assets; using FlaxEditor.Windows.Assets;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
@@ -29,7 +28,6 @@ namespace FlaxEditor.Viewport
{ {
public PrefabWindowViewport Viewport; public PrefabWindowViewport Viewport;
/// <inheritdoc />
public override bool CanRender() public override bool CanRender()
{ {
return (Task.View.Flags & ViewFlags.EditorSprites) == ViewFlags.EditorSprites && Enabled; return (Task.View.Flags & ViewFlags.EditorSprites) == ViewFlags.EditorSprites && Enabled;
@@ -41,19 +39,34 @@ namespace FlaxEditor.Viewport
} }
} }
[HideInEditor]
private sealed class PrefabUIEditorRoot : UIEditorRoot
{
private readonly PrefabWindowViewport _viewport;
private bool UI => _viewport._hasUILinkedCached;
public PrefabUIEditorRoot(PrefabWindowViewport viewport)
: base(true)
{
_viewport = viewport;
Parent = viewport;
}
public override bool EnableInputs => !UI;
public override bool EnableSelecting => UI;
public override bool EnableBackground => UI;
public override TransformGizmo TransformGizmo => _viewport.TransformGizmo;
}
private readonly PrefabWindow _window; private readonly PrefabWindow _window;
private UpdateDelegate _update; private UpdateDelegate _update;
private readonly ViewportWidgetButton _gizmoModeTranslate;
private readonly ViewportWidgetButton _gizmoModeRotate;
private readonly ViewportWidgetButton _gizmoModeScale;
private ViewportWidgetButton _translateSnappng;
private ViewportWidgetButton _rotateSnapping;
private ViewportWidgetButton _scaleSnapping;
private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32); private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32);
private PrefabSpritesRenderer _spritesRenderer; private PrefabSpritesRenderer _spritesRenderer;
private IntPtr _tempDebugDrawContext;
private bool _hasUILinkedCached;
private PrefabUIEditorRoot _uiRoot;
/// <summary> /// <summary>
/// Drag and drop handlers /// Drag and drop handlers
@@ -106,144 +119,74 @@ namespace FlaxEditor.Viewport
// Add transformation gizmo // Add transformation gizmo
TransformGizmo = new TransformGizmo(this); TransformGizmo = new TransformGizmo(this);
TransformGizmo.ApplyTransformation += ApplyTransform; TransformGizmo.ApplyTransformation += ApplyTransform;
TransformGizmo.ModeChanged += OnGizmoModeChanged;
TransformGizmo.Duplicate += _window.Duplicate; TransformGizmo.Duplicate += _window.Duplicate;
Gizmos.Active = TransformGizmo; Gizmos.Active = TransformGizmo;
// Transform space widget // Use custom root for UI controls
var transformSpaceWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); _uiRoot = new PrefabUIEditorRoot(this);
var transformSpaceToggle = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Globe32, null, true) _uiRoot.IndexInParent = 0; // Move viewport down below other widgets in the viewport
{ _uiParentLink = _uiRoot.UIRoot;
Checked = TransformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World,
TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})",
Parent = transformSpaceWidget
};
transformSpaceToggle.Toggled += OnTransformSpaceToggle;
transformSpaceWidget.Parent = this;
// Scale snapping widget EditorGizmoViewport.AddGizmoViewportWidgets(this, TransformGizmo);
var scaleSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var enableScaleSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.ScaleSnap32, null, true)
{
Checked = TransformGizmo.ScaleSnapEnabled,
TooltipText = "Enable scale snapping",
Parent = scaleSnappingWidget
};
enableScaleSnapping.Toggled += OnScaleSnappingToggle;
var scaleSnappingCM = new ContextMenu();
_scaleSnapping = new ViewportWidgetButton(TransformGizmo.ScaleSnapValue.ToString(), SpriteHandle.Invalid, scaleSnappingCM)
{
TooltipText = "Scale snapping values"
};
for (int i = 0; i < EditorViewportScaleSnapValues.Length; i++)
{
var v = EditorViewportScaleSnapValues[i];
var button = scaleSnappingCM.AddButton(v.ToString());
button.Tag = v;
}
scaleSnappingCM.ButtonClicked += OnWidgetScaleSnapClick;
scaleSnappingCM.VisibleChanged += OnWidgetScaleSnapShowHide;
_scaleSnapping.Parent = scaleSnappingWidget;
scaleSnappingWidget.Parent = this;
// Rotation snapping widget
var rotateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var enableRotateSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.RotateSnap32, null, true)
{
Checked = TransformGizmo.RotationSnapEnabled,
TooltipText = "Enable rotation snapping",
Parent = rotateSnappingWidget
};
enableRotateSnapping.Toggled += OnRotateSnappingToggle;
var rotateSnappingCM = new ContextMenu();
_rotateSnapping = new ViewportWidgetButton(TransformGizmo.RotationSnapValue.ToString(), SpriteHandle.Invalid, rotateSnappingCM)
{
TooltipText = "Rotation snapping values"
};
for (int i = 0; i < EditorViewportRotateSnapValues.Length; i++)
{
var v = EditorViewportRotateSnapValues[i];
var button = rotateSnappingCM.AddButton(v.ToString());
button.Tag = v;
}
rotateSnappingCM.ButtonClicked += OnWidgetRotateSnapClick;
rotateSnappingCM.VisibleChanged += OnWidgetRotateSnapShowHide;
_rotateSnapping.Parent = rotateSnappingWidget;
rotateSnappingWidget.Parent = this;
// Translation snapping widget
var translateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var enableTranslateSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Grid32, null, true)
{
Checked = TransformGizmo.TranslationSnapEnable,
TooltipText = "Enable position snapping",
Parent = translateSnappingWidget
};
enableTranslateSnapping.Toggled += OnTranslateSnappingToggle;
var translateSnappingCM = new ContextMenu();
_translateSnappng = new ViewportWidgetButton(TransformGizmo.TranslationSnapValue.ToString(), SpriteHandle.Invalid, translateSnappingCM)
{
TooltipText = "Position snapping values"
};
for (int i = 0; i < EditorViewportTranslateSnapValues.Length; i++)
{
var v = EditorViewportTranslateSnapValues[i];
var button = translateSnappingCM.AddButton(v.ToString());
button.Tag = v;
}
translateSnappingCM.ButtonClicked += OnWidgetTranslateSnapClick;
translateSnappingCM.VisibleChanged += OnWidgetTranslateSnapShowHide;
_translateSnappng.Parent = translateSnappingWidget;
translateSnappingWidget.Parent = this;
// Gizmo mode widget
var gizmoMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
_gizmoModeTranslate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Translate32, null, true)
{
Tag = TransformGizmoBase.Mode.Translate,
TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})",
Checked = true,
Parent = gizmoMode
};
_gizmoModeTranslate.Toggled += OnGizmoModeToggle;
_gizmoModeRotate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Rotate32, null, true)
{
Tag = TransformGizmoBase.Mode.Rotate,
TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})",
Parent = gizmoMode
};
_gizmoModeRotate.Toggled += OnGizmoModeToggle;
_gizmoModeScale = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Scale32, null, true)
{
Tag = TransformGizmoBase.Mode.Scale,
TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})",
Parent = gizmoMode
};
_gizmoModeScale.Toggled += OnGizmoModeToggle;
gizmoMode.Parent = this;
// Setup input actions // Setup input actions
InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate);
InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate);
InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale);
InputActions.Add(options => options.ToggleTransformSpace, () =>
{
OnTransformSpaceToggle(transformSpaceToggle);
transformSpaceToggle.Checked = !transformSpaceToggle.Checked;
});
InputActions.Add(options => options.FocusSelection, ShowSelectedActors); InputActions.Add(options => options.FocusSelection, ShowSelectedActors);
SetUpdate(ref _update, OnUpdate); SetUpdate(ref _update, OnUpdate);
} }
/// <summary>
/// Updates the viewport's gizmos, especially to toggle between 3D and UI editing modes.
/// </summary>
internal void UpdateGizmoMode()
{
// Skip if gizmo mode was unmodified
if (_hasUILinked == _hasUILinkedCached)
return;
_hasUILinkedCached = _hasUILinked;
if (_hasUILinked)
{
// UI widget
Gizmos.Active = null;
ViewportCamera = new UIEditorCamera { UIEditor = _uiRoot };
// Hide 3D visuals
ShowEditorPrimitives = false;
ShowDefaultSceneActors = false;
ShowDebugDraw = false;
// Show whole UI on startup
var canvas = (CanvasRootControl)_uiParentLink.Children.FirstOrDefault(x => x is CanvasRootControl);
if (canvas != null)
ViewportCamera.ShowActor(canvas.Canvas);
else if (Instance is UIControl)
ViewportCamera.ShowActor(Instance);
}
else
{
// Generic prefab
Gizmos.Active = TransformGizmo;
ViewportCamera = new FPSCamera();
}
// Update default components usage
bool defaultFeatures = !_hasUILinked;
_disableInputUpdate = _hasUILinked;
_spritesRenderer.Enabled = defaultFeatures;
SelectionOutline.Enabled = defaultFeatures;
_showDefaultSceneButton.Visible = defaultFeatures;
_cameraWidget.Visible = defaultFeatures;
_cameraButton.Visible = defaultFeatures;
_orthographicModeButton.Visible = defaultFeatures;
Task.Enabled = defaultFeatures;
UseAutomaticTaskManagement = defaultFeatures;
TintColor = defaultFeatures ? Color.White : Color.Transparent;
}
private void OnUpdate(float deltaTime) private void OnUpdate(float deltaTime)
{ {
UpdateGizmoMode();
for (int i = 0; i < Gizmos.Count; i++) for (int i = 0; i < Gizmos.Count; i++)
{ {
Gizmos[i].Update(deltaTime); Gizmos[i].Update(deltaTime);
@@ -258,11 +201,19 @@ namespace FlaxEditor.Viewport
var selectedParents = TransformGizmo.SelectedParents; var selectedParents = TransformGizmo.SelectedParents;
if (selectedParents.Count > 0) if (selectedParents.Count > 0)
{ {
// Use temporary Debug Draw context to pull any debug shapes drawing in Scene Graph Nodes - those are used in OnDebugDraw down below
if (_tempDebugDrawContext == IntPtr.Zero)
_tempDebugDrawContext = DebugDraw.AllocateContext();
DebugDraw.SetContext(_tempDebugDrawContext);
DebugDraw.UpdateContext(_tempDebugDrawContext, 1.0f);
for (int i = 0; i < selectedParents.Count; i++) for (int i = 0; i < selectedParents.Count; i++)
{ {
if (selectedParents[i].IsActiveInHierarchy) if (selectedParents[i].IsActiveInHierarchy)
selectedParents[i].OnDebugDraw(_debugDrawData); selectedParents[i].OnDebugDraw(_debugDrawData);
} }
DebugDraw.SetContext(IntPtr.Zero);
} }
} }
@@ -306,7 +257,7 @@ namespace FlaxEditor.Viewport
public void ShowSelectedActors() public void ShowSelectedActors()
{ {
var orient = ViewOrientation; var orient = ViewOrientation;
((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orient); ViewportCamera.ShowActors(TransformGizmo.SelectedParents, ref orient);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -366,6 +317,13 @@ namespace FlaxEditor.Viewport
_window.Spawn(actor); _window.Spawn(actor);
} }
/// <inheritdoc />
public void OpenContextMenu()
{
var mouse = PointFromWindow(Root.MousePosition);
_window.ShowContextMenu(this, ref mouse);
}
/// <inheritdoc /> /// <inheritdoc />
protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false; protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false;
@@ -385,151 +343,6 @@ namespace FlaxEditor.Viewport
root.UpdateCallbacksToRemove.Add(_update); root.UpdateCallbacksToRemove.Add(_update);
} }
private void OnGizmoModeToggle(ViewportWidgetButton button)
{
TransformGizmo.ActiveMode = (TransformGizmoBase.Mode)(int)button.Tag;
}
private void OnTranslateSnappingToggle(ViewportWidgetButton button)
{
TransformGizmo.TranslationSnapEnable = !TransformGizmo.TranslationSnapEnable;
}
private void OnRotateSnappingToggle(ViewportWidgetButton button)
{
TransformGizmo.RotationSnapEnabled = !TransformGizmo.RotationSnapEnabled;
}
private void OnScaleSnappingToggle(ViewportWidgetButton button)
{
TransformGizmo.ScaleSnapEnabled = !TransformGizmo.ScaleSnapEnabled;
}
private void OnTransformSpaceToggle(ViewportWidgetButton button)
{
TransformGizmo.ToggleTransformSpace();
}
private void OnGizmoModeChanged()
{
// Update all viewport widgets status
var mode = TransformGizmo.ActiveMode;
_gizmoModeTranslate.Checked = mode == TransformGizmoBase.Mode.Translate;
_gizmoModeRotate.Checked = mode == TransformGizmoBase.Mode.Rotate;
_gizmoModeScale.Checked = mode == TransformGizmoBase.Mode.Scale;
}
private static readonly float[] EditorViewportScaleSnapValues =
{
0.05f,
0.1f,
0.25f,
0.5f,
1.0f,
2.0f,
4.0f,
6.0f,
8.0f,
};
private void OnWidgetScaleSnapClick(ContextMenuButton button)
{
var v = (float)button.Tag;
TransformGizmo.ScaleSnapValue = v;
_scaleSnapping.Text = v.ToString();
}
private void OnWidgetScaleSnapShowHide(Control control)
{
if (control.Visible == false)
return;
var ccm = (ContextMenu)control;
foreach (var e in ccm.Items)
{
if (e is ContextMenuButton b)
{
var v = (float)b.Tag;
b.Icon = Mathf.Abs(TransformGizmo.ScaleSnapValue - v) < 0.001f
? Style.Current.CheckBoxTick
: SpriteHandle.Invalid;
}
}
}
private static readonly float[] EditorViewportRotateSnapValues =
{
1.0f,
5.0f,
10.0f,
15.0f,
30.0f,
45.0f,
60.0f,
90.0f,
};
private void OnWidgetRotateSnapClick(ContextMenuButton button)
{
var v = (float)button.Tag;
TransformGizmo.RotationSnapValue = v;
_rotateSnapping.Text = v.ToString();
}
private void OnWidgetRotateSnapShowHide(Control control)
{
if (control.Visible == false)
return;
var ccm = (ContextMenu)control;
foreach (var e in ccm.Items)
{
if (e is ContextMenuButton b)
{
var v = (float)b.Tag;
b.Icon = Mathf.Abs(TransformGizmo.RotationSnapValue - v) < 0.001f
? Style.Current.CheckBoxTick
: SpriteHandle.Invalid;
}
}
}
private static readonly float[] EditorViewportTranslateSnapValues =
{
0.1f,
0.5f,
1.0f,
5.0f,
10.0f,
100.0f,
1000.0f,
};
private void OnWidgetTranslateSnapClick(ContextMenuButton button)
{
var v = (float)button.Tag;
TransformGizmo.TranslationSnapValue = v;
_translateSnappng.Text = v.ToString();
}
private void OnWidgetTranslateSnapShowHide(Control control)
{
if (control.Visible == false)
return;
var ccm = (ContextMenu)control;
foreach (var e in ccm.Items)
{
if (e is ContextMenuButton b)
{
var v = (float)b.Tag;
b.Icon = Mathf.Abs(TransformGizmo.TranslationSnapValue - v) < 0.001f
? Style.Current.CheckBoxTick
: SpriteHandle.Invalid;
}
}
}
private void OnSelectionChanged() private void OnSelectionChanged()
{ {
Gizmos.ForEach(x => x.OnSelectionChanged(_window.Selection)); Gizmos.ForEach(x => x.OnSelectionChanged(_window.Selection));
@@ -584,40 +397,6 @@ namespace FlaxEditor.Viewport
} }
} }
/// <inheritdoc />
public override void Draw()
{
base.Draw();
// Selected UI controls outline
bool drawAnySelectedControl = false;
// TODO: optimize this (eg. cache list of selected UIControl's when selection gets changed)
for (var i = 0; i < _window.Selection.Count; i++)
{
if (_window.Selection[i]?.EditableObject is UIControl controlActor && controlActor && controlActor.Control != null && controlActor.Control.VisibleInHierarchy && controlActor.Control.RootWindow != null)
{
if (!drawAnySelectedControl)
{
drawAnySelectedControl = true;
Render2D.PushTransform(ref _cachedTransform);
}
var control = controlActor.Control;
var bounds = control.EditorBounds;
var p1 = control.PointToParent(this, bounds.UpperLeft);
var p2 = control.PointToParent(this, bounds.UpperRight);
var p3 = control.PointToParent(this, bounds.BottomLeft);
var p4 = control.PointToParent(this, bounds.BottomRight);
var min = Float2.Min(Float2.Min(p1, p2), Float2.Min(p3, p4));
var max = Float2.Max(Float2.Max(p1, p2), Float2.Max(p3, p4));
bounds = new Rectangle(min, Float2.Max(max - min, Float2.Zero));
var options = Editor.Instance.Options.Options.Visual;
Render2D.DrawRectangle(bounds, options.SelectionOutlineColor0, options.UISelectionOutlineSize);
}
}
if (drawAnySelectedControl)
Render2D.PopTransform();
}
/// <inheritdoc /> /// <inheritdoc />
protected override void OnLeftMouseButtonUp() protected override void OnLeftMouseButtonUp()
{ {
@@ -760,14 +539,7 @@ namespace FlaxEditor.Viewport
/// <param name="orientation">The target view orientation.</param> /// <param name="orientation">The target view orientation.</param>
public void FocusSelection(ref Quaternion orientation) public void FocusSelection(ref Quaternion orientation)
{ {
if (TransformGizmo.SelectedParents.Count == 0) ViewportCamera.FocusSelection(Gizmos, ref orientation);
return;
var gizmoBounds = Gizmos.Active.FocusBounds;
if (gizmoBounds != BoundingSphere.Empty)
((FPSCamera)ViewportCamera).ShowSphere(ref gizmoBounds, ref orientation);
else
((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orientation);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -792,6 +564,13 @@ namespace FlaxEditor.Viewport
/// <inheritdoc /> /// <inheritdoc />
public override void OnDestroy() public override void OnDestroy()
{ {
if (IsDisposing)
return;
if (_tempDebugDrawContext != IntPtr.Zero)
{
DebugDraw.FreeContext(_tempDebugDrawContext);
_tempDebugDrawContext = IntPtr.Zero;
}
FlaxEngine.Object.Destroy(ref SelectionOutline); FlaxEngine.Object.Destroy(ref SelectionOutline);
FlaxEngine.Object.Destroy(ref _spritesRenderer); FlaxEngine.Object.Destroy(ref _spritesRenderer);

View File

@@ -21,7 +21,7 @@ namespace FlaxEditor.Viewport.Previews
/// <seealso cref="FlaxEditor.Viewport.EditorViewport" /> /// <seealso cref="FlaxEditor.Viewport.EditorViewport" />
public abstract class AssetPreview : EditorViewport, IEditorPrimitivesOwner public abstract class AssetPreview : EditorViewport, IEditorPrimitivesOwner
{ {
private ContextMenuButton _showDefaultSceneButton; internal ContextMenuButton _showDefaultSceneButton;
private IntPtr _debugDrawContext; private IntPtr _debugDrawContext;
private bool _debugDrawEnable; private bool _debugDrawEnable;
private bool _editorPrimitivesEnable; private bool _editorPrimitivesEnable;

View File

@@ -1,8 +1,8 @@
// 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.Generic;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI;
using Object = FlaxEngine.Object; using Object = FlaxEngine.Object;
namespace FlaxEditor.Viewport.Previews namespace FlaxEditor.Viewport.Previews
@@ -13,19 +13,11 @@ namespace FlaxEditor.Viewport.Previews
/// <seealso cref="AssetPreview" /> /// <seealso cref="AssetPreview" />
public class PrefabPreview : AssetPreview public class PrefabPreview : AssetPreview
{ {
/// <summary>
/// The currently spawned prefab instance owner. Used to link some actors such as UIControl to preview scene and view.
/// </summary>
internal static PrefabPreview LoadingPreview;
/// <summary>
/// The list of active prefab previews. Used to link some actors such as UIControl to preview scene and view.
/// </summary>
internal static List<PrefabPreview> ActivePreviews;
private Prefab _prefab; private Prefab _prefab;
private Actor _instance; private Actor _instance;
internal UIControl customControlLinked; private UIControl _uiControlLinked;
internal bool _hasUILinked;
internal ContainerControl _uiParentLink;
/// <summary> /// <summary>
/// Gets or sets the prefab asset to preview. /// Gets or sets the prefab asset to preview.
@@ -54,13 +46,10 @@ namespace FlaxEditor.Viewport.Previews
_prefab.WaitForLoaded(); _prefab.WaitForLoaded();
// Spawn prefab // Spawn prefab
LoadingPreview = this;
var instance = PrefabManager.SpawnPrefab(_prefab, null); var instance = PrefabManager.SpawnPrefab(_prefab, null);
LoadingPreview = null;
if (instance == null) if (instance == null)
{ {
_prefab = null; _prefab = null;
ActivePreviews.Remove(this);
throw new Exception("Failed to spawn a prefab for the preview."); throw new Exception("Failed to spawn a prefab for the preview.");
} }
@@ -84,11 +73,11 @@ namespace FlaxEditor.Viewport.Previews
if (_instance) if (_instance)
{ {
// Unlink UI control // Unlink UI control
if (customControlLinked) if (_uiControlLinked)
{ {
if (customControlLinked.Control?.Parent == this) if (_uiControlLinked.Control?.Parent == _uiParentLink)
customControlLinked.Control.Parent = null; _uiControlLinked.Control.Parent = null;
customControlLinked = null; _uiControlLinked = null;
} }
// Remove for the preview // Remove for the preview
@@ -96,27 +85,51 @@ namespace FlaxEditor.Viewport.Previews
} }
_instance = value; _instance = value;
_hasUILinked = false;
if (_instance) if (_instance)
{ {
// Add to the preview // Add to the preview
Task.AddCustomActor(_instance); Task.AddCustomActor(_instance);
UpdateLinkage();
// Link UI canvases to the preview
LinkCanvas(_instance);
} }
} }
} }
private void UpdateLinkage()
{
// Clear flag
_hasUILinked = false;
// Link UI canvases to the preview (eg. after canvas added to the prefab)
LinkCanvas(_instance);
// Link UI control to the preview
if (_uiControlLinked == null &&
_instance is UIControl uiControl &&
uiControl.Control != null &&
uiControl.Control.Parent == null)
{
uiControl.Control.Parent = _uiParentLink;
_uiControlLinked = uiControl;
_hasUILinked = true;
}
else if (_uiControlLinked != null)
_hasUILinked = true;
}
private void LinkCanvas(Actor actor) private void LinkCanvas(Actor actor)
{ {
if (actor is UICanvas uiCanvas) if (actor is UICanvas uiCanvas)
uiCanvas.EditorOverride(Task, this); {
uiCanvas.EditorOverride(Task, _uiParentLink);
if (uiCanvas.GUI.Parent == _uiParentLink)
_hasUILinked = true;
}
var children = actor.ChildrenCount; var children = actor.ChildrenCount;
for (int i = 0; i < children; i++) for (int i = 0; i < children; i++)
{
LinkCanvas(actor.GetChild(i)); LinkCanvas(actor.GetChild(i));
}
} }
/// <summary> /// <summary>
@@ -126,9 +139,8 @@ namespace FlaxEditor.Viewport.Previews
public PrefabPreview(bool useWidgets) public PrefabPreview(bool useWidgets)
: base(useWidgets) : base(useWidgets)
{ {
if (ActivePreviews == null) // Link to itself by default
ActivePreviews = new List<PrefabPreview>(); _uiParentLink = this;
ActivePreviews.Add(this);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -138,15 +150,13 @@ namespace FlaxEditor.Viewport.Previews
if (_instance != null) if (_instance != null)
{ {
// Link UI canvases to the preview (eg. after canvas added to the prefab) UpdateLinkage();
LinkCanvas(_instance);
} }
} }
/// <inheritdoc /> /// <inheritdoc />
public override void OnDestroy() public override void OnDestroy()
{ {
ActivePreviews.Remove(this);
Prefab = null; Prefab = null;
base.OnDestroy(); base.OnDestroy();

View File

@@ -146,7 +146,7 @@ namespace FlaxEditor.Viewport
var gridPlane = new Plane(Vector3.Zero, Vector3.Up); var gridPlane = new Plane(Vector3.Zero, Vector3.Up);
var flags = SceneGraphNode.RayCastData.FlagTypes.SkipColliders | SceneGraphNode.RayCastData.FlagTypes.SkipEditorPrimitives; var flags = SceneGraphNode.RayCastData.FlagTypes.SkipColliders | SceneGraphNode.RayCastData.FlagTypes.SkipEditorPrimitives;
hit = _owner.SceneGraphRoot.RayCast(ref ray, ref view, out var closest, out var normal, flags); hit = _owner.SceneGraphRoot.RayCast(ref ray, ref view, out var closest, out var normal, flags);
var girdGizmo = (GridGizmo)_owner.Gizmos.FirstOrDefault(x => x is GridGizmo); var girdGizmo = _owner.Gizmos.Get<GridGizmo>();
if (hit != null) if (hit != null)
{ {
// Use hit location // Use hit location
@@ -180,7 +180,7 @@ namespace FlaxEditor.Viewport
var location = hitLocation + new Vector3(0, bottomToCenter, 0); var location = hitLocation + new Vector3(0, bottomToCenter, 0);
// Apply grid snapping if enabled // Apply grid snapping if enabled
var transformGizmo = (TransformGizmo)_owner.Gizmos.FirstOrDefault(x => x is TransformGizmo); var transformGizmo = _owner.Gizmos.Get<TransformGizmo>();
if (transformGizmo != null && (_owner.UseSnapping || transformGizmo.TranslationSnapEnable)) if (transformGizmo != null && (_owner.UseSnapping || transformGizmo.TranslationSnapEnable))
{ {
float snapValue = transformGizmo.TranslationSnapValue; float snapValue = transformGizmo.TranslationSnapValue;

View File

@@ -186,6 +186,10 @@ namespace FlaxEditor.Windows.Assets
base.Initialize(layout); base.Initialize(layout);
// Ignore import settings GUI if the type is not animation. This removes the import UI if the animation asset was not created using an import.
if (proxy.ImportSettings.Settings.Type != FlaxEngine.Tools.ModelTool.ModelType.Animation)
return;
// Import Settings // Import Settings
{ {
var group = layout.Group("Import Settings"); var group = layout.Group("Import Settings");

View File

@@ -360,10 +360,9 @@ namespace FlaxEditor.Windows.Assets
/// </summary> /// </summary>
/// <param name="parent">The parent control.</param> /// <param name="parent">The parent control.</param>
/// <param name="location">The location (within a given control).</param> /// <param name="location">The location (within a given control).</param>
private void ShowContextMenu(Control parent, ref Float2 location) internal void ShowContextMenu(Control parent, ref Float2 location)
{ {
var contextMenu = CreateContextMenu(); var contextMenu = CreateContextMenu();
contextMenu.Show(parent, location); contextMenu.Show(parent, location);
} }

View File

@@ -3,11 +3,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using FlaxEditor.Gizmo;
using FlaxEditor.GUI.Tree; using FlaxEditor.GUI.Tree;
using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph;
using FlaxEditor.SceneGraph.GUI; using FlaxEditor.SceneGraph.GUI;
using FlaxEditor.Viewport.Cameras;
using FlaxEngine; using FlaxEngine;
namespace FlaxEditor.Windows.Assets namespace FlaxEditor.Windows.Assets
@@ -64,8 +62,11 @@ namespace FlaxEditor.Windows.Assets
private void OnSelectionUndo(SceneGraphNode[] toSelect) private void OnSelectionUndo(SceneGraphNode[] toSelect)
{ {
Selection.Clear(); Selection.Clear();
Selection.AddRange(toSelect); foreach (var e in toSelect)
{
if (e != null)
Selection.Add(e);
}
OnSelectionChanges(); OnSelectionChanges();
} }
@@ -118,11 +119,13 @@ namespace FlaxEditor.Windows.Assets
/// <param name="nodes">The nodes.</param> /// <param name="nodes">The nodes.</param>
public void Select(List<SceneGraphNode> nodes) public void Select(List<SceneGraphNode> nodes)
{ {
nodes?.RemoveAll(x => x == null);
if (nodes == null || nodes.Count == 0) if (nodes == null || nodes.Count == 0)
{ {
Deselect(); Deselect();
return; return;
} }
if (Utils.ArraysEqual(Selection, nodes)) if (Utils.ArraysEqual(Selection, nodes))
return; return;

View File

@@ -132,7 +132,7 @@ namespace FlaxEditor.Windows.Assets
IsScrollable = false, IsScrollable = false,
Offsets = new Margin(0, 0, 0, 18 + 6), Offsets = new Margin(0, 0, 0, 18 + 6),
}; };
_searchBox = new SearchBox() _searchBox = new SearchBox
{ {
AnchorPreset = AnchorPresets.HorizontalStretchMiddle, AnchorPreset = AnchorPresets.HorizontalStretchMiddle,
Parent = headerPanel, Parent = headerPanel,
@@ -140,7 +140,8 @@ namespace FlaxEditor.Windows.Assets
}; };
_searchBox.TextChanged += OnSearchBoxTextChanged; _searchBox.TextChanged += OnSearchBoxTextChanged;
_treePanel = new Panel() // Prefab structure tree
_treePanel = new Panel
{ {
AnchorPreset = AnchorPresets.StretchAll, AnchorPreset = AnchorPresets.StretchAll,
Offsets = new Margin(0.0f, 0.0f, headerPanel.Bottom, 0.0f), Offsets = new Margin(0.0f, 0.0f, headerPanel.Bottom, 0.0f),
@@ -148,8 +149,6 @@ namespace FlaxEditor.Windows.Assets
IsScrollable = true, IsScrollable = true,
Parent = sceneTreePanel, Parent = sceneTreePanel,
}; };
// Prefab structure tree
Graph = new LocalSceneGraph(new CustomRootNode(this)); Graph = new LocalSceneGraph(new CustomRootNode(this));
Graph.Root.TreeNode.Expand(true); Graph.Root.TreeNode.Expand(true);
_tree = new PrefabTree _tree = new PrefabTree
@@ -316,11 +315,7 @@ namespace FlaxEditor.Windows.Assets
return; return;
// Restore // Restore
_viewport.Prefab = _asset; OnPrefabOpened();
Graph.MainActor = _viewport.Instance;
Selection.Clear();
Select(Graph.Main);
Graph.Root.TreeNode.Expand(true);
_undo.Clear(); _undo.Clear();
ClearEditedFlag(); ClearEditedFlag();
} }
@@ -346,6 +341,16 @@ namespace FlaxEditor.Windows.Assets
} }
} }
private void OnPrefabOpened()
{
_viewport.Prefab = _asset;
_viewport.UpdateGizmoMode();
Graph.MainActor = _viewport.Instance;
Selection.Clear();
Select(Graph.Main);
Graph.Root.TreeNode.Expand(true);
}
/// <inheritdoc /> /// <inheritdoc />
public override void Save() public override void Save()
{ {
@@ -355,7 +360,7 @@ namespace FlaxEditor.Windows.Assets
try try
{ {
Editor.Scene.OnSaveStart(_viewport); Editor.Scene.OnSaveStart(_viewport._uiParentLink);
// Simply update changes // Simply update changes
Editor.Prefabs.ApplyAll(_viewport.Instance); Editor.Prefabs.ApplyAll(_viewport.Instance);
@@ -375,7 +380,7 @@ namespace FlaxEditor.Windows.Assets
} }
finally finally
{ {
Editor.Scene.OnSaveEnd(_viewport); Editor.Scene.OnSaveEnd(_viewport._uiParentLink);
} }
} }
@@ -417,13 +422,8 @@ namespace FlaxEditor.Windows.Assets
return; return;
} }
_viewport.Prefab = _asset; OnPrefabOpened();
Graph.MainActor = _viewport.Instance;
_focusCamera = true; _focusCamera = true;
Selection.Clear();
Select(Graph.Main);
Graph.Root.TreeNode.Expand(true);
_undo.Clear(); _undo.Clear();
ClearEditedFlag(); ClearEditedFlag();
@@ -468,11 +468,7 @@ namespace FlaxEditor.Windows.Assets
_viewport.Prefab = null; _viewport.Prefab = null;
if (_asset.IsLoaded) if (_asset.IsLoaded)
{ {
_viewport.Prefab = _asset; OnPrefabOpened();
Graph.MainActor = _viewport.Instance;
Selection.Clear();
Select(Graph.Main);
Graph.Root.TreeNode.ExpandAll(true);
} }
} }
finally finally
@@ -484,7 +480,6 @@ namespace FlaxEditor.Windows.Assets
if (_focusCamera && _viewport.Task.FrameCount > 1) if (_focusCamera && _viewport.Task.FrameCount > 1)
{ {
_focusCamera = false; _focusCamera = false;
Editor.GetActorEditorSphere(_viewport.Instance, out BoundingSphere bounds); Editor.GetActorEditorSphere(_viewport.Instance, out BoundingSphere bounds);
_viewport.ViewPosition = bounds.Center - _viewport.ViewDirection * (bounds.Radius * 1.2f); _viewport.ViewPosition = bounds.Center - _viewport.ViewDirection * (bounds.Radius * 1.2f);
} }

View File

@@ -281,6 +281,9 @@ namespace FlaxEditor.Windows
_view.OnDelete += Delete; _view.OnDelete += Delete;
_view.OnDuplicate += Duplicate; _view.OnDuplicate += Duplicate;
_view.OnPaste += Paste; _view.OnPaste += Paste;
_view.InputActions.Add(options => options.Search, () => _itemsSearchBox.Focus());
InputActions.Add(options => options.Search, () => _itemsSearchBox.Focus());
} }
private ContextMenu OnViewDropdownPopupCreate(ComboBox comboBox) private ContextMenu OnViewDropdownPopupCreate(ComboBox comboBox)

View File

@@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Xml; using System.Xml;
using FlaxEditor.Gizmo;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input; using FlaxEditor.GUI.Input;
using FlaxEditor.Options; using FlaxEditor.Options;
@@ -194,133 +195,14 @@ namespace FlaxEditor.Windows
public bool Active; public bool Active;
} }
private class GameRoot : ContainerControl /// <summary>
/// Root control for game UI preview in Editor. Supports basic UI editing via <see cref="UIEditorRoot"/>.
/// </summary>
private class GameRoot : UIEditorRoot
{ {
public bool EnableEvents => !Time.GamePaused; public override bool EnableInputs => !Time.GamePaused && Editor.IsPlayMode;
public override bool EnableSelecting => !Editor.IsPlayMode || Time.GamePaused;
public override bool RayCast(ref Float2 location, out Control hit) public override TransformGizmo TransformGizmo => Editor.Instance.MainTransformGizmo;
{
return RayCastChildren(ref location, out hit);
}
public override bool ContainsPoint(ref Float2 location, bool precise = false)
{
if (precise)
return false;
return base.ContainsPoint(ref location, precise);
}
public override bool OnCharInput(char c)
{
if (!EnableEvents)
return false;
return base.OnCharInput(c);
}
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
{
if (!EnableEvents)
return DragDropEffect.None;
return base.OnDragDrop(ref location, data);
}
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
{
if (!EnableEvents)
return DragDropEffect.None;
return base.OnDragEnter(ref location, data);
}
public override void OnDragLeave()
{
if (!EnableEvents)
return;
base.OnDragLeave();
}
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
{
if (!EnableEvents)
return DragDropEffect.None;
return base.OnDragMove(ref location, data);
}
public override bool OnKeyDown(KeyboardKeys key)
{
if (!EnableEvents)
return false;
return base.OnKeyDown(key);
}
public override void OnKeyUp(KeyboardKeys key)
{
if (!EnableEvents)
return;
base.OnKeyUp(key);
}
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
{
if (!EnableEvents)
return false;
return base.OnMouseDoubleClick(location, button);
}
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (!EnableEvents)
return false;
return base.OnMouseDown(location, button);
}
public override void OnMouseEnter(Float2 location)
{
if (!EnableEvents)
return;
base.OnMouseEnter(location);
}
public override void OnMouseLeave()
{
if (!EnableEvents)
return;
base.OnMouseLeave();
}
public override void OnMouseMove(Float2 location)
{
if (!EnableEvents)
return;
base.OnMouseMove(location);
}
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (!EnableEvents)
return false;
return base.OnMouseUp(location, button);
}
public override bool OnMouseWheel(Float2 location, float delta)
{
if (!EnableEvents)
return false;
return base.OnMouseWheel(location, delta);
}
} }
/// <summary> /// <summary>
@@ -348,13 +230,9 @@ namespace FlaxEditor.Windows
// Override the game GUI root // Override the game GUI root
_guiRoot = new GameRoot _guiRoot = new GameRoot
{ {
AnchorPreset = AnchorPresets.StretchAll,
Offsets = Margin.Zero,
//Visible = false,
AutoFocus = false,
Parent = _viewport Parent = _viewport
}; };
RootControl.GameRoot = _guiRoot; RootControl.GameRoot = _guiRoot.UIRoot;
SizeChanged += control => { ResizeViewport(); }; SizeChanged += control => { ResizeViewport(); };
@@ -382,6 +260,56 @@ namespace FlaxEditor.Windows
Editor.Instance.Windows.ProfilerWin.Clear(); Editor.Instance.Windows.ProfilerWin.Clear();
Editor.Instance.UI.AddStatusMessage($"Profiling results cleared."); Editor.Instance.UI.AddStatusMessage($"Profiling results cleared.");
}); });
InputActions.Add(options => options.Save, () =>
{
if (Editor.IsPlayMode)
return;
Editor.Instance.SaveAll();
});
InputActions.Add(options => options.Undo, () =>
{
if (Editor.IsPlayMode)
return;
Editor.Instance.PerformUndo();
Focus();
});
InputActions.Add(options => options.Redo, () =>
{
if (Editor.IsPlayMode)
return;
Editor.Instance.PerformRedo();
Focus();
});
InputActions.Add(options => options.Cut, () =>
{
if (Editor.IsPlayMode)
return;
Editor.Instance.SceneEditing.Cut();
});
InputActions.Add(options => options.Copy, () =>
{
if (Editor.IsPlayMode)
return;
Editor.Instance.SceneEditing.Copy();
});
InputActions.Add(options => options.Paste, () =>
{
if (Editor.IsPlayMode)
return;
Editor.Instance.SceneEditing.Paste();
});
InputActions.Add(options => options.Duplicate, () =>
{
if (Editor.IsPlayMode)
return;
Editor.Instance.SceneEditing.Duplicate();
});
InputActions.Add(options => options.Delete, () =>
{
if (Editor.IsPlayMode)
return;
Editor.Instance.SceneEditing.Delete();
});
} }
private void ChangeViewportRatio(ViewportScaleOptions v) private void ChangeViewportRatio(ViewportScaleOptions v)
@@ -916,35 +844,6 @@ namespace FlaxEditor.Windows
Render2D.DrawText(style.FontLarge, "No camera", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); Render2D.DrawText(style.FontLarge, "No camera", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center);
} }
// Selected UI controls outline
bool drawAnySelectedControl = false;
// TODO: optimize this (eg. cache list of selected UIControl's when selection gets changed)
var selection = Editor.SceneEditing.Selection;
for (var i = 0; i < selection.Count; i++)
{
if (selection[i].EditableObject is UIControl controlActor && controlActor && controlActor.Control != null && controlActor.Control.VisibleInHierarchy && controlActor.Control.RootWindow != null)
{
if (!drawAnySelectedControl)
{
drawAnySelectedControl = true;
Render2D.PushTransform(ref _viewport._cachedTransform);
}
var options = Editor.Options.Options.Visual;
var control = controlActor.Control;
var bounds = control.EditorBounds;
var p1 = control.PointToParent(_viewport, bounds.UpperLeft);
var p2 = control.PointToParent(_viewport, bounds.UpperRight);
var p3 = control.PointToParent(_viewport, bounds.BottomLeft);
var p4 = control.PointToParent(_viewport, bounds.BottomRight);
var min = Float2.Min(Float2.Min(p1, p2), Float2.Min(p3, p4));
var max = Float2.Max(Float2.Max(p1, p2), Float2.Max(p3, p4));
bounds = new Rectangle(min, Float2.Max(max - min, Float2.Zero));
Render2D.DrawRectangle(bounds, options.SelectionOutlineColor0, options.UISelectionOutlineSize);
}
}
if (drawAnySelectedControl)
Render2D.PopTransform();
// Play mode hints and overlay // Play mode hints and overlay
if (Editor.StateMachine.IsPlayMode) if (Editor.StateMachine.IsPlayMode)
{ {

View File

@@ -65,6 +65,11 @@ namespace FlaxEditor.Windows
/// </summary> /// </summary>
public OutputLogWindow Window; public OutputLogWindow Window;
/// <summary>
/// The input actions collection to processed during user input.
/// </summary>
public InputActionsContainer InputActions = new InputActionsContainer();
/// <summary> /// <summary>
/// The default text style. /// The default text style.
/// </summary> /// </summary>
@@ -80,6 +85,14 @@ namespace FlaxEditor.Windows
/// </summary> /// </summary>
public TextBlockStyle ErrorStyle; public TextBlockStyle ErrorStyle;
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
if (InputActions.Process(Editor.Instance, this, key))
return true;
return base.OnKeyDown(key);
}
/// <inheritdoc /> /// <inheritdoc />
protected override void OnParseTextBlocks() protected override void OnParseTextBlocks()
{ {
@@ -201,6 +214,9 @@ namespace FlaxEditor.Windows
// Setup editor options // Setup editor options
Editor.Options.OptionsChanged += OnEditorOptionsChanged; Editor.Options.OptionsChanged += OnEditorOptionsChanged;
OnEditorOptionsChanged(Editor.Options.Options); OnEditorOptionsChanged(Editor.Options.Options);
_output.InputActions.Add(options => options.Search, () => _searchBox.Focus());
InputActions.Add(options => options.Search, () => _searchBox.Focus());
GameCooker.Event += OnGameCookerEvent; GameCooker.Event += OnGameCookerEvent;
ScriptsBuilder.CompilationFailed += OnScriptsCompilationFailed; ScriptsBuilder.CompilationFailed += OnScriptsCompilationFailed;

View File

@@ -1,7 +1,6 @@
// 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.Generic;
using System.Linq; using System.Linq;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph;
@@ -258,10 +257,9 @@ namespace FlaxEditor.Windows
/// </summary> /// </summary>
/// <param name="parent">The parent control.</param> /// <param name="parent">The parent control.</param>
/// <param name="location">The location (within a given control).</param> /// <param name="location">The location (within a given control).</param>
private void ShowContextMenu(Control parent, Float2 location) internal void ShowContextMenu(Control parent, Float2 location)
{ {
var contextMenu = CreateContextMenu(); var contextMenu = CreateContextMenu();
contextMenu.Show(parent, location); contextMenu.Show(parent, location);
} }

View File

@@ -221,7 +221,6 @@ namespace FlaxEditor.Windows
{ {
if (!Editor.StateMachine.CurrentState.CanEditScene) if (!Editor.StateMachine.CurrentState.CanEditScene)
return; return;
ShowContextMenu(node, location); ShowContextMenu(node, location);
} }

View File

@@ -584,7 +584,43 @@ Asset::LoadResult BinaryAsset::loadAsset()
ASSERT(Storage && _header.ID.IsValid() && _header.TypeName.HasChars()); ASSERT(Storage && _header.ID.IsValid() && _header.TypeName.HasChars());
auto lock = Storage->Lock(); auto lock = Storage->Lock();
return load(); auto chunksToPreload = getChunksToPreload();
if (chunksToPreload != 0)
{
// Ensure that any chunks that were requested before are loaded in memory (in case streaming flushed them out after timeout)
for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++)
{
const auto chunk = _header.Chunks[i];
if (GET_CHUNK_FLAG(i) & chunksToPreload && chunk && chunk->IsMissing())
Storage->LoadAssetChunk(chunk);
}
}
const LoadResult result = load();
#if !BUILD_RELEASE
if (result == LoadResult::MissingDataChunk)
{
// Provide more insights on potentially missing asset data chunk
Char chunksBitMask[ASSET_FILE_DATA_CHUNKS + 1];
Char chunksExistBitMask[ASSET_FILE_DATA_CHUNKS + 1];
Char chunksLoadBitMask[ASSET_FILE_DATA_CHUNKS + 1];
for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++)
{
if (const FlaxChunk* chunk = _header.Chunks[i])
{
chunksBitMask[i] = '1';
chunksExistBitMask[i] = chunk->ExistsInFile() ? '1' : '0';
chunksLoadBitMask[i] = chunk->IsLoaded() ? '1' : '0';
}
else
{
chunksBitMask[i] = chunksExistBitMask[i] = chunksLoadBitMask[i] = '0';
}
}
chunksBitMask[ASSET_FILE_DATA_CHUNKS] = chunksExistBitMask[ASSET_FILE_DATA_CHUNKS] = chunksLoadBitMask[ASSET_FILE_DATA_CHUNKS] = 0;
LOG(Warning, "Asset reports missing data chunk. Chunks bitmask: {}, existing chunks: {} loaded chunks: {}. '{}'", chunksBitMask, chunksExistBitMask, chunksLoadBitMask, ToString());
}
#endif
return result;
} }
void BinaryAsset::releaseStorage() void BinaryAsset::releaseStorage()

View File

@@ -82,36 +82,17 @@ public:
/// <summary> /// <summary>
/// Gets the amount of created asset chunks. /// Gets the amount of created asset chunks.
/// </summary> /// </summary>
/// <returns>Created asset chunks</returns> int32 GetChunksCount() const;
int32 GetChunksCount() const
{
int32 result = 0;
for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++)
{
if (Chunks[i] != nullptr)
result++;
}
return result;
}
/// <summary> /// <summary>
/// Deletes all chunks. Warning! Chunks are managed internally, use with caution! /// Deletes all chunks. Warning! Chunks are managed internally, use with caution!
/// </summary> /// </summary>
void DeleteChunks() void DeleteChunks();
{
for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++)
{
SAFE_DELETE(Chunks[i]);
}
}
/// <summary> /// <summary>
/// Unlinks all chunks. /// Unlinks all chunks.
/// </summary> /// </summary>
void UnlinkChunks() void UnlinkChunks();
{
Platform::MemoryClear(Chunks, sizeof(Chunks));
}
/// <summary> /// <summary>
/// Gets string with a human-readable info about that header /// Gets string with a human-readable info about that header

View File

@@ -20,6 +20,30 @@
#endif #endif
#include <ThirdParty/LZ4/lz4.h> #include <ThirdParty/LZ4/lz4.h>
int32 AssetHeader::GetChunksCount() const
{
int32 result = 0;
for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++)
{
if (Chunks[i] != nullptr)
result++;
}
return result;
}
void AssetHeader::DeleteChunks()
{
for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++)
{
SAFE_DELETE(Chunks[i]);
}
}
void AssetHeader::UnlinkChunks()
{
Platform::MemoryClear(Chunks, sizeof(Chunks));
}
String AssetHeader::ToString() const String AssetHeader::ToString() const
{ {
return String::Format(TEXT("ID: {0}, TypeName: {1}, Chunks Count: {2}"), ID, TypeName, GetChunksCount()); return String::Format(TEXT("ID: {0}, TypeName: {1}, Chunks Count: {2}"), ID, TypeName, GetChunksCount());

View File

@@ -18,13 +18,13 @@ API_STRUCT() struct FLAXENGINE_API Transform
/// <summary> /// <summary>
/// The translation vector of the transform. /// The translation vector of the transform.
/// </summary> /// </summary>
API_FIELD(Attributes="EditorOrder(10), EditorDisplay(null, \"Position\")") API_FIELD(Attributes="EditorOrder(10), EditorDisplay(null, \"Position\"), ValueCategory(Utils.ValueCategory.Distance)")
Vector3 Translation; Vector3 Translation;
/// <summary> /// <summary>
/// The rotation of the transform. /// The rotation of the transform.
/// </summary> /// </summary>
API_FIELD(Attributes="EditorOrder(20), EditorDisplay(null, \"Rotation\")") API_FIELD(Attributes="EditorOrder(20), EditorDisplay(null, \"Rotation\"), ValueCategory(Utils.ValueCategory.Angle)")
Quaternion Orientation; Quaternion Orientation;
/// <summary> /// <summary>

View File

@@ -127,7 +127,8 @@ PACK_STRUCT(struct Vertex {
PACK_STRUCT(struct Data { PACK_STRUCT(struct Data {
Matrix ViewProjection; Matrix ViewProjection;
Float3 Padding; Float2 Padding;
float ClipPosZBias;
bool EnableDepthTest; bool EnableDepthTest;
}); });
@@ -356,6 +357,19 @@ namespace
Float3 CircleCache[DEBUG_DRAW_CIRCLE_VERTICES]; Float3 CircleCache[DEBUG_DRAW_CIRCLE_VERTICES];
Array<Float3> SphereTriangleCache; Array<Float3> SphereTriangleCache;
DebugSphereCache SphereCache[3]; DebugSphereCache SphereCache[3];
#if COMPILE_WITH_DEV_ENV
void OnShaderReloading(Asset* obj)
{
DebugDrawPsLinesDefault.Release();
DebugDrawPsLinesDepthTest.Release();
DebugDrawPsWireTrianglesDefault.Release();
DebugDrawPsWireTrianglesDepthTest.Release();
DebugDrawPsTrianglesDefault.Release();
DebugDrawPsTrianglesDepthTest.Release();
}
#endif
}; };
extern int32 BoxTrianglesIndicesCache[]; extern int32 BoxTrianglesIndicesCache[];
@@ -615,17 +629,19 @@ void DebugDrawService::Update()
GlobalContext.DebugDrawDefault.Update(deltaTime); GlobalContext.DebugDrawDefault.Update(deltaTime);
GlobalContext.DebugDrawDepthTest.Update(deltaTime); GlobalContext.DebugDrawDepthTest.Update(deltaTime);
// Check if need to setup a resources // Lazy-init resources
if (DebugDrawShader == nullptr) if (DebugDrawShader == nullptr)
{ {
// Shader
DebugDrawShader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/DebugDraw")); DebugDrawShader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/DebugDraw"));
if (DebugDrawShader == nullptr) if (DebugDrawShader == nullptr)
{ {
LOG(Fatal, "Cannot load DebugDraw shader"); LOG(Fatal, "Cannot load DebugDraw shader");
} }
#if COMPILE_WITH_DEV_ENV
DebugDrawShader->OnReloading.Bind(&OnShaderReloading);
#endif
} }
if (DebugDrawVB == nullptr && DebugDrawShader && DebugDrawShader->IsLoaded()) if (DebugDrawPsWireTrianglesDepthTest.Depth == nullptr && DebugDrawShader && DebugDrawShader->IsLoaded())
{ {
bool failed = false; bool failed = false;
const auto shader = DebugDrawShader->GetShader(); const auto shader = DebugDrawShader->GetShader();
@@ -661,10 +677,11 @@ void DebugDrawService::Update()
{ {
LOG(Fatal, "Cannot setup DebugDraw service!"); LOG(Fatal, "Cannot setup DebugDraw service!");
} }
// Vertex buffer
DebugDrawVB = New<DynamicVertexBuffer>((uint32)(DEBUG_DRAW_INITIAL_VB_CAPACITY * sizeof(Vertex)), (uint32)sizeof(Vertex), TEXT("DebugDraw.VB"));
} }
// Vertex buffer
if (DebugDrawVB == nullptr)
DebugDrawVB = New<DynamicVertexBuffer>((uint32)(DEBUG_DRAW_INITIAL_VB_CAPACITY * sizeof(Vertex)), (uint32)sizeof(Vertex), TEXT("DebugDraw.VB"));
} }
void DebugDrawService::Dispose() void DebugDrawService::Dispose()
@@ -723,7 +740,7 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
// Ensure to have shader loaded and any lines to render // Ensure to have shader loaded and any lines to render
const int32 debugDrawDepthTestCount = Context->DebugDrawDepthTest.Count(); const int32 debugDrawDepthTestCount = Context->DebugDrawDepthTest.Count();
const int32 debugDrawDefaultCount = Context->DebugDrawDefault.Count(); const int32 debugDrawDefaultCount = Context->DebugDrawDefault.Count();
if (DebugDrawShader == nullptr || !DebugDrawShader->IsLoaded() || debugDrawDepthTestCount + debugDrawDefaultCount == 0) if (DebugDrawShader == nullptr || !DebugDrawShader->IsLoaded() || debugDrawDepthTestCount + debugDrawDefaultCount == 0 || DebugDrawPsWireTrianglesDepthTest.Depth == nullptr)
return; return;
if (renderContext.Buffers == nullptr || !DebugDrawVB) if (renderContext.Buffers == nullptr || !DebugDrawVB)
return; return;
@@ -739,6 +756,7 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
} }
Context->LastViewPos = view.Position; Context->LastViewPos = view.Position;
Context->LastViewProj = view.Projection; Context->LastViewProj = view.Projection;
TaaJitterRemoveContext taaJitterRemove(view);
// Fallback to task buffers // Fallback to task buffers
if (target == nullptr && renderContext.Task) if (target == nullptr && renderContext.Task)
@@ -766,8 +784,9 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
const auto cb = DebugDrawShader->GetShader()->GetCB(0); const auto cb = DebugDrawShader->GetShader()->GetCB(0);
Data data; Data data;
Matrix vp; Matrix vp;
Matrix::Multiply(view.View, view.NonJitteredProjection, vp); Matrix::Multiply(view.View, view.Projection, vp);
Matrix::Transpose(vp, data.ViewProjection); Matrix::Transpose(vp, data.ViewProjection);
data.ClipPosZBias = -0.2f; // Reduce Z-fighting artifacts (eg. editor grid)
data.EnableDepthTest = enableDepthTest; data.EnableDepthTest = enableDepthTest;
context->UpdateCB(cb, &data); context->UpdateCB(cb, &data);
context->BindCB(0, cb); context->BindCB(0, cb);
@@ -848,6 +867,8 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
{ {
PROFILE_GPU_CPU_NAMED("Text"); PROFILE_GPU_CPU_NAMED("Text");
auto features = Render2D::Features; auto features = Render2D::Features;
// Disable vertex snapping when rendering 3D text
Render2D::Features = (Render2D::RenderingFeatures)((uint32)features & ~(uint32)Render2D::RenderingFeatures::VertexSnapping); Render2D::Features = (Render2D::RenderingFeatures)((uint32)features & ~(uint32)Render2D::RenderingFeatures::VertexSnapping);
if (!DebugDrawFont) if (!DebugDrawFont)

View File

@@ -960,7 +960,7 @@ API_STRUCT() struct FLAXENGINE_API EyeAdaptationSettings : ISerializable
/// The speed at which the exposure changes when the scene brightness moves from a bright area to a dark area (brightness goes down). /// The speed at which the exposure changes when the scene brightness moves from a bright area to a dark area (brightness goes down).
/// </summary> /// </summary>
API_FIELD(Attributes="Limit(0, 100.0f, 0.01f), EditorOrder(2), PostProcessSetting((int)EyeAdaptationSettingsOverride.SpeedDown)") API_FIELD(Attributes="Limit(0, 100.0f, 0.01f), EditorOrder(2), PostProcessSetting((int)EyeAdaptationSettingsOverride.SpeedDown)")
float SpeedDown = 1.0f; float SpeedDown = 10.0f;
/// <summary> /// <summary>
/// The pre-exposure value applied to the scene color before performing post-processing (such as bloom, lens flares, etc.). /// The pre-exposure value applied to the scene color before performing post-processing (such as bloom, lens flares, etc.).
@@ -984,7 +984,7 @@ API_STRUCT() struct FLAXENGINE_API EyeAdaptationSettings : ISerializable
/// The maximum brightness for the auto exposure which limits the upper brightness the eye can adapt within. /// The maximum brightness for the auto exposure which limits the upper brightness the eye can adapt within.
/// </summary> /// </summary>
API_FIELD(Attributes="Limit(0, 100.0f, 0.01f), EditorOrder(6), PostProcessSetting((int)EyeAdaptationSettingsOverride.MaxBrightness), EditorDisplay(null, \"Maximum Brightness\")") API_FIELD(Attributes="Limit(0, 100.0f, 0.01f), EditorOrder(6), PostProcessSetting((int)EyeAdaptationSettingsOverride.MaxBrightness), EditorDisplay(null, \"Maximum Brightness\")")
float MaxBrightness = 2.0f; float MaxBrightness = 15.0f;
/// <summary> /// <summary>
/// The lower bound for the luminance histogram of the scene color. This value is in percent and limits the pixels below this brightness. Use values in the range of 60-80. Used only in AutomaticHistogram mode. /// The lower bound for the luminance histogram of the scene color. This value is in percent and limits the pixels below this brightness. Use values in the range of 60-80. Used only in AutomaticHistogram mode.
@@ -996,7 +996,7 @@ API_STRUCT() struct FLAXENGINE_API EyeAdaptationSettings : ISerializable
/// The upper bound for the luminance histogram of the scene color. This value is in percent and limits the pixels above this brightness. Use values in the range of 80-95. Used only in AutomaticHistogram mode. /// The upper bound for the luminance histogram of the scene color. This value is in percent and limits the pixels above this brightness. Use values in the range of 80-95. Used only in AutomaticHistogram mode.
/// </summary> /// </summary>
API_FIELD(Attributes="Limit(1, 99, 0.001f), EditorOrder(3), PostProcessSetting((int)EyeAdaptationSettingsOverride.HistogramHighPercent)") API_FIELD(Attributes="Limit(1, 99, 0.001f), EditorOrder(3), PostProcessSetting((int)EyeAdaptationSettingsOverride.HistogramHighPercent)")
float HistogramHighPercent = 98.0f; float HistogramHighPercent = 90.0f;
public: public:
/// <summary> /// <summary>
@@ -1079,10 +1079,10 @@ API_STRUCT() struct FLAXENGINE_API CameraArtifactsSettings : ISerializable
CameraArtifactsSettingsOverride OverrideFlags = Override::None; CameraArtifactsSettingsOverride OverrideFlags = Override::None;
/// <summary> /// <summary>
/// Strength of the vignette effect. Value 0 hides it. The default value is 0.8. /// Strength of the vignette effect. Value 0 hides it. The default value is 0.4.
/// </summary> /// </summary>
API_FIELD(Attributes="Limit(0, 2, 0.001f), EditorOrder(0), PostProcessSetting((int)CameraArtifactsSettingsOverride.VignetteIntensity)") API_FIELD(Attributes="Limit(0, 2, 0.001f), EditorOrder(0), PostProcessSetting((int)CameraArtifactsSettingsOverride.VignetteIntensity)")
float VignetteIntensity = 0.8f; float VignetteIntensity = 0.4f;
/// <summary> /// <summary>
/// Color of the vignette. /// Color of the vignette.
@@ -1230,25 +1230,25 @@ API_STRUCT() struct FLAXENGINE_API LensFlaresSettings : ISerializable
/// Strength of the effect. A value of 0 disables it. /// Strength of the effect. A value of 0 disables it.
/// </summary> /// </summary>
API_FIELD(Attributes="Limit(0, 10.0f, 0.01f), EditorOrder(0), PostProcessSetting((int)LensFlaresSettingsOverride.Intensity)") API_FIELD(Attributes="Limit(0, 10.0f, 0.01f), EditorOrder(0), PostProcessSetting((int)LensFlaresSettingsOverride.Intensity)")
float Intensity = 1.0f; float Intensity = 0.5f;
/// <summary> /// <summary>
/// Amount of lens flares ghosts. /// Amount of lens flares ghosts.
/// </summary> /// </summary>
API_FIELD(Attributes="Limit(0, 16), EditorOrder(1), PostProcessSetting((int)LensFlaresSettingsOverride.Ghosts)") API_FIELD(Attributes="Limit(0, 16), EditorOrder(1), PostProcessSetting((int)LensFlaresSettingsOverride.Ghosts)")
int32 Ghosts = 8; int32 Ghosts = 4;
/// <summary> /// <summary>
/// Lens flares halo width. /// Lens flares halo width.
/// </summary> /// </summary>
API_FIELD(Attributes="EditorOrder(2), PostProcessSetting((int)LensFlaresSettingsOverride.HaloWidth)") API_FIELD(Attributes="EditorOrder(2), PostProcessSetting((int)LensFlaresSettingsOverride.HaloWidth)")
float HaloWidth = 0.16f; float HaloWidth = 0.04f;
/// <summary> /// <summary>
/// Lens flares halo intensity. /// Lens flares halo intensity.
/// </summary> /// </summary>
API_FIELD(Attributes="Limit(0, 10.0f, 0.01f), EditorOrder(3), PostProcessSetting((int)LensFlaresSettingsOverride.HaloIntensity)") API_FIELD(Attributes="Limit(0, 10.0f, 0.01f), EditorOrder(3), PostProcessSetting((int)LensFlaresSettingsOverride.HaloIntensity)")
float HaloIntensity = 0.666f; float HaloIntensity = 0.5f;
/// <summary> /// <summary>
/// Ghost samples dispersal parameter. /// Ghost samples dispersal parameter.
@@ -1584,7 +1584,7 @@ API_STRUCT() struct FLAXENGINE_API MotionBlurSettings : ISerializable
/// The blur effect strength. A value of 0 disables it, while higher values increase the effect. /// The blur effect strength. A value of 0 disables it, while higher values increase the effect.
/// </summary> /// </summary>
API_FIELD(Attributes="Limit(0, 5, 0.01f), EditorOrder(1), PostProcessSetting((int)MotionBlurSettingsOverride.Scale)") API_FIELD(Attributes="Limit(0, 5, 0.01f), EditorOrder(1), PostProcessSetting((int)MotionBlurSettingsOverride.Scale)")
float Scale = 1.0f; float Scale = 0.5f;
/// <summary> /// <summary>
/// The amount of sample points used during motion blur rendering. It affects blur quality and performance. /// The amount of sample points used during motion blur rendering. It affects blur quality and performance.

View File

@@ -18,6 +18,7 @@ void RenderView::Prepare(RenderContext& renderContext)
// Check if use TAA (need to modify the projection matrix) // Check if use TAA (need to modify the projection matrix)
Float2 taaJitter; Float2 taaJitter;
NonJitteredProjection = Projection; NonJitteredProjection = Projection;
IsTaaResolved = false;
if (renderContext.List->Setup.UseTemporalAAJitter) if (renderContext.List->Setup.UseTemporalAAJitter)
{ {
// Move to the next frame // Move to the next frame
@@ -82,6 +83,18 @@ void RenderView::PrepareCache(const RenderContext& renderContext, float width, f
MainScreenSize = mainView->ScreenSize; MainScreenSize = mainView->ScreenSize;
} }
void RenderView::UpdateCachedData()
{
Matrix::Invert(View, IV);
Matrix::Invert(Projection, IP);
Matrix viewProjection;
Matrix::Multiply(View, Projection, viewProjection);
Frustum.SetMatrix(viewProjection);
Matrix::Invert(viewProjection, IVP);
CullingFrustum = Frustum;
NonJitteredProjection = Projection;
}
void RenderView::SetUp(const Matrix& viewProjection) void RenderView::SetUp(const Matrix& viewProjection)
{ {
// Copy data // Copy data
@@ -201,3 +214,27 @@ void RenderView::GetWorldMatrix(const Transform& transform, Matrix& world) const
const Float3 translation = transform.Translation - Origin; const Float3 translation = transform.Translation - Origin;
Matrix::Transformation(transform.Scale, transform.Orientation, translation, world); Matrix::Transformation(transform.Scale, transform.Orientation, translation, world);
} }
TaaJitterRemoveContext::TaaJitterRemoveContext(const RenderView& view)
{
if (view.IsTaaResolved)
{
// Cancel-out sub-pixel jitter when drawing geometry after TAA has been resolved
_view = (RenderView*)&view;
_prevProjection = view.Projection;
_prevNonJitteredProjection = view.NonJitteredProjection;
_view->Projection = _prevNonJitteredProjection;
_view->UpdateCachedData();
}
}
TaaJitterRemoveContext::~TaaJitterRemoveContext()
{
if (_view)
{
// Restore projection
_view->Projection = _prevProjection;
_view->UpdateCachedData();
_view->NonJitteredProjection = _prevNonJitteredProjection;
}
}

View File

@@ -117,6 +117,11 @@ public:
/// </summary> /// </summary>
API_FIELD() bool IsCullingDisabled = false; API_FIELD() bool IsCullingDisabled = false;
/// <summary>
/// True if TAA has been resolved when rendering view and frame doesn't contain jitter anymore. Rendering geometry after this point should not use jitter anymore (eg. editor gizmos or custom geometry as overlay).
/// </summary>
API_FIELD() bool IsTaaResolved = false;
/// <summary> /// <summary>
/// The static flags mask used to hide objects that don't have a given static flags. Eg. use StaticFlags::Lightmap to render only objects that can use lightmap. /// The static flags mask used to hide objects that don't have a given static flags. Eg. use StaticFlags::Lightmap to render only objects that can use lightmap.
/// </summary> /// </summary>
@@ -160,7 +165,7 @@ public:
API_FIELD() DEPRECATED float ShadowModelLODDistanceFactor = 1.0f; API_FIELD() DEPRECATED float ShadowModelLODDistanceFactor = 1.0f;
/// <summary> /// <summary>
/// The Temporal Anti-Aliasing jitter frame index. /// Temporal Anti-Aliasing jitter frame index.
/// </summary> /// </summary>
API_FIELD() int32 TaaFrameIndex = 0; API_FIELD() int32 TaaFrameIndex = 0;
@@ -261,6 +266,11 @@ public:
RenderView& operator=(const RenderView& other) = default; RenderView& operator=(const RenderView& other) = default;
PRAGMA_ENABLE_DEPRECATION_WARNINGS PRAGMA_ENABLE_DEPRECATION_WARNINGS
/// <summary>
/// Updates the cached data for the view (inverse matrices, etc.).
/// </summary>
void UpdateCachedData();
// Set up view with custom params // Set up view with custom params
// @param viewProjection View * Projection matrix // @param viewProjection View * Projection matrix
void SetUp(const Matrix& viewProjection); void SetUp(const Matrix& viewProjection);
@@ -344,3 +354,15 @@ public:
world.M43 -= Origin.Z; world.M43 -= Origin.Z;
} }
}; };
// Removes TAA jitter from the RenderView when drawing geometry after TAA has been resolved to prevent unwanted jittering.
struct TaaJitterRemoveContext
{
private:
RenderView* _view = nullptr;
Matrix _prevProjection, _prevNonJitteredProjection;
public:
TaaJitterRemoveContext(const RenderView& view);
~TaaJitterRemoveContext();
};

View File

@@ -10,6 +10,7 @@
#include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Graphics/Shaders/GPUShader.h"
#include "Engine/Graphics/Materials/MaterialShader.h" #include "Engine/Graphics/Materials/MaterialShader.h"
#include "Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h" #include "Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h"
#include "FlaxEngine.Gen.h"
const Char* ShaderProfileCacheDirNames[] = const Char* ShaderProfileCacheDirNames[] =
{ {
@@ -179,6 +180,7 @@ bool ShaderCacheManagerService::Init()
// Validate the database cache version (need to recompile all shaders on shader cache format change) // Validate the database cache version (need to recompile all shaders on shader cache format change)
struct CacheVersion struct CacheVersion
{ {
int32 EngineVersion = -1;
int32 ShaderCacheVersion = -1; int32 ShaderCacheVersion = -1;
int32 MaterialGraphVersion = -1; int32 MaterialGraphVersion = -1;
int32 ParticleGraphVersion = -1; int32 ParticleGraphVersion = -1;
@@ -193,7 +195,8 @@ bool ShaderCacheManagerService::Init()
LOG(Warning, "Failed to read the shaders cache database version file."); LOG(Warning, "Failed to read the shaders cache database version file.");
} }
} }
if (cacheVersion.ShaderCacheVersion != GPU_SHADER_CACHE_VERSION if (cacheVersion.EngineVersion != FLAXENGINE_VERSION_BUILD
|| cacheVersion.ShaderCacheVersion != GPU_SHADER_CACHE_VERSION
|| cacheVersion.MaterialGraphVersion != MATERIAL_GRAPH_VERSION || cacheVersion.MaterialGraphVersion != MATERIAL_GRAPH_VERSION
|| cacheVersion.ParticleGraphVersion != PARTICLE_GPU_GRAPH_VERSION || cacheVersion.ParticleGraphVersion != PARTICLE_GPU_GRAPH_VERSION
) )
@@ -209,6 +212,7 @@ bool ShaderCacheManagerService::Init()
LOG(Error, "Failed to createe the shaders cache database directory."); LOG(Error, "Failed to createe the shaders cache database directory.");
} }
cacheVersion.EngineVersion = FLAXENGINE_VERSION_BUILD;
cacheVersion.ShaderCacheVersion = GPU_SHADER_CACHE_VERSION; cacheVersion.ShaderCacheVersion = GPU_SHADER_CACHE_VERSION;
cacheVersion.MaterialGraphVersion = MATERIAL_GRAPH_VERSION; cacheVersion.MaterialGraphVersion = MATERIAL_GRAPH_VERSION;
cacheVersion.ParticleGraphVersion = PARTICLE_GPU_GRAPH_VERSION; cacheVersion.ParticleGraphVersion = PARTICLE_GPU_GRAPH_VERSION;

View File

@@ -77,7 +77,7 @@ public:
/// <summary> /// <summary>
/// Gets the camera's field of view (in degrees). /// Gets the camera's field of view (in degrees).
/// </summary> /// </summary>
API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(60.0f), Limit(0, 179), EditorDisplay(\"Camera\", \"Field Of View\"), VisibleIf(nameof(UsePerspective))") API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(60.0f), Limit(0, 179), EditorDisplay(\"Camera\", \"Field Of View\"), VisibleIf(nameof(UsePerspective)), ValueCategory(Utils.ValueCategory.Angle)")
float GetFieldOfView() const; float GetFieldOfView() const;
/// <summary> /// <summary>
@@ -99,7 +99,7 @@ public:
/// <summary> /// <summary>
/// Gets camera's near plane distance. /// Gets camera's near plane distance.
/// </summary> /// </summary>
API_PROPERTY(Attributes="EditorOrder(30), DefaultValue(10.0f), Limit(0, 1000, 0.05f), EditorDisplay(\"Camera\")") API_PROPERTY(Attributes="EditorOrder(30), DefaultValue(10.0f), Limit(0, 1000, 0.05f), EditorDisplay(\"Camera\"), ValueCategory(Utils.ValueCategory.Distance)")
float GetNearPlane() const; float GetNearPlane() const;
/// <summary> /// <summary>
@@ -110,7 +110,7 @@ public:
/// <summary> /// <summary>
/// Gets camera's far plane distance. /// Gets camera's far plane distance.
/// </summary> /// </summary>
API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(40000.0f), Limit(0, float.MaxValue, 5), EditorDisplay(\"Camera\")") API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(40000.0f), Limit(0, float.MaxValue, 5), EditorDisplay(\"Camera\"), ValueCategory(Utils.ValueCategory.Distance)")
float GetFarPlane() const; float GetFarPlane() const;
/// <summary> /// <summary>

View File

@@ -553,13 +553,11 @@ const Span<MaterialSlot> StaticModel::GetMaterialSlots() const
MaterialBase* StaticModel::GetMaterial(int32 entryIndex) MaterialBase* StaticModel::GetMaterial(int32 entryIndex)
{ {
if (Model) if (!Model || Model->WaitForLoaded())
Model->WaitForLoaded();
else
return nullptr; return nullptr;
CHECK_RETURN(entryIndex >= 0 && entryIndex < Entries.Count(), nullptr); CHECK_RETURN(entryIndex >= 0 && entryIndex < Entries.Count(), nullptr);
MaterialBase* material = Entries[entryIndex].Material.Get(); MaterialBase* material = Entries[entryIndex].Material.Get();
if (!material) if (!material && entryIndex < Model->MaterialSlots.Count())
{ {
material = Model->MaterialSlots[entryIndex].Material.Get(); material = Model->MaterialSlots[entryIndex].Material.Get();
if (!material) if (!material)

View File

@@ -569,7 +569,7 @@ void RigidBody::OnTransformChanged()
void RigidBody::OnPhysicsSceneChanged(PhysicsScene* previous) void RigidBody::OnPhysicsSceneChanged(PhysicsScene* previous)
{ {
PhysicsBackend::RemoveSceneActor(previous->GetPhysicsScene(), _actor); PhysicsBackend::RemoveSceneActor(previous->GetPhysicsScene(), _actor, true);
void* scene = GetPhysicsScene()->GetPhysicsScene(); void* scene = GetPhysicsScene()->GetPhysicsScene();
PhysicsBackend::AddSceneActor(scene, _actor); PhysicsBackend::AddSceneActor(scene, _actor);
const bool putToSleep = !_startAwake && GetEnableSimulation() && !GetIsKinematic() && IsActiveInHierarchy(); const bool putToSleep = !_startAwake && GetEnableSimulation() && !GetIsKinematic() && IsActiveInHierarchy();

View File

@@ -181,7 +181,7 @@ public:
/// <summary> /// <summary>
/// Gets the mass value measured in kilograms (use override value only if OverrideMass is checked). /// Gets the mass value measured in kilograms (use override value only if OverrideMass is checked).
/// </summary> /// </summary>
API_PROPERTY(Attributes="EditorOrder(110), Limit(0), EditorDisplay(\"Rigid Body\")") API_PROPERTY(Attributes="EditorOrder(110), Limit(0), EditorDisplay(\"Rigid Body\"), ValueCategory(Utils.ValueCategory.Mass)")
float GetMass() const; float GetMass() const;
/// <summary> /// <summary>

View File

@@ -23,7 +23,7 @@ public:
/// Gets the size of the box, measured in the object's local space. /// Gets the size of the box, measured in the object's local space.
/// </summary> /// </summary>
/// <remarks>The box size will be scaled by the actor's world scale. </remarks> /// <remarks>The box size will be scaled by the actor's world scale. </remarks>
API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(typeof(Float3), \"100,100,100\"), EditorDisplay(\"Collider\")") API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(typeof(Float3), \"100,100,100\"), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)")
FORCE_INLINE Float3 GetSize() const FORCE_INLINE Float3 GetSize() const
{ {
return _size; return _size;

View File

@@ -25,7 +25,7 @@ public:
/// Gets the radius of the sphere, measured in the object's local space. /// Gets the radius of the sphere, measured in the object's local space.
/// </summary> /// </summary>
/// <remarks>The sphere radius will be scaled by the actor's world scale.</remarks> /// <remarks>The sphere radius will be scaled by the actor's world scale.</remarks>
API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(20.0f), EditorDisplay(\"Collider\")") API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(20.0f), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)")
FORCE_INLINE float GetRadius() const FORCE_INLINE float GetRadius() const
{ {
return _radius; return _radius;
@@ -41,7 +41,7 @@ public:
/// Gets the height of the capsule, measured in the object's local space between the centers of the hemispherical ends. /// Gets the height of the capsule, measured in the object's local space between the centers of the hemispherical ends.
/// </summary> /// </summary>
/// <remarks>The capsule height will be scaled by the actor's world scale.</remarks> /// <remarks>The capsule height will be scaled by the actor's world scale.</remarks>
API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(100.0f), EditorDisplay(\"Collider\")") API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(100.0f), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)")
FORCE_INLINE float GetHeight() const FORCE_INLINE float GetHeight() const
{ {
return _height; return _height;

View File

@@ -73,7 +73,7 @@ public:
/// <summary> /// <summary>
/// Gets the radius of the sphere, measured in the object's local space. The sphere radius will be scaled by the actor's world scale. /// Gets the radius of the sphere, measured in the object's local space. The sphere radius will be scaled by the actor's world scale.
/// </summary> /// </summary>
API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(50.0f), EditorDisplay(\"Collider\")") API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(50.0f), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)")
float GetRadius() const; float GetRadius() const;
/// <summary> /// <summary>
@@ -84,7 +84,7 @@ public:
/// <summary> /// <summary>
/// Gets the height of the capsule, measured in the object's local space. The capsule height will be scaled by the actor's world scale. /// Gets the height of the capsule, measured in the object's local space. The capsule height will be scaled by the actor's world scale.
/// </summary> /// </summary>
API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(150.0f), EditorDisplay(\"Collider\")") API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(150.0f), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)")
float GetHeight() const; float GetHeight() const;
/// <summary> /// <summary>
@@ -95,7 +95,7 @@ public:
/// <summary> /// <summary>
/// Gets the slope limit (in degrees). Limits the collider to only climb slopes that are less steep (in degrees) than the indicated value. /// Gets the slope limit (in degrees). Limits the collider to only climb slopes that are less steep (in degrees) than the indicated value.
/// </summary> /// </summary>
API_PROPERTY(Attributes="EditorOrder(210), DefaultValue(45.0f), Limit(0, 100), EditorDisplay(\"Character Controller\")") API_PROPERTY(Attributes="EditorOrder(210), DefaultValue(45.0f), Limit(0, 100), EditorDisplay(\"Character Controller\"), ValueCategory(Utils.ValueCategory.Angle)")
float GetSlopeLimit() const; float GetSlopeLimit() const;
/// <summary> /// <summary>
@@ -117,7 +117,7 @@ public:
/// <summary> /// <summary>
/// Gets the step height. The character will step up a stair only if it is closer to the ground than the indicated value. This should not be greater than the Character Controllers height or it will generate an error. /// Gets the step height. The character will step up a stair only if it is closer to the ground than the indicated value. This should not be greater than the Character Controllers height or it will generate an error.
/// </summary> /// </summary>
API_PROPERTY(Attributes="EditorOrder(220), DefaultValue(30.0f), Limit(0), EditorDisplay(\"Character Controller\")") API_PROPERTY(Attributes="EditorOrder(220), DefaultValue(30.0f), Limit(0), EditorDisplay(\"Character Controller\"), ValueCategory(Utils.ValueCategory.Distance)")
float GetStepOffset() const; float GetStepOffset() const;
/// <summary> /// <summary>
@@ -139,7 +139,7 @@ public:
/// <summary> /// <summary>
/// Gets the minimum move distance of the character controller. The minimum traveled distance to consider. If traveled distance is smaller, the character doesn't move. This is used to stop the recursive motion algorithm when remaining distance to travel is small. /// Gets the minimum move distance of the character controller. The minimum traveled distance to consider. If traveled distance is smaller, the character doesn't move. This is used to stop the recursive motion algorithm when remaining distance to travel is small.
/// </summary> /// </summary>
API_PROPERTY(Attributes="EditorOrder(230), DefaultValue(0.0f), Limit(0, 1000), EditorDisplay(\"Character Controller\")") API_PROPERTY(Attributes="EditorOrder(230), DefaultValue(0.0f), Limit(0, 1000), EditorDisplay(\"Character Controller\"), ValueCategory(Utils.ValueCategory.Distance)")
float GetMinMoveDistance() const; float GetMinMoveDistance() const;
/// <summary> /// <summary>

View File

@@ -438,7 +438,7 @@ void Collider::OnPhysicsSceneChanged(PhysicsScene* previous)
if (_staticActor != nullptr) if (_staticActor != nullptr)
{ {
PhysicsBackend::RemoveSceneActor(previous->GetPhysicsScene(), _staticActor); PhysicsBackend::RemoveSceneActor(previous->GetPhysicsScene(), _staticActor, true);
void* scene = GetPhysicsScene()->GetPhysicsScene(); void* scene = GetPhysicsScene()->GetPhysicsScene();
PhysicsBackend::AddSceneActor(scene, _staticActor); PhysicsBackend::AddSceneActor(scene, _staticActor);
} }

View File

@@ -52,7 +52,7 @@ public:
/// <summary> /// <summary>
/// Gets the center of the collider, measured in the object's local space. /// Gets the center of the collider, measured in the object's local space.
/// </summary> /// </summary>
API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(typeof(Vector3), \"0,0,0\"), EditorDisplay(\"Collider\")") API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(typeof(Vector3), \"0,0,0\"), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)")
FORCE_INLINE Vector3 GetCenter() const FORCE_INLINE Vector3 GetCenter() const
{ {
return _center; return _center;
@@ -66,7 +66,7 @@ public:
/// <summary> /// <summary>
/// Gets the contact offset. Colliders whose distance is less than the sum of their ContactOffset values will generate contacts. The contact offset must be positive. Contact offset allows the collision detection system to predictively enforce the contact constraint even when the objects are slightly separated. /// Gets the contact offset. Colliders whose distance is less than the sum of their ContactOffset values will generate contacts. The contact offset must be positive. Contact offset allows the collision detection system to predictively enforce the contact constraint even when the objects are slightly separated.
/// </summary> /// </summary>
API_PROPERTY(Attributes="EditorOrder(1), DefaultValue(2.0f), Limit(0, 100), EditorDisplay(\"Collider\")") API_PROPERTY(Attributes="EditorOrder(1), DefaultValue(2.0f), Limit(0, 100), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)")
FORCE_INLINE float GetContactOffset() const FORCE_INLINE float GetContactOffset() const
{ {
return _contactOffset; return _contactOffset;

View File

@@ -21,7 +21,7 @@ public:
/// Gets the radius of the sphere, measured in the object's local space. /// Gets the radius of the sphere, measured in the object's local space.
/// </summary> /// </summary>
/// <remarks>The sphere radius will be scaled by the actor's world scale.</remarks> /// <remarks>The sphere radius will be scaled by the actor's world scale.</remarks>
API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(50.0f), EditorDisplay(\"Collider\")") API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(50.0f), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)")
FORCE_INLINE float GetRadius() const FORCE_INLINE float GetRadius() const
{ {
return _radius; return _radius;

View File

@@ -67,7 +67,7 @@ public:
/// Gets the allowed minimum distance for the joint. /// Gets the allowed minimum distance for the joint.
/// </summary> /// </summary>
/// <remarks>Used only when DistanceJointFlag.MinDistance flag is set. The minimum distance must be no more than the maximum distance. Default: 0, Range: [0, float.MaxValue].</remarks> /// <remarks>Used only when DistanceJointFlag.MinDistance flag is set. The minimum distance must be no more than the maximum distance. Default: 0, Range: [0, float.MaxValue].</remarks>
API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(0.0f), Limit(0.0f), EditorDisplay(\"Joint\")") API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(0.0f), Limit(0.0f), EditorDisplay(\"Joint\"), ValueCategory(Utils.ValueCategory.Distance)")
FORCE_INLINE float GetMinDistance() const FORCE_INLINE float GetMinDistance() const
{ {
return _minDistance; return _minDistance;
@@ -83,7 +83,7 @@ public:
/// Gets the allowed maximum distance for the joint. /// Gets the allowed maximum distance for the joint.
/// </summary> /// </summary>
/// <remarks>Used only when DistanceJointFlag.MaxDistance flag is set. The maximum distance must be no less than the minimum distance. Default: 0, Range: [0, float.MaxValue].</remarks> /// <remarks>Used only when DistanceJointFlag.MaxDistance flag is set. The maximum distance must be no less than the minimum distance. Default: 0, Range: [0, float.MaxValue].</remarks>
API_PROPERTY(Attributes="EditorOrder(120), DefaultValue(10.0f), Limit(0.0f), EditorDisplay(\"Joint\")") API_PROPERTY(Attributes="EditorOrder(120), DefaultValue(10.0f), Limit(0.0f), EditorDisplay(\"Joint\"), ValueCategory(Utils.ValueCategory.Distance)")
FORCE_INLINE float GetMaxDistance() const FORCE_INLINE float GetMaxDistance() const
{ {
return _maxDistance; return _maxDistance;

View File

@@ -38,7 +38,7 @@ public:
/// <summary> /// <summary>
/// Gets the break force. Determines the maximum force the joint can apply before breaking. Broken joints no longer participate in physics simulation. /// Gets the break force. Determines the maximum force the joint can apply before breaking. Broken joints no longer participate in physics simulation.
/// </summary> /// </summary>
API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(float.MaxValue), EditorDisplay(\"Joint\")") API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(float.MaxValue), EditorDisplay(\"Joint\"), ValueCategory(Utils.ValueCategory.Force)")
FORCE_INLINE float GetBreakForce() const FORCE_INLINE float GetBreakForce() const
{ {
return _breakForce; return _breakForce;
@@ -52,7 +52,7 @@ public:
/// <summary> /// <summary>
/// Gets the break torque. Determines the maximum torque the joint can apply before breaking. Broken joints no longer participate in physics simulation. /// Gets the break torque. Determines the maximum torque the joint can apply before breaking. Broken joints no longer participate in physics simulation.
/// </summary> /// </summary>
API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(float.MaxValue), EditorDisplay(\"Joint\")") API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(float.MaxValue), EditorDisplay(\"Joint\"), ValueCategory(Utils.ValueCategory.Torque)")
FORCE_INLINE float GetBreakTorque() const FORCE_INLINE float GetBreakTorque() const
{ {
return _breakTorque; return _breakTorque;

View File

@@ -1998,11 +1998,14 @@ void PhysicsBackend::AddSceneActor(void* scene, void* actor)
FlushLocker.Unlock(); FlushLocker.Unlock();
} }
void PhysicsBackend::RemoveSceneActor(void* scene, void* actor) void PhysicsBackend::RemoveSceneActor(void* scene, void* actor, bool immediately)
{ {
auto scenePhysX = (ScenePhysX*)scene; auto scenePhysX = (ScenePhysX*)scene;
FlushLocker.Lock(); FlushLocker.Lock();
scenePhysX->RemoveActors.Add((PxActor*)actor); if (immediately)
scenePhysX->Scene->removeActor(*(PxActor*)actor);
else
scenePhysX->RemoveActors.Add((PxActor*)actor);
FlushLocker.Unlock(); FlushLocker.Unlock();
} }

View File

@@ -113,7 +113,7 @@ public:
static void SetSceneBounceThresholdVelocity(void* scene, float value); static void SetSceneBounceThresholdVelocity(void* scene, float value);
static void SetSceneOrigin(void* scene, const Vector3& oldOrigin, const Vector3& newOrigin); static void SetSceneOrigin(void* scene, const Vector3& oldOrigin, const Vector3& newOrigin);
static void AddSceneActor(void* scene, void* actor); static void AddSceneActor(void* scene, void* actor);
static void RemoveSceneActor(void* scene, void* actor); static void RemoveSceneActor(void* scene, void* actor, bool immediately = false);
static void AddSceneActorAction(void* scene, void* actor, ActionType action); static void AddSceneActorAction(void* scene, void* actor, ActionType action);
#if COMPILE_WITH_PROFILER #if COMPILE_WITH_PROFILER
static void GetSceneStatistics(void* scene, PhysicsStatistics& result); static void GetSceneStatistics(void* scene, PhysicsStatistics& result);

View File

@@ -115,7 +115,7 @@ void PhysicsBackend::AddSceneActor(void* scene, void* actor)
{ {
} }
void PhysicsBackend::RemoveSceneActor(void* scene, void* actor) void PhysicsBackend::RemoveSceneActor(void* scene, void* actor, bool immediately)
{ {
} }

View File

@@ -149,4 +149,7 @@ void TAA::Render(const RenderContext& renderContext, GPUTexture* input, GPUTextu
context->Draw(output); context->Draw(output);
renderContext.Buffers->TemporalAA = outputHistory; renderContext.Buffers->TemporalAA = outputHistory;
} }
// Mark TAA jitter as resolved for future drawing
(bool&)renderContext.View.IsTaaResolved = true;
} }

View File

@@ -652,6 +652,7 @@ void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsL
const auto* batchesData = list.Batches.Get(); const auto* batchesData = list.Batches.Get();
const auto context = GPUDevice::Instance->GetMainContext(); const auto context = GPUDevice::Instance->GetMainContext();
bool useInstancing = list.CanUseInstancing && CanUseInstancing(renderContext.View.Pass) && GPUDevice::Instance->Limits.HasInstancing; bool useInstancing = list.CanUseInstancing && CanUseInstancing(renderContext.View.Pass) && GPUDevice::Instance->Limits.HasInstancing;
TaaJitterRemoveContext taaJitterRemove(renderContext.View);
// Clear SR slots to prevent any resources binding issues (leftovers from the previous passes) // Clear SR slots to prevent any resources binding issues (leftovers from the previous passes)
context->ResetSR(); context->ResetSR();

View File

@@ -612,7 +612,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
// Color Grading LUT generation // Color Grading LUT generation
auto colorGradingLUT = ColorGradingPass::Instance()->RenderLUT(renderContext); auto colorGradingLUT = ColorGradingPass::Instance()->RenderLUT(renderContext);
// Post processing // Post-processing
EyeAdaptationPass::Instance()->Render(renderContext, frameBuffer); EyeAdaptationPass::Instance()->Render(renderContext, frameBuffer);
PostProcessingPass::Instance()->Render(renderContext, frameBuffer, tempBuffer, colorGradingLUT); PostProcessingPass::Instance()->Render(renderContext, frameBuffer, tempBuffer, colorGradingLUT);
RenderTargetPool::Release(colorGradingLUT); RenderTargetPool::Release(colorGradingLUT);

View File

@@ -0,0 +1,36 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
namespace FlaxEngine
{
/// <summary>
/// Specifies the value category of a numeric value as either as-is (a scalar), a distance (formatted as cm/m/km) or an angle (formatted with a degree sign).
/// </summary>
[Serializable]
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class ValueCategoryAttribute : Attribute
{
/// <summary>
/// The value category used for formatting.
/// </summary>
public Utils.ValueCategory Category;
/// <summary>
/// Initializes a new instance of the <see cref="ValueCategoryAttribute"/> class.
/// </summary>
private ValueCategoryAttribute()
{
Category = Utils.ValueCategory.None;
}
/// <summary>
/// Initializes a new instance of the <see cref="ValueCategoryAttribute"/> class.
/// </summary>
/// <param name="category">The value category.</param>
public ValueCategoryAttribute(Utils.ValueCategory category)
{
Category = category;
}
}
}

View File

@@ -2148,7 +2148,9 @@ bool TerrainPatch::CreateHeightField()
if (collisionHeader->CheckOldMagicNumber != MAX_int32 || collisionHeader->Version != TerrainCollisionDataHeader::CurrentVersion) if (collisionHeader->CheckOldMagicNumber != MAX_int32 || collisionHeader->Version != TerrainCollisionDataHeader::CurrentVersion)
{ {
// Reset height map // Reset height map
return InitializeHeightMap(); PROFILE_CPU_NAMED("ResetHeightMap");
const float* data = GetHeightmapData();
return SetupHeightMap(_cachedHeightMap.Count(), data);
} }
// Create heightfield object from the data // Create heightfield object from the data
@@ -2580,7 +2582,7 @@ void TerrainPatch::Deserialize(DeserializeStream& stream, ISerializeModifier* mo
void TerrainPatch::OnPhysicsSceneChanged(PhysicsScene* previous) void TerrainPatch::OnPhysicsSceneChanged(PhysicsScene* previous)
{ {
PhysicsBackend::RemoveSceneActor(previous->GetPhysicsScene(), _physicsActor); PhysicsBackend::RemoveSceneActor(previous->GetPhysicsScene(), _physicsActor, true);
void* scene = _terrain->GetPhysicsScene()->GetPhysicsScene(); void* scene = _terrain->GetPhysicsScene()->GetPhysicsScene();
PhysicsBackend::AddSceneActor(scene, _physicsActor); PhysicsBackend::AddSceneActor(scene, _physicsActor);
} }

View File

@@ -429,6 +429,13 @@ namespace FlaxEngine.GUI
return ContainsPoint(ref location); return ContainsPoint(ref location);
} }
/// <inheritdoc />
public override bool IntersectsChildContent(Control child, Float2 location, out Float2 childSpaceLocation)
{
location /= _scale;
return base.IntersectsChildContent(child, location, out childSpaceLocation);
}
/// <inheritdoc /> /// <inheritdoc />
public override bool ContainsPoint(ref Float2 location, bool precise = false) public override bool ContainsPoint(ref Float2 location, bool precise = false)
{ {
@@ -462,97 +469,6 @@ namespace FlaxEngine.GUI
return result; return result;
} }
/// <inheritdoc />
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
{
location /= _scale;
return base.OnDragEnter(ref location, data);
}
/// <inheritdoc />
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
{
location /= _scale;
return base.OnDragMove(ref location, data);
}
/// <inheritdoc />
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
{
location /= _scale;
return base.OnDragDrop(ref location, data);
}
/// <inheritdoc />
public override void OnMouseEnter(Float2 location)
{
location /= _scale;
base.OnMouseEnter(location);
}
/// <inheritdoc />
public override void OnMouseMove(Float2 location)
{
location /= _scale;
base.OnMouseMove(location);
}
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{
location /= _scale;
return base.OnMouseDown(location, button);
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
location /= _scale;
return base.OnMouseUp(location, button);
}
/// <inheritdoc />
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
{
location /= _scale;
return base.OnMouseDoubleClick(location, button);
}
/// <inheritdoc />
public override bool OnMouseWheel(Float2 location, float delta)
{
location /= _scale;
return base.OnMouseWheel(location, delta);
}
/// <inheritdoc />
public override void OnTouchEnter(Float2 location, int pointerId)
{
location /= _scale;
base.OnTouchEnter(location, pointerId);
}
/// <inheritdoc />
public override void OnTouchMove(Float2 location, int pointerId)
{
location /= _scale;
base.OnTouchMove(location, pointerId);
}
/// <inheritdoc />
public override bool OnTouchDown(Float2 location, int pointerId)
{
location /= _scale;
return base.OnTouchDown(location, pointerId);
}
/// <inheritdoc />
public override bool OnTouchUp(Float2 location, int pointerId)
{
location /= _scale;
return base.OnTouchUp(location, pointerId);
}
#endregion #endregion
} }
} }

View File

@@ -356,7 +356,7 @@ namespace FlaxEngine.GUI
for (int i = _children.Count - 1; i >= 0; i--) for (int i = _children.Count - 1; i >= 0; i--)
{ {
var child = _children[i]; var child = _children[i];
if (IntersectsChildContent(child, point, out var childLocation)) if (child.Visible && IntersectsChildContent(child, point, out var childLocation))
{ {
var containerControl = child as ContainerControl; var containerControl = child as ContainerControl;
var childAtRecursive = containerControl?.GetChildAtRecursive(childLocation); var childAtRecursive = containerControl?.GetChildAtRecursive(childLocation);

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.ComponentModel;
namespace FlaxEngine.GUI namespace FlaxEngine.GUI
{ {
@@ -382,6 +383,7 @@ namespace FlaxEngine.GUI
/// <summary> /// <summary>
/// Gets or sets the shear transform angles (x, y). Defined in degrees. Shearing happens relative to the control pivot point. /// Gets or sets the shear transform angles (x, y). Defined in degrees. Shearing happens relative to the control pivot point.
/// </summary> /// </summary>
[DefaultValue(typeof(Float2), "0,0")]
[ExpandGroups, EditorDisplay("Transform"), EditorOrder(1040), Tooltip("The shear transform angles (x, y). Defined in degrees. Shearing happens relative to the control pivot point.")] [ExpandGroups, EditorDisplay("Transform"), EditorOrder(1040), Tooltip("The shear transform angles (x, y). Defined in degrees. Shearing happens relative to the control pivot point.")]
public Float2 Shear public Float2 Shear
{ {
@@ -398,6 +400,7 @@ namespace FlaxEngine.GUI
/// <summary> /// <summary>
/// Gets or sets the rotation angle (in degrees). Control is rotated around it's pivot point (middle of the control by default). /// Gets or sets the rotation angle (in degrees). Control is rotated around it's pivot point (middle of the control by default).
/// </summary> /// </summary>
[DefaultValue(0.0f)]
[ExpandGroups, EditorDisplay("Transform"), EditorOrder(1050), Tooltip("The control rotation angle (in degrees). Control is rotated around it's pivot point (middle of the control by default).")] [ExpandGroups, EditorDisplay("Transform"), EditorOrder(1050), Tooltip("The control rotation angle (in degrees). Control is rotated around it's pivot point (middle of the control by default).")]
public float Rotation public float Rotation
{ {

View File

@@ -257,6 +257,7 @@ namespace FlaxEngine
{ {
public static FieldInfo itemsField; public static FieldInfo itemsField;
} }
internal static T[] ExtractArrayFromList<T>(List<T> list) internal static T[] ExtractArrayFromList<T>(List<T> list)
{ {
if (list == null) if (list == null)
@@ -1038,5 +1039,66 @@ namespace FlaxEngine
parameterTypes = Array.Empty<Type>(); parameterTypes = Array.Empty<Type>();
return parameterTypes; return parameterTypes;
} }
/// <summary>
/// A category of number values used for formatting and input fields.
/// </summary>
public enum ValueCategory
{
/// <summary>
/// Nothing.
/// </summary>
None,
/// <summary>
/// Distance (eg. meters).
/// </summary>
Distance,
/// <summary>
/// Area (eg. m^2).
/// </summary>
Area,
/// <summary>
/// Volume (eg. m^3).
/// </summary>
Volume,
/// <summary>
/// Mass (eg. kilograms).
/// </summary>
Mass,
/// <summary>
/// Angle (eg. degrees).
/// </summary>
Angle,
/// <summary>
/// Speed (distance / time).
/// </summary>
Speed,
/// <summary>
/// Acceleration (distance^2 / time).
/// </summary>
Acceleration,
/// <summary>
/// Time (eg. seconds).
/// </summary>
Time,
/// <summary>
/// Force (mass * distance / time^2).
/// </summary>
Force,
/// <summary>
/// Torque (mass * distance^2 / time^2).
/// </summary>
Torque,
}
} }
} }

View File

@@ -4,7 +4,8 @@
META_CB_BEGIN(0, Data) META_CB_BEGIN(0, Data)
float4x4 ViewProjection; float4x4 ViewProjection;
float3 Padding; float2 Padding;
float ClipPosZBias;
bool EnableDepthTest; bool EnableDepthTest;
META_CB_END META_CB_END
@@ -23,6 +24,7 @@ VS2PS VS(float3 Position : POSITION, float4 Color : COLOR)
{ {
VS2PS output; VS2PS output;
output.Position = mul(float4(Position, 1), ViewProjection); output.Position = mul(float4(Position, 1), ViewProjection);
output.Position.z += ClipPosZBias;
output.Color = Color; output.Color = Color;
return output; return output;
} }

View File

@@ -24,7 +24,7 @@ namespace Flax.Build.Bindings
private static readonly List<PropertyInfo> CppAutoSerializeProperties = new List<PropertyInfo>(); private static readonly List<PropertyInfo> CppAutoSerializeProperties = new List<PropertyInfo>();
public static readonly HashSet<string> CppIncludeFiles = new HashSet<string>(); public static readonly HashSet<string> CppIncludeFiles = new HashSet<string>();
private static readonly List<string> CppIncludeFilesList = new List<string>(); private static readonly List<string> CppIncludeFilesList = new List<string>();
private static readonly HashSet<TypeInfo> CppVariantToTypes = new HashSet<TypeInfo>(); private static readonly Dictionary<string, TypeInfo> CppVariantToTypes = new Dictionary<string, TypeInfo>();
private static readonly Dictionary<string, TypeInfo> CppVariantFromTypes = new Dictionary<string, TypeInfo>(); private static readonly Dictionary<string, TypeInfo> CppVariantFromTypes = new Dictionary<string, TypeInfo>();
private static bool CppNonPodTypesConvertingGeneration = false; private static bool CppNonPodTypesConvertingGeneration = false;
private static StringBuilder CppContentsEnd; private static StringBuilder CppContentsEnd;
@@ -231,13 +231,15 @@ namespace Flax.Build.Bindings
throw new Exception($"Not supported type to convert from the Variant to fixed-size array '{typeInfo}[{typeInfo.ArraySize}]'."); throw new Exception($"Not supported type to convert from the Variant to fixed-size array '{typeInfo}[{typeInfo.ArraySize}]'.");
if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null) if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null)
{ {
CppVariantToTypes.Add(typeInfo); var wrapperName = GenerateCppWrapperNativeToVariantMethodName(typeInfo);
return $"MoveTemp(VariantTo{GenerateCppWrapperNativeToVariantMethodName(typeInfo)}({value}))"; CppVariantToTypes[wrapperName] = typeInfo;
return $"MoveTemp(VariantTo{wrapperName}({value}))";
} }
if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null) if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null)
{ {
CppVariantToTypes.Add(typeInfo); var wrapperName = GenerateCppWrapperNativeToVariantMethodName(typeInfo);
return $"MoveTemp(VariantTo{GenerateCppWrapperNativeToVariantMethodName(typeInfo)}({value}))"; CppVariantToTypes[wrapperName] = typeInfo;
return $"MoveTemp(VariantTo{wrapperName}({value}))";
} }
if (typeInfo.Type == "Span" && typeInfo.GenericArgs != null) if (typeInfo.Type == "Span" && typeInfo.GenericArgs != null)
{ {
@@ -2790,12 +2792,14 @@ namespace Flax.Build.Bindings
var header = GetStringBuilder(); var header = GetStringBuilder();
// Variant converting helper methods // Variant converting helper methods
foreach (var typeInfo in CppVariantToTypes) foreach (var e in CppVariantToTypes)
{ {
var wrapperName = e.Key;
var typeInfo = e.Value;
var name = typeInfo.ToString(false); var name = typeInfo.ToString(false);
header.AppendLine(); header.AppendLine();
header.AppendLine("namespace {"); header.AppendLine("namespace {");
header.Append($"{name} VariantTo{GenerateCppWrapperNativeToVariantMethodName(typeInfo)}(const Variant& v)").AppendLine(); header.Append($"{name} VariantTo{wrapperName}(const Variant& v)").AppendLine();
header.Append('{').AppendLine(); header.Append('{').AppendLine();
header.Append($" {name} result;").AppendLine(); header.Append($" {name} result;").AppendLine();
if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null) if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null)

View File

@@ -839,10 +839,11 @@ namespace Flax.Build.Plugins
module.GetType("System.IntPtr", out var intPtrType); module.GetType("System.IntPtr", out var intPtrType);
module.GetType("FlaxEngine.Object", out var scriptingObjectType); module.GetType("FlaxEngine.Object", out var scriptingObjectType);
var fromUnmanagedPtr = scriptingObjectType.Resolve().GetMethod("FromUnmanagedPtr"); var fromUnmanagedPtr = scriptingObjectType.Resolve().GetMethod("FromUnmanagedPtr");
TypeReference intPtr = module.ImportReference(intPtrType);
var m = new MethodDefinition(name + "Native", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, context.VoidType); var m = new MethodDefinition(name + "Native", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, context.VoidType);
m.Parameters.Add(new ParameterDefinition("instancePtr", ParameterAttributes.None, intPtrType)); m.Parameters.Add(new ParameterDefinition("instancePtr", ParameterAttributes.None, intPtr));
m.Parameters.Add(new ParameterDefinition("streamPtr", ParameterAttributes.None, intPtrType)); m.Parameters.Add(new ParameterDefinition("streamPtr", ParameterAttributes.None, intPtr));
TypeReference networkStream = module.ImportReference(context.NetworkStreamType); TypeReference networkStream = module.ImportReference(context.NetworkStreamType);
ILProcessor il = m.Body.GetILProcessor(); ILProcessor il = m.Body.GetILProcessor();
il.Emit(OpCodes.Nop); il.Emit(OpCodes.Nop);
@@ -1645,12 +1646,13 @@ namespace Flax.Build.Plugins
module.GetType("FlaxEngine.Object", out var scriptingObjectType); module.GetType("FlaxEngine.Object", out var scriptingObjectType);
var fromUnmanagedPtr = scriptingObjectType.Resolve().GetMethod("FromUnmanagedPtr"); var fromUnmanagedPtr = scriptingObjectType.Resolve().GetMethod("FromUnmanagedPtr");
TypeReference networkStream = module.ImportReference(networkStreamType); TypeReference networkStream = module.ImportReference(networkStreamType);
TypeReference intPtr = module.ImportReference(intPtrType);
// Generate static method to execute RPC locally // Generate static method to execute RPC locally
{ {
var m = new MethodDefinition(method.Name + "_Execute", MethodAttributes.Static | MethodAttributes.Assembly | MethodAttributes.HideBySig, voidType); var m = new MethodDefinition(method.Name + "_Execute", MethodAttributes.Static | MethodAttributes.Assembly | MethodAttributes.HideBySig, voidType);
m.Parameters.Add(new ParameterDefinition("instancePtr", ParameterAttributes.None, intPtrType)); m.Parameters.Add(new ParameterDefinition("instancePtr", ParameterAttributes.None, intPtr));
m.Parameters.Add(new ParameterDefinition("streamPtr", ParameterAttributes.None, module.ImportReference(intPtrType))); m.Parameters.Add(new ParameterDefinition("streamPtr", ParameterAttributes.None, intPtr));
ILProcessor ilp = m.Body.GetILProcessor(); ILProcessor ilp = m.Body.GetILProcessor();
var il = new DotnetIlContext(ilp, method); var il = new DotnetIlContext(ilp, method);
il.Emit(OpCodes.Nop); il.Emit(OpCodes.Nop);