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