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

# Conflicts:
#	Source/Editor/GUI/Dialogs/ColorPickerDialog.cs
#	Source/Editor/GUI/Dialogs/ColorSelector.cs
This commit is contained in:
2026-03-24 23:41:58 +01:00
36 changed files with 725 additions and 431 deletions
Binary file not shown.
+5
View File
@@ -69,6 +69,11 @@ namespace FlaxEditor
/// </summary>
public static string WindowIcon = "Editor/EditorIcon";
/// <summary>
/// The material used for the HS color wheel.
/// </summary>
public static string HSWheelMaterial = "Editor/HSWheel";
/// <summary>
/// The window icons font.
/// </summary>
+154 -114
View File
@@ -1,10 +1,11 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using FlaxEditor.GUI.Input;
using FlaxEditor.GUI.Tabs;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;
using System.Collections.Generic;
namespace FlaxEditor.GUI.Dialogs
{
@@ -25,15 +26,16 @@ namespace FlaxEditor.GUI.Dialogs
/// <seealso cref="FlaxEditor.GUI.Dialogs.Dialog" />
public class ColorPickerDialog : Dialog, IColorPickerDialog
{
private const float ButtonsWidth = 60.0f;
private const float PickerMargin = 6.0f;
private const float EyedropperMargin = 8.0f;
private const float RGBAMargin = 12.0f;
private const float HSVMargin = 0.0f;
private const float ChannelsMargin = 4.0f;
private const float ChannelTextWidth = 12.0f;
private const float SavedColorButtonWidth = 20.0f;
private const float SavedColorButtonHeight = 20.0f;
private const float TabHeight = 20;
private const float ValueBoxesWidth = 100.0f;
private const float HSVRGBTextWidth = 15.0f;
private const float ColorPreviewHeight = 50.0f;
private const int SavedColorsAmount = 10;
private Color _initialValue;
private Color _value;
@@ -46,16 +48,19 @@ namespace FlaxEditor.GUI.Dialogs
private ColorValueBox.ColorPickerClosedEvent _onClosed;
private ColorSelectorWithSliders _cSelector;
private Tabs.Tabs _hsvRGBTabs;
private Tab _RGBTab;
private Panel _rgbPanel;
private FloatValueBox _cRed;
private FloatValueBox _cGreen;
private FloatValueBox _cBlue;
private FloatValueBox _cAlpha;
private Tab _hsvTab;
private Panel _hsvPanel;
private FloatValueBox _cHue;
private FloatValueBox _cSaturation;
private FloatValueBox _cValue;
private TextBox _cHex;
private Button _cCancel;
private Button _cOK;
private FloatValueBox _cAlpha;
private Button _cEyedropper;
private Button _cLinearSRGB;
@@ -127,97 +132,110 @@ namespace FlaxEditor.GUI.Dialogs
_savedColors = JsonSerializer.Deserialize<List<Color>>(savedColors);
// Selector
_cSelector = new ColorSelectorWithSliders(180, 18)
_cSelector = new ColorSelectorWithSliders(180, 21)
{
Location = new Float2(PickerMargin, PickerMargin),
Parent = this
};
_cSelector.ColorChanged += x => SelectedColor = x;
// Red
_cRed = new FloatValueBox(0, _cSelector.Right + PickerMargin + RGBAMargin + ChannelTextWidth, PickerMargin, 100, 0, float.MaxValue, 0.001f)
_hsvRGBTabs = new Tabs.Tabs
{
Parent = this
Location = new Float2(_cSelector.Right + 30.0f, PickerMargin),
TabsTextHorizontalAlignment = TextAlignment.Center,
Width = ValueBoxesWidth + HSVRGBTextWidth * 2.0f + ChannelsMargin * 2.0f,
Height = (FloatValueBox.DefaultHeight + ChannelsMargin) * 4 + ChannelsMargin,
Parent = this,
};
_hsvRGBTabs.TabsSize = new Float2(_hsvRGBTabs.Width * 0.5f, TabHeight);
_hsvRGBTabs.SelectedTabChanged += SelectedTabChanged;
// RGB Tab
_RGBTab = _hsvRGBTabs.AddTab(new Tab("RGB"));
_rgbPanel = new Panel(ScrollBars.Vertical)
{
AnchorPreset = AnchorPresets.StretchAll,
Offsets = Margin.Zero,
Parent = _RGBTab,
};
// HSV Tab
_hsvTab = _hsvRGBTabs.AddTab(new Tab("HSV"));
_hsvPanel = new Panel(ScrollBars.Vertical)
{
AnchorPreset = AnchorPresets.StretchAll,
Offsets = Margin.Zero,
Parent = _hsvTab,
};
// Red
_cRed = new FloatValueBox(0, HSVRGBTextWidth + ChannelsMargin, PickerMargin, ValueBoxesWidth, 0, float.MaxValue, 0.001f)
{
Parent = _rgbPanel,
};
_cRed.ValueChanged += OnRGBAChanged;
// Green
_cGreen = new FloatValueBox(0, _cRed.X, _cRed.Bottom + ChannelsMargin, _cRed.Width, 0, float.MaxValue, 0.001f)
{
Parent = this
Parent = _rgbPanel,
};
_cGreen.ValueChanged += OnRGBAChanged;
// Blue
_cBlue = new FloatValueBox(0, _cRed.X, _cGreen.Bottom + ChannelsMargin, _cRed.Width, 0, float.MaxValue, 0.001f)
{
Parent = this
Parent = _rgbPanel,
};
_cBlue.ValueChanged += OnRGBAChanged;
// Alpha
_cAlpha = new FloatValueBox(0, _cRed.X, _cBlue.Bottom + ChannelsMargin, _cRed.Width, 0, float.MaxValue, 0.001f)
{
Parent = this
};
_cAlpha.ValueChanged += OnRGBAChanged;
// Hue
_cHue = new FloatValueBox(0, PickerMargin + HSVMargin + ChannelTextWidth, _cSelector.Bottom + PickerMargin, 100, 0, 360)
_cHue = new FloatValueBox(0, HSVRGBTextWidth + ChannelsMargin, PickerMargin, ValueBoxesWidth)
{
Parent = this
Parent = _hsvPanel,
Category = Utils.ValueCategory.Angle,
};
_cHue.ValueChanged += OnHSVChanged;
// Saturation
_cSaturation = new FloatValueBox(0, _cHue.X, _cHue.Bottom + ChannelsMargin, _cHue.Width, 0, 100.0f, 0.1f)
{
Parent = this
Parent = _hsvPanel,
};
_cSaturation.ValueChanged += OnHSVChanged;
// Value
_cValue = new FloatValueBox(0, _cHue.X, _cSaturation.Bottom + ChannelsMargin, _cHue.Width, 0, float.MaxValue, 0.1f)
{
Parent = this
Parent = _hsvPanel,
};
_cValue.ValueChanged += OnHSVChanged;
// Set valid dialog size based on UI content
_dialogSize = Size = new Float2(_cRed.Right + PickerMargin, 300);
// Alpha
_cAlpha = new FloatValueBox(0, _hsvRGBTabs.Left + HSVRGBTextWidth + ChannelsMargin, _hsvRGBTabs.Bottom + ChannelsMargin * 4.0f, ValueBoxesWidth, 0, float.MaxValue, 0.001f)
{
Parent = this,
};
_cAlpha.ValueChanged += OnRGBAChanged;
// Hex
const float hexTextBoxWidth = 80;
_cHex = new TextBox(false, Width - hexTextBoxWidth - PickerMargin, _cSelector.Bottom + PickerMargin, hexTextBoxWidth)
_cHex = new TextBox(false, _hsvRGBTabs.Left + HSVRGBTextWidth + ChannelsMargin, _cAlpha.Bottom + ChannelsMargin * 2.0f, ValueBoxesWidth)
{
Parent = this
Parent = this,
};
_cHex.EditEnd += OnHexChanged;
// Cancel
_cCancel = new Button(Width - ButtonsWidth - PickerMargin, Height - Button.DefaultHeight - PickerMargin, ButtonsWidth)
{
Text = "Cancel",
Parent = this
};
_cCancel.Clicked += OnCancel;
// OK
_cOK = new Button(_cCancel.Left - ButtonsWidth - PickerMargin, _cCancel.Y, ButtonsWidth)
{
Text = "Ok",
Parent = this
};
_cOK.Clicked += OnSubmit;
// Set valid dialog size based on UI content
_dialogSize = Size = new Float2(_hsvRGBTabs.Right + PickerMargin, _cHex.Bottom + 40.0f + ColorPreviewHeight + PickerMargin);
// Create saved color buttons
CreateAllSaveButtons();
CreateAllSavedColorsButtons();
// Eyedropper button
var style = Style.Current;
_cEyedropper = new Button(_cOK.X - EyedropperMargin, _cHex.Bottom + PickerMargin)
_cEyedropper = new Button(_cSelector.BottomLeft.X, _cSelector.BottomLeft.Y - 25.0f, 25.0f, 25.0f)
{
TooltipText = "Eyedropper tool to pick a color directly from the screen",
TooltipText = "Eyedropper tool to pick a color directly from the screen.",
BackgroundBrush = new SpriteBrush(Editor.Instance.Icons.Search32),
BackgroundColor = style.Foreground,
BackgroundColorHighlighted = style.Foreground.RGBMultiplied(0.9f),
@@ -226,14 +244,11 @@ namespace FlaxEditor.GUI.Dialogs
Parent = this,
};
_cEyedropper.Clicked += OnEyedropStart;
_cEyedropper.Height = (_cValue.Bottom - _cEyedropper.Y) * 0.5f;
_cEyedropper.Width = _cEyedropper.Height;
_cEyedropper.X -= _cEyedropper.Width;
// Linear/sRGB toggle button
_cLinearSRGB = new Button(_cOK.X - EyedropperMargin, _cHex.Bottom + PickerMargin)
_cLinearSRGB = new Button(_cSelector.X, _cHex.Bottom + PickerMargin)
{
TooltipText = "Toggles between color preview in Linear and sRGB",
TooltipText = "Toggles between color preview in Linear and sRGB.",
BackgroundBrush = new SpriteBrush(Editor.Instance.Icons.SplineAligned64),
BackgroundColor = _cEyedropper.BackgroundColor,
BackgroundColorHighlighted = _cEyedropper.BackgroundColorHighlighted,
@@ -261,12 +276,10 @@ namespace FlaxEditor.GUI.Dialogs
foreach (var color in _savedColors)
{
if (color == _value)
{
return;
}
}
// Set color of button to current value;
// Set color of button to current value
button.BackgroundColor = _value;
button.BackgroundColorHighlighted = _value;
button.BackgroundColorSelected = _value.RGBMultiplied(0.8f);
@@ -278,10 +291,10 @@ namespace FlaxEditor.GUI.Dialogs
var savedColors = JsonSerializer.Serialize(_savedColors, typeof(List<Color>));
Editor.Instance.ProjectCache.SetCustomData("ColorPickerSavedColors", savedColors);
// create new + button
if (_savedColorButtons.Count < 8)
// Create new + button
if (_savedColorButtons.Count < SavedColorsAmount)
{
var savedColorButton = new Button(PickerMargin * (_savedColorButtons.Count + 1) + SavedColorButtonWidth * _savedColorButtons.Count, Height - SavedColorButtonHeight - PickerMargin, SavedColorButtonWidth, SavedColorButtonHeight)
var savedColorButton = new Button(PickerMargin * (_savedColorButtons.Count + 1) + SavedColorButtonWidth * _savedColorButtons.Count, _cHex.Bottom + 40.0f + ColorPreviewHeight * 0.5f, SavedColorButtonWidth, SavedColorButtonHeight)
{
Text = "+",
Parent = this,
@@ -298,11 +311,32 @@ namespace FlaxEditor.GUI.Dialogs
}
}
private void SelectedTabChanged(Tabs.Tabs tabs)
{
if (_rgbPanel == null || _hsvPanel == null)
return;
switch (tabs.SelectedTabIndex)
{
// RGB
case 0:
_rgbPanel.Visible = true;
_hsvPanel.Visible = false;
break;
// HSV
case 1:
_rgbPanel.Visible = false;
_hsvPanel.Visible = true;
break;
}
}
private void OnColorPicked(Color32 colorPicked)
{
if (_activeEyedropper)
{
_activeEyedropper = false;
_cEyedropper.BackgroundColor = _cEyedropper.BackgroundColorHighlighted = Style.Current.Foreground;
if (colorPicked != Color.Transparent)
{
Color color = colorPicked;
@@ -317,6 +351,7 @@ namespace FlaxEditor.GUI.Dialogs
private void OnEyedropStart()
{
_activeEyedropper = true;
_cEyedropper.BackgroundColor = _cEyedropper.BackgroundColorHighlighted = Style.Current.BackgroundHighlighted;
Platform.PickScreenColor();
Platform.PickScreenColorDone += OnColorPicked;
}
@@ -334,6 +369,7 @@ namespace FlaxEditor.GUI.Dialogs
if (_disableEvents)
return;
_cHue.Value = Mathf.Wrap(_cHue.Value, 0f, 360f);
SelectedColor = Color.FromHSV(_cHue.Value, _cSaturation.Value / 100.0f, _cValue.Value / 100.0f, _cAlpha.Value);
}
@@ -374,64 +410,76 @@ namespace FlaxEditor.GUI.Dialogs
base.Draw();
// RGBA
var rgbaR = new Rectangle(_cRed.Left - ChannelTextWidth, _cRed.Y, 10000, _cRed.Height);
Render2D.DrawText(style.FontMedium, "R", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center);
rgbaR.Location.Y = _cGreen.Y;
Render2D.DrawText(style.FontMedium, "G", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center);
rgbaR.Location.Y = _cBlue.Y;
Render2D.DrawText(style.FontMedium, "B", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center);
rgbaR.Location.Y = _cAlpha.Y;
Render2D.DrawText(style.FontMedium, "A", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center);
switch (_hsvRGBTabs.SelectedTabIndex)
{
// RGB
case 0:
var rgbRect = new Rectangle(_hsvRGBTabs.Left + PickerMargin, _hsvRGBTabs.Top + TabHeight + PickerMargin, 10000, _cRed.Height);
Render2D.DrawText(style.FontMedium, "R", rgbRect, textColor, TextAlignment.Near, TextAlignment.Center);
rgbRect.Location.Y += _cRed.Height + ChannelsMargin;
Render2D.DrawText(style.FontMedium, "G", rgbRect, textColor, TextAlignment.Near, TextAlignment.Center);
rgbRect.Location.Y += _cRed.Height + ChannelsMargin;
Render2D.DrawText(style.FontMedium, "B", rgbRect, textColor, TextAlignment.Near, TextAlignment.Center);
break;
// HSV
case 1:
// Left
var hsvLeftRect = new Rectangle(_hsvRGBTabs.Left + PickerMargin, _hsvRGBTabs.Top + TabHeight + PickerMargin, 10000, _cHue.Height);
Render2D.DrawText(style.FontMedium, "H", hsvLeftRect, textColor, TextAlignment.Near, TextAlignment.Center);
hsvLeftRect.Location.Y += _cHue.Height + ChannelsMargin;
Render2D.DrawText(style.FontMedium, "S", hsvLeftRect, textColor, TextAlignment.Near, TextAlignment.Center);
hsvLeftRect.Location.Y += _cHue.Height + ChannelsMargin;
Render2D.DrawText(style.FontMedium, "V", hsvLeftRect, textColor, TextAlignment.Near, TextAlignment.Center);
// HSV left
var hsvHl = new Rectangle(_cHue.Left - ChannelTextWidth, _cHue.Y, 10000, _cHue.Height);
Render2D.DrawText(style.FontMedium, "H", hsvHl, textColor, TextAlignment.Near, TextAlignment.Center);
hsvHl.Location.Y = _cSaturation.Y;
Render2D.DrawText(style.FontMedium, "S", hsvHl, textColor, TextAlignment.Near, TextAlignment.Center);
hsvHl.Location.Y = _cValue.Y;
Render2D.DrawText(style.FontMedium, "V", hsvHl, textColor, TextAlignment.Near, TextAlignment.Center);
// Right
var hsvRightRect = new Rectangle(_hsvRGBTabs.Right - HSVRGBTextWidth, _hsvRGBTabs.Top + TabHeight + PickerMargin, ChannelTextWidth, _cHue.Height);
Render2D.DrawText(style.FontMedium, "°", hsvRightRect, textColor, TextAlignment.Near, TextAlignment.Center);
hsvRightRect.Location.Y += _cHue.Height + ChannelsMargin;
Render2D.DrawText(style.FontMedium, "%", hsvRightRect, textColor, TextAlignment.Near, TextAlignment.Center);
hsvRightRect.Location.Y += _cHue.Height + ChannelsMargin;
Render2D.DrawText(style.FontMedium, "%", hsvRightRect, textColor, TextAlignment.Near, TextAlignment.Center);
break;
}
// HSV right
var hsvHr = new Rectangle(_cHue.Right + 2, _cHue.Y, 10000, _cHue.Height);
Render2D.DrawText(style.FontMedium, "°", hsvHr, textColor, TextAlignment.Near, TextAlignment.Center);
hsvHr.Location.Y = _cSaturation.Y;
Render2D.DrawText(style.FontMedium, "%", hsvHr, textColor, TextAlignment.Near, TextAlignment.Center);
hsvHr.Location.Y = _cValue.Y;
Render2D.DrawText(style.FontMedium, "%", hsvHr, textColor, TextAlignment.Near, TextAlignment.Center);
// A
var alphaHexRect = new Rectangle(_hsvRGBTabs.Left + PickerMargin, _cAlpha.Top, ChannelTextWidth, _cAlpha.Height);
Render2D.DrawText(style.FontMedium, "A", alphaHexRect, textColor, TextAlignment.Near, TextAlignment.Center);
// Hex
var hex = new Rectangle(_cHex.Left - 26, _cHex.Y, 10000, _cHex.Height);
Render2D.DrawText(style.FontMedium, "Hex", hex, textColor, TextAlignment.Near, TextAlignment.Center);
alphaHexRect.Y += _cAlpha.Height + ChannelsMargin * 2.0f;
alphaHexRect.X -= 5.0f; // "Hex" is two characters wider than the other labels so we need to adjust for that
Render2D.DrawText(style.FontMedium, "Hex", alphaHexRect, textColor, TextAlignment.Far, TextAlignment.Center);
// Color difference
var newRect = new Rectangle(_cOK.X - 3, _cHex.Bottom + PickerMargin, 130, 0);
newRect.Size.Y = 50;
Render2D.FillRectangle(newRect, Color.White);
var smallRectSize = 10;
var numHor = Mathf.FloorToInt(newRect.Width / smallRectSize);
var numVer = Mathf.FloorToInt(newRect.Height / smallRectSize);
var differenceRect = new Rectangle(_hsvRGBTabs.Left, _cHex.Bottom + 40.0f, _hsvRGBTabs.Width, ColorPreviewHeight);
// Draw checkerboard for background of color to help with transparency
Render2D.FillRectangle(differenceRect, Color.White);
var smallRectSize = 10;
var numHor = Mathf.CeilToInt(differenceRect.Width / smallRectSize);
var numVer = Mathf.CeilToInt(differenceRect.Height / smallRectSize);
Render2D.PushClip(differenceRect);
for (int i = 0; i < numHor; i++)
{
for (int j = 0; j < numVer; j++)
{
if ((i + j) % 2 == 0)
{
var rect = new Rectangle(newRect.X + smallRectSize * i, newRect.Y + smallRectSize * j, new Float2(smallRectSize));
var rect = new Rectangle(differenceRect.X + smallRectSize * i, differenceRect.Y + smallRectSize * j, new Float2(smallRectSize));
Render2D.FillRectangle(rect, Color.Gray);
}
}
}
Render2D.FillRectangle(newRect, _linear ? _value.ToSRgb() : _value);
Render2D.PopClip();
Render2D.FillRectangle(differenceRect, _linear ? _value.ToSRgb() : _value);
}
/// <inheritdoc />
protected override void OnShow()
{
// Auto cancel on lost focus
// Apply changes on lost focus
#if !PLATFORM_LINUX
((WindowRootControl)Root).Window.LostFocus += OnWindowLostFocus;
((WindowRootControl)Root).Window.LostFocus += OnSubmit;
#endif
base.OnShow();
@@ -444,6 +492,7 @@ namespace FlaxEditor.GUI.Dialogs
{
// Cancel eye dropping
_activeEyedropper = false;
_cEyedropper.BackgroundColor = _cEyedropper.BackgroundColorHighlighted = Style.Current.Foreground;
Platform.PickScreenColorDone -= OnColorPicked;
return true;
}
@@ -455,9 +504,7 @@ namespace FlaxEditor.GUI.Dialogs
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (base.OnMouseUp(location, button))
{
return true;
}
var child = GetChildAtRecursive(location);
if (button == MouseButton.Right && child is Button b && b.Tag is Color c)
@@ -519,20 +566,20 @@ namespace FlaxEditor.GUI.Dialogs
}
_savedColorButtons.Clear();
CreateAllSaveButtons();
CreateAllSavedColorsButtons();
// Save new colors
var savedColors = JsonSerializer.Serialize(_savedColors, typeof(List<Color>));
Editor.Instance.ProjectCache.SetCustomData("ColorPickerSavedColors", savedColors);
}
private void CreateAllSaveButtons()
private void CreateAllSavedColorsButtons()
{
// Create saved color buttons
for (int i = 0; i < _savedColors.Count; i++)
{
var savedColor = _savedColors[i];
var savedColorButton = new Button(PickerMargin * (i + 1) + SavedColorButtonWidth * i, Height - SavedColorButtonHeight - PickerMargin, SavedColorButtonWidth, SavedColorButtonHeight)
var savedColorButton = new Button(PickerMargin * (i + 1) + SavedColorButtonWidth * i, _cHex.Bottom + 40.0f + ColorPreviewHeight * 0.5f, SavedColorButtonWidth, SavedColorButtonHeight)
{
Parent = this,
Tag = savedColor,
@@ -543,9 +590,9 @@ namespace FlaxEditor.GUI.Dialogs
savedColorButton.ButtonClicked += OnSavedColorButtonClicked;
_savedColorButtons.Add(savedColorButton);
}
if (_savedColors.Count < 8)
if (_savedColors.Count < SavedColorsAmount)
{
var savedColorButton = new Button(PickerMargin * (_savedColors.Count + 1) + SavedColorButtonWidth * _savedColors.Count, Height - SavedColorButtonHeight - PickerMargin, SavedColorButtonWidth, SavedColorButtonHeight)
var savedColorButton = new Button(PickerMargin * (_savedColors.Count + 1) + SavedColorButtonWidth * _savedColors.Count, _cHex.Bottom + 40.0f + ColorPreviewHeight * 0.5f, SavedColorButtonWidth, SavedColorButtonHeight)
{
Text = "+",
Parent = this,
@@ -557,19 +604,6 @@ namespace FlaxEditor.GUI.Dialogs
}
}
private void OnWindowLostFocus()
{
// Auto apply color on defocus
var autoAcceptColorPickerChange = Editor.Instance.Options.Options.Interface.AutoAcceptColorPickerChange;
if (_useDynamicEditing && _initialValue != _value && _canPassLastChangeEvent && autoAcceptColorPickerChange)
{
_canPassLastChangeEvent = false;
_onChanged?.Invoke(_value, false);
}
OnCancel();
}
/// <inheritdoc />
public override void OnSubmit()
{
@@ -577,6 +611,9 @@ namespace FlaxEditor.GUI.Dialogs
return;
_disableEvents = true;
// Ensure the cursor is restored
Cursor = CursorType.Default;
// Send color event if modified
if (_value != _initialValue)
{
@@ -593,6 +630,9 @@ namespace FlaxEditor.GUI.Dialogs
return;
_disableEvents = true;
// Ensure the cursor is restored
Cursor = CursorType.Default;
// Restore color if modified
if (_useDynamicEditing && _initialValue != _value && _canPassLastChangeEvent)
{
+114 -56
View File
@@ -12,6 +12,13 @@ namespace FlaxEditor.GUI.Dialogs
/// <seealso cref="FlaxEngine.GUI.ContainerControl" />
public class ColorSelector : ContainerControl
{
private const String GrayedOutParamName = "GrayedOut";
/// <summary>
/// Offset value applied to mouse cursor position when the user lets go of wheel or sliders.
/// </summary>
protected const float MouseCursorOffset = 6.0f;
/// <summary>
/// The color.
/// </summary>
@@ -22,9 +29,22 @@ namespace FlaxEditor.GUI.Dialogs
/// </summary>
protected Rectangle _wheelRect;
private readonly SpriteHandle _colorWheelSprite;
private readonly MaterialBase _hsWheelMaterial;
private bool _isMouseDownWheel;
private Rectangle wheelDragRect
{
get
{
var hsv = _color.ToHSV();
float hAngle = hsv.X * Mathf.DegreesToRadians;
float hRadius = hsv.Y * _wheelRect.Width * 0.5f;
var hsPos = new Float2(hRadius * Mathf.Cos(hAngle), -hRadius * Mathf.Sin(hAngle));
float wheelBoxSize = IsSliding ? 9.0f : 5.0f;
return new Rectangle(hsPos - (wheelBoxSize * 0.5f) + _wheelRect.Center, new Float2(wheelBoxSize));
}
}
/// <summary>
/// Occurs when selected color gets changed.
/// </summary>
@@ -78,7 +98,8 @@ namespace FlaxEditor.GUI.Dialogs
{
AutoFocus = true;
_colorWheelSprite = Editor.Instance.Icons.ColorWheel128;
_hsWheelMaterial = FlaxEngine.Content.LoadAsyncInternal<MaterialBase>(EditorAssets.HSWheelMaterial);
_hsWheelMaterial = _hsWheelMaterial.CreateVirtualInstance();
_wheelRect = new Rectangle(0, 0, wheelSize, wheelSize);
}
@@ -165,17 +186,19 @@ namespace FlaxEditor.GUI.Dialogs
{
base.Draw();
var hsv = _color.ToHSV();
bool enabled = EnabledInHierarchy;
_hsWheelMaterial.SetParameterValue(GrayedOutParamName, enabled ? 1.0f : 0.5f);
Render2D.DrawMaterial(_hsWheelMaterial, _wheelRect, enabled ? Color.White : Color.Gray);
// Wheel
float boxExpand = (2.0f * 4.0f / 128.0f) * _wheelRect.Width;
Render2D.DrawSprite(_colorWheelSprite, _wheelRect.MakeExpanded(boxExpand), enabled ? Color.White : Color.Gray);
float hAngle = hsv.X * Mathf.DegreesToRadians;
float hRadius = hsv.Y * _wheelRect.Width * 0.5f;
var hsPos = new Float2(hRadius * Mathf.Cos(hAngle), -hRadius * Mathf.Sin(hAngle));
const float wheelBoxSize = 4.0f;
Render2D.DrawRectangle(new Rectangle(hsPos - (wheelBoxSize * 0.5f) + _wheelRect.Center, new Float2(wheelBoxSize)), _isMouseDownWheel ? Color.Gray : Color.Black);
Render2D.DrawMaterial(_hsWheelMaterial, _wheelRect, enabled ? Color.White : Color.Gray);
Float3 hsv = _color.ToHSV();
Color hsColor = Color.FromHSV(new Float3(hsv.X, hsv.Y, 1));
Rectangle wheelRect = wheelDragRect;
Render2D.FillRectangle(wheelRect, hsColor);
Render2D.DrawRectangle(wheelRect, Color.Black, _isMouseDownWheel ? 2.0f : 1.0f);
}
/// <inheritdoc />
@@ -194,6 +217,15 @@ namespace FlaxEditor.GUI.Dialogs
base.OnMouseMove(location);
}
/// <inheritdoc />
public override void OnMouseMoveRelative(Float2 motion)
{
var location = PointFromScreen(FlaxEngine.Input.MouseScreenPosition);
UpdateMouse(ref location);
base.OnMouseMoveRelative(motion);
}
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{
@@ -202,6 +234,7 @@ namespace FlaxEditor.GUI.Dialogs
if (!_isMouseDownWheel)
{
_isMouseDownWheel = true;
Cursor = CursorType.Hidden;
StartMouseCapture();
SlidingStart?.Invoke();
}
@@ -218,6 +251,10 @@ namespace FlaxEditor.GUI.Dialogs
if (button == MouseButton.Left && _isMouseDownWheel)
{
EndMouseCapture();
// Make the cursor appear where the user expects it to be (position of selection rectangle)
Rectangle dragRect = wheelDragRect;
Root.MousePosition = dragRect.Center + MouseCursorOffset;
Cursor = CursorType.Default;
EndSliding();
return true;
}
@@ -246,10 +283,10 @@ namespace FlaxEditor.GUI.Dialogs
/// <seealso cref="ColorSelector" />
public class ColorSelectorWithSliders : ColorSelector
{
private Rectangle _slider1Rect;
private Rectangle _slider2Rect;
private bool _isMouseDownSlider1;
private bool _isMouseDownSlider2;
private Rectangle _valueSliderRect;
private Rectangle _alphaSliderRect;
private bool _isMouseDownValueSlider;
private bool _isMouseDownAlphaSlider;
/// <summary>
/// Initializes a new instance of the <see cref="ColorSelectorWithSliders"/> class.
@@ -260,26 +297,26 @@ namespace FlaxEditor.GUI.Dialogs
: base(wheelSize)
{
// Setup dimensions
const float slidersMargin = 8.0f;
_slider1Rect = new Rectangle(wheelSize + slidersMargin, 0, slidersThickness, wheelSize);
_slider2Rect = new Rectangle(_slider1Rect.Right + slidersMargin, _slider1Rect.Y, slidersThickness, _slider1Rect.Height);
Size = new Float2(_slider2Rect.Right, wheelSize);
const float slidersMargin = 10.0f;
_valueSliderRect = new Rectangle(wheelSize + slidersMargin, 0, slidersThickness, wheelSize);
_alphaSliderRect = new Rectangle(_valueSliderRect.Right + slidersMargin * 1.5f, _valueSliderRect.Y, slidersThickness, _valueSliderRect.Height);
Size = new Float2(_alphaSliderRect.Right, wheelSize);
}
/// <inheritdoc />
protected override void UpdateMouse(ref Float2 location)
{
if (_isMouseDownSlider1)
if (_isMouseDownValueSlider)
{
var hsv = _color.ToHSV();
hsv.Z = 1.0f - Mathf.Saturate((location.Y - _slider1Rect.Y) / _slider1Rect.Height);
hsv.Z = 1.0f - Mathf.Saturate((location.Y - _valueSliderRect.Y) / _valueSliderRect.Height);
Color = Color.FromHSV(hsv, _color.A);
}
else if (_isMouseDownSlider2)
else if (_isMouseDownAlphaSlider)
{
var color = _color;
color.A = 1.0f - Mathf.Saturate((location.Y - _slider2Rect.Y) / _slider2Rect.Height);
color.A = 1.0f - Mathf.Saturate((location.Y - _alphaSliderRect.Y) / _alphaSliderRect.Height);
Color = color;
}
@@ -296,56 +333,69 @@ namespace FlaxEditor.GUI.Dialogs
// Cache data
var style = Style.Current;
var features = Render2D.Features;
Render2D.Features = features & ~Render2D.RenderingFeatures.VertexSnapping;
var hsv = _color.ToHSV();
var hs = hsv;
hs.Z = 1.0f;
Color hsC = Color.FromHSV(hs);
const float slidersOffset = 3.0f;
const float slidersThickness = 4.0f;
// Value
float valueY = _slider2Rect.Height * (1 - hsv.Z);
var valueR = new Rectangle(_slider1Rect.X - slidersOffset, _slider1Rect.Y + valueY - slidersThickness / 2, _slider1Rect.Width + slidersOffset * 2, slidersThickness);
Render2D.FillRectangle(_slider1Rect, hsC, hsC, Color.Black, Color.Black);
Render2D.DrawRectangle(_slider1Rect, _isMouseDownSlider1 ? style.BackgroundSelected : Color.Black);
Render2D.DrawRectangle(valueR, _isMouseDownSlider1 ? Color.White : Color.Gray);
// Value slider
float valueKnobExpand = _isMouseDownValueSlider ? 10.0f : 4.0f;
float valueY = _valueSliderRect.Height * (1 - hsv.Z);
float valueKnobWidth = _valueSliderRect.Width + valueKnobExpand;
float valueKnobHeight = _isMouseDownValueSlider ? 7.0f : 4.0f;
float valueKnobX = _valueSliderRect.X - valueKnobExpand * 0.5f;
float valueKnobY = _valueSliderRect.Y + valueY - valueKnobHeight * 0.5f;
Rectangle valueKnobRect = new Rectangle(valueKnobX, valueKnobY, valueKnobWidth, valueKnobHeight);
Render2D.FillRectangle(_valueSliderRect, hsC, hsC, Color.Black, Color.Black);
// Draw one black and one white border to make the knob visible at any saturation level
Render2D.DrawRectangle(valueKnobRect, Color.White, _isMouseDownValueSlider ? 3.0f : 2.0f);
Render2D.DrawRectangle(valueKnobRect, Color.Black, _isMouseDownValueSlider ? 2.0f : 1.0f);
// Draw checkerboard pattern to part of the alpha slider background
var alphaRect = _slider2Rect;
Render2D.FillRectangle(alphaRect, Color.White);
var smallRectSize = alphaRect.Width * 0.5f;
var numHor = Mathf.CeilToInt(alphaRect.Width / smallRectSize);
var numVer = Mathf.CeilToInt(alphaRect.Height / smallRectSize);
// Draw checkerboard pattern as background of alpha slider
Render2D.FillRectangle(_alphaSliderRect, Color.White);
var smallRectSize = _alphaSliderRect.Width / 2.0f;
var numHor = Mathf.CeilToInt(_alphaSliderRect.Width / smallRectSize);
var numVer = Mathf.CeilToInt(_alphaSliderRect.Height / smallRectSize);
Render2D.PushClip(_alphaSliderRect);
for (int i = 0; i < numHor; i++)
{
for (int j = 0; j < numVer; j++)
{
if ((i + j) % 2 == 0)
{
var rect = new Rectangle(alphaRect.X + smallRectSize * i, alphaRect.Y + smallRectSize * j, new Float2(smallRectSize));
Render2D.PushClip(alphaRect);
var rect = new Rectangle(_alphaSliderRect.X + smallRectSize * i, _alphaSliderRect.Y + smallRectSize * j, new Float2(smallRectSize));
Render2D.FillRectangle(rect, Color.Gray);
Render2D.PopClip();
}
}
}
Render2D.PopClip();
// Alpha
float alphaY = _slider2Rect.Height * (1 - _color.A);
var alphaR = new Rectangle(_slider2Rect.X - slidersOffset, _slider2Rect.Y + alphaY - slidersThickness / 2, _slider2Rect.Width + slidersOffset * 2, slidersThickness);
// Alpha slider
float alphaKnobExpand = _isMouseDownAlphaSlider ? 10.0f : 4.0f;
float alphaY = _alphaSliderRect.Height * (1 - _color.A);
float alphaKnobWidth = _alphaSliderRect.Width + alphaKnobExpand;
float alphaKnobHeight = _isMouseDownAlphaSlider ? 7.0f : 4.0f;
float alphaKnobX = _alphaSliderRect.X - alphaKnobExpand * 0.5f;
float alphaKnobY = _alphaSliderRect.Y + alphaY - alphaKnobExpand * 0.5f;
Rectangle alphaKnobRect = new Rectangle(alphaKnobX, alphaKnobY, alphaKnobWidth, alphaKnobHeight);
var color = _color;
color.A = 1; // Keep slider 2 fill rect from changing color alpha while selecting.
Render2D.FillRectangle(_slider2Rect, color, color, Color.Transparent, Color.Transparent);
Render2D.DrawRectangle(_slider2Rect, _isMouseDownSlider2 ? style.BackgroundSelected : Color.Black);
Render2D.DrawRectangle(alphaR, _isMouseDownSlider2 ? Color.White : Color.Gray);
color.A = 1; // Prevent alpha slider fill from becoming transparent
Render2D.FillRectangle(_alphaSliderRect, color, color, Color.Transparent, Color.Transparent);
// Draw one black and one white border to make the knob visible at any saturation level
Render2D.DrawRectangle(alphaKnobRect, Color.White, _isMouseDownAlphaSlider ? 3.0f : 2.0f);
Render2D.DrawRectangle(alphaKnobRect, Color.Black, _isMouseDownAlphaSlider ? 2.0f : 1.0f);
Render2D.Features = features;
}
/// <inheritdoc />
public override void OnLostFocus()
{
// Clear flags
_isMouseDownSlider1 = false;
_isMouseDownSlider2 = false;
_isMouseDownValueSlider = false;
_isMouseDownAlphaSlider = false;
base.OnLostFocus();
}
@@ -353,15 +403,17 @@ namespace FlaxEditor.GUI.Dialogs
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && _slider1Rect.Contains(location))
if (button == MouseButton.Left && _valueSliderRect.Contains(location))
{
_isMouseDownSlider1 = true;
_isMouseDownValueSlider = true;
Cursor = CursorType.Hidden;
StartMouseCapture();
UpdateMouse(ref location);
}
if (button == MouseButton.Left && _slider2Rect.Contains(location))
if (button == MouseButton.Left && _alphaSliderRect.Contains(location))
{
_isMouseDownSlider2 = true;
_isMouseDownAlphaSlider = true;
Cursor = CursorType.Hidden;
StartMouseCapture();
UpdateMouse(ref location);
}
@@ -372,10 +424,16 @@ namespace FlaxEditor.GUI.Dialogs
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && (_isMouseDownSlider1 || _isMouseDownSlider2))
if (button == MouseButton.Left && (_isMouseDownValueSlider || _isMouseDownAlphaSlider))
{
_isMouseDownSlider1 = false;
_isMouseDownSlider2 = false;
// Make the cursor appear where the user expects it to be (center of slider horizontally and slider knob position vertically)
float sliderCenter = _isMouseDownValueSlider ? _valueSliderRect.Center.X : _alphaSliderRect.Center.X;
// Calculate y position based on the slider knob to avoid incrementing value by a small amount when the user repeatedly clicks the slider (f.e. when moving in small steps)
float mouseSliderPosition = _isMouseDownValueSlider ? _valueSliderRect.Y + _valueSliderRect.Height * (1 - _color.ToHSV().Z) + MouseCursorOffset : _alphaSliderRect.Y + _alphaSliderRect.Height * (1 - _color.A) + MouseCursorOffset;
Root.MousePosition = new Float2(sliderCenter + MouseCursorOffset, Mathf.Clamp(mouseSliderPosition, _valueSliderRect.Top, _valueSliderRect.Bottom));
Cursor = CursorType.Default;
_isMouseDownValueSlider = false;
_isMouseDownAlphaSlider = false;
EndMouseCapture();
return true;
}
@@ -387,8 +445,8 @@ namespace FlaxEditor.GUI.Dialogs
public override void OnEndMouseCapture()
{
// Clear flags
_isMouseDownSlider1 = false;
_isMouseDownSlider2 = false;
_isMouseDownValueSlider = false;
_isMouseDownAlphaSlider = false;
base.OnEndMouseCapture();
}
+3 -4
View File
@@ -2148,10 +2148,9 @@ namespace FlaxEditor.GUI.Timeline
/// </summary>
public void ShowWholeTimeline()
{
var viewWidth = Width;
var timelineWidth = Duration * UnitsPerSecond * Zoom + 8 * StartOffset;
_backgroundArea.ViewOffset = Float2.Zero;
Zoom = viewWidth / timelineWidth;
const float padding = 40f;
Zoom = (_backgroundArea.Width - padding * 2f) / (Duration * UnitsPerSecond);
_backgroundArea.ViewOffset = new Float2(-_leftEdge.X + padding, _backgroundArea.ViewOffset.Y);
}
/// <summary>
@@ -253,13 +253,6 @@ namespace FlaxEditor.Options
[EditorDisplay("Interface"), EditorOrder(280), Tooltip("Editor content window orientation.")]
public FlaxEngine.GUI.Orientation ContentWindowOrientation { get; set; } = FlaxEngine.GUI.Orientation.Horizontal;
/// <summary>
/// If checked, color pickers will always modify the color unless 'Cancel' if pressed, otherwise color won't change unless 'Ok' is pressed.
/// </summary>
[DefaultValue(true)]
[EditorDisplay("Interface"), EditorOrder(290)]
public bool AutoAcceptColorPickerChange { get; set; } = true;
/// <summary>
/// Gets or sets the formatting option for numeric values in the editor.
/// </summary>
@@ -0,0 +1,29 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using FlaxEngine;
namespace FlaxEditor.SceneGraph.Actors
{
/// <summary>
/// Scene tree node for <see cref="Cloth"/> actor type.
/// </summary>
[HideInEditor]
public sealed class ClothNode : ActorNode
{
/// <inheritdoc />
public ClothNode(Actor actor)
: base(actor)
{
}
/// <inheritdoc />
public override void PostSpawn()
{
base.PostSpawn();
// Snap to the parent
if (!(ParentNode is SceneNode))
Actor.LocalTransform = Transform.Identity;
}
}
}
@@ -51,6 +51,7 @@ namespace FlaxEditor.SceneGraph
CustomNodesTypes.Add(typeof(AudioSource), typeof(AudioSourceNode));
CustomNodesTypes.Add(typeof(BoneSocket), typeof(BoneSocketNode));
CustomNodesTypes.Add(typeof(Decal), typeof(DecalNode));
CustomNodesTypes.Add(typeof(Cloth), typeof(ClothNode));
CustomNodesTypes.Add(typeof(BoxCollider), typeof(BoxColliderNode));
CustomNodesTypes.Add(typeof(SphereCollider), typeof(ColliderNode));
CustomNodesTypes.Add(typeof(CapsuleCollider), typeof(ColliderNode));
+102 -102
View File
@@ -22,15 +22,97 @@ namespace FlaxEditor.Surface.Archetypes
[HideInEditor]
public static class Parameters
{
/// <summary>
/// Surface node type for parameters group Get/Set nodes.
/// </summary>
/// <seealso cref="FlaxEditor.Surface.SurfaceNode" />
public abstract class SurfaceNodeParamsBase : SurfaceNode
{
/// <summary>
/// The combobox for picking parameter.
/// </summary>
protected ComboBoxElement _combobox;
/// <summary>
/// Fag used to block layout updated when updating node.
/// </summary>
protected bool _isUpdateLocked;
/// <inheritdoc />
protected SurfaceNodeParamsBase(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
}
/// <summary>
/// Gets the selected parameter.
/// </summary>
/// <returns>Surface parameter object or null if nothing selected or cannot find it.</returns>
protected SurfaceParameter GetSelected()
{
if (Surface != null)
return Surface.GetParameter(_combobox.SelectedItem);
return Context.GetParameter((Guid)Values[0]);
}
/// <summary>
/// Updates the combo box.
/// </summary>
protected void UpdateCombo()
{
if (_isUpdateLocked)
return;
_isUpdateLocked = true;
if (_combobox == null)
{
_combobox = GetChild<ComboBoxElement>();
_combobox.SelectedIndexChanged += OnSelectedChanged;
}
string toSelect = null;
Guid loadedSelected = (Guid)Values[0];
_combobox.ClearItems();
var parameters = Surface.Parameters;
for (int i = 0; i < parameters.Count; i++)
{
var param = parameters[i];
if (!param.IsPublic && !Surface.CanShowPrivateParameters)
continue;
_combobox.AddItem(param.Name);
if (param.ID == loadedSelected)
toSelect = param.Name;
}
_combobox.SelectedItem = toSelect;
_isUpdateLocked = false;
}
private void OnSelectedChanged(ComboBox cb)
{
if (_isUpdateLocked)
return;
var selected = GetSelected();
var selectedID = selected?.ID ?? Guid.Empty;
if (selectedID != (Guid)Values[0])
Set(selected, ref selectedID);
}
/// <summary>
/// Sets the selected parameter.
/// </summary>
/// <param name="selected">Parameter.</param>
/// <param name="selectedID">Parameter identifier.</param>
protected virtual void Set(SurfaceParameter selected, ref Guid selectedID)
{
SetValue(0, selectedID);
}
}
/// <summary>
/// Surface node type for parameters group Get node.
/// </summary>
/// <seealso cref="FlaxEditor.Surface.SurfaceNode" />
public class SurfaceNodeParamsGet : SurfaceNode, IParametersDependantNode
public class SurfaceNodeParamsGet : SurfaceNodeParamsBase, IParametersDependantNode
{
private ComboBoxElement _combobox;
private readonly List<ISurfaceNodeElement> _dynamicChildren = new List<ISurfaceNodeElement>();
private bool _isUpdateLocked;
private ScriptType _layoutType;
private NodeElementArchetype[] _layoutElements;
@@ -306,49 +388,6 @@ namespace FlaxEditor.Surface.Archetypes
UpdateTitle();
}
private void UpdateCombo()
{
if (_isUpdateLocked)
return;
_isUpdateLocked = true;
if (_combobox == null)
{
_combobox = (ComboBoxElement)_children[0];
_combobox.SelectedIndexChanged += OnSelectedChanged;
}
int toSelect = -1;
Guid loadedSelected = (Guid)Values[0];
_combobox.ClearItems();
for (int i = 0; i < Surface.Parameters.Count; i++)
{
var param = Surface.Parameters[i];
_combobox.AddItem(param.Name);
if (param.ID == loadedSelected)
toSelect = i;
}
_combobox.SelectedIndex = toSelect;
_isUpdateLocked = false;
}
private void OnSelectedChanged(ComboBox cb)
{
if (_isUpdateLocked)
return;
var selected = GetSelected();
var selectedID = selected?.ID ?? Guid.Empty;
SetValue(0, selectedID);
}
private SurfaceParameter GetSelected()
{
if (Surface != null)
{
var selectedIndex = _combobox.SelectedIndex;
return selectedIndex >= 0 && selectedIndex < Surface.Parameters.Count ? Surface.Parameters[selectedIndex] : null;
}
return Context.GetParameter((Guid)Values[0]);
}
private void ClearDynamicElements()
{
for (int i = 0; i < _dynamicChildren.Count; i++)
@@ -463,15 +502,19 @@ namespace FlaxEditor.Surface.Archetypes
else if (!_isUpdateLocked)
{
_isUpdateLocked = true;
int toSelect = -1;
string toSelect = null;
Guid loadedSelected = (Guid)Values[0];
for (int i = 0; i < Surface.Parameters.Count; i++)
var parameters = Surface.Parameters;
for (int i = 0; i < parameters.Count; i++)
{
var param = Surface.Parameters[i];
var param = parameters[i];
if (param.ID == loadedSelected)
toSelect = i;
{
toSelect = param.Name;
break;
}
}
_combobox.SelectedIndex = toSelect;
_combobox.SelectedItem = toSelect;
_isUpdateLocked = false;
}
UpdateLayout();
@@ -817,66 +860,23 @@ namespace FlaxEditor.Surface.Archetypes
/// Surface node type for parameters group Set node.
/// </summary>
/// <seealso cref="FlaxEditor.Surface.SurfaceNode" />
public class SurfaceNodeParamsSet : SurfaceNode, IParametersDependantNode
public class SurfaceNodeParamsSet : SurfaceNodeParamsBase, IParametersDependantNode
{
private ComboBoxElement _combobox;
private bool _isUpdateLocked;
/// <inheritdoc />
public SurfaceNodeParamsSet(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
}
private void UpdateCombo()
/// <inheritdoc />
protected override void Set(SurfaceParameter selected, ref Guid selectedID)
{
if (_isUpdateLocked)
return;
_isUpdateLocked = true;
if (_combobox == null)
SetValues(new[]
{
_combobox = GetChild<ComboBoxElement>();
_combobox.SelectedIndexChanged += OnSelectedChanged;
}
int toSelect = -1;
Guid loadedSelected = (Guid)Values[0];
_combobox.ClearItems();
for (int i = 0; i < Surface.Parameters.Count; i++)
{
var param = Surface.Parameters[i];
_combobox.AddItem(param.Name);
if (param.ID == loadedSelected)
toSelect = i;
}
_combobox.SelectedIndex = toSelect;
_isUpdateLocked = false;
}
private void OnSelectedChanged(ComboBox cb)
{
if (_isUpdateLocked)
return;
var selected = GetSelected();
var selectedID = selected?.ID ?? Guid.Empty;
if (selectedID != (Guid)Values[0])
{
SetValues(new[]
{
selectedID,
selected != null ? TypeUtils.GetDefaultValue(selected.Type) : null,
});
UpdateUI();
}
}
private SurfaceParameter GetSelected()
{
if (Surface != null)
{
var selectedIndex = _combobox.SelectedIndex;
return selectedIndex >= 0 && selectedIndex < Surface.Parameters.Count ? Surface.Parameters[selectedIndex] : null;
}
return Context.GetParameter((Guid)Values[0]);
selectedID,
selected != null ? TypeUtils.GetDefaultValue(selected.Type) : null,
});
UpdateUI();
}
/// <inheritdoc />
+5
View File
@@ -583,6 +583,11 @@ namespace FlaxEditor.Surface
/// </summary>
public virtual bool CanSetParameters => false;
/// <summary>
/// Gets a value indicating whether surface private parameters can be used, otherwise they will remain hidden.
/// </summary>
public virtual bool CanShowPrivateParameters => false;
/// <summary>
/// True of the context menu should make use of a description panel drawn at the bottom of the menu
/// </summary>
@@ -254,9 +254,10 @@ namespace FlaxEditor.Surface
public SurfaceParameter GetParameter(Guid id)
{
SurfaceParameter result = null;
for (int i = 0; i < Parameters.Count; i++)
var parameters = Parameters;
for (int i = 0; i < parameters.Count; i++)
{
var parameter = Parameters[i];
var parameter = parameters[i];
if (parameter.ID == id)
{
result = parameter;
@@ -274,9 +275,10 @@ namespace FlaxEditor.Surface
public SurfaceParameter GetParameter(string name)
{
SurfaceParameter result = null;
for (int i = 0; i < Parameters.Count; i++)
var parameters = Parameters;
for (int i = 0; i < parameters.Count; i++)
{
var parameter = Parameters[i];
var parameter = parameters[i];
if (parameter.Name == name)
{
result = parameter;
@@ -187,6 +187,9 @@ namespace FlaxEditor.Surface
/// <inheritdoc />
public override bool CanSetParameters => true;
/// <inheritdoc />
public override bool CanShowPrivateParameters => true;
/// <inheritdoc />
public override bool UseContextMenuDescriptionPanel => true;
@@ -198,6 +198,11 @@ namespace FlaxEditor.Viewport
actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation);
_owner.Spawn(actor);
_viewport.Focus();
// Scroll to the new actor in the hierarchy
var actorNode = Editor.Instance.Scene.GetActorNode(actor);
if (actorNode?.TreeNode.ParentTree.Parent is Panel treePanel)
FlaxEngine.Scripting.InvokeOnUpdate(() => treePanel.ScrollViewTo(actorNode.TreeNode));
}
private void Spawn(ScriptItem item, SceneGraphNode hit, ref Float2 location, ref Vector3 hitLocation, ref Vector3 hitNormal)
@@ -455,6 +455,7 @@ namespace FlaxEditor.Windows.Assets
_timeline.Enabled = true;
_timeline.SetNoTracksText(null);
ClearEditedFlag();
_timeline.ShowWholeTimeline();
}
}
+18 -5
View File
@@ -691,11 +691,7 @@ Asset::LoadResult Animation::load()
continue;
}
#if USE_EDITOR
if (!_registeredForScriptingReload)
{
_registeredForScriptingReload = true;
Level::ScriptsReloadStart.Bind<Animation, &Animation::OnScriptsReloadStart>(this);
}
_registerForScriptingReload = true;
#endif
}
}
@@ -733,6 +729,7 @@ void Animation::unload(bool isReloading)
{
ScopeWriteLock systemScope(Animations::SystemLocker);
#if USE_EDITOR
_registerForScriptingReload = false;
if (_registeredForScriptingReload)
{
_registeredForScriptingReload = false;
@@ -752,6 +749,22 @@ void Animation::unload(bool isReloading)
NestedAnims.Clear();
}
#if USE_EDITOR
void Animation::onLoaded_MainThread()
{
if (_registerForScriptingReload && !_registeredForScriptingReload)
{
_registeredForScriptingReload = true;
Level::ScriptsReloadStart.Bind<Animation, &Animation::OnScriptsReloadStart>(this);
}
_registerForScriptingReload = false;
BinaryAsset::onLoaded_MainThread();
}
#endif
AssetChunksFlag Animation::getChunksToPreload() const
{
return GET_CHUNK_FLAG(0);
+4
View File
@@ -78,6 +78,7 @@ API_CLASS(NoSpawn) class FLAXENGINE_API Animation : public BinaryAsset
private:
#if USE_EDITOR
bool _registerForScriptingReload = false;
bool _registeredForScriptingReload = false;
void OnScriptsReloadStart();
#endif
@@ -163,5 +164,8 @@ protected:
// [BinaryAsset]
LoadResult load() override;
void unload(bool isReloading) override;
#if USE_EDITOR
void onLoaded_MainThread() override;
#endif
AssetChunksFlag getChunksToPreload() const override;
};
@@ -413,10 +413,16 @@ namespace FlaxEngine
return true;
for (int i = 0; i < buffersLocal.Length; i++)
{
_data[(int)buffersLocal[i]] = meshBuffers[i];
_layouts[(int)buffersLocal[i]] = meshLayouts[i];
int buffer = (int)buffersLocal[i];
_data[buffer] = meshBuffers[i];
_layouts[buffer] = meshLayouts[i];
// Get format if using a single item (eg. index buffer type)
var format = PixelFormat.Unknown;
if (meshLayouts[i] && meshLayouts[i].Elements.Length == 1)
format = meshLayouts[i].Elements[0].Format;
_formats[buffer] = format;
}
_formats[(int)MeshBufferType.Index] = mesh.Use16BitIndexBuffer ? PixelFormat.R16_UInt : PixelFormat.R32_UInt;
return false;
}
@@ -39,51 +39,61 @@ public:
FORCE_INLINE int32 GetInt(int32 index) const
{
ASSERT_LOW_LAYER(index * _stride < _data.Length());
return (int32)_sampler.Read(_data.Get() + index * _stride).X;
}
FORCE_INLINE float GetFloat(int32 index) const
{
ASSERT_LOW_LAYER(index * _stride < _data.Length());
return _sampler.Read(_data.Get() + index * _stride).X;
}
FORCE_INLINE Float2 GetFloat2(int32 index) const
{
ASSERT_LOW_LAYER(index * _stride < _data.Length());
return Float2(_sampler.Read(_data.Get() + index * _stride));
}
FORCE_INLINE Float3 GetFloat3(int32 index) const
{
ASSERT_LOW_LAYER(index * _stride < _data.Length());
return Float3(_sampler.Read(_data.Get() + index * _stride));
}
FORCE_INLINE Float4 GetFloat4(int32 index) const
{
ASSERT_LOW_LAYER(index * _stride < _data.Length());
return _sampler.Read(_data.Get() + index * _stride);
}
FORCE_INLINE void SetInt(int32 index, const int32 value)
{
ASSERT_LOW_LAYER(index * _stride < _data.Length());
_sampler.Write(_data.Get() + index * _stride, Float4((float)value));
}
FORCE_INLINE void SetFloat(int32 index, const float value)
{
ASSERT_LOW_LAYER(index * _stride < _data.Length());
_sampler.Write(_data.Get() + index * _stride, Float4(value));
}
FORCE_INLINE void SetFloat2(int32 index, const Float2& value)
{
ASSERT_LOW_LAYER(index * _stride < _data.Length());
_sampler.Write(_data.Get() + index * _stride, Float4(value));
}
FORCE_INLINE void SetFloat3(int32 index, const Float3& value)
{
ASSERT_LOW_LAYER(index * _stride < _data.Length());
_sampler.Write(_data.Get() + index * _stride, Float4(value));
}
FORCE_INLINE void SetFloat4(int32 index, const Float4& value)
{
ASSERT_LOW_LAYER(index * _stride < _data.Length());
_sampler.Write(_data.Get() + index * _stride, value);
}
@@ -94,19 +104,33 @@ public:
void Set(Span<Float2> src);
void Set(Span<Float3> src);
void Set(Span<Color> src);
template<typename T>
void Set(const Array<T>& dst) const
{
Set(Span<T>(dst.Get(), dst.Count()));
}
// Copies the contents of this stream into a destination data span.
void CopyTo(Span<Float2> dst) const;
void CopyTo(Span<Float3> dst) const;
void CopyTo(Span<Color> dst) const;
template<typename T>
void CopyTo(Array<T>& dst) const
{
dst.Resize(GetCount());
CopyTo(Span<T>(dst.Get(), dst.Count()));
}
};
private:
BytesContainer _data[(int32)MeshBufferType::MAX];
PixelFormat _formats[(int32)MeshBufferType::MAX] = {};
GPUVertexLayout* _layouts[(int32)MeshBufferType::MAX] = {};
Array<ModelBase*, InlinedAllocation<4>> _usedModels;
public:
~MeshAccessor();
/// <summary>
/// Loads the data from the mesh.
/// </summary>
+32 -3
View File
@@ -188,6 +188,16 @@ void MeshAccessor::Stream::CopyTo(Span<::Color> dst) const
}
}
MeshAccessor::~MeshAccessor()
{
for (ModelBase* model : _usedModels)
{
if (model->Storage)
model->Storage->UnlockChunks();
model->RemoveReference();
}
}
bool MeshAccessor::LoadMesh(const MeshBase* mesh, bool forceGpu, Span<MeshBufferType> buffers)
{
CHECK_RETURN(mesh, true);
@@ -196,14 +206,28 @@ bool MeshAccessor::LoadMesh(const MeshBase* mesh, bool forceGpu, Span<MeshBuffer
buffers = Span<MeshBufferType>(allBuffers, ARRAY_COUNT(allBuffers));
Array<BytesContainer, FixedAllocation<4>> meshBuffers;
Array<GPUVertexLayout*, FixedAllocation<4>> meshLayouts;
if (ModelBase* model = mesh->GetModelBase())
{
// Maintain reference to mesh data (it's buffers might be referenced, not copied)
model->AddReference();
if (model->Storage)
model->Storage->LockChunks();
_usedModels.Add(model);
}
if (mesh->DownloadData(buffers, meshBuffers, meshLayouts, forceGpu))
return true;
for (int32 i = 0; i < buffers.Length(); i++)
{
_data[(int32)buffers[i]] = MoveTemp(meshBuffers[i]);
_layouts[(int32)buffers[i]] = meshLayouts[i];
const int32 buffer = (int32)buffers[i];
_data[buffer] = MoveTemp(meshBuffers[i]);
_layouts[buffer] = meshLayouts[i];
// Get format if using a single item (eg. index buffer type)
PixelFormat format = PixelFormat::Unknown;
if (meshLayouts[i] && meshLayouts[i]->GetElements().Count() == 1)
format = meshLayouts[i]->GetElements()[0].Format;
_formats[buffer] = format;
}
_formats[(int32)MeshBufferType::Index] = mesh->Use16BitIndexBuffer() ? PixelFormat::R16_UInt : PixelFormat::R32_UInt;
return false;
}
@@ -714,6 +738,9 @@ bool MeshBase::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int3
_cachedVertexBufferCount = meshData.Vertices;
_cachedIndexBufferCount = (int32)meshData.Triangles * 3;
_cachedIndexBuffer.Copy((const byte*)meshData.IBData, _cachedIndexBufferCount * (int32)meshData.IBStride);
GPUVertexLayout::Elements ibLayout;
ibLayout.Add({ VertexElement::Types::Attribute, 0, 0, 0, meshData.IBStride == sizeof(uint16) ? PixelFormat::R16_UInt : PixelFormat::R32_UInt });
_cachedVertexLayouts[3] = GPUVertexLayout::Get(ibLayout);
for (int32 vb = 0; vb < meshData.VBData.Count(); vb++)
{
_cachedVertexBuffers[vb].Copy((const byte*)meshData.VBData[vb], (int32)(meshData.VBLayout[vb]->GetStride() * meshData.Vertices));
@@ -728,6 +755,8 @@ bool MeshBase::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int3
case MeshBufferType::Index:
result.Link(_cachedIndexBuffer);
count = _cachedIndexBufferCount;
if (layout)
*layout = _cachedVertexLayouts[3];
break;
case MeshBufferType::Vertex0:
result.Link(_cachedVertexBuffers[0]);
+1 -1
View File
@@ -50,7 +50,7 @@ protected:
GPUBuffer* _indexBuffer = nullptr;
mutable BytesContainer _cachedVertexBuffers[MODEL_MAX_VB];
mutable GPUVertexLayout* _cachedVertexLayouts[MODEL_MAX_VB] = {};
mutable GPUVertexLayout* _cachedVertexLayouts[MODEL_MAX_VB + 1] = {};
mutable BytesContainer _cachedIndexBuffer;
mutable int32 _cachedIndexBufferCount = 0, _cachedVertexBufferCount = 0;
@@ -1397,6 +1397,16 @@ bool AnimatedModel::GetMeshData(const MeshReference& ref, MeshBufferType type, B
return mesh.DownloadDataCPU(type, result, count, layout);
}
MeshBase* AnimatedModel::GetMesh(const MeshReference& ref) const
{
const auto model = SkinnedModel.Get();
if (!model || model->WaitForLoaded())
return nullptr;
auto& lod = model->LODs[Math::Min(ref.LODIndex, model->LODs.Count() - 1)];
auto& mesh = lod.Meshes[Math::Min(ref.MeshIndex, lod.Meshes.Count() - 1)];
return &mesh;
}
MeshDeformation* AnimatedModel::GetMeshDeformation() const
{
if (!_deformation)
@@ -479,6 +479,7 @@ public:
bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override;
bool IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex) override;
bool GetMeshData(const MeshReference& ref, MeshBufferType type, BytesContainer& result, int32& count, GPUVertexLayout** layout) const override;
MeshBase* GetMesh(const MeshReference& ref) const override;
void UpdateBounds() override;
MeshDeformation* GetMeshDeformation() const override;
void OnDeleteObject() override;
@@ -14,6 +14,12 @@ String ModelInstanceActor::MeshReference::ToString() const
return String::Format(TEXT("Actor={},LOD={},Mesh={}"), Actor ? Actor->GetNamePath() : String::Empty, LODIndex, MeshIndex);
}
MeshBase* ModelInstanceActor::MeshReference::Get() const
{
auto actor = Actor.Get();
return actor ? actor->GetMesh(*this) : nullptr;
}
void ModelInstanceActor::SetEntries(const Array<ModelInstanceEntry>& value)
{
WaitForModelLoad();
@@ -29,6 +29,7 @@ API_CLASS(Abstract) class FLAXENGINE_API ModelInstanceActor : public Actor
API_FIELD() int32 MeshIndex = 0;
String ToString() const;
MeshBase* Get() const;
};
protected:
@@ -113,6 +114,7 @@ public:
/// <summary>
/// Extracts mesh buffer data from CPU. Might be cached internally (eg. by Model/SkinnedModel).
/// [Deprecated in 1.12]
/// </summary>
/// <param name="ref">Mesh reference.</param>
/// <param name="type">Buffer type</param>
@@ -120,11 +122,22 @@ public:
/// <param name="count">The amount of items inside the result buffer.</param>
/// <param name="layout">The result layout of the result buffer (for vertex buffers). Optional, pass null to ignore it.</param>
/// <returns>True if failed, otherwise false.</returns>
DEPRECATED("Use GetMesh to resolve mesh reference and access mesh data with MeshAccessor.")
virtual bool GetMeshData(const MeshReference& ref, MeshBufferType type, BytesContainer& result, int32& count, GPUVertexLayout** layout = nullptr) const
{
return true;
}
/// <summary>
/// Resolves a given mesh reference.
/// </summary>
/// <param name="ref">Mesh reference.</param>
/// <returns>Mesh or null if invalid ref.</returns>
virtual MeshBase* GetMesh(const MeshReference& ref) const
{
return nullptr;
}
/// <summary>
/// Gets the mesh deformation utility for this model instance (optional).
/// </summary>
@@ -665,6 +665,16 @@ bool StaticModel::GetMeshData(const MeshReference& ref, MeshBufferType type, Byt
return mesh.DownloadDataCPU(type, result, count, layout);
}
MeshBase* StaticModel::GetMesh(const MeshReference& ref) const
{
const auto model = Model.Get();
if (!model || model->WaitForLoaded())
return nullptr;
auto& lod = model->LODs[Math::Min(ref.LODIndex, model->LODs.Count() - 1)];
auto& mesh = lod.Meshes[Math::Min(ref.MeshIndex, lod.Meshes.Count() - 1)];
return &mesh;
}
MeshDeformation* StaticModel::GetMeshDeformation() const
{
if (!_deformation)
+1
View File
@@ -181,6 +181,7 @@ public:
bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override;
bool IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex) override;
bool GetMeshData(const MeshReference& ref, MeshBufferType type, BytesContainer& result, int32& count, GPUVertexLayout** layout) const override;
MeshBase* GetMesh(const MeshReference& ref) const override;
MeshDeformation* GetMeshDeformation() const override;
void UpdateBounds() override;
+130 -122
View File
@@ -133,7 +133,7 @@ Array<Float3> Cloth::GetParticles() const
if (_cloth)
{
PROFILE_CPU();
PROFILE_MEM(Physics);
PROFILE_MEM(PhysicsCloth);
PhysicsBackend::LockClothParticles(_cloth);
const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth);
result.Resize(particles.Length());
@@ -150,7 +150,7 @@ Array<Float3> Cloth::GetParticles() const
void Cloth::SetParticles(Span<const Float3> value)
{
PROFILE_CPU();
PROFILE_MEM(Physics);
PROFILE_MEM(PhysicsCloth);
#if USE_CLOTH_SANITY_CHECKS
{
// Sanity check
@@ -180,7 +180,7 @@ Span<float> Cloth::GetPaint() const
void Cloth::SetPaint(Span<const float> value)
{
PROFILE_CPU();
PROFILE_MEM(Physics);
PROFILE_MEM(PhysicsCloth);
#if USE_CLOTH_SANITY_CHECKS
{
// Sanity check
@@ -208,8 +208,12 @@ void Cloth::SetPaint(Span<const float> value)
if (_cloth)
{
// Update cloth particles
MeshAccessor accessor;
MeshBufferType bufferTypes[2] = { MeshBufferType::Index, MeshBufferType::Vertex0 };
if (accessor.LoadMesh(GetMesh().Get(), false, ToSpan(bufferTypes, 2)))
return;
Array<float> invMasses;
CalculateInvMasses(invMasses);
CalculateInvMasses(accessor, invMasses);
PhysicsBackend::LockClothParticles(_cloth);
PhysicsBackend::SetClothParticles(_cloth, Span<const Float4>(), Span<const Float3>(), ToSpan<float, const float>(invMasses));
PhysicsBackend::SetClothPaint(_cloth, value);
@@ -227,18 +231,20 @@ bool Cloth::IntersectsItself(const Ray& ray, Real& distance, Vector3& normal)
if (_cloth)
{
// Precise per-triangle intersection
const ModelInstanceActor::MeshReference mesh = GetMesh();
if (mesh.Actor == nullptr)
const ModelInstanceActor::MeshReference meshRef = GetMesh();
if (meshRef.Actor == nullptr)
return false;
BytesContainer indicesData;
int32 indicesCount;
if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Index, indicesData, indicesCount))
MeshAccessor accessor;
MeshBufferType bufferTypes[1] = { MeshBufferType::Index };
if (accessor.LoadMesh(meshRef.Get(), false, ToSpan(bufferTypes, 1)))
return false;
auto indices = accessor.Index();
auto indicesData = indices.GetData();
PhysicsBackend::LockClothParticles(_cloth);
const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth);
const Transform transform = GetTransform();
const bool indices16bit = indicesData.Length() / indicesCount == sizeof(uint16);
const int32 trianglesCount = indicesCount / 3;
const bool indices16bit = indices.GetFormat() == PixelFormat::R16_UInt;
const int32 trianglesCount = indices.GetCount() / 3;
bool result = false;
distance = MAX_Real;
for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++)
@@ -306,7 +312,7 @@ void Cloth::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
{
Actor::Deserialize(stream, modifier);
PROFILE_MEM(Physics);
PROFILE_MEM(PhysicsCloth);
DESERIALIZE_MEMBER(Mesh, _mesh);
_mesh.Actor = nullptr; // Don't store this reference
DESERIALIZE_MEMBER(Force, _forceSettings);
@@ -341,18 +347,20 @@ void Cloth::DrawPhysicsDebug(RenderView& view)
if (_cloth)
{
PROFILE_CPU();
const ModelInstanceActor::MeshReference mesh = GetMesh();
if (mesh.Actor == nullptr)
const ModelInstanceActor::MeshReference meshRef = GetMesh();
if (meshRef.Actor == nullptr)
return;
BytesContainer indicesData;
int32 indicesCount;
if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Index, indicesData, indicesCount))
MeshAccessor accessor;
MeshBufferType bufferTypes[1] = { MeshBufferType::Index };
if (accessor.LoadMesh(meshRef.Get(), false, ToSpan(bufferTypes, 1)))
return;
auto indices = accessor.Index();
auto indicesData = indices.GetData();
PhysicsBackend::LockClothParticles(_cloth);
const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth);
const Transform transform = GetTransform();
const bool indices16bit = indicesData.Length() / indicesCount == sizeof(uint16);
const int32 trianglesCount = indicesCount / 3;
const bool indices16bit = indices.GetFormat() == PixelFormat::R16_UInt;
const int32 trianglesCount = indices.GetCount() / 3;
for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++)
{
const int32 index = triangleIndex * 3;
@@ -390,18 +398,20 @@ void Cloth::OnDebugDrawSelected()
if (_cloth)
{
DEBUG_DRAW_WIRE_BOX(_box, Color::Violet.RGBMultiplied(0.8f), 0, true);
const ModelInstanceActor::MeshReference mesh = GetMesh();
if (mesh.Actor == nullptr)
const ModelInstanceActor::MeshReference meshRef = GetMesh();
if (meshRef.Actor == nullptr)
return;
BytesContainer indicesData;
int32 indicesCount;
if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Index, indicesData, indicesCount))
MeshAccessor accessor;
MeshBufferType bufferTypes[1] = { MeshBufferType::Index };
if (accessor.LoadMesh(meshRef.Get(), false, ToSpan(bufferTypes, 1)))
return;
auto indices = accessor.Index();
auto indicesData = indices.GetData();
PhysicsBackend::LockClothParticles(_cloth);
const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth);
const Transform transform = GetTransform();
const bool indices16bit = indicesData.Length() / indicesCount == sizeof(uint16);
const int32 trianglesCount = indicesCount / 3;
const bool indices16bit = indices.GetFormat() == PixelFormat::R16_UInt;
const int32 trianglesCount = indices.GetCount() / 3;
for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++)
{
const int32 index = triangleIndex * 3;
@@ -542,7 +552,7 @@ bool Cloth::CreateCloth()
{
#if WITH_CLOTH
PROFILE_CPU();
PROFILE_MEM(Physics);
PROFILE_MEM(PhysicsCloth);
// Skip if all vertices are fixed so cloth sim doesn't make sense
if (_paint.HasItems())
@@ -556,26 +566,36 @@ bool Cloth::CreateCloth()
// Get mesh data
// TODO: consider making it via async task so physics can wait on the cloth setup from mesh data just before next fixed update which gives more time when loading scene
const ModelInstanceActor::MeshReference mesh = GetMesh();
if (mesh.Actor == nullptr)
const ModelInstanceActor::MeshReference meshRef = GetMesh();
if (meshRef.Actor == nullptr)
return false;
PhysicsClothDesc desc;
desc.Actor = this;
BytesContainer data;
int32 count;
if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Vertex0, data, count))
MeshAccessor accessor;
MeshBufferType bufferTypes[2] = { MeshBufferType::Index, MeshBufferType::Vertex0 };
if (accessor.LoadMesh(meshRef.Get(), false, ToSpan(bufferTypes, 2)))
return true;
// TODO: use MeshAccessor vertex data layout descriptor instead hardcoded position data at the beginning of VB0
desc.VerticesData = data.Get();
desc.VerticesCount = count;
desc.VerticesStride = data.Length() / count;
if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Index, data, count))
return true;
desc.IndicesData = data.Get();
desc.IndicesCount = count;
desc.IndicesStride = data.Length() / count;
auto position = accessor.Position();
Array<Float3> tempPositions;
if (position.GetFormat() == PixelFormat::R32G32B32_Float)
{
desc.VerticesData = position.GetData().Get();
desc.VerticesCount = position.GetCount();
desc.VerticesStride = position.GetStride();
}
else
{
position.CopyTo(tempPositions);
desc.VerticesData = tempPositions.Get();
desc.VerticesCount = tempPositions.Count();
desc.VerticesStride = sizeof(Float3);
}
auto indices = accessor.Index();
desc.IndicesData = indices.GetData().Get();
desc.IndicesCount = indices.GetCount();
desc.IndicesStride = indices.GetStride();
Array<float> invMasses;
CalculateInvMasses(invMasses);
CalculateInvMasses(accessor, invMasses);
desc.InvMassesData = invMasses.Count() == desc.VerticesCount ? invMasses.Get() : nullptr;
desc.InvMassesStride = sizeof(float);
desc.MaxDistancesData = _paint.Count() == desc.VerticesCount ? _paint.Get() : nullptr;
@@ -595,13 +615,13 @@ bool Cloth::CreateCloth()
PhysicsBackend::ClearClothInertia(_cloth);
// Add cloth mesh deformer
if (auto* deformation = mesh.Actor->GetMeshDeformation())
if (auto* deformation = meshRef.Actor->GetMeshDeformation())
{
Function<void(const MeshBase*, MeshDeformationData&)> deformer;
deformer.Bind<Cloth, &Cloth::RunClothDeformer>(this);
deformation->AddDeformer(mesh.LODIndex, mesh.MeshIndex, MeshBufferType::Vertex0, deformer);
deformation->AddDeformer(meshRef.LODIndex, meshRef.MeshIndex, MeshBufferType::Vertex0, deformer);
if (_simulationSettings.ComputeNormals)
deformation->AddDeformer(mesh.LODIndex, mesh.MeshIndex, MeshBufferType::Vertex1, deformer);
deformation->AddDeformer(meshRef.LODIndex, meshRef.MeshIndex, MeshBufferType::Vertex1, deformer);
_meshDeformation = deformation;
}
@@ -631,39 +651,32 @@ void Cloth::DestroyCloth()
#endif
}
void Cloth::CalculateInvMasses(Array<float>& invMasses)
void Cloth::CalculateInvMasses(MeshAccessor& accessor, Array<float>& invMasses)
{
// Use per-particle max distance to evaluate which particles are immovable
#if WITH_CLOTH
if (_paint.IsEmpty())
return;
PROFILE_CPU();
PROFILE_MEM(Physics);
PROFILE_MEM(PhysicsCloth);
// Get mesh data
const ModelInstanceActor::MeshReference mesh = GetMesh();
if (mesh.Actor == nullptr)
return;
BytesContainer verticesData;
int32 verticesCount;
if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Vertex0, verticesData, verticesCount))
return;
BytesContainer indicesData;
int32 indicesCount;
if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Index, indicesData, indicesCount))
return;
auto positions = accessor.Position();
auto indices = accessor.Index();
CHECK(positions.IsValid() && indices.IsValid());
int32 verticesCount = positions.GetCount();
if (_paint.Count() != verticesCount)
{
// Fix incorrect paint data
LOG(Warning, "Incorrect cloth '{}' paint size {} for mesh '{}' that has {} vertices", GetNamePath(), _paint.Count(), mesh.ToString(), verticesCount);
LOG(Warning, "Incorrect cloth '{}' paint size {} for mesh '{}' that has {} vertices", GetNamePath(), _paint.Count(), GetMesh().ToString(), verticesCount);
int32 countBefore = _paint.Count();
_paint.Resize(verticesCount);
for (int32 i = countBefore; i < verticesCount; i++)
_paint.Get()[i] = 0.0f;
}
const int32 verticesStride = verticesData.Length() / verticesCount;
const bool indices16bit = indicesData.Length() / indicesCount == sizeof(uint16);
const int32 trianglesCount = indicesCount / 3;
const bool indices16bit = indices.GetFormat() == PixelFormat::R16_UInt;
const int32 trianglesCount = indices.GetCount() / 3;
auto indicesData = indices.GetData();
// Sum triangle area for each influenced particle
invMasses.Resize(verticesCount);
@@ -676,12 +689,9 @@ void Cloth::CalculateInvMasses(Array<float>& invMasses)
const int32 i0 = indicesData.Get<uint16>()[index];
const int32 i1 = indicesData.Get<uint16>()[index + 1];
const int32 i2 = indicesData.Get<uint16>()[index + 2];
// TODO: use MeshAccessor vertex data layout descriptor instead hardcoded position data at the beginning of VB0
#define GET_POS(i) *(Float3*)((byte*)verticesData.Get() + i * verticesStride)
const Float3 v0(GET_POS(i0));
const Float3 v1(GET_POS(i1));
const Float3 v2(GET_POS(i2));
#undef GET_POS
const Float3 v0(positions.GetFloat3(i0));
const Float3 v1(positions.GetFloat3(i1));
const Float3 v2(positions.GetFloat3(i2));
const float area = Float3::TriangleArea(v0, v1, v2);
invMasses.Get()[i0] += area;
invMasses.Get()[i1] += area;
@@ -696,12 +706,9 @@ void Cloth::CalculateInvMasses(Array<float>& invMasses)
const int32 i0 = indicesData.Get<uint32>()[index];
const int32 i1 = indicesData.Get<uint32>()[index + 1];
const int32 i2 = indicesData.Get<uint32>()[index + 2];
// TODO: use MeshAccessor vertex data layout descriptor instead hardcoded position data at the beginning of VB0
#define GET_POS(i) *(Float3*)((byte*)verticesData.Get() + i * verticesStride)
const Float3 v0(GET_POS(i0));
const Float3 v1(GET_POS(i1));
const Float3 v2(GET_POS(i2));
#undef GET_POS
const Float3 v0(positions.GetFloat3(i0));
const Float3 v1(positions.GetFloat3(i1));
const Float3 v2(positions.GetFloat3(i2));
const float area = Float3::TriangleArea(v0, v1, v2);
invMasses.Get()[i0] += area;
invMasses.Get()[i1] += area;
@@ -787,25 +794,22 @@ bool Cloth::OnPreUpdate()
{
if (animatedModel->GraphInstance.NodesPose.IsEmpty() || _paint.IsEmpty())
return false;
const ModelInstanceActor::MeshReference mesh = GetMesh();
if (mesh.Actor == nullptr)
return false;
BytesContainer verticesData;
int32 verticesCount;
GPUVertexLayout* layout;
if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Vertex0, verticesData, verticesCount, &layout))
const ModelInstanceActor::MeshReference meshRef = GetMesh();
if (meshRef.Actor == nullptr)
return false;
MeshAccessor accessor;
if (accessor.LoadBuffer(MeshBufferType::Vertex0, verticesData, layout))
MeshBufferType bufferTypes[1] = { MeshBufferType::Vertex0 };
if (accessor.LoadMesh(meshRef.Get(), false, ToSpan(bufferTypes, 1)))
return false;
auto positionStream = accessor.Position();
auto blendIndicesStream = accessor.BlendIndices();
auto blendWeightsStream = accessor.BlendWeights();
if (!positionStream.IsValid() || !blendIndicesStream.IsValid() || !blendWeightsStream.IsValid())
return false;
const int32 verticesCount = positionStream.GetCount();
if (verticesCount != _paint.Count())
{
LOG(Warning, "Incorrect cloth '{}' paint size {} for mesh '{}' that has {} vertices", GetNamePath(), _paint.Count(), mesh.ToString(), verticesCount);
LOG(Warning, "Incorrect cloth '{}' paint size {} for mesh '{}' that has {} vertices", GetNamePath(), _paint.Count(), meshRef.ToString(), verticesCount);
return false;
}
PROFILE_CPU_NAMED("Skinned Pose");
@@ -925,7 +929,7 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat
return;
#if WITH_CLOTH
PROFILE_CPU_NAMED("Cloth");
PROFILE_MEM(Physics);
PROFILE_MEM(PhysicsCloth);
PhysicsBackend::LockClothParticles(_cloth);
const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth);
auto vbCount = (uint32)mesh->GetVertexCount();
@@ -934,49 +938,53 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat
// Calculate normals
Array<Float3> normals;
const ModelInstanceActor::MeshReference meshRef = GetMesh();
BytesContainer indicesData;
int32 indicesCount;
if ((_simulationSettings.ComputeNormals || deformation.Type == MeshBufferType::Vertex1) &&
meshRef.Actor && !meshRef.Actor->GetMeshData(meshRef, MeshBufferType::Index, indicesData, indicesCount))
if ((_simulationSettings.ComputeNormals || deformation.Type == MeshBufferType::Vertex1) && meshRef.Actor)
{
PROFILE_CPU_NAMED("Normals");
// TODO: optimize memory allocs (eg. use shared allocator)
normals.Resize(vbCount);
Platform::MemoryClear(normals.Get(), vbCount * sizeof(Float3));
const bool indices16bit = indicesData.Length() / indicesCount == sizeof(uint16);
const int32 trianglesCount = indicesCount / 3;
if (indices16bit)
MeshAccessor accessor;
MeshBufferType bufferTypes[1] = { MeshBufferType::Index };
if (!accessor.LoadMesh(meshRef.Get(), false, ToSpan(bufferTypes, 1)))
{
for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++)
PROFILE_CPU_NAMED("Normals");
auto indices = accessor.Index();
auto indicesData = indices.GetData();
// TODO: optimize memory allocs (eg. use shared allocator)
normals.Resize(vbCount);
Platform::MemoryClear(normals.Get(), vbCount * sizeof(Float3));
const bool indices16bit = indices.GetFormat() == PixelFormat::R16_UInt;
const int32 trianglesCount = indices.GetCount() / 3;
if (indices16bit)
{
const int32 index = triangleIndex * 3;
const int32 i0 = indicesData.Get<uint16>()[index];
const int32 i1 = indicesData.Get<uint16>()[index + 1];
const int32 i2 = indicesData.Get<uint16>()[index + 2];
const Float3 v0(particles.Get()[i0]);
const Float3 v1(particles.Get()[i1]);
const Float3 v2(particles.Get()[i2]);
const Float3 normal = Float3::Cross(v1 - v0, v2 - v0);
normals.Get()[i0] += normal;
normals.Get()[i1] += normal;
normals.Get()[i2] += normal;
for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++)
{
const int32 index = triangleIndex * 3;
const int32 i0 = indicesData.Get<uint16>()[index];
const int32 i1 = indicesData.Get<uint16>()[index + 1];
const int32 i2 = indicesData.Get<uint16>()[index + 2];
const Float3 v0(particles.Get()[i0]);
const Float3 v1(particles.Get()[i1]);
const Float3 v2(particles.Get()[i2]);
const Float3 normal = Float3::Cross(v1 - v0, v2 - v0);
normals.Get()[i0] += normal;
normals.Get()[i1] += normal;
normals.Get()[i2] += normal;
}
}
}
else
{
for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++)
else
{
const int32 index = triangleIndex * 3;
const int32 i0 = indicesData.Get<uint32>()[index];
const int32 i1 = indicesData.Get<uint32>()[index + 1];
const int32 i2 = indicesData.Get<uint32>()[index + 2];
const Float3 v0(particles.Get()[i0]);
const Float3 v1(particles.Get()[i1]);
const Float3 v2(particles.Get()[i2]);
const Float3 normal = Float3::Cross(v1 - v0, v2 - v0);
normals.Get()[i0] += normal;
normals.Get()[i1] += normal;
normals.Get()[i2] += normal;
for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++)
{
const int32 index = triangleIndex * 3;
const int32 i0 = indicesData.Get<uint32>()[index];
const int32 i1 = indicesData.Get<uint32>()[index + 1];
const int32 i2 = indicesData.Get<uint32>()[index + 2];
const Float3 v0(particles.Get()[i0]);
const Float3 v1(particles.Get()[i1]);
const Float3 v2(particles.Get()[i2]);
const Float3 normal = Float3::Cross(v1 - v0, v2 - v0);
normals.Get()[i0] += normal;
normals.Get()[i1] += normal;
normals.Get()[i2] += normal;
}
}
}
}
+1 -1
View File
@@ -371,6 +371,6 @@ private:
ImplementPhysicsDebug;
bool CreateCloth();
void DestroyCloth();
void CalculateInvMasses(Array<float>& invMasses);
void CalculateInvMasses(class MeshAccessor& accessor, Array<float>& invMasses);
void RunClothDeformer(const MeshBase* mesh, struct MeshDeformationData& deformation);
};
@@ -26,7 +26,7 @@ void SphereCollider::SetRadius(const float value)
void SphereCollider::DrawPhysicsDebug(RenderView& view)
{
const BoundingSphere sphere(_sphere.Center - view.Origin, _sphere.Radius);
const BoundingSphere sphere(_sphere.Center - view.Origin, Math::Abs(_sphere.Radius));
if (!view.CullingFrustum.Intersects(sphere))
return;
if (view.Mode == ViewMode::PhysicsColliders && !GetIsTrigger())
@@ -1201,6 +1201,7 @@ void ScenePhysX::UpdateVehicles(float dt)
void ScenePhysX::PreSimulateCloth(int32 i)
{
PROFILE_CPU();
PROFILE_MEM(PhysicsCloth);
auto clothPhysX = ClothsList[i];
auto& clothSettings = Cloths[clothPhysX];
@@ -1403,6 +1404,7 @@ void ScenePhysX::UpdateCloths(float dt)
if (!clothSolver || ClothsList.IsEmpty())
return;
PROFILE_CPU_NAMED("Physics.Cloth");
PROFILE_MEM(PhysicsCloth);
{
PROFILE_CPU_NAMED("Pre");
@@ -4029,7 +4031,7 @@ void PhysicsBackend::RemoveVehicle(void* scene, WheeledVehicle* actor)
void* PhysicsBackend::CreateCloth(const PhysicsClothDesc& desc)
{
PROFILE_CPU();
PROFILE_MEM(Physics);
PROFILE_MEM(PhysicsCloth);
#if USE_CLOTH_SANITY_CHECKS
{
// Sanity check
@@ -263,6 +263,7 @@ void InitProfilerMemory(const Char* cmdLine, int32 stage)
INIT_PARENT(Level, LevelTerrain);
INIT_PARENT(Navigation, NavigationMesh);
INIT_PARENT(Navigation, NavigationBuilding);
INIT_PARENT(Physics, PhysicsCloth);
INIT_PARENT(Scripting, ScriptingVisual);
INIT_PARENT(Scripting, ScriptingCSharp);
INIT_PARENT(ScriptingCSharp, ScriptingCSharpGCCommitted);
+2
View File
@@ -120,6 +120,8 @@ public:
// Total physics memory.
Physics,
// Cloth simulation and particles data.
PhysicsCloth,
// Total scripting memory allocated by game.
Scripting,
+2 -2
View File
@@ -2241,11 +2241,11 @@ void TerrainPatch::DestroyCollision()
_physicsHeightField = nullptr;
#if TERRAIN_USE_PHYSICS_DEBUG
_debugLinesDirty = true;
SAFE_DELETE(_debugLines);
SAFE_DELETE_GPU_RESOURCE(_debugLines);
#endif
#if USE_EDITOR
_collisionTriangles.Resize(0);
SAFE_DELETE(_collisionTrianglesBuffer);
SAFE_DELETE_GPU_RESOURCE(_collisionTrianglesBuffer);
_collisionTrianglesBufferDirty = true;
#endif
_collisionVertices.Resize(0);
@@ -349,7 +349,7 @@ void MeshAccelerationStructure::Add(Model* model, int32 lodIndex)
meshData.IndexBuffer.Copy(indexStream.GetData());
meshData.VertexBuffer.Allocate(meshData.Vertices * sizeof(Float3));
positionStream.CopyTo(ToSpan(meshData.VertexBuffer.Get<Float3>(), meshData.Vertices));
meshData.Use16BitIndexBuffer = mesh.Use16BitIndexBuffer();
meshData.Use16BitIndexBuffer = indexStream.GetFormat() == PixelFormat::R16_UInt;
meshData.Bounds = mesh.GetBox();
}
}
+1 -1
View File
@@ -595,7 +595,7 @@ namespace FlaxEngine.GUI
Size = new Float2(size.X - margin, size.Y),
Font = Font,
TextColor = TextColor * 0.9f,
TextColorHighlighted = TextColorHighlighted.Brightness < 0.05f ? Color.Lerp(TextColorHighlighted, Color.White, 0.3f) : TextColorHighlighted,
TextColorHighlighted = BackgroundColorSelected.Brightness < 0.05f ? Color.Lerp(TextColorHighlighted, Color.White, 0.3f) : TextColorHighlighted,
HorizontalAlignment = HorizontalAlignment,
VerticalAlignment = VerticalAlignment,
Text = _items[i],
@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml;
using Flax.Build.Graph;
@@ -26,6 +25,11 @@ namespace Flax.Build.Platforms
/// </summary>
protected readonly string _vcToolPath;
/// <summary>
/// The VC tools version.
/// </summary>
protected readonly string _vcToolVersion;
/// <summary>
/// The compiler path.
/// </summary>
@@ -147,6 +151,21 @@ namespace Flax.Build.Platforms
_libToolPath = Path.Combine(_vcToolPath, "lib.exe");
_xdcmakePath = Path.Combine(_vcToolPath, "xdcmake.exe");
// Find 'MSVC\XX.YY.ZZ\bin' to get version
_vcToolVersion = string.Empty;
var pathParts = _vcToolPath.Split('\\');
if (pathParts.Length >= 3)
{
for (int i = 3; i < pathParts.Length; i++)
{
if (pathParts[i] == "bin" && pathParts[i - 2] == "MSVC")
{
_vcToolVersion = pathParts[i - 1];
break;
}
}
}
// Add Visual C++ toolset include and library paths
var vcToolChainDir = toolsets[Toolset];
SystemIncludePaths.Add(Path.Combine(vcToolChainDir, "include"));
@@ -370,7 +389,7 @@ namespace Flax.Build.Platforms
public override void LogInfo()
{
var sdkPath = WindowsPlatformBase.GetSDKs()[SDK];
Log.Info(string.Format("Using Windows Toolset {0} ({1})", Toolset, sdkPath));
Log.Info(string.Format("Using Windows Toolset {0}, {2} ({1})", Toolset, sdkPath, _vcToolVersion));
Log.Info(string.Format("Using Windows SDK {0} ({1})", WindowsPlatformBase.GetSDKVersion(SDK), _vcToolPath));
}
@@ -673,8 +692,9 @@ namespace Flax.Build.Platforms
var pchSourceFile = Path.Combine(options.IntermediateFolder, Path.ChangeExtension(pchFilName, "cpp"));
var contents = Bindings.BindingsGenerator.GetStringBuilder();
contents.AppendLine("// This code was auto-generated. Do not modify it.");
// TODO: write compiler version to properly rebuild pch on Visual Studio updates
contents.Append("// Compiler: ").AppendLine(_compilerPath);
contents.Append("// Toolchain: ").AppendLine(_vcToolVersion);
contents.Append("// CppVersion: ").AppendLine(compileEnvironment.CppVersion.ToString());
contents.Append("#include \"").Append(pchSource).AppendLine("\"");
Utilities.WriteFileIfChanged(pchSourceFile, contents.ToString());
Bindings.BindingsGenerator.PutStringBuilder(contents);