diff --git a/Content/Engine/Textures/DefaultLensDirt.flax b/Content/Engine/Textures/DefaultLensDirt.flax
index f5aab3eaa..a4cc62324 100644
--- a/Content/Engine/Textures/DefaultLensDirt.flax
+++ b/Content/Engine/Textures/DefaultLensDirt.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:e7e30c93ed54ef1ebfd3b8ecb59a73b037c297d058951e616da758cfeca6ee91
-size 16589149
+oid sha256:149b9a2d5d9d5f02e3e1e0a8d58964d53c4432d969ee290dd2be08ce2285d946
+size 8295253
diff --git a/Content/Engine/Textures/DefaultLensStarburst.flax b/Content/Engine/Textures/DefaultLensStarburst.flax
index cc3351fed..ffc15ae75 100644
--- a/Content/Engine/Textures/DefaultLensStarburst.flax
+++ b/Content/Engine/Textures/DefaultLensStarburst.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:6698438109bfd26be8e320986ce3b93b5bf0f44907ad5562e3d4fc555be2c36a
-size 11185242
+oid sha256:9b6d662c3f40e7c03da5854bccc34ccf9b81219e38a05141eabefc9d830ff4c3
+size 1399104
diff --git a/Source/Editor/Content/Items/JsonAssetItem.cs b/Source/Editor/Content/Items/JsonAssetItem.cs
index 09deb4ace..2cd22e799 100644
--- a/Source/Editor/Content/Items/JsonAssetItem.cs
+++ b/Source/Editor/Content/Items/JsonAssetItem.cs
@@ -11,7 +11,10 @@ namespace FlaxEditor.Content
///
public class JsonAssetItem : AssetItem
{
- private readonly SpriteHandle _thumbnail;
+ ///
+ /// Asset icon.
+ ///
+ protected SpriteHandle _thumbnail;
///
/// Initializes a new instance of the class.
diff --git a/Source/Editor/Content/Proxy/JsonAssetProxy.cs b/Source/Editor/Content/Proxy/JsonAssetProxy.cs
index 8bfaae384..f526d12bc 100644
--- a/Source/Editor/Content/Proxy/JsonAssetProxy.cs
+++ b/Source/Editor/Content/Proxy/JsonAssetProxy.cs
@@ -127,7 +127,7 @@ namespace FlaxEditor.Content
/// Generic Json assets proxy (supports all json assets that don't have dedicated proxy).
///
///
- public sealed class GenericJsonAssetProxy : JsonAssetProxy
+ public class GenericJsonAssetProxy : JsonAssetProxy
{
///
public override string TypeName => typeof(JsonAsset).FullName;
@@ -161,7 +161,7 @@ namespace FlaxEditor.Content
/// Content proxy for a json assets of the given type that can be spawned in the editor.
///
///
- public sealed class SpawnableJsonAssetProxy : JsonAssetProxy where T : new()
+ public class SpawnableJsonAssetProxy : JsonAssetProxy where T : new()
{
///
public override string Name { get; } = Utilities.Utils.GetPropertyNameUI(typeof(T).Name);
diff --git a/Source/Editor/CustomEditors/Editors/TagEditor.cs b/Source/Editor/CustomEditors/Editors/TagEditor.cs
index c5c1ee6b0..54401cf72 100644
--- a/Source/Editor/CustomEditors/Editors/TagEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/TagEditor.cs
@@ -55,8 +55,8 @@ namespace FlaxEditor.CustomEditors.Editors
{
if (Values[0] is Tag asTag)
return asTag;
- if (Values[0] is int asInt)
- return new Tag(asInt);
+ if (Values[0] is uint asUInt)
+ return new Tag(asUInt);
if (Values[0] is string asString)
return Tags.Get(asString);
return Tag.Default;
@@ -65,7 +65,7 @@ namespace FlaxEditor.CustomEditors.Editors
{
if (Values[0] is Tag)
SetValue(value);
- if (Values[0] is int)
+ if (Values[0] is uint)
SetValue(value.Index);
else if (Values[0] is string)
SetValue(value.ToString());
@@ -114,7 +114,7 @@ namespace FlaxEditor.CustomEditors.Editors
private static void GetTags(TreeNode n, PickerData pickerData)
{
if (n is TreeNodeWithAddons a && a.Addons.Count != 0 && a.Addons[0] is CheckBox c && c.Checked)
- pickerData.CachedTags.Add(new Tag((int)n.Tag));
+ pickerData.CachedTags.Add((Tag)n.Tag);
foreach (var child in n.Children)
{
if (child is TreeNode treeNode)
@@ -139,7 +139,7 @@ namespace FlaxEditor.CustomEditors.Editors
if (pickerData.IsSingle)
{
UncheckAll(node.ParentTree, node);
- var value = new Tag(c.Checked ? (int)node.Tag : -1);
+ var value = c.Checked ? (Tag)node.Tag : Tag.Default;
pickerData.SetValue?.Invoke(value);
pickerData.SetValues?.Invoke(new[] { value });
}
@@ -166,8 +166,8 @@ namespace FlaxEditor.CustomEditors.Editors
if (parentNode.CustomArrowRect.HasValue)
{
indentation = (int)((parentNode.CustomArrowRect.Value.Location.X - 18) / nodeIndent) + 1;
- var parentIndex = (int)parentNode.Tag;
- parentTag = Tags.List[parentIndex];
+ var parentTagValue = (Tag)parentNode.Tag;
+ parentTag = parentTagValue.ToString();
}
var node = new TreeNodeWithAddons
{
@@ -244,7 +244,7 @@ namespace FlaxEditor.CustomEditors.Editors
// Add tag
var tag = Tags.Get(tagName);
node.Text = name;
- node.Tag = tag.Index;
+ node.Tag = tag;
var settingsAsset = GameSettings.LoadAsset();
if (settingsAsset && !settingsAsset.WaitForLoaded())
{
@@ -283,7 +283,8 @@ namespace FlaxEditor.CustomEditors.Editors
for (var i = 0; i < tags.Length; i++)
{
var tag = tags[i];
- bool isSelected = pickerData.IsSingle ? value.Index == i : values.Contains(new Tag(i));
+ var tagValue = new Tag((uint)(i + 1));
+ bool isSelected = pickerData.IsSingle ? value == tagValue : values.Contains(tagValue);
// Count parent tags count
int indentation = 0;
@@ -296,7 +297,7 @@ namespace FlaxEditor.CustomEditors.Editors
// Create node
var node = new TreeNodeWithAddons
{
- Tag = i,
+ Tag = tagValue,
Text = tag,
ChildrenIndent = nodeIndent,
CullChildren = false,
diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs
index 54ce17158..79d1149f1 100644
--- a/Source/Editor/Editor.cs
+++ b/Source/Editor/Editor.cs
@@ -1205,6 +1205,7 @@ namespace FlaxEditor
{
public byte AutoReloadScriptsOnMainWindowFocus;
public byte ForceScriptCompilationOnStartup;
+ public byte UseAssetImportPathRelative;
public byte AutoRebuildCSG;
public float AutoRebuildCSGTimeoutMs;
public byte AutoRebuildNavMesh;
diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs
index 0bd87082d..4df17364a 100644
--- a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs
+++ b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs
@@ -356,10 +356,15 @@ namespace FlaxEditor.GUI.ContextMenu
private void OnWindowGotFocus()
{
- if (_childCM != null && _window && _window.IsForegroundWindow)
+ var child = _childCM;
+ if (child != null && _window && _window.IsForegroundWindow)
{
// Hide child if user clicked over parent (do it next frame to process other events before - eg. child windows focus loss)
- FlaxEngine.Scripting.InvokeOnUpdate(HideChild);
+ FlaxEngine.Scripting.InvokeOnUpdate(() =>
+ {
+ if (child == _childCM)
+ HideChild();
+ });
}
}
diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp
index 1ffc9b745..4d9e1876d 100644
--- a/Source/Editor/Managed/ManagedEditor.Internal.cpp
+++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp
@@ -830,6 +830,9 @@ public:
{
SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_SetOptions")
ManagedEditor::ManagedEditorOptions = *options;
+
+ // Apply options
+ AssetsImportingManager::UseImportPathRelative = ManagedEditor::ManagedEditorOptions.UseAssetImportPathRelative != 0;
}
static void DrawNavMesh()
diff --git a/Source/Editor/Managed/ManagedEditor.h b/Source/Editor/Managed/ManagedEditor.h
index 8d6547377..dd3ebb3bb 100644
--- a/Source/Editor/Managed/ManagedEditor.h
+++ b/Source/Editor/Managed/ManagedEditor.h
@@ -26,6 +26,7 @@ public:
{
byte AutoReloadScriptsOnMainWindowFocus = 1;
byte ForceScriptCompilationOnStartup = 1;
+ byte UseAssetImportPathRelative = 1;
byte AutoRebuildCSG = 1;
float AutoRebuildCSGTimeoutMs = 50;
byte AutoRebuildNavMesh = 1;
diff --git a/Source/Editor/Options/GeneralOptions.cs b/Source/Editor/Options/GeneralOptions.cs
index 5f05cae2c..ea3b0bee9 100644
--- a/Source/Editor/Options/GeneralOptions.cs
+++ b/Source/Editor/Options/GeneralOptions.cs
@@ -148,6 +148,13 @@ namespace FlaxEditor.Options
[EditorDisplay("Scripting", "Auto Save Visual Script On Play Start"), EditorOrder(505), Tooltip("Determines whether automatically save the Visual Script asset editors when starting the play mode in editor.")]
public bool AutoSaveVisualScriptOnPlayStart { get; set; } = true;
+ ///
+ /// If checked, imported file path will be stored relative to the project folder within imported asset metadata. Otherwise will use absolute path.
+ ///
+ [DefaultValue(true)]
+ [EditorDisplay("Content"), EditorOrder(550)]
+ public bool UseAssetImportPathRelative { get; set; } = true;
+
///
/// Gets or sets a value indicating whether perform automatic CSG rebuild on brush change.
///
diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs
index 0b4476e0a..7d83f7dba 100644
--- a/Source/Editor/Options/OptionsModule.cs
+++ b/Source/Editor/Options/OptionsModule.cs
@@ -186,6 +186,7 @@ namespace FlaxEditor.Options
Editor.InternalOptions internalOptions;
internalOptions.AutoReloadScriptsOnMainWindowFocus = (byte)(Options.General.AutoReloadScriptsOnMainWindowFocus ? 1 : 0);
internalOptions.ForceScriptCompilationOnStartup = (byte)(Options.General.ForceScriptCompilationOnStartup ? 1 : 0);
+ internalOptions.UseAssetImportPathRelative = (byte)(Options.General.UseAssetImportPathRelative ? 1 : 0);
internalOptions.AutoRebuildCSG = (byte)(Options.General.AutoRebuildCSG ? 1 : 0);
internalOptions.AutoRebuildCSGTimeoutMs = Options.General.AutoRebuildCSGTimeoutMs;
internalOptions.AutoRebuildNavMesh = (byte)(Options.General.AutoRebuildNavMesh ? 1 : 0);
diff --git a/Source/Editor/Surface/Elements/InputBox.cs b/Source/Editor/Surface/Elements/InputBox.cs
index e97beee2c..87a4aff00 100644
--- a/Source/Editor/Surface/Elements/InputBox.cs
+++ b/Source/Editor/Surface/Elements/InputBox.cs
@@ -119,7 +119,8 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = IntegerValue.Get(box.ParentNode, box.Archetype, box.Value);
- var control = new IntValueBox(value, bounds.X, bounds.Y, 40, int.MinValue, int.MaxValue, 0.01f)
+ var width = 40;
+ var control = new IntValueBox(value, bounds.X, bounds.Y, width + 12, int.MinValue, int.MaxValue, 0.01f)
{
Height = bounds.Height,
Parent = box.Parent,
@@ -164,7 +165,8 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = UnsignedIntegerValue.Get(box.ParentNode, box.Archetype, box.Value);
- var control = new UIntValueBox(value, bounds.X, bounds.Y, 40, uint.MinValue, uint.MaxValue, 0.01f)
+ var width = 40;
+ var control = new UIntValueBox(value, bounds.X, bounds.Y, width + 12, uint.MinValue, uint.MaxValue, 0.01f)
{
Height = bounds.Height,
Parent = box.Parent,
@@ -209,7 +211,8 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = FloatValue.Get(box.ParentNode, box.Archetype, box.Value);
- var control = new FloatValueBox(value, bounds.X, bounds.Y, 40, float.MinValue, float.MaxValue, 0.01f)
+ var width = 40;
+ var control = new FloatValueBox(value, bounds.X, bounds.Y, width + 12, float.MinValue, float.MaxValue, 0.01f)
{
Height = bounds.Height,
Parent = box.Parent,
@@ -254,7 +257,7 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = box.Value as string;
- var control = new TextBox(false, bounds.X, bounds.Y, 40)
+ var control = new TextBox(false, bounds.X, bounds.Y, 50)
{
Text = value,
Height = bounds.Height,
@@ -299,20 +302,21 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
- var control = new ContainerControl(bounds.X, bounds.Y, 22 * 2 - 2, bounds.Height)
+ var width = 30;
+ var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 2 - 2, bounds.Height)
{
ClipChildren = false,
AutoFocus = false,
Parent = box.Parent,
Tag = box,
};
- var floatX = new RealValueBox(value.X, 0, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatX = new RealValueBox(value.X, 0, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatX.BoxValueChanged += OnValueChanged;
- var floatY = new RealValueBox(value.Y, 22, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatY = new RealValueBox(value.Y, width + 2, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
@@ -372,26 +376,27 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
- var control = new ContainerControl(bounds.X, bounds.Y, 22 * 3 - 2, bounds.Height)
+ var width = 30;
+ var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 3 - 2, bounds.Height)
{
ClipChildren = false,
AutoFocus = false,
Parent = box.Parent,
Tag = box,
};
- var floatX = new RealValueBox(value.X, 0, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatX = new RealValueBox(value.X, 0, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatX.BoxValueChanged += OnValueChanged;
- var floatY = new RealValueBox(value.Y, 22, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatY = new RealValueBox(value.Y, width + 2, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatY.BoxValueChanged += OnValueChanged;
- var floatZ = new RealValueBox(value.Z, 44, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatZ = new RealValueBox(value.Z, width * 2 + 4, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
@@ -454,32 +459,33 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
- var control = new ContainerControl(bounds.X, bounds.Y, 22 * 4 - 2, bounds.Height)
+ var width = 20;
+ var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 4 - 2, bounds.Height)
{
ClipChildren = false,
AutoFocus = false,
Parent = box.Parent,
Tag = box,
};
- var floatX = new RealValueBox(value.X, 0, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatX = new RealValueBox(value.X, 0, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatX.BoxValueChanged += OnValueChanged;
- var floatY = new RealValueBox(value.Y, 22, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatY = new RealValueBox(value.Y, width + 2, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatY.BoxValueChanged += OnValueChanged;
- var floatZ = new RealValueBox(value.Z, 44, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatZ = new RealValueBox(value.Z, width * 2 + 4, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatZ.BoxValueChanged += OnValueChanged;
- var floatW = new RealValueBox(value.W, 66, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatW = new RealValueBox(value.W, width * 3 + 6, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
@@ -546,20 +552,21 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
- var control = new ContainerControl(bounds.X, bounds.Y, 22 * 2 - 2, bounds.Height)
+ var width = 30;
+ var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 2 - 2, bounds.Height)
{
ClipChildren = false,
AutoFocus = false,
Parent = box.Parent,
Tag = box,
};
- var floatX = new FloatValueBox(value.X, 0, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatX = new FloatValueBox(value.X, 0, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatX.BoxValueChanged += OnValueChanged;
- var floatY = new FloatValueBox(value.Y, 22, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatY = new FloatValueBox(value.Y, width + 2, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
@@ -619,26 +626,27 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
- var control = new ContainerControl(bounds.X, bounds.Y, 22 * 3 - 2, bounds.Height)
+ var width = 30;
+ var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 3 - 2, bounds.Height)
{
ClipChildren = false,
AutoFocus = false,
Parent = box.Parent,
Tag = box,
};
- var floatX = new FloatValueBox(value.X, 0, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatX = new FloatValueBox(value.X, 0, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatX.BoxValueChanged += OnValueChanged;
- var floatY = new FloatValueBox(value.Y, 22, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatY = new FloatValueBox(value.Y, width + 2, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatY.BoxValueChanged += OnValueChanged;
- var floatZ = new FloatValueBox(value.Z, 44, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatZ = new FloatValueBox(value.Z, width * 2 + 4, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
@@ -701,32 +709,33 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
- var control = new ContainerControl(bounds.X, bounds.Y, 22 * 4 - 2, bounds.Height)
+ var width = 20;
+ var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 4 - 2, bounds.Height)
{
ClipChildren = false,
AutoFocus = false,
Parent = box.Parent,
Tag = box,
};
- var floatX = new FloatValueBox(value.X, 0, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatX = new FloatValueBox(value.X, 0, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatX.BoxValueChanged += OnValueChanged;
- var floatY = new FloatValueBox(value.Y, 22, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatY = new FloatValueBox(value.Y, width + 2, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatY.BoxValueChanged += OnValueChanged;
- var floatZ = new FloatValueBox(value.Z, 44, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatZ = new FloatValueBox(value.Z, width * 2 + 4, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatZ.BoxValueChanged += OnValueChanged;
- var floatW = new FloatValueBox(value.W, 66, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatW = new FloatValueBox(value.W, width * 3 + 6, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
@@ -793,20 +802,21 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
- var control = new ContainerControl(bounds.X, bounds.Y, 22 * 2 - 2, bounds.Height)
+ var width = 30;
+ var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 2 - 2, bounds.Height)
{
ClipChildren = false,
AutoFocus = false,
Parent = box.Parent,
Tag = box,
};
- var floatX = new DoubleValueBox(value.X, 0, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatX = new DoubleValueBox(value.X, 0, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatX.BoxValueChanged += OnValueChanged;
- var floatY = new DoubleValueBox(value.Y, 22, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatY = new DoubleValueBox(value.Y, width + 2, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
@@ -866,26 +876,27 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
- var control = new ContainerControl(bounds.X, bounds.Y, 22 * 3 - 2, bounds.Height)
+ var width = 30;
+ var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 3 - 2, bounds.Height)
{
ClipChildren = false,
AutoFocus = false,
Parent = box.Parent,
Tag = box,
};
- var floatX = new DoubleValueBox(value.X, 0, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatX = new DoubleValueBox(value.X, 0, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatX.BoxValueChanged += OnValueChanged;
- var floatY = new DoubleValueBox(value.Y, 22, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatY = new DoubleValueBox(value.Y, width + 2, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatY.BoxValueChanged += OnValueChanged;
- var floatZ = new DoubleValueBox(value.Z, 44, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatZ = new DoubleValueBox(value.Z, width * 2 + 4, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
@@ -948,32 +959,33 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box);
- var control = new ContainerControl(bounds.X, bounds.Y, 22 * 4 - 2, bounds.Height)
+ var width = 20;
+ var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 4 - 2, bounds.Height)
{
ClipChildren = false,
AutoFocus = false,
Parent = box.Parent,
Tag = box,
};
- var floatX = new DoubleValueBox(value.X, 0, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatX = new DoubleValueBox(value.X, 0, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatX.BoxValueChanged += OnValueChanged;
- var floatY = new DoubleValueBox(value.Y, 22, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatY = new DoubleValueBox(value.Y, width + 2, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatY.BoxValueChanged += OnValueChanged;
- var floatZ = new DoubleValueBox(value.Z, 44, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatZ = new DoubleValueBox(value.Z, width * 2 + 4, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatZ.BoxValueChanged += OnValueChanged;
- var floatW = new DoubleValueBox(value.W, 66, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatW = new DoubleValueBox(value.W, width * 3 + 6, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
@@ -1040,26 +1052,27 @@ namespace FlaxEditor.Surface.Elements
public Control Create(InputBox box, ref Rectangle bounds)
{
var value = GetValue(box).EulerAngles;
- var control = new ContainerControl(bounds.X, bounds.Y, 22 * 3 - 2, bounds.Height)
+ var width = 20;
+ var control = new ContainerControl(bounds.X, bounds.Y, (width + 2) * 3 - 2, bounds.Height)
{
ClipChildren = false,
AutoFocus = false,
Parent = box.Parent,
Tag = box,
};
- var floatX = new FloatValueBox(value.X, 0, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatX = new FloatValueBox(value.X, 0, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatX.BoxValueChanged += OnValueChanged;
- var floatY = new FloatValueBox(value.Y, 22, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatY = new FloatValueBox(value.Y, width + 2, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
};
floatY.BoxValueChanged += OnValueChanged;
- var floatZ = new FloatValueBox(value.Z, 44, 0, 20, float.MinValue, float.MaxValue, 0.0f)
+ var floatZ = new FloatValueBox(value.Z, width * 2 + 4, 0, width, float.MinValue, float.MaxValue, 0.0f)
{
Height = bounds.Height,
Parent = control,
diff --git a/Source/Editor/Tools/Terrain/Sculpt/Mode.cs b/Source/Editor/Tools/Terrain/Sculpt/Mode.cs
index a8b8cf53a..4297408c8 100644
--- a/Source/Editor/Tools/Terrain/Sculpt/Mode.cs
+++ b/Source/Editor/Tools/Terrain/Sculpt/Mode.cs
@@ -147,17 +147,8 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
Apply(ref p);
}
- var editorOptions = Editor.Instance.Options.Options;
- bool isPlayMode = Editor.Instance.StateMachine.IsPlayMode;
-
// Auto NavMesh rebuild
- if (!isPlayMode && editorOptions.General.AutoRebuildNavMesh)
- {
- if (terrain.Scene && terrain.HasStaticFlag(StaticFlags.Navigation))
- {
- Navigation.BuildNavMesh(terrain.Scene, brushBounds, editorOptions.General.AutoRebuildNavMeshTimeoutMs);
- }
- }
+ gizmo.CurrentEditUndoAction.AddDirtyBounds(ref brushBounds);
}
///
diff --git a/Source/Editor/Tools/Terrain/Undo/EditTerrainMapAction.cs b/Source/Editor/Tools/Terrain/Undo/EditTerrainMapAction.cs
index baede8a73..c82b49b7e 100644
--- a/Source/Editor/Tools/Terrain/Undo/EditTerrainMapAction.cs
+++ b/Source/Editor/Tools/Terrain/Undo/EditTerrainMapAction.cs
@@ -41,33 +41,24 @@ namespace FlaxEditor.Tools.Terrain.Undo
public object Tag;
}
- ///
- /// The terrain (actor Id).
- ///
[Serialize]
protected readonly Guid _terrain;
- ///
- /// The heightmap length (vertex count).
- ///
[Serialize]
protected readonly int _heightmapLength;
- ///
- /// The heightmap data size (in bytes).
- ///
[Serialize]
protected readonly int _heightmapDataSize;
- ///
- /// The terrain patches
- ///
[Serialize]
protected readonly List _patches;
- ///
- /// Gets a value indicating whether this action has any modification to the terrain (recorded patches changes).
- ///
+ [Serialize]
+ protected readonly List _navmeshBoundsModifications;
+
+ [Serialize]
+ protected readonly float _dirtyNavMeshTimeoutMs;
+
[NoSerialize]
public bool HasAnyModification => _patches.Count > 0;
@@ -98,6 +89,27 @@ namespace FlaxEditor.Tools.Terrain.Undo
var heightmapSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1;
_heightmapLength = heightmapSize * heightmapSize;
_heightmapDataSize = _heightmapLength * stride;
+
+ // Auto NavMesh rebuild
+ var editorOptions = Editor.Instance.Options.Options;
+ bool isPlayMode = Editor.Instance.StateMachine.IsPlayMode;
+ if (!isPlayMode && editorOptions.General.AutoRebuildNavMesh)
+ {
+ if (terrain.Scene && terrain.HasStaticFlag(StaticFlags.Navigation))
+ {
+ _navmeshBoundsModifications = new List();
+ _dirtyNavMeshTimeoutMs = editorOptions.General.AutoRebuildNavMeshTimeoutMs;
+ }
+ }
+ }
+
+ ///
+ /// Adds modified bounds to the undo actor modifications list.
+ ///
+ /// The world-space bounds.
+ public void AddDirtyBounds(ref BoundingBox bounds)
+ {
+ _navmeshBoundsModifications?.Add(bounds);
}
///
@@ -155,7 +167,15 @@ namespace FlaxEditor.Tools.Terrain.Undo
_patches[i] = patch;
}
- Editor.Instance.Scene.MarkSceneEdited(Terrain.Scene);
+ // Update navmesh
+ var scene = Terrain.Scene;
+ if (_navmeshBoundsModifications != null)
+ {
+ foreach (var bounds in _navmeshBoundsModifications)
+ Navigation.BuildNavMesh(scene, bounds, _dirtyNavMeshTimeoutMs);
+ }
+
+ Editor.Instance.Scene.MarkSceneEdited(scene);
}
///
@@ -183,10 +203,12 @@ namespace FlaxEditor.Tools.Terrain.Undo
Marshal.FreeHGlobal(_patches[i].After);
}
_patches.Clear();
+ _navmeshBoundsModifications?.Clear();
}
private void Set(Func dataGetter)
{
+ // Update patches
for (int i = 0; i < _patches.Count; i++)
{
var patch = _patches[i];
@@ -194,6 +216,14 @@ namespace FlaxEditor.Tools.Terrain.Undo
SetData(ref patch.PatchCoord, data, patch.Tag);
}
+ // Update navmesh
+ var scene = Terrain.Scene;
+ if (_navmeshBoundsModifications != null)
+ {
+ foreach (var bounds in _navmeshBoundsModifications)
+ Navigation.BuildNavMesh(scene, bounds, _dirtyNavMeshTimeoutMs);
+ }
+
Editor.Instance.Scene.MarkSceneEdited(Terrain.Scene);
}
diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs
index 89f13d6a3..06ad40e5c 100644
--- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs
+++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs
@@ -155,7 +155,7 @@ namespace FlaxEditor.Windows
var scriptType = new ScriptType(typeof(Script));
foreach (var type in Editor.CodeEditing.All.Get())
{
- if (type.IsAbstract)
+ if (type.IsAbstract || type.Type == null)
continue;
if (actorType.IsAssignableFrom(type) || scriptType.IsAssignableFrom(type))
continue;
diff --git a/Source/Engine/Content/BinaryAsset.cpp b/Source/Engine/Content/BinaryAsset.cpp
index 99c91cc4a..f247d46d4 100644
--- a/Source/Engine/Content/BinaryAsset.cpp
+++ b/Source/Engine/Content/BinaryAsset.cpp
@@ -4,15 +4,16 @@
#include "Cache/AssetsCache.h"
#include "Storage/ContentStorageManager.h"
#include "Loading/Tasks/LoadAssetDataTask.h"
+#include "Factories/BinaryAssetFactory.h"
#include "Engine/ContentImporters/AssetsImportingManager.h"
#include "Engine/Content/Content.h"
#include "Engine/Serialization/JsonTools.h"
#include "Engine/Debug/Exceptions/JsonParseException.h"
-#include "Factories/BinaryAssetFactory.h"
#include "Engine/Threading/ThreadPoolTask.h"
#if USE_EDITOR
#include "Engine/Platform/FileSystem.h"
#include "Engine/Threading/Threading.h"
+#include "Engine/Engine/Globals.h"
#endif
REGISTER_BINARY_ASSET_ABSTRACT(BinaryAsset, "FlaxEngine.BinaryAsset");
@@ -123,6 +124,12 @@ void BinaryAsset::GetImportMetadata(String& path, String& username) const
{
path = JsonTools::GetString(document, "ImportPath");
username = JsonTools::GetString(document, "ImportUsername");
+ if (path.HasChars() && FileSystem::IsRelative(path))
+ {
+ // Convert path back to thr absolute (eg. if stored in relative format)
+ path = Globals::ProjectFolder / path;
+ StringUtils::PathRemoveRelativeParts(path);
+ }
}
else
{
diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp
index 1e1f81e09..f894b7417 100644
--- a/Source/Engine/Content/Content.cpp
+++ b/Source/Engine/Content/Content.cpp
@@ -2,6 +2,8 @@
#include "Content.h"
#include "JsonAsset.h"
+#include "SceneReference.h"
+#include "Engine/Serialization/Serialization.h"
#include "Cache/AssetsCache.h"
#include "Storage/ContentStorageManager.h"
#include "Storage/JsonStorageProxy.h"
@@ -39,6 +41,16 @@ String AssetInfo::ToString() const
return String::Format(TEXT("ID: {0}, TypeName: {1}, Path: \'{2}\'"), ID, TypeName, Path);
}
+void FLAXENGINE_API Serialization::Serialize(ISerializable::SerializeStream& stream, const SceneReference& v, const void* otherObj)
+{
+ Serialize(stream, v.ID, otherObj);
+}
+
+void FLAXENGINE_API Serialization::Deserialize(ISerializable::DeserializeStream& stream, SceneReference& v, ISerializeModifier* modifier)
+{
+ Deserialize(stream, v.ID, modifier);
+}
+
namespace
{
// Assets
diff --git a/Source/Engine/Content/SceneReference.h b/Source/Engine/Content/SceneReference.h
index c1e405f4e..50b24955d 100644
--- a/Source/Engine/Content/SceneReference.h
+++ b/Source/Engine/Content/SceneReference.h
@@ -1,8 +1,9 @@
-// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
+// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Core/Types/Guid.h"
+#include "Engine/Core/ISerializable.h"
///
/// Represents the reference to the scene asset. Stores the unique ID of the scene to reference. Can be used to load the selected scene.
@@ -15,4 +16,37 @@ API_STRUCT(NoDefault) struct FLAXENGINE_API SceneReference
/// The identifier of the scene asset (and the scene object).
///
API_FIELD() Guid ID;
+
+ FORCE_INLINE bool operator==(const SceneReference& other) const
+ {
+ return ID == other.ID;
+ }
+
+ FORCE_INLINE bool operator!=(const SceneReference& other) const
+ {
+ return ID != other.ID;
+ }
};
+
+template<>
+struct TIsPODType
+{
+ enum { Value = true };
+};
+
+inline uint32 GetHash(const SceneReference& key)
+{
+ return GetHash(key.ID);
+}
+
+// @formatter:off
+namespace Serialization
+{
+ inline bool ShouldSerialize(const SceneReference& v, const void* otherObj)
+ {
+ return !otherObj || v != *(SceneReference*)otherObj;
+ }
+ void FLAXENGINE_API Serialize(ISerializable::SerializeStream& stream, const SceneReference& v, const void* otherObj);
+ void FLAXENGINE_API Deserialize(ISerializable::DeserializeStream& stream, SceneReference& v, ISerializeModifier* modifier);
+}
+// @formatter:on
diff --git a/Source/Engine/ContentImporters/AssetsImportingManager.cpp b/Source/Engine/ContentImporters/AssetsImportingManager.cpp
index 9c654aada..2b80d3454 100644
--- a/Source/Engine/ContentImporters/AssetsImportingManager.cpp
+++ b/Source/Engine/ContentImporters/AssetsImportingManager.cpp
@@ -3,6 +3,7 @@
#if COMPILE_WITH_ASSETS_IMPORTER
#include "AssetsImportingManager.h"
+#include "Engine/Core/Log.h"
#include "Engine/Core/Utilities.h"
#include "Engine/Serialization/JsonWriters.h"
#include "Engine/Threading/MainThreadTask.h"
@@ -10,9 +11,9 @@
#include "Engine/Content/Content.h"
#include "Engine/Content/Cache/AssetsCache.h"
#include "Engine/Engine/EngineService.h"
-#include "Engine/Core/Log.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/Platform.h"
+#include "Engine/Engine/Globals.h"
#include "ImportTexture.h"
#include "ImportModelFile.h"
#include "ImportAudio.h"
@@ -71,6 +72,7 @@ AssetsImportingManagerService AssetsImportingManagerServiceInstance;
Array AssetsImportingManager::Importers;
Array AssetsImportingManager::Creators;
+bool AssetsImportingManager::UseImportPathRelative = false;
CreateAssetContext::CreateAssetContext(const StringView& inputPath, const StringView& outputPath, const Guid& id, void* arg)
{
@@ -161,7 +163,15 @@ bool CreateAssetContext::AllocateChunk(int32 index)
void CreateAssetContext::AddMeta(JsonWriter& writer) const
{
writer.JKEY("ImportPath");
- writer.String(InputPath);
+ if (AssetsImportingManager::UseImportPathRelative && !FileSystem::IsRelative(InputPath))
+ {
+ const String relativePath = FileSystem::ConvertAbsolutePathToRelative(Globals::ProjectFolder, InputPath);
+ writer.String(relativePath);
+ }
+ else
+ {
+ writer.String(InputPath);
+ }
writer.JKEY("ImportUsername");
writer.String(Platform::GetUserName());
}
diff --git a/Source/Engine/ContentImporters/AssetsImportingManager.h b/Source/Engine/ContentImporters/AssetsImportingManager.h
index 0ec23d6bb..c329ea10a 100644
--- a/Source/Engine/ContentImporters/AssetsImportingManager.h
+++ b/Source/Engine/ContentImporters/AssetsImportingManager.h
@@ -22,6 +22,11 @@ public:
///
static Array Creators;
+ ///
+ /// If true store asset import path relative to the current workspace, otherwise will store absolute path.
+ ///
+ static bool UseImportPathRelative;
+
public:
///
/// The create texture tag (using internal import pipeline to crate texture asset from custom image source).
diff --git a/Source/Engine/Level/Tags.cpp b/Source/Engine/Level/Tags.cpp
index 67633df9e..21cd69c08 100644
--- a/Source/Engine/Level/Tags.cpp
+++ b/Source/Engine/Level/Tags.cpp
@@ -12,7 +12,8 @@ FLAXENGINE_API String* TagsListDebug = nullptr;
const String& Tag::ToString() const
{
- return Index >= 0 && Index < Tags::List.Count() ? Tags::List.Get()[Index] : String::Empty;
+ const int32 index = (int32)Index - 1;
+ return index >= 0 && index < Tags::List.Count() ? Tags::List.Get()[index] : String::Empty;
}
bool Tag::operator==(const StringView& other) const
@@ -27,7 +28,7 @@ bool Tag::operator!=(const StringView& other) const
void FLAXENGINE_API Serialization::Serialize(ISerializable::SerializeStream& stream, const Tag& v, const void* otherObj)
{
- if (v.Index != -1)
+ if (v.Index != 0)
stream.String(v.ToString());
else
stream.String("", 0);
@@ -42,11 +43,11 @@ Tag Tags::Get(const StringView& tagName)
{
if (tagName.IsEmpty())
return Tag();
- Tag tag = List.Find(tagName);
- if (tag.Index == -1 && tagName.HasChars())
+ Tag tag(List.Find(tagName) + 1);
+ if (tag.Index == 0 && tagName.HasChars())
{
- tag.Index = List.Count();
List.AddOne() = tagName;
+ tag.Index = List.Count();
#if !BUILD_RELEASE
TagsListDebug = List.Get();
#endif
@@ -56,7 +57,7 @@ Tag Tags::Get(const StringView& tagName)
bool Tags::HasTag(const Array& list, const Tag& tag)
{
- if (tag.Index == -1)
+ if (tag.Index == 0)
return false;
const String& tagName = tag.ToString();
for (const Tag& e : list)
@@ -70,7 +71,7 @@ bool Tags::HasTag(const Array& list, const Tag& tag)
bool Tags::HasTagExact(const Array& list, const Tag& tag)
{
- if (tag.Index == -1)
+ if (tag.Index == 0)
return false;
return list.Contains(tag);
}
@@ -119,7 +120,7 @@ bool Tags::HasAllExact(const Array& list, const Array& tags)
return true;
}
-const String& Tags::GetTagName(int32 tag)
+const String& Tags::GetTagName(uint32 tag)
{
return Tag(tag).ToString();
}
diff --git a/Source/Engine/Level/Tags.cs b/Source/Engine/Level/Tags.cs
index b4aa3468c..8f46d2f85 100644
--- a/Source/Engine/Level/Tags.cs
+++ b/Source/Engine/Level/Tags.cs
@@ -1,33 +1,29 @@
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
using System;
+using System.ComponentModel;
+using System.Globalization;
using System.Runtime.CompilerServices;
namespace FlaxEngine
{
+ [TypeConverter(typeof(TypeConverters.TagConverter))]
partial struct Tag : IEquatable, IEquatable, IComparable, IComparable, IComparable
{
///
/// The default .
///
- public static Tag Default => new Tag(-1);
+ public static Tag Default => new Tag(0);
///
/// Initializes a new instance of the struct.
///
/// The tag index.
- public Tag(int index)
+ public Tag(uint index)
{
Index = index;
}
- [System.Runtime.Serialization.OnDeserializing]
- internal void OnDeserializing(System.Runtime.Serialization.StreamingContext context)
- {
- // Initialize structure with default values to replicate C++ deserialization behavior
- Index = -1;
- }
-
///
/// Compares two tags.
///
@@ -50,6 +46,28 @@ namespace FlaxEngine
return left.Index == right.Index;
}
+ ///
+ /// Compares two tags.
+ ///
+ /// The lft tag.
+ /// The right tag name.
+ /// True if both values are equal, otherwise false.
+ public static bool operator ==(Tag left, string right)
+ {
+ return string.Equals(left.ToString(), right, StringComparison.Ordinal);
+ }
+
+ ///
+ /// Compares two tags.
+ ///
+ /// The lft tag.
+ /// The right tag name.
+ /// True if both values are not equal, otherwise false.
+ public static bool operator !=(Tag left, string right)
+ {
+ return !string.Equals(left.ToString(), right, StringComparison.Ordinal);
+ }
+
///
/// Checks if tag is valid.
///
@@ -58,7 +76,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator bool(Tag tag)
{
- return tag.Index != -1;
+ return tag.Index != 0;
}
///
@@ -104,7 +122,7 @@ namespace FlaxEngine
///
public override int GetHashCode()
{
- return Index;
+ return (int)Index;
}
///
@@ -124,7 +142,7 @@ namespace FlaxEngine
/// True if given tag is contained by the list of tags. Returns false for empty list.
public static bool HasTag(this Tag[] list, Tag tag)
{
- if (tag.Index == -1)
+ if (tag.Index == 0)
return false;
string tagName = tag.ToString();
foreach (Tag e in list)
@@ -144,7 +162,7 @@ namespace FlaxEngine
/// True if given tag is contained by the list of tags. Returns false for empty list.
public static bool HasTagExact(this Tag[] list, Tag tag)
{
- if (tag.Index == -1)
+ if (tag.Index == 0)
return false;
if (list == null)
return false;
@@ -233,3 +251,40 @@ namespace FlaxEngine
}
}
}
+
+namespace FlaxEngine.TypeConverters
+{
+ internal class TagConverter : TypeConverter
+ {
+ ///
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+ {
+ if (sourceType == typeof(string))
+ return true;
+ return base.CanConvertFrom(context, sourceType);
+ }
+
+ ///
+ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+ {
+ if (value is string str)
+ {
+ if (str.Length == 0)
+ return Tag.Default;
+ return Tags.Get(str);
+ }
+ return base.ConvertFrom(context, culture, value);
+ }
+
+ ///
+ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
+ {
+ if (destinationType == typeof(string))
+ {
+ var v = (Tag)value;
+ return v.ToString();
+ }
+ return base.ConvertTo(context, culture, value, destinationType);
+ }
+ }
+}
diff --git a/Source/Engine/Level/Tags.h b/Source/Engine/Level/Tags.h
index eda44e224..fac49598d 100644
--- a/Source/Engine/Level/Tags.h
+++ b/Source/Engine/Level/Tags.h
@@ -14,9 +14,9 @@ API_STRUCT(NoDefault) struct FLAXENGINE_API Tag
DECLARE_SCRIPTING_TYPE_MINIMAL(Tag);
///
- /// Index of the tag (in global Level.Tags list).
+ /// Index of the tag (in global Level.Tags list). Index 0 is invalid. 1 is the first index.
///
- API_FIELD() int32 Index = -1;
+ API_FIELD() uint32 Index = 0;
///
/// Gets the tag name.
@@ -26,14 +26,14 @@ API_STRUCT(NoDefault) struct FLAXENGINE_API Tag
public:
Tag() = default;
- FORCE_INLINE Tag(int32 index)
+ FORCE_INLINE explicit Tag(uint32 index)
: Index(index)
{
}
FORCE_INLINE operator bool() const
{
- return Index != -1;
+ return Index != 0;
}
FORCE_INLINE bool operator==(const Tag& other) const
@@ -142,7 +142,7 @@ public:
static bool HasAllExact(const Array& list, const Array& tags);
private:
- API_FUNCTION(NoProxy) static const String& GetTagName(int32 tag);
+ API_FUNCTION(NoProxy) static const String& GetTagName(uint32 tag);
};
#if !BUILD_RELEASE
diff --git a/Source/Engine/Physics/PhysicalMaterial.h b/Source/Engine/Physics/PhysicalMaterial.h
index b87cb5035..8716833a7 100644
--- a/Source/Engine/Physics/PhysicalMaterial.h
+++ b/Source/Engine/Physics/PhysicalMaterial.h
@@ -4,6 +4,7 @@
#include "Types.h"
#include "Engine/Core/ISerializable.h"
+#include "Engine/Level/Tags.h"
///
/// Physical materials are used to define the response of a physical object when interacting dynamically with the world.
@@ -69,6 +70,12 @@ public:
API_FIELD(Attributes="EditorOrder(10), EditorDisplay(\"Physical Material\")")
float Density = 1000.0f;
+ ///
+ /// Physical material tag used to identify it (eg. `Surface.Wood`). Can be used to play proper footstep sounds when walking over object with that material.
+ ///
+ API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"Physical Material\")")
+ Tag Tag;
+
public:
///
/// Gets the PhysX material.
diff --git a/Source/Engine/Scripting/ScriptingObject.h b/Source/Engine/Scripting/ScriptingObject.h
index ea4409c3a..bbb6c5b47 100644
--- a/Source/Engine/Scripting/ScriptingObject.h
+++ b/Source/Engine/Scripting/ScriptingObject.h
@@ -119,6 +119,11 @@ public:
// Tries to cast native interface object to scripting object instance. Returns null if fails.
static ScriptingObject* FromInterface(void* interfaceObj, const ScriptingTypeHandle& interfaceType);
+ template
+ static ScriptingObject* FromInterface(T* interfaceObj)
+ {
+ return FromInterface(interfaceObj, T::TypeInitializer);
+ }
static void* ToInterface(ScriptingObject* obj, const ScriptingTypeHandle& interfaceType);
template
static T* ToInterface(ScriptingObject* obj)
diff --git a/Source/Engine/Tests/TestScripting.cpp b/Source/Engine/Tests/TestScripting.cpp
index ecea80db7..29c7d5eb5 100644
--- a/Source/Engine/Tests/TestScripting.cpp
+++ b/Source/Engine/Tests/TestScripting.cpp
@@ -21,7 +21,9 @@ TEST_CASE("Scripting")
CHECK(object->Is());
TestClassNative* testClass = (TestClassNative*)object;
CHECK(testClass->SimpleField == 1);
- int32 methodResult = testClass->Test(TEXT("123"));
+ CHECK(testClass->SimpleStruct.Object == nullptr);
+ CHECK(testClass->SimpleStruct.Vector == Float3::One);
+ int32 methodResult = testClass->TestMethod(TEXT("123"));
CHECK(methodResult == 3);
// Test managed class
@@ -34,7 +36,80 @@ TEST_CASE("Scripting")
MObject* managed = testClass->GetOrCreateManagedInstance(); // Ensure to create C# object and run it's ctor
CHECK(managed);
CHECK(testClass->SimpleField == 2);
- methodResult = testClass->Test(TEXT("123"));
+ CHECK(testClass->SimpleStruct.Object == testClass);
+ CHECK(testClass->SimpleStruct.Vector == Float3::UnitX);
+ methodResult = testClass->TestMethod(TEXT("123"));
CHECK(methodResult == 6);
}
+
+ SECTION("Test Event")
+ {
+ ScriptingTypeHandle type = Scripting::FindScriptingType("FlaxEngine.TestClassManaged");
+ CHECK(type);
+ ScriptingObject* object = Scripting::NewObject(type.GetType().ManagedClass);
+ CHECK(object);
+ MObject* managed = object->GetOrCreateManagedInstance(); // Ensure to create C# object and run it's ctor
+ CHECK(managed);
+ TestClassNative* testClass = (TestClassNative*)object;
+ CHECK(testClass->SimpleField == 2);
+ String str1 = TEXT("1");
+ String str2 = TEXT("2");
+ Array arr1 = { testClass->SimpleStruct };
+ Array arr2 = { testClass->SimpleStruct };
+ testClass->SimpleEvent(1, Float3::One, str1, str2, arr1, arr2);
+ CHECK(testClass->SimpleField == 4);
+ CHECK(str2 == TEXT("4"));
+ CHECK(arr2.Count() == 2);
+ CHECK(arr2[0].Vector == Float3::Half);
+ CHECK(arr2[0].Object == nullptr);
+ CHECK(arr2[1].Vector == testClass->SimpleStruct.Vector);
+ CHECK(arr2[1].Object == testClass);
+ }
+ SECTION("Test Interface")
+ {
+ // Test native interface implementation
+ ScriptingTypeHandle type = Scripting::FindScriptingType("FlaxEngine.TestClassNative");
+ CHECK(type);
+ ScriptingObject* object = Scripting::NewObject(type.GetType().ManagedClass);
+ CHECK(object);
+ TestClassNative* testClass = (TestClassNative*)object;
+ int32 methodResult = testClass->TestInterfaceMethod(TEXT("123"));
+ CHECK(methodResult == 3);
+ ITestInterface* interface = ScriptingObject::ToInterface(object);
+ CHECK(interface);
+ methodResult = interface->TestInterfaceMethod(TEXT("1234"));
+ CHECK(methodResult == 4);
+ ScriptingObject* interfaceObject = ScriptingObject::FromInterface(interface);
+ CHECK(interfaceObject);
+ CHECK(interfaceObject == object);
+
+ // Test managed interface override
+ type = Scripting::FindScriptingType("FlaxEngine.TestClassManaged");
+ CHECK(type);
+ object = Scripting::NewObject(type.GetType().ManagedClass);
+ CHECK(object);
+ testClass = (TestClassNative*)object;
+ methodResult = testClass->TestInterfaceMethod(TEXT("123"));
+ CHECK(methodResult == 6);
+ interface = ScriptingObject::ToInterface(object);
+ CHECK(interface);
+ methodResult = interface->TestInterfaceMethod(TEXT("1234"));
+ CHECK(methodResult == 8);
+ interfaceObject = ScriptingObject::FromInterface(interface);
+ CHECK(interfaceObject);
+ CHECK(interfaceObject == object);
+
+ // Test managed interface implementation
+ type = Scripting::FindScriptingType("FlaxEngine.TestInterfaceManaged");
+ CHECK(type);
+ object = Scripting::NewObject(type.GetType().ManagedClass);
+ CHECK(object);
+ interface = ScriptingObject::ToInterface(object);
+ CHECK(interface);
+ methodResult = interface->TestInterfaceMethod(TEXT("1234"));
+ CHECK(methodResult == 4);
+ interfaceObject = ScriptingObject::FromInterface(interface);
+ CHECK(interfaceObject);
+ CHECK(interfaceObject == object);
+ }
}
diff --git a/Source/Engine/Tests/TestScripting.cs b/Source/Engine/Tests/TestScripting.cs
index 6abc667a1..6203de314 100644
--- a/Source/Engine/Tests/TestScripting.cs
+++ b/Source/Engine/Tests/TestScripting.cs
@@ -3,6 +3,48 @@
#if FLAX_TESTS
namespace FlaxEngine
{
+ partial struct TestStruct : System.IEquatable
+ {
+ ///
+ public static bool operator ==(TestStruct left, TestStruct right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ public static bool operator !=(TestStruct left, TestStruct right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public bool Equals(TestStruct other)
+ {
+ return Vector.Equals(other.Vector) && Equals(Object, other.Object);
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ return obj is TestStruct other && Equals(other);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (Vector.GetHashCode() * 397) ^ (Object != null ? Object.GetHashCode() : 0);
+ }
+ }
+
+ ///
+ public override string ToString()
+ {
+ return $"Vector={Vector}, Object={Object?.ToString() ?? "null"}";
+ }
+ }
+
///
/// Test class.
///
@@ -10,13 +52,65 @@ namespace FlaxEngine
{
TestClassManaged()
{
+ // Test setting C++ values from C#
SimpleField = 2;
+ SimpleStruct = new TestStruct
+ {
+ Vector = Float3.UnitX,
+ Object = this,
+ };
+ SimpleEvent += OnSimpleEvent;
}
///
- public override int Test(string str)
+ public override int TestMethod(string str)
{
- return str.Length + base.Test(str);
+ // Test C++ base method invocation
+ return str.Length + base.TestMethod(str);
+ }
+
+ ///
+ public override int TestInterfaceMethod(string str)
+ {
+ // Test C++ base method invocation
+ return str.Length + base.TestInterfaceMethod(str);
+ }
+
+ private void OnSimpleEvent(int arg1, Float3 arg2, string arg3, ref string arg4, TestStruct[] arg5, ref TestStruct[] arg6)
+ {
+ // Verify that C++ passed proper data to C# via event bindings
+ if (arg1 == 1 &&
+ arg2 == Float3.One &&
+ arg3 == "1" &&
+ arg4 == "2" &&
+ arg5 != null && arg5.Length == 1 && arg5[0] == SimpleStruct &&
+ arg6 != null && arg6.Length == 1 && arg6[0] == SimpleStruct)
+ {
+ // Test passing data back from C# to C++
+ SimpleField = 4;
+ arg4 = "4";
+ arg6 = new TestStruct[2]
+ {
+ new TestStruct
+ {
+ Vector = Float3.Half,
+ Object = null,
+ },
+ SimpleStruct,
+ };
+ }
+ }
+ }
+
+ ///
+ /// Test interface in C#.
+ ///
+ public class TestInterfaceManaged : Object, ITestInterface
+ {
+ ///
+ public int TestInterfaceMethod(string str)
+ {
+ return str.Length;
}
}
}
diff --git a/Source/Engine/Tests/TestScripting.h b/Source/Engine/Tests/TestScripting.h
index 06b5dddb5..c5ba39b10 100644
--- a/Source/Engine/Tests/TestScripting.h
+++ b/Source/Engine/Tests/TestScripting.h
@@ -3,10 +3,34 @@
#pragma once
#include "Engine/Core/ISerializable.h"
+#include "Engine/Core/Math/Vector3.h"
+#include "Engine/Core/Collections/Array.h"
#include "Engine/Scripting/ScriptingObject.h"
+// Test structure.
+API_STRUCT(NoDefault) struct TestStruct : public ISerializable
+{
+ API_AUTO_SERIALIZATION();
+ DECLARE_SCRIPTING_TYPE_MINIMAL(TestStruct);
+
+ // Var
+ API_FIELD() Float3 Vector = Float3::One;
+ // Ref
+ API_FIELD() ScriptingObject* Object = nullptr;
+};
+
+// Test interface.
+API_INTERFACE() class ITestInterface
+{
+ DECLARE_SCRIPTING_TYPE_MINIMAL(ITestInterface);
+ ~ITestInterface() = default;
+
+ // Test abstract method
+ API_FUNCTION() virtual int32 TestInterfaceMethod(const String& str) = 0;
+};
+
// Test class.
-API_CLASS() class TestClassNative : public ScriptingObject, public ISerializable
+API_CLASS() class TestClassNative : public ScriptingObject, public ISerializable, public ITestInterface
{
API_AUTO_SERIALIZATION();
DECLARE_SCRIPTING_TYPE(TestClassNative);
@@ -15,8 +39,19 @@ public:
// Test value
API_FIELD() int32 SimpleField = 1;
+ // Test struct
+ API_FIELD() TestStruct SimpleStruct;
+
+ // Test event
+ API_EVENT() Delegate&, Array&> SimpleEvent;
+
// Test virtual method
- API_FUNCTION() virtual int32 Test(const String& str)
+ API_FUNCTION() virtual int32 TestMethod(const String& str)
+ {
+ return str.Length();
+ }
+
+ int32 TestInterfaceMethod(const String& str) override
{
return str.Length();
}
diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
index 5a62096c3..811d334e5 100644
--- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
+++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
@@ -99,14 +99,15 @@ namespace Flax.Build.Bindings
return sb.ToString();
}
- private static string GenerateCppWrapperNativeToManagedParam(BuildData buildData, StringBuilder contents, TypeInfo paramType, string paramName, ApiTypeInfo caller, bool isOut)
+ private static string GenerateCppWrapperNativeToManagedParam(BuildData buildData, StringBuilder contents, TypeInfo paramType, string paramName, ApiTypeInfo caller, bool isOut, out bool useLocalVar)
{
+ useLocalVar = false;
var nativeToManaged = GenerateCppWrapperNativeToManaged(buildData, paramType, caller, out var managedTypeAsNative, null);
string result;
if (!string.IsNullOrEmpty(nativeToManaged))
{
result = string.Format(nativeToManaged, paramName);
- if (managedTypeAsNative[managedTypeAsNative.Length - 1] == '*')
+ if (managedTypeAsNative[managedTypeAsNative.Length - 1] == '*' && !isOut)
{
// Pass pointer value
}
@@ -117,6 +118,7 @@ namespace Flax.Build.Bindings
result = string.Format(nativeToManaged, '*' + paramName);
contents.Append($" auto __param_{paramName} = {result};").AppendLine();
result = $"&__param_{paramName}";
+ useLocalVar = true;
}
}
else
@@ -588,12 +590,12 @@ namespace Flax.Build.Bindings
}
}
- private static string GenerateCppWrapperManagedToNative(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, out string type, FunctionInfo functionInfo, out bool needLocalVariable)
+ private static string GenerateCppWrapperManagedToNative(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, out string type, out ApiTypeInfo apiType, FunctionInfo functionInfo, out bool needLocalVariable)
{
needLocalVariable = false;
// Register any API types usage
- var apiType = FindApiTypeInfo(buildData, typeInfo, caller);
+ apiType = FindApiTypeInfo(buildData, typeInfo, caller);
CppReferencesFiles.Add(apiType?.File);
if (typeInfo.GenericArgs != null)
{
@@ -608,7 +610,7 @@ namespace Flax.Build.Bindings
if (typeInfo.IsArray)
{
var arrayType = new TypeInfo { Type = "Array", GenericArgs = new List { new TypeInfo(typeInfo) { IsArray = false } } };
- var result = GenerateCppWrapperManagedToNative(buildData, arrayType, caller, out type, functionInfo, out needLocalVariable);
+ var result = GenerateCppWrapperManagedToNative(buildData, arrayType, caller, out type, out _, functionInfo, out needLocalVariable);
return result + ".Get()";
}
@@ -967,7 +969,7 @@ namespace Flax.Build.Bindings
separator = true;
CppParamsThatNeedConversion[i] = false;
- CppParamsWrappersCache[i] = GenerateCppWrapperManagedToNative(buildData, parameterInfo.Type, caller, out var managedType, functionInfo, out CppParamsThatNeedLocalVariable[i]);
+ CppParamsWrappersCache[i] = GenerateCppWrapperManagedToNative(buildData, parameterInfo.Type, caller, out var managedType, out var apiType, functionInfo, out CppParamsThatNeedLocalVariable[i]);
// Out parameters that need additional converting will be converted at the native side (eg. object reference)
var isOutWithManagedConverter = parameterInfo.IsOut && !string.IsNullOrEmpty(GenerateCSharpManagedToNativeConverter(buildData, parameterInfo.Type, caller));
@@ -985,7 +987,6 @@ namespace Flax.Build.Bindings
if (parameterInfo.IsOut || isRefOut)
{
bool convertOutputParameter = false;
- var apiType = FindApiTypeInfo(buildData, parameterInfo.Type, caller);
if (apiType != null)
{
// Non-POD structure passed as value (eg. it contains string or array inside)
@@ -1038,7 +1039,7 @@ namespace Flax.Build.Bindings
contents.Append(", ");
separator = true;
- GenerateCppWrapperManagedToNative(buildData, parameterInfo.Type, caller, out var managedType, functionInfo, out _);
+ GenerateCppWrapperManagedToNative(buildData, parameterInfo.Type, caller, out var managedType, out _, functionInfo, out _);
contents.Append(managedType);
if (parameterInfo.IsRef || parameterInfo.IsOut || UsePassByReference(buildData, parameterInfo.Type, caller))
contents.Append('*');
@@ -1336,7 +1337,7 @@ namespace Flax.Build.Bindings
for (var i = 0; i < functionInfo.Parameters.Count; i++)
{
var parameterInfo = functionInfo.Parameters[i];
- var paramValue = GenerateCppWrapperNativeToManagedParam(buildData, contents, parameterInfo.Type, parameterInfo.Name, classInfo, parameterInfo.IsOut);
+ var paramValue = GenerateCppWrapperNativeToManagedParam(buildData, contents, parameterInfo.Type, parameterInfo.Name, classInfo, parameterInfo.IsOut, out _);
contents.Append($" params[{i}] = {paramValue};").AppendLine();
}
@@ -1728,7 +1729,8 @@ namespace Flax.Build.Bindings
{
var paramType = eventInfo.Type.GenericArgs[i];
var paramName = "arg" + i;
- var paramValue = GenerateCppWrapperNativeToManagedParam(buildData, contents, paramType, paramName, classInfo, false);
+ var paramIsOut = paramType.IsRef && !paramType.IsConst;
+ var paramValue = GenerateCppWrapperNativeToManagedParam(buildData, contents, paramType, paramName, classInfo, paramIsOut, out CppParamsThatNeedConversion[i]);
contents.Append($" params[{i}] = {paramValue};").AppendLine();
}
if (eventInfo.IsStatic)
@@ -1741,13 +1743,15 @@ namespace Flax.Build.Bindings
for (var i = 0; i < paramsCount; i++)
{
var paramType = eventInfo.Type.GenericArgs[i];
- if (paramType.IsRef && !paramType.IsConst)
+ var paramIsOut = paramType.IsRef && !paramType.IsConst;
+ if (paramIsOut)
{
// Convert value back from managed to native (could be modified there)
paramType.IsRef = false;
- var managedToNative = GenerateCppWrapperManagedToNative(buildData, paramType, classInfo, out var managedType, null, out _);
+ var managedToNative = GenerateCppWrapperManagedToNative(buildData, paramType, classInfo, out var managedType, out var apiType, null, out _);
var passAsParamPtr = managedType.EndsWith("*");
- var paramValue = $"({managedType}{(passAsParamPtr ? "" : "*")})params[{i}]";
+ var useLocalVarPointer = CppParamsThatNeedConversion[i] && !apiType.IsValueType;
+ var paramValue = useLocalVarPointer ? $"*({managedType}{(passAsParamPtr ? "" : "*")}*)params[{i}]" : $"({managedType}{(passAsParamPtr ? "" : "*")})params[{i}]";
if (!string.IsNullOrEmpty(managedToNative))
{
if (!passAsParamPtr)
@@ -2468,7 +2472,7 @@ namespace Flax.Build.Bindings
if (typeInfo.IsArray)
{
typeInfo.IsArray = false;
- header.Append($"{GenerateCppWrapperNativeToVariantMethodName(typeInfo)}Array(const {typeInfo}* v, const int32 length)").AppendLine();
+ header.Append($"{GenerateCppWrapperNativeToVariantMethodName(typeInfo)}Array({(typeInfo.IsConst ? "const " : "")}{typeInfo}* v, const int32 length)").AppendLine();
header.Append('{').AppendLine();
header.Append(" Variant result;").AppendLine();
header.Append(" result.SetType(VariantType(VariantType::Array));").AppendLine();
@@ -2481,7 +2485,7 @@ namespace Flax.Build.Bindings
else if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null)
{
var valueType = typeInfo.GenericArgs[0];
- header.Append($"{GenerateCppWrapperNativeToVariantMethodName(valueType)}Array(const {valueType}* v, const int32 length)").AppendLine();
+ header.Append($"{GenerateCppWrapperNativeToVariantMethodName(valueType)}Array({(typeInfo.IsConst ? "const " : "")}{valueType}* v, const int32 length)").AppendLine();
header.Append('{').AppendLine();
header.Append(" Variant result;").AppendLine();
header.Append(" result.SetType(VariantType(VariantType::Array));").AppendLine();
@@ -2646,7 +2650,7 @@ namespace Flax.Build.Bindings
continue;
CppNonPodTypesConvertingGeneration = true;
- var wrapper = GenerateCppWrapperManagedToNative(buildData, fieldInfo.Type, apiType, out _, null, out _);
+ var wrapper = GenerateCppWrapperManagedToNative(buildData, fieldInfo.Type, apiType, out _, out _, null, out _);
CppNonPodTypesConvertingGeneration = false;
if (fieldInfo.Type.IsArray)
@@ -2730,7 +2734,7 @@ namespace Flax.Build.Bindings
continue;
CppNonPodTypesConvertingGeneration = true;
- var wrapper = GenerateCppWrapperManagedToNative(buildData, fieldInfo.Type, apiType, out _, null, out _);
+ var wrapper = GenerateCppWrapperManagedToNative(buildData, fieldInfo.Type, apiType, out _, out _, null, out _);
CppNonPodTypesConvertingGeneration = false;
if (fieldInfo.Type.IsArray)