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; Folder.ParentFolder = parent.Folder;
Parent = parent; Parent = parent;
} }
IconColor = Style.Current.Foreground;
} }
/// <summary> /// <summary>

View File

@@ -157,7 +157,7 @@ namespace FlaxEditor.CustomEditors
var values = _values; var values = _values;
var presenter = _presenter; var presenter = _presenter;
var layout = _layout; 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 // There are more editors using the same layout so rebuild parent editor to prevent removing others editors
_parent?.RebuildLayout(); _parent?.RebuildLayout();

View File

@@ -695,7 +695,41 @@ namespace FlaxEditor.CustomEditors.Dedicated
private void SetType(ref ScriptType controlType, UIControl uiControl) private void SetType(ref ScriptType controlType, UIControl uiControl)
{ {
string previousName = uiControl.Control?.GetType().Name ?? nameof(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)) if (uiControl.Name.StartsWith(previousName))
{ {
string newName = controlType.Name + uiControl.Name.Substring(previousName.Length); 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) public override void Initialize(LayoutElementsContainer layout)
{ {
// No support for different collections for now // No support for different collections for now
if (HasDifferentValues || HasDifferentTypes) if (HasDifferentTypes)
return; return;
var size = Count; var size = Count;

View File

@@ -28,14 +28,16 @@ namespace FlaxEditor.CustomEditors.Editors
var group = layout.Group("Entry"); var group = layout.Group("Entry");
_group = group; _group = group;
if (ParentEditor == null) if (ParentEditor == null || HasDifferentTypes)
return; return;
var entry = (ModelInstanceEntry)Values[0]; var entry = (ModelInstanceEntry)Values[0];
var entryIndex = ParentEditor.ChildrenEditors.IndexOf(this); var entryIndex = ParentEditor.ChildrenEditors.IndexOf(this);
var materialLabel = new PropertyNameLabel("Material"); var materialLabel = new PropertyNameLabel("Material");
materialLabel.TooltipText = "The mesh surface material used for the rendering."; 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; _entryIndex = entryIndex;
_modelInstance = modelInstance; _modelInstance = modelInstance;
var slots = modelInstance.MaterialSlots; var slots = modelInstance.MaterialSlots;
@@ -56,6 +58,8 @@ namespace FlaxEditor.CustomEditors.Editors
// Create material picker // Create material picker
var materialValue = new CustomValueContainer(new ScriptType(typeof(MaterialBase)), _material, (instance, index) => _material, (instance, index, value) => _material = value as MaterialBase); 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); var materialEditor = (AssetRefEditor)_group.Property(materialLabel, materialValue);
materialEditor.Values.SetDefaultValue(defaultValue); materialEditor.Values.SetDefaultValue(defaultValue);
materialEditor.RefreshDefaultValue(); materialEditor.RefreshDefaultValue();

View File

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

View File

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

View File

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

View File

@@ -98,7 +98,7 @@ namespace FlaxEditor.GUI
rect.Width -= leftDepthMargin; rect.Width -= leftDepthMargin;
Render2D.PushClip(rect); 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(); Render2D.PopClip();
x += width; x += width;

View File

@@ -239,7 +239,7 @@ namespace FlaxEditor.GUI.Tabs
/// </summary> /// </summary>
public Tab SelectedTab 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; set => SelectedTabIndex = value != null ? Children.IndexOf(value) - 1 : -1;
} }

View File

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

View File

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

View File

@@ -124,6 +124,7 @@ namespace FlaxEditor.Modules
if (!Editor.StateMachine.CurrentState.CanEditScene) if (!Editor.StateMachine.CurrentState.CanEditScene)
return; return;
undo = Editor.Undo; undo = Editor.Undo;
Editor.Scene.MarkSceneEdited(actor.Scene);
} }
// Record undo for prefab creating (backend links the target instance with the prefab) // 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 // If a non-default style was chosen, switch to that style
string styleName = themeOptions.SelectedStyle; 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; Style.Current = style;
} }
else 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> /// <returns>The style object.</returns>
public Style CreateDefaultStyle() public Style CreateDefaultStyle()
{ {
// Metro Style colors
var options = Options; var options = Options;
var style = new Style var style = new Style
{ {
@@ -233,6 +239,7 @@ namespace FlaxEditor.Options
Foreground = Color.FromBgra(0xFFFFFFFF), Foreground = Color.FromBgra(0xFFFFFFFF),
ForegroundGrey = Color.FromBgra(0xFFA9A9B3), ForegroundGrey = Color.FromBgra(0xFFA9A9B3),
ForegroundDisabled = Color.FromBgra(0xFF787883), ForegroundDisabled = Color.FromBgra(0xFF787883),
ForegroundViewport = Color.FromBgra(0xFFFFFFFF),
BackgroundHighlighted = Color.FromBgra(0xFF54545C), BackgroundHighlighted = Color.FromBgra(0xFF54545C),
BorderHighlighted = Color.FromBgra(0xFF6A6A75), BorderHighlighted = Color.FromBgra(0xFF6A6A75),
BackgroundSelected = Color.FromBgra(0xFF007ACC), BackgroundSelected = Color.FromBgra(0xFF007ACC),
@@ -274,7 +281,58 @@ namespace FlaxEditor.Options
SharedTooltip = new Tooltip(), SharedTooltip = new Tooltip(),
}; };
style.DragWindow = style.BackgroundSelected * 0.7f; 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; return style;
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -882,6 +882,60 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Output(1, "Inv Size", typeof(Float2), 1), 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> /// <summary>
/// Brush radius (world-space). /// Brush radius (world-space).
/// </summary> /// </summary>
[Limit(0)]
public float BrushSize = 50.0f; public float BrushSize = 50.0f;
/// <summary> /// <summary>
/// Brush paint intensity. /// Brush paint intensity.
/// </summary> /// </summary>
[Limit(0)]
public float BrushStrength = 2.0f; public float BrushStrength = 2.0f;
/// <summary> /// <summary>
/// Brush paint falloff. Hardens or softens painting. /// Brush paint falloff. Hardens or softens painting.
/// </summary> /// </summary>
[Limit(0)]
public float BrushFalloff = 1.5f; public float BrushFalloff = 1.5f;
/// <summary> /// <summary>
/// Value to paint with. Hold Ctrl hey to paint with inverse value (1 - value). /// Value to paint with. Hold Ctrl hey to paint with inverse value (1 - value).
/// </summary> /// </summary>
[Limit(0, 1, 0.01f)]
public float PaintValue = 0.0f; public float PaintValue = 0.0f;
/// <summary> /// <summary>

View File

@@ -7,6 +7,8 @@ using FlaxEngine.GUI;
using FlaxEditor.Viewport.Widgets; using FlaxEditor.Viewport.Widgets;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using Object = FlaxEngine.Object; using Object = FlaxEngine.Object;
using FlaxEditor.GUI;
using FlaxEditor.Scripting;
namespace FlaxEditor.Viewport.Previews namespace FlaxEditor.Viewport.Previews
{ {
@@ -49,6 +51,8 @@ namespace FlaxEditor.Viewport.Previews
private Image _guiMaterialControl; private Image _guiMaterialControl;
private readonly MaterialBase[] _postFxMaterialsCache = new MaterialBase[1]; private readonly MaterialBase[] _postFxMaterialsCache = new MaterialBase[1];
private ContextMenu _modelWidgetButtonMenu; private ContextMenu _modelWidgetButtonMenu;
private AssetPicker _customModelPicker;
private Model _customModel;
/// <summary> /// <summary>
/// Gets or sets the material asset to preview. It can be <see cref="FlaxEngine.Material"/> or <see cref="FlaxEngine.MaterialInstance"/>. /// 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; get => _selectedModelIndex;
set set
{ {
if (value == -1) // Using Custom Model
return;
if (value < 0 || value > Models.Length) if (value < 0 || value > Models.Length)
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
if (_customModelPicker != null)
_customModelPicker.Validator.SelectedAsset = null;
_selectedModelIndex = value; _selectedModelIndex = value;
_previewModel.Model = FlaxEngine.Content.LoadAsyncInternal<Model>("Editor/Primitives/" + Models[value]); _previewModel.Model = FlaxEngine.Content.LoadAsyncInternal<Model>("Editor/Primitives/" + Models[value]);
_previewModel.Transform = Transforms[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> /// <summary>
/// Initializes a new instance of the <see cref="MaterialPreview"/> class. /// Initializes a new instance of the <see cref="MaterialPreview"/> class.
/// </summary> /// </summary>
@@ -107,17 +162,7 @@ namespace FlaxEditor.Viewport.Previews
{ {
if (!control.Visible) if (!control.Visible)
return; return;
_modelWidgetButtonMenu.ItemsContainer.DisposeChildren(); ResetModelContextMenu();
// 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;
}
}; };
new ViewportWidgetButton("Model", SpriteHandle.Invalid, _modelWidgetButtonMenu) new ViewportWidgetButton("Model", SpriteHandle.Invalid, _modelWidgetButtonMenu)
{ {

View File

@@ -115,7 +115,7 @@ namespace FlaxEditor.Viewport.Widgets
if (Icon.IsValid) if (Icon.IsValid)
{ {
// Draw icon // Draw icon
Render2D.DrawSprite(Icon, iconRect, style.Foreground); Render2D.DrawSprite(Icon, iconRect, style.ForegroundViewport);
// Update text rectangle // Update text rectangle
textRect.Location.X += iconSize; textRect.Location.X += iconSize;
@@ -123,7 +123,7 @@ namespace FlaxEditor.Viewport.Widgets
} }
// Draw text // 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 /> /// <inheritdoc />

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -90,7 +90,7 @@ namespace FlaxEditor.Windows.Profiler
if (_nameLength < bounds.Width + 4) if (_nameLength < bounds.Width + 4)
{ {
Render2D.PushClip(bounds); 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(); Render2D.PopClip();
} }
} }
@@ -115,7 +115,7 @@ namespace FlaxEditor.Windows.Profiler
var style = Style.Current; var style = Style.Current;
var rect = new Rectangle(Float2.Zero, Size); var rect = new Rectangle(Float2.Zero, Size);
Render2D.PushClip(rect); 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(); Render2D.PopClip();
} }
} }

View File

@@ -150,7 +150,13 @@ void BehaviorKnowledge::InitMemory(BehaviorTree* tree)
RelevantNodes.Resize(tree->Graph.NodesCount, false); RelevantNodes.Resize(tree->Graph.NodesCount, false);
RelevantNodes.SetAll(false); RelevantNodes.SetAll(false);
if (!Memory && tree->Graph.NodesStatesSize) if (!Memory && tree->Graph.NodesStatesSize)
{
Memory = Allocator::Allocate(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() void BehaviorKnowledge::FreeMemory()

View File

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

View File

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

View File

@@ -85,6 +85,8 @@ BehaviorUpdateResult BehaviorTreeNode::InvokeUpdate(const BehaviorUpdateContext&
result = BehaviorUpdateResult::Failed; result = BehaviorUpdateResult::Failed;
else else
result = Update(context); 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 // Post-process result from decorators
for (BehaviorTreeDecorator* decorator : _decorators) for (BehaviorTreeDecorator* decorator : _decorators)

View File

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

View File

@@ -522,6 +522,14 @@ void Asset::InitAsVirtual()
void Asset::CancelStreaming() 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 #if USE_EDITOR

View File

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

View File

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

View File

@@ -48,6 +48,8 @@ protected:
// [ContentLoadTask] // [ContentLoadTask]
Result run() override Result run() override
{ {
if (IsCancelRequested())
return Result::Ok;
PROFILE_CPU(); PROFILE_CPU();
AssetReference<BinaryAsset> ref = _asset.Get(); AssetReference<BinaryAsset> ref = _asset.Get();
@@ -67,8 +69,6 @@ protected:
{ {
if (IsCancelRequested()) if (IsCancelRequested())
return Result::Ok; return Result::Ok;
// Load it
#if TRACY_ENABLE #if TRACY_ENABLE
ZoneScoped; ZoneScoped;
ZoneName(*name, name.Length()); 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 // In those situations all the async tasks using this storage should be cancelled externally
// Ensure that no one is using this resource // Ensure that no one is using this resource
int32 waitTime = 10; int32 waitTime = 100;
while (Platform::AtomicRead(&_chunksLock) != 0 && waitTime-- > 0) while (Platform::AtomicRead(&_chunksLock) != 0 && waitTime-- > 0)
Platform::Sleep(10); Platform::Sleep(1);
if (Platform::AtomicRead(&_chunksLock) != 0) if (Platform::AtomicRead(&_chunksLock) != 0)
{ {
// File can be locked by some streaming tasks (eg. AudioClip::StreamingTask or StreamModelLODTask) // File can be locked by some streaming tasks (eg. AudioClip::StreamingTask or StreamModelLODTask)
Entry e;
for (int32 i = 0; i < GetEntriesCount(); i++) for (int32 i = 0; i < GetEntriesCount(); i++)
{ {
Entry e;
GetEntry(i, e); GetEntry(i, e);
Asset* asset = Content::GetAsset(e.ID); Asset* asset = Content::GetAsset(e.ID);
if (asset) if (asset)
@@ -1320,8 +1320,12 @@ void FlaxStorage::CloseFileHandles()
} }
} }
} }
waitTime = 100;
while (Platform::AtomicRead(&_chunksLock) != 0 && waitTime-- > 0)
Platform::Sleep(1);
ASSERT(_chunksLock == 0); ASSERT(_chunksLock == 0);
// Close file handles (from all threads)
_file.DeleteAll(); _file.DeleteAll();
} }

View File

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

View File

@@ -2821,7 +2821,10 @@ void Variant::Inline()
type = VariantType::Types::Vector4; type = VariantType::Types::Vector4;
} }
if (type != VariantType::Null) if (type != VariantType::Null)
{
ASSERT(sizeof(data) >= AsBlob.Length);
Platform::MemoryCopy(data, AsBlob.Data, AsBlob.Length); Platform::MemoryCopy(data, AsBlob.Data, AsBlob.Length);
}
} }
if (type != VariantType::Null) 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 Variant::NewValue(const StringAnsiView& typeName)
{ {
Variant v; 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). // Inlines potential value type into in-built format (eg. Vector3 stored as Structure, or String stored as ManagedObject).
void Inline(); 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). // Allocates the Variant of the specific type (eg. structure or object or value).
static Variant NewValue(const StringAnsiView& typeName); static Variant NewValue(const StringAnsiView& typeName);

View File

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

View File

@@ -278,44 +278,70 @@ namespace FlaxEngine.Interop
if (typeCache.TryGetValue(typeName, out Type type)) if (typeCache.TryGetValue(typeName, out Type type))
return type; return type;
type = Type.GetType(typeName, ResolveAssemblyByName, null); type = Type.GetType(typeName, ResolveAssembly, null);
if (type == null) if (type == null)
{ type = ResolveSlow(typeName);
foreach (var assembly in scriptingAssemblyLoadContext.Assemblies)
{
type = assembly.GetType(typeName);
if (type != null)
break;
}
}
if (type == null) if (type == null)
{ {
string oldTypeName = typeName; string fullTypeName = typeName;
typeName = typeName.Substring(0, typeName.IndexOf(',')); typeName = typeName.Substring(0, typeName.IndexOf(','));
type = Type.GetType(typeName, ResolveAssemblyByName, null); type = Type.GetType(typeName, ResolveAssembly, null);
if (type == null) if (type == null)
{ type = ResolveSlow(typeName);
foreach (var assembly in scriptingAssemblyLoadContext.Assemblies)
{ typeName = fullTypeName;
type = assembly.GetType(typeName);
if (type != null)
break;
}
}
typeName = oldTypeName;
} }
typeCache.Add(typeName, type); typeCache.Add(typeName, type);
return 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) var lc = scriptingAssemblyLoadContext;
if (assembly.GetName() == assemblyName)
if (lc is null)
return null;
foreach (Assembly assembly in lc.Assemblies)
{
var curName = assembly.GetName();
if (curName == assemblyName)
return assembly; 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; return null;
} }

View File

@@ -3,7 +3,6 @@
#pragma once #pragma once
#include "../Enums.h" #include "../Enums.h"
#include "Engine/Core/Math/Math.h"
/// <summary> /// <summary>
/// Material domain type. Material domain defines the target usage of the material shader. /// 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 API_ENUM() enum class MaterialShadingModel : byte
{ {
/// <summary> /// <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> /// </summary>
Unlit = 0, Unlit = 0,
@@ -96,7 +95,7 @@ API_ENUM() enum class MaterialShadingModel : byte
Lit = 1, Lit = 1,
/// <summary> /// <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> /// </summary>
Subsurface = 2, Subsurface = 2,
@@ -366,12 +365,12 @@ API_ENUM() enum class MaterialDecalBlendingMode : byte
API_ENUM() enum class MaterialTransparentLightingMode : byte API_ENUM() enum class MaterialTransparentLightingMode : byte
{ {
/// <summary> /// <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> /// </summary>
Surface = 0, Surface = 0,
/// <summary> /// <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> /// </summary>
SurfaceNonDirectional = 1, SurfaceNonDirectional = 1,
}; };

View File

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

View File

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

View File

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

View File

@@ -13,7 +13,7 @@ class FLAXENGINE_API DirectionalLight : public LightWithShadow
DECLARE_SCENE_OBJECT(DirectionalLight); DECLARE_SCENE_OBJECT(DirectionalLight);
public: public:
/// <summary> /// <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> /// </summary>
API_FIELD(Attributes="EditorOrder(65), DefaultValue(4), Limit(1, 4), EditorDisplay(\"Shadow\")") API_FIELD(Attributes="EditorOrder(65), DefaultValue(4), Limit(1, 4), EditorDisplay(\"Shadow\")")
int32 CascadeCount = 4; int32 CascadeCount = 4;

View File

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

View File

@@ -149,9 +149,14 @@ float Spline::GetSplineDuration() const
float Spline::GetSplineLength() const float Spline::GetSplineLength() const
{ {
float sum = 0.0f; float sum = 0.0f;
const int32 slices = 20; constexpr int32 slices = 20;
const float step = 1.0f / (float)slices; constexpr float step = 1.0f / (float)slices;
Vector3 prevPoint = Vector3::Zero; 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++) for (int32 i = 1; i < Curve.GetKeyframes().Count(); i++)
{ {
const auto& a = Curve[i - 1]; const auto& a = Curve[i - 1];
@@ -176,6 +181,37 @@ float Spline::GetSplineLength() const
return Math::Sqrt(sum); 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 float Spline::GetSplineTime(int32 index) const
{ {
CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), 0.0f) CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), 0.0f)

View File

@@ -167,6 +167,13 @@ public:
/// </summary> /// </summary>
API_PROPERTY() float GetSplineLength() const; 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> /// <summary>
/// Gets the time of the spline keyframe. /// Gets the time of the spline keyframe.
/// </summary> /// </summary>

View File

@@ -9,6 +9,8 @@
#include "Engine/Content/JsonAsset.h" #include "Engine/Content/JsonAsset.h"
#include "Engine/Threading/Threading.h" #include "Engine/Threading/Threading.h"
#if USE_EDITOR #if USE_EDITOR
#include "Editor/Editor.h"
#include "Editor/Managed/ManagedEditor.h"
#include "Engine/Level/Level.h" #include "Engine/Level/Level.h"
#include "Engine/Level/Scene/Scene.h" #include "Engine/Level/Scene/Scene.h"
#endif #endif
@@ -220,6 +222,17 @@ void NavigationSettings::Apply()
#endif #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) void NavigationSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)

View File

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

View File

@@ -35,7 +35,11 @@ void RigidBody::SetIsKinematic(const bool value)
return; return;
_isKinematic = value; _isKinematic = value;
if (_actor) if (_actor)
{
PhysicsBackend::SetRigidDynamicActorFlag(_actor, PhysicsBackend::RigidDynamicFlags::Kinematic, value); PhysicsBackend::SetRigidDynamicActorFlag(_actor, PhysicsBackend::RigidDynamicFlags::Kinematic, value);
if (!value && _isActive && _startAwake)
WakeUp();
}
} }
void RigidBody::SetLinearDamping(float value) 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.ThisActor = static_cast<PhysicsColliderActor*>(pair.shapes[0]->userData);
c.OtherActor = static_cast<PhysicsColliderActor*>(pair.shapes[1]->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 // Extract contact points
while (i.hasNextPatch()) while (i.hasNextPatch())

View File

@@ -14,8 +14,8 @@ class Texture;
/// </summary> /// </summary>
API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API AndroidPlatformSettings : public SettingsBase API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API AndroidPlatformSettings : public SettingsBase
{ {
DECLARE_SCRIPTING_TYPE_MINIMAL(AndroidPlatformSettings); DECLARE_SCRIPTING_TYPE_MINIMAL(AndroidPlatformSettings);
public: API_AUTO_SERIALIZATION();
/// <summary> /// <summary>
/// The application package name (eg. com.company.product). Custom tokens: ${PROJECT_NAME}, ${COMPANY_NAME}. /// 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\")") API_FIELD(Attributes="EditorOrder(1030), EditorDisplay(\"Other\")")
SoftObjectReference<Texture> OverrideIcon; SoftObjectReference<Texture> OverrideIcon;
public:
/// <summary> /// <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. /// 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> /// </summary>
static AndroidPlatformSettings* Get(); static AndroidPlatformSettings* Get();
// [SettingsBase]
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override
{
DESERIALIZE(PackageName);
DESERIALIZE(Permissions);
DESERIALIZE(OverrideIcon);
}
}; };
#if PLATFORM_ANDROID #if PLATFORM_ANDROID

View File

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

View File

@@ -16,7 +16,8 @@ class Texture;
API_CLASS(Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API GDKPlatformSettings : public SettingsBase API_CLASS(Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API GDKPlatformSettings : public SettingsBase
{ {
DECLARE_SCRIPTING_TYPE_MINIMAL(GDKPlatformSettings); DECLARE_SCRIPTING_TYPE_MINIMAL(GDKPlatformSettings);
public: API_AUTO_SERIALIZATION();
/// <summary> /// <summary>
/// Game identity name stored in game package manifest (for store). If empty the product name will be used from Game Settings. /// Game identity name stored in game package manifest (for store). If empty the product name will be used from Game Settings.
/// </summary> /// </summary>
@@ -118,28 +119,6 @@ public:
/// </summary> /// </summary>
API_FIELD(Attributes="EditorOrder(420), EditorDisplay(\"Media Capture\")") API_FIELD(Attributes="EditorOrder(420), EditorDisplay(\"Media Capture\")")
bool BlockGameDVR = false; 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 #endif

View File

@@ -14,8 +14,8 @@ class Texture;
/// </summary> /// </summary>
API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API LinuxPlatformSettings : public SettingsBase API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API LinuxPlatformSettings : public SettingsBase
{ {
DECLARE_SCRIPTING_TYPE_MINIMAL(LinuxPlatformSettings); DECLARE_SCRIPTING_TYPE_MINIMAL(LinuxPlatformSettings);
public: API_AUTO_SERIALIZATION();
/// <summary> /// <summary>
/// The default game window mode. /// The default game window mode.
@@ -65,25 +65,10 @@ public:
API_FIELD(Attributes="EditorOrder(2000), DefaultValue(true), EditorDisplay(\"Graphics\")") API_FIELD(Attributes="EditorOrder(2000), DefaultValue(true), EditorDisplay(\"Graphics\")")
bool SupportVulkan = true; bool SupportVulkan = true;
public:
/// <summary> /// <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. /// 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> /// </summary>
static LinuxPlatformSettings* Get(); 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 #if PLATFORM_LINUX

View File

@@ -12,6 +12,7 @@
API_CLASS(Sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API MacPlatformSettings : public ApplePlatformSettings API_CLASS(Sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API MacPlatformSettings : public ApplePlatformSettings
{ {
DECLARE_SCRIPTING_TYPE_MINIMAL(MacPlatformSettings); DECLARE_SCRIPTING_TYPE_MINIMAL(MacPlatformSettings);
API_AUTO_SERIALIZATION();
/// <summary> /// <summary>
/// The default game window mode. /// 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\")") API_FIELD(Attributes="EditorOrder(1010), EditorDisplay(\"Other\", \"Run In Background\")")
bool RunInBackground = false; bool RunInBackground = false;
public:
/// <summary> /// <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. /// 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> /// </summary>
static MacPlatformSettings* Get(); 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 #if PLATFORM_MAC

View File

@@ -11,8 +11,8 @@
/// </summary> /// </summary>
API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API UWPPlatformSettings : public SettingsBase API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API UWPPlatformSettings : public SettingsBase
{ {
DECLARE_SCRIPTING_TYPE_MINIMAL(UWPPlatformSettings); DECLARE_SCRIPTING_TYPE_MINIMAL(UWPPlatformSettings);
public: API_AUTO_SERIALIZATION();
/// <summary> /// <summary>
/// The preferred launch windowing mode. /// The preferred launch windowing mode.
@@ -66,8 +66,6 @@ public:
All = Landscape | LandscapeFlipped | Portrait | PortraitFlipped All = Landscape | LandscapeFlipped | Portrait | PortraitFlipped
}; };
public:
/// <summary> /// <summary>
/// The preferred launch windowing mode. Always fullscreen on Xbox. /// The preferred launch windowing mode. Always fullscreen on Xbox.
/// </summary> /// </summary>
@@ -98,22 +96,10 @@ public:
API_FIELD(Attributes="EditorOrder(2010), DefaultValue(false), EditorDisplay(\"Graphics\", \"Support DirectX 10\")") API_FIELD(Attributes="EditorOrder(2010), DefaultValue(false), EditorDisplay(\"Graphics\", \"Support DirectX 10\")")
bool SupportDX10 = false; bool SupportDX10 = false;
public:
/// <summary> /// <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. /// 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> /// </summary>
static UWPPlatformSettings* Get(); 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 #if PLATFORM_UWP

View File

@@ -14,8 +14,8 @@ class Texture;
/// </summary> /// </summary>
API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API WindowsPlatformSettings : public SettingsBase API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API WindowsPlatformSettings : public SettingsBase
{ {
DECLARE_SCRIPTING_TYPE_MINIMAL(WindowsPlatformSettings); DECLARE_SCRIPTING_TYPE_MINIMAL(WindowsPlatformSettings);
public: API_AUTO_SERIALIZATION();
/// <summary> /// <summary>
/// The default game window mode. /// The default game window mode.
@@ -83,28 +83,10 @@ public:
API_FIELD(Attributes="EditorOrder(2030), DefaultValue(false), EditorDisplay(\"Graphics\")") API_FIELD(Attributes="EditorOrder(2030), DefaultValue(false), EditorDisplay(\"Graphics\")")
bool SupportVulkan = false; bool SupportVulkan = false;
public:
/// <summary> /// <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. /// 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> /// </summary>
static WindowsPlatformSettings* Get(); 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 #if PLATFORM_WINDOWS

View File

@@ -12,6 +12,7 @@
API_CLASS(Sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API iOSPlatformSettings : public ApplePlatformSettings API_CLASS(Sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API iOSPlatformSettings : public ApplePlatformSettings
{ {
DECLARE_SCRIPTING_TYPE_MINIMAL(ApplePlatformSettings); DECLARE_SCRIPTING_TYPE_MINIMAL(ApplePlatformSettings);
API_AUTO_SERIALIZATION();
/// <summary> /// <summary>
/// The app export destination methods. /// 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. /// 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> /// </summary>
static iOSPlatformSettings* Get(); 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 #if PLATFORM_IOS

View File

@@ -87,6 +87,16 @@ bool FontAsset::Init()
return error; 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) void FontAsset::SetOptions(const FontOptions& value)
{ {
_options = value; _options = value;

View File

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

View File

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

View File

@@ -906,6 +906,7 @@ void ManagedBinaryModule::OnLoaded(MAssembly* assembly)
#if !COMPILE_WITHOUT_CSHARP #if !COMPILE_WITHOUT_CSHARP
PROFILE_CPU(); PROFILE_CPU();
ASSERT(ClassToTypeIndex.IsEmpty()); ASSERT(ClassToTypeIndex.IsEmpty());
ScopeLock lock(Locker);
const auto& classes = assembly->GetClasses(); 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) void Unbox(T& result, MObject* data)
{ {
CHECK(data); if (data)
Platform::MemoryCopy(&result, MCore::Object::Unbox(data), sizeof(T)); Platform::MemoryCopy(&result, MCore::Object::Unbox(data), sizeof(T));
} }
void ToManagedArray(MArray* result, const Span<T>& data) 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) DEFINE_INTERNAL_CALL(void) NativeInterop_CreateClass(NativeClassDefinitions* managedClass, void* assemblyHandle)
{ {
ScopeLock lock(BinaryModule::Locker);
MAssembly* assembly = GetAssembly(assemblyHandle); MAssembly* assembly = GetAssembly(assemblyHandle);
if (assembly == nullptr) 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); MClass* klass = New<MClass>(assembly, managedClass->typeHandle, managedClass->name, managedClass->fullname, managedClass->namespace_, managedClass->typeAttributes);
if (assembly != nullptr) 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; managedClass->nativePointer = klass;
} }
@@ -873,7 +885,7 @@ MClass::MClass(const MAssembly* parentAssembly, void* handle, const char* name,
static void* TypeIsEnumPtr = GetStaticMethodPointer(TEXT("TypeIsEnum")); static void* TypeIsEnumPtr = GetStaticMethodPointer(TEXT("TypeIsEnum"));
_isEnum = CallStaticMethod<bool, void*>(TypeIsEnumPtr, handle); _isEnum = CallStaticMethod<bool, void*>(TypeIsEnumPtr, handle);
CachedClassHandles.Add(handle, this); CachedClassHandles[handle] = this;
} }
bool MAssembly::ResolveMissingFile(String& assemblyPath) const bool MAssembly::ResolveMissingFile(String& assemblyPath) const
@@ -1551,6 +1563,7 @@ const Array<MObject*>& MProperty::GetAttributes() const
MAssembly* GetAssembly(void* assemblyHandle) MAssembly* GetAssembly(void* assemblyHandle)
{ {
ScopeLock lock(BinaryModule::Locker);
MAssembly* assembly; MAssembly* assembly;
if (CachedAssemblyHandles.TryGet(assemblyHandle, assembly)) if (CachedAssemblyHandles.TryGet(assemblyHandle, assembly))
return assembly; return assembly;
@@ -1559,6 +1572,7 @@ MAssembly* GetAssembly(void* assemblyHandle)
MClass* GetClass(MType* typeHandle) MClass* GetClass(MType* typeHandle)
{ {
ScopeLock lock(BinaryModule::Locker);
MClass* klass = nullptr; MClass* klass = nullptr;
CachedClassHandles.TryGet(typeHandle, klass); CachedClassHandles.TryGet(typeHandle, klass);
return nullptr; return nullptr;
@@ -1568,6 +1582,7 @@ MClass* GetOrCreateClass(MType* typeHandle)
{ {
if (!typeHandle) if (!typeHandle)
return nullptr; return nullptr;
ScopeLock lock(BinaryModule::Locker);
MClass* klass; MClass* klass;
if (!CachedClassHandles.TryGet(typeHandle, 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); klass = New<MClass>(assembly, classInfo.typeHandle, classInfo.name, classInfo.fullname, classInfo.namespace_, classInfo.typeAttributes);
if (assembly != nullptr) 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) if (typeHandle != classInfo.typeHandle)

View File

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

View File

@@ -268,6 +268,7 @@ namespace FlaxEngine
Foreground = Color.FromBgra(0xFFFFFFFF), Foreground = Color.FromBgra(0xFFFFFFFF),
ForegroundGrey = Color.FromBgra(0xFFA9A9B3), ForegroundGrey = Color.FromBgra(0xFFA9A9B3),
ForegroundDisabled = Color.FromBgra(0xFF787883), ForegroundDisabled = Color.FromBgra(0xFF787883),
ForegroundViewport = Color.FromBgra(0xFFFFFFFF),
BackgroundHighlighted = Color.FromBgra(0xFF54545C), BackgroundHighlighted = Color.FromBgra(0xFF54545C),
BorderHighlighted = Color.FromBgra(0xFF6A6A75), BorderHighlighted = Color.FromBgra(0xFF6A6A75),
BackgroundSelected = Color.FromBgra(0xFF007ACC), 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 internal class SerializerCache
{ {
public readonly JsonSerializerSettings JsonSettings;
public Newtonsoft.Json.JsonSerializer JsonSerializer; public Newtonsoft.Json.JsonSerializer JsonSerializer;
public StringBuilder StringBuilder; public StringBuilder StringBuilder;
public StringWriter StringWriter; public StringWriter StringWriter;
@@ -28,17 +29,106 @@ namespace FlaxEngine.Json
public StreamReader Reader; public StreamReader Reader;
public bool IsWriting; public bool IsWriting;
public bool IsReading; public bool IsReading;
#if FLAX_EDITOR
public uint CacheVersion;
#endif
public unsafe SerializerCache(JsonSerializerSettings settings) public unsafe SerializerCache(JsonSerializerSettings settings)
{ {
JsonSerializer = Newtonsoft.Json.JsonSerializer.CreateDefault(settings); JsonSettings = settings;
JsonSerializer.Formatting = Formatting.Indented;
JsonSerializer.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
StringBuilder = new StringBuilder(256); StringBuilder = new StringBuilder(256);
StringWriter = new StringWriter(StringBuilder, CultureInfo.InvariantCulture); StringWriter = new StringWriter(StringBuilder, CultureInfo.InvariantCulture);
SerializerWriter = new JsonSerializerInternalWriter(JsonSerializer);
MemoryStream = new UnmanagedMemoryStream((byte*)0, 0); 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); Reader = new StreamReader(MemoryStream, Encoding.UTF8, false);
}
/// <summary>Builds the writer state</summary>
private void BuildWrite()
{
SerializerWriter = new JsonSerializerInternalWriter(JsonSerializer);
JsonWriter = new JsonTextWriter(StringWriter) JsonWriter = new JsonTextWriter(StringWriter)
{ {
IndentChar = '\t', IndentChar = '\t',
@@ -52,65 +142,30 @@ namespace FlaxEngine.Json
DateFormatString = JsonSerializer.DateFormatString, 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 Settings = CreateDefaultSettings(false);
internal static JsonSerializerSettings SettingsManagedOnly = CreateDefaultSettings(true); internal static JsonSerializerSettings SettingsManagedOnly = CreateDefaultSettings(true);
internal static ExtendedSerializationBinder SerializationBinder;
internal static FlaxObjectConverter ObjectConverter; internal static FlaxObjectConverter ObjectConverter;
internal static ThreadLocal<SerializerCache> Current = new ThreadLocal<SerializerCache>(); internal static ThreadLocal<SerializerCache> Current = new ThreadLocal<SerializerCache>();
internal static ThreadLocal<SerializerCache> Cache = new ThreadLocal<SerializerCache>(() => new SerializerCache(Settings)); 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<SerializerCache> CacheManagedOnly = new ThreadLocal<SerializerCache>(() => new SerializerCache(SettingsManagedOnly));
internal static ThreadLocal<IntPtr> CachedGuidBuffer = new ThreadLocal<IntPtr>(() => Marshal.AllocHGlobal(32 * sizeof(char)), true); internal static ThreadLocal<IntPtr> CachedGuidBuffer = new ThreadLocal<IntPtr>(() => Marshal.AllocHGlobal(32 * sizeof(char)), true);
internal static string CachedGuidDigits = "0123456789abcdef"; 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) internal static JsonSerializerSettings CreateDefaultSettings(bool isManagedOnly)
{ {
//Newtonsoft.Json.Utilities.MiscellaneousUtils.ValueEquals = ValueEquals; //Newtonsoft.Json.Utilities.MiscellaneousUtils.ValueEquals = ValueEquals;
if (SerializationBinder is null)
SerializationBinder = new();
var settings = new JsonSerializerSettings var settings = new JsonSerializerSettings
{ {
ContractResolver = new ExtendedDefaultContractResolver(isManagedOnly), ContractResolver = new ExtendedDefaultContractResolver(isManagedOnly),
@@ -118,6 +173,7 @@ namespace FlaxEngine.Json
TypeNameHandling = TypeNameHandling.Auto, TypeNameHandling = TypeNameHandling.Auto,
NullValueHandling = NullValueHandling.Include, NullValueHandling = NullValueHandling.Include,
ObjectCreationHandling = ObjectCreationHandling.Auto, ObjectCreationHandling = ObjectCreationHandling.Auto,
SerializationBinder = SerializationBinder,
}; };
if (ObjectConverter == null) if (ObjectConverter == null)
ObjectConverter = new FlaxObjectConverter(); ObjectConverter = new FlaxObjectConverter();
@@ -134,6 +190,23 @@ namespace FlaxEngine.Json
return settings; 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() internal static void Dispose()
{ {
CachedGuidBuffer.Values.ForEach(Marshal.FreeHGlobal); CachedGuidBuffer.Values.ForEach(Marshal.FreeHGlobal);

View File

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

View File

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

View File

@@ -36,7 +36,7 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(TextureGroup);
int32 MaxAnisotropy = 16; int32 MaxAnisotropy = 16;
/// <summary> /// <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> /// </summary>
API_FIELD(Attributes="EditorOrder(20), Limit(0, 1)") API_FIELD(Attributes="EditorOrder(20), Limit(0, 1)")
float Quality = 1.0f; float Quality = 1.0f;
@@ -60,20 +60,20 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(TextureGroup);
int32 MipLevelsMin = 0; int32 MipLevelsMin = 0;
/// <summary> /// <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> /// </summary>
API_FIELD(Attributes="EditorOrder(40), Limit(1, 14)") API_FIELD(Attributes="EditorOrder(40), Limit(1, 14)")
int32 MipLevelsMax = 14; int32 MipLevelsMax = 14;
/// <summary> /// <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> /// </summary>
API_FIELD(Attributes="EditorOrder(50), Limit(-14, 14)") API_FIELD(Attributes="EditorOrder(50), Limit(-14, 14)")
int32 MipLevelsBias = 0; int32 MipLevelsBias = 0;
#if USE_EDITOR #if USE_EDITOR
/// <summary> /// <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> /// </summary>
API_FIELD(Attributes="EditorOrder(50)") API_FIELD(Attributes="EditorOrder(50)")
Dictionary<PlatformType, int32> MipLevelsMaxPerPlatform; Dictionary<PlatformType, int32> MipLevelsMaxPerPlatform;

View File

@@ -519,6 +519,40 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
} }
break; 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: default:
break; break;
} }

View File

@@ -228,25 +228,25 @@ public:
public: // Geometry public: // Geometry
// Enable model normal vectors recalculating. // Enable model normal vectors re-calculating.
API_FIELD(Attributes="EditorOrder(20), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))") API_FIELD(Attributes="EditorOrder(20), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))")
bool CalculateNormals = false; 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)") API_FIELD(Attributes="EditorOrder(30), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowSmoothingNormalsAngle)), Limit(0, 175, 0.1f)")
float SmoothingNormalsAngle = 175.0f; float SmoothingNormalsAngle = 175.0f;
// If checked, the imported normal vectors of the mesh will be flipped (scaled by -1). // 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))") API_FIELD(Attributes="EditorOrder(35), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))")
bool FlipNormals = false; bool FlipNormals = false;
// Enable model tangent vectors recalculating. // Enable model tangent vectors re-calculating.
API_FIELD(Attributes="EditorOrder(40), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))") API_FIELD(Attributes="EditorOrder(40), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))")
bool CalculateTangents = false; 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)") API_FIELD(Attributes="EditorOrder(45), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowSmoothingTangentsAngle)), Limit(0, 45, 0.1f)")
float SmoothingTangentsAngle = 45.0f; float SmoothingTangentsAngle = 45.0f;
// Enable/disable meshes geometry optimization. // Enable/disable meshes geometry optimization.
API_FIELD(Attributes="EditorOrder(50), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))") API_FIELD(Attributes="EditorOrder(50), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))")
bool OptimizeMeshes = true; 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))") API_FIELD(Attributes="EditorOrder(60), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))")
bool MergeMeshes = true; bool MergeMeshes = true;
// Enable/disable importing meshes Level of Details. // Enable/disable importing meshes Level of Details.
@@ -258,16 +258,16 @@ public:
// Enable/disable importing blend shapes (morph targets). // Enable/disable importing blend shapes (morph targets).
API_FIELD(Attributes="EditorOrder(85), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowSkinnedModel))") API_FIELD(Attributes="EditorOrder(85), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowSkinnedModel))")
bool ImportBlendShapes = false; 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))") API_FIELD(Attributes="EditorOrder(86), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowSkinnedModel))")
bool CalculateBoneOffsetMatrices = false; bool CalculateBoneOffsetMatrices = false;
// The lightmap UVs source. // The lightmap UVs source.
API_FIELD(Attributes="EditorOrder(90), EditorDisplay(\"Geometry\", \"Lightmap UVs Source\"), VisibleIf(nameof(ShowModel))") API_FIELD(Attributes="EditorOrder(90), EditorDisplay(\"Geometry\", \"Lightmap UVs Source\"), VisibleIf(nameof(ShowModel))")
ModelLightmapUVsSource LightmapUVsSource = ModelLightmapUVsSource::Disable; 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))") API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))")
String CollisionMeshesPrefix = TEXT(""); 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))") API_FIELD(Attributes = "EditorOrder(105), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))")
CollisionDataType CollisionType = CollisionDataType::TriangleMesh; CollisionDataType CollisionType = CollisionDataType::TriangleMesh;
@@ -291,19 +291,19 @@ public:
public: // Animation 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))") API_FIELD(Attributes="EditorOrder(1000), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowAnimation))")
AnimationDuration Duration = AnimationDuration::Imported; AnimationDuration Duration = AnimationDuration::Imported;
// Imported animation first/last frame index. Used only if Duration mode is set to Custom. // 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)") API_FIELD(Attributes="EditorOrder(1010), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowFramesRange)), Limit(0)")
Float2 FramesRange = Float2::Zero; 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)") API_FIELD(Attributes="EditorOrder(1020), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowAnimation)), Limit(0, 1000, 0.01f)")
float DefaultFrameRate = 0.0f; float DefaultFrameRate = 0.0f;
// The imported animation sampling rate. If value is 0 then the original animation speed will be used. // 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)") API_FIELD(Attributes="EditorOrder(1030), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowAnimation)), Limit(0, 1000, 0.01f)")
float SamplingRate = 0.0f; 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))") API_FIELD(Attributes="EditorOrder(1040), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowAnimation))")
bool SkipEmptyCurves = true; bool SkipEmptyCurves = true;
// The imported animation channels will be optimized to remove redundant keyframes. // 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. // 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))") API_FIELD(Attributes = "EditorOrder(401), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterials))")
bool ImportMaterialsAsInstances = false; 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))") API_FIELD(Attributes = "EditorOrder(402), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterialsAsInstances))")
AssetReference<MaterialBase> InstanceToImportAs; AssetReference<MaterialBase> InstanceToImportAs;
// If checked, the importer will import texture files used by the model and any embedded texture resources. // If checked, the importer will import texture files used by the model and any embedded texture resources.
@@ -369,16 +369,16 @@ public:
public: // Splitting 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\")") API_FIELD(Attributes="EditorOrder(2000), EditorDisplay(\"Splitting\")")
bool SplitObjects = false; 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\")") API_FIELD(Attributes="EditorOrder(2010), EditorDisplay(\"Splitting\")")
int32 ObjectIndex = -1; int32 ObjectIndex = -1;
public: // Other 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\")") API_FIELD(Attributes="EditorOrder(3030), EditorDisplay(\"Other\")")
String SubAssetFolder = TEXT(""); String SubAssetFolder = TEXT("");

View File

@@ -57,11 +57,11 @@ API_CLASS(Namespace="FlaxEngine.Tools", Static) class FLAXENGINE_API TextureTool
API_FIELD(Attributes="EditorOrder(70)") API_FIELD(Attributes="EditorOrder(70)")
bool FlipY = false; 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)") API_FIELD(Attributes="EditorOrder(80), Limit(0.0001f, 1000.0f, 0.01f)")
float Scale = 1.0f; 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") API_FIELD(Attributes="HideInEditor")
int32 MaxSize = 8192; int32 MaxSize = 8192;
@@ -69,11 +69,11 @@ API_CLASS(Namespace="FlaxEngine.Tools", Static) class FLAXENGINE_API TextureTool
API_FIELD(Attributes="EditorOrder(100)") API_FIELD(Attributes="EditorOrder(100)")
bool Resize = false; 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") API_FIELD(Attributes="HideInEditor")
int32 SizeX = 1024; 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") API_FIELD(Attributes="HideInEditor")
int32 SizeY = 1024; int32 SizeY = 1024;
@@ -85,7 +85,7 @@ API_CLASS(Namespace="FlaxEngine.Tools", Static) class FLAXENGINE_API TextureTool
API_FIELD(Attributes="EditorOrder(210), VisibleIf(\"PreserveAlphaCoverage\")") API_FIELD(Attributes="EditorOrder(210), VisibleIf(\"PreserveAlphaCoverage\")")
float PreserveAlphaCoverageReference = 0.5f; 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\")") API_FIELD(Attributes="EditorOrder(300), CustomEditorAlias(\"FlaxEditor.CustomEditors.Dedicated.TextureGroupEditor\")")
int32 TextureGroup = -1; int32 TextureGroup = -1;

View File

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

View File

@@ -22,12 +22,28 @@ namespace FlaxEngine.GUI
/// Occurs when popup lost focus. /// Occurs when popup lost focus.
/// </summary> /// </summary>
public Action LostFocus; 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 /> /// <inheritdoc />
public override void OnEndContainsFocus() public override void OnEndContainsFocus()
{ {
base.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 // Call event after this 'focus contains flag' propagation ends to prevent focus issues
if (LostFocus != null) if (LostFocus != null)
Scripting.RunOnUpdate(LostFocus); Scripting.RunOnUpdate(LostFocus);
@@ -125,6 +141,8 @@ namespace FlaxEngine.GUI
public override void OnDestroy() public override void OnDestroy()
{ {
LostFocus = null; LostFocus = null;
MainPanel = null;
SelectedControl = null;
base.OnDestroy(); 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> /// <summary>
/// Event fired when selected index gets changed. /// Event fired when selected index gets changed.
/// </summary> /// </summary>
@@ -411,13 +441,24 @@ namespace FlaxEngine.GUI
// TODO: support item templates // TODO: support item templates
var container = new VerticalPanel var panel = new Panel
{ {
AnchorPreset = AnchorPresets.StretchAll, AnchorPreset = AnchorPresets.StretchAll,
BackgroundColor = BackgroundColor, BackgroundColor = BackgroundColor,
AutoSize = false, ScrollBars = ScrollBars.Vertical,
AutoFocus = true,
Parent = popup, 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 var border = new Border
{ {
BorderColor = BorderColorHighlighted, BorderColor = BorderColorHighlighted,
@@ -482,10 +523,20 @@ namespace FlaxEngine.GUI
//AnchorPreset = AnchorPresets.VerticalStretchLeft, //AnchorPreset = AnchorPresets.VerticalStretchLeft,
Parent = item, 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; return popup;
} }
@@ -527,7 +578,16 @@ namespace FlaxEngine.GUI
/// </summary> /// </summary>
public void ShowPopup() 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) if (_items.Count == 0 || root == null)
return; return;
@@ -542,7 +602,7 @@ namespace FlaxEngine.GUI
// Show dropdown popup // Show dropdown popup
var locationRootSpace = Location + new Float2(0, Height); var locationRootSpace = Location + new Float2(0, Height);
var parent = Parent; var parent = Parent;
while (parent != null && parent != Root) while (parent != null && parent != root)
{ {
locationRootSpace = parent.PointToParent(ref locationRootSpace); locationRootSpace = parent.PointToParent(ref locationRootSpace);
parent = parent.Parent; parent = parent.Parent;
@@ -551,6 +611,8 @@ namespace FlaxEngine.GUI
_popup.Parent = root; _popup.Parent = root;
_popup.Focus(); _popup.Focus();
_popup.StartMouseCapture(); _popup.StartMouseCapture();
if (_popup.SelectedControl != null && _popup.MainPanel != null)
_popup.MainPanel.ScrollViewTo(_popup.SelectedControl, true);
OnPopupShow(); OnPopupShow();
} }

View File

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

View File

@@ -8,7 +8,6 @@ using System.IO;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
using FlaxEditor.Utilities; using FlaxEditor.Utilities;
#endif #endif
using FlaxEngine;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace FlaxEngine.Utilities namespace FlaxEngine.Utilities
@@ -18,6 +17,78 @@ namespace FlaxEngine.Utilities
/// </summary> /// </summary>
public static class VariantUtils 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 internal enum VariantType
{ {
Null = 0, Null = 0,

View File

@@ -685,7 +685,7 @@ void VisjectExecutor::ProcessGroupPacking(Box* box, Node* node, Value& value)
case 36: case 36:
{ {
// Get value with structure data // 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()) if (!node->GetBox(0)->HasConnection())
return; return;
@@ -741,7 +741,9 @@ void VisjectExecutor::ProcessGroupPacking(Box* box, Node* node, Value& value)
return; return;
} }
const ScriptingType& type = typeHandle.GetType(); 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)); OnError(node, box, String::Format(TEXT("Cannot unpack value of type {0} to structure of type {1}"), structureValue.Type, typeName));
return; return;

View File

@@ -1793,6 +1793,18 @@ namespace Flax.Build.Bindings
var apiTypeInfo = FindApiTypeInfo(buildData, typeInfo, caller); var apiTypeInfo = FindApiTypeInfo(buildData, typeInfo, caller);
if (apiTypeInfo != null && apiTypeInfo.IsInterface) if (apiTypeInfo != null && apiTypeInfo.IsInterface)
return true; 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; return false;
} }