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> /// </summary>
public static string WindowIcon = "Editor/EditorIcon"; public static string WindowIcon = "Editor/EditorIcon";
/// <summary>
/// The material used for the HS color wheel.
/// </summary>
public static string HSWheelMaterial = "Editor/HSWheel";
/// <summary> /// <summary>
/// The window icons font. /// The window icons font.
/// </summary> /// </summary>
+154 -114
View File
@@ -1,10 +1,11 @@
// Copyright (c) Wojciech Figat. All rights reserved. // Copyright (c) Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using FlaxEditor.GUI.Input; using FlaxEditor.GUI.Input;
using FlaxEditor.GUI.Tabs;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using FlaxEngine.Json; using FlaxEngine.Json;
using System.Collections.Generic;
namespace FlaxEditor.GUI.Dialogs namespace FlaxEditor.GUI.Dialogs
{ {
@@ -25,15 +26,16 @@ namespace FlaxEditor.GUI.Dialogs
/// <seealso cref="FlaxEditor.GUI.Dialogs.Dialog" /> /// <seealso cref="FlaxEditor.GUI.Dialogs.Dialog" />
public class ColorPickerDialog : Dialog, IColorPickerDialog public class ColorPickerDialog : Dialog, IColorPickerDialog
{ {
private const float ButtonsWidth = 60.0f;
private const float PickerMargin = 6.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 ChannelsMargin = 4.0f;
private const float ChannelTextWidth = 12.0f; private const float ChannelTextWidth = 12.0f;
private const float SavedColorButtonWidth = 20.0f; private const float SavedColorButtonWidth = 20.0f;
private const float SavedColorButtonHeight = 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 _initialValue;
private Color _value; private Color _value;
@@ -46,16 +48,19 @@ namespace FlaxEditor.GUI.Dialogs
private ColorValueBox.ColorPickerClosedEvent _onClosed; private ColorValueBox.ColorPickerClosedEvent _onClosed;
private ColorSelectorWithSliders _cSelector; private ColorSelectorWithSliders _cSelector;
private Tabs.Tabs _hsvRGBTabs;
private Tab _RGBTab;
private Panel _rgbPanel;
private FloatValueBox _cRed; private FloatValueBox _cRed;
private FloatValueBox _cGreen; private FloatValueBox _cGreen;
private FloatValueBox _cBlue; private FloatValueBox _cBlue;
private FloatValueBox _cAlpha; private Tab _hsvTab;
private Panel _hsvPanel;
private FloatValueBox _cHue; private FloatValueBox _cHue;
private FloatValueBox _cSaturation; private FloatValueBox _cSaturation;
private FloatValueBox _cValue; private FloatValueBox _cValue;
private TextBox _cHex; private TextBox _cHex;
private Button _cCancel; private FloatValueBox _cAlpha;
private Button _cOK;
private Button _cEyedropper; private Button _cEyedropper;
private Button _cLinearSRGB; private Button _cLinearSRGB;
@@ -127,97 +132,110 @@ namespace FlaxEditor.GUI.Dialogs
_savedColors = JsonSerializer.Deserialize<List<Color>>(savedColors); _savedColors = JsonSerializer.Deserialize<List<Color>>(savedColors);
// Selector // Selector
_cSelector = new ColorSelectorWithSliders(180, 18) _cSelector = new ColorSelectorWithSliders(180, 21)
{ {
Location = new Float2(PickerMargin, PickerMargin), Location = new Float2(PickerMargin, PickerMargin),
Parent = this Parent = this
}; };
_cSelector.ColorChanged += x => SelectedColor = x; _cSelector.ColorChanged += x => SelectedColor = x;
// Red _hsvRGBTabs = new Tabs.Tabs
_cRed = new FloatValueBox(0, _cSelector.Right + PickerMargin + RGBAMargin + ChannelTextWidth, PickerMargin, 100, 0, float.MaxValue, 0.001f)
{ {
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; _cRed.ValueChanged += OnRGBAChanged;
// Green // Green
_cGreen = new FloatValueBox(0, _cRed.X, _cRed.Bottom + ChannelsMargin, _cRed.Width, 0, float.MaxValue, 0.001f) _cGreen = new FloatValueBox(0, _cRed.X, _cRed.Bottom + ChannelsMargin, _cRed.Width, 0, float.MaxValue, 0.001f)
{ {
Parent = this Parent = _rgbPanel,
}; };
_cGreen.ValueChanged += OnRGBAChanged; _cGreen.ValueChanged += OnRGBAChanged;
// Blue // Blue
_cBlue = new FloatValueBox(0, _cRed.X, _cGreen.Bottom + ChannelsMargin, _cRed.Width, 0, float.MaxValue, 0.001f) _cBlue = new FloatValueBox(0, _cRed.X, _cGreen.Bottom + ChannelsMargin, _cRed.Width, 0, float.MaxValue, 0.001f)
{ {
Parent = this Parent = _rgbPanel,
}; };
_cBlue.ValueChanged += OnRGBAChanged; _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 // 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; _cHue.ValueChanged += OnHSVChanged;
// Saturation // Saturation
_cSaturation = new FloatValueBox(0, _cHue.X, _cHue.Bottom + ChannelsMargin, _cHue.Width, 0, 100.0f, 0.1f) _cSaturation = new FloatValueBox(0, _cHue.X, _cHue.Bottom + ChannelsMargin, _cHue.Width, 0, 100.0f, 0.1f)
{ {
Parent = this Parent = _hsvPanel,
}; };
_cSaturation.ValueChanged += OnHSVChanged; _cSaturation.ValueChanged += OnHSVChanged;
// Value // Value
_cValue = new FloatValueBox(0, _cHue.X, _cSaturation.Bottom + ChannelsMargin, _cHue.Width, 0, float.MaxValue, 0.1f) _cValue = new FloatValueBox(0, _cHue.X, _cSaturation.Bottom + ChannelsMargin, _cHue.Width, 0, float.MaxValue, 0.1f)
{ {
Parent = this Parent = _hsvPanel,
}; };
_cValue.ValueChanged += OnHSVChanged; _cValue.ValueChanged += OnHSVChanged;
// Set valid dialog size based on UI content // Alpha
_dialogSize = Size = new Float2(_cRed.Right + PickerMargin, 300); _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 // Hex
const float hexTextBoxWidth = 80; _cHex = new TextBox(false, _hsvRGBTabs.Left + HSVRGBTextWidth + ChannelsMargin, _cAlpha.Bottom + ChannelsMargin * 2.0f, ValueBoxesWidth)
_cHex = new TextBox(false, Width - hexTextBoxWidth - PickerMargin, _cSelector.Bottom + PickerMargin, hexTextBoxWidth)
{ {
Parent = this Parent = this,
}; };
_cHex.EditEnd += OnHexChanged; _cHex.EditEnd += OnHexChanged;
// Cancel // Set valid dialog size based on UI content
_cCancel = new Button(Width - ButtonsWidth - PickerMargin, Height - Button.DefaultHeight - PickerMargin, ButtonsWidth) _dialogSize = Size = new Float2(_hsvRGBTabs.Right + PickerMargin, _cHex.Bottom + 40.0f + ColorPreviewHeight + PickerMargin);
{
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;
// Create saved color buttons // Create saved color buttons
CreateAllSaveButtons(); CreateAllSavedColorsButtons();
// Eyedropper button // Eyedropper button
var style = Style.Current; 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), BackgroundBrush = new SpriteBrush(Editor.Instance.Icons.Search32),
BackgroundColor = style.Foreground, BackgroundColor = style.Foreground,
BackgroundColorHighlighted = style.Foreground.RGBMultiplied(0.9f), BackgroundColorHighlighted = style.Foreground.RGBMultiplied(0.9f),
@@ -226,14 +244,11 @@ namespace FlaxEditor.GUI.Dialogs
Parent = this, Parent = this,
}; };
_cEyedropper.Clicked += OnEyedropStart; _cEyedropper.Clicked += OnEyedropStart;
_cEyedropper.Height = (_cValue.Bottom - _cEyedropper.Y) * 0.5f;
_cEyedropper.Width = _cEyedropper.Height;
_cEyedropper.X -= _cEyedropper.Width;
// Linear/sRGB toggle button // 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), BackgroundBrush = new SpriteBrush(Editor.Instance.Icons.SplineAligned64),
BackgroundColor = _cEyedropper.BackgroundColor, BackgroundColor = _cEyedropper.BackgroundColor,
BackgroundColorHighlighted = _cEyedropper.BackgroundColorHighlighted, BackgroundColorHighlighted = _cEyedropper.BackgroundColorHighlighted,
@@ -261,12 +276,10 @@ namespace FlaxEditor.GUI.Dialogs
foreach (var color in _savedColors) foreach (var color in _savedColors)
{ {
if (color == _value) if (color == _value)
{
return; return;
}
} }
// Set color of button to current value; // Set color of button to current value
button.BackgroundColor = _value; button.BackgroundColor = _value;
button.BackgroundColorHighlighted = _value; button.BackgroundColorHighlighted = _value;
button.BackgroundColorSelected = _value.RGBMultiplied(0.8f); button.BackgroundColorSelected = _value.RGBMultiplied(0.8f);
@@ -278,10 +291,10 @@ namespace FlaxEditor.GUI.Dialogs
var savedColors = JsonSerializer.Serialize(_savedColors, typeof(List<Color>)); var savedColors = JsonSerializer.Serialize(_savedColors, typeof(List<Color>));
Editor.Instance.ProjectCache.SetCustomData("ColorPickerSavedColors", savedColors); Editor.Instance.ProjectCache.SetCustomData("ColorPickerSavedColors", savedColors);
// create new + button // Create new + button
if (_savedColorButtons.Count < 8) 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 = "+", Text = "+",
Parent = this, 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) private void OnColorPicked(Color32 colorPicked)
{ {
if (_activeEyedropper) if (_activeEyedropper)
{ {
_activeEyedropper = false; _activeEyedropper = false;
_cEyedropper.BackgroundColor = _cEyedropper.BackgroundColorHighlighted = Style.Current.Foreground;
if (colorPicked != Color.Transparent) if (colorPicked != Color.Transparent)
{ {
Color color = colorPicked; Color color = colorPicked;
@@ -317,6 +351,7 @@ namespace FlaxEditor.GUI.Dialogs
private void OnEyedropStart() private void OnEyedropStart()
{ {
_activeEyedropper = true; _activeEyedropper = true;
_cEyedropper.BackgroundColor = _cEyedropper.BackgroundColorHighlighted = Style.Current.BackgroundHighlighted;
Platform.PickScreenColor(); Platform.PickScreenColor();
Platform.PickScreenColorDone += OnColorPicked; Platform.PickScreenColorDone += OnColorPicked;
} }
@@ -334,6 +369,7 @@ namespace FlaxEditor.GUI.Dialogs
if (_disableEvents) if (_disableEvents)
return; return;
_cHue.Value = Mathf.Wrap(_cHue.Value, 0f, 360f);
SelectedColor = Color.FromHSV(_cHue.Value, _cSaturation.Value / 100.0f, _cValue.Value / 100.0f, _cAlpha.Value); 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(); base.Draw();
// RGBA switch (_hsvRGBTabs.SelectedTabIndex)
var rgbaR = new Rectangle(_cRed.Left - ChannelTextWidth, _cRed.Y, 10000, _cRed.Height); {
Render2D.DrawText(style.FontMedium, "R", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center); // RGB
rgbaR.Location.Y = _cGreen.Y; case 0:
Render2D.DrawText(style.FontMedium, "G", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center); var rgbRect = new Rectangle(_hsvRGBTabs.Left + PickerMargin, _hsvRGBTabs.Top + TabHeight + PickerMargin, 10000, _cRed.Height);
rgbaR.Location.Y = _cBlue.Y; Render2D.DrawText(style.FontMedium, "R", rgbRect, textColor, TextAlignment.Near, TextAlignment.Center);
Render2D.DrawText(style.FontMedium, "B", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center); rgbRect.Location.Y += _cRed.Height + ChannelsMargin;
rgbaR.Location.Y = _cAlpha.Y; Render2D.DrawText(style.FontMedium, "G", rgbRect, textColor, TextAlignment.Near, TextAlignment.Center);
Render2D.DrawText(style.FontMedium, "A", rgbaR, 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 // Right
var hsvHl = new Rectangle(_cHue.Left - ChannelTextWidth, _cHue.Y, 10000, _cHue.Height); var hsvRightRect = new Rectangle(_hsvRGBTabs.Right - HSVRGBTextWidth, _hsvRGBTabs.Top + TabHeight + PickerMargin, ChannelTextWidth, _cHue.Height);
Render2D.DrawText(style.FontMedium, "H", hsvHl, textColor, TextAlignment.Near, TextAlignment.Center); Render2D.DrawText(style.FontMedium, "°", hsvRightRect, textColor, TextAlignment.Near, TextAlignment.Center);
hsvHl.Location.Y = _cSaturation.Y; hsvRightRect.Location.Y += _cHue.Height + ChannelsMargin;
Render2D.DrawText(style.FontMedium, "S", hsvHl, textColor, TextAlignment.Near, TextAlignment.Center); Render2D.DrawText(style.FontMedium, "%", hsvRightRect, textColor, TextAlignment.Near, TextAlignment.Center);
hsvHl.Location.Y = _cValue.Y; hsvRightRect.Location.Y += _cHue.Height + ChannelsMargin;
Render2D.DrawText(style.FontMedium, "V", hsvHl, textColor, TextAlignment.Near, TextAlignment.Center); Render2D.DrawText(style.FontMedium, "%", hsvRightRect, textColor, TextAlignment.Near, TextAlignment.Center);
break;
}
// HSV right // A
var hsvHr = new Rectangle(_cHue.Right + 2, _cHue.Y, 10000, _cHue.Height); var alphaHexRect = new Rectangle(_hsvRGBTabs.Left + PickerMargin, _cAlpha.Top, ChannelTextWidth, _cAlpha.Height);
Render2D.DrawText(style.FontMedium, "°", hsvHr, textColor, TextAlignment.Near, TextAlignment.Center); Render2D.DrawText(style.FontMedium, "A", alphaHexRect, 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);
// Hex // Hex
var hex = new Rectangle(_cHex.Left - 26, _cHex.Y, 10000, _cHex.Height); alphaHexRect.Y += _cAlpha.Height + ChannelsMargin * 2.0f;
Render2D.DrawText(style.FontMedium, "Hex", hex, textColor, TextAlignment.Near, TextAlignment.Center); 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 // Color difference
var newRect = new Rectangle(_cOK.X - 3, _cHex.Bottom + PickerMargin, 130, 0); var differenceRect = new Rectangle(_hsvRGBTabs.Left, _cHex.Bottom + 40.0f, _hsvRGBTabs.Width, ColorPreviewHeight);
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);
// Draw checkerboard for background of color to help with transparency // 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 i = 0; i < numHor; i++)
{ {
for (int j = 0; j < numVer; j++) for (int j = 0; j < numVer; j++)
{ {
if ((i + j) % 2 == 0) 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(rect, Color.Gray);
} }
} }
} }
Render2D.FillRectangle(newRect, _linear ? _value.ToSRgb() : _value); Render2D.PopClip();
Render2D.FillRectangle(differenceRect, _linear ? _value.ToSRgb() : _value);
} }
/// <inheritdoc /> /// <inheritdoc />
protected override void OnShow() protected override void OnShow()
{ {
// Auto cancel on lost focus // Apply changes on lost focus
#if !PLATFORM_LINUX #if !PLATFORM_LINUX
((WindowRootControl)Root).Window.LostFocus += OnWindowLostFocus; ((WindowRootControl)Root).Window.LostFocus += OnSubmit;
#endif #endif
base.OnShow(); base.OnShow();
@@ -444,6 +492,7 @@ namespace FlaxEditor.GUI.Dialogs
{ {
// Cancel eye dropping // Cancel eye dropping
_activeEyedropper = false; _activeEyedropper = false;
_cEyedropper.BackgroundColor = _cEyedropper.BackgroundColorHighlighted = Style.Current.Foreground;
Platform.PickScreenColorDone -= OnColorPicked; Platform.PickScreenColorDone -= OnColorPicked;
return true; return true;
} }
@@ -455,9 +504,7 @@ namespace FlaxEditor.GUI.Dialogs
public override bool OnMouseUp(Float2 location, MouseButton button) public override bool OnMouseUp(Float2 location, MouseButton button)
{ {
if (base.OnMouseUp(location, button)) if (base.OnMouseUp(location, button))
{
return true; return true;
}
var child = GetChildAtRecursive(location); var child = GetChildAtRecursive(location);
if (button == MouseButton.Right && child is Button b && b.Tag is Color c) if (button == MouseButton.Right && child is Button b && b.Tag is Color c)
@@ -519,20 +566,20 @@ namespace FlaxEditor.GUI.Dialogs
} }
_savedColorButtons.Clear(); _savedColorButtons.Clear();
CreateAllSaveButtons(); CreateAllSavedColorsButtons();
// Save new colors // Save new colors
var savedColors = JsonSerializer.Serialize(_savedColors, typeof(List<Color>)); var savedColors = JsonSerializer.Serialize(_savedColors, typeof(List<Color>));
Editor.Instance.ProjectCache.SetCustomData("ColorPickerSavedColors", savedColors); Editor.Instance.ProjectCache.SetCustomData("ColorPickerSavedColors", savedColors);
} }
private void CreateAllSaveButtons() private void CreateAllSavedColorsButtons()
{ {
// Create saved color buttons // Create saved color buttons
for (int i = 0; i < _savedColors.Count; i++) for (int i = 0; i < _savedColors.Count; i++)
{ {
var savedColor = _savedColors[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, Parent = this,
Tag = savedColor, Tag = savedColor,
@@ -543,9 +590,9 @@ namespace FlaxEditor.GUI.Dialogs
savedColorButton.ButtonClicked += OnSavedColorButtonClicked; savedColorButton.ButtonClicked += OnSavedColorButtonClicked;
_savedColorButtons.Add(savedColorButton); _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 = "+", Text = "+",
Parent = this, 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 /> /// <inheritdoc />
public override void OnSubmit() public override void OnSubmit()
{ {
@@ -577,6 +611,9 @@ namespace FlaxEditor.GUI.Dialogs
return; return;
_disableEvents = true; _disableEvents = true;
// Ensure the cursor is restored
Cursor = CursorType.Default;
// Send color event if modified // Send color event if modified
if (_value != _initialValue) if (_value != _initialValue)
{ {
@@ -593,6 +630,9 @@ namespace FlaxEditor.GUI.Dialogs
return; return;
_disableEvents = true; _disableEvents = true;
// Ensure the cursor is restored
Cursor = CursorType.Default;
// Restore color if modified // Restore color if modified
if (_useDynamicEditing && _initialValue != _value && _canPassLastChangeEvent) if (_useDynamicEditing && _initialValue != _value && _canPassLastChangeEvent)
{ {
+114 -56
View File
@@ -12,6 +12,13 @@ namespace FlaxEditor.GUI.Dialogs
/// <seealso cref="FlaxEngine.GUI.ContainerControl" /> /// <seealso cref="FlaxEngine.GUI.ContainerControl" />
public class ColorSelector : 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> /// <summary>
/// The color. /// The color.
/// </summary> /// </summary>
@@ -22,9 +29,22 @@ namespace FlaxEditor.GUI.Dialogs
/// </summary> /// </summary>
protected Rectangle _wheelRect; protected Rectangle _wheelRect;
private readonly SpriteHandle _colorWheelSprite; private readonly MaterialBase _hsWheelMaterial;
private bool _isMouseDownWheel; 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> /// <summary>
/// Occurs when selected color gets changed. /// Occurs when selected color gets changed.
/// </summary> /// </summary>
@@ -78,7 +98,8 @@ namespace FlaxEditor.GUI.Dialogs
{ {
AutoFocus = true; AutoFocus = true;
_colorWheelSprite = Editor.Instance.Icons.ColorWheel128; _hsWheelMaterial = FlaxEngine.Content.LoadAsyncInternal<MaterialBase>(EditorAssets.HSWheelMaterial);
_hsWheelMaterial = _hsWheelMaterial.CreateVirtualInstance();
_wheelRect = new Rectangle(0, 0, wheelSize, wheelSize); _wheelRect = new Rectangle(0, 0, wheelSize, wheelSize);
} }
@@ -165,17 +186,19 @@ namespace FlaxEditor.GUI.Dialogs
{ {
base.Draw(); base.Draw();
var hsv = _color.ToHSV();
bool enabled = EnabledInHierarchy; bool enabled = EnabledInHierarchy;
_hsWheelMaterial.SetParameterValue(GrayedOutParamName, enabled ? 1.0f : 0.5f);
Render2D.DrawMaterial(_hsWheelMaterial, _wheelRect, enabled ? Color.White : Color.Gray);
// Wheel // Wheel
float boxExpand = (2.0f * 4.0f / 128.0f) * _wheelRect.Width; float boxExpand = (2.0f * 4.0f / 128.0f) * _wheelRect.Width;
Render2D.DrawSprite(_colorWheelSprite, _wheelRect.MakeExpanded(boxExpand), enabled ? Color.White : Color.Gray); Render2D.DrawMaterial(_hsWheelMaterial, _wheelRect, enabled ? Color.White : Color.Gray);
float hAngle = hsv.X * Mathf.DegreesToRadians; Float3 hsv = _color.ToHSV();
float hRadius = hsv.Y * _wheelRect.Width * 0.5f; Color hsColor = Color.FromHSV(new Float3(hsv.X, hsv.Y, 1));
var hsPos = new Float2(hRadius * Mathf.Cos(hAngle), -hRadius * Mathf.Sin(hAngle)); Rectangle wheelRect = wheelDragRect;
const float wheelBoxSize = 4.0f; Render2D.FillRectangle(wheelRect, hsColor);
Render2D.DrawRectangle(new Rectangle(hsPos - (wheelBoxSize * 0.5f) + _wheelRect.Center, new Float2(wheelBoxSize)), _isMouseDownWheel ? Color.Gray : Color.Black); Render2D.DrawRectangle(wheelRect, Color.Black, _isMouseDownWheel ? 2.0f : 1.0f);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -194,6 +217,15 @@ namespace FlaxEditor.GUI.Dialogs
base.OnMouseMove(location); base.OnMouseMove(location);
} }
/// <inheritdoc />
public override void OnMouseMoveRelative(Float2 motion)
{
var location = PointFromScreen(FlaxEngine.Input.MouseScreenPosition);
UpdateMouse(ref location);
base.OnMouseMoveRelative(motion);
}
/// <inheritdoc /> /// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button) public override bool OnMouseDown(Float2 location, MouseButton button)
{ {
@@ -202,6 +234,7 @@ namespace FlaxEditor.GUI.Dialogs
if (!_isMouseDownWheel) if (!_isMouseDownWheel)
{ {
_isMouseDownWheel = true; _isMouseDownWheel = true;
Cursor = CursorType.Hidden;
StartMouseCapture(); StartMouseCapture();
SlidingStart?.Invoke(); SlidingStart?.Invoke();
} }
@@ -218,6 +251,10 @@ namespace FlaxEditor.GUI.Dialogs
if (button == MouseButton.Left && _isMouseDownWheel) if (button == MouseButton.Left && _isMouseDownWheel)
{ {
EndMouseCapture(); 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(); EndSliding();
return true; return true;
} }
@@ -246,10 +283,10 @@ namespace FlaxEditor.GUI.Dialogs
/// <seealso cref="ColorSelector" /> /// <seealso cref="ColorSelector" />
public class ColorSelectorWithSliders : ColorSelector public class ColorSelectorWithSliders : ColorSelector
{ {
private Rectangle _slider1Rect; private Rectangle _valueSliderRect;
private Rectangle _slider2Rect; private Rectangle _alphaSliderRect;
private bool _isMouseDownSlider1; private bool _isMouseDownValueSlider;
private bool _isMouseDownSlider2; private bool _isMouseDownAlphaSlider;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ColorSelectorWithSliders"/> class. /// Initializes a new instance of the <see cref="ColorSelectorWithSliders"/> class.
@@ -260,26 +297,26 @@ namespace FlaxEditor.GUI.Dialogs
: base(wheelSize) : base(wheelSize)
{ {
// Setup dimensions // Setup dimensions
const float slidersMargin = 8.0f; const float slidersMargin = 10.0f;
_slider1Rect = new Rectangle(wheelSize + slidersMargin, 0, slidersThickness, wheelSize); _valueSliderRect = new Rectangle(wheelSize + slidersMargin, 0, slidersThickness, wheelSize);
_slider2Rect = new Rectangle(_slider1Rect.Right + slidersMargin, _slider1Rect.Y, slidersThickness, _slider1Rect.Height); _alphaSliderRect = new Rectangle(_valueSliderRect.Right + slidersMargin * 1.5f, _valueSliderRect.Y, slidersThickness, _valueSliderRect.Height);
Size = new Float2(_slider2Rect.Right, wheelSize); Size = new Float2(_alphaSliderRect.Right, wheelSize);
} }
/// <inheritdoc /> /// <inheritdoc />
protected override void UpdateMouse(ref Float2 location) protected override void UpdateMouse(ref Float2 location)
{ {
if (_isMouseDownSlider1) if (_isMouseDownValueSlider)
{ {
var hsv = _color.ToHSV(); 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); Color = Color.FromHSV(hsv, _color.A);
} }
else if (_isMouseDownSlider2) else if (_isMouseDownAlphaSlider)
{ {
var color = _color; 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; Color = color;
} }
@@ -296,56 +333,69 @@ namespace FlaxEditor.GUI.Dialogs
// Cache data // Cache data
var style = Style.Current; var style = Style.Current;
var features = Render2D.Features;
Render2D.Features = features & ~Render2D.RenderingFeatures.VertexSnapping;
var hsv = _color.ToHSV(); var hsv = _color.ToHSV();
var hs = hsv; var hs = hsv;
hs.Z = 1.0f; hs.Z = 1.0f;
Color hsC = Color.FromHSV(hs); Color hsC = Color.FromHSV(hs);
const float slidersOffset = 3.0f;
const float slidersThickness = 4.0f;
// Value // Value slider
float valueY = _slider2Rect.Height * (1 - hsv.Z); float valueKnobExpand = _isMouseDownValueSlider ? 10.0f : 4.0f;
var valueR = new Rectangle(_slider1Rect.X - slidersOffset, _slider1Rect.Y + valueY - slidersThickness / 2, _slider1Rect.Width + slidersOffset * 2, slidersThickness); float valueY = _valueSliderRect.Height * (1 - hsv.Z);
Render2D.FillRectangle(_slider1Rect, hsC, hsC, Color.Black, Color.Black); float valueKnobWidth = _valueSliderRect.Width + valueKnobExpand;
Render2D.DrawRectangle(_slider1Rect, _isMouseDownSlider1 ? style.BackgroundSelected : Color.Black); float valueKnobHeight = _isMouseDownValueSlider ? 7.0f : 4.0f;
Render2D.DrawRectangle(valueR, _isMouseDownSlider1 ? Color.White : Color.Gray); 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 // Draw checkerboard pattern as background of alpha slider
var alphaRect = _slider2Rect; Render2D.FillRectangle(_alphaSliderRect, Color.White);
Render2D.FillRectangle(alphaRect, Color.White); var smallRectSize = _alphaSliderRect.Width / 2.0f;
var smallRectSize = alphaRect.Width * 0.5f; var numHor = Mathf.CeilToInt(_alphaSliderRect.Width / smallRectSize);
var numHor = Mathf.CeilToInt(alphaRect.Width / smallRectSize); var numVer = Mathf.CeilToInt(_alphaSliderRect.Height / smallRectSize);
var numVer = Mathf.CeilToInt(alphaRect.Height / smallRectSize); Render2D.PushClip(_alphaSliderRect);
for (int i = 0; i < numHor; i++) for (int i = 0; i < numHor; i++)
{ {
for (int j = 0; j < numVer; j++) for (int j = 0; j < numVer; j++)
{ {
if ((i + j) % 2 == 0) if ((i + j) % 2 == 0)
{ {
var rect = new Rectangle(alphaRect.X + smallRectSize * i, alphaRect.Y + smallRectSize * j, new Float2(smallRectSize)); var rect = new Rectangle(_alphaSliderRect.X + smallRectSize * i, _alphaSliderRect.Y + smallRectSize * j, new Float2(smallRectSize));
Render2D.PushClip(alphaRect);
Render2D.FillRectangle(rect, Color.Gray); Render2D.FillRectangle(rect, Color.Gray);
Render2D.PopClip();
} }
} }
} }
Render2D.PopClip();
// Alpha // Alpha slider
float alphaY = _slider2Rect.Height * (1 - _color.A); float alphaKnobExpand = _isMouseDownAlphaSlider ? 10.0f : 4.0f;
var alphaR = new Rectangle(_slider2Rect.X - slidersOffset, _slider2Rect.Y + alphaY - slidersThickness / 2, _slider2Rect.Width + slidersOffset * 2, slidersThickness); 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; var color = _color;
color.A = 1; // Keep slider 2 fill rect from changing color alpha while selecting. color.A = 1; // Prevent alpha slider fill from becoming transparent
Render2D.FillRectangle(_slider2Rect, color, color, Color.Transparent, Color.Transparent); Render2D.FillRectangle(_alphaSliderRect, color, color, Color.Transparent, Color.Transparent);
Render2D.DrawRectangle(_slider2Rect, _isMouseDownSlider2 ? style.BackgroundSelected : Color.Black); // Draw one black and one white border to make the knob visible at any saturation level
Render2D.DrawRectangle(alphaR, _isMouseDownSlider2 ? Color.White : Color.Gray); Render2D.DrawRectangle(alphaKnobRect, Color.White, _isMouseDownAlphaSlider ? 3.0f : 2.0f);
Render2D.DrawRectangle(alphaKnobRect, Color.Black, _isMouseDownAlphaSlider ? 2.0f : 1.0f);
Render2D.Features = features;
} }
/// <inheritdoc /> /// <inheritdoc />
public override void OnLostFocus() public override void OnLostFocus()
{ {
// Clear flags // Clear flags
_isMouseDownSlider1 = false; _isMouseDownValueSlider = false;
_isMouseDownSlider2 = false; _isMouseDownAlphaSlider = false;
base.OnLostFocus(); base.OnLostFocus();
} }
@@ -353,15 +403,17 @@ namespace FlaxEditor.GUI.Dialogs
/// <inheritdoc /> /// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button) 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(); StartMouseCapture();
UpdateMouse(ref location); 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(); StartMouseCapture();
UpdateMouse(ref location); UpdateMouse(ref location);
} }
@@ -372,10 +424,16 @@ namespace FlaxEditor.GUI.Dialogs
/// <inheritdoc /> /// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button) public override bool OnMouseUp(Float2 location, MouseButton button)
{ {
if (button == MouseButton.Left && (_isMouseDownSlider1 || _isMouseDownSlider2)) if (button == MouseButton.Left && (_isMouseDownValueSlider || _isMouseDownAlphaSlider))
{ {
_isMouseDownSlider1 = false; // Make the cursor appear where the user expects it to be (center of slider horizontally and slider knob position vertically)
_isMouseDownSlider2 = false; 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(); EndMouseCapture();
return true; return true;
} }
@@ -387,8 +445,8 @@ namespace FlaxEditor.GUI.Dialogs
public override void OnEndMouseCapture() public override void OnEndMouseCapture()
{ {
// Clear flags // Clear flags
_isMouseDownSlider1 = false; _isMouseDownValueSlider = false;
_isMouseDownSlider2 = false; _isMouseDownAlphaSlider = false;
base.OnEndMouseCapture(); base.OnEndMouseCapture();
} }
+3 -4
View File
@@ -2148,10 +2148,9 @@ namespace FlaxEditor.GUI.Timeline
/// </summary> /// </summary>
public void ShowWholeTimeline() public void ShowWholeTimeline()
{ {
var viewWidth = Width; const float padding = 40f;
var timelineWidth = Duration * UnitsPerSecond * Zoom + 8 * StartOffset; Zoom = (_backgroundArea.Width - padding * 2f) / (Duration * UnitsPerSecond);
_backgroundArea.ViewOffset = Float2.Zero; _backgroundArea.ViewOffset = new Float2(-_leftEdge.X + padding, _backgroundArea.ViewOffset.Y);
Zoom = viewWidth / timelineWidth;
} }
/// <summary> /// <summary>
@@ -253,13 +253,6 @@ 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>
/// 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> /// <summary>
/// Gets or sets the formatting option for numeric values in the editor. /// Gets or sets the formatting option for numeric values in the editor.
/// </summary> /// </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(AudioSource), typeof(AudioSourceNode));
CustomNodesTypes.Add(typeof(BoneSocket), typeof(BoneSocketNode)); CustomNodesTypes.Add(typeof(BoneSocket), typeof(BoneSocketNode));
CustomNodesTypes.Add(typeof(Decal), typeof(DecalNode)); CustomNodesTypes.Add(typeof(Decal), typeof(DecalNode));
CustomNodesTypes.Add(typeof(Cloth), typeof(ClothNode));
CustomNodesTypes.Add(typeof(BoxCollider), typeof(BoxColliderNode)); CustomNodesTypes.Add(typeof(BoxCollider), typeof(BoxColliderNode));
CustomNodesTypes.Add(typeof(SphereCollider), typeof(ColliderNode)); CustomNodesTypes.Add(typeof(SphereCollider), typeof(ColliderNode));
CustomNodesTypes.Add(typeof(CapsuleCollider), typeof(ColliderNode)); CustomNodesTypes.Add(typeof(CapsuleCollider), typeof(ColliderNode));
+102 -102
View File
@@ -22,15 +22,97 @@ namespace FlaxEditor.Surface.Archetypes
[HideInEditor] [HideInEditor]
public static class Parameters 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> /// <summary>
/// Surface node type for parameters group Get node. /// Surface node type for parameters group Get node.
/// </summary> /// </summary>
/// <seealso cref="FlaxEditor.Surface.SurfaceNode" /> /// <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 readonly List<ISurfaceNodeElement> _dynamicChildren = new List<ISurfaceNodeElement>();
private bool _isUpdateLocked;
private ScriptType _layoutType; private ScriptType _layoutType;
private NodeElementArchetype[] _layoutElements; private NodeElementArchetype[] _layoutElements;
@@ -306,49 +388,6 @@ namespace FlaxEditor.Surface.Archetypes
UpdateTitle(); 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() private void ClearDynamicElements()
{ {
for (int i = 0; i < _dynamicChildren.Count; i++) for (int i = 0; i < _dynamicChildren.Count; i++)
@@ -463,15 +502,19 @@ namespace FlaxEditor.Surface.Archetypes
else if (!_isUpdateLocked) else if (!_isUpdateLocked)
{ {
_isUpdateLocked = true; _isUpdateLocked = true;
int toSelect = -1; string toSelect = null;
Guid loadedSelected = (Guid)Values[0]; 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) if (param.ID == loadedSelected)
toSelect = i; {
toSelect = param.Name;
break;
}
} }
_combobox.SelectedIndex = toSelect; _combobox.SelectedItem = toSelect;
_isUpdateLocked = false; _isUpdateLocked = false;
} }
UpdateLayout(); UpdateLayout();
@@ -817,66 +860,23 @@ namespace FlaxEditor.Surface.Archetypes
/// Surface node type for parameters group Set node. /// Surface node type for parameters group Set node.
/// </summary> /// </summary>
/// <seealso cref="FlaxEditor.Surface.SurfaceNode" /> /// <seealso cref="FlaxEditor.Surface.SurfaceNode" />
public class SurfaceNodeParamsSet : SurfaceNode, IParametersDependantNode public class SurfaceNodeParamsSet : SurfaceNodeParamsBase, IParametersDependantNode
{ {
private ComboBoxElement _combobox;
private bool _isUpdateLocked;
/// <inheritdoc /> /// <inheritdoc />
public SurfaceNodeParamsSet(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) public SurfaceNodeParamsSet(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch) : base(id, context, nodeArch, groupArch)
{ {
} }
private void UpdateCombo() /// <inheritdoc />
protected override void Set(SurfaceParameter selected, ref Guid selectedID)
{ {
if (_isUpdateLocked) SetValues(new[]
return;
_isUpdateLocked = true;
if (_combobox == null)
{ {
_combobox = GetChild<ComboBoxElement>(); selectedID,
_combobox.SelectedIndexChanged += OnSelectedChanged; selected != null ? TypeUtils.GetDefaultValue(selected.Type) : null,
} });
int toSelect = -1; UpdateUI();
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]);
} }
/// <inheritdoc /> /// <inheritdoc />
+5
View File
@@ -583,6 +583,11 @@ namespace FlaxEditor.Surface
/// </summary> /// </summary>
public virtual bool CanSetParameters => false; 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> /// <summary>
/// True of the context menu should make use of a description panel drawn at the bottom of the menu /// True of the context menu should make use of a description panel drawn at the bottom of the menu
/// </summary> /// </summary>
@@ -254,9 +254,10 @@ namespace FlaxEditor.Surface
public SurfaceParameter GetParameter(Guid id) public SurfaceParameter GetParameter(Guid id)
{ {
SurfaceParameter result = null; 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) if (parameter.ID == id)
{ {
result = parameter; result = parameter;
@@ -274,9 +275,10 @@ namespace FlaxEditor.Surface
public SurfaceParameter GetParameter(string name) public SurfaceParameter GetParameter(string name)
{ {
SurfaceParameter result = null; 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) if (parameter.Name == name)
{ {
result = parameter; result = parameter;
@@ -187,6 +187,9 @@ namespace FlaxEditor.Surface
/// <inheritdoc /> /// <inheritdoc />
public override bool CanSetParameters => true; public override bool CanSetParameters => true;
/// <inheritdoc />
public override bool CanShowPrivateParameters => true;
/// <inheritdoc /> /// <inheritdoc />
public override bool UseContextMenuDescriptionPanel => true; public override bool UseContextMenuDescriptionPanel => true;
@@ -198,6 +198,11 @@ namespace FlaxEditor.Viewport
actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation);
_owner.Spawn(actor); _owner.Spawn(actor);
_viewport.Focus(); _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) 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.Enabled = true;
_timeline.SetNoTracksText(null); _timeline.SetNoTracksText(null);
ClearEditedFlag(); ClearEditedFlag();
_timeline.ShowWholeTimeline();
} }
} }
+18 -5
View File
@@ -691,11 +691,7 @@ Asset::LoadResult Animation::load()
continue; continue;
} }
#if USE_EDITOR #if USE_EDITOR
if (!_registeredForScriptingReload) _registerForScriptingReload = true;
{
_registeredForScriptingReload = true;
Level::ScriptsReloadStart.Bind<Animation, &Animation::OnScriptsReloadStart>(this);
}
#endif #endif
} }
} }
@@ -733,6 +729,7 @@ void Animation::unload(bool isReloading)
{ {
ScopeWriteLock systemScope(Animations::SystemLocker); ScopeWriteLock systemScope(Animations::SystemLocker);
#if USE_EDITOR #if USE_EDITOR
_registerForScriptingReload = false;
if (_registeredForScriptingReload) if (_registeredForScriptingReload)
{ {
_registeredForScriptingReload = false; _registeredForScriptingReload = false;
@@ -752,6 +749,22 @@ void Animation::unload(bool isReloading)
NestedAnims.Clear(); 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 AssetChunksFlag Animation::getChunksToPreload() const
{ {
return GET_CHUNK_FLAG(0); return GET_CHUNK_FLAG(0);
+4
View File
@@ -78,6 +78,7 @@ API_CLASS(NoSpawn) class FLAXENGINE_API Animation : public BinaryAsset
private: private:
#if USE_EDITOR #if USE_EDITOR
bool _registerForScriptingReload = false;
bool _registeredForScriptingReload = false; bool _registeredForScriptingReload = false;
void OnScriptsReloadStart(); void OnScriptsReloadStart();
#endif #endif
@@ -163,5 +164,8 @@ protected:
// [BinaryAsset] // [BinaryAsset]
LoadResult load() override; LoadResult load() override;
void unload(bool isReloading) override; void unload(bool isReloading) override;
#if USE_EDITOR
void onLoaded_MainThread() override;
#endif
AssetChunksFlag getChunksToPreload() const override; AssetChunksFlag getChunksToPreload() const override;
}; };
@@ -413,10 +413,16 @@ namespace FlaxEngine
return true; return true;
for (int i = 0; i < buffersLocal.Length; i++) for (int i = 0; i < buffersLocal.Length; i++)
{ {
_data[(int)buffersLocal[i]] = meshBuffers[i]; int buffer = (int)buffersLocal[i];
_layouts[(int)buffersLocal[i]] = meshLayouts[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; return false;
} }
@@ -39,51 +39,61 @@ public:
FORCE_INLINE int32 GetInt(int32 index) const FORCE_INLINE int32 GetInt(int32 index) const
{ {
ASSERT_LOW_LAYER(index * _stride < _data.Length());
return (int32)_sampler.Read(_data.Get() + index * _stride).X; return (int32)_sampler.Read(_data.Get() + index * _stride).X;
} }
FORCE_INLINE float GetFloat(int32 index) const FORCE_INLINE float GetFloat(int32 index) const
{ {
ASSERT_LOW_LAYER(index * _stride < _data.Length());
return _sampler.Read(_data.Get() + index * _stride).X; return _sampler.Read(_data.Get() + index * _stride).X;
} }
FORCE_INLINE Float2 GetFloat2(int32 index) const FORCE_INLINE Float2 GetFloat2(int32 index) const
{ {
ASSERT_LOW_LAYER(index * _stride < _data.Length());
return Float2(_sampler.Read(_data.Get() + index * _stride)); return Float2(_sampler.Read(_data.Get() + index * _stride));
} }
FORCE_INLINE Float3 GetFloat3(int32 index) const FORCE_INLINE Float3 GetFloat3(int32 index) const
{ {
ASSERT_LOW_LAYER(index * _stride < _data.Length());
return Float3(_sampler.Read(_data.Get() + index * _stride)); return Float3(_sampler.Read(_data.Get() + index * _stride));
} }
FORCE_INLINE Float4 GetFloat4(int32 index) const FORCE_INLINE Float4 GetFloat4(int32 index) const
{ {
ASSERT_LOW_LAYER(index * _stride < _data.Length());
return _sampler.Read(_data.Get() + index * _stride); return _sampler.Read(_data.Get() + index * _stride);
} }
FORCE_INLINE void SetInt(int32 index, const int32 value) 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)); _sampler.Write(_data.Get() + index * _stride, Float4((float)value));
} }
FORCE_INLINE void SetFloat(int32 index, const 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)); _sampler.Write(_data.Get() + index * _stride, Float4(value));
} }
FORCE_INLINE void SetFloat2(int32 index, const Float2& 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)); _sampler.Write(_data.Get() + index * _stride, Float4(value));
} }
FORCE_INLINE void SetFloat3(int32 index, const Float3& 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)); _sampler.Write(_data.Get() + index * _stride, Float4(value));
} }
FORCE_INLINE void SetFloat4(int32 index, const 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); _sampler.Write(_data.Get() + index * _stride, value);
} }
@@ -94,19 +104,33 @@ public:
void Set(Span<Float2> src); void Set(Span<Float2> src);
void Set(Span<Float3> src); void Set(Span<Float3> src);
void Set(Span<Color> 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. // Copies the contents of this stream into a destination data span.
void CopyTo(Span<Float2> dst) const; void CopyTo(Span<Float2> dst) const;
void CopyTo(Span<Float3> dst) const; void CopyTo(Span<Float3> dst) const;
void CopyTo(Span<Color> 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: private:
BytesContainer _data[(int32)MeshBufferType::MAX]; BytesContainer _data[(int32)MeshBufferType::MAX];
PixelFormat _formats[(int32)MeshBufferType::MAX] = {}; PixelFormat _formats[(int32)MeshBufferType::MAX] = {};
GPUVertexLayout* _layouts[(int32)MeshBufferType::MAX] = {}; GPUVertexLayout* _layouts[(int32)MeshBufferType::MAX] = {};
Array<ModelBase*, InlinedAllocation<4>> _usedModels;
public: public:
~MeshAccessor();
/// <summary> /// <summary>
/// Loads the data from the mesh. /// Loads the data from the mesh.
/// </summary> /// </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) bool MeshAccessor::LoadMesh(const MeshBase* mesh, bool forceGpu, Span<MeshBufferType> buffers)
{ {
CHECK_RETURN(mesh, true); 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)); buffers = Span<MeshBufferType>(allBuffers, ARRAY_COUNT(allBuffers));
Array<BytesContainer, FixedAllocation<4>> meshBuffers; Array<BytesContainer, FixedAllocation<4>> meshBuffers;
Array<GPUVertexLayout*, FixedAllocation<4>> meshLayouts; 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)) if (mesh->DownloadData(buffers, meshBuffers, meshLayouts, forceGpu))
return true; return true;
for (int32 i = 0; i < buffers.Length(); i++) for (int32 i = 0; i < buffers.Length(); i++)
{ {
_data[(int32)buffers[i]] = MoveTemp(meshBuffers[i]); const int32 buffer = (int32)buffers[i];
_layouts[(int32)buffers[i]] = meshLayouts[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; return false;
} }
@@ -714,6 +738,9 @@ bool MeshBase::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int3
_cachedVertexBufferCount = meshData.Vertices; _cachedVertexBufferCount = meshData.Vertices;
_cachedIndexBufferCount = (int32)meshData.Triangles * 3; _cachedIndexBufferCount = (int32)meshData.Triangles * 3;
_cachedIndexBuffer.Copy((const byte*)meshData.IBData, _cachedIndexBufferCount * (int32)meshData.IBStride); _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++) for (int32 vb = 0; vb < meshData.VBData.Count(); vb++)
{ {
_cachedVertexBuffers[vb].Copy((const byte*)meshData.VBData[vb], (int32)(meshData.VBLayout[vb]->GetStride() * meshData.Vertices)); _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: case MeshBufferType::Index:
result.Link(_cachedIndexBuffer); result.Link(_cachedIndexBuffer);
count = _cachedIndexBufferCount; count = _cachedIndexBufferCount;
if (layout)
*layout = _cachedVertexLayouts[3];
break; break;
case MeshBufferType::Vertex0: case MeshBufferType::Vertex0:
result.Link(_cachedVertexBuffers[0]); result.Link(_cachedVertexBuffers[0]);
+1 -1
View File
@@ -50,7 +50,7 @@ protected:
GPUBuffer* _indexBuffer = nullptr; GPUBuffer* _indexBuffer = nullptr;
mutable BytesContainer _cachedVertexBuffers[MODEL_MAX_VB]; mutable BytesContainer _cachedVertexBuffers[MODEL_MAX_VB];
mutable GPUVertexLayout* _cachedVertexLayouts[MODEL_MAX_VB] = {}; mutable GPUVertexLayout* _cachedVertexLayouts[MODEL_MAX_VB + 1] = {};
mutable BytesContainer _cachedIndexBuffer; mutable BytesContainer _cachedIndexBuffer;
mutable int32 _cachedIndexBufferCount = 0, _cachedVertexBufferCount = 0; 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); 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 MeshDeformation* AnimatedModel::GetMeshDeformation() const
{ {
if (!_deformation) if (!_deformation)
@@ -479,6 +479,7 @@ public:
bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override; 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 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; 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; void UpdateBounds() override;
MeshDeformation* GetMeshDeformation() const override; MeshDeformation* GetMeshDeformation() const override;
void OnDeleteObject() 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); 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) void ModelInstanceActor::SetEntries(const Array<ModelInstanceEntry>& value)
{ {
WaitForModelLoad(); WaitForModelLoad();
@@ -29,6 +29,7 @@ API_CLASS(Abstract) class FLAXENGINE_API ModelInstanceActor : public Actor
API_FIELD() int32 MeshIndex = 0; API_FIELD() int32 MeshIndex = 0;
String ToString() const; String ToString() const;
MeshBase* Get() const;
}; };
protected: protected:
@@ -113,6 +114,7 @@ public:
/// <summary> /// <summary>
/// Extracts mesh buffer data from CPU. Might be cached internally (eg. by Model/SkinnedModel). /// Extracts mesh buffer data from CPU. Might be cached internally (eg. by Model/SkinnedModel).
/// [Deprecated in 1.12]
/// </summary> /// </summary>
/// <param name="ref">Mesh reference.</param> /// <param name="ref">Mesh reference.</param>
/// <param name="type">Buffer type</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="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> /// <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> /// <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 virtual bool GetMeshData(const MeshReference& ref, MeshBufferType type, BytesContainer& result, int32& count, GPUVertexLayout** layout = nullptr) const
{ {
return true; 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> /// <summary>
/// Gets the mesh deformation utility for this model instance (optional). /// Gets the mesh deformation utility for this model instance (optional).
/// </summary> /// </summary>
@@ -665,6 +665,16 @@ bool StaticModel::GetMeshData(const MeshReference& ref, MeshBufferType type, Byt
return mesh.DownloadDataCPU(type, result, count, layout); 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 MeshDeformation* StaticModel::GetMeshDeformation() const
{ {
if (!_deformation) if (!_deformation)
+1
View File
@@ -181,6 +181,7 @@ public:
bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override; 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 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; 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; MeshDeformation* GetMeshDeformation() const override;
void UpdateBounds() override; void UpdateBounds() override;
+130 -122
View File
@@ -133,7 +133,7 @@ Array<Float3> Cloth::GetParticles() const
if (_cloth) if (_cloth)
{ {
PROFILE_CPU(); PROFILE_CPU();
PROFILE_MEM(Physics); PROFILE_MEM(PhysicsCloth);
PhysicsBackend::LockClothParticles(_cloth); PhysicsBackend::LockClothParticles(_cloth);
const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth); const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth);
result.Resize(particles.Length()); result.Resize(particles.Length());
@@ -150,7 +150,7 @@ Array<Float3> Cloth::GetParticles() const
void Cloth::SetParticles(Span<const Float3> value) void Cloth::SetParticles(Span<const Float3> value)
{ {
PROFILE_CPU(); PROFILE_CPU();
PROFILE_MEM(Physics); PROFILE_MEM(PhysicsCloth);
#if USE_CLOTH_SANITY_CHECKS #if USE_CLOTH_SANITY_CHECKS
{ {
// Sanity check // Sanity check
@@ -180,7 +180,7 @@ Span<float> Cloth::GetPaint() const
void Cloth::SetPaint(Span<const float> value) void Cloth::SetPaint(Span<const float> value)
{ {
PROFILE_CPU(); PROFILE_CPU();
PROFILE_MEM(Physics); PROFILE_MEM(PhysicsCloth);
#if USE_CLOTH_SANITY_CHECKS #if USE_CLOTH_SANITY_CHECKS
{ {
// Sanity check // Sanity check
@@ -208,8 +208,12 @@ void Cloth::SetPaint(Span<const float> value)
if (_cloth) if (_cloth)
{ {
// Update cloth particles // 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; Array<float> invMasses;
CalculateInvMasses(invMasses); CalculateInvMasses(accessor, invMasses);
PhysicsBackend::LockClothParticles(_cloth); PhysicsBackend::LockClothParticles(_cloth);
PhysicsBackend::SetClothParticles(_cloth, Span<const Float4>(), Span<const Float3>(), ToSpan<float, const float>(invMasses)); PhysicsBackend::SetClothParticles(_cloth, Span<const Float4>(), Span<const Float3>(), ToSpan<float, const float>(invMasses));
PhysicsBackend::SetClothPaint(_cloth, value); PhysicsBackend::SetClothPaint(_cloth, value);
@@ -227,18 +231,20 @@ bool Cloth::IntersectsItself(const Ray& ray, Real& distance, Vector3& normal)
if (_cloth) if (_cloth)
{ {
// Precise per-triangle intersection // Precise per-triangle intersection
const ModelInstanceActor::MeshReference mesh = GetMesh(); const ModelInstanceActor::MeshReference meshRef = GetMesh();
if (mesh.Actor == nullptr) if (meshRef.Actor == nullptr)
return false; return false;
BytesContainer indicesData; MeshAccessor accessor;
int32 indicesCount; MeshBufferType bufferTypes[1] = { MeshBufferType::Index };
if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Index, indicesData, indicesCount)) if (accessor.LoadMesh(meshRef.Get(), false, ToSpan(bufferTypes, 1)))
return false; return false;
auto indices = accessor.Index();
auto indicesData = indices.GetData();
PhysicsBackend::LockClothParticles(_cloth); PhysicsBackend::LockClothParticles(_cloth);
const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth); const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth);
const Transform transform = GetTransform(); const Transform transform = GetTransform();
const bool indices16bit = indicesData.Length() / indicesCount == sizeof(uint16); const bool indices16bit = indices.GetFormat() == PixelFormat::R16_UInt;
const int32 trianglesCount = indicesCount / 3; const int32 trianglesCount = indices.GetCount() / 3;
bool result = false; bool result = false;
distance = MAX_Real; distance = MAX_Real;
for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++) for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++)
@@ -306,7 +312,7 @@ void Cloth::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
{ {
Actor::Deserialize(stream, modifier); Actor::Deserialize(stream, modifier);
PROFILE_MEM(Physics); PROFILE_MEM(PhysicsCloth);
DESERIALIZE_MEMBER(Mesh, _mesh); DESERIALIZE_MEMBER(Mesh, _mesh);
_mesh.Actor = nullptr; // Don't store this reference _mesh.Actor = nullptr; // Don't store this reference
DESERIALIZE_MEMBER(Force, _forceSettings); DESERIALIZE_MEMBER(Force, _forceSettings);
@@ -341,18 +347,20 @@ void Cloth::DrawPhysicsDebug(RenderView& view)
if (_cloth) if (_cloth)
{ {
PROFILE_CPU(); PROFILE_CPU();
const ModelInstanceActor::MeshReference mesh = GetMesh(); const ModelInstanceActor::MeshReference meshRef = GetMesh();
if (mesh.Actor == nullptr) if (meshRef.Actor == nullptr)
return; return;
BytesContainer indicesData; MeshAccessor accessor;
int32 indicesCount; MeshBufferType bufferTypes[1] = { MeshBufferType::Index };
if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Index, indicesData, indicesCount)) if (accessor.LoadMesh(meshRef.Get(), false, ToSpan(bufferTypes, 1)))
return; return;
auto indices = accessor.Index();
auto indicesData = indices.GetData();
PhysicsBackend::LockClothParticles(_cloth); PhysicsBackend::LockClothParticles(_cloth);
const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth); const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth);
const Transform transform = GetTransform(); const Transform transform = GetTransform();
const bool indices16bit = indicesData.Length() / indicesCount == sizeof(uint16); const bool indices16bit = indices.GetFormat() == PixelFormat::R16_UInt;
const int32 trianglesCount = indicesCount / 3; const int32 trianglesCount = indices.GetCount() / 3;
for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++) for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++)
{ {
const int32 index = triangleIndex * 3; const int32 index = triangleIndex * 3;
@@ -390,18 +398,20 @@ void Cloth::OnDebugDrawSelected()
if (_cloth) if (_cloth)
{ {
DEBUG_DRAW_WIRE_BOX(_box, Color::Violet.RGBMultiplied(0.8f), 0, true); DEBUG_DRAW_WIRE_BOX(_box, Color::Violet.RGBMultiplied(0.8f), 0, true);
const ModelInstanceActor::MeshReference mesh = GetMesh(); const ModelInstanceActor::MeshReference meshRef = GetMesh();
if (mesh.Actor == nullptr) if (meshRef.Actor == nullptr)
return; return;
BytesContainer indicesData; MeshAccessor accessor;
int32 indicesCount; MeshBufferType bufferTypes[1] = { MeshBufferType::Index };
if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Index, indicesData, indicesCount)) if (accessor.LoadMesh(meshRef.Get(), false, ToSpan(bufferTypes, 1)))
return; return;
auto indices = accessor.Index();
auto indicesData = indices.GetData();
PhysicsBackend::LockClothParticles(_cloth); PhysicsBackend::LockClothParticles(_cloth);
const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth); const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth);
const Transform transform = GetTransform(); const Transform transform = GetTransform();
const bool indices16bit = indicesData.Length() / indicesCount == sizeof(uint16); const bool indices16bit = indices.GetFormat() == PixelFormat::R16_UInt;
const int32 trianglesCount = indicesCount / 3; const int32 trianglesCount = indices.GetCount() / 3;
for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++) for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++)
{ {
const int32 index = triangleIndex * 3; const int32 index = triangleIndex * 3;
@@ -542,7 +552,7 @@ bool Cloth::CreateCloth()
{ {
#if WITH_CLOTH #if WITH_CLOTH
PROFILE_CPU(); PROFILE_CPU();
PROFILE_MEM(Physics); PROFILE_MEM(PhysicsCloth);
// Skip if all vertices are fixed so cloth sim doesn't make sense // Skip if all vertices are fixed so cloth sim doesn't make sense
if (_paint.HasItems()) if (_paint.HasItems())
@@ -556,26 +566,36 @@ bool Cloth::CreateCloth()
// Get mesh data // 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 // 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(); const ModelInstanceActor::MeshReference meshRef = GetMesh();
if (mesh.Actor == nullptr) if (meshRef.Actor == nullptr)
return false; return false;
PhysicsClothDesc desc; PhysicsClothDesc desc;
desc.Actor = this; desc.Actor = this;
BytesContainer data; MeshAccessor accessor;
int32 count; MeshBufferType bufferTypes[2] = { MeshBufferType::Index, MeshBufferType::Vertex0 };
if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Vertex0, data, count)) if (accessor.LoadMesh(meshRef.Get(), false, ToSpan(bufferTypes, 2)))
return true; return true;
// TODO: use MeshAccessor vertex data layout descriptor instead hardcoded position data at the beginning of VB0 auto position = accessor.Position();
desc.VerticesData = data.Get(); Array<Float3> tempPositions;
desc.VerticesCount = count; if (position.GetFormat() == PixelFormat::R32G32B32_Float)
desc.VerticesStride = data.Length() / count; {
if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Index, data, count)) desc.VerticesData = position.GetData().Get();
return true; desc.VerticesCount = position.GetCount();
desc.IndicesData = data.Get(); desc.VerticesStride = position.GetStride();
desc.IndicesCount = count; }
desc.IndicesStride = data.Length() / count; 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; Array<float> invMasses;
CalculateInvMasses(invMasses); CalculateInvMasses(accessor, invMasses);
desc.InvMassesData = invMasses.Count() == desc.VerticesCount ? invMasses.Get() : nullptr; desc.InvMassesData = invMasses.Count() == desc.VerticesCount ? invMasses.Get() : nullptr;
desc.InvMassesStride = sizeof(float); desc.InvMassesStride = sizeof(float);
desc.MaxDistancesData = _paint.Count() == desc.VerticesCount ? _paint.Get() : nullptr; desc.MaxDistancesData = _paint.Count() == desc.VerticesCount ? _paint.Get() : nullptr;
@@ -595,13 +615,13 @@ bool Cloth::CreateCloth()
PhysicsBackend::ClearClothInertia(_cloth); PhysicsBackend::ClearClothInertia(_cloth);
// Add cloth mesh deformer // Add cloth mesh deformer
if (auto* deformation = mesh.Actor->GetMeshDeformation()) if (auto* deformation = meshRef.Actor->GetMeshDeformation())
{ {
Function<void(const MeshBase*, MeshDeformationData&)> deformer; Function<void(const MeshBase*, MeshDeformationData&)> deformer;
deformer.Bind<Cloth, &Cloth::RunClothDeformer>(this); 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) if (_simulationSettings.ComputeNormals)
deformation->AddDeformer(mesh.LODIndex, mesh.MeshIndex, MeshBufferType::Vertex1, deformer); deformation->AddDeformer(meshRef.LODIndex, meshRef.MeshIndex, MeshBufferType::Vertex1, deformer);
_meshDeformation = deformation; _meshDeformation = deformation;
} }
@@ -631,39 +651,32 @@ void Cloth::DestroyCloth()
#endif #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 // Use per-particle max distance to evaluate which particles are immovable
#if WITH_CLOTH #if WITH_CLOTH
if (_paint.IsEmpty()) if (_paint.IsEmpty())
return; return;
PROFILE_CPU(); PROFILE_CPU();
PROFILE_MEM(Physics); PROFILE_MEM(PhysicsCloth);
// Get mesh data // Get mesh data
const ModelInstanceActor::MeshReference mesh = GetMesh(); auto positions = accessor.Position();
if (mesh.Actor == nullptr) auto indices = accessor.Index();
return; CHECK(positions.IsValid() && indices.IsValid());
BytesContainer verticesData; int32 verticesCount = positions.GetCount();
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;
if (_paint.Count() != verticesCount) if (_paint.Count() != verticesCount)
{ {
// Fix incorrect paint data // 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(); int32 countBefore = _paint.Count();
_paint.Resize(verticesCount); _paint.Resize(verticesCount);
for (int32 i = countBefore; i < verticesCount; i++) for (int32 i = countBefore; i < verticesCount; i++)
_paint.Get()[i] = 0.0f; _paint.Get()[i] = 0.0f;
} }
const int32 verticesStride = verticesData.Length() / verticesCount; const bool indices16bit = indices.GetFormat() == PixelFormat::R16_UInt;
const bool indices16bit = indicesData.Length() / indicesCount == sizeof(uint16); const int32 trianglesCount = indices.GetCount() / 3;
const int32 trianglesCount = indicesCount / 3; auto indicesData = indices.GetData();
// Sum triangle area for each influenced particle // Sum triangle area for each influenced particle
invMasses.Resize(verticesCount); invMasses.Resize(verticesCount);
@@ -676,12 +689,9 @@ void Cloth::CalculateInvMasses(Array<float>& invMasses)
const int32 i0 = indicesData.Get<uint16>()[index]; const int32 i0 = indicesData.Get<uint16>()[index];
const int32 i1 = indicesData.Get<uint16>()[index + 1]; const int32 i1 = indicesData.Get<uint16>()[index + 1];
const int32 i2 = indicesData.Get<uint16>()[index + 2]; const int32 i2 = indicesData.Get<uint16>()[index + 2];
// TODO: use MeshAccessor vertex data layout descriptor instead hardcoded position data at the beginning of VB0 const Float3 v0(positions.GetFloat3(i0));
#define GET_POS(i) *(Float3*)((byte*)verticesData.Get() + i * verticesStride) const Float3 v1(positions.GetFloat3(i1));
const Float3 v0(GET_POS(i0)); const Float3 v2(positions.GetFloat3(i2));
const Float3 v1(GET_POS(i1));
const Float3 v2(GET_POS(i2));
#undef GET_POS
const float area = Float3::TriangleArea(v0, v1, v2); const float area = Float3::TriangleArea(v0, v1, v2);
invMasses.Get()[i0] += area; invMasses.Get()[i0] += area;
invMasses.Get()[i1] += area; invMasses.Get()[i1] += area;
@@ -696,12 +706,9 @@ void Cloth::CalculateInvMasses(Array<float>& invMasses)
const int32 i0 = indicesData.Get<uint32>()[index]; const int32 i0 = indicesData.Get<uint32>()[index];
const int32 i1 = indicesData.Get<uint32>()[index + 1]; const int32 i1 = indicesData.Get<uint32>()[index + 1];
const int32 i2 = indicesData.Get<uint32>()[index + 2]; const int32 i2 = indicesData.Get<uint32>()[index + 2];
// TODO: use MeshAccessor vertex data layout descriptor instead hardcoded position data at the beginning of VB0 const Float3 v0(positions.GetFloat3(i0));
#define GET_POS(i) *(Float3*)((byte*)verticesData.Get() + i * verticesStride) const Float3 v1(positions.GetFloat3(i1));
const Float3 v0(GET_POS(i0)); const Float3 v2(positions.GetFloat3(i2));
const Float3 v1(GET_POS(i1));
const Float3 v2(GET_POS(i2));
#undef GET_POS
const float area = Float3::TriangleArea(v0, v1, v2); const float area = Float3::TriangleArea(v0, v1, v2);
invMasses.Get()[i0] += area; invMasses.Get()[i0] += area;
invMasses.Get()[i1] += area; invMasses.Get()[i1] += area;
@@ -787,25 +794,22 @@ bool Cloth::OnPreUpdate()
{ {
if (animatedModel->GraphInstance.NodesPose.IsEmpty() || _paint.IsEmpty()) if (animatedModel->GraphInstance.NodesPose.IsEmpty() || _paint.IsEmpty())
return false; return false;
const ModelInstanceActor::MeshReference mesh = GetMesh(); const ModelInstanceActor::MeshReference meshRef = GetMesh();
if (mesh.Actor == nullptr) if (meshRef.Actor == nullptr)
return false;
BytesContainer verticesData;
int32 verticesCount;
GPUVertexLayout* layout;
if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Vertex0, verticesData, verticesCount, &layout))
return false; return false;
MeshAccessor accessor; 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; return false;
auto positionStream = accessor.Position(); auto positionStream = accessor.Position();
auto blendIndicesStream = accessor.BlendIndices(); auto blendIndicesStream = accessor.BlendIndices();
auto blendWeightsStream = accessor.BlendWeights(); auto blendWeightsStream = accessor.BlendWeights();
if (!positionStream.IsValid() || !blendIndicesStream.IsValid() || !blendWeightsStream.IsValid()) if (!positionStream.IsValid() || !blendIndicesStream.IsValid() || !blendWeightsStream.IsValid())
return false; return false;
const int32 verticesCount = positionStream.GetCount();
if (verticesCount != _paint.Count()) 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; return false;
} }
PROFILE_CPU_NAMED("Skinned Pose"); PROFILE_CPU_NAMED("Skinned Pose");
@@ -925,7 +929,7 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat
return; return;
#if WITH_CLOTH #if WITH_CLOTH
PROFILE_CPU_NAMED("Cloth"); PROFILE_CPU_NAMED("Cloth");
PROFILE_MEM(Physics); PROFILE_MEM(PhysicsCloth);
PhysicsBackend::LockClothParticles(_cloth); PhysicsBackend::LockClothParticles(_cloth);
const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth); const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth);
auto vbCount = (uint32)mesh->GetVertexCount(); auto vbCount = (uint32)mesh->GetVertexCount();
@@ -934,49 +938,53 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat
// Calculate normals // Calculate normals
Array<Float3> normals; Array<Float3> normals;
const ModelInstanceActor::MeshReference meshRef = GetMesh(); const ModelInstanceActor::MeshReference meshRef = GetMesh();
BytesContainer indicesData; if ((_simulationSettings.ComputeNormals || deformation.Type == MeshBufferType::Vertex1) && meshRef.Actor)
int32 indicesCount;
if ((_simulationSettings.ComputeNormals || deformation.Type == MeshBufferType::Vertex1) &&
meshRef.Actor && !meshRef.Actor->GetMeshData(meshRef, MeshBufferType::Index, indicesData, indicesCount))
{ {
PROFILE_CPU_NAMED("Normals"); MeshAccessor accessor;
// TODO: optimize memory allocs (eg. use shared allocator) MeshBufferType bufferTypes[1] = { MeshBufferType::Index };
normals.Resize(vbCount); if (!accessor.LoadMesh(meshRef.Get(), false, ToSpan(bufferTypes, 1)))
Platform::MemoryClear(normals.Get(), vbCount * sizeof(Float3));
const bool indices16bit = indicesData.Length() / indicesCount == sizeof(uint16);
const int32 trianglesCount = indicesCount / 3;
if (indices16bit)
{ {
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; for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++)
const int32 i0 = indicesData.Get<uint16>()[index]; {
const int32 i1 = indicesData.Get<uint16>()[index + 1]; const int32 index = triangleIndex * 3;
const int32 i2 = indicesData.Get<uint16>()[index + 2]; const int32 i0 = indicesData.Get<uint16>()[index];
const Float3 v0(particles.Get()[i0]); const int32 i1 = indicesData.Get<uint16>()[index + 1];
const Float3 v1(particles.Get()[i1]); const int32 i2 = indicesData.Get<uint16>()[index + 2];
const Float3 v2(particles.Get()[i2]); const Float3 v0(particles.Get()[i0]);
const Float3 normal = Float3::Cross(v1 - v0, v2 - v0); const Float3 v1(particles.Get()[i1]);
normals.Get()[i0] += normal; const Float3 v2(particles.Get()[i2]);
normals.Get()[i1] += normal; const Float3 normal = Float3::Cross(v1 - v0, v2 - v0);
normals.Get()[i2] += normal; normals.Get()[i0] += normal;
normals.Get()[i1] += normal;
normals.Get()[i2] += normal;
}
} }
} else
else
{
for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++)
{ {
const int32 index = triangleIndex * 3; for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++)
const int32 i0 = indicesData.Get<uint32>()[index]; {
const int32 i1 = indicesData.Get<uint32>()[index + 1]; const int32 index = triangleIndex * 3;
const int32 i2 = indicesData.Get<uint32>()[index + 2]; const int32 i0 = indicesData.Get<uint32>()[index];
const Float3 v0(particles.Get()[i0]); const int32 i1 = indicesData.Get<uint32>()[index + 1];
const Float3 v1(particles.Get()[i1]); const int32 i2 = indicesData.Get<uint32>()[index + 2];
const Float3 v2(particles.Get()[i2]); const Float3 v0(particles.Get()[i0]);
const Float3 normal = Float3::Cross(v1 - v0, v2 - v0); const Float3 v1(particles.Get()[i1]);
normals.Get()[i0] += normal; const Float3 v2(particles.Get()[i2]);
normals.Get()[i1] += normal; const Float3 normal = Float3::Cross(v1 - v0, v2 - v0);
normals.Get()[i2] += normal; normals.Get()[i0] += normal;
normals.Get()[i1] += normal;
normals.Get()[i2] += normal;
}
} }
} }
} }
+1 -1
View File
@@ -371,6 +371,6 @@ private:
ImplementPhysicsDebug; ImplementPhysicsDebug;
bool CreateCloth(); bool CreateCloth();
void DestroyCloth(); void DestroyCloth();
void CalculateInvMasses(Array<float>& invMasses); void CalculateInvMasses(class MeshAccessor& accessor, Array<float>& invMasses);
void RunClothDeformer(const MeshBase* mesh, struct MeshDeformationData& deformation); void RunClothDeformer(const MeshBase* mesh, struct MeshDeformationData& deformation);
}; };
@@ -26,7 +26,7 @@ void SphereCollider::SetRadius(const float value)
void SphereCollider::DrawPhysicsDebug(RenderView& view) 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)) if (!view.CullingFrustum.Intersects(sphere))
return; return;
if (view.Mode == ViewMode::PhysicsColliders && !GetIsTrigger()) if (view.Mode == ViewMode::PhysicsColliders && !GetIsTrigger())
@@ -1201,6 +1201,7 @@ void ScenePhysX::UpdateVehicles(float dt)
void ScenePhysX::PreSimulateCloth(int32 i) void ScenePhysX::PreSimulateCloth(int32 i)
{ {
PROFILE_CPU(); PROFILE_CPU();
PROFILE_MEM(PhysicsCloth);
auto clothPhysX = ClothsList[i]; auto clothPhysX = ClothsList[i];
auto& clothSettings = Cloths[clothPhysX]; auto& clothSettings = Cloths[clothPhysX];
@@ -1403,6 +1404,7 @@ void ScenePhysX::UpdateCloths(float dt)
if (!clothSolver || ClothsList.IsEmpty()) if (!clothSolver || ClothsList.IsEmpty())
return; return;
PROFILE_CPU_NAMED("Physics.Cloth"); PROFILE_CPU_NAMED("Physics.Cloth");
PROFILE_MEM(PhysicsCloth);
{ {
PROFILE_CPU_NAMED("Pre"); PROFILE_CPU_NAMED("Pre");
@@ -4029,7 +4031,7 @@ void PhysicsBackend::RemoveVehicle(void* scene, WheeledVehicle* actor)
void* PhysicsBackend::CreateCloth(const PhysicsClothDesc& desc) void* PhysicsBackend::CreateCloth(const PhysicsClothDesc& desc)
{ {
PROFILE_CPU(); PROFILE_CPU();
PROFILE_MEM(Physics); PROFILE_MEM(PhysicsCloth);
#if USE_CLOTH_SANITY_CHECKS #if USE_CLOTH_SANITY_CHECKS
{ {
// Sanity check // Sanity check
@@ -263,6 +263,7 @@ void InitProfilerMemory(const Char* cmdLine, int32 stage)
INIT_PARENT(Level, LevelTerrain); INIT_PARENT(Level, LevelTerrain);
INIT_PARENT(Navigation, NavigationMesh); INIT_PARENT(Navigation, NavigationMesh);
INIT_PARENT(Navigation, NavigationBuilding); INIT_PARENT(Navigation, NavigationBuilding);
INIT_PARENT(Physics, PhysicsCloth);
INIT_PARENT(Scripting, ScriptingVisual); INIT_PARENT(Scripting, ScriptingVisual);
INIT_PARENT(Scripting, ScriptingCSharp); INIT_PARENT(Scripting, ScriptingCSharp);
INIT_PARENT(ScriptingCSharp, ScriptingCSharpGCCommitted); INIT_PARENT(ScriptingCSharp, ScriptingCSharpGCCommitted);
+2
View File
@@ -120,6 +120,8 @@ public:
// Total physics memory. // Total physics memory.
Physics, Physics,
// Cloth simulation and particles data.
PhysicsCloth,
// Total scripting memory allocated by game. // Total scripting memory allocated by game.
Scripting, Scripting,
+2 -2
View File
@@ -2241,11 +2241,11 @@ void TerrainPatch::DestroyCollision()
_physicsHeightField = nullptr; _physicsHeightField = nullptr;
#if TERRAIN_USE_PHYSICS_DEBUG #if TERRAIN_USE_PHYSICS_DEBUG
_debugLinesDirty = true; _debugLinesDirty = true;
SAFE_DELETE(_debugLines); SAFE_DELETE_GPU_RESOURCE(_debugLines);
#endif #endif
#if USE_EDITOR #if USE_EDITOR
_collisionTriangles.Resize(0); _collisionTriangles.Resize(0);
SAFE_DELETE(_collisionTrianglesBuffer); SAFE_DELETE_GPU_RESOURCE(_collisionTrianglesBuffer);
_collisionTrianglesBufferDirty = true; _collisionTrianglesBufferDirty = true;
#endif #endif
_collisionVertices.Resize(0); _collisionVertices.Resize(0);
@@ -349,7 +349,7 @@ void MeshAccelerationStructure::Add(Model* model, int32 lodIndex)
meshData.IndexBuffer.Copy(indexStream.GetData()); meshData.IndexBuffer.Copy(indexStream.GetData());
meshData.VertexBuffer.Allocate(meshData.Vertices * sizeof(Float3)); meshData.VertexBuffer.Allocate(meshData.Vertices * sizeof(Float3));
positionStream.CopyTo(ToSpan(meshData.VertexBuffer.Get<Float3>(), meshData.Vertices)); positionStream.CopyTo(ToSpan(meshData.VertexBuffer.Get<Float3>(), meshData.Vertices));
meshData.Use16BitIndexBuffer = mesh.Use16BitIndexBuffer(); meshData.Use16BitIndexBuffer = indexStream.GetFormat() == PixelFormat::R16_UInt;
meshData.Bounds = mesh.GetBox(); meshData.Bounds = mesh.GetBox();
} }
} }
+1 -1
View File
@@ -595,7 +595,7 @@ namespace FlaxEngine.GUI
Size = new Float2(size.X - margin, size.Y), Size = new Float2(size.X - margin, size.Y),
Font = Font, Font = Font,
TextColor = TextColor * 0.9f, 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, HorizontalAlignment = HorizontalAlignment,
VerticalAlignment = VerticalAlignment, VerticalAlignment = VerticalAlignment,
Text = _items[i], Text = _items[i],
@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Xml; using System.Xml;
using Flax.Build.Graph; using Flax.Build.Graph;
@@ -26,6 +25,11 @@ namespace Flax.Build.Platforms
/// </summary> /// </summary>
protected readonly string _vcToolPath; protected readonly string _vcToolPath;
/// <summary>
/// The VC tools version.
/// </summary>
protected readonly string _vcToolVersion;
/// <summary> /// <summary>
/// The compiler path. /// The compiler path.
/// </summary> /// </summary>
@@ -147,6 +151,21 @@ namespace Flax.Build.Platforms
_libToolPath = Path.Combine(_vcToolPath, "lib.exe"); _libToolPath = Path.Combine(_vcToolPath, "lib.exe");
_xdcmakePath = Path.Combine(_vcToolPath, "xdcmake.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 // Add Visual C++ toolset include and library paths
var vcToolChainDir = toolsets[Toolset]; var vcToolChainDir = toolsets[Toolset];
SystemIncludePaths.Add(Path.Combine(vcToolChainDir, "include")); SystemIncludePaths.Add(Path.Combine(vcToolChainDir, "include"));
@@ -370,7 +389,7 @@ namespace Flax.Build.Platforms
public override void LogInfo() public override void LogInfo()
{ {
var sdkPath = WindowsPlatformBase.GetSDKs()[SDK]; 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)); 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 pchSourceFile = Path.Combine(options.IntermediateFolder, Path.ChangeExtension(pchFilName, "cpp"));
var contents = Bindings.BindingsGenerator.GetStringBuilder(); var contents = Bindings.BindingsGenerator.GetStringBuilder();
contents.AppendLine("// This code was auto-generated. Do not modify it."); 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("// Compiler: ").AppendLine(_compilerPath);
contents.Append("// Toolchain: ").AppendLine(_vcToolVersion);
contents.Append("// CppVersion: ").AppendLine(compileEnvironment.CppVersion.ToString());
contents.Append("#include \"").Append(pchSource).AppendLine("\""); contents.Append("#include \"").Append(pchSource).AppendLine("\"");
Utilities.WriteFileIfChanged(pchSourceFile, contents.ToString()); Utilities.WriteFileIfChanged(pchSourceFile, contents.ToString());
Bindings.BindingsGenerator.PutStringBuilder(contents); Bindings.BindingsGenerator.PutStringBuilder(contents);