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

# Conflicts:
#	Flax.flaxproj
This commit is contained in:
Wojtek Figat
2023-11-14 12:20:26 +01:00
86 changed files with 1208 additions and 335 deletions

View File

@@ -86,6 +86,7 @@ namespace FlaxEditor.Content
Folder.ParentFolder = parent.Folder;
Parent = parent;
}
IconColor = Style.Current.Foreground;
}
/// <summary>

View File

@@ -157,7 +157,7 @@ namespace FlaxEditor.CustomEditors
var values = _values;
var presenter = _presenter;
var layout = _layout;
if (layout.Editors.Count != 1)
if (layout.Editors.Count > 1)
{
// There are more editors using the same layout so rebuild parent editor to prevent removing others editors
_parent?.RebuildLayout();

View File

@@ -695,7 +695,41 @@ namespace FlaxEditor.CustomEditors.Dedicated
private void SetType(ref ScriptType controlType, UIControl uiControl)
{
string previousName = uiControl.Control?.GetType().Name ?? nameof(UIControl);
uiControl.Control = (Control)controlType.CreateInstance();
var oldControlType = (Control)uiControl.Control;
var newControlType = (Control)controlType.CreateInstance();
// copy old control data to new control
if (oldControlType != null)
{
newControlType.Visible = oldControlType.Visible;
newControlType.Enabled = oldControlType.Enabled;
newControlType.AutoFocus = oldControlType.AutoFocus;
newControlType.AnchorMin = oldControlType.AnchorMin;
newControlType.AnchorMax = oldControlType.AnchorMax;
newControlType.Offsets = oldControlType.Offsets;
newControlType.LocalLocation = oldControlType.LocalLocation;
newControlType.Scale = oldControlType.Scale;
newControlType.Bounds = oldControlType.Bounds;
newControlType.Width = oldControlType.Width;
newControlType.Height = oldControlType.Height;
newControlType.Center = oldControlType.Center;
newControlType.PivotRelative = oldControlType.PivotRelative;
newControlType.Pivot = oldControlType.Pivot;
newControlType.Shear = oldControlType.Shear;
newControlType.Rotation = oldControlType.Rotation;
}
if (oldControlType is ContainerControl oldContainer && newControlType is ContainerControl newContainer)
{
newContainer.CullChildren = oldContainer.CullChildren;
newContainer.ClipChildren = oldContainer.ClipChildren;
}
uiControl.Control = newControlType;
if (uiControl.Name.StartsWith(previousName))
{
string newName = controlType.Name + uiControl.Name.Substring(previousName.Length);

View File

@@ -113,7 +113,7 @@ namespace FlaxEditor.CustomEditors.Editors
public override void Initialize(LayoutElementsContainer layout)
{
// No support for different collections for now
if (HasDifferentValues || HasDifferentTypes)
if (HasDifferentTypes)
return;
var size = Count;

View File

@@ -28,14 +28,16 @@ namespace FlaxEditor.CustomEditors.Editors
var group = layout.Group("Entry");
_group = group;
if (ParentEditor == null)
if (ParentEditor == null || HasDifferentTypes)
return;
var entry = (ModelInstanceEntry)Values[0];
var entryIndex = ParentEditor.ChildrenEditors.IndexOf(this);
var materialLabel = new PropertyNameLabel("Material");
materialLabel.TooltipText = "The mesh surface material used for the rendering.";
if (ParentEditor.ParentEditor?.Values[0] is ModelInstanceActor modelInstance)
var parentEditorValues = ParentEditor.ParentEditor?.Values;
if (parentEditorValues?[0] is ModelInstanceActor modelInstance)
{
// TODO: store _modelInstance and _material in array for each selected model instance actor
_entryIndex = entryIndex;
_modelInstance = modelInstance;
var slots = modelInstance.MaterialSlots;
@@ -56,6 +58,8 @@ namespace FlaxEditor.CustomEditors.Editors
// Create material picker
var materialValue = new CustomValueContainer(new ScriptType(typeof(MaterialBase)), _material, (instance, index) => _material, (instance, index, value) => _material = value as MaterialBase);
for (var i = 1; i < parentEditorValues.Count; i++)
materialValue.Add(_material);
var materialEditor = (AssetRefEditor)_group.Property(materialLabel, materialValue);
materialEditor.Values.SetDefaultValue(defaultValue);
materialEditor.RefreshDefaultValue();

View File

@@ -173,8 +173,8 @@ namespace FlaxEditor.GUI
else
{
// No element selected
Render2D.FillRectangle(iconRect, new Color(0.2f));
Render2D.DrawText(style.FontMedium, "No asset\nselected", iconRect, Color.Wheat, TextAlignment.Center, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, Height / DefaultIconSize);
Render2D.FillRectangle(iconRect, style.BackgroundNormal);
Render2D.DrawText(style.FontMedium, "No asset\nselected", iconRect, Color.Orange, TextAlignment.Center, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, Height / DefaultIconSize);
}
// Check if drag is over

View File

@@ -14,6 +14,8 @@ namespace FlaxEditor.GUI.Input
[HideInEditor]
public class ColorValueBox : Control
{
private bool _isMouseDown;
/// <summary>
/// Delegate function used for the color picker events handling.
/// </summary>
@@ -134,11 +136,22 @@ namespace FlaxEditor.GUI.Input
Render2D.DrawRectangle(r, IsMouseOver || IsNavFocused ? style.BackgroundSelected : Color.Black);
}
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{
_isMouseDown = true;
return base.OnMouseDown(location, button);
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
Focus();
OnSubmit();
if (_isMouseDown)
{
_isMouseDown = false;
Focus();
OnSubmit();
}
return true;
}

View File

@@ -100,9 +100,10 @@ namespace FlaxEditor.GUI
AutoResize = true;
Offsets = new Margin(0, 0, 0, IconSize);
_mouseOverColor = style.Foreground;
_selectedColor = style.Foreground;
_defaultColor = style.ForegroundGrey;
// Ignoring style on purpose (style would make sense if the icons were white, but they are colored)
_mouseOverColor = new Color(0.8f, 0.8f, 0.8f, 1f);
_selectedColor = Color.White;
_defaultColor = new Color(0.7f, 0.7f, 0.7f, 0.5f);
for (int i = 0; i < platforms.Length; i++)
{

View File

@@ -98,7 +98,7 @@ namespace FlaxEditor.GUI
rect.Width -= leftDepthMargin;
Render2D.PushClip(rect);
Render2D.DrawText(style.FontMedium, text, rect, Color.White, column.CellAlignment, TextAlignment.Center);
Render2D.DrawText(style.FontMedium, text, rect, style.Foreground, column.CellAlignment, TextAlignment.Center);
Render2D.PopClip();
x += width;

View File

@@ -239,7 +239,7 @@ namespace FlaxEditor.GUI.Tabs
/// </summary>
public Tab SelectedTab
{
get => _selectedIndex == -1 && Children.Count > _selectedIndex + 1 ? null : Children[_selectedIndex + 1] as Tab;
get => _selectedIndex < 0 || Children.Count <= _selectedIndex ? null : Children[_selectedIndex + 1] as Tab;
set => SelectedTabIndex = value != null ? Children.IndexOf(value) - 1 : -1;
}

View File

@@ -627,10 +627,11 @@ namespace FlaxEditor.GUI.Timeline
Parent = this
};
var style = Style.Current;
var headerTopArea = new ContainerControl
{
AutoFocus = false,
BackgroundColor = Style.Current.LightBackground,
BackgroundColor = style.LightBackground,
AnchorPreset = AnchorPresets.HorizontalStretchTop,
Offsets = new Margin(0, 0, 0, HeaderTopAreaHeight),
Parent = _splitter.Panel1
@@ -683,7 +684,7 @@ namespace FlaxEditor.GUI.Timeline
{
AutoFocus = false,
ClipChildren = false,
BackgroundColor = Style.Current.LightBackground,
BackgroundColor = style.LightBackground,
AnchorPreset = AnchorPresets.HorizontalStretchBottom,
Offsets = new Margin(0, 0, -playbackButtonsSize, playbackButtonsSize),
Parent = _splitter.Panel1
@@ -845,7 +846,7 @@ namespace FlaxEditor.GUI.Timeline
_timeIntervalsHeader = new TimeIntervalsHeader(this)
{
AutoFocus = false,
BackgroundColor = Style.Current.Background.RGBMultiplied(0.9f),
BackgroundColor = style.Background.RGBMultiplied(0.9f),
AnchorPreset = AnchorPresets.HorizontalStretchTop,
Offsets = new Margin(0, 0, 0, HeaderTopAreaHeight),
Parent = _splitter.Panel2
@@ -854,7 +855,7 @@ namespace FlaxEditor.GUI.Timeline
{
AutoFocus = false,
ClipChildren = false,
BackgroundColor = Style.Current.Background.RGBMultiplied(0.7f),
BackgroundColor = style.Background.RGBMultiplied(0.7f),
AnchorPreset = AnchorPresets.StretchAll,
Offsets = new Margin(0, 0, HeaderTopAreaHeight, 0),
Parent = _splitter.Panel2

View File

@@ -330,14 +330,15 @@ bool ManagedEditor::CanReloadScripts()
bool ManagedEditor::CanAutoBuildCSG()
{
if (!ManagedEditorOptions.AutoRebuildCSG)
return false;
// Skip calls from non-managed thread (eg. physics worker)
if (!MCore::Thread::IsAttached())
return false;
if (!HasManagedInstance())
return false;
if (!ManagedEditorOptions.AutoRebuildCSG)
return false;
if (Internal_CanAutoBuildCSG == nullptr)
{
Internal_CanAutoBuildCSG = GetClass()->GetMethod("Internal_CanAutoBuildCSG");
@@ -348,14 +349,15 @@ bool ManagedEditor::CanAutoBuildCSG()
bool ManagedEditor::CanAutoBuildNavMesh()
{
if (!ManagedEditorOptions.AutoRebuildNavMesh)
return false;
// Skip calls from non-managed thread (eg. physics worker)
if (!MCore::Thread::IsAttached())
return false;
if (!HasManagedInstance())
return false;
if (!ManagedEditorOptions.AutoRebuildNavMesh)
return false;
if (Internal_CanAutoBuildNavMesh == nullptr)
{
Internal_CanAutoBuildNavMesh = GetClass()->GetMethod("Internal_CanAutoBuildNavMesh");

View File

@@ -124,6 +124,7 @@ namespace FlaxEditor.Modules
if (!Editor.StateMachine.CurrentState.CanEditScene)
return;
undo = Editor.Undo;
Editor.Scene.MarkSceneEdited(actor.Scene);
}
// Record undo for prefab creating (backend links the target instance with the prefab)

View File

@@ -208,13 +208,20 @@ namespace FlaxEditor.Options
// If a non-default style was chosen, switch to that style
string styleName = themeOptions.SelectedStyle;
if (styleName != "Default" && themeOptions.Styles.TryGetValue(styleName, out var style) && style != null)
if (styleName != ThemeOptions.DefaultName && styleName != ThemeOptions.LightDefault && themeOptions.Styles.TryGetValue(styleName, out var style) && style != null)
{
Style.Current = style;
}
else
{
Style.Current = CreateDefaultStyle();
if (styleName == ThemeOptions.LightDefault)
{
Style.Current = CreateLightStyle();
}
else
{
Style.Current = CreateDefaultStyle();
}
}
}
@@ -224,7 +231,6 @@ namespace FlaxEditor.Options
/// <returns>The style object.</returns>
public Style CreateDefaultStyle()
{
// Metro Style colors
var options = Options;
var style = new Style
{
@@ -233,6 +239,7 @@ namespace FlaxEditor.Options
Foreground = Color.FromBgra(0xFFFFFFFF),
ForegroundGrey = Color.FromBgra(0xFFA9A9B3),
ForegroundDisabled = Color.FromBgra(0xFF787883),
ForegroundViewport = Color.FromBgra(0xFFFFFFFF),
BackgroundHighlighted = Color.FromBgra(0xFF54545C),
BorderHighlighted = Color.FromBgra(0xFF6A6A75),
BackgroundSelected = Color.FromBgra(0xFF007ACC),
@@ -274,7 +281,58 @@ namespace FlaxEditor.Options
SharedTooltip = new Tooltip(),
};
style.DragWindow = style.BackgroundSelected * 0.7f;
return style;
}
/// <summary>
/// Creates the light style (2nd default).
/// </summary>
/// <returns>The style object.</returns>
public Style CreateLightStyle()
{
var options = Options;
var style = new Style
{
Background = new Color(0.92f, 0.92f, 0.92f, 1f),
LightBackground = new Color(0.84f, 0.84f, 0.88f, 1f),
DragWindow = new Color(0.0f, 0.26f, 0.43f, 0.70f),
Foreground = new Color(0.0f, 0.0f, 0.0f, 1f),
ForegroundGrey = new Color(0.30f, 0.30f, 0.31f, 1f),
ForegroundDisabled = new Color(0.45f, 0.45f, 0.49f, 1f),
ForegroundViewport = new Color(1.0f, 1.0f, 1.0f, 1f),
BackgroundHighlighted = new Color(0.59f, 0.59f, 0.64f, 1f),
BorderHighlighted = new Color(0.50f, 0.50f, 0.55f, 1f),
BackgroundSelected = new Color(0.00f, 0.46f, 0.78f, 0.78f),
BorderSelected = new Color(0.11f, 0.57f, 0.88f, 0.65f),
BackgroundNormal = new Color(0.67f, 0.67f, 0.75f, 1f),
BorderNormal = new Color(0.59f, 0.59f, 0.64f, 1f),
TextBoxBackground = new Color(0.75f, 0.75f, 0.81f, 1f),
TextBoxBackgroundSelected = new Color(0.73f, 0.73f, 0.80f, 1f),
CollectionBackgroundColor = new Color(0.85f, 0.85f, 0.88f, 1f),
ProgressNormal = new Color(0.03f, 0.65f, 0.12f, 1f),
// Fonts
FontTitle = options.Interface.TitleFont.GetFont(),
FontLarge = options.Interface.LargeFont.GetFont(),
FontMedium = options.Interface.MediumFont.GetFont(),
FontSmall = options.Interface.SmallFont.GetFont(),
// Icons
ArrowDown = Editor.Icons.ArrowDown12,
ArrowRight = Editor.Icons.ArrowRight12,
Search = Editor.Icons.Search12,
Settings = Editor.Icons.Settings12,
Cross = Editor.Icons.Cross12,
CheckBoxIntermediate = Editor.Icons.CheckBoxIntermediate12,
CheckBoxTick = Editor.Icons.CheckBoxTick12,
StatusBarSizeGrip = Editor.Icons.WindowDrag12,
Translate = Editor.Icons.Translate32,
Rotate = Editor.Icons.Rotate32,
Scale = Editor.Icons.Scale32,
Scalar = Editor.Icons.Scalar32,
SharedTooltip = new Tooltip(),
};
return style;
}

View File

@@ -15,6 +15,9 @@ namespace FlaxEditor.Options
[CustomEditor(typeof(ThemeOptionsEditor))]
public sealed class ThemeOptions
{
internal const string DefaultName = "Default";
internal const string LightDefault = "LightDefault";
internal class ThemeOptionsEditor : Editor<ThemeOptions>
{
private LabelElement _infoLabel;
@@ -63,13 +66,14 @@ namespace FlaxEditor.Options
private void ReloadOptions(ComboBox obj)
{
var themeOptions = (ThemeOptions)ParentEditor.Values[0];
var options = new string[themeOptions.Styles.Count + 1];
options[0] = "Default";
var options = new string[themeOptions.Styles.Count + 2];
options[0] = DefaultName;
options[1] = LightDefault;
int i = 0;
foreach (var styleName in themeOptions.Styles.Keys)
{
options[i + 1] = styleName;
options[i + 2] = styleName;
i++;
}
_combobox.ComboBox.SetItems(options);

View File

@@ -25,7 +25,6 @@ namespace FlaxEditor.Progress.Handlers
ScriptsBuilder.ScriptsReloadCalled += () => OnUpdate(0.8f, "Reloading scripts...");
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd;
ScriptsBuilder.ScriptsReload += OnScriptsReload;
}
private void OnScriptsReloadBegin()
@@ -38,14 +37,6 @@ namespace FlaxEditor.Progress.Handlers
Editor.Instance.Scene.ClearRefsToSceneObjects(true);
}
private void OnScriptsReload()
{
#if !USE_NETCORE
// Clear types cache
Newtonsoft.Json.JsonSerializer.ClearCache();
#endif
}
private void OnCompilationFailed()
{
OnFail("Scripts compilation failed");

View File

@@ -207,9 +207,9 @@ void RiderCodeEditor::FindEditors(Array<CodeEditor*>* output)
FileSystem::GetChildDirectories(subDirectories, TEXT("/opt/"));
// Versions installed via JetBrains Toolbox
SearchDirectory(&installations, localAppDataPath / TEXT(".local/share/JetBrains/Toolbox/apps/rider/"));
FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT(".local/share/JetBrains/Toolbox/apps/Rider/ch-0"));
FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT(".local/share/JetBrains/Toolbox/apps/Rider/ch-1")); // Beta versions
SearchDirectory(&installations, localAppDataPath / TEXT("JetBrains/Toolbox/apps/rider/"));
FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT("JetBrains/Toolbox/apps/Rider/ch-0"));
FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT("JetBrains/Toolbox/apps/Rider/ch-1")); // Beta versions
// Detect Flatpak installations
SearchDirectory(&installations,

View File

@@ -1,7 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEngine;
using FlaxEditor.Utilities;
using FlaxEngine.Utilities;
namespace FlaxEditor.States

View File

@@ -882,6 +882,60 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Output(1, "Inv Size", typeof(Float2), 1),
}
},
new NodeArchetype
{
TypeID = 40,
Title = "Rectangle Mask",
Description = "Creates a rectangle mask",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(150, 40),
ConnectionsHints = ConnectionsHint.Vector,
DefaultValues = new object[]
{
new Float2(0.5f, 0.5f),
},
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0),
NodeElementArchetype.Factory.Input(1, "Rectangle", true, typeof(Float2), 1, 0),
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 2),
}
},
new NodeArchetype
{
TypeID = 41,
Title = "FWidth",
Description = "Creates a partial derivative (fwidth)",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(150, 20),
ConnectionsHints = ConnectionsHint.Numeric,
IndependentBoxes = new[] { 0 },
DependentBoxes = new[] { 1 },
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Value", true, null, 0),
NodeElementArchetype.Factory.Output(0, string.Empty, null, 1),
}
},
new NodeArchetype
{
TypeID = 42,
Title = "AA Step",
Description = "Smooth version of step function with less aliasing",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(150, 40),
ConnectionsHints = ConnectionsHint.Vector,
DefaultValues = new object[]
{
0.5f
},
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Value", true, typeof(float), 0),
NodeElementArchetype.Factory.Input(1, "Gradient", true, typeof(float), 1, 0),
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 2),
}
},
};
}
}

View File

@@ -18,21 +18,25 @@ namespace FlaxEngine.Tools
/// <summary>
/// Brush radius (world-space).
/// </summary>
[Limit(0)]
public float BrushSize = 50.0f;
/// <summary>
/// Brush paint intensity.
/// </summary>
[Limit(0)]
public float BrushStrength = 2.0f;
/// <summary>
/// Brush paint falloff. Hardens or softens painting.
/// </summary>
[Limit(0)]
public float BrushFalloff = 1.5f;
/// <summary>
/// Value to paint with. Hold Ctrl hey to paint with inverse value (1 - value).
/// </summary>
[Limit(0, 1, 0.01f)]
public float PaintValue = 0.0f;
/// <summary>

View File

@@ -7,6 +7,8 @@ using FlaxEngine.GUI;
using FlaxEditor.Viewport.Widgets;
using FlaxEditor.GUI.ContextMenu;
using Object = FlaxEngine.Object;
using FlaxEditor.GUI;
using FlaxEditor.Scripting;
namespace FlaxEditor.Viewport.Previews
{
@@ -49,6 +51,8 @@ namespace FlaxEditor.Viewport.Previews
private Image _guiMaterialControl;
private readonly MaterialBase[] _postFxMaterialsCache = new MaterialBase[1];
private ContextMenu _modelWidgetButtonMenu;
private AssetPicker _customModelPicker;
private Model _customModel;
/// <summary>
/// Gets or sets the material asset to preview. It can be <see cref="FlaxEngine.Material"/> or <see cref="FlaxEngine.MaterialInstance"/>.
@@ -74,15 +78,66 @@ namespace FlaxEditor.Viewport.Previews
get => _selectedModelIndex;
set
{
if (value == -1) // Using Custom Model
return;
if (value < 0 || value > Models.Length)
throw new ArgumentOutOfRangeException();
if (_customModelPicker != null)
_customModelPicker.Validator.SelectedAsset = null;
_selectedModelIndex = value;
_previewModel.Model = FlaxEngine.Content.LoadAsyncInternal<Model>("Editor/Primitives/" + Models[value]);
_previewModel.Transform = Transforms[value];
}
}
// Used to automatically update which entry is checked.
// TODO: Maybe a better system with predicate bool checks could be used?
private void ResetModelContextMenu()
{
_modelWidgetButtonMenu.ItemsContainer.DisposeChildren();
// Fill out all models
for (int i = 0; i < Models.Length; i++)
{
var index = i;
var button = _modelWidgetButtonMenu.AddButton(Models[index]);
button.ButtonClicked += _ => SelectedModelIndex = index;
button.Checked = SelectedModelIndex == index && _customModel == null;
button.Tag = index;
}
_modelWidgetButtonMenu.AddSeparator();
_customModelPicker = new AssetPicker(new ScriptType(typeof(Model)), Float2.Zero);
// Label button
var customModelPickerLabel = _modelWidgetButtonMenu.AddButton("Custom Model:");
customModelPickerLabel.CloseMenuOnClick = false;
customModelPickerLabel.Checked = _customModel != null;
// Container button
var customModelPickerButton = _modelWidgetButtonMenu.AddButton("");
customModelPickerButton.Height = _customModelPicker.Height + 4;
customModelPickerButton.CloseMenuOnClick = false;
_customModelPicker.Parent = customModelPickerButton;
_customModelPicker.Validator.SelectedAsset = _customModel;
_customModelPicker.SelectedItemChanged += () =>
{
_customModel = _customModelPicker.Validator.SelectedAsset as Model;
if (_customModelPicker.Validator.SelectedAsset == null)
{
SelectedModelIndex = 0;
ResetModelContextMenu();
return;
}
_previewModel.Model = _customModel;
_previewModel.Transform = Transforms[0];
SelectedModelIndex = -1;
ResetModelContextMenu();
};
}
/// <summary>
/// Initializes a new instance of the <see cref="MaterialPreview"/> class.
/// </summary>
@@ -107,17 +162,7 @@ namespace FlaxEditor.Viewport.Previews
{
if (!control.Visible)
return;
_modelWidgetButtonMenu.ItemsContainer.DisposeChildren();
// Fill out all models
for (int i = 0; i < Models.Length; i++)
{
var index = i;
var button = _modelWidgetButtonMenu.AddButton(Models[index]);
button.ButtonClicked += _ => SelectedModelIndex = index;
button.Checked = SelectedModelIndex == index;
button.Tag = index;
}
ResetModelContextMenu();
};
new ViewportWidgetButton("Model", SpriteHandle.Invalid, _modelWidgetButtonMenu)
{

View File

@@ -115,7 +115,7 @@ namespace FlaxEditor.Viewport.Widgets
if (Icon.IsValid)
{
// Draw icon
Render2D.DrawSprite(Icon, iconRect, style.Foreground);
Render2D.DrawSprite(Icon, iconRect, style.ForegroundViewport);
// Update text rectangle
textRect.Location.X += iconSize;
@@ -123,7 +123,7 @@ namespace FlaxEditor.Viewport.Widgets
}
// Draw text
Render2D.DrawText(style.FontMedium, _text, textRect, style.Foreground * (IsMouseOver ? 1.0f : 0.9f), TextAlignment.Center, TextAlignment.Center);
Render2D.DrawText(style.FontMedium, _text, textRect, style.ForegroundViewport * (IsMouseOver ? 1.0f : 0.9f), TextAlignment.Center, TextAlignment.Center);
}
/// <inheritdoc />

View File

@@ -62,7 +62,9 @@ namespace FlaxEditor.Windows.Profiler
_memoryUsageChart.SelectedSampleChanged += OnSelectedSampleChanged;
// Table
var headerColor = Style.Current.LightBackground;
var style = Style.Current;
var headerColor = style.LightBackground;
var textColor = style.Foreground;
_table = new Table
{
Columns = new[]
@@ -73,22 +75,26 @@ namespace FlaxEditor.Windows.Profiler
CellAlignment = TextAlignment.Near,
Title = "Resource",
TitleBackgroundColor = headerColor,
TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Type",
CellAlignment = TextAlignment.Center,
TitleBackgroundColor = headerColor,
TitleColor = textColor,
},
new ColumnDefinition
{
Title = "References",
TitleBackgroundColor = headerColor,
TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Memory Usage",
TitleBackgroundColor = headerColor,
TitleColor = textColor,
FormatValue = v => Utilities.Utils.FormatBytesCount((ulong)v),
},
},

View File

@@ -92,7 +92,9 @@ namespace FlaxEditor.Windows.Profiler
};
// Table
var headerColor = Style.Current.LightBackground;
var style = Style.Current;
var headerColor = style.LightBackground;
var textColor = style.Foreground;
_table = new Table
{
Columns = new[]
@@ -103,36 +105,42 @@ namespace FlaxEditor.Windows.Profiler
CellAlignment = TextAlignment.Near,
Title = "Event",
TitleBackgroundColor = headerColor,
TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Total",
TitleBackgroundColor = headerColor,
FormatValue = FormatCellPercentage,
TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Self",
TitleBackgroundColor = headerColor,
FormatValue = FormatCellPercentage,
TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Time ms",
TitleBackgroundColor = headerColor,
FormatValue = FormatCellMs,
TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Self ms",
TitleBackgroundColor = headerColor,
FormatValue = FormatCellMs,
TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Memory",
TitleBackgroundColor = headerColor,
FormatValue = FormatCellBytes,
TitleColor = textColor,
},
},
Parent = layout,

View File

@@ -63,7 +63,9 @@ namespace FlaxEditor.Windows.Profiler
};
// Table
var headerColor = Style.Current.LightBackground;
var style = Style.Current;
var headerColor = style.LightBackground;
var textColor = style.Foreground;
_table = new Table
{
Columns = new[]
@@ -74,35 +76,41 @@ namespace FlaxEditor.Windows.Profiler
CellAlignment = TextAlignment.Near,
Title = "Event",
TitleBackgroundColor = headerColor,
TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Total",
TitleBackgroundColor = headerColor,
TitleColor = textColor,
FormatValue = (x) => ((float)x).ToString("0.0") + '%',
},
new ColumnDefinition
{
Title = "GPU ms",
TitleBackgroundColor = headerColor,
TitleColor = textColor,
FormatValue = (x) => ((float)x).ToString("0.000"),
},
new ColumnDefinition
{
Title = "Draw Calls",
TitleBackgroundColor = headerColor,
TitleColor = textColor,
FormatValue = FormatCountLong,
},
new ColumnDefinition
{
Title = "Triangles",
TitleBackgroundColor = headerColor,
TitleColor = textColor,
FormatValue = FormatCountLong,
},
new ColumnDefinition
{
Title = "Vertices",
TitleBackgroundColor = headerColor,
TitleColor = textColor,
FormatValue = FormatCountLong,
},
},

View File

@@ -63,7 +63,9 @@ namespace FlaxEditor.Windows.Profiler
_memoryUsageChart.SelectedSampleChanged += OnSelectedSampleChanged;
// Table
var headerColor = Style.Current.LightBackground;
var style = Style.Current;
var headerColor = style.LightBackground;
var textColor = style.Foreground;
_table = new Table
{
Columns = new[]
@@ -74,18 +76,21 @@ namespace FlaxEditor.Windows.Profiler
CellAlignment = TextAlignment.Near,
Title = "Resource",
TitleBackgroundColor = headerColor,
TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Type",
CellAlignment = TextAlignment.Center,
TitleBackgroundColor = headerColor,
TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Memory Usage",
TitleBackgroundColor = headerColor,
FormatValue = v => Utilities.Utils.FormatBytesCount((ulong)v),
TitleColor = textColor,
},
},
Parent = layout,

View File

@@ -252,7 +252,9 @@ namespace FlaxEditor.Windows.Profiler
private static Table InitTable(ContainerControl parent, string name)
{
var headerColor = Style.Current.LightBackground;
var style = Style.Current;
var headerColor = style.LightBackground;
var textColor = style.Foreground;
var table = new Table
{
Columns = new[]
@@ -263,28 +265,33 @@ namespace FlaxEditor.Windows.Profiler
CellAlignment = TextAlignment.Near,
Title = name,
TitleBackgroundColor = headerColor,
TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Count",
TitleBackgroundColor = headerColor,
TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Data Size",
TitleBackgroundColor = headerColor,
TitleColor = textColor,
FormatValue = FormatCellBytes,
},
new ColumnDefinition
{
Title = "Message Size",
TitleBackgroundColor = headerColor,
TitleColor = textColor,
FormatValue = FormatCellBytes,
},
new ColumnDefinition
{
Title = "Receivers",
TitleBackgroundColor = headerColor,
TitleColor = textColor,
},
},
Splits = new[]

View File

@@ -105,7 +105,7 @@ namespace FlaxEditor.Windows.Profiler
if (_selectedSampleIndex != -1)
{
float selectedX = Width - (_samples.Count - _selectedSampleIndex - 1) * PointsOffset;
Render2D.DrawLine(new Float2(selectedX, 0), new Float2(selectedX, chartHeight), Color.White, 1.5f);
Render2D.DrawLine(new Float2(selectedX, 0), new Float2(selectedX, chartHeight), style.Foreground, 1.5f);
}
int samplesInViewCount = Math.Min((int)(Width / PointsOffset), _samples.Count) - 1;
@@ -138,8 +138,8 @@ namespace FlaxEditor.Windows.Profiler
var headerRect = new Rectangle(0, chartHeight, Width, TitleHeight);
var headerTextRect = new Rectangle(2, chartHeight, Width - 4, TitleHeight);
Render2D.FillRectangle(headerRect, style.BackgroundNormal);
Render2D.DrawText(style.FontMedium, Title, headerTextRect, Color.White * 0.8f, TextAlignment.Near, TextAlignment.Center);
Render2D.DrawText(style.FontMedium, _sample, headerTextRect, Color.White, TextAlignment.Far, TextAlignment.Center);
Render2D.DrawText(style.FontMedium, Title, headerTextRect, style.ForegroundGrey, TextAlignment.Near, TextAlignment.Center);
Render2D.DrawText(style.FontMedium, _sample, headerTextRect, style.Foreground, TextAlignment.Far, TextAlignment.Center);
}
private void OnClick(ref Float2 location)

View File

@@ -90,7 +90,7 @@ namespace FlaxEditor.Windows.Profiler
if (_nameLength < bounds.Width + 4)
{
Render2D.PushClip(bounds);
Render2D.DrawText(style.FontMedium, _name, bounds, Color.White, TextAlignment.Center, TextAlignment.Center);
Render2D.DrawText(style.FontMedium, _name, bounds, Style.Current.Foreground, TextAlignment.Center, TextAlignment.Center);
Render2D.PopClip();
}
}
@@ -115,7 +115,7 @@ namespace FlaxEditor.Windows.Profiler
var style = Style.Current;
var rect = new Rectangle(Float2.Zero, Size);
Render2D.PushClip(rect);
Render2D.DrawText(style.FontMedium, Name, rect, Color.White, TextAlignment.Center, TextAlignment.Center, TextWrapping.WrapChars);
Render2D.DrawText(style.FontMedium, Name, rect, Style.Current.Foreground, TextAlignment.Center, TextAlignment.Center, TextWrapping.WrapChars);
Render2D.PopClip();
}
}

View File

@@ -150,7 +150,13 @@ void BehaviorKnowledge::InitMemory(BehaviorTree* tree)
RelevantNodes.Resize(tree->Graph.NodesCount, false);
RelevantNodes.SetAll(false);
if (!Memory && tree->Graph.NodesStatesSize)
{
Memory = Allocator::Allocate(tree->Graph.NodesStatesSize);
#if !BUILD_RELEASE
// Clear memory to make it easier to spot missing data issues (eg. zero GCHandle in C# BT node due to missing state init)
Platform::MemoryClear(Memory, tree->Graph.NodesStatesSize);
#endif
}
}
void BehaviorKnowledge::FreeMemory()

View File

@@ -202,7 +202,7 @@ namespace FlaxEngine
public T Get(BehaviorKnowledge knowledge)
{
if (knowledge != null && knowledge.Get(Path, out var value))
return (T)value;
return Utilities.VariantUtils.Cast<T>(value);
return default;
}
@@ -218,7 +218,7 @@ namespace FlaxEngine
object tmp = null;
bool result = knowledge != null && knowledge.Get(Path, out tmp);
if (result)
value = (T)tmp;
value = Utilities.VariantUtils.Cast<T>(tmp);
return result;
}

View File

@@ -95,12 +95,16 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T GetState<T>(IntPtr memory) where T : struct
{
var ptr = IntPtr.Add(memory, _memoryOffset).ToPointer();
var handle = GCHandle.FromIntPtr(Unsafe.Read<IntPtr>(ptr));
var ptr = Unsafe.Read<IntPtr>(IntPtr.Add(memory, _memoryOffset).ToPointer());
#if !BUILD_RELEASE
if (ptr == IntPtr.Zero)
throw new Exception($"Missing state '{typeof(T).FullName}' for node '{GetType().FullName}'");
#endif
var handle = GCHandle.FromIntPtr(ptr);
var state = handle.Target;
#if !BUILD_RELEASE
if (state == null)
throw new NullReferenceException();
throw new Exception($"Missing state '{typeof(T).FullName}' for node '{GetType().FullName}'");
#endif
return ref Unsafe.Unbox<T>(state);
}
@@ -111,8 +115,10 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FreeState(IntPtr memory)
{
var ptr = IntPtr.Add(memory, _memoryOffset).ToPointer();
var handle = GCHandle.FromIntPtr(Unsafe.Read<IntPtr>(ptr));
var ptr = Unsafe.Read<IntPtr>(IntPtr.Add(memory, _memoryOffset).ToPointer());
if (ptr == IntPtr.Zero)
return;
var handle = GCHandle.FromIntPtr(ptr);
handle.Free();
}
}

View File

@@ -85,6 +85,8 @@ BehaviorUpdateResult BehaviorTreeNode::InvokeUpdate(const BehaviorUpdateContext&
result = BehaviorUpdateResult::Failed;
else
result = Update(context);
if ((int32)result < 0 || (int32)result > (int32)BehaviorUpdateResult::Failed)
result = BehaviorUpdateResult::Failed; // Invalid value is a failure
// Post-process result from decorators
for (BehaviorTreeDecorator* decorator : _decorators)

View File

@@ -225,6 +225,7 @@ bool AudioClip::ExtractDataRaw(Array<byte>& resultData, AudioDataInfo& resultDat
void AudioClip::CancelStreaming()
{
Asset::CancelStreaming();
CancelStreamingTasks();
}

View File

@@ -522,6 +522,14 @@ void Asset::InitAsVirtual()
void Asset::CancelStreaming()
{
// Cancel loading task but go over asset locker to prevent case if other load threads still loads asset while it's reimported on other thread
Locker.Lock();
ContentLoadTask* loadTask = _loadingTask;
Locker.Unlock();
if (loadTask)
{
loadTask->Cancel();
}
}
#if USE_EDITOR

View File

@@ -783,6 +783,7 @@ void Model::InitAsVirtual()
void Model::CancelStreaming()
{
Asset::CancelStreaming();
CancelStreamingTasks();
}

View File

@@ -969,6 +969,7 @@ void SkinnedModel::InitAsVirtual()
void SkinnedModel::CancelStreaming()
{
Asset::CancelStreaming();
CancelStreamingTasks();
}

View File

@@ -48,6 +48,8 @@ protected:
// [ContentLoadTask]
Result run() override
{
if (IsCancelRequested())
return Result::Ok;
PROFILE_CPU();
AssetReference<BinaryAsset> ref = _asset.Get();
@@ -67,8 +69,6 @@ protected:
{
if (IsCancelRequested())
return Result::Ok;
// Load it
#if TRACY_ENABLE
ZoneScoped;
ZoneName(*name, name.Length());

View File

@@ -1302,15 +1302,15 @@ void FlaxStorage::CloseFileHandles()
// In those situations all the async tasks using this storage should be cancelled externally
// Ensure that no one is using this resource
int32 waitTime = 10;
int32 waitTime = 100;
while (Platform::AtomicRead(&_chunksLock) != 0 && waitTime-- > 0)
Platform::Sleep(10);
Platform::Sleep(1);
if (Platform::AtomicRead(&_chunksLock) != 0)
{
// File can be locked by some streaming tasks (eg. AudioClip::StreamingTask or StreamModelLODTask)
Entry e;
for (int32 i = 0; i < GetEntriesCount(); i++)
{
Entry e;
GetEntry(i, e);
Asset* asset = Content::GetAsset(e.ID);
if (asset)
@@ -1320,8 +1320,12 @@ void FlaxStorage::CloseFileHandles()
}
}
}
waitTime = 100;
while (Platform::AtomicRead(&_chunksLock) != 0 && waitTime-- > 0)
Platform::Sleep(1);
ASSERT(_chunksLock == 0);
// Close file handles (from all threads)
_file.DeleteAll();
}

View File

@@ -3,7 +3,7 @@
#pragma once
#include "Engine/Core/Config/Settings.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Serialization/SerializationFwd.h"
/// <summary>
/// Specifies the display mode of a game window.

View File

@@ -2821,7 +2821,10 @@ void Variant::Inline()
type = VariantType::Types::Vector4;
}
if (type != VariantType::Null)
{
ASSERT(sizeof(data) >= AsBlob.Length);
Platform::MemoryCopy(data, AsBlob.Data, AsBlob.Length);
}
}
if (type != VariantType::Null)
{
@@ -2912,6 +2915,60 @@ void Variant::Inline()
}
}
void Variant::InvertInline()
{
byte data[sizeof(Matrix)];
switch (Type.Type)
{
case VariantType::Bool:
case VariantType::Int:
case VariantType::Uint:
case VariantType::Int64:
case VariantType::Uint64:
case VariantType::Float:
case VariantType::Double:
case VariantType::Pointer:
case VariantType::String:
case VariantType::Float2:
case VariantType::Float3:
case VariantType::Float4:
case VariantType::Color:
#if !USE_LARGE_WORLDS
case VariantType::BoundingSphere:
case VariantType::BoundingBox:
case VariantType::Ray:
#endif
case VariantType::Guid:
case VariantType::Quaternion:
case VariantType::Rectangle:
case VariantType::Int2:
case VariantType::Int3:
case VariantType::Int4:
case VariantType::Int16:
case VariantType::Uint16:
case VariantType::Double2:
case VariantType::Double3:
case VariantType::Double4:
static_assert(sizeof(data) >= sizeof(AsData), "Invalid memory size.");
Platform::MemoryCopy(data, AsData, sizeof(AsData));
break;
#if USE_LARGE_WORLDS
case VariantType::BoundingSphere:
case VariantType::BoundingBox:
case VariantType::Ray:
#endif
case VariantType::Transform:
case VariantType::Matrix:
ASSERT(sizeof(data) >= AsBlob.Length);
Platform::MemoryCopy(data, AsBlob.Data, AsBlob.Length);
break;
default:
return; // Not used
}
SetType(VariantType(VariantType::Structure, InBuiltTypesTypeNames[Type.Type]));
CopyStructure(data);
}
Variant Variant::NewValue(const StringAnsiView& typeName)
{
Variant v;

View File

@@ -372,6 +372,9 @@ public:
// Inlines potential value type into in-built format (eg. Vector3 stored as Structure, or String stored as ManagedObject).
void Inline();
// Inverts the inlined value from in-built format into generic storage (eg. Float3 from inlined format into Structure).
void InvertInline();
// Allocates the Variant of the specific type (eg. structure or object or value).
static Variant NewValue(const StringAnsiView& typeName);

View File

@@ -1022,6 +1022,8 @@ namespace FlaxEngine.Interop
pair.Value.Free();
classAttributesCacheCollectible.Clear();
FlaxEngine.Json.JsonSerializer.ResetCache();
// Unload the ALC
bool unloading = true;
scriptingAssemblyLoadContext.Unloading += (alc) => { unloading = false; };

View File

@@ -278,44 +278,70 @@ namespace FlaxEngine.Interop
if (typeCache.TryGetValue(typeName, out Type type))
return type;
type = Type.GetType(typeName, ResolveAssemblyByName, null);
type = Type.GetType(typeName, ResolveAssembly, null);
if (type == null)
{
foreach (var assembly in scriptingAssemblyLoadContext.Assemblies)
{
type = assembly.GetType(typeName);
if (type != null)
break;
}
}
type = ResolveSlow(typeName);
if (type == null)
{
string oldTypeName = typeName;
string fullTypeName = typeName;
typeName = typeName.Substring(0, typeName.IndexOf(','));
type = Type.GetType(typeName, ResolveAssemblyByName, null);
type = Type.GetType(typeName, ResolveAssembly, null);
if (type == null)
{
foreach (var assembly in scriptingAssemblyLoadContext.Assemblies)
{
type = assembly.GetType(typeName);
if (type != null)
break;
}
}
typeName = oldTypeName;
type = ResolveSlow(typeName);
typeName = fullTypeName;
}
typeCache.Add(typeName, type);
return type;
static Type ResolveSlow(string typeName)
{
foreach (var assembly in scriptingAssemblyLoadContext.Assemblies)
{
var type = assembly.GetType(typeName);
if (type != null)
return type;
}
return null;
}
static Assembly ResolveAssembly(AssemblyName name) => ResolveScriptingAssemblyByName(name, allowPartial: false);
}
private static Assembly ResolveAssemblyByName(AssemblyName assemblyName)
/// <summary>Find <paramref name="assemblyName"/> among the scripting assemblies.</summary>
/// <param name="assemblyName">The name to find</param>
/// <param name="allowPartial">If true, partial names should be allowed to be resolved.</param>
/// <returns>The resolved assembly, or null if none could be found.</returns>
internal static Assembly ResolveScriptingAssemblyByName(AssemblyName assemblyName, bool allowPartial = false)
{
foreach (Assembly assembly in scriptingAssemblyLoadContext.Assemblies)
if (assembly.GetName() == assemblyName)
var lc = scriptingAssemblyLoadContext;
if (lc is null)
return null;
foreach (Assembly assembly in lc.Assemblies)
{
var curName = assembly.GetName();
if (curName == assemblyName)
return assembly;
}
if (allowPartial) // Check partial names if full name isn't found
{
string partialName = assemblyName.Name;
foreach (Assembly assembly in lc.Assemblies)
{
var curName = assembly.GetName();
if (curName.Name == partialName)
return assembly;
}
}
return null;
}

View File

@@ -3,7 +3,6 @@
#pragma once
#include "../Enums.h"
#include "Engine/Core/Math/Math.h"
/// <summary>
/// Material domain type. Material domain defines the target usage of the material shader.
@@ -86,7 +85,7 @@ API_ENUM() enum class MaterialBlendMode : byte
API_ENUM() enum class MaterialShadingModel : byte
{
/// <summary>
/// The unlit material. Emissive channel is used as an output color. Can perform custom lighting operations or just glow. Won't be affected by the lighting pipeline.
/// The unlit material. The emissive channel is used as an output color. Can perform custom lighting operations or just glow. Won't be affected by the lighting pipeline.
/// </summary>
Unlit = 0,
@@ -96,7 +95,7 @@ API_ENUM() enum class MaterialShadingModel : byte
Lit = 1,
/// <summary>
/// The subsurface material. Intended for materials like vax or skin that need light scattering to transport simulation through the object.
/// The subsurface material. Intended for materials like wax or skin that need light scattering to transport simulation through the object.
/// </summary>
Subsurface = 2,
@@ -366,12 +365,12 @@ API_ENUM() enum class MaterialDecalBlendingMode : byte
API_ENUM() enum class MaterialTransparentLightingMode : byte
{
/// <summary>
/// Default directional lighting evaluated per-pixel at the material surface. Use it for semi-transparent surfaces - with both diffuse and specular lighting component active.
/// Default directional lighting evaluated per-pixel at the material surface. Use it for semi-transparent surfaces - with both diffuse and specular lighting components active.
/// </summary>
Surface = 0,
/// <summary>
/// Non-directional lighting evaluated per-pixel at material surface. Use it for volumetric objects such as smoke, rain or dust - only diffuse lighting term is active (no specular highlights).
/// Non-directional lighting evaluated per-pixel at material surface. Use it for volumetric objects such as smoke, rain or dust - only the diffuse lighting term is active (no specular highlights).
/// </summary>
SurfaceNonDirectional = 1,
};

View File

@@ -660,6 +660,7 @@ uint64 TextureBase::GetMemoryUsage() const
void TextureBase::CancelStreaming()
{
Asset::CancelStreaming();
_texture.CancelStreamingTasks();
}

View File

@@ -4,6 +4,7 @@
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/GPUResource.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "../GPUDeviceDX.h"
#include "../IncludeDirectXHeaders.h"

View File

@@ -7,6 +7,7 @@
#include "Engine/Graphics/GPUPipelineState.h"
#include "GPUDeviceDX12.h"
#include "Types.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "../IncludeDirectXHeaders.h"
class GPUTextureViewDX12;

View File

@@ -13,7 +13,7 @@ class FLAXENGINE_API DirectionalLight : public LightWithShadow
DECLARE_SCENE_OBJECT(DirectionalLight);
public:
/// <summary>
/// The number of cascades used for slicing the range of depth covered by the light. Values are 1, 2 or 4 cascades; a typical scene uses 4 cascades.
/// The number of cascades used for slicing the range of depth covered by the light during shadow rendering. Values are 1, 2 or 4 cascades; a typical scene uses 4 cascades.
/// </summary>
API_FIELD(Attributes="EditorOrder(65), DefaultValue(4), Limit(1, 4), EditorDisplay(\"Shadow\")")
int32 CascadeCount = 4;

View File

@@ -29,7 +29,7 @@ public:
float Brightness = 3.14f;
/// <summary>
/// Controls light visibility range. The distance at which the light be completely faded. Use value 0 to always draw light.
/// Controls light visibility range. The distance at which the light becomes completely faded. Use a value of 0 to always draw light.
/// </summary>
API_FIELD(Attributes="EditorOrder(35), Limit(0, float.MaxValue, 10.0f), EditorDisplay(\"Light\")")
float ViewDistance = 0.0f;
@@ -87,19 +87,19 @@ public:
float MinRoughness = 0.04f;
/// <summary>
/// The light shadows casting distance from view.
/// Shadows casting distance from view.
/// </summary>
API_FIELD(Attributes="EditorOrder(80), EditorDisplay(\"Shadow\", \"Distance\"), Limit(0, 1000000)")
float ShadowsDistance = 5000.0f;
/// <summary>
/// The light shadows fade off distance
/// Shadows fade off distance.
/// </summary>
API_FIELD(Attributes="EditorOrder(90), EditorDisplay(\"Shadow\", \"Fade Distance\"), Limit(0.0f, 10000.0f, 0.1f)")
float ShadowsFadeDistance = 500.0f;
/// <summary>
/// The light shadows edges sharpness
/// TheShadows edges sharpness.
/// </summary>
API_FIELD(Attributes="EditorOrder(70), EditorDisplay(\"Shadow\", \"Sharpness\"), Limit(1.0f, 10.0f, 0.001f)")
float ShadowsSharpness = 1.0f;
@@ -129,7 +129,7 @@ public:
float ContactShadowsLength = 0.0f;
/// <summary>
/// Shadows casting mode by this visual element
/// Describes how a visual element casts shadows.
/// </summary>
API_FIELD(Attributes="EditorOrder(60), EditorDisplay(\"Shadow\", \"Mode\")")
ShadowsCastingMode ShadowsMode = ShadowsCastingMode::All;

View File

@@ -149,9 +149,14 @@ float Spline::GetSplineDuration() const
float Spline::GetSplineLength() const
{
float sum = 0.0f;
const int32 slices = 20;
const float step = 1.0f / (float)slices;
constexpr int32 slices = 20;
constexpr float step = 1.0f / (float)slices;
Vector3 prevPoint = Vector3::Zero;
if (Curve.GetKeyframes().Count() != 0)
{
const auto& a = Curve[0];
prevPoint = a.Value.Translation * _transform.Scale;
}
for (int32 i = 1; i < Curve.GetKeyframes().Count(); i++)
{
const auto& a = Curve[i - 1];
@@ -176,6 +181,37 @@ float Spline::GetSplineLength() const
return Math::Sqrt(sum);
}
float Spline::GetSplineSegmentLength(int32 index) const
{
if (index == 0)
return 0.0f;
CHECK_RETURN(index > 0 && index < GetSplinePointsCount(), 0.0f);
float sum = 0.0f;
constexpr int32 slices = 20;
constexpr float step = 1.0f / (float)slices;
const auto& a = Curve[index - 1];
const auto& b = Curve[index];
Vector3 startPoint = a.Value.Translation * _transform.Scale;
{
const float length = Math::Abs(b.Time - a.Time);
Vector3 leftTangent, rightTangent;
AnimationUtils::GetTangent(a.Value.Translation, a.TangentOut.Translation, length, leftTangent);
AnimationUtils::GetTangent(b.Value.Translation, b.TangentIn.Translation, length, rightTangent);
// TODO: implement sth more analytical than brute-force solution
for (int32 slice = 0; slice < slices; slice++)
{
const float t = (float)slice * step;
Vector3 pos;
AnimationUtils::Bezier(a.Value.Translation, leftTangent, rightTangent, b.Value.Translation, t, pos);
pos *= _transform.Scale;
sum += (float)Vector3::DistanceSquared(pos, startPoint);
startPoint = pos;
}
}
return Math::Sqrt(sum);
}
float Spline::GetSplineTime(int32 index) const
{
CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), 0.0f)

View File

@@ -167,6 +167,13 @@ public:
/// </summary>
API_PROPERTY() float GetSplineLength() const;
/// <summary>
/// Gets the length of the spline segment (distance between pair of two points).
/// </summary>
/// <param name="index">The index of the segment end index. Zero-based, smaller than GetSplinePointsCount().</param>
/// <returns>The spline segment length.</returns>
API_FUNCTION() float GetSplineSegmentLength(int32 index) const;
/// <summary>
/// Gets the time of the spline keyframe.
/// </summary>

View File

@@ -9,6 +9,8 @@
#include "Engine/Content/JsonAsset.h"
#include "Engine/Threading/Threading.h"
#if USE_EDITOR
#include "Editor/Editor.h"
#include "Editor/Managed/ManagedEditor.h"
#include "Engine/Level/Level.h"
#include "Engine/Level/Scene/Scene.h"
#endif
@@ -220,6 +222,17 @@ void NavigationSettings::Apply()
#endif
}
}
#if USE_EDITOR
if (!Editor::IsPlayMode && Editor::Managed && Editor::Managed->CanAutoBuildNavMesh())
{
// Rebuild all navmeshs after apply changes on navigation
for (auto scene : Level::Scenes)
{
Navigation::BuildNavMesh(scene);
}
}
#endif
}
void NavigationSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)

View File

@@ -301,11 +301,8 @@ void NetworkTransform::Deserialize(NetworkStream* stream)
_buffer.Clear();
_bufferHasDeltas = true;
}
// TODO: items are added in order to do batch removal
for (int32 i = 0; i < _buffer.Count() && _buffer[i].SequenceIndex < sequenceIndex; i++)
{
_buffer.RemoveAtKeepOrder(i);
}
while (_buffer.Count() != 0 && _buffer[0].SequenceIndex < sequenceIndex)
_buffer.RemoveAtKeepOrder(0);
// Use received authoritative actor transformation but re-apply all deltas not yet processed by the server due to lag (reconciliation)
for (auto& e : _buffer)

View File

@@ -35,7 +35,11 @@ void RigidBody::SetIsKinematic(const bool value)
return;
_isKinematic = value;
if (_actor)
{
PhysicsBackend::SetRigidDynamicActorFlag(_actor, PhysicsBackend::RigidDynamicFlags::Kinematic, value);
if (!value && _isActive && _startAwake)
WakeUp();
}
}
void RigidBody::SetLinearDamping(float value)

View File

@@ -137,7 +137,11 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c
c.ThisActor = static_cast<PhysicsColliderActor*>(pair.shapes[0]->userData);
c.OtherActor = static_cast<PhysicsColliderActor*>(pair.shapes[1]->userData);
ASSERT_LOW_LAYER(c.ThisActor && c.OtherActor);
if (c.ThisActor == nullptr || c.OtherActor == nullptr)
{
// One of the actors was deleted (eg. via RigidBody destroyed by gameplay) then skip processing this collision
continue;
}
// Extract contact points
while (i.hasNextPatch())

View File

@@ -14,8 +14,8 @@ class Texture;
/// </summary>
API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API AndroidPlatformSettings : public SettingsBase
{
DECLARE_SCRIPTING_TYPE_MINIMAL(AndroidPlatformSettings);
public:
DECLARE_SCRIPTING_TYPE_MINIMAL(AndroidPlatformSettings);
API_AUTO_SERIALIZATION();
/// <summary>
/// The application package name (eg. com.company.product). Custom tokens: ${PROJECT_NAME}, ${COMPANY_NAME}.
@@ -35,20 +35,10 @@ public:
API_FIELD(Attributes="EditorOrder(1030), EditorDisplay(\"Other\")")
SoftObjectReference<Texture> OverrideIcon;
public:
/// <summary>
/// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use.
/// </summary>
static AndroidPlatformSettings* Get();
// [SettingsBase]
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override
{
DESERIALIZE(PackageName);
DESERIALIZE(Permissions);
DESERIALIZE(OverrideIcon);
}
};
#if PLATFORM_ANDROID

View File

@@ -16,6 +16,7 @@ class Texture;
API_CLASS(Abstract, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API ApplePlatformSettings : public SettingsBase
{
DECLARE_SCRIPTING_TYPE_MINIMAL(ApplePlatformSettings);
API_AUTO_SERIALIZATION();
/// <summary>
/// The app identifier (reversed DNS, eg. com.company.product). Custom tokens: ${PROJECT_NAME}, ${COMPANY_NAME}.
@@ -28,14 +29,6 @@ API_CLASS(Abstract, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_AP
/// </summary>
API_FIELD(Attributes="EditorOrder(1000), EditorDisplay(\"Other\")")
SoftObjectReference<Texture> OverrideIcon;
public:
// [SettingsBase]
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override
{
DESERIALIZE(AppIdentifier);
DESERIALIZE(OverrideIcon);
}
};
#endif

View File

@@ -16,7 +16,8 @@ class Texture;
API_CLASS(Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API GDKPlatformSettings : public SettingsBase
{
DECLARE_SCRIPTING_TYPE_MINIMAL(GDKPlatformSettings);
public:
API_AUTO_SERIALIZATION();
/// <summary>
/// Game identity name stored in game package manifest (for store). If empty the product name will be used from Game Settings.
/// </summary>
@@ -118,28 +119,6 @@ public:
/// </summary>
API_FIELD(Attributes="EditorOrder(420), EditorDisplay(\"Media Capture\")")
bool BlockGameDVR = false;
public:
// [SettingsBase]
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override
{
DESERIALIZE(Name);
DESERIALIZE(PublisherName);
DESERIALIZE(PublisherDisplayName);
DESERIALIZE(Square150x150Logo);
DESERIALIZE(Square480x480Logo);
DESERIALIZE(Square44x44Logo);
DESERIALIZE(SplashScreenImage);
DESERIALIZE(StoreLogo);
DESERIALIZE(BackgroundColor);
DESERIALIZE(TitleId);
DESERIALIZE(StoreId);
DESERIALIZE(RequiresXboxLive);
DESERIALIZE(SCID);
DESERIALIZE(GameDVRSystemComponent);
DESERIALIZE(BlockBroadcast);
DESERIALIZE(BlockGameDVR);
}
};
#endif

View File

@@ -14,8 +14,8 @@ class Texture;
/// </summary>
API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API LinuxPlatformSettings : public SettingsBase
{
DECLARE_SCRIPTING_TYPE_MINIMAL(LinuxPlatformSettings);
public:
DECLARE_SCRIPTING_TYPE_MINIMAL(LinuxPlatformSettings);
API_AUTO_SERIALIZATION();
/// <summary>
/// The default game window mode.
@@ -65,25 +65,10 @@ public:
API_FIELD(Attributes="EditorOrder(2000), DefaultValue(true), EditorDisplay(\"Graphics\")")
bool SupportVulkan = true;
public:
/// <summary>
/// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use.
/// </summary>
static LinuxPlatformSettings* Get();
// [SettingsBase]
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override
{
DESERIALIZE(WindowMode);
DESERIALIZE(ScreenWidth);
DESERIALIZE(ScreenHeight);
DESERIALIZE(RunInBackground);
DESERIALIZE(ResizableWindow);
DESERIALIZE(ForceSingleInstance);
DESERIALIZE(OverrideIcon);
DESERIALIZE(SupportVulkan);
}
};
#if PLATFORM_LINUX

View File

@@ -12,6 +12,7 @@
API_CLASS(Sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API MacPlatformSettings : public ApplePlatformSettings
{
DECLARE_SCRIPTING_TYPE_MINIMAL(MacPlatformSettings);
API_AUTO_SERIALIZATION();
/// <summary>
/// The default game window mode.
@@ -43,22 +44,10 @@ API_CLASS(Sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API
API_FIELD(Attributes="EditorOrder(1010), EditorDisplay(\"Other\", \"Run In Background\")")
bool RunInBackground = false;
public:
/// <summary>
/// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use.
/// </summary>
static MacPlatformSettings* Get();
// [SettingsBase]
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override
{
ApplePlatformSettings::Deserialize(stream, modifier);
DESERIALIZE(WindowMode);
DESERIALIZE(ScreenWidth);
DESERIALIZE(ScreenHeight);
DESERIALIZE(ResizableWindow);
DESERIALIZE(RunInBackground);
}
};
#if PLATFORM_MAC

View File

@@ -11,8 +11,8 @@
/// </summary>
API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API UWPPlatformSettings : public SettingsBase
{
DECLARE_SCRIPTING_TYPE_MINIMAL(UWPPlatformSettings);
public:
DECLARE_SCRIPTING_TYPE_MINIMAL(UWPPlatformSettings);
API_AUTO_SERIALIZATION();
/// <summary>
/// The preferred launch windowing mode.
@@ -66,8 +66,6 @@ public:
All = Landscape | LandscapeFlipped | Portrait | PortraitFlipped
};
public:
/// <summary>
/// The preferred launch windowing mode. Always fullscreen on Xbox.
/// </summary>
@@ -98,22 +96,10 @@ public:
API_FIELD(Attributes="EditorOrder(2010), DefaultValue(false), EditorDisplay(\"Graphics\", \"Support DirectX 10\")")
bool SupportDX10 = false;
public:
/// <summary>
/// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use.
/// </summary>
static UWPPlatformSettings* Get();
// [SettingsBase]
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override
{
DESERIALIZE(PreferredLaunchWindowingMode);
DESERIALIZE(AutoRotationPreferences);
DESERIALIZE(CertificateLocation);
DESERIALIZE(SupportDX11);
DESERIALIZE(SupportDX10);
}
};
#if PLATFORM_UWP

View File

@@ -14,8 +14,8 @@ class Texture;
/// </summary>
API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API WindowsPlatformSettings : public SettingsBase
{
DECLARE_SCRIPTING_TYPE_MINIMAL(WindowsPlatformSettings);
public:
DECLARE_SCRIPTING_TYPE_MINIMAL(WindowsPlatformSettings);
API_AUTO_SERIALIZATION();
/// <summary>
/// The default game window mode.
@@ -83,28 +83,10 @@ public:
API_FIELD(Attributes="EditorOrder(2030), DefaultValue(false), EditorDisplay(\"Graphics\")")
bool SupportVulkan = false;
public:
/// <summary>
/// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use.
/// </summary>
static WindowsPlatformSettings* Get();
// [SettingsBase]
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override
{
DESERIALIZE(WindowMode);
DESERIALIZE(ScreenWidth);
DESERIALIZE(ScreenHeight);
DESERIALIZE(RunInBackground);
DESERIALIZE(ResizableWindow);
DESERIALIZE(ForceSingleInstance);
DESERIALIZE(OverrideIcon);
DESERIALIZE(SupportDX12);
DESERIALIZE(SupportDX11);
DESERIALIZE(SupportDX10);
DESERIALIZE(SupportVulkan);
}
};
#if PLATFORM_WINDOWS

View File

@@ -12,6 +12,7 @@
API_CLASS(Sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API iOSPlatformSettings : public ApplePlatformSettings
{
DECLARE_SCRIPTING_TYPE_MINIMAL(ApplePlatformSettings);
API_AUTO_SERIALIZATION();
/// <summary>
/// The app export destination methods.
@@ -79,17 +80,6 @@ API_CLASS(Sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API
/// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use.
/// </summary>
static iOSPlatformSettings* Get();
// [SettingsBase]
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override
{
ApplePlatformSettings::Deserialize(stream, modifier);
DESERIALIZE(AppTeamId);
DESERIALIZE(AppVersion);
DESERIALIZE(ExportMethod);
DESERIALIZE(SupportedInterfaceOrientationsiPhone);
DESERIALIZE(SupportedInterfaceOrientationsiPad);
}
};
#if PLATFORM_IOS

View File

@@ -87,6 +87,16 @@ bool FontAsset::Init()
return error;
}
FontFlags FontAsset::GetStyle() const
{
FontFlags flags = FontFlags::None;
if ((_face->style_flags & FT_STYLE_FLAG_ITALIC) != 0)
flags |= FontFlags::Italic;
if ((_face->style_flags & FT_STYLE_FLAG_BOLD) != 0)
flags |= FontFlags::Bold;
return flags;
}
void FontAsset::SetOptions(const FontOptions& value)
{
_options = value;

View File

@@ -128,6 +128,11 @@ public:
return _options;
}
/// <summary>
/// Gets the font style flags.
/// </summary>
API_PROPERTY() FontFlags GetStyle() const;
/// <summary>
/// Sets the font options.
/// </summary>

View File

@@ -108,19 +108,19 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(LightmapSettings);
};
/// <summary>
/// Controls how much all lights will contribute indirect lighting.
/// Controls how much all lights will contribute to indirect lighting.
/// </summary>
API_FIELD(Attributes="EditorOrder(0), Limit(0, 100.0f, 0.1f)")
float IndirectLightingIntensity = 1.0f;
/// <summary>
/// Global scale for objects in lightmap to increase quality
/// Global scale for objects in the lightmap to increase quality
/// </summary>
API_FIELD(Attributes="EditorOrder(10), Limit(0, 100.0f, 0.1f)")
float GlobalObjectsScale = 1.0f;
/// <summary>
/// Amount of pixels space between charts in lightmap atlas
/// Amount of pixel space between charts in lightmap atlas
/// </summary>
API_FIELD(Attributes="EditorOrder(20), Limit(0, 16, 0.1f)")
int32 ChartsPadding = 3;

View File

@@ -906,6 +906,7 @@ void ManagedBinaryModule::OnLoaded(MAssembly* assembly)
#if !COMPILE_WITHOUT_CSHARP
PROFILE_CPU();
ASSERT(ClassToTypeIndex.IsEmpty());
ScopeLock lock(Locker);
const auto& classes = assembly->GetClasses();

View File

@@ -64,8 +64,8 @@ struct MConverter<T, typename TEnableIf<TAnd<TIsPODType<T>, TNot<TIsBaseOf<class
void Unbox(T& result, MObject* data)
{
CHECK(data);
Platform::MemoryCopy(&result, MCore::Object::Unbox(data), sizeof(T));
if (data)
Platform::MemoryCopy(&result, MCore::Object::Unbox(data), sizeof(T));
}
void ToManagedArray(MArray* result, const Span<T>& data)

View File

@@ -719,6 +719,7 @@ void GetAssemblyName(void* assemblyHandle, StringAnsi& name, StringAnsi& fullnam
DEFINE_INTERNAL_CALL(void) NativeInterop_CreateClass(NativeClassDefinitions* managedClass, void* assemblyHandle)
{
ScopeLock lock(BinaryModule::Locker);
MAssembly* assembly = GetAssembly(assemblyHandle);
if (assembly == nullptr)
{
@@ -732,7 +733,18 @@ DEFINE_INTERNAL_CALL(void) NativeInterop_CreateClass(NativeClassDefinitions* man
MClass* klass = New<MClass>(assembly, managedClass->typeHandle, managedClass->name, managedClass->fullname, managedClass->namespace_, managedClass->typeAttributes);
if (assembly != nullptr)
{
const_cast<MAssembly::ClassesDictionary&>(assembly->GetClasses()).Add(klass->GetFullName(), klass);
auto& classes = const_cast<MAssembly::ClassesDictionary&>(assembly->GetClasses());
MClass* oldKlass;
if (classes.TryGet(klass->GetFullName(), oldKlass))
{
LOG(Warning, "Class '{0}' was already added to assembly '{1}'", String(klass->GetFullName()), String(assembly->GetName()));
Delete(klass);
klass = oldKlass;
}
else
{
classes.Add(klass->GetFullName(), klass);
}
}
managedClass->nativePointer = klass;
}
@@ -873,7 +885,7 @@ MClass::MClass(const MAssembly* parentAssembly, void* handle, const char* name,
static void* TypeIsEnumPtr = GetStaticMethodPointer(TEXT("TypeIsEnum"));
_isEnum = CallStaticMethod<bool, void*>(TypeIsEnumPtr, handle);
CachedClassHandles.Add(handle, this);
CachedClassHandles[handle] = this;
}
bool MAssembly::ResolveMissingFile(String& assemblyPath) const
@@ -1551,6 +1563,7 @@ const Array<MObject*>& MProperty::GetAttributes() const
MAssembly* GetAssembly(void* assemblyHandle)
{
ScopeLock lock(BinaryModule::Locker);
MAssembly* assembly;
if (CachedAssemblyHandles.TryGet(assemblyHandle, assembly))
return assembly;
@@ -1559,6 +1572,7 @@ MAssembly* GetAssembly(void* assemblyHandle)
MClass* GetClass(MType* typeHandle)
{
ScopeLock lock(BinaryModule::Locker);
MClass* klass = nullptr;
CachedClassHandles.TryGet(typeHandle, klass);
return nullptr;
@@ -1568,6 +1582,7 @@ MClass* GetOrCreateClass(MType* typeHandle)
{
if (!typeHandle)
return nullptr;
ScopeLock lock(BinaryModule::Locker);
MClass* klass;
if (!CachedClassHandles.TryGet(typeHandle, klass))
{
@@ -1579,7 +1594,12 @@ MClass* GetOrCreateClass(MType* typeHandle)
klass = New<MClass>(assembly, classInfo.typeHandle, classInfo.name, classInfo.fullname, classInfo.namespace_, classInfo.typeAttributes);
if (assembly != nullptr)
{
const_cast<MAssembly::ClassesDictionary&>(assembly->GetClasses()).Add(klass->GetFullName(), klass);
auto& classes = const_cast<MAssembly::ClassesDictionary&>(assembly->GetClasses());
if (classes.ContainsKey(klass->GetFullName()))
{
LOG(Warning, "Class '{0}' was already added to assembly '{1}'", String(klass->GetFullName()), String(assembly->GetName()));
}
classes[klass->GetFullName()] = klass;
}
if (typeHandle != classInfo.typeHandle)

View File

@@ -436,6 +436,7 @@ bool Scripting::Load()
PROFILE_CPU();
// Note: this action can be called from main thread (due to Mono problems with assemblies actions from other threads)
ASSERT(IsInMainThread());
ScopeLock lock(BinaryModule::Locker);
#if USE_CSHARP
// Load C# core assembly

View File

@@ -268,6 +268,7 @@ namespace FlaxEngine
Foreground = Color.FromBgra(0xFFFFFFFF),
ForegroundGrey = Color.FromBgra(0xFFA9A9B3),
ForegroundDisabled = Color.FromBgra(0xFF787883),
ForegroundViewport = Color.FromBgra(0xFFFFFFFF),
BackgroundHighlighted = Color.FromBgra(0xFF54545C),
BorderHighlighted = Color.FromBgra(0xFF6A6A75),
BackgroundSelected = Color.FromBgra(0xFF007ACC),

View File

@@ -0,0 +1,254 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Serialization;
using FlaxEngine.Interop;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
#nullable enable
namespace FlaxEngine.Json.JsonCustomSerializers
{
internal class ExtendedSerializationBinder : SerializationBinder, ISerializationBinder
{
private record struct TypeKey(string? assemblyName, string typeName);
private ConcurrentDictionary<TypeKey, Type> _typeCache;
private Func<TypeKey, Type> _resolveType;
/// <summary>Clear the cache</summary>
/// <remarks>Should be cleared on scripting domain reload to avoid out of date types participating in dynamic type resolution</remarks>
public void ResetCache()
{
_typeCache.Clear();
}
public override Type BindToType(string? assemblyName, string typeName)
{
return FindCachedType(new(assemblyName, typeName));
}
public override void BindToName(Type serializedType, out string? assemblyName, out string? typeName)
{
assemblyName = serializedType.Assembly.FullName;
typeName = serializedType.FullName;
}
public ExtendedSerializationBinder()
{
_resolveType = ResolveType;
_typeCache = new();
}
Type ResolveType(TypeKey key)
{
Type? type = null;
if (key.assemblyName is null)
{
// No assembly name, attempt to find globally
type = FindTypeGlobal(key.typeName);
}
if (type is null && key.assemblyName is not null)
{
// Type not found yet, but we have assembly name
var assembly = ResolveAssembly(new(key.assemblyName));
// We have assembly, attempt to load from assembly
type = FindTypeInAssembly(key.typeName, assembly);
}
//if (type is null)
// type = _fallBack.BindToType(key.assemblyName, key.typeName); // Use fallback
if (type is null)
throw MakeTypeResolutionException(key.assemblyName, key.typeName);
return type;
}
Assembly ResolveAssembly(AssemblyName name)
{
Assembly? assembly = null;
assembly = FindScriptingAssembly(name); // Attempt to find in scripting assemblies
if (assembly is null)
assembly = FindLoadAssembly(name); // Attempt to load
if (assembly is null)
assembly = FindDomainAssembly(name); // Attempt to find in the current domain
if (assembly is null)
throw MakeAsmResolutionException(name.FullName); // Assembly failed to resolve
return assembly;
}
/// <summary>Attempt to find the assembly among loaded scripting assemblies</summary>
Assembly? FindScriptingAssembly(AssemblyName assemblyName)
{
return NativeInterop.ResolveScriptingAssemblyByName(assemblyName, allowPartial: true);
}
/// <summary>Attempt to find the assembly in the current domain</summary>
Assembly? FindDomainAssembly(AssemblyName assemblyName)
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies)
{
// Looking in domain may be necessary (in case of anon dynamic assembly for example)
var curName = assembly.GetName();
if (curName == assemblyName || curName.Name == assemblyName.Name)
return assembly;
}
return null;
}
/// <summary>Attempt to load the assembly</summary>
Assembly? FindLoadAssembly(AssemblyName assemblyName)
{
Assembly? assembly = null;
assembly = Assembly.Load(assemblyName);
if (assembly is null && assemblyName.Name is not null)
#pragma warning disable CS0618 // Type or member is obsolete
assembly = Assembly.LoadWithPartialName(assemblyName.Name); // Copying behavior of DefaultSerializationBinder
#pragma warning restore CS0618 // Type or member is obsolete
return assembly;
}
/// <summary>Attempt to find a type in a specified assembly</summary>
Type? FindTypeInAssembly(string typeName, Assembly assembly)
{
var type = assembly.GetType(typeName); // Attempt to load directly
if (type is null && typeName.IndexOf('`') >= 0) // Attempt failed, but name has generic variant tick, try resolving generic manually
type = FindTypeGeneric(typeName, assembly);
return type;
}
/// <summary>Attempt to find unqualified type by only name</summary>
Type? FindTypeGlobal(string typeName)
{
return Type.GetType(typeName);
}
/// <summary>Get type from the cache</summary>
private Type FindCachedType(TypeKey key)
{
return _typeCache.GetOrAdd(key, _resolveType);
}
/*********************************************
** Below code is adapted from Newtonsoft.Json
*********************************************/
/// <summary>Attempt to recursively resolve a generic type</summary>
private Type? FindTypeGeneric(string typeName, Assembly assembly)
{
Type? type = null;
int openBracketIndex = typeName.IndexOf('[', StringComparison.Ordinal);
if (openBracketIndex >= 0)
{
string genericTypeDefName = typeName.Substring(0, openBracketIndex); // Find the unspecialized type
Type? genericTypeDef = assembly.GetType(genericTypeDefName);
if (genericTypeDef != null)
{
List<Type> genericTypeArguments = new List<Type>(); // Recursively resolve the arguments
int scope = 0;
int typeArgStartIndex = 0;
int endIndex = typeName.Length - 1;
for (int i = openBracketIndex + 1; i < endIndex; ++i)
{
char current = typeName[i];
switch (current)
{
case '[':
if (scope == 0)
{
typeArgStartIndex = i + 1;
}
++scope;
break;
case ']':
--scope;
if (scope == 0)
{
// All arguments resolved, compose our type
string typeArgAssemblyQualifiedName = typeName.Substring(typeArgStartIndex, i - typeArgStartIndex);
TypeKey typeNameKey = SplitFullyQualifiedTypeName(typeArgAssemblyQualifiedName);
genericTypeArguments.Add(FindCachedType(typeNameKey));
}
break;
}
}
type = genericTypeDef.MakeGenericType(genericTypeArguments.ToArray());
}
}
return type;
}
/// <summary>Split a fully qualified type name into assembly name, and type name</summary>
private static TypeKey SplitFullyQualifiedTypeName(string fullyQualifiedTypeName)
{
int? assemblyDelimiterIndex = GetAssemblyDelimiterIndex(fullyQualifiedTypeName);
ReadOnlySpan<char> typeName, assemblyName;
if (assemblyDelimiterIndex != null)
{
typeName = fullyQualifiedTypeName.AsSpan().Slice(0, assemblyDelimiterIndex ?? 0);
assemblyName = fullyQualifiedTypeName.AsSpan().Slice((assemblyDelimiterIndex ?? 0) + 1);
}
else
{
typeName = fullyQualifiedTypeName;
assemblyName = null;
}
return new(new(assemblyName), new(typeName));
}
/// <summary>Find the assembly name inside a fully qualified type name</summary>
private static int? GetAssemblyDelimiterIndex(string fullyQualifiedTypeName)
{
// we need to get the first comma following all surrounded in brackets because of generic types
// e.g. System.Collections.Generic.Dictionary`2[[System.String, mscorlib,Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
int scope = 0;
for (int i = 0; i < fullyQualifiedTypeName.Length; i++)
{
char current = fullyQualifiedTypeName[i];
switch (current)
{
case '[':
scope++;
break;
case ']':
scope--;
break;
case ',':
if (scope == 0)
{
return i;
}
break;
}
}
return null;
}
private static JsonSerializationException MakeAsmResolutionException(string asmName)
{
return new($"Could not load assembly '{asmName}'.");
}
private static JsonSerializationException MakeTypeResolutionException(string? asmName, string typeName)
{
if (asmName is null)
return new($"Could not find '{typeName}'");
else
return new($"Could not find '{typeName}' in assembly '{asmName}'.");
}
}
}

View File

@@ -19,6 +19,7 @@ namespace FlaxEngine.Json
{
internal class SerializerCache
{
public readonly JsonSerializerSettings JsonSettings;
public Newtonsoft.Json.JsonSerializer JsonSerializer;
public StringBuilder StringBuilder;
public StringWriter StringWriter;
@@ -28,17 +29,106 @@ namespace FlaxEngine.Json
public StreamReader Reader;
public bool IsWriting;
public bool IsReading;
#if FLAX_EDITOR
public uint CacheVersion;
#endif
public unsafe SerializerCache(JsonSerializerSettings settings)
{
JsonSerializer = Newtonsoft.Json.JsonSerializer.CreateDefault(settings);
JsonSerializer.Formatting = Formatting.Indented;
JsonSerializer.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
JsonSettings = settings;
StringBuilder = new StringBuilder(256);
StringWriter = new StringWriter(StringBuilder, CultureInfo.InvariantCulture);
SerializerWriter = new JsonSerializerInternalWriter(JsonSerializer);
MemoryStream = new UnmanagedMemoryStream((byte*)0, 0);
#if FLAX_EDITOR
lock (CurrentCacheSyncRoot)
#endif
{
BuildSerializer();
BuildRead();
BuildWrite();
#if FLAX_EDITOR
CacheVersion = Json.JsonSerializer.CurrentCacheVersion;
#endif
}
}
public void ReadBegin()
{
CheckCacheVersionRebuild();
// TODO: Reset reading state (eg if previous deserialization got exception)
if (IsReading)
BuildRead();
IsWriting = false;
IsReading = true;
}
public void ReadEnd()
{
IsReading = false;
}
public void WriteBegin()
{
CheckCacheVersionRebuild();
// Reset writing state (eg if previous serialization got exception)
if (IsWriting)
BuildWrite();
StringBuilder.Clear();
IsWriting = true;
IsReading = false;
}
public void WriteEnd()
{
IsWriting = false;
}
/// <summary>Check that the cache is up to date, rebuild it if it isn't</summary>
private void CheckCacheVersionRebuild()
{
#if FLAX_EDITOR
var version = Json.JsonSerializer.CurrentCacheVersion;
if (CacheVersion == version)
return;
lock (CurrentCacheSyncRoot)
{
version = Json.JsonSerializer.CurrentCacheVersion;
if (CacheVersion == version)
return;
BuildSerializer();
BuildRead();
BuildWrite();
CacheVersion = version;
}
#endif
}
/// <summary>Builds the serializer</summary>
private void BuildSerializer()
{
JsonSerializer = Newtonsoft.Json.JsonSerializer.CreateDefault(Settings);
JsonSerializer.Formatting = Formatting.Indented;
JsonSerializer.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
}
/// <summary>Builds the reader state</summary>
private void BuildRead()
{
Reader = new StreamReader(MemoryStream, Encoding.UTF8, false);
}
/// <summary>Builds the writer state</summary>
private void BuildWrite()
{
SerializerWriter = new JsonSerializerInternalWriter(JsonSerializer);
JsonWriter = new JsonTextWriter(StringWriter)
{
IndentChar = '\t',
@@ -52,65 +142,30 @@ namespace FlaxEngine.Json
DateFormatString = JsonSerializer.DateFormatString,
};
}
public void ReadBegin()
{
if (IsReading)
{
// TODO: Reset reading state (eg if previous deserialization got exception)
}
IsWriting = false;
IsReading = true;
}
public void ReadEnd()
{
IsReading = false;
}
public void WriteBegin()
{
if (IsWriting)
{
// Reset writing state (eg if previous serialization got exception)
SerializerWriter = new JsonSerializerInternalWriter(JsonSerializer);
JsonWriter = new JsonTextWriter(StringWriter)
{
IndentChar = '\t',
Indentation = 1,
Formatting = JsonSerializer.Formatting,
DateFormatHandling = JsonSerializer.DateFormatHandling,
DateTimeZoneHandling = JsonSerializer.DateTimeZoneHandling,
FloatFormatHandling = JsonSerializer.FloatFormatHandling,
StringEscapeHandling = JsonSerializer.StringEscapeHandling,
Culture = JsonSerializer.Culture,
DateFormatString = JsonSerializer.DateFormatString,
};
}
StringBuilder.Clear();
IsWriting = true;
IsReading = false;
}
public void WriteEnd()
{
IsWriting = false;
}
}
internal static JsonSerializerSettings Settings = CreateDefaultSettings(false);
internal static JsonSerializerSettings SettingsManagedOnly = CreateDefaultSettings(true);
internal static ExtendedSerializationBinder SerializationBinder;
internal static FlaxObjectConverter ObjectConverter;
internal static ThreadLocal<SerializerCache> Current = new ThreadLocal<SerializerCache>();
internal static ThreadLocal<SerializerCache> Cache = new ThreadLocal<SerializerCache>(() => new SerializerCache(Settings));
internal static ThreadLocal<SerializerCache> CacheManagedOnly = new ThreadLocal<SerializerCache>(() => new SerializerCache(SettingsManagedOnly));
internal static ThreadLocal<IntPtr> CachedGuidBuffer = new ThreadLocal<IntPtr>(() => Marshal.AllocHGlobal(32 * sizeof(char)), true);
internal static string CachedGuidDigits = "0123456789abcdef";
#if FLAX_EDITOR
/// <summary>The version of the cache, used to check that a cache is not out of date</summary>
internal static uint CurrentCacheVersion = 0;
/// <summary>Used to synchronize cache operations such as <see cref="SerializerCache"/> rebuild, and <see cref="ResetCache"/></summary>
internal static readonly object CurrentCacheSyncRoot = new();
#endif
internal static JsonSerializerSettings CreateDefaultSettings(bool isManagedOnly)
{
//Newtonsoft.Json.Utilities.MiscellaneousUtils.ValueEquals = ValueEquals;
if (SerializationBinder is null)
SerializationBinder = new();
var settings = new JsonSerializerSettings
{
ContractResolver = new ExtendedDefaultContractResolver(isManagedOnly),
@@ -118,6 +173,7 @@ namespace FlaxEngine.Json
TypeNameHandling = TypeNameHandling.Auto,
NullValueHandling = NullValueHandling.Include,
ObjectCreationHandling = ObjectCreationHandling.Auto,
SerializationBinder = SerializationBinder,
};
if (ObjectConverter == null)
ObjectConverter = new FlaxObjectConverter();
@@ -134,6 +190,23 @@ namespace FlaxEngine.Json
return settings;
}
#if FLAX_EDITOR
/// <summary>Resets the serialization cache.</summary>
internal static void ResetCache()
{
lock (CurrentCacheSyncRoot)
{
unchecked
{
CurrentCacheVersion++;
}
Newtonsoft.Json.JsonSerializer.ClearCache();
SerializationBinder.ResetCache();
}
}
#endif
internal static void Dispose()
{
CachedGuidBuffer.Values.ForEach(Marshal.FreeHGlobal);

View File

@@ -3,6 +3,7 @@
#pragma once
#include "SerializationFwd.h"
#include "ISerializeModifier.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Scripting/ScriptingObject.h"

View File

@@ -3,10 +3,11 @@
#pragma once
#include "Engine/Core/ISerializable.h"
#include "ISerializeModifier.h"
#include "Json.h"
#include "JsonWriter.h"
class ISerializeModifier;
// The floating-point values serialization epsilon for equality checks precision
#define SERIALIZE_EPSILON 1e-7f
#define SERIALIZE_EPSILON_DOUBLE 1e-17

View File

@@ -36,7 +36,7 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(TextureGroup);
int32 MaxAnisotropy = 16;
/// <summary>
/// The quality scale factor applied to textures in this group. Can be used to increase or decrease textures resolution. In range 0-1 where 0 means lowest quality, 1 means full quality.
/// The quality scale factor applied to textures in this group. Can be used to increase or decrease textures resolution. In the range 0-1 where 0 means lowest quality, 1 means full quality.
/// </summary>
API_FIELD(Attributes="EditorOrder(20), Limit(0, 1)")
float Quality = 1.0f;
@@ -60,20 +60,20 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(TextureGroup);
int32 MipLevelsMin = 0;
/// <summary>
/// The maximum amount of loaded mip levels for textures in this group. Defines the maximum amount of the mips that can be loaded. Overriden per-platform. Lower values reduce textures quality and improve performance.
/// The maximum amount of loaded mip levels for textures in this group. Defines the maximum amount of mips that can be loaded. Overriden per-platform. Lower values reduce texture quality and improve performance.
/// </summary>
API_FIELD(Attributes="EditorOrder(40), Limit(1, 14)")
int32 MipLevelsMax = 14;
/// <summary>
/// The loaded mip levels bias for textures in this group. Can be used to increase or decrease quality of the streaming for textures in this group (eg. bump up the quality during cinematic sequence).
/// The loaded mip levels bias for textures in this group. Can be used to increase or decrease the quality of streaming for textures in this group (eg. bump up the quality during cinematic sequence).
/// </summary>
API_FIELD(Attributes="EditorOrder(50), Limit(-14, 14)")
int32 MipLevelsBias = 0;
#if USE_EDITOR
/// <summary>
/// The per-platform maximum amount of mip levels for textures in this group. Can be used to strip textures quality when cooking game for a target platform.
/// The per-platform maximum amount of mip levels for textures in this group. Can be used to strip textures quality when cooking the game for a target platform.
/// </summary>
API_FIELD(Attributes="EditorOrder(50)")
Dictionary<PlatformType, int32> MipLevelsMaxPerPlatform;

View File

@@ -519,6 +519,40 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
}
break;
}
// Rectangle Mask
case 40:
{
const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
const auto rectangle = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat2();
auto d = writeLocal(ValueType::Float2, String::Format(TEXT("abs({0} * 2 - 1) - {1}"), uv.Value, rectangle.Value), node);
auto d2 = writeLocal(ValueType::Float2, String::Format(TEXT("1 - {0} / fwidth({0})"), d.Value), node);
value = writeLocal(ValueType::Float, String::Format(TEXT("saturate(min({0}.x, {0}.y))"), d2.Value), node);
break;
}
// FWidth
case 41:
{
const auto inValue = tryGetValue(node->GetBox(0), 0, Value::Zero);
value = writeLocal(inValue.Type, String::Format(TEXT("fwidth({0})"), inValue.Value), node);
break;
}
// AA Step
case 42:
{
// Reference: https://www.ronja-tutorials.com/post/046-fwidth/#a-better-step
const auto compValue = tryGetValue(node->GetBox(0), getUVs).AsFloat();
const auto gradient = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat();
auto change = writeLocal(ValueType::Float, String::Format(TEXT("fwidth({0})"), gradient.Value), node);
// Base the range of the inverse lerp on the change over two pixels
auto lowerEdge = writeLocal(ValueType::Float, String::Format(TEXT("{0} - {1}"), compValue.Value, change.Value), node);
auto upperEdge = writeLocal(ValueType::Float, String::Format(TEXT("{0} + {1}"), compValue.Value, change.Value), node);
// Do the inverse interpolation and saturate it
value = writeLocal(ValueType::Float, String::Format(TEXT("saturate((({0} - {1}) / ({2} - {1})))"), gradient.Value, lowerEdge.Value, upperEdge.Value), node);
}
default:
break;
}

View File

@@ -228,25 +228,25 @@ public:
public: // Geometry
// Enable model normal vectors recalculating.
// Enable model normal vectors re-calculating.
API_FIELD(Attributes="EditorOrder(20), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))")
bool CalculateNormals = false;
// Specifies the maximum angle (in degrees) that may be between two face normals at the same vertex position that their are smoothed together. The default value is 175.
// Specifies the maximum angle (in degrees) that may be between two face normals at the same vertex position before they are smoothed together. The default value is 175.
API_FIELD(Attributes="EditorOrder(30), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowSmoothingNormalsAngle)), Limit(0, 175, 0.1f)")
float SmoothingNormalsAngle = 175.0f;
// If checked, the imported normal vectors of the mesh will be flipped (scaled by -1).
API_FIELD(Attributes="EditorOrder(35), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))")
bool FlipNormals = false;
// Enable model tangent vectors recalculating.
// Enable model tangent vectors re-calculating.
API_FIELD(Attributes="EditorOrder(40), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))")
bool CalculateTangents = false;
// Specifies the maximum angle (in degrees) that may be between two vertex tangents that their tangents and bi-tangents are smoothed. The default value is 45.
// Specifies the maximum angle (in degrees) that may be between two vertex tangents before their tangents and bi-tangents are smoothed. The default value is 45.
API_FIELD(Attributes="EditorOrder(45), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowSmoothingTangentsAngle)), Limit(0, 45, 0.1f)")
float SmoothingTangentsAngle = 45.0f;
// Enable/disable meshes geometry optimization.
API_FIELD(Attributes="EditorOrder(50), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))")
bool OptimizeMeshes = true;
// Enable/disable geometry merge for meshes with the same materials.
// Enable/disable geometry merge for meshes with the same materials. Index buffer will be reordered to improve performance and other modifications will be applied. However, importing time will be increased.
API_FIELD(Attributes="EditorOrder(60), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))")
bool MergeMeshes = true;
// Enable/disable importing meshes Level of Details.
@@ -258,16 +258,16 @@ public:
// Enable/disable importing blend shapes (morph targets).
API_FIELD(Attributes="EditorOrder(85), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowSkinnedModel))")
bool ImportBlendShapes = false;
// Enable skeleton bones offset matrices recalculating.
// Enable skeleton bones offset matrices re-calculating.
API_FIELD(Attributes="EditorOrder(86), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowSkinnedModel))")
bool CalculateBoneOffsetMatrices = false;
// The lightmap UVs source.
API_FIELD(Attributes="EditorOrder(90), EditorDisplay(\"Geometry\", \"Lightmap UVs Source\"), VisibleIf(nameof(ShowModel))")
ModelLightmapUVsSource LightmapUVsSource = ModelLightmapUVsSource::Disable;
// If specified, all meshes which name starts with this prefix will be imported as a separate collision data (excluded used for rendering).
// If specified, all meshes that name starts with this prefix in the name will be imported as a separate collision data asset (excluded used for rendering).
API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))")
String CollisionMeshesPrefix = TEXT("");
// The type of collision that should be generated if has collision prefix specified.
// The type of collision that should be generated if the mesh has a collision prefix specified.
API_FIELD(Attributes = "EditorOrder(105), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))")
CollisionDataType CollisionType = CollisionDataType::TriangleMesh;
@@ -291,19 +291,19 @@ public:
public: // Animation
// Imported animation duration mode. Can use the original value or overriden by settings.
// Imported animation duration mode. Can use the original value or be overriden by settings.
API_FIELD(Attributes="EditorOrder(1000), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowAnimation))")
AnimationDuration Duration = AnimationDuration::Imported;
// Imported animation first/last frame index. Used only if Duration mode is set to Custom.
API_FIELD(Attributes="EditorOrder(1010), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowFramesRange)), Limit(0)")
Float2 FramesRange = Float2::Zero;
// The imported animation default frame rate. Can specify the default frames per second amount for imported animation. If value is 0 then the original animation frame rate will be used.
// The imported animation default frame rate. Can specify the default frames per second amount for imported animations. If the value is 0 then the original animation frame rate will be used.
API_FIELD(Attributes="EditorOrder(1020), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowAnimation)), Limit(0, 1000, 0.01f)")
float DefaultFrameRate = 0.0f;
// The imported animation sampling rate. If value is 0 then the original animation speed will be used.
API_FIELD(Attributes="EditorOrder(1030), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowAnimation)), Limit(0, 1000, 0.01f)")
float SamplingRate = 0.0f;
// The imported animation will have removed tracks with no keyframes or unspecified data.
// The imported animation will have tracks with no keyframes or unspecified data removed.
API_FIELD(Attributes="EditorOrder(1040), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowAnimation))")
bool SkipEmptyCurves = true;
// The imported animation channels will be optimized to remove redundant keyframes.
@@ -348,7 +348,7 @@ public:
// If checked, the importer will create the model's materials as instances of a base material.
API_FIELD(Attributes = "EditorOrder(401), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterials))")
bool ImportMaterialsAsInstances = false;
// The material to import the model's materials as an instance of.
// The material used as the base material that will be instanced as the imported model's material.
API_FIELD(Attributes = "EditorOrder(402), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterialsAsInstances))")
AssetReference<MaterialBase> InstanceToImportAs;
// If checked, the importer will import texture files used by the model and any embedded texture resources.
@@ -369,16 +369,16 @@ public:
public: // Splitting
// If checked, the imported mesh/animations are splitted into separate assets. Used if ObjectIndex is set to -1.
// If checked, the imported mesh/animations are split into separate assets. Used if ObjectIndex is set to -1.
API_FIELD(Attributes="EditorOrder(2000), EditorDisplay(\"Splitting\")")
bool SplitObjects = false;
// The zero-based index for the mesh/animation clip to import. If the source file has more than one mesh/animation it can be used to pick a desire object. Default -1 imports all objects.
// The zero-based index for the mesh/animation clip to import. If the source file has more than one mesh/animation it can be used to pick a desired object. Default -1 imports all objects.
API_FIELD(Attributes="EditorOrder(2010), EditorDisplay(\"Splitting\")")
int32 ObjectIndex = -1;
public: // Other
// If specified, will be used as sub-directory name for automatically imported sub assets such as textures and materials. Set to whitespace (single space) to import to the same directory.
// If specified, the specified folder will be used as sub-directory name for automatically imported sub assets such as textures and materials. Set to whitespace (single space) to import to the same directory.
API_FIELD(Attributes="EditorOrder(3030), EditorDisplay(\"Other\")")
String SubAssetFolder = TEXT("");

View File

@@ -57,11 +57,11 @@ API_CLASS(Namespace="FlaxEngine.Tools", Static) class FLAXENGINE_API TextureTool
API_FIELD(Attributes="EditorOrder(70)")
bool FlipY = false;
// Texture size scale. Default is 1.
// Texture size scale. Allows increasing or decreasing the imported texture resolution. Default is 1.
API_FIELD(Attributes="EditorOrder(80), Limit(0.0001f, 1000.0f, 0.01f)")
float Scale = 1.0f;
// Maximum size of the texture (for both width and height). Higher resolution textures will be resized during importing process.
// Maximum size of the texture (for both width and height). Higher resolution textures will be resized during importing process. Used to clip textures that are too big.
API_FIELD(Attributes="HideInEditor")
int32 MaxSize = 8192;
@@ -69,11 +69,11 @@ API_CLASS(Namespace="FlaxEngine.Tools", Static) class FLAXENGINE_API TextureTool
API_FIELD(Attributes="EditorOrder(100)")
bool Resize = false;
// The width of the imported texture. If Resize property is set to true then texture will be resized during the import to this value. Otherwise it will be ignored.
// The width of the imported texture. If Resize property is set to true then texture will be resized during the import to this value during the import, otherwise it will be ignored.
API_FIELD(Attributes="HideInEditor")
int32 SizeX = 1024;
// The height of the imported texture. If Resize property is set to true then texture will be resized during the import to this value. Otherwise it will be ignored.
// The height of the imported texture. If Resize property is set to true then texture will be resized during the import to this value during the import, otherwise it will be ignored.
API_FIELD(Attributes="HideInEditor")
int32 SizeY = 1024;
@@ -85,7 +85,7 @@ API_CLASS(Namespace="FlaxEngine.Tools", Static) class FLAXENGINE_API TextureTool
API_FIELD(Attributes="EditorOrder(210), VisibleIf(\"PreserveAlphaCoverage\")")
float PreserveAlphaCoverageReference = 0.5f;
// Texture group for streaming (negative if unused). See Streaming Settings.
// The texture group for streaming (negative if unused). See Streaming Settings.
API_FIELD(Attributes="EditorOrder(300), CustomEditorAlias(\"FlaxEditor.CustomEditors.Dedicated.TextureGroupEditor\")")
int32 TextureGroup = -1;

View File

@@ -198,8 +198,12 @@ namespace FlaxEngine.GUI
/// <inheritdoc />
public override void Update(float deltaTime)
{
// UI navigation
// Update navigation
if (SkipEvents)
{
_navigationHeldTimeUp = _navigationHeldTimeDown = _navigationHeldTimeLeft = _navigationHeldTimeRight = 0;
_navigationRateTimeUp = _navigationRateTimeDown = _navigationRateTimeLeft = _navigationRateTimeRight = 0;
}
{
UpdateNavigation(deltaTime, _canvas.NavigateUp.Name, NavDirection.Up, ref _navigationHeldTimeUp, ref _navigationRateTimeUp);
UpdateNavigation(deltaTime, _canvas.NavigateDown.Name, NavDirection.Down, ref _navigationHeldTimeDown, ref _navigationRateTimeDown);
@@ -207,11 +211,6 @@ namespace FlaxEngine.GUI
UpdateNavigation(deltaTime, _canvas.NavigateRight.Name, NavDirection.Right, ref _navigationHeldTimeRight, ref _navigationRateTimeRight);
UpdateNavigation(deltaTime, _canvas.NavigateSubmit.Name, ref _navigationHeldTimeSubmit, ref _navigationRateTimeSubmit, SubmitFocused);
}
else
{
_navigationHeldTimeUp = _navigationHeldTimeDown = _navigationHeldTimeLeft = _navigationHeldTimeRight = 0;
_navigationRateTimeUp = _navigationRateTimeDown = _navigationRateTimeLeft = _navigationRateTimeRight = 0;
}
base.Update(deltaTime);
}

View File

@@ -22,12 +22,28 @@ namespace FlaxEngine.GUI
/// Occurs when popup lost focus.
/// </summary>
public Action LostFocus;
/// <summary>
/// The selected control. Used to scroll to the control on popup creation.
/// </summary>
public ContainerControl SelectedControl = null;
/// <summary>
/// The main panel used to hold the items.
/// </summary>
public Panel MainPanel = null;
/// <inheritdoc />
public override void OnEndContainsFocus()
{
base.OnEndContainsFocus();
// Dont lose focus when using panel. Does prevent LostFocus even from being called if clicking inside of the panel.
if (MainPanel != null && MainPanel.IsMouseOver && !MainPanel.ContainsFocus)
{
MainPanel.Focus();
return;
}
// Call event after this 'focus contains flag' propagation ends to prevent focus issues
if (LostFocus != null)
Scripting.RunOnUpdate(LostFocus);
@@ -125,6 +141,8 @@ namespace FlaxEngine.GUI
public override void OnDestroy()
{
LostFocus = null;
MainPanel = null;
SelectedControl = null;
base.OnDestroy();
}
@@ -233,6 +251,18 @@ namespace FlaxEngine.GUI
}
}
/// <summary>
/// Gets or sets whether to show all of the items.
/// </summary>
[EditorOrder(3), Tooltip("Whether to show all of the items in the drop down.")]
public bool ShowAllItems { get; set; } = true;
/// <summary>
/// Gets or sets the maximum number of items to show at once. Only used if ShowAllItems is false.
/// </summary>
[EditorOrder(4), VisibleIf(nameof(ShowAllItems), true), Limit(1), Tooltip("The number of items to show in the drop down.")]
public int ShowMaxItemsCount { get; set; } = 5;
/// <summary>
/// Event fired when selected index gets changed.
/// </summary>
@@ -411,13 +441,24 @@ namespace FlaxEngine.GUI
// TODO: support item templates
var container = new VerticalPanel
var panel = new Panel
{
AnchorPreset = AnchorPresets.StretchAll,
BackgroundColor = BackgroundColor,
AutoSize = false,
ScrollBars = ScrollBars.Vertical,
AutoFocus = true,
Parent = popup,
};
popup.MainPanel = panel;
var container = new VerticalPanel
{
AnchorPreset = AnchorPresets.StretchAll,
BackgroundColor = Color.Transparent,
IsScrollable = true,
AutoSize = true,
Parent = panel,
};
var border = new Border
{
BorderColor = BorderColorHighlighted,
@@ -482,10 +523,20 @@ namespace FlaxEngine.GUI
//AnchorPreset = AnchorPresets.VerticalStretchLeft,
Parent = item,
};
popup.SelectedControl = item;
}
}
popup.Size = new Float2(itemsWidth, height);
if (ShowAllItems || _items.Count < ShowMaxItemsCount)
{
popup.Size = new Float2(itemsWidth, height);
panel.Size = popup.Size;
}
else
{
popup.Size = new Float2(itemsWidth, (itemsHeight + container.Spacing) * ShowMaxItemsCount);
panel.Size = popup.Size;
}
return popup;
}
@@ -527,7 +578,16 @@ namespace FlaxEngine.GUI
/// </summary>
public void ShowPopup()
{
var root = Root;
// Find canvas scalar and set as root if it exists.
ContainerControl c = Parent;
while(c.Parent != Root && c.Parent != null)
{
c = c.Parent;
if (c is CanvasScaler scalar)
break;
}
var root = c is CanvasScaler ? c : Root;
if (_items.Count == 0 || root == null)
return;
@@ -542,7 +602,7 @@ namespace FlaxEngine.GUI
// Show dropdown popup
var locationRootSpace = Location + new Float2(0, Height);
var parent = Parent;
while (parent != null && parent != Root)
while (parent != null && parent != root)
{
locationRootSpace = parent.PointToParent(ref locationRootSpace);
parent = parent.Parent;
@@ -551,6 +611,8 @@ namespace FlaxEngine.GUI
_popup.Parent = root;
_popup.Focus();
_popup.StartMouseCapture();
if (_popup.SelectedControl != null && _popup.MainPanel != null)
_popup.MainPanel.ScrollViewTo(_popup.SelectedControl, true);
OnPopupShow();
}

View File

@@ -104,6 +104,12 @@ namespace FlaxEngine.GUI
[EditorOrder(110)]
public Color ForegroundDisabled;
/// <summary>
/// The foreground color in viewports (usually have a dark background)
/// </summary>
[EditorOrder(115)]
public Color ForegroundViewport;
/// <summary>
/// The background highlighted color.
/// </summary>

View File

@@ -8,7 +8,6 @@ using System.IO;
using FlaxEditor.Scripting;
using FlaxEditor.Utilities;
#endif
using FlaxEngine;
using Newtonsoft.Json;
namespace FlaxEngine.Utilities
@@ -18,6 +17,78 @@ namespace FlaxEngine.Utilities
/// </summary>
public static class VariantUtils
{
/// <summary>
/// Casts the generic value to a given type. Matches native Variant casting logic that favors returning null/zero value rather than error.
/// </summary>
/// <typeparam name="T">The destination value type.</typeparam>
/// <param name="value">The input value to cast.</param>
/// <returns>The result value.</returns>
internal static T Cast<T>(object value)
{
if (value == null)
return default;
var type = value.GetType();
if (type != typeof(T))
{
if (typeof(T) == typeof(Vector2))
{
if (value is Float2 asFloat2)
return (T)(object)new Vector2(asFloat2.X, asFloat2.Y);
if (value is Float3 asFloat3)
return (T)(object)new Vector2(asFloat3.X, asFloat3.Y);
if (value is Float4 asFloat4)
return (T)(object)new Vector2(asFloat4.X, asFloat4.Y);
}
else if (typeof(T) == typeof(Vector3))
{
if (value is Float2 asFloat2)
return (T)(object)new Vector3(asFloat2.X, asFloat2.Y, 0);
if (value is Float3 asFloat3)
return (T)(object)new Vector3(asFloat3.X, asFloat3.Y, asFloat3.Z);
if (value is Float4 asFloat4)
return (T)(object)new Vector3(asFloat4.X, asFloat4.Y, asFloat4.Z);
}
else if (typeof(T) == typeof(Vector4))
{
if (value is Float2 asFloat2)
return (T)(object)new Vector4(asFloat2.X, asFloat2.Y, 0, 0);
if (value is Float3 asFloat3)
return (T)(object)new Vector4(asFloat3.X, asFloat3.Y, asFloat3.Z, 0);
if (value is Vector4 asFloat4)
return (T)(object)new Vector4(asFloat4.X, asFloat4.Y, asFloat4.Z, asFloat4.W);
}
else if (typeof(T) == typeof(Float2))
{
if (value is Vector2 asVector2)
return (T)(object)new Float2(asVector2.X, asVector2.Y);
if (value is Vector3 asVector3)
return (T)(object)new Float2(asVector3.X, asVector3.Y);
if (value is Vector4 asVector4)
return (T)(object)new Float2(asVector4.X, asVector4.Y);
}
else if (typeof(T) == typeof(Float3))
{
if (value is Vector2 asVector2)
return (T)(object)new Float3(asVector2.X, asVector2.Y, 0);
if (value is Vector3 asVector3)
return (T)(object)new Float3(asVector3.X, asVector3.Y, asVector3.Z);
if (value is Vector4 asFloat4)
return (T)(object)new Float3(asFloat4.X, asFloat4.Y, asFloat4.Z);
}
else if (typeof(T) == typeof(Float4))
{
if (value is Vector2 asVector2)
return (T)(object)new Float4(asVector2.X, asVector2.Y, 0, 0);
if (value is Vector3 asVector3)
return (T)(object)new Float4(asVector3.X, asVector3.Y, asVector3.Z, 0);
if (value is Vector4 asVector4)
return (T)(object)new Float4(asVector4.X, asVector4.Y, asVector4.Z, asVector4.W);
}
return (T)Convert.ChangeType(value, typeof(T));
}
return (T)value;
}
internal enum VariantType
{
Null = 0,

View File

@@ -685,7 +685,7 @@ void VisjectExecutor::ProcessGroupPacking(Box* box, Node* node, Value& value)
case 36:
{
// Get value with structure data
const Variant structureValue = eatBox(node, node->GetBox(0)->FirstConnection());
Variant structureValue = eatBox(node, node->GetBox(0)->FirstConnection());
if (!node->GetBox(0)->HasConnection())
return;
@@ -741,7 +741,9 @@ void VisjectExecutor::ProcessGroupPacking(Box* box, Node* node, Value& value)
return;
}
const ScriptingType& type = typeHandle.GetType();
if (structureValue.Type.Type != VariantType::Structure || StringUtils::Compare(typeNameAnsi.Get(), structureValue.Type.TypeName) != 0)
structureValue.InvertInline(); // Extract any Float3/Int32 into Structure type from inlined format
const ScriptingTypeHandle structureValueTypeHandle = Scripting::FindScriptingType(structureValue.Type.GetTypeName());
if (structureValue.Type.Type != VariantType::Structure || typeHandle != structureValueTypeHandle)
{
OnError(node, box, String::Format(TEXT("Cannot unpack value of type {0} to structure of type {1}"), structureValue.Type, typeName));
return;

View File

@@ -1793,6 +1793,18 @@ namespace Flax.Build.Bindings
var apiTypeInfo = FindApiTypeInfo(buildData, typeInfo, caller);
if (apiTypeInfo != null && apiTypeInfo.IsInterface)
return true;
// Add includes to properly compile bindings (eg. SoftObjectReference<class Texture>)
CppReferencesFiles.Add(apiTypeInfo?.File);
if (typeInfo.GenericArgs != null)
{
for (int i = 0; i < typeInfo.GenericArgs.Count; i++)
{
var t = FindApiTypeInfo(buildData, typeInfo.GenericArgs[i], caller);
CppReferencesFiles.Add(t?.File);
}
}
return false;
}