diff --git a/Source/Editor/Content/Create/ParticleEmitterCreateEntry.cs b/Source/Editor/Content/Create/ParticleEmitterCreateEntry.cs
index 1be5330e7..76edde777 100644
--- a/Source/Editor/Content/Create/ParticleEmitterCreateEntry.cs
+++ b/Source/Editor/Content/Create/ParticleEmitterCreateEntry.cs
@@ -81,7 +81,7 @@ namespace FlaxEditor.Content.Create
switch (_options.Template)
{
case Templates.Empty:
- return Editor.CreateAsset(Editor.NewAssetType.ParticleEmitter, ResultUrl);
+ return Editor.CreateAsset("ParticleEmitter", ResultUrl);
case Templates.ConstantBurst:
templateName = "Constant Burst";
break;
diff --git a/Source/Editor/Content/Proxy/AnimationGraphFunctionProxy.cs b/Source/Editor/Content/Proxy/AnimationGraphFunctionProxy.cs
index f714ddbeb..7efc02368 100644
--- a/Source/Editor/Content/Proxy/AnimationGraphFunctionProxy.cs
+++ b/Source/Editor/Content/Proxy/AnimationGraphFunctionProxy.cs
@@ -38,7 +38,7 @@ namespace FlaxEditor.Content
///
public override void Create(string outputPath, object arg)
{
- if (Editor.CreateAsset(Editor.NewAssetType.AnimationGraphFunction, outputPath))
+ if (Editor.CreateAsset("AnimationGraphFunction", outputPath))
throw new Exception("Failed to create new asset.");
}
}
diff --git a/Source/Editor/Content/Proxy/AnimationGraphProxy.cs b/Source/Editor/Content/Proxy/AnimationGraphProxy.cs
index 3e6c35c6d..20d3c5a2c 100644
--- a/Source/Editor/Content/Proxy/AnimationGraphProxy.cs
+++ b/Source/Editor/Content/Proxy/AnimationGraphProxy.cs
@@ -38,7 +38,7 @@ namespace FlaxEditor.Content
///
public override void Create(string outputPath, object arg)
{
- if (Editor.CreateAsset(Editor.NewAssetType.AnimationGraph, outputPath))
+ if (Editor.CreateAsset("AnimationGraph", outputPath))
throw new Exception("Failed to create new asset.");
}
}
diff --git a/Source/Editor/Content/Proxy/AnimationProxy.cs b/Source/Editor/Content/Proxy/AnimationProxy.cs
index 2cf46d3a3..6636fccb8 100644
--- a/Source/Editor/Content/Proxy/AnimationProxy.cs
+++ b/Source/Editor/Content/Proxy/AnimationProxy.cs
@@ -47,7 +47,7 @@ namespace FlaxEditor.Content
///
public override void Create(string outputPath, object arg)
{
- if (Editor.CreateAsset(Editor.NewAssetType.Animation, outputPath))
+ if (Editor.CreateAsset("Animation", outputPath))
throw new Exception("Failed to create new asset.");
}
diff --git a/Source/Editor/Content/Proxy/BehaviorTreeProxy.cs b/Source/Editor/Content/Proxy/BehaviorTreeProxy.cs
index 33ad0862f..234dfaf7d 100644
--- a/Source/Editor/Content/Proxy/BehaviorTreeProxy.cs
+++ b/Source/Editor/Content/Proxy/BehaviorTreeProxy.cs
@@ -47,7 +47,7 @@ namespace FlaxEditor.Content
///
public override void Create(string outputPath, object arg)
{
- if (Editor.CreateAsset(Editor.NewAssetType.BehaviorTree, outputPath))
+ if (Editor.CreateAsset("BehaviorTree", outputPath))
throw new Exception("Failed to create new asset.");
}
diff --git a/Source/Editor/Content/Proxy/CollisionDataProxy.cs b/Source/Editor/Content/Proxy/CollisionDataProxy.cs
index 55e8c6327..df26dca75 100644
--- a/Source/Editor/Content/Proxy/CollisionDataProxy.cs
+++ b/Source/Editor/Content/Proxy/CollisionDataProxy.cs
@@ -71,7 +71,7 @@ namespace FlaxEditor.Content
///
public override void Create(string outputPath, object arg)
{
- if (Editor.CreateAsset(Editor.NewAssetType.CollisionData, outputPath))
+ if (Editor.CreateAsset("CollisionData", outputPath))
throw new Exception("Failed to create new asset.");
}
diff --git a/Source/Editor/Content/Proxy/MaterialFunctionProxy.cs b/Source/Editor/Content/Proxy/MaterialFunctionProxy.cs
index ab59f11f3..ca012f70a 100644
--- a/Source/Editor/Content/Proxy/MaterialFunctionProxy.cs
+++ b/Source/Editor/Content/Proxy/MaterialFunctionProxy.cs
@@ -38,7 +38,7 @@ namespace FlaxEditor.Content
///
public override void Create(string outputPath, object arg)
{
- if (Editor.CreateAsset(Editor.NewAssetType.MaterialFunction, outputPath))
+ if (Editor.CreateAsset("MaterialFunction", outputPath))
throw new Exception("Failed to create new asset.");
}
}
diff --git a/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs b/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs
index 331ff81c3..cd245b149 100644
--- a/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs
+++ b/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs
@@ -43,7 +43,7 @@ namespace FlaxEditor.Content
///
public override void Create(string outputPath, object arg)
{
- if (Editor.CreateAsset(Editor.NewAssetType.MaterialInstance, outputPath))
+ if (Editor.CreateAsset("MaterialInstance", outputPath))
throw new Exception("Failed to create new asset.");
}
diff --git a/Source/Editor/Content/Proxy/MaterialProxy.cs b/Source/Editor/Content/Proxy/MaterialProxy.cs
index a7fcfecc8..f7db2c83d 100644
--- a/Source/Editor/Content/Proxy/MaterialProxy.cs
+++ b/Source/Editor/Content/Proxy/MaterialProxy.cs
@@ -44,7 +44,7 @@ namespace FlaxEditor.Content
///
public override void Create(string outputPath, object arg)
{
- if (Editor.CreateAsset(Editor.NewAssetType.Material, outputPath))
+ if (Editor.CreateAsset("Material", outputPath))
throw new Exception("Failed to create new asset.");
}
diff --git a/Source/Editor/Content/Proxy/ParticleEmitterFunctionProxy.cs b/Source/Editor/Content/Proxy/ParticleEmitterFunctionProxy.cs
index 3a2ed749f..aaf9445e2 100644
--- a/Source/Editor/Content/Proxy/ParticleEmitterFunctionProxy.cs
+++ b/Source/Editor/Content/Proxy/ParticleEmitterFunctionProxy.cs
@@ -38,7 +38,7 @@ namespace FlaxEditor.Content
///
public override void Create(string outputPath, object arg)
{
- if (Editor.CreateAsset(Editor.NewAssetType.ParticleEmitterFunction, outputPath))
+ if (Editor.CreateAsset("ParticleEmitterFunction", outputPath))
throw new Exception("Failed to create new asset.");
}
}
diff --git a/Source/Editor/Content/Proxy/ParticleSystemProxy.cs b/Source/Editor/Content/Proxy/ParticleSystemProxy.cs
index 047853f0b..74f391513 100644
--- a/Source/Editor/Content/Proxy/ParticleSystemProxy.cs
+++ b/Source/Editor/Content/Proxy/ParticleSystemProxy.cs
@@ -75,7 +75,7 @@ namespace FlaxEditor.Content
///
public override void Create(string outputPath, object arg)
{
- if (Editor.CreateAsset(Editor.NewAssetType.ParticleSystem, outputPath))
+ if (Editor.CreateAsset("ParticleSystem", outputPath))
throw new Exception("Failed to create new asset.");
}
diff --git a/Source/Editor/Content/Proxy/SceneAnimationProxy.cs b/Source/Editor/Content/Proxy/SceneAnimationProxy.cs
index fe31f0e34..5f7fa800a 100644
--- a/Source/Editor/Content/Proxy/SceneAnimationProxy.cs
+++ b/Source/Editor/Content/Proxy/SceneAnimationProxy.cs
@@ -69,7 +69,7 @@ namespace FlaxEditor.Content
///
public override void Create(string outputPath, object arg)
{
- if (Editor.CreateAsset(Editor.NewAssetType.SceneAnimation, outputPath))
+ if (Editor.CreateAsset("SceneAnimation", outputPath))
throw new Exception("Failed to create new asset.");
}
}
diff --git a/Source/Editor/Content/Proxy/SkeletonMaskProxy.cs b/Source/Editor/Content/Proxy/SkeletonMaskProxy.cs
index f300a4c61..560df0a7c 100644
--- a/Source/Editor/Content/Proxy/SkeletonMaskProxy.cs
+++ b/Source/Editor/Content/Proxy/SkeletonMaskProxy.cs
@@ -38,7 +38,7 @@ namespace FlaxEditor.Content
///
public override void Create(string outputPath, object arg)
{
- if (Editor.CreateAsset(Editor.NewAssetType.SkeletonMask, outputPath))
+ if (Editor.CreateAsset("SkeletonMask", outputPath))
throw new Exception("Failed to create new asset.");
}
}
diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs
index a519b1da1..6ae0f0562 100644
--- a/Source/Editor/CustomEditors/CustomEditor.cs
+++ b/Source/Editor/CustomEditors/CustomEditor.cs
@@ -543,10 +543,11 @@ namespace FlaxEditor.CustomEditors
try
{
string text;
+ var value = Values[0];
if (ParentEditor is Dedicated.ScriptsEditor)
{
// Script
- text = JsonSerializer.Serialize(Values[0]);
+ text = JsonSerializer.Serialize(value);
// Remove properties that should be ignored when copy/pasting data
if (text == null)
@@ -576,12 +577,12 @@ namespace FlaxEditor.CustomEditors
else if (ScriptType.FlaxObject.IsAssignableFrom(Values.Type))
{
// Object reference
- text = JsonSerializer.GetStringID(Values[0] as FlaxEngine.Object);
+ text = JsonSerializer.GetStringID(value as FlaxEngine.Object);
}
else
{
// Default
- text = JsonSerializer.Serialize(Values[0]);
+ text = JsonSerializer.Serialize(value, TypeUtils.GetType(Values.Type));
}
Clipboard.Text = text;
}
diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
index f783822a5..e1a358b6e 100644
--- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
@@ -591,14 +591,14 @@ namespace FlaxEditor.CustomEditors.Dedicated
var group = layout.Group(title, editor);
if ((Presenter.Features & FeatureFlags.CacheExpandedGroups) != 0)
{
- if (Editor.Instance.ProjectCache.IsCollapsedGroup(title))
- group.Panel.Close(false);
+ if (Editor.Instance.ProjectCache.IsGroupToggled(title))
+ group.Panel.Close();
else
- group.Panel.Open(false);
- group.Panel.IsClosedChanged += panel => Editor.Instance.ProjectCache.SetCollapsedGroup(panel.HeaderText, panel.IsClosed);
+ group.Panel.Open();
+ group.Panel.IsClosedChanged += panel => Editor.Instance.ProjectCache.SetGroupToggle(panel.HeaderText, panel.IsClosed);
}
else
- group.Panel.Open(false);
+ group.Panel.Open();
// Customize
group.Panel.TooltipText = Editor.Instance.CodeDocs.GetTooltip(scriptType);
diff --git a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs
index 1f3359fd5..db8ec1152 100644
--- a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs
@@ -51,10 +51,13 @@ namespace FlaxEditor.CustomEditors.Editors
return;
Picker = layout.Custom().CustomControl;
- _valueType = Values.Type.Type != typeof(object) || Values[0] == null ? Values.Type : TypeUtils.GetObjectType(Values[0]);
+ var value = Values[0];
+ _valueType = Values.Type.Type != typeof(object) || value == null ? Values.Type : TypeUtils.GetObjectType(value);
var assetType = _valueType;
if (assetType == typeof(string))
assetType = new ScriptType(typeof(Asset));
+ else if (_valueType.Type != null && _valueType.Type.Name == typeof(JsonAssetReference<>).Name)
+ assetType = new ScriptType(_valueType.Type.GenericTypeArguments[0]);
float height = 48;
var attributes = Values.GetAttributes();
@@ -102,6 +105,12 @@ namespace FlaxEditor.CustomEditors.Editors
SetValue(new SceneReference(Picker.Validator.SelectedID));
else if (_valueType.Type == typeof(string))
SetValue(Picker.Validator.SelectedPath);
+ else if (_valueType.Type.Name == typeof(JsonAssetReference<>).Name)
+ {
+ var value = Values[0];
+ value.GetType().GetField("Asset").SetValue(value, Picker.Validator.SelectedAsset as JsonAsset);
+ SetValue(value);
+ }
else
SetValue(Picker.Validator.SelectedAsset);
}
@@ -114,16 +123,19 @@ namespace FlaxEditor.CustomEditors.Editors
if (!HasDifferentValues)
{
_isRefreshing = true;
- if (Values[0] is AssetItem assetItem)
+ var value = Values[0];
+ if (value is AssetItem assetItem)
Picker.Validator.SelectedItem = assetItem;
- else if (Values[0] is Guid guid)
+ else if (value is Guid guid)
Picker.Validator.SelectedID = guid;
- else if (Values[0] is SceneReference sceneAsset)
+ else if (value is SceneReference sceneAsset)
Picker.Validator.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID);
- else if (Values[0] is string path)
+ else if (value is string path)
Picker.Validator.SelectedPath = path;
+ else if (value != null && value.GetType().Name == typeof(JsonAssetReference<>).Name)
+ Picker.Validator.SelectedAsset = value.GetType().GetField("Asset").GetValue(value) as JsonAsset;
else
- Picker.Validator.SelectedAsset = Values[0] as Asset;
+ Picker.Validator.SelectedAsset = value as Asset;
_isRefreshing = false;
}
}
diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
index 00322fc81..38dce0a25 100644
--- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
@@ -176,8 +176,8 @@ namespace FlaxEditor.CustomEditors.Editors
private IntValueBox _sizeBox;
private Color _background;
- private int _elementsCount;
- private bool _readOnly;
+ private int _elementsCount, _minCount, _maxCount;
+ private bool _canResize;
private bool _canReorderItems;
private CollectionAttribute.DisplayType _displayType;
@@ -209,8 +209,10 @@ namespace FlaxEditor.CustomEditors.Editors
return;
var size = Count;
- _readOnly = false;
+ _canResize = true;
_canReorderItems = true;
+ _minCount = 0;
+ _maxCount = 0;
_background = FlaxEngine.GUI.Style.Current.CollectionBackgroundColor;
_displayType = CollectionAttribute.DisplayType.Header;
NotNullItems = false;
@@ -222,7 +224,9 @@ namespace FlaxEditor.CustomEditors.Editors
var collection = (CollectionAttribute)attributes?.FirstOrDefault(x => x is CollectionAttribute);
if (collection != null)
{
- _readOnly = collection.ReadOnly;
+ _canResize = !collection.ReadOnly;
+ _minCount = collection.MinCount;
+ _maxCount = collection.MaxCount;
_canReorderItems = collection.CanReorderItems;
NotNullItems = collection.NotNullItems;
if (collection.BackgroundColor.HasValue)
@@ -231,6 +235,9 @@ namespace FlaxEditor.CustomEditors.Editors
spacing = collection.Spacing;
_displayType = collection.Display;
}
+ if (_maxCount == 0)
+ _maxCount = ushort.MaxValue;
+ _canResize &= _minCount < _maxCount;
var dragArea = layout.CustomContainer();
dragArea.CustomControl.Editor = this;
@@ -268,8 +275,8 @@ namespace FlaxEditor.CustomEditors.Editors
var y = -dropPanel.HeaderHeight + dropPanel.HeaderTextMargin.Top;
_sizeBox = new IntValueBox(size)
{
- MinValue = 0,
- MaxValue = ushort.MaxValue,
+ MinValue = _minCount,
+ MaxValue = _maxCount,
AnchorPreset = AnchorPresets.TopRight,
Bounds = new Rectangle(-40 - dropPanel.ItemsMargin.Right, y, 40, height),
Parent = dropPanel,
@@ -283,7 +290,7 @@ namespace FlaxEditor.CustomEditors.Editors
Parent = dropPanel
};
- if (_readOnly || (NotNullItems && size == 0))
+ if (!_canResize || (NotNullItems && size == 0))
{
_sizeBox.IsReadOnly = true;
_sizeBox.Enabled = false;
@@ -339,7 +346,7 @@ namespace FlaxEditor.CustomEditors.Editors
_elementsCount = size;
// Add/Remove buttons
- if (!_readOnly)
+ if (_canResize)
{
var panel = dragArea.HorizontalPanel();
panel.Panel.Size = new Float2(0, 20);
@@ -347,25 +354,23 @@ namespace FlaxEditor.CustomEditors.Editors
var removeButton = panel.Button("-", "Remove last item");
removeButton.Button.Size = new Float2(16, 16);
- removeButton.Button.Enabled = size > 0;
+ removeButton.Button.Enabled = size > _minCount;
removeButton.Button.AnchorPreset = AnchorPresets.TopRight;
removeButton.Button.Clicked += () =>
{
if (IsSetBlocked)
return;
-
Resize(Count - 1);
};
var addButton = panel.Button("+", "Add new item");
addButton.Button.Size = new Float2(16, 16);
- addButton.Button.Enabled = !NotNullItems || size > 0;
+ addButton.Button.Enabled = (!NotNullItems || size > 0) && size < _maxCount;
addButton.Button.AnchorPreset = AnchorPresets.TopRight;
addButton.Button.Clicked += () =>
{
if (IsSetBlocked)
return;
-
Resize(Count + 1);
};
}
diff --git a/Source/Editor/CustomEditors/LayoutElementsContainer.cs b/Source/Editor/CustomEditors/LayoutElementsContainer.cs
index 936851b15..e9a501bec 100644
--- a/Source/Editor/CustomEditors/LayoutElementsContainer.cs
+++ b/Source/Editor/CustomEditors/LayoutElementsContainer.cs
@@ -25,6 +25,11 @@ namespace FlaxEditor.CustomEditors
///
internal bool isRootGroup = true;
+ ///
+ /// Parent container who created this one.
+ ///
+ internal LayoutElementsContainer _parent;
+
///
/// The children.
///
@@ -40,6 +45,24 @@ namespace FlaxEditor.CustomEditors
///
public abstract ContainerControl ContainerControl { get; }
+ ///
+ /// Gets the Custom Editors layout presenter.
+ ///
+ internal CustomEditorPresenter Presenter
+ {
+ get
+ {
+ CustomEditorPresenter result;
+ var container = this;
+ do
+ {
+ result = container as CustomEditorPresenter;
+ container = container._parent;
+ } while (container != null);
+ return result;
+ }
+ }
+
///
/// Adds new group element.
///
@@ -81,17 +104,31 @@ namespace FlaxEditor.CustomEditors
public GroupElement Group(string title, bool useTransparentHeader = false)
{
var element = new GroupElement();
- if (!isRootGroup)
+ var presenter = Presenter;
+ var isSubGroup = !isRootGroup;
+ if (isSubGroup)
+ element.Panel.Close();
+ if (presenter != null && (presenter.Features & FeatureFlags.CacheExpandedGroups) != 0)
{
- element.Panel.Close(false);
- }
- else if (this is CustomEditorPresenter presenter && (presenter.Features & FeatureFlags.CacheExpandedGroups) != 0)
- {
- if (Editor.Instance.ProjectCache.IsCollapsedGroup(title))
- element.Panel.Close(false);
- element.Panel.IsClosedChanged += OnPanelIsClosedChanged;
+ // Build group identifier (made of path from group titles)
+ var expandPath = title;
+ var container = this;
+ while (container != null && !(container is CustomEditorPresenter))
+ {
+ if (container.ContainerControl is DropPanel dropPanel)
+ expandPath = dropPanel.HeaderText + "/" + expandPath;
+ container = container._parent;
+ }
+
+ // Caching/restoring expanded groups (non-root groups cache expanded state so invert boolean expression)
+ if (Editor.Instance.ProjectCache.IsGroupToggled(expandPath) ^ isSubGroup)
+ element.Panel.Close();
+ else
+ element.Panel.Open();
+ element.Panel.IsClosedChanged += panel => Editor.Instance.ProjectCache.SetGroupToggle(expandPath, panel.IsClosed ^ isSubGroup);
}
element.isRootGroup = false;
+ element._parent = this;
element.Panel.HeaderText = title;
if (useTransparentHeader)
{
@@ -103,11 +140,6 @@ namespace FlaxEditor.CustomEditors
return element;
}
- private void OnPanelIsClosedChanged(DropPanel panel)
- {
- Editor.Instance.ProjectCache.SetCollapsedGroup(panel.HeaderText, panel.IsClosed);
- }
-
///
/// Adds new horizontal panel element.
///
@@ -627,7 +659,6 @@ namespace FlaxEditor.CustomEditors
if (style == DisplayStyle.Group)
{
var group = Group(name, editor, true);
- group.Panel.Close(false);
group.Panel.TooltipText = tooltip;
return group.Object(values, editor);
}
@@ -657,7 +688,6 @@ namespace FlaxEditor.CustomEditors
if (style == DisplayStyle.Group)
{
var group = Group(label.Text, editor, true);
- group.Panel.Close(false);
group.Panel.TooltipText = tooltip;
return group.Object(values, editor);
}
diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs
index 393bf564e..266d8235f 100644
--- a/Source/Editor/Editor.cs
+++ b/Source/Editor/Editor.cs
@@ -869,7 +869,9 @@ namespace FlaxEditor
///
/// New asset types allowed to create.
+ /// [Deprecated in v1.8]
///
+ [Obsolete("Use CreateAsset with named tag.")]
public enum NewAssetType
{
///
@@ -1046,12 +1048,59 @@ namespace FlaxEditor
///
/// Creates new asset at the target location.
+ /// [Deprecated in v1.8]
///
/// New asset type.
/// Output asset path.
+ [Obsolete("Use CreateAsset with named tag.")]
public static bool CreateAsset(NewAssetType type, string outputPath)
{
- return Internal_CreateAsset(type, outputPath);
+ // [Deprecated on 18.02.2024, expires on 18.02.2025]
+ string tag;
+ switch (type)
+ {
+ case NewAssetType.Material:
+ tag = "Material";
+ break;
+ case NewAssetType.MaterialInstance:
+ tag = "MaterialInstance";
+ break;
+ case NewAssetType.CollisionData:
+ tag = "CollisionData";
+ break;
+ case NewAssetType.AnimationGraph:
+ tag = "AnimationGraph";
+ break;
+ case NewAssetType.SkeletonMask:
+ tag = "SkeletonMask";
+ break;
+ case NewAssetType.ParticleEmitter:
+ tag = "ParticleEmitter";
+ break;
+ case NewAssetType.ParticleSystem:
+ tag = "ParticleSystem";
+ break;
+ case NewAssetType.SceneAnimation:
+ tag = "SceneAnimation";
+ break;
+ case NewAssetType.MaterialFunction:
+ tag = "MaterialFunction";
+ break;
+ case NewAssetType.ParticleEmitterFunction:
+ tag = "ParticleEmitterFunction";
+ break;
+ case NewAssetType.AnimationGraphFunction:
+ tag = "AnimationGraphFunction";
+ break;
+ case NewAssetType.Animation:
+ tag = "Animation";
+ break;
+ case NewAssetType.BehaviorTree:
+ tag = "BehaviorTree";
+ break;
+ default: return true;
+ }
+ return CreateAsset(tag, outputPath);
}
///
@@ -1588,10 +1637,6 @@ namespace FlaxEditor
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CloseSplashScreen", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
internal static partial void Internal_CloseSplashScreen();
- [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CreateAsset", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
- [return: MarshalAs(UnmanagedType.U1)]
- internal static partial bool Internal_CreateAsset(NewAssetType type, string outputPath);
-
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CreateVisualScript", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
[return: MarshalAs(UnmanagedType.U1)]
internal static partial bool Internal_CreateVisualScript(string outputPath, string baseTypename);
diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp
index ae8b71ee2..53c0fbab2 100644
--- a/Source/Editor/Managed/ManagedEditor.Internal.cpp
+++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp
@@ -170,78 +170,6 @@ DEFINE_INTERNAL_CALL(bool) EditorInternal_CloneAssetFile(MString* dstPathObj, MS
return Content::CloneAssetFile(dstPath, srcPath, *dstId);
}
-enum class NewAssetType
-{
- Material = 0,
- MaterialInstance = 1,
- CollisionData = 2,
- AnimationGraph = 3,
- SkeletonMask = 4,
- ParticleEmitter = 5,
- ParticleSystem = 6,
- SceneAnimation = 7,
- MaterialFunction = 8,
- ParticleEmitterFunction = 9,
- AnimationGraphFunction = 10,
- Animation = 11,
- BehaviorTree = 12,
-};
-
-DEFINE_INTERNAL_CALL(bool) EditorInternal_CreateAsset(NewAssetType type, MString* outputPathObj)
-{
- String tag;
- switch (type)
- {
- case NewAssetType::Material:
- tag = AssetsImportingManager::CreateMaterialTag;
- break;
- case NewAssetType::MaterialInstance:
- tag = AssetsImportingManager::CreateMaterialInstanceTag;
- break;
- case NewAssetType::CollisionData:
- tag = AssetsImportingManager::CreateCollisionDataTag;
- break;
- case NewAssetType::AnimationGraph:
- tag = AssetsImportingManager::CreateAnimationGraphTag;
- break;
- case NewAssetType::SkeletonMask:
- tag = AssetsImportingManager::CreateSkeletonMaskTag;
- break;
- case NewAssetType::ParticleEmitter:
- tag = AssetsImportingManager::CreateParticleEmitterTag;
- break;
- case NewAssetType::ParticleSystem:
- tag = AssetsImportingManager::CreateParticleSystemTag;
- break;
- case NewAssetType::SceneAnimation:
- tag = AssetsImportingManager::CreateSceneAnimationTag;
- break;
- case NewAssetType::MaterialFunction:
- tag = AssetsImportingManager::CreateMaterialFunctionTag;
- break;
- case NewAssetType::ParticleEmitterFunction:
- tag = AssetsImportingManager::CreateParticleEmitterFunctionTag;
- break;
- case NewAssetType::AnimationGraphFunction:
- tag = AssetsImportingManager::CreateAnimationGraphFunctionTag;
- break;
- case NewAssetType::Animation:
- tag = AssetsImportingManager::CreateAnimationTag;
- break;
- case NewAssetType::BehaviorTree:
- tag = AssetsImportingManager::CreateBehaviorTreeTag;
- break;
- default:
- return true;
- }
-
- String outputPath;
- MUtils::ToString(outputPathObj, outputPath);
- FileSystem::NormalizePath(outputPath);
-
- return AssetsImportingManager::Create(tag, outputPath);
-}
-
DEFINE_INTERNAL_CALL(bool) EditorInternal_CreateVisualScript(MString* outputPathObj, MString* baseTypenameObj)
{
String outputPath;
@@ -634,13 +562,11 @@ bool ManagedEditor::Import(const String& inputPath, const String& outputPath, co
bool ManagedEditor::TryRestoreImportOptions(ModelTool::Options& options, String assetPath)
{
- // Initialize defaults
+ // Initialize defaults
if (const auto* graphicsSettings = GraphicsSettings::Get())
{
options.GenerateSDF = graphicsSettings->GenerateSDFOnModelImport;
}
-
- // Get options from model
FileSystem::NormalizePath(assetPath);
return ImportModel::TryGetImportOptions(assetPath, options);
}
@@ -652,7 +578,12 @@ bool ManagedEditor::Import(const String& inputPath, const String& outputPath, co
bool ManagedEditor::TryRestoreImportOptions(AudioTool::Options& options, String assetPath)
{
- // Get options from model
FileSystem::NormalizePath(assetPath);
return ImportAudio::TryGetImportOptions(assetPath, options);
}
+
+bool ManagedEditor::CreateAsset(const String& tag, String outputPath)
+{
+ FileSystem::NormalizePath(outputPath);
+ return AssetsImportingManager::Create(tag, outputPath);
+}
diff --git a/Source/Editor/Managed/ManagedEditor.h b/Source/Editor/Managed/ManagedEditor.h
index 8c9571cfd..6aa7514b0 100644
--- a/Source/Editor/Managed/ManagedEditor.h
+++ b/Source/Editor/Managed/ManagedEditor.h
@@ -210,6 +210,13 @@ public:
API_FUNCTION() static bool TryRestoreImportOptions(API_PARAM(Ref) AudioTool::Options& options, String assetPath);
#endif
+ ///
+ /// Creates a new asset at the target location.
+ ///
+ /// New asset type.
+ /// Output asset path.
+ API_FUNCTION() static bool CreateAsset(const String& tag, String outputPath);
+
public:
API_STRUCT(Internal, NoDefault) struct VisualScriptStackFrame
{
diff --git a/Source/Editor/Modules/ProjectCacheModule.cs b/Source/Editor/Modules/ProjectCacheModule.cs
index acb6e997e..eebea3ba0 100644
--- a/Source/Editor/Modules/ProjectCacheModule.cs
+++ b/Source/Editor/Modules/ProjectCacheModule.cs
@@ -18,7 +18,7 @@ namespace FlaxEditor.Modules
private DateTime _lastSaveTime;
private readonly HashSet _expandedActors = new HashSet();
- private readonly HashSet _collapsedGroups = new HashSet();
+ private readonly HashSet _toggledGroups = new HashSet();
private readonly Dictionary _customData = new Dictionary();
///
@@ -62,26 +62,26 @@ namespace FlaxEditor.Modules
}
///
- /// Determines whether group identified by the given title is collapsed in the UI.
+ /// Determines whether group identified by the given title is collapsed/opened in the UI.
///
/// The group title.
- /// true if group is collapsed; otherwise, false.
- public bool IsCollapsedGroup(string title)
+ /// true if group is toggled; otherwise, false.
+ public bool IsGroupToggled(string title)
{
- return _collapsedGroups.Contains(title);
+ return _toggledGroups.Contains(title);
}
///
- /// Sets the group collapsed cached value.
+ /// Sets the group collapsed/opened cached value.
///
/// The group title.
- /// If set to true group will be cached as an collapsed, otherwise false.
- public void SetCollapsedGroup(string title, bool isCollapsed)
+ /// If set to true group will be cached as a toggled, otherwise false.
+ public void SetGroupToggle(string title, bool isToggled)
{
- if (isCollapsed)
- _collapsedGroups.Add(title);
+ if (isToggled)
+ _toggledGroups.Add(title);
else
- _collapsedGroups.Remove(title);
+ _toggledGroups.Remove(title);
_isDirty = true;
}
@@ -160,7 +160,7 @@ namespace FlaxEditor.Modules
_expandedActors.Add(new Guid(bytes16));
}
- _collapsedGroups.Clear();
+ _toggledGroups.Clear();
_customData.Clear();
break;
@@ -176,7 +176,7 @@ namespace FlaxEditor.Modules
_expandedActors.Add(new Guid(bytes16));
}
- _collapsedGroups.Clear();
+ _toggledGroups.Clear();
_customData.Clear();
int customDataCount = reader.ReadInt32();
@@ -201,11 +201,9 @@ namespace FlaxEditor.Modules
}
int collapsedGroupsCount = reader.ReadInt32();
- _collapsedGroups.Clear();
+ _toggledGroups.Clear();
for (int i = 0; i < collapsedGroupsCount; i++)
- {
- _collapsedGroups.Add(reader.ReadString());
- }
+ _toggledGroups.Add(reader.ReadString());
_customData.Clear();
int customDataCount = reader.ReadInt32();
@@ -259,11 +257,9 @@ namespace FlaxEditor.Modules
writer.Write(e.ToByteArray());
}
- writer.Write(_collapsedGroups.Count);
- foreach (var e in _collapsedGroups)
- {
+ writer.Write(_toggledGroups.Count);
+ foreach (var e in _toggledGroups)
writer.Write(e);
- }
writer.Write(_customData.Count);
foreach (var e in _customData)
@@ -284,7 +280,6 @@ namespace FlaxEditor.Modules
try
{
SaveGuarded();
-
_isDirty = false;
}
catch (Exception ex)
diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs
index 970ca8e99..1657cb3a1 100644
--- a/Source/Editor/Modules/SceneEditingModule.cs
+++ b/Source/Editor/Modules/SceneEditingModule.cs
@@ -534,6 +534,41 @@ namespace FlaxEditor.Modules
Delete();
}
+ ///
+ /// Create parent for selected actors.
+ ///
+ public void CreateParentForSelectedActors()
+ {
+ Actor actor = new EmptyActor();
+ Editor.SceneEditing.Spawn(actor, null, false);
+ List selection = Editor.SceneEditing.Selection;
+ var actors = selection.Where(x => x is ActorNode).Select(x => ((ActorNode)x).Actor);
+ using (new UndoMultiBlock(Undo, actors, "Reparent actors"))
+ {
+ for (int i = 0; i < selection.Count; i++)
+ {
+ if (selection[i] is ActorNode node)
+ {
+ if (node.ParentNode != node.ParentScene) // If parent node is not a scene
+ {
+ if (selection.Contains(node.ParentNode))
+ {
+ continue; // If parent and child nodes selected together, don't touch child nodes
+ }
+
+ // Put created node as child of the Parent Node of node
+ int parentOrder = node.Actor.OrderInParent;
+ actor.Parent = node.Actor.Parent;
+ actor.OrderInParent = parentOrder;
+ }
+ node.Actor.Parent = actor;
+ }
+ }
+ }
+ Editor.SceneEditing.Select(actor);
+ Editor.Scene.GetActorNode(actor).TreeNode.StartRenaming(Editor.Windows.SceneWin, Editor.Windows.SceneWin.SceneTreePanel);
+ }
+
///
/// Duplicates the selected objects. Supports undo/redo.
///
diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs
index 3386d411c..4537d732e 100644
--- a/Source/Editor/Modules/UIModule.cs
+++ b/Source/Editor/Modules/UIModule.cs
@@ -50,6 +50,7 @@ namespace FlaxEditor.Modules
private ContextMenuButton _menuEditCut;
private ContextMenuButton _menuEditCopy;
private ContextMenuButton _menuEditPaste;
+ private ContextMenuButton _menuCreateParentForSelectedActors;
private ContextMenuButton _menuEditDelete;
private ContextMenuButton _menuEditDuplicate;
private ContextMenuButton _menuEditSelectAll;
@@ -548,11 +549,11 @@ namespace FlaxEditor.Modules
_menuEditCut = cm.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut);
_menuEditCopy = cm.AddButton("Copy", inputOptions.Copy, Editor.SceneEditing.Copy);
_menuEditPaste = cm.AddButton("Paste", inputOptions.Paste, Editor.SceneEditing.Paste);
- cm.AddSeparator();
_menuEditDelete = cm.AddButton("Delete", inputOptions.Delete, Editor.SceneEditing.Delete);
_menuEditDuplicate = cm.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate);
cm.AddSeparator();
_menuEditSelectAll = cm.AddButton("Select all", inputOptions.SelectAll, Editor.SceneEditing.SelectAllScenes);
+ _menuCreateParentForSelectedActors = cm.AddButton("Create parent for selected actors", Editor.SceneEditing.CreateParentForSelectedActors);
_menuEditFind = cm.AddButton("Find", inputOptions.Search, Editor.Windows.SceneWin.Search);
cm.AddSeparator();
cm.AddButton("Game Settings", () =>
@@ -858,6 +859,7 @@ namespace FlaxEditor.Modules
_menuEditCut.Enabled = hasSthSelected;
_menuEditCopy.Enabled = hasSthSelected;
_menuEditPaste.Enabled = canEditScene;
+ _menuCreateParentForSelectedActors.Enabled = canEditScene && hasSthSelected;
_menuEditDelete.Enabled = hasSthSelected;
_menuEditDuplicate.Enabled = hasSthSelected;
_menuEditSelectAll.Enabled = Level.IsAnySceneLoaded;
diff --git a/Source/Editor/SceneGraph/Actors/DirectionalLightNode.cs b/Source/Editor/SceneGraph/Actors/DirectionalLightNode.cs
index 5363d1f55..1eabc946d 100644
--- a/Source/Editor/SceneGraph/Actors/DirectionalLightNode.cs
+++ b/Source/Editor/SceneGraph/Actors/DirectionalLightNode.cs
@@ -23,7 +23,7 @@ namespace FlaxEditor.SceneGraph.Actors
base.OnDebugDraw(data);
var transform = Actor.Transform;
- DebugDraw.DrawWireArrow(transform.Translation, transform.Orientation, 1.0f, Color.Red, 0.0f, false);
+ DebugDraw.DrawWireArrow(transform.Translation, transform.Orientation, 1.0f, 0.5f, Color.Red, 0.0f, false);
}
}
}
diff --git a/Source/Editor/SceneGraph/Actors/SpotLightNode.cs b/Source/Editor/SceneGraph/Actors/SpotLightNode.cs
index b961ce205..567e8e430 100644
--- a/Source/Editor/SceneGraph/Actors/SpotLightNode.cs
+++ b/Source/Editor/SceneGraph/Actors/SpotLightNode.cs
@@ -23,7 +23,7 @@ namespace FlaxEditor.SceneGraph.Actors
base.OnDebugDraw(data);
var transform = Actor.Transform;
- DebugDraw.DrawWireArrow(transform.Translation, transform.Orientation, 0.3f, Color.Red, 0.0f, false);
+ DebugDraw.DrawWireArrow(transform.Translation, transform.Orientation, 0.3f, 0.15f, Color.Red, 0.0f, false);
}
}
}
diff --git a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs
index bb4bc724d..f96ac8e4b 100644
--- a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs
+++ b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs
@@ -482,7 +482,7 @@ namespace FlaxEditor.Surface.Archetypes
var startPos = PointToParent(ref center);
targetState.GetConnectionEndPoint(ref startPos, out var endPos);
var color = style.Foreground;
- StateMachineState.DrawConnection(ref startPos, ref endPos, ref color);
+ SurfaceStyle.DrawStraightConnection(startPos, endPos, color);
}
}
@@ -512,7 +512,7 @@ namespace FlaxEditor.Surface.Archetypes
///
public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color)
{
- StateMachineState.DrawConnection(ref startPos, ref endPos, ref color);
+ SurfaceStyle.DrawStraightConnection(startPos, endPos, color);
}
///
@@ -676,38 +676,6 @@ namespace FlaxEditor.Surface.Archetypes
{
}
- ///
- /// Draws the connection between two state machine nodes.
- ///
- /// The start position.
- /// The end position.
- /// The line color.
- public static void DrawConnection(ref Float2 startPos, ref Float2 endPos, ref Color color)
- {
- var sub = endPos - startPos;
- var length = sub.Length;
- if (length > Mathf.Epsilon)
- {
- var dir = sub / length;
- var arrowRect = new Rectangle(0, 0, 16.0f, 16.0f);
- float rotation = Float2.Dot(dir, Float2.UnitY);
- if (endPos.X < startPos.X)
- rotation = 2 - rotation;
- var sprite = Editor.Instance.Icons.VisjectArrowClosed32;
- var arrowTransform =
- Matrix3x3.Translation2D(-6.5f, -8) *
- Matrix3x3.RotationZ(rotation * Mathf.PiOverTwo) *
- Matrix3x3.Translation2D(endPos - dir * 8);
-
- Render2D.PushTransform(ref arrowTransform);
- Render2D.DrawSprite(sprite, arrowRect, color);
- Render2D.PopTransform();
-
- endPos -= dir * 4.0f;
- }
- Render2D.DrawLine(startPos, endPos, color);
- }
-
///
/// Gets the connection end point for the given input position. Puts the end point near the edge of the node bounds.
///
@@ -1308,7 +1276,7 @@ namespace FlaxEditor.Surface.Archetypes
isMouseOver = Float2.DistanceSquared(ref mousePosition, ref point) < 25.0f;
}
var color = isMouseOver ? Color.Wheat : t.LineColor;
- DrawConnection(ref t.StartPos, ref t.EndPos, ref color);
+ SurfaceStyle.DrawStraightConnection(t.StartPos, t.EndPos, color);
}
}
@@ -1337,7 +1305,7 @@ namespace FlaxEditor.Surface.Archetypes
///
public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color)
{
- DrawConnection(ref startPos, ref endPos, ref color);
+ SurfaceStyle.DrawStraightConnection(startPos, endPos, color);
}
///
diff --git a/Source/Editor/Surface/BehaviorTreeSurface.cs b/Source/Editor/Surface/BehaviorTreeSurface.cs
index 9b201d3f8..760110769 100644
--- a/Source/Editor/Surface/BehaviorTreeSurface.cs
+++ b/Source/Editor/Surface/BehaviorTreeSurface.cs
@@ -31,7 +31,7 @@ namespace FlaxEditor.Surface
var editor = Editor.Instance;
var style = SurfaceStyle.CreateStyleHandler(editor);
style.DrawBox = DrawBox;
- style.DrawConnection = DrawConnection;
+ style.DrawConnection = SurfaceStyle.DrawStraightConnection;
return style;
}
@@ -49,11 +49,6 @@ namespace FlaxEditor.Surface
Render2D.FillRectangle(rect, color);
}
- private static void DrawConnection(Float2 start, Float2 end, Color color, float thickness)
- {
- Archetypes.Animation.StateMachineStateBase.DrawConnection(ref start, ref end, ref color);
- }
-
private void OnActiveContextMenuVisibleChanged(Control activeCM)
{
_nodesCache.Wait();
diff --git a/Source/Editor/Surface/SurfaceStyle.cs b/Source/Editor/Surface/SurfaceStyle.cs
index 2b8e97f62..5a67d6fb2 100644
--- a/Source/Editor/Surface/SurfaceStyle.cs
+++ b/Source/Editor/Surface/SurfaceStyle.cs
@@ -295,5 +295,38 @@ namespace FlaxEditor.Surface
Background = editor.UI.VisjectSurfaceBackground,
};
}
+
+ ///
+ /// Draws a simple straight connection between two locations.
+ ///
+ /// The start position.
+ /// The end position.
+ /// The line color.
+ /// The line thickness.
+ public static void DrawStraightConnection(Float2 startPos, Float2 endPos, Color color, float thickness = 1.0f)
+ {
+ var sub = endPos - startPos;
+ var length = sub.Length;
+ if (length > Mathf.Epsilon)
+ {
+ var dir = sub / length;
+ var arrowRect = new Rectangle(0, 0, 16.0f, 16.0f);
+ float rotation = Float2.Dot(dir, Float2.UnitY);
+ if (endPos.X < startPos.X)
+ rotation = 2 - rotation;
+ var sprite = Editor.Instance.Icons.VisjectArrowClosed32;
+ var arrowTransform =
+ Matrix3x3.Translation2D(-6.5f, -8) *
+ Matrix3x3.RotationZ(rotation * Mathf.PiOverTwo) *
+ Matrix3x3.Translation2D(endPos - dir * 8);
+
+ Render2D.PushTransform(ref arrowTransform);
+ Render2D.DrawSprite(sprite, arrowRect, color);
+ Render2D.PopTransform();
+
+ endPos -= dir * 4.0f;
+ }
+ Render2D.DrawLine(startPos, endPos, color);
+ }
}
}
diff --git a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs
index 3ca7ddfc7..51339d6bc 100644
--- a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs
+++ b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs
@@ -20,8 +20,15 @@ namespace FlaxEditor.Surface
///
/// Utility for easy nodes archetypes generation for Visject Surface based on scripting types.
///
- internal class NodesCache
+ [HideInEditor]
+ public class NodesCache
{
+ ///
+ /// Delegate for scripting types filtering into cache.
+ ///
+ /// The input type to process.
+ /// Node groups cache that can be used for reusing groups for different nodes.
+ /// The cache version number. Can be used to reject any cached data after rebuilt.
public delegate void IterateType(ScriptType scriptType, Dictionary, GroupArchetype> cache, int version);
internal static readonly List Caches = new List(8);
@@ -33,11 +40,18 @@ namespace FlaxEditor.Surface
private VisjectCM _taskContextMenu;
private Dictionary, GroupArchetype> _cache;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The iterator callback to build node types from Scripting.
public NodesCache(IterateType iterator)
{
_iterator = iterator;
}
+ ///
+ /// Waits for the async caching job to finish.
+ ///
public void Wait()
{
if (_task != null)
@@ -48,6 +62,9 @@ namespace FlaxEditor.Surface
}
}
+ ///
+ /// Clears cache.
+ ///
public void Clear()
{
Wait();
@@ -62,6 +79,10 @@ namespace FlaxEditor.Surface
}
}
+ ///
+ /// Updates the Visject Context Menu to contain current nodes.
+ ///
+ /// The output context menu to setup.
public void Get(VisjectCM contextMenu)
{
Profiler.BeginEvent("Setup Context Menu");
diff --git a/Source/Editor/Surface/VisjectSurfaceWindow.cs b/Source/Editor/Surface/VisjectSurfaceWindow.cs
index cce86d5c0..6a9cba841 100644
--- a/Source/Editor/Surface/VisjectSurfaceWindow.cs
+++ b/Source/Editor/Surface/VisjectSurfaceWindow.cs
@@ -25,7 +25,7 @@ namespace FlaxEditor.Surface
/// The base interface for editor windows that use for content editing.
///
///
- interface IVisjectSurfaceWindow : IVisjectSurfaceOwner
+ public interface IVisjectSurfaceWindow : IVisjectSurfaceOwner
{
///
/// Gets the asset edited by the window.
diff --git a/Source/Editor/Tools/ClothPainting.cs b/Source/Editor/Tools/ClothPainting.cs
index 28536ea83..6a98a3b5f 100644
--- a/Source/Editor/Tools/ClothPainting.cs
+++ b/Source/Editor/Tools/ClothPainting.cs
@@ -43,6 +43,19 @@ namespace FlaxEngine.Tools
/// Enables continuous painting, otherwise single paint on click.
///
public bool ContinuousPaint;
+
+ ///
+ /// Enables drawing cloth paint debugging with Depth Test enabled (skips occluded vertices).
+ ///
+ public bool DebugDrawDepthTest
+ {
+ get => Gizmo.Cloth?.DebugDrawDepthTest ?? true;
+ set
+ {
+ if (Gizmo.Cloth != null)
+ Gizmo.Cloth.DebugDrawDepthTest = value;
+ }
+ }
#pragma warning restore CS0649
public override void Init(IGizmoOwner owner)
@@ -62,6 +75,7 @@ namespace FlaxEngine.Tools
public override void Dispose()
{
Owner.Gizmos.Remove(Gizmo);
+ Gizmo = null;
base.Dispose();
}
@@ -83,6 +97,7 @@ namespace FlaxEngine.Tools
private EditClothPaintAction _undoAction;
public bool IsPainting => _isPainting;
+ public Cloth Cloth => _cloth;
public ClothPaintingGizmo(IGizmoOwner owner, ClothPaintingGizmoMode mode)
: base(owner)
diff --git a/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs b/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs
index 252891d44..fd28c302b 100644
--- a/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs
+++ b/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs
@@ -45,9 +45,6 @@ namespace FlaxEditor.Tools.Terrain
[EditorOrder(130), EditorDisplay("Layout"), DefaultValue(null), Tooltip("The default material used for terrain rendering (chunks can override this). It must have Domain set to terrain.")]
public MaterialBase Material;
- [EditorOrder(200), EditorDisplay("Collision"), DefaultValue(null), AssetReference(typeof(PhysicalMaterial), true), Tooltip("Terrain default physical material used to define the collider physical properties.")]
- public JsonAsset PhysicalMaterial;
-
[EditorOrder(210), EditorDisplay("Collision", "Collision LOD"), DefaultValue(-1), Limit(-1, 100, 0.1f), Tooltip("Terrain geometry LOD index used for collision.")]
public int CollisionLOD = -1;
@@ -152,7 +149,6 @@ namespace FlaxEditor.Tools.Terrain
terrain.Setup(_options.LODCount, (int)_options.ChunkSize);
terrain.Transform = new Transform(_options.Position, _options.Orientation, _options.Scale);
terrain.Material = _options.Material;
- terrain.PhysicalMaterial = _options.PhysicalMaterial;
terrain.CollisionLOD = _options.CollisionLOD;
if (_options.Heightmap)
terrain.Position -= new Vector3(0, _options.HeightmapScale * 0.5f, 0);
diff --git a/Source/Editor/Tools/Terrain/Paint/Mode.cs b/Source/Editor/Tools/Terrain/Paint/Mode.cs
index 3647691b3..03e4a4690 100644
--- a/Source/Editor/Tools/Terrain/Paint/Mode.cs
+++ b/Source/Editor/Tools/Terrain/Paint/Mode.cs
@@ -67,11 +67,13 @@ namespace FlaxEditor.Tools.Terrain.Paint
// Prepare
var splatmapIndex = ActiveSplatmapIndex;
+ var splatmapIndexOther = (splatmapIndex + 1) % 2;
var chunkSize = terrain.ChunkSize;
var heightmapSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1;
var heightmapLength = heightmapSize * heightmapSize;
var patchSize = chunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount;
- var tempBuffer = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes).ToPointer();
+ var tempBuffer = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes, splatmapIndex).ToPointer();
+ var tempBufferOther = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes, (splatmapIndex + 1) % 2).ToPointer();
var unitsPerVertexInv = 1.0f / FlaxEngine.Terrain.UnitsPerVertex;
ApplyParams p = new ApplyParams
{
@@ -81,8 +83,10 @@ namespace FlaxEditor.Tools.Terrain.Paint
Options = options,
Strength = strength,
SplatmapIndex = splatmapIndex,
+ SplatmapIndexOther = splatmapIndexOther,
HeightmapSize = heightmapSize,
TempBuffer = tempBuffer,
+ TempBufferOther = tempBufferOther,
};
// Get brush bounds in terrain local space
@@ -131,11 +135,16 @@ namespace FlaxEditor.Tools.Terrain.Paint
var sourceData = TerrainTools.GetSplatMapData(terrain, ref patch.PatchCoord, splatmapIndex);
if (sourceData == null)
throw new Exception("Cannot modify terrain. Loading splatmap failed. See log for more info.");
+
+ var sourceDataOther = TerrainTools.GetSplatMapData(terrain, ref patch.PatchCoord, splatmapIndexOther);
+ if (sourceDataOther == null)
+ throw new Exception("Cannot modify terrain. Loading splatmap failed. See log for more info.");
// Record patch data before editing it
if (!gizmo.CurrentEditUndoAction.HashPatch(ref patch.PatchCoord))
{
gizmo.CurrentEditUndoAction.AddPatch(ref patch.PatchCoord, splatmapIndex);
+ gizmo.CurrentEditUndoAction.AddPatch(ref patch.PatchCoord, splatmapIndexOther);
}
// Apply modification
@@ -144,6 +153,7 @@ namespace FlaxEditor.Tools.Terrain.Paint
p.PatchCoord = patch.PatchCoord;
p.PatchPositionLocal = patchPositionLocal;
p.SourceData = sourceData;
+ p.SourceDataOther = sourceDataOther;
Apply(ref p);
}
}
@@ -197,16 +207,32 @@ namespace FlaxEditor.Tools.Terrain.Paint
/// The splatmap texture index.
///
public int SplatmapIndex;
+
+ ///
+ /// The splatmap texture index. If is 0, this will be 1. If is 1, this will be 0.
+ ///
+ public int SplatmapIndexOther;
///
/// The temporary data buffer (for modified data).
///
public Color32* TempBuffer;
+
+ ///
+ /// The 'other' temporary data buffer (for modified data). If refersto the splatmap with index 0, this one will refer to the one with index 1.
+ ///
+ public Color32* TempBufferOther;
///
/// The source data buffer.
///
public Color32* SourceData;
+
+ ///
+ /// The 'other' source data buffer. If refers
+ /// to the splatmap with index 0, this one will refer to the one with index 1.
+ ///
+ public Color32* SourceDataOther;
///
/// The heightmap size (edge).
diff --git a/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs b/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs
index ec41b5286..5921f7d10 100644
--- a/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs
+++ b/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs
@@ -1,6 +1,5 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
-using System;
using FlaxEngine;
namespace FlaxEditor.Tools.Terrain.Paint
@@ -73,53 +72,53 @@ namespace FlaxEditor.Tools.Terrain.Paint
var strength = p.Strength;
var layer = (int)Layer;
var brushPosition = p.Gizmo.CursorPosition;
- var layerComponent = layer % 4;
+ var c = layer % 4;
// Apply brush modification
Profiler.BeginEvent("Apply Brush");
+ bool otherModified = false;
for (int z = 0; z < p.ModifiedSize.Y; z++)
{
var zz = z + p.ModifiedOffset.Y;
for (int x = 0; x < p.ModifiedSize.X; x++)
{
var xx = x + p.ModifiedOffset.X;
- var src = p.SourceData[zz * p.HeightmapSize + xx];
+ var src = (Color)p.SourceData[zz * p.HeightmapSize + xx];
var samplePositionLocal = p.PatchPositionLocal + new Vector3(xx * FlaxEngine.Terrain.UnitsPerVertex, 0, zz * FlaxEngine.Terrain.UnitsPerVertex);
Vector3.Transform(ref samplePositionLocal, ref p.TerrainWorld, out Vector3 samplePositionWorld);
+ var sample = Mathf.Saturate(p.Brush.Sample(ref brushPosition, ref samplePositionWorld));
- var paintAmount = p.Brush.Sample(ref brushPosition, ref samplePositionWorld) * strength;
+ var paintAmount = sample * strength;
+ if (paintAmount < 0.0f)
+ continue; // Skip when pixel won't be affected
- // Extract layer weight
- byte* srcPtr = &src.R;
- var srcWeight = *(srcPtr + layerComponent) / 255.0f;
-
- // Accumulate weight
- float dstWeight = srcWeight + paintAmount;
-
- // Check for solid layer case
- if (dstWeight >= 1.0f)
- {
- // Erase other layers
- // TODO: maybe erase only the higher layers?
- // TODO: need to erase also weights form the other splatmaps
- src = Color32.Transparent;
-
- // Use limit value
- dstWeight = 1.0f;
- }
-
- // Modify packed weight
- *(srcPtr + layerComponent) = (byte)(dstWeight * 255.0f);
-
- // Write back
+ // Paint on the active splatmap texture
+ src[c] = Mathf.Saturate(src[c] + paintAmount);
+ src[(c + 1) % 4] = Mathf.Saturate(src[(c + 1) % 4] - paintAmount);
+ src[(c + 2) % 4] = Mathf.Saturate(src[(c + 2) % 4] - paintAmount);
+ src[(c + 3) % 4] = Mathf.Saturate(src[(c + 3) % 4] - paintAmount);
p.TempBuffer[z * p.ModifiedSize.X + x] = src;
+
+ var other = (Color)p.SourceDataOther[zz * p.HeightmapSize + xx];
+ //if (other.ValuesSum > 0.0f) // Skip editing the other splatmap if it's empty
+ {
+ // Remove 'paint' from the other splatmap texture
+ other[c] = Mathf.Saturate(other[c] - paintAmount);
+ other[(c + 1) % 4] = Mathf.Saturate(other[(c + 1) % 4] - paintAmount);
+ other[(c + 2) % 4] = Mathf.Saturate(other[(c + 2) % 4] - paintAmount);
+ other[(c + 3) % 4] = Mathf.Saturate(other[(c + 3) % 4] - paintAmount);
+ p.TempBufferOther[z * p.ModifiedSize.X + x] = other;
+ otherModified = true;
+ }
}
}
Profiler.EndEvent();
// Update terrain patch
TerrainTools.ModifySplatMap(p.Terrain, ref p.PatchCoord, p.SplatmapIndex, p.TempBuffer, ref p.ModifiedOffset, ref p.ModifiedSize);
+ if (otherModified)
+ TerrainTools.ModifySplatMap(p.Terrain, ref p.PatchCoord, p.SplatmapIndexOther, p.TempBufferOther, ref p.ModifiedOffset, ref p.ModifiedSize);
}
}
}
diff --git a/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs b/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs
index 1d1bf87ca..4e7925dd9 100644
--- a/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs
+++ b/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs
@@ -36,9 +36,34 @@ namespace FlaxEditor.Tools.Terrain
"Layer 7",
};
- private IntPtr _cachedSplatmapData;
- private int _cachedSplatmapDataSize;
+ private struct SplatmapData
+ {
+ public IntPtr DataPtr;
+ public int Size;
+
+ public void EnsureCapacity(int size)
+ {
+ if (Size < size)
+ {
+ if (DataPtr != IntPtr.Zero)
+ Marshal.FreeHGlobal(DataPtr);
+ DataPtr = Marshal.AllocHGlobal(size);
+ Size = size;
+ }
+ }
+
+ public void Free()
+ {
+ if (DataPtr == IntPtr.Zero)
+ return;
+ Marshal.FreeHGlobal(DataPtr);
+ DataPtr = IntPtr.Zero;
+ Size = 0;
+ }
+ }
+
private EditTerrainMapAction _activeAction;
+ private SplatmapData[] _cachedSplatmapData = new SplatmapData[2];
///
/// The terrain painting gizmo.
@@ -230,20 +255,13 @@ namespace FlaxEditor.Tools.Terrain
/// Gets the splatmap temporary scratch memory buffer used to modify terrain samples. Allocated memory is unmanaged by GC.
///
/// The minimum buffer size (in bytes).
+ /// The splatmap index for which to return/create the temp buffer.
/// The allocated memory using interface.
- public IntPtr GetSplatmapTempBuffer(int size)
+ public IntPtr GetSplatmapTempBuffer(int size, int splatmapIndex)
{
- if (_cachedSplatmapDataSize < size)
- {
- if (_cachedSplatmapData != IntPtr.Zero)
- {
- Marshal.FreeHGlobal(_cachedSplatmapData);
- }
- _cachedSplatmapData = Marshal.AllocHGlobal(size);
- _cachedSplatmapDataSize = size;
- }
-
- return _cachedSplatmapData;
+ ref var splatmapData = ref _cachedSplatmapData[splatmapIndex];
+ splatmapData.EnsureCapacity(size);
+ return splatmapData.DataPtr;
}
///
@@ -276,12 +294,8 @@ namespace FlaxEditor.Tools.Terrain
base.OnDeactivated();
// Free temporary memory buffer
- if (_cachedSplatmapData != IntPtr.Zero)
- {
- Marshal.FreeHGlobal(_cachedSplatmapData);
- _cachedSplatmapData = IntPtr.Zero;
- _cachedSplatmapDataSize = 0;
- }
+ foreach (var splatmapData in _cachedSplatmapData)
+ splatmapData.Free();
}
///
diff --git a/Source/Editor/Tools/Terrain/TerrainTools.cpp b/Source/Editor/Tools/Terrain/TerrainTools.cpp
index 89d0aa17d..ba1da6638 100644
--- a/Source/Editor/Tools/Terrain/TerrainTools.cpp
+++ b/Source/Editor/Tools/Terrain/TerrainTools.cpp
@@ -20,7 +20,7 @@ bool TerrainTools::TryGetPatchCoordToAdd(Terrain* terrain, const Ray& ray, Int2&
{
CHECK_RETURN(terrain, true);
result = Int2::Zero;
- const float patchSize = terrain->GetChunkSize() * TERRAIN_UNITS_PER_VERTEX * TerrainPatch::CHUNKS_COUNT_EDGE;
+ const float patchSize = terrain->GetChunkSize() * TERRAIN_UNITS_PER_VERTEX * Terrain::ChunksCountEdge;
// Try to pick any of the patch edges
for (int32 patchIndex = 0; patchIndex < terrain->GetPatchesCount(); patchIndex++)
@@ -179,7 +179,7 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches
terrain->AddPatches(numberOfPatches);
// Prepare data
- const auto heightmapSize = terrain->GetChunkSize() * TerrainPatch::CHUNKS_COUNT_EDGE + 1;
+ const auto heightmapSize = terrain->GetChunkSize() * Terrain::ChunksCountEdge + 1;
Array heightmapData;
heightmapData.Resize(heightmapSize * heightmapSize);
@@ -380,7 +380,7 @@ bool TerrainTools::ExportTerrain(Terrain* terrain, String outputFolder)
const auto firstPatch = terrain->GetPatch(0);
// Calculate texture size
- const int32 patchEdgeVertexCount = terrain->GetChunkSize() * TerrainPatch::CHUNKS_COUNT_EDGE + 1;
+ const int32 patchEdgeVertexCount = terrain->GetChunkSize() * Terrain::ChunksCountEdge + 1;
const int32 patchVertexCount = patchEdgeVertexCount * patchEdgeVertexCount;
// Find size of heightmap in patches
diff --git a/Source/Editor/Viewport/ViewportDraggingHelper.cs b/Source/Editor/Viewport/ViewportDraggingHelper.cs
index bf4ec7979..dce2867c3 100644
--- a/Source/Editor/Viewport/ViewportDraggingHelper.cs
+++ b/Source/Editor/Viewport/ViewportDraggingHelper.cs
@@ -234,7 +234,6 @@ namespace FlaxEditor.Viewport
LocalOrientation = RootNode.RaycastNormalRotation(ref hitNormal),
Name = item.ShortName
};
- DebugDraw.DrawWireArrow(PostProcessSpawnedActorLocation(actor, ref hitNormal), actor.LocalOrientation, 1.0f, Color.Red, 1000000);
Spawn(actor, ref hitLocation, ref hitNormal);
}
else if (hit is StaticModelNode staticModelNode)
diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs
index 0c8e0f283..abad36829 100644
--- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs
+++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs
@@ -132,10 +132,13 @@ namespace FlaxEditor.Windows
b = contextMenu.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut);
b.Enabled = canEditScene;
- // Prefab options
+ // Create option
contextMenu.AddSeparator();
+ b = contextMenu.AddButton("Create parent for selected actors", Editor.SceneEditing.CreateParentForSelectedActors);
+ b.Enabled = canEditScene && hasSthSelected;
+
b = contextMenu.AddButton("Create Prefab", Editor.Prefabs.CreatePrefab);
b.Enabled = isSingleActorSelected &&
((ActorNode)Editor.SceneEditing.Selection[0]).CanCreatePrefab &&
diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs
index 97bf85b35..c5d790a3d 100644
--- a/Source/Editor/Windows/SceneTreeWindow.cs
+++ b/Source/Editor/Windows/SceneTreeWindow.cs
@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using FlaxEditor.Gizmo;
using FlaxEditor.Content;
using FlaxEditor.GUI.Tree;
@@ -14,7 +13,6 @@ using FlaxEditor.Scripting;
using FlaxEditor.States;
using FlaxEngine;
using FlaxEngine.GUI;
-using static FlaxEditor.GUI.ItemsListContextMenu;
namespace FlaxEditor.Windows
{
@@ -35,6 +33,11 @@ namespace FlaxEditor.Windows
private DragScriptItems _dragScriptItems;
private DragHandlers _dragHandlers;
+ ///
+ /// Scene tree panel.
+ ///
+ public Panel SceneTreePanel => _sceneTreePanel;
+
///
/// Initializes a new instance of the class.
///
diff --git a/Source/Engine/AI/BehaviorKnowledgeSelector.cs b/Source/Engine/AI/BehaviorKnowledgeSelector.cs
index 5c642e92a..cdcaf6c40 100644
--- a/Source/Engine/AI/BehaviorKnowledgeSelector.cs
+++ b/Source/Engine/AI/BehaviorKnowledgeSelector.cs
@@ -146,7 +146,7 @@ namespace FlaxEngine
public string Path;
///
- /// Initializes a new instance of the structure.
+ /// Initializes a new instance of the structure.
///
/// The selector path.
public BehaviorKnowledgeSelector(string path)
@@ -155,7 +155,7 @@ namespace FlaxEngine
}
///
- /// Initializes a new instance of the structure.
+ /// Initializes a new instance of the structure.
///
/// The other selector.
public BehaviorKnowledgeSelector(BehaviorKnowledgeSelectorAny other)
diff --git a/Source/Engine/AI/BehaviorKnowledgeSelector.h b/Source/Engine/AI/BehaviorKnowledgeSelector.h
index 976711282..092d42b1c 100644
--- a/Source/Engine/AI/BehaviorKnowledgeSelector.h
+++ b/Source/Engine/AI/BehaviorKnowledgeSelector.h
@@ -91,6 +91,13 @@ API_STRUCT(InBuild, Template, MarshalAs=StringAnsi) struct FLAXENGINE_API Behavi
return false;
}
+ BehaviorKnowledgeSelector() = default;
+
+ BehaviorKnowledgeSelector(const StringAnsi& other)
+ {
+ Path = other;
+ }
+
BehaviorKnowledgeSelector& operator=(const StringAnsiView& other) noexcept
{
Path = other;
diff --git a/Source/Engine/Animations/Graph/AnimGraph.cpp b/Source/Engine/Animations/Graph/AnimGraph.cpp
index 93bfc2609..2fc9332ba 100644
--- a/Source/Engine/Animations/Graph/AnimGraph.cpp
+++ b/Source/Engine/Animations/Graph/AnimGraph.cpp
@@ -221,6 +221,7 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
context.NodePath.Clear();
context.Data = &data;
context.DeltaTime = dt;
+ context.StackOverFlow = false;
context.CurrentFrameIndex = ++data.CurrentFrame;
context.CallStack.Clear();
context.Functions.Clear();
@@ -411,9 +412,12 @@ VisjectExecutor::Value AnimGraphExecutor::eatBox(Node* caller, Box* box)
auto& context = *Context.Get();
// Check if graph is looped or is too deep
+ if (context.StackOverFlow)
+ return Value::Zero;
if (context.CallStack.Count() >= ANIM_GRAPH_MAX_CALL_STACK)
{
OnError(caller, box, TEXT("Graph is looped or too deep!"));
+ context.StackOverFlow = true;
return Value::Zero;
}
#if !BUILD_RELEASE
diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h
index b570cf9fe..c069ddb06 100644
--- a/Source/Engine/Animations/Graph/AnimGraph.h
+++ b/Source/Engine/Animations/Graph/AnimGraph.h
@@ -796,6 +796,7 @@ struct AnimGraphContext
AnimGraphInstanceData* Data;
AnimGraphImpulse EmptyNodes;
AnimGraphTransitionData TransitionData;
+ bool StackOverFlow;
Array> CallStack;
Array> GraphStack;
Array > NodePath;
diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp
index 897817850..2be4df29d 100644
--- a/Source/Engine/Content/Content.cpp
+++ b/Source/Engine/Content/Content.cpp
@@ -277,6 +277,8 @@ bool Content::GetAssetInfo(const StringView& path, AssetInfo& info)
// Find asset in registry
if (Cache.FindAsset(path, info))
return true;
+ if (!FileSystem::FileExists(path))
+ return false;
PROFILE_CPU();
const auto extension = FileSystem::GetExtension(path).ToLower();
diff --git a/Source/Engine/Content/JsonAsset.cpp b/Source/Engine/Content/JsonAsset.cpp
index 9977a28e1..60ff29be1 100644
--- a/Source/Engine/Content/JsonAsset.cpp
+++ b/Source/Engine/Content/JsonAsset.cpp
@@ -393,34 +393,57 @@ bool JsonAsset::CreateInstance()
if (typeHandle)
{
auto& type = typeHandle.GetType();
+
+ // Ensure that object can deserialized
+ const ScriptingType::InterfaceImplementation* interface = type.GetInterface(ISerializable::TypeInitializer);
+ if (!interface)
+ {
+ LOG(Warning, "Cannot deserialize {0} from Json Asset because it doesn't implement ISerializable interface.", type.ToString());
+ return false;
+ }
+ auto modifier = Cache::ISerializeModifier.Get();
+ modifier->EngineBuild = DataEngineBuild;
+
+ // Create object
switch (type.Type)
{
case ScriptingTypes::Class:
+ case ScriptingTypes::Structure:
{
- // Ensure that object can deserialized
- const ScriptingType::InterfaceImplementation* interface = type.GetInterface(ISerializable::TypeInitializer);
- if (!interface)
- {
- LOG(Warning, "Cannot deserialize {0} from Json Asset because it doesn't implement ISerializable interface.", type.ToString());
- break;
- }
-
- // Allocate object
const auto instance = Allocator::Allocate(type.Size);
if (!instance)
return true;
Instance = instance;
- InstanceType = typeHandle;
- _dtor = type.Class.Dtor;
- type.Class.Ctor(instance);
+ if (type.Type == ScriptingTypes::Class)
+ {
+ _dtor = type.Class.Dtor;
+ type.Class.Ctor(instance);
+ }
+ else
+ {
+ _dtor = type.Struct.Dtor;
+ type.Struct.Ctor(instance);
+ }
// Deserialize object
- auto modifier = Cache::ISerializeModifier.Get();
- modifier->EngineBuild = DataEngineBuild;
((ISerializable*)((byte*)instance + interface->VTableOffset))->Deserialize(*Data, modifier.Value);
break;
}
+ case ScriptingTypes::Script:
+ {
+ const ScriptingObjectSpawnParams params(Guid::New(), typeHandle);
+ const auto instance = type.Script.Spawn(params);
+ if (!instance)
+ return true;
+ Instance = instance;
+ _dtor = nullptr;
+
+ // Deserialize object
+ ToInterface(instance)->Deserialize(*Data, modifier.Value);
+ break;
}
+ }
+ InstanceType = typeHandle;
}
return false;
@@ -441,13 +464,20 @@ void JsonAsset::DeleteInstance()
}
// C++ instance
- if (!Instance || !_dtor)
+ if (!Instance)
return;
- _dtor(Instance);
+ if (_dtor)
+ {
+ _dtor(Instance);
+ _dtor = nullptr;
+ Allocator::Free(Instance);
+ }
+ else
+ {
+ Delete((ScriptingObject*)Instance);
+ }
InstanceType = ScriptingTypeHandle();
- Allocator::Free(Instance);
Instance = nullptr;
- _dtor = nullptr;
}
#if USE_EDITOR
diff --git a/Source/Engine/Content/JsonAsset.h b/Source/Engine/Content/JsonAsset.h
index a8e89cec0..21b68c691 100644
--- a/Source/Engine/Content/JsonAsset.h
+++ b/Source/Engine/Content/JsonAsset.h
@@ -139,7 +139,8 @@ public:
T* GetInstance() const
{
const_cast(this)->CreateInstance();
- return Instance && InstanceType.IsAssignableFrom(T::TypeInitializer) ? (T*)Instance : nullptr;
+ const ScriptingTypeHandle& type = T::TypeInitializer;
+ return Instance && type.IsAssignableFrom(InstanceType) ? (T*)Instance : nullptr;
}
public:
diff --git a/Source/Engine/Content/JsonAssetReference.cs b/Source/Engine/Content/JsonAssetReference.cs
new file mode 100644
index 000000000..766645161
--- /dev/null
+++ b/Source/Engine/Content/JsonAssetReference.cs
@@ -0,0 +1,133 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace FlaxEngine
+{
+ ///
+ /// Json asset reference utility. References resource with a typed data type.
+ ///
+ /// Type of the asset instance type.
+#if FLAX_EDITOR
+ [CustomEditor(typeof(FlaxEditor.CustomEditors.Editors.AssetRefEditor))]
+#endif
+ public struct JsonAssetReference : IComparable, IComparable>, IEquatable>
+ {
+ ///
+ /// Gets or sets the referenced asset.
+ ///
+ public JsonAsset Asset;
+
+ ///
+ /// Gets the instance of the serialized object from the json asset data. Cached internally.
+ ///
+ public T Instance => (T)Asset?.Instance;
+
+ ///
+ /// Initializes a new instance of the structure.
+ ///
+ /// The Json Asset.
+ public JsonAssetReference(JsonAsset asset)
+ {
+ Asset = asset;
+ }
+
+ ///
+ /// Implicit cast operator.
+ ///
+ public static implicit operator JsonAsset(JsonAssetReference value)
+ {
+ return value.Asset;
+ }
+
+ ///
+ /// Implicit cast operator.
+ ///
+ public static implicit operator IntPtr(JsonAssetReference value)
+ {
+ return Object.GetUnmanagedPtr(value.Asset);
+ }
+
+ ///
+ /// Implicit cast operator.
+ ///
+ public static implicit operator JsonAssetReference(JsonAsset value)
+ {
+ return new JsonAssetReference(value);
+ }
+
+ ///
+ /// Implicit cast operator.
+ ///
+ public static implicit operator JsonAssetReference(IntPtr valuePtr)
+ {
+ return new JsonAssetReference(Object.FromUnmanagedPtr(valuePtr) as JsonAsset);
+ }
+
+ ///
+ /// Checks if the object exists (reference is not null and the unmanaged object pointer is valid).
+ ///
+ /// The object to check.
+ /// True if object is valid, otherwise false.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator bool(JsonAssetReference obj)
+ {
+ return obj.Asset;
+ }
+
+ ///
+ /// Checks whether the two objects are equal.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator ==(JsonAssetReference left, JsonAssetReference right)
+ {
+ return left.Asset == right.Asset;
+ }
+
+ ///
+ /// Checks whether the two objects are not equal.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator !=(JsonAssetReference left, JsonAssetReference right)
+ {
+ return left.Asset != right.Asset;
+ }
+
+ ///
+ public bool Equals(JsonAssetReference other)
+ {
+ return Asset == other.Asset;
+ }
+
+ ///
+ public int CompareTo(JsonAssetReference other)
+ {
+ return Object.GetUnmanagedPtr(Asset).CompareTo(Object.GetUnmanagedPtr(other.Asset));
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ return obj is JsonAssetReference other && Asset == other.Asset;
+ }
+
+ ///
+ public override string ToString()
+ {
+ return Asset?.ToString();
+ }
+
+ ///
+ public int CompareTo(object obj)
+ {
+ return obj is JsonAssetReference other ? CompareTo(other) : 1;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return (Asset != null ? Asset.GetHashCode() : 0);
+ }
+ }
+}
diff --git a/Source/Engine/Content/JsonAssetReference.h b/Source/Engine/Content/JsonAssetReference.h
new file mode 100644
index 000000000..201d0b3a3
--- /dev/null
+++ b/Source/Engine/Content/JsonAssetReference.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#include "Engine/Content/JsonAsset.h"
+#include "Engine/Content/AssetReference.h"
+
+///
+/// Json asset reference utility. References resource with a typed data type.
+///
+/// Type of the asset instance type.
+template
+API_STRUCT(NoDefault, Template, MarshalAs=JsonAsset*) struct JsonAssetReference : AssetReference
+{
+ JsonAssetReference() = default;
+
+ JsonAssetReference(JsonAsset* asset)
+ {
+ OnSet(asset);
+ }
+
+ ///
+ /// Gets the deserialized native object instance of the given type. Returns null if asset is not loaded or loaded object has different type.
+ ///
+ /// The asset instance object or null.
+ FORCE_INLINE T* GetInstance() const
+ {
+ return _asset ? Get()->template GetInstance() : nullptr;
+ }
+
+ JsonAssetReference& operator=(JsonAsset* asset) noexcept
+ {
+ OnSet(asset);
+ return *this;
+ }
+
+ operator JsonAsset*() const
+ {
+ return Get();
+ }
+};
diff --git a/Source/Engine/ContentImporters/AssetsImportingManager.cpp b/Source/Engine/ContentImporters/AssetsImportingManager.cpp
index a0b28f4f8..5fb5ff0c0 100644
--- a/Source/Engine/ContentImporters/AssetsImportingManager.cpp
+++ b/Source/Engine/ContentImporters/AssetsImportingManager.cpp
@@ -234,7 +234,6 @@ bool AssetsImportingManager::Create(const String& tag, const StringView& outputP
LOG(Warning, "Cannot find asset creator object for tag \'{0}\'.", tag);
return true;
}
-
return Create(creator->Callback, outputPath, assetId, arg);
}
diff --git a/Source/Engine/ContentImporters/Types.h b/Source/Engine/ContentImporters/Types.h
index 388c533a8..0275edc35 100644
--- a/Source/Engine/ContentImporters/Types.h
+++ b/Source/Engine/ContentImporters/Types.h
@@ -113,7 +113,7 @@ private:
///
/// Asset importer entry
///
-struct AssetImporter
+struct FLAXENGINE_API AssetImporter
{
public:
///
@@ -135,7 +135,7 @@ public:
///
/// Asset creator entry
///
-struct AssetCreator
+struct FLAXENGINE_API AssetCreator
{
public:
///
diff --git a/Source/Engine/Core/Math/Color.cs b/Source/Engine/Core/Math/Color.cs
index d585177a0..7474b913d 100644
--- a/Source/Engine/Core/Math/Color.cs
+++ b/Source/Engine/Core/Math/Color.cs
@@ -92,6 +92,21 @@ namespace FlaxEngine
///
public float MaxColorComponent => Mathf.Max(Mathf.Max(R, G), B);
+ ///
+ /// Gets a minimum component value (max of r,g,b,a).
+ ///
+ public float MinValue => Math.Min(R, Math.Min(G, Math.Min(B, A)));
+
+ ///
+ /// Gets a maximum component value (min of r,g,b,a).
+ ///
+ public float MaxValue => Math.Max(R, Math.Max(G, Math.Max(B, A)));
+
+ ///
+ /// Gets a sum of the component values.
+ ///
+ public float ValuesSum => R + G + B + A;
+
///
/// Constructs a new Color with given r,g,b,a component.
///
diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp
index 059ebbd5d..bd7a218a7 100644
--- a/Source/Engine/Debug/DebugDraw.cpp
+++ b/Source/Engine/Debug/DebugDraw.cpp
@@ -1940,15 +1940,15 @@ void DebugDraw::DrawWireArc(const Vector3& position, const Quaternion& orientati
DrawLine(prevPos, world.GetTranslation(), color, duration, depthTest);
}
-void DebugDraw::DrawWireArrow(const Vector3& position, const Quaternion& orientation, float scale, const Color& color, float duration, bool depthTest)
+void DebugDraw::DrawWireArrow(const Vector3& position, const Quaternion& orientation, float scale, float capScale, const Color& color, float duration, bool depthTest)
{
Float3 direction, up, right;
Float3::Transform(Float3::Forward, orientation, direction);
Float3::Transform(Float3::Up, orientation, up);
Float3::Transform(Float3::Right, orientation, right);
const Vector3 end = position + direction * (100.0f * scale);
- const Vector3 capEnd = position + direction * (70.0f * scale);
- const float arrowSidesRatio = scale * 30.0f;
+ const Vector3 capEnd = end - (direction * (100 * Math::Min(capScale, scale * 0.5f)));
+ const float arrowSidesRatio = Math::Min(capScale, scale * 0.5f) * 30.0f;
DrawLine(position, end, color, duration, depthTest);
DrawLine(end, capEnd + up * arrowSidesRatio, color, duration, depthTest);
diff --git a/Source/Engine/Debug/DebugDraw.cs b/Source/Engine/Debug/DebugDraw.cs
index bc000e03d..8ff04d072 100644
--- a/Source/Engine/Debug/DebugDraw.cs
+++ b/Source/Engine/Debug/DebugDraw.cs
@@ -218,10 +218,11 @@ namespace FlaxEngine
/// The arrow origin position.
/// The orientation (defines the arrow direction).
/// The arrow scale (used to adjust the arrow size).
+ /// The arrow cap scale.
/// The color.
/// The duration (in seconds). Use 0 to draw it only once.
/// If set to true depth test will be performed, otherwise depth will be ignored.
- public static void DrawWireArrow(Vector3 position, Quaternion orientation, float scale, Color color, float duration = 0.0f, bool depthTest = true)
+ public static void DrawWireArrow(Vector3 position, Quaternion orientation, float scale, float capScale, Color color, float duration = 0.0f, bool depthTest = true)
{
}
diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h
index b02b94475..30feb4440 100644
--- a/Source/Engine/Debug/DebugDraw.h
+++ b/Source/Engine/Debug/DebugDraw.h
@@ -570,10 +570,11 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
/// The arrow origin position.
/// The orientation (defines the arrow direction).
/// The arrow scale (used to adjust the arrow size).
+ /// The arrow cap scale.
/// The color.
/// The duration (in seconds). Use 0 to draw it only once.
/// If set to true depth test will be performed, otherwise depth will be ignored.
- API_FUNCTION() static void DrawWireArrow(const Vector3& position, const Quaternion& orientation, float scale, const Color& color, float duration = 0.0f, bool depthTest = true);
+ API_FUNCTION() static void DrawWireArrow(const Vector3& position, const Quaternion& orientation, float scale, float capScale, const Color& color, float duration = 0.0f, bool depthTest = true);
///
/// Draws the box.
@@ -650,7 +651,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
#define DEBUG_DRAW_WIRE_CYLINDER(position, orientation, radius, height, color, duration, depthTest) DebugDraw::DrawWireCylinder(position, orientation, radius, height, color, duration, depthTest)
#define DEBUG_DRAW_WIRE_CONE(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) DebugDraw::DrawWireCone(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest)
#define DEBUG_DRAW_WIRE_ARC(position, orientation, radius, angle, color, duration, depthTest) DebugDraw::DrawWireArc(position, orientation, radius, angle, color, duration, depthTest)
-#define DEBUG_DRAW_WIRE_ARROW(position, orientation, scale, color, duration, depthTest) DebugDraw::DrawWireArrow(position, orientation, scale, color, duration, depthTest)
+#define DEBUG_DRAW_WIRE_ARROW(position, orientation, scale, capScale, color, duration, depthTest) DebugDraw::DrawWireArrow(position, orientation, scale, capScale, color, duration, depthTest)
#define DEBUG_DRAW_TEXT(text, position, color, size, duration) DebugDraw::DrawText(text, position, color, size, duration)
#else
@@ -679,7 +680,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
#define DEBUG_DRAW_WIRE_CYLINDER(position, orientation, radius, height, color, duration, depthTest)
#define DEBUG_DRAW_WIRE_CONE(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest)
#define DEBUG_DRAW_WIRE_ARC(position, orientation, radius, angle, color, duration, depthTest)
-#define DEBUG_DRAW_WIRE_ARROW(position, orientation, scale, color, duration, depthTest)
+#define DEBUG_DRAW_WIRE_ARROW(position, orientation, scale, capScale, color, duration, depthTest)
#define DEBUG_DRAW_TEXT(text, position, color, size, duration)
#endif
diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp
index 259649062..fe7541adb 100644
--- a/Source/Engine/Engine/Engine.cpp
+++ b/Source/Engine/Engine/Engine.cpp
@@ -214,9 +214,6 @@ int32 Engine::Main(const Char* cmdLine)
Time::OnEndDraw();
FrameMark;
}
-
- // Collect physics simulation results (does nothing if Simulate hasn't been called in the previous loop step)
- Physics::CollectResults();
}
// Call on exit event
@@ -288,6 +285,9 @@ void Engine::OnLateFixedUpdate()
// Update services
EngineService::OnLateFixedUpdate();
+
+ // Collect physics simulation results (does nothing if Simulate hasn't been called in the previous loop step)
+ Physics::CollectResults();
}
void Engine::OnUpdate()
diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs
index 026f54fec..7dfd52d22 100644
--- a/Source/Engine/Engine/NativeInterop.cs
+++ b/Source/Engine/Engine/NativeInterop.cs
@@ -249,7 +249,7 @@ namespace FlaxEngine.Interop
/// The input array.
/// Converter callback.
/// The output array.
- public static TDst[] ConvertArray(Span src, Func convertFunc)
+ public static TDst[] ConvertArray(this Span src, Func convertFunc)
{
TDst[] dst = new TDst[src.Length];
for (int i = 0; i < src.Length; i++)
@@ -265,7 +265,7 @@ namespace FlaxEngine.Interop
/// The input array.
/// Converter callback.
/// The output array.
- public static TDst[] ConvertArray(TSrc[] src, Func convertFunc)
+ public static TDst[] ConvertArray(this TSrc[] src, Func convertFunc)
{
TDst[] dst = new TDst[src.Length];
for (int i = 0; i < src.Length; i++)
diff --git a/Source/Engine/Graphics/GPUBuffer.cpp b/Source/Engine/Graphics/GPUBuffer.cpp
index 6e400bbdd..6d1ceea41 100644
--- a/Source/Engine/Graphics/GPUBuffer.cpp
+++ b/Source/Engine/Graphics/GPUBuffer.cpp
@@ -12,6 +12,7 @@
#include "Engine/Debug/Exceptions/InvalidOperationException.h"
#include "Engine/Debug/Exceptions/ArgumentNullException.h"
#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h"
+#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/Enums.h"
#include "Engine/Threading/ThreadPoolTask.h"
#include "Engine/Threading/Threading.h"
@@ -81,33 +82,10 @@ bool GPUBufferDescription::Equals(const GPUBufferDescription& other) const
String GPUBufferDescription::ToString() const
{
- // TODO: add tool to Format to string
-
- String flags;
- if (Flags == GPUBufferFlags::None)
- {
- flags = TEXT("None");
- }
- else
- {
- // TODO: create tool to auto convert flag enums to string
-
-#define CONVERT_FLAGS_FLAGS_2_STR(value) if (EnumHasAnyFlags(Flags, GPUBufferFlags::value)) { if (flags.HasChars()) flags += TEXT('|'); flags += TEXT(#value); }
- CONVERT_FLAGS_FLAGS_2_STR(ShaderResource);
- CONVERT_FLAGS_FLAGS_2_STR(VertexBuffer);
- CONVERT_FLAGS_FLAGS_2_STR(IndexBuffer);
- CONVERT_FLAGS_FLAGS_2_STR(UnorderedAccess);
- CONVERT_FLAGS_FLAGS_2_STR(Append);
- CONVERT_FLAGS_FLAGS_2_STR(Counter);
- CONVERT_FLAGS_FLAGS_2_STR(Argument);
- CONVERT_FLAGS_FLAGS_2_STR(Structured);
-#undef CONVERT_FLAGS_FLAGS_2_STR
- }
-
return String::Format(TEXT("Size: {0}, Stride: {1}, Flags: {2}, Format: {3}, Usage: {4}"),
Size,
Stride,
- flags,
+ ScriptingEnum::ToStringFlags(Flags),
ScriptingEnum::ToString(Format),
(int32)Usage);
}
@@ -212,7 +190,7 @@ GPUBuffer* GPUBuffer::ToStagingUpload() const
bool GPUBuffer::Resize(uint32 newSize)
{
- // Validate input
+ PROFILE_CPU();
if (!IsAllocated())
{
Log::InvalidOperationException(TEXT("Buffer.Resize"));
@@ -236,12 +214,12 @@ bool GPUBuffer::DownloadData(BytesContainer& result)
LOG(Warning, "Cannot download GPU buffer data from an empty buffer.");
return true;
}
-
if (_desc.Usage == GPUResourceUsage::StagingReadback || _desc.Usage == GPUResourceUsage::Dynamic)
{
// Use faster path for staging resources
return GetData(result);
}
+ PROFILE_CPU();
// Ensure not running on main thread
if (IsInMainThread())
@@ -358,6 +336,7 @@ Task* GPUBuffer::DownloadDataAsync(BytesContainer& result)
bool GPUBuffer::GetData(BytesContainer& output)
{
+ PROFILE_CPU();
void* mapped = Map(GPUResourceMapMode::Read);
if (!mapped)
return true;
@@ -368,6 +347,7 @@ bool GPUBuffer::GetData(BytesContainer& output)
void GPUBuffer::SetData(const void* data, uint32 size)
{
+ PROFILE_CPU();
if (size == 0 || data == nullptr)
{
Log::ArgumentNullException(TEXT("Buffer.SetData"));
diff --git a/Source/Engine/Graphics/Textures/GPUTexture.cpp b/Source/Engine/Graphics/Textures/GPUTexture.cpp
index 403f545ba..93642f00a 100644
--- a/Source/Engine/Graphics/Textures/GPUTexture.cpp
+++ b/Source/Engine/Graphics/Textures/GPUTexture.cpp
@@ -15,6 +15,7 @@
#include "Engine/Graphics/GPULimits.h"
#include "Engine/Threading/ThreadPoolTask.h"
#include "Engine/Graphics/GPUDevice.h"
+#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/Enums.h"
namespace
@@ -158,29 +159,6 @@ bool GPUTextureDescription::Equals(const GPUTextureDescription& other) const
String GPUTextureDescription::ToString() const
{
- // TODO: add tool to Format to string
-
- String flags;
- if (Flags == GPUTextureFlags::None)
- {
- flags = TEXT("None");
- }
- else
- {
- // TODO: create tool to auto convert flag enums to string
-
-#define CONVERT_FLAGS_FLAGS_2_STR(value) if (EnumHasAnyFlags(Flags, GPUTextureFlags::value)) { if (flags.HasChars()) flags += TEXT('|'); flags += TEXT(#value); }
- CONVERT_FLAGS_FLAGS_2_STR(ShaderResource);
- CONVERT_FLAGS_FLAGS_2_STR(RenderTarget);
- CONVERT_FLAGS_FLAGS_2_STR(UnorderedAccess);
- CONVERT_FLAGS_FLAGS_2_STR(DepthStencil);
- CONVERT_FLAGS_FLAGS_2_STR(PerMipViews);
- CONVERT_FLAGS_FLAGS_2_STR(PerSliceViews);
- CONVERT_FLAGS_FLAGS_2_STR(ReadOnlyDepthView);
- CONVERT_FLAGS_FLAGS_2_STR(BackBuffer);
-#undef CONVERT_FLAGS_FLAGS_2_STR
- }
-
return String::Format(TEXT("Size: {0}x{1}x{2}[{3}], Type: {4}, Mips: {5}, Format: {6}, MSAA: {7}, Flags: {8}, Usage: {9}"),
Width,
Height,
@@ -190,7 +168,7 @@ String GPUTextureDescription::ToString() const
MipLevels,
ScriptingEnum::ToString(Format),
::ToString(MultiSampleLevel),
- flags,
+ ScriptingEnum::ToStringFlags(Flags),
(int32)Usage);
}
@@ -544,7 +522,7 @@ GPUTexture* GPUTexture::ToStagingUpload() const
bool GPUTexture::Resize(int32 width, int32 height, int32 depth, PixelFormat format)
{
- // Validate texture is created
+ PROFILE_CPU();
if (!IsAllocated())
{
LOG(Warning, "Cannot resize not created textures.");
@@ -608,6 +586,7 @@ GPUTask* GPUTexture::UploadMipMapAsync(const BytesContainer& data, int32 mipInde
GPUTask* GPUTexture::UploadMipMapAsync(const BytesContainer& data, int32 mipIndex, int32 rowPitch, int32 slicePitch, bool copyData)
{
+ PROFILE_CPU();
ASSERT(IsAllocated());
ASSERT(mipIndex < MipLevels() && data.IsValid());
ASSERT(data.Length() >= slicePitch);
@@ -699,6 +678,7 @@ bool GPUTexture::DownloadData(TextureData& result)
{
MISSING_CODE("support volume texture data downloading.");
}
+ PROFILE_CPU();
// Use faster path for staging resources
if (IsStaging())
@@ -780,6 +760,7 @@ Task* GPUTexture::DownloadDataAsync(TextureData& result)
{
MISSING_CODE("support volume texture data downloading.");
}
+ PROFILE_CPU();
// Use faster path for staging resources
if (IsStaging())
diff --git a/Source/Engine/Level/Actor.cs b/Source/Engine/Level/Actor.cs
index dbe8a89b5..1d64ff5ae 100644
--- a/Source/Engine/Level/Actor.cs
+++ b/Source/Engine/Level/Actor.cs
@@ -269,7 +269,7 @@ namespace FlaxEngine
{
return FindActor(typeof(T), name) as T;
}
-
+
///
/// Tries to find actor of the given type and tag in this actor hierarchy (checks this actor and all children hierarchy).
///
@@ -386,5 +386,9 @@ namespace FlaxEngine
{
return $"{Name} ({GetType().Name})";
}
+
+#if FLAX_EDITOR
+ internal bool ShowTransform => !(this is UIControl);
+#endif
}
}
diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h
index 0ce9a0dbc..f66d13f40 100644
--- a/Source/Engine/Level/Actor.h
+++ b/Source/Engine/Level/Actor.h
@@ -534,9 +534,7 @@ public:
///
/// Gets actor direction vector (forward vector).
///
- /// The result value.
- API_PROPERTY(Attributes="HideInEditor, NoSerialize")
- FORCE_INLINE Float3 GetDirection() const
+ API_PROPERTY(Attributes="HideInEditor, NoSerialize") FORCE_INLINE Float3 GetDirection() const
{
return Float3::Transform(Float3::Forward, GetOrientation());
}
@@ -571,7 +569,7 @@ public:
///
/// Gets local position of the actor in parent actor space.
///
- API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Position\"), DefaultValue(typeof(Vector3), \"0,0,0\"), EditorOrder(-30), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+PositionEditor\")")
+ API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Position\"), VisibleIf(\"ShowTransform\"), DefaultValue(typeof(Vector3), \"0,0,0\"), EditorOrder(-30), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+PositionEditor\")")
FORCE_INLINE Vector3 GetLocalPosition() const
{
return _localTransform.Translation;
@@ -587,7 +585,7 @@ public:
/// Gets local rotation of the actor in parent actor space.
///
/// Actor.LocalOrientation *= Quaternion.Euler(0, 10 * Time.DeltaTime, 0)
- API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Rotation\"), DefaultValue(typeof(Quaternion), \"0,0,0,1\"), EditorOrder(-20), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+OrientationEditor\")")
+ API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Rotation\"), VisibleIf(\"ShowTransform\"), DefaultValue(typeof(Quaternion), \"0,0,0,1\"), EditorOrder(-20), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+OrientationEditor\")")
FORCE_INLINE Quaternion GetLocalOrientation() const
{
return _localTransform.Orientation;
@@ -602,7 +600,7 @@ public:
///
/// Gets local scale vector of the actor in parent actor space.
///
- API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Scale\"), DefaultValue(typeof(Float3), \"1,1,1\"), Limit(float.MinValue, float.MaxValue, 0.01f), EditorOrder(-10), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+ScaleEditor\")")
+ API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Scale\"), VisibleIf(\"ShowTransform\"), DefaultValue(typeof(Float3), \"1,1,1\"), Limit(float.MinValue, float.MaxValue, 0.01f), EditorOrder(-10), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+ScaleEditor\")")
FORCE_INLINE Float3 GetLocalScale() const
{
return _localTransform.Scale;
diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp
index b2db80b0b..a35acb066 100644
--- a/Source/Engine/Physics/Actors/Cloth.cpp
+++ b/Source/Engine/Physics/Actors/Cloth.cpp
@@ -415,9 +415,9 @@ void Cloth::OnDebugDrawSelected()
c1 = Color::Lerp(Color::Red, Color::White, _paint[i1]);
c2 = Color::Lerp(Color::Red, Color::White, _paint[i2]);
}
- DebugDraw::DrawLine(v0, v1, c0, c1, 0, false);
- DebugDraw::DrawLine(v1, v2, c1, c2, 0, false);
- DebugDraw::DrawLine(v2, v0, c2, c0, 0, false);
+ DebugDraw::DrawLine(v0, v1, c0, c1, 0, DebugDrawDepthTest);
+ DebugDraw::DrawLine(v1, v2, c1, c2, 0, DebugDrawDepthTest);
+ DebugDraw::DrawLine(v2, v0, c2, c0, 0, DebugDrawDepthTest);
}
PhysicsBackend::UnlockClothParticles(_cloth);
}
diff --git a/Source/Engine/Physics/Actors/Cloth.h b/Source/Engine/Physics/Actors/Cloth.h
index 6137ec3b6..1b97330e0 100644
--- a/Source/Engine/Physics/Actors/Cloth.h
+++ b/Source/Engine/Physics/Actors/Cloth.h
@@ -332,6 +332,11 @@ public:
bool OnPreUpdate();
void OnPostUpdate();
+private:
+#if USE_EDITOR
+ API_FIELD(Internal) bool DebugDrawDepthTest = true;
+#endif
+
public:
// [Actor]
void Draw(RenderContext& renderContext) override;
diff --git a/Source/Engine/Physics/Actors/PhysicsColliderActor.h b/Source/Engine/Physics/Actors/PhysicsColliderActor.h
index caea76c79..92693695d 100644
--- a/Source/Engine/Physics/Actors/PhysicsColliderActor.h
+++ b/Source/Engine/Physics/Actors/PhysicsColliderActor.h
@@ -5,6 +5,7 @@
#include "Engine/Level/Actor.h"
#include "Engine/Physics/Collisions.h"
+struct RayCastHit;
struct Collision;
///
@@ -42,6 +43,40 @@ public:
/// The rigid body or null.
API_PROPERTY() virtual RigidBody* GetAttachedRigidBody() const = 0;
+ ///
+ /// Performs a raycast against this collider shape.
+ ///
+ /// The origin of the ray.
+ /// The normalized direction of the ray.
+ /// The raycast result hit position distance from the ray origin. Valid only if raycast hits anything.
+ /// The maximum distance the ray should check for collisions.
+ /// True if ray hits an object, otherwise false.
+ API_FUNCTION(Sealed) virtual bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, float maxDistance = MAX_float) const = 0;
+
+ ///
+ /// Performs a raycast against this collider, returns results in a RaycastHit structure.
+ ///
+ /// The origin of the ray.
+ /// The normalized direction of the ray.
+ /// The result hit information. Valid only when method returns true.
+ /// The maximum distance the ray should check for collisions.
+ /// True if ray hits an object, otherwise false.
+ API_FUNCTION(Sealed) virtual bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float) const = 0;
+
+ ///
+ /// Gets a point on the collider that is closest to a given location. Can be used to find a hit location or position to apply explosion force or any other special effects.
+ ///
+ /// The position to find the closest point to it.
+ /// The result point on the collider that is closest to the specified location.
+ API_FUNCTION(Sealed) virtual void ClosestPoint(const Vector3& point, API_PARAM(Out) Vector3& result) const = 0;
+
+ ///
+ /// Checks if a point is inside the collider.
+ ///
+ /// The point to check if is contained by the collider shape (in world-space).
+ /// True if collider shape contains a given point, otherwise false.
+ API_FUNCTION(Sealed) virtual bool ContainsPoint(const Vector3& point) const = 0;
+
public:
///
/// Called when a collision start gets registered for this collider (it collides with something).
diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp
index 9b36a92ff..e9f86ebab 100644
--- a/Source/Engine/Physics/Colliders/CharacterController.cpp
+++ b/Source/Engine/Physics/Colliders/CharacterController.cpp
@@ -206,7 +206,7 @@ void CharacterController::CreateController()
_cachedScale = GetScale();
const float scaling = _cachedScale.GetAbsolute().MaxValue();
const Vector3 position = _transform.LocalToWorld(_center);
- _controller = PhysicsBackend::CreateController(GetPhysicsScene()->GetPhysicsScene(), this, this, _contactOffset, position, _slopeLimit, (int32)_nonWalkableMode, Material.Get(), Math::Abs(_radius) * scaling, Math::Abs(_height) * scaling, _stepOffset, _shape);
+ _controller = PhysicsBackend::CreateController(GetPhysicsScene()->GetPhysicsScene(), this, this, _contactOffset, position, _slopeLimit, (int32)_nonWalkableMode, Material, Math::Abs(_radius) * scaling, Math::Abs(_height) * scaling, _stepOffset, _shape);
// Setup
PhysicsBackend::SetControllerUpDirection(_controller, _upDirection);
@@ -280,12 +280,8 @@ void CharacterController::OnActiveTransformChanged()
// Change actor transform (but with locking)
ASSERT(!_isUpdatingTransform);
_isUpdatingTransform = true;
- Transform transform;
- PhysicsBackend::GetRigidActorPose(PhysicsBackend::GetShapeActor(_shape), transform.Translation, transform.Orientation);
- transform.Translation -= _center;
- transform.Orientation = _transform.Orientation;
- transform.Scale = _transform.Scale;
- SetTransform(transform);
+ const Vector3 position = PhysicsBackend::GetControllerPosition(_controller) - _center;
+ SetPosition(position);
_isUpdatingTransform = false;
UpdateBounds();
diff --git a/Source/Engine/Physics/Colliders/Collider.cpp b/Source/Engine/Physics/Colliders/Collider.cpp
index 4326a033d..e354057d2 100644
--- a/Source/Engine/Physics/Colliders/Collider.cpp
+++ b/Source/Engine/Physics/Colliders/Collider.cpp
@@ -201,7 +201,7 @@ void Collider::CreateShape()
// Create shape
const bool isTrigger = _isTrigger && CanBeTrigger();
- _shape = PhysicsBackend::CreateShape(this, shape, Material.Get(), IsActiveInHierarchy(), isTrigger);
+ _shape = PhysicsBackend::CreateShape(this, shape, Material, IsActiveInHierarchy(), isTrigger);
PhysicsBackend::SetShapeContactOffset(_shape, _contactOffset);
UpdateLayerBits();
}
@@ -288,7 +288,7 @@ void Collider::OnMaterialChanged()
{
// Update the shape material
if (_shape)
- PhysicsBackend::SetShapeMaterial(_shape, Material.Get());
+ PhysicsBackend::SetShapeMaterial(_shape, Material);
}
void Collider::BeginPlay(SceneBeginData* data)
diff --git a/Source/Engine/Physics/Colliders/Collider.h b/Source/Engine/Physics/Colliders/Collider.h
index bd17aa27a..68f5fe346 100644
--- a/Source/Engine/Physics/Colliders/Collider.h
+++ b/Source/Engine/Physics/Colliders/Collider.h
@@ -4,7 +4,7 @@
#include "Engine/Physics/Types.h"
#include "Engine/Content/JsonAsset.h"
-#include "Engine/Content/AssetReference.h"
+#include "Engine/Content/JsonAssetReference.h"
#include "Engine/Physics/Actors/PhysicsColliderActor.h"
struct RayCastHit;
@@ -80,44 +80,10 @@ public:
///
/// The physical material used to define the collider physical properties.
///
- API_FIELD(Attributes="EditorOrder(2), DefaultValue(null), AssetReference(typeof(PhysicalMaterial), true), EditorDisplay(\"Collider\")")
- AssetReference Material;
+ API_FIELD(Attributes="EditorOrder(2), DefaultValue(null), EditorDisplay(\"Collider\")")
+ JsonAssetReference Material;
public:
- ///
- /// Performs a raycast against this collider shape.
- ///
- /// The origin of the ray.
- /// The normalized direction of the ray.
- /// The raycast result hit position distance from the ray origin. Valid only if raycast hits anything.
- /// The maximum distance the ray should check for collisions.
- /// True if ray hits an object, otherwise false.
- API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, float maxDistance = MAX_float) const;
-
- ///
- /// Performs a raycast against this collider, returns results in a RaycastHit structure.
- ///
- /// The origin of the ray.
- /// The normalized direction of the ray.
- /// The result hit information. Valid only when method returns true.
- /// The maximum distance the ray should check for collisions.
- /// True if ray hits an object, otherwise false.
- API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float) const;
-
- ///
- /// Gets a point on the collider that is closest to a given location. Can be used to find a hit location or position to apply explosion force or any other special effects.
- ///
- /// The position to find the closest point to it.
- /// The result point on the collider that is closest to the specified location.
- API_FUNCTION() void ClosestPoint(const Vector3& point, API_PARAM(Out) Vector3& result) const;
-
- ///
- /// Checks if a point is inside the collider.
- ///
- /// The point to check if is contained by the collider shape (in world-space).
- /// True if collider shape contains a given point, otherwise false.
- API_FUNCTION() bool ContainsPoint(const Vector3& point) const;
-
///
/// Computes minimum translational distance between two geometry objects.
/// Translating the first collider by direction * distance will separate the colliders apart if the function returned true. Otherwise, direction and distance are not defined.
@@ -198,6 +164,10 @@ private:
public:
// [PhysicsColliderActor]
RigidBody* GetAttachedRigidBody() const override;
+ bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, float maxDistance = MAX_float) const final;
+ bool RayCast(const Vector3& origin, const Vector3& direction, RayCastHit& hitInfo, float maxDistance = MAX_float) const final;
+ void ClosestPoint(const Vector3& point, Vector3& result) const final;
+ bool ContainsPoint(const Vector3& point) const final;
protected:
// [PhysicsColliderActor]
diff --git a/Source/Engine/Physics/Collisions.h b/Source/Engine/Physics/Collisions.h
index 230337bc9..c1a2679a4 100644
--- a/Source/Engine/Physics/Collisions.h
+++ b/Source/Engine/Physics/Collisions.h
@@ -58,9 +58,7 @@ API_STRUCT(NoDefault) struct FLAXENGINE_API Collision
///
/// The total impulse applied to this contact pair to resolve the collision.
///
- ///
- /// The total impulse is obtained by summing up impulses applied at all contact points in this collision pair.
- ///
+ /// The total impulse is obtained by summing up impulses applied at all contact points in this collision pair.
API_FIELD() Vector3 Impulse;
///
@@ -87,9 +85,7 @@ public:
///
/// Gets the relative linear velocity of the two colliding objects.
///
- ///
- /// Can be used to detect stronger collisions.
- ///
+ /// Can be used to detect stronger collisions.
Vector3 GetRelativeVelocity() const
{
return ThisVelocity - OtherVelocity;
diff --git a/Source/Engine/Physics/Joints/D6Joint.cpp b/Source/Engine/Physics/Joints/D6Joint.cpp
index a5ef035a3..685fa760a 100644
--- a/Source/Engine/Physics/Joints/D6Joint.cpp
+++ b/Source/Engine/Physics/Joints/D6Joint.cpp
@@ -159,7 +159,8 @@ void D6Joint::OnDebugDrawSelected()
const float twistSize = 9.0f;
const Color swingColor = Color::Green.AlphaMultiplied(0.6f);
const Color twistColor = Color::Yellow.AlphaMultiplied(0.5f);
- DEBUG_DRAW_WIRE_ARROW(target, targetRotation, swingSize / 100.0f * 0.5f, Color::Red, 0, false);
+ const float arrowSize = swingSize / 100.0f * 0.5f;
+ DEBUG_DRAW_WIRE_ARROW(target, targetRotation, arrowSize, arrowSize * 0.5f, Color::Red, 0, false);
if (_motion[(int32)D6JointAxis::SwingY] == D6JointMotion::Locked && _motion[(int32)D6JointAxis::SwingZ] == D6JointMotion::Locked)
{
// Swing is locked
diff --git a/Source/Engine/Physics/Joints/HingeJoint.cpp b/Source/Engine/Physics/Joints/HingeJoint.cpp
index 7f85941ef..6952711e8 100644
--- a/Source/Engine/Physics/Joints/HingeJoint.cpp
+++ b/Source/Engine/Physics/Joints/HingeJoint.cpp
@@ -63,8 +63,9 @@ void HingeJoint::OnDebugDrawSelected()
const Quaternion targetRotation = GetTargetOrientation() * xRotation;
const float size = 15.0f;
const Color color = Color::Green.AlphaMultiplied(0.6f);
- DEBUG_DRAW_WIRE_ARROW(source, sourceRotation, size / 100.0f * 0.5f, Color::Red, 0, false);
- DEBUG_DRAW_WIRE_ARROW(target, targetRotation, size / 100.0f * 0.5f, Color::Blue, 0, false);
+ const float arrowSize = size / 100.0f * 0.5f;
+ DEBUG_DRAW_WIRE_ARROW(source, sourceRotation, arrowSize, arrowSize * 0.5f, Color::Red, 0, false);
+ DEBUG_DRAW_WIRE_ARROW(target, targetRotation, arrowSize, arrowSize * 0.5f, Color::Blue, 0, false);
if (EnumHasAnyFlags(_flags, HingeJointFlag::Limit))
{
const float upper = Math::Max(_limit.Upper, _limit.Lower);
diff --git a/Source/Engine/Physics/Joints/SphericalJoint.cpp b/Source/Engine/Physics/Joints/SphericalJoint.cpp
index c44d4bc6e..84ab9d6e9 100644
--- a/Source/Engine/Physics/Joints/SphericalJoint.cpp
+++ b/Source/Engine/Physics/Joints/SphericalJoint.cpp
@@ -38,8 +38,9 @@ void SphericalJoint::OnDebugDrawSelected()
const Vector3 source = GetPosition();
const Vector3 target = GetTargetPosition();
const float size = 15.0f;
+ const float arrowSize = size / 100.0f * 0.5f;
const Color color = Color::Green.AlphaMultiplied(0.6f);
- DEBUG_DRAW_WIRE_ARROW(source, GetOrientation(), size / 100.0f * 0.5f, Color::Red, 0, false);
+ DEBUG_DRAW_WIRE_ARROW(source, GetOrientation(), arrowSize, arrowSize * 0.5f, Color::Red, 0, false);
if (EnumHasAnyFlags(_flags, SphericalJointFlag::Limit))
{
DEBUG_DRAW_CONE(source, GetOrientation(), size, _limit.YLimitAngle * DegreesToRadians, _limit.ZLimitAngle * DegreesToRadians, color, 0, false);
diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp
index 99a5abc56..75c088db7 100644
--- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp
+++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp
@@ -228,9 +228,7 @@ class QueryFilterPhysX : public PxQueryFilterCallback
// Check mask
const PxFilterData shapeFilter = shape->getQueryFilterData();
if ((filterData.word0 & shapeFilter.word0) == 0)
- {
return PxQueryHitType::eNONE;
- }
// Check if skip triggers
const bool hitTriggers = filterData.word2 != 0;
@@ -483,8 +481,10 @@ protected:
}
};
+#define PxHitFlagEmpty (PxHitFlags)0
+#define SCENE_QUERY_FLAGS (PxHitFlag::ePOSITION | PxHitFlag::eNORMAL | PxHitFlag::eFACE_INDEX | PxHitFlag::eUV)
+
#define SCENE_QUERY_SETUP(blockSingle) auto scenePhysX = (ScenePhysX*)scene; if (scene == nullptr) return false; \
- const PxHitFlags hitFlags = PxHitFlag::ePOSITION | PxHitFlag::eNORMAL | PxHitFlag::eUV; \
PxQueryFilterData filterData; \
filterData.flags |= PxQueryFlag::ePREFILTER; \
filterData.data.word0 = layerMask; \
@@ -644,6 +644,19 @@ void GetShapeGeometry(const CollisionShape& shape, PxGeometryHolder& geometry)
}
}
+void GetShapeMaterials(Array>& materialsPhysX, Span materials)
+{
+ materialsPhysX.Resize(materials.Length());
+ for (int32 i = 0; i < materials.Length(); i++)
+ {
+ PxMaterial* materialPhysX = DefaultMaterial;
+ const JsonAsset* material = materials.Get()[i];
+ if (material && !material->WaitForLoaded() && material->Instance)
+ materialPhysX = (PxMaterial*)((PhysicalMaterial*)material->Instance)->GetPhysicsMaterial();
+ materialsPhysX.Get()[i] = materialPhysX;
+ }
+}
+
PxFilterFlags FilterShader(
PxFilterObjectAttributes attributes0, PxFilterData filterData0,
PxFilterObjectAttributes attributes1, PxFilterData filterData1,
@@ -1735,8 +1748,6 @@ void PhysicsBackend::EndSimulateScene(void* scene)
{
PROFILE_CPU_NAMED("Physics.SendEvents");
-
- scenePhysX->EventsCallback.CollectResults();
scenePhysX->EventsCallback.SendTriggerEvents();
scenePhysX->EventsCallback.SendCollisionEvents();
scenePhysX->EventsCallback.SendJointEvents();
@@ -1880,14 +1891,14 @@ bool PhysicsBackend::RayCast(void* scene, const Vector3& origin, const Vector3&
{
SCENE_QUERY_SETUP(true);
PxRaycastBuffer buffer;
- return scenePhysX->Scene->raycast(C2P(origin - scenePhysX->Origin), C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter);
+ return scenePhysX->Scene->raycast(C2P(origin - scenePhysX->Origin), C2P(direction), maxDistance, buffer, PxHitFlagEmpty, filterData, &QueryFilter);
}
bool PhysicsBackend::RayCast(void* scene, const Vector3& origin, const Vector3& direction, RayCastHit& hitInfo, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
SCENE_QUERY_SETUP(true);
PxRaycastBuffer buffer;
- if (!scenePhysX->Scene->raycast(C2P(origin - scenePhysX->Origin), C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter))
+ if (!scenePhysX->Scene->raycast(C2P(origin - scenePhysX->Origin), C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter))
return false;
SCENE_QUERY_COLLECT_SINGLE();
return true;
@@ -1897,7 +1908,7 @@ bool PhysicsBackend::RayCastAll(void* scene, const Vector3& origin, const Vector
{
SCENE_QUERY_SETUP(false);
DynamicHitBuffer buffer;
- if (!scenePhysX->Scene->raycast(C2P(origin - scenePhysX->Origin), C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter))
+ if (!scenePhysX->Scene->raycast(C2P(origin - scenePhysX->Origin), C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter))
return false;
SCENE_QUERY_COLLECT_ALL();
return true;
@@ -1908,7 +1919,7 @@ bool PhysicsBackend::BoxCast(void* scene, const Vector3& center, const Vector3&
SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation));
const PxBoxGeometry geometry(C2P(halfExtents));
- return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter);
+ return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, PxHitFlagEmpty, filterData, &QueryFilter);
}
bool PhysicsBackend::BoxCast(void* scene, const Vector3& center, const Vector3& halfExtents, const Vector3& direction, RayCastHit& hitInfo, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
@@ -1916,7 +1927,7 @@ bool PhysicsBackend::BoxCast(void* scene, const Vector3& center, const Vector3&
SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation));
const PxBoxGeometry geometry(C2P(halfExtents));
- if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter))
+ if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter))
return false;
SCENE_QUERY_COLLECT_SINGLE();
return true;
@@ -1927,7 +1938,7 @@ bool PhysicsBackend::BoxCastAll(void* scene, const Vector3& center, const Vector
SCENE_QUERY_SETUP_SWEEP();
const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation));
const PxBoxGeometry geometry(C2P(halfExtents));
- if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter))
+ if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter))
return false;
SCENE_QUERY_COLLECT_ALL();
return true;
@@ -1938,7 +1949,7 @@ bool PhysicsBackend::SphereCast(void* scene, const Vector3& center, const float
SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center - scenePhysX->Origin));
const PxSphereGeometry geometry(radius);
- return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter);
+ return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, PxHitFlagEmpty, filterData, &QueryFilter);
}
bool PhysicsBackend::SphereCast(void* scene, const Vector3& center, const float radius, const Vector3& direction, RayCastHit& hitInfo, const float maxDistance, uint32 layerMask, bool hitTriggers)
@@ -1946,7 +1957,7 @@ bool PhysicsBackend::SphereCast(void* scene, const Vector3& center, const float
SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center - scenePhysX->Origin));
const PxSphereGeometry geometry(radius);
- if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter))
+ if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter))
return false;
SCENE_QUERY_COLLECT_SINGLE();
return true;
@@ -1957,7 +1968,7 @@ bool PhysicsBackend::SphereCastAll(void* scene, const Vector3& center, const flo
SCENE_QUERY_SETUP_SWEEP();
const PxTransform pose(C2P(center - scenePhysX->Origin));
const PxSphereGeometry geometry(radius);
- if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter))
+ if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter))
return false;
SCENE_QUERY_COLLECT_ALL();
return true;
@@ -1968,7 +1979,7 @@ bool PhysicsBackend::CapsuleCast(void* scene, const Vector3& center, const float
SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation));
const PxCapsuleGeometry geometry(radius, height * 0.5f);
- return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter);
+ return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, PxHitFlagEmpty, filterData, &QueryFilter);
}
bool PhysicsBackend::CapsuleCast(void* scene, const Vector3& center, const float radius, const float height, const Vector3& direction, RayCastHit& hitInfo, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
@@ -1976,7 +1987,7 @@ bool PhysicsBackend::CapsuleCast(void* scene, const Vector3& center, const float
SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation));
const PxCapsuleGeometry geometry(radius, height * 0.5f);
- if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter))
+ if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter))
return false;
SCENE_QUERY_COLLECT_SINGLE();
return true;
@@ -1987,7 +1998,7 @@ bool PhysicsBackend::CapsuleCastAll(void* scene, const Vector3& center, const fl
SCENE_QUERY_SETUP_SWEEP();
const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation));
const PxCapsuleGeometry geometry(radius, height * 0.5f);
- if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter))
+ if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter))
return false;
SCENE_QUERY_COLLECT_ALL();
return true;
@@ -1999,7 +2010,7 @@ bool PhysicsBackend::ConvexCast(void* scene, const Vector3& center, const Collis
SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation));
const PxConvexMeshGeometry geometry((PxConvexMesh*)convexMesh->GetConvex(), PxMeshScale(C2P(scale)));
- return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter);
+ return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, PxHitFlagEmpty, filterData, &QueryFilter);
}
bool PhysicsBackend::ConvexCast(void* scene, const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, RayCastHit& hitInfo, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
@@ -2008,7 +2019,7 @@ bool PhysicsBackend::ConvexCast(void* scene, const Vector3& center, const Collis
SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation));
const PxConvexMeshGeometry geometry((PxConvexMesh*)convexMesh->GetConvex(), PxMeshScale(C2P(scale)));
- if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter))
+ if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter))
return false;
SCENE_QUERY_COLLECT_SINGLE();
return true;
@@ -2020,7 +2031,7 @@ bool PhysicsBackend::ConvexCastAll(void* scene, const Vector3& center, const Col
SCENE_QUERY_SETUP_SWEEP();
const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation));
const PxConvexMeshGeometry geometry((PxConvexMesh*)convexMesh->GetConvex(), PxMeshScale(C2P(scale)));
- if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter))
+ if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter))
return false;
SCENE_QUERY_COLLECT_ALL();
return true;
@@ -2451,17 +2462,14 @@ void PhysicsBackend::AddRigidDynamicActorTorque(void* actor, const Vector3& torq
actorPhysX->addTorque(C2P(torque), static_cast(mode));
}
-void* PhysicsBackend::CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, JsonAsset* material, bool enabled, bool trigger)
+void* PhysicsBackend::CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, Span materials, bool enabled, bool trigger)
{
const PxShapeFlags shapeFlags = GetShapeFlags(trigger, enabled);
- PxMaterial* materialPhysX = DefaultMaterial;
- if (material && !material->WaitForLoaded() && material->Instance)
- {
- materialPhysX = (PxMaterial*)((PhysicalMaterial*)material->Instance)->GetPhysicsMaterial();
- }
+ Array> materialsPhysX;
+ GetShapeMaterials(materialsPhysX, materials);
PxGeometryHolder geometryPhysX;
GetShapeGeometry(geometry, geometryPhysX);
- PxShape* shapePhysX = PhysX->createShape(geometryPhysX.any(), *materialPhysX, true, shapeFlags);
+ PxShape* shapePhysX = PhysX->createShape(geometryPhysX.any(), materialsPhysX.Get(), materialsPhysX.Count(), true, shapeFlags);
shapePhysX->userData = collider;
#if PHYSX_DEBUG_NAMING
shapePhysX->setName("Shape");
@@ -2551,15 +2559,12 @@ void PhysicsBackend::SetShapeContactOffset(void* shape, float value)
shapePhysX->setContactOffset(Math::Max(shapePhysX->getRestOffset() + ZeroTolerance, value));
}
-void PhysicsBackend::SetShapeMaterial(void* shape, JsonAsset* material)
+void PhysicsBackend::SetShapeMaterials(void* shape, Span materials)
{
auto shapePhysX = (PxShape*)shape;
- PxMaterial* materialPhysX = DefaultMaterial;
- if (material && !material->WaitForLoaded() && material->Instance)
- {
- materialPhysX = (PxMaterial*)((PhysicalMaterial*)material->Instance)->GetPhysicsMaterial();
- }
- shapePhysX->setMaterials(&materialPhysX, 1);
+ Array> materialsPhysX;
+ GetShapeMaterials(materialsPhysX, materials);
+ shapePhysX->setMaterials(materialsPhysX.Get(), materialsPhysX.Count());
}
void PhysicsBackend::SetShapeGeometry(void* shape, const CollisionShape& geometry)
@@ -2608,9 +2613,8 @@ bool PhysicsBackend::RayCastShape(void* shape, const Vector3& position, const Qu
auto shapePhysX = (PxShape*)shape;
const Vector3 sceneOrigin = SceneOrigins[shapePhysX->getActor() ? shapePhysX->getActor()->getScene() : nullptr];
const PxTransform trans(C2P(position - sceneOrigin), C2P(orientation));
- const PxHitFlags hitFlags = (PxHitFlags)0;
PxRaycastHit hit;
- if (PxGeometryQuery::raycast(C2P(origin - sceneOrigin), C2P(direction), shapePhysX->getGeometry(), trans, maxDistance, hitFlags, 1, &hit) != 0)
+ if (PxGeometryQuery::raycast(C2P(origin - sceneOrigin), C2P(direction), shapePhysX->getGeometry(), trans, maxDistance, PxHitFlagEmpty, 1, &hit) != 0)
{
resultHitDistance = hit.distance;
return true;
@@ -2623,10 +2627,10 @@ bool PhysicsBackend::RayCastShape(void* shape, const Vector3& position, const Qu
auto shapePhysX = (PxShape*)shape;
const Vector3 sceneOrigin = SceneOrigins[shapePhysX->getActor() ? shapePhysX->getActor()->getScene() : nullptr];
const PxTransform trans(C2P(position - sceneOrigin), C2P(orientation));
- const PxHitFlags hitFlags = PxHitFlag::ePOSITION | PxHitFlag::eNORMAL | PxHitFlag::eFACE_INDEX | PxHitFlag::eUV;
PxRaycastHit hit;
- if (PxGeometryQuery::raycast(C2P(origin - sceneOrigin), C2P(direction), shapePhysX->getGeometry(), trans, maxDistance, hitFlags, 1, &hit) == 0)
+ if (PxGeometryQuery::raycast(C2P(origin - sceneOrigin), C2P(direction), shapePhysX->getGeometry(), trans, maxDistance, SCENE_QUERY_FLAGS, 1, &hit) == 0)
return false;
+ hit.shape = shapePhysX;
P2C(hit, hitInfo);
hitInfo.Point += sceneOrigin;
return true;
@@ -3004,7 +3008,7 @@ void* PhysicsBackend::CreateController(void* scene, IPhysicsActor* actor, Physic
desc.material = DefaultMaterial;
const float minSize = 0.001f;
desc.height = Math::Max(height, minSize);
- desc.radius = Math::Max(radius - desc.contactOffset, minSize);
+ desc.radius = Math::Max(radius - Math::Max(contactOffset, 0.0f), minSize);
desc.stepOffset = Math::Min(stepOffset, desc.height + desc.radius * 2.0f - minSize);
auto controllerPhysX = (PxCapsuleController*)scenePhysX->ControllerManager->createController(desc);
PxRigidActor* actorPhysX = controllerPhysX->getActor();
@@ -4081,10 +4085,17 @@ void PhysicsBackend::GetHeightFieldSize(void* heightField, int32& rows, int32& c
columns = (int32)heightFieldPhysX->getNbColumns();
}
-float PhysicsBackend::GetHeightFieldHeight(void* heightField, float x, float z)
+float PhysicsBackend::GetHeightFieldHeight(void* heightField, int32 x, int32 z)
{
auto heightFieldPhysX = (PxHeightField*)heightField;
- return heightFieldPhysX->getHeight(x, z);
+ return heightFieldPhysX->getHeight((float)x, (float)z);
+}
+
+PhysicsBackend::HeightFieldSample PhysicsBackend::GetHeightFieldSample(void* heightField, int32 x, int32 z)
+{
+ auto heightFieldPhysX = (PxHeightField*)heightField;
+ auto sample = heightFieldPhysX->getSample(x, z);
+ return { sample.height, sample.materialIndex0, sample.materialIndex1 };
}
bool PhysicsBackend::ModifyHeightField(void* heightField, int32 startCol, int32 startRow, int32 cols, int32 rows, const HeightFieldSample* data)
diff --git a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp
index 5781e4641..62e588771 100644
--- a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp
+++ b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp
@@ -38,10 +38,6 @@ void SimulationEventCallback::Clear()
BrokenJoints.Clear();
}
-void SimulationEventCallback::CollectResults()
-{
-}
-
void SimulationEventCallback::SendCollisionEvents()
{
for (auto& c : RemovedCollisions)
@@ -132,7 +128,6 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c
//const PxU32 flippedContacts = (pair.flags & PxContactPairFlag::eINTERNAL_CONTACTS_ARE_FLIPPED);
const bool hasImpulses = pair.flags.isSet(PxContactPairFlag::eINTERNAL_HAS_IMPULSES);
const bool hasPostVelocities = !pair.flags.isSet(PxContactPairFlag::eACTOR_PAIR_LOST_TOUCH);
- PxU32 nbContacts = 0;
PxVec3 totalImpulse(0.0f);
c.ThisActor = static_cast(pair.shapes[0]->userData);
@@ -144,48 +139,38 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c
}
// Extract contact points
+ c.ContactsCount = 0;
while (i.hasNextPatch())
{
i.nextPatch();
- while (i.hasNextContact() && nbContacts < COLLISION_NAX_CONTACT_POINTS)
+ while (i.hasNextContact() && c.ContactsCount < COLLISION_NAX_CONTACT_POINTS)
{
i.nextContact();
-
const PxVec3 point = i.getContactPoint();
const PxVec3 normal = i.getContactNormal();
if (hasImpulses)
- totalImpulse += normal * impulses[nbContacts];
+ totalImpulse += normal * impulses[c.ContactsCount];
- //PxU32 internalFaceIndex0 = flippedContacts ? iter.getFaceIndex1() : iter.getFaceIndex0();
- //PxU32 internalFaceIndex1 = flippedContacts ? iter.getFaceIndex0() : iter.getFaceIndex1();
-
- ContactPoint& contact = c.Contacts[nbContacts];
+ ContactPoint& contact = c.Contacts[c.ContactsCount++];
contact.Point = P2C(point);
contact.Normal = P2C(normal);
contact.Separation = i.getSeparation();
-
- nbContacts++;
}
}
+ c.Impulse = P2C(totalImpulse);
// Extract velocities
c.ThisVelocity = c.OtherVelocity = Vector3::Zero;
if (hasPostVelocities && j.nextItemSet())
{
- ASSERT(j.contactPairIndex == pairIndex);
+ ASSERT_LOW_LAYER(j.contactPairIndex == pairIndex);
if (j.postSolverVelocity)
{
- const PxVec3 linearVelocityActor0 = j.postSolverVelocity->linearVelocity[0];
- const PxVec3 linearVelocityActor1 = j.postSolverVelocity->linearVelocity[1];
-
- c.ThisVelocity = P2C(linearVelocityActor0);
- c.OtherVelocity = P2C(linearVelocityActor1);
+ c.ThisVelocity = P2C(j.postSolverVelocity->linearVelocity[0]);
+ c.OtherVelocity = P2C(j.postSolverVelocity->linearVelocity[1]);
}
}
- c.ContactsCount = nbContacts;
- c.Impulse = P2C(totalImpulse);
-
if (pair.flags & PxContactPairFlag::eACTOR_PAIR_HAS_FIRST_TOUCH)
{
NewCollisions.Add(c);
diff --git a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.h b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.h
index f9081df66..f10f926eb 100644
--- a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.h
+++ b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.h
@@ -49,11 +49,6 @@ public:
///
void Clear();
- ///
- /// Generates the new/old/removed collisions and a valid trigger pairs.
- ///
- void CollectResults();
-
///
/// Sends the collision events to the managed objects.
///
diff --git a/Source/Engine/Physics/PhysX/Types.h b/Source/Engine/Physics/PhysX/Types.h
index d95d6b140..c86a03a62 100644
--- a/Source/Engine/Physics/PhysX/Types.h
+++ b/Source/Engine/Physics/PhysX/Types.h
@@ -17,6 +17,7 @@
#include
#include
#include
+#include
#include
namespace physx
@@ -233,12 +234,28 @@ inline float RadPerSToRpm(float v)
return v * (30.0f / PI);
}
+inline PhysicalMaterial* GetMaterial(const PxShape* shape, PxU32 faceIndex)
+{
+ if (faceIndex != 0xFFFFffff)
+ {
+ PxBaseMaterial* mat = shape->getMaterialFromInternalFaceIndex(faceIndex);
+ return mat ? (PhysicalMaterial*)mat->userData : nullptr;
+ }
+ else
+ {
+ PxMaterial* mat;
+ shape->getMaterials(&mat, 1);
+ return mat ? (PhysicalMaterial*)mat->userData : nullptr;
+ }
+}
+
inline void P2C(const PxRaycastHit& hit, RayCastHit& result)
{
result.Point = P2C(hit.position);
result.Normal = P2C(hit.normal);
result.Distance = hit.distance;
result.Collider = hit.shape ? static_cast(hit.shape->userData) : nullptr;
+ result.Material = hit.shape ? GetMaterial(hit.shape, hit.faceIndex) : nullptr;
result.FaceIndex = hit.faceIndex;
result.UV.X = hit.u;
result.UV.Y = hit.v;
@@ -250,6 +267,7 @@ inline void P2C(const PxSweepHit& hit, RayCastHit& result)
result.Normal = P2C(hit.normal);
result.Distance = hit.distance;
result.Collider = hit.shape ? static_cast(hit.shape->userData) : nullptr;
+ result.Material = hit.shape ? GetMaterial(hit.shape, hit.faceIndex) : nullptr;
result.FaceIndex = hit.faceIndex;
result.UV = Vector2::Zero;
}
diff --git a/Source/Engine/Physics/PhysicalMaterial.h b/Source/Engine/Physics/PhysicalMaterial.h
index b9c2e4554..cb8202876 100644
--- a/Source/Engine/Physics/PhysicalMaterial.h
+++ b/Source/Engine/Physics/PhysicalMaterial.h
@@ -4,27 +4,21 @@
#include "Types.h"
#include "Engine/Core/ISerializable.h"
+#include "Engine/Scripting/ScriptingObject.h"
#include "Engine/Level/Tags.h"
///
/// Physical materials are used to define the response of a physical object when interacting dynamically with the world.
///
-API_CLASS(Attributes = "ContentContextMenu(\"New/Physics/Physical Material\")") class FLAXENGINE_API PhysicalMaterial final : public ISerializable
+API_CLASS(Attributes = "ContentContextMenu(\"New/Physics/Physical Material\")")
+class FLAXENGINE_API PhysicalMaterial final : public ScriptingObject, public ISerializable
{
API_AUTO_SERIALIZATION();
- DECLARE_SCRIPTING_TYPE_MINIMAL(PhysicalMaterial);
+ DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(PhysicalMaterial, ScriptingObject);
private:
- void* _material;
+ void* _material = nullptr;
public:
- ///
- /// Initializes a new instance of the class.
- ///
- PhysicalMaterial();
-
- ///
- /// Finalizes an instance of the class.
- ///
~PhysicalMaterial();
public:
diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp
index 5d9b218ec..942f1b79b 100644
--- a/Source/Engine/Physics/Physics.cpp
+++ b/Source/Engine/Physics/Physics.cpp
@@ -78,11 +78,6 @@ void PhysicsSettings::Deserialize(DeserializeStream& stream, ISerializeModifier*
}
}
-PhysicalMaterial::PhysicalMaterial()
- : _material(nullptr)
-{
-}
-
PhysicalMaterial::~PhysicalMaterial()
{
if (_material)
diff --git a/Source/Engine/Physics/PhysicsBackend.h b/Source/Engine/Physics/PhysicsBackend.h
index f20683e31..d7393df2c 100644
--- a/Source/Engine/Physics/PhysicsBackend.h
+++ b/Source/Engine/Physics/PhysicsBackend.h
@@ -4,6 +4,7 @@
#include "Physics.h"
#include "PhysicsSettings.h"
+#include "Engine/Core/Types/Span.h"
struct HingeJointDrive;
struct SpringParameters;
@@ -182,7 +183,7 @@ public:
static void AddRigidDynamicActorTorque(void* actor, const Vector3& torque, ForceMode mode);
// Shapes
- static void* CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, JsonAsset* material, bool enabled, bool trigger);
+ static void* CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, Span materials, bool enabled, bool trigger);
static void SetShapeState(void* shape, bool enabled, bool trigger);
static void SetShapeFilterMask(void* shape, uint32 mask0, uint32 mask1);
static void* GetShapeActor(void* shape);
@@ -191,7 +192,7 @@ public:
static void GetShapeLocalPose(void* shape, Vector3& position, Quaternion& orientation);
static void SetShapeLocalPose(void* shape, const Vector3& position, const Quaternion& orientation);
static void SetShapeContactOffset(void* shape, float value);
- static void SetShapeMaterial(void* shape, JsonAsset* material);
+ static void SetShapeMaterials(void* shape, Span materials);
static void SetShapeGeometry(void* shape, const CollisionShape& geometry);
static void AttachShape(void* shape, void* actor);
static void DetachShape(void* shape, void* actor);
@@ -303,7 +304,8 @@ public:
static void GetTriangleMeshTriangles(void* triangleMesh, Array& vertexBuffer, Array& indexBuffer);
static const uint32* GetTriangleMeshRemap(void* triangleMesh, uint32& count);
static void GetHeightFieldSize(void* heightField, int32& rows, int32& columns);
- static float GetHeightFieldHeight(void* heightField, float x, float z);
+ static float GetHeightFieldHeight(void* heightField, int32 x, int32 z);
+ static HeightFieldSample GetHeightFieldSample(void* heightField, int32 x, int32 z);
static bool ModifyHeightField(void* heightField, int32 startCol, int32 startRow, int32 cols, int32 rows, const HeightFieldSample* data);
static void FlushRequests();
static void FlushRequests(void* scene);
@@ -330,6 +332,14 @@ public:
flags = (RigidDynamicFlags)(((uint32)flags & ~(uint32)flag) | (value ? (uint32)flag : 0));
SetRigidDynamicActorFlags(actor, flags);
}
+ FORCE_INLINE static void* CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, JsonAsset* material, bool enabled, bool trigger)
+ {
+ return CreateShape(collider, geometry, Span(&material, 1), enabled, trigger);
+ }
+ FORCE_INLINE static void SetShapeMaterial(void* shape, JsonAsset* material)
+ {
+ SetShapeMaterials(shape, Span(&material, 1));
+ }
};
DECLARE_ENUM_OPERATORS(PhysicsBackend::ActorFlags);
diff --git a/Source/Engine/Physics/PhysicsBackendEmpty.cpp b/Source/Engine/Physics/PhysicsBackendEmpty.cpp
index 1a418af0c..e26898494 100644
--- a/Source/Engine/Physics/PhysicsBackendEmpty.cpp
+++ b/Source/Engine/Physics/PhysicsBackendEmpty.cpp
@@ -408,7 +408,7 @@ void PhysicsBackend::AddRigidDynamicActorTorque(void* actor, const Vector3& torq
{
}
-void* PhysicsBackend::CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, JsonAsset* material, bool enabled, bool trigger)
+void* PhysicsBackend::CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, Span materials, bool enabled, bool trigger)
{
return DUMY_HANDLE;
}
@@ -447,7 +447,7 @@ void PhysicsBackend::SetShapeContactOffset(void* shape, float value)
{
}
-void PhysicsBackend::SetShapeMaterial(void* shape, JsonAsset* material)
+void PhysicsBackend::SetShapeMaterials(void* shape, Span materials)
{
}
@@ -826,11 +826,16 @@ void PhysicsBackend::GetHeightFieldSize(void* heightField, int32& rows, int32& c
columns = 0;
}
-float PhysicsBackend::GetHeightFieldHeight(void* heightField, float x, float z)
+float PhysicsBackend::GetHeightFieldHeight(void* heightField, int32 x, int32 z)
{
return 0.0f;
}
+PhysicsBackend::HeightFieldSample PhysicsBackend::GetHeightFieldSample(void* heightField, int32 x, int32 z)
+{
+ return HeightFieldSample();
+}
+
bool PhysicsBackend::ModifyHeightField(void* heightField, int32 startCol, int32 startRow, int32 cols, int32 rows, const HeightFieldSample* data)
{
return true;
diff --git a/Source/Engine/Physics/Types.h b/Source/Engine/Physics/Types.h
index ed12f8611..f7cb9f89c 100644
--- a/Source/Engine/Physics/Types.h
+++ b/Source/Engine/Physics/Types.h
@@ -10,6 +10,7 @@
struct PhysicsStatistics;
class PhysicsColliderActor;
class PhysicsScene;
+class PhysicalMaterial;
class Joint;
class Collider;
class CollisionData;
@@ -132,7 +133,7 @@ DECLARE_ENUM_OPERATORS(RigidbodyConstraints);
///
/// Raycast hit result data.
///
-API_STRUCT() struct RayCastHit
+API_STRUCT(NoDefault) struct RayCastHit
{
DECLARE_SCRIPTING_TYPE_NO_SPAWN(RayCastHit);
@@ -141,6 +142,11 @@ API_STRUCT() struct RayCastHit
///
API_FIELD() PhysicsColliderActor* Collider = nullptr;
+ ///
+ /// The physical material of the surface that was hit.
+ ///
+ API_FIELD() PhysicalMaterial* Material = nullptr;
+
///
/// The normal of the surface the ray hit.
///
@@ -151,17 +157,17 @@ API_STRUCT() struct RayCastHit
///
API_FIELD() float Distance;
+ ///
+ /// The point in the world space where ray hit the collider.
+ ///
+ API_FIELD() Vector3 Point;
+
///
/// The index of the face that was hit. Valid only for convex mesh (polygon index), triangle mesh (triangle index) and height field (triangle index).
///
///
API_FIELD() uint32 FaceIndex;
- ///
- /// The point in the world space where ray hit the collider.
- ///
- API_FIELD() Vector3 Point;
-
///
/// The barycentric coordinates of hit triangle. Valid only for triangle mesh and height field.
///
diff --git a/Source/Engine/Scripting/Attributes/CollectionAttribute.cs b/Source/Engine/Scripting/Attributes/CollectionAttribute.cs
index cd2e962e6..0603a9390 100644
--- a/Source/Engine/Scripting/Attributes/CollectionAttribute.cs
+++ b/Source/Engine/Scripting/Attributes/CollectionAttribute.cs
@@ -61,6 +61,16 @@ namespace FlaxEngine
///
public float Spacing;
+ ///
+ /// The minimum size of the collection.
+ ///
+ public int MinCount;
+
+ ///
+ /// The maximum size of the collection. Zero if unlimited.
+ ///
+ public int MaxCount;
+
///
/// The collection background color.
///
diff --git a/Source/Engine/Scripting/Enums.h b/Source/Engine/Scripting/Enums.h
index dd605d303..7c8d36f24 100644
--- a/Source/Engine/Scripting/Enums.h
+++ b/Source/Engine/Scripting/Enums.h
@@ -68,4 +68,30 @@ public:
{
return FromString(StringAnsi(name));
}
+
+ // Gets the name of the enum value as separated flags
+ template
+ static String ToStringFlags(EnumType value, Char separator = '|')
+ {
+ String result;
+ if (const auto items = GetItems())
+ {
+ for (int32 i = 0; items[i].Name; i++)
+ {
+ const uint64 itemValue = items[i].Value;
+ if ((uint64)value == 0 && itemValue == 0)
+ {
+ result = items[i].Name;
+ break;
+ }
+ if (itemValue != 0 && EnumHasAllFlags(value, (EnumType)itemValue))
+ {
+ if (result.HasChars())
+ result += separator;
+ result += items[i].Name;
+ }
+ }
+ }
+ return result;
+ }
};
diff --git a/Source/Engine/ShadowsOfMordor/Builder.Entries.cpp b/Source/Engine/ShadowsOfMordor/Builder.Entries.cpp
index ed6ecbf9c..67af59585 100644
--- a/Source/Engine/ShadowsOfMordor/Builder.Entries.cpp
+++ b/Source/Engine/ShadowsOfMordor/Builder.Entries.cpp
@@ -84,7 +84,7 @@ bool cacheStaticGeometryTree(Actor* actor, ShadowsOfMordor::Builder::SceneBuildC
{
auto patch = terrain->GetPatch(patchIndex);
entry.AsTerrain.PatchIndex = patchIndex;
- for (int32 chunkIndex = 0; chunkIndex < TerrainPatch::CHUNKS_COUNT; chunkIndex++)
+ for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++)
{
auto chunk = patch->Chunks[chunkIndex];
entry.AsTerrain.ChunkIndex = chunkIndex;
diff --git a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp
index 11d136d62..8aed89eb3 100644
--- a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp
+++ b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp
@@ -165,7 +165,7 @@ void ShadowsOfMordor::Builder::onJobRender(GPUContext* context)
Matrix::Transpose(world, shaderData.WorldMatrix);
shaderData.LightmapArea = chunk->Lightmap.UVsArea;
shaderData.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize;
- chunk->GetHeightmapUVScaleBias(&shaderData.HeightmapUVScaleBias);
+ shaderData.HeightmapUVScaleBias = chunk->GetHeightmapUVScaleBias();
// Extract per axis scales from LocalToWorld transform
const float scaleX = Float3(world.M11, world.M12, world.M13).Length();
diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp
index f347e3732..83b30166d 100644
--- a/Source/Engine/Terrain/Terrain.cpp
+++ b/Source/Engine/Terrain/Terrain.cpp
@@ -29,7 +29,7 @@ Terrain::Terrain(const SpawnParams& params)
, _cachedScale(1.0f)
{
_drawCategory = SceneRendering::SceneDrawAsync;
- PhysicalMaterial.Changed.Bind(this);
+ _physicalMaterials.Resize(8);
}
Terrain::~Terrain()
@@ -59,7 +59,7 @@ void Terrain::CacheNeighbors()
for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++)
{
const auto patch = _patches[pathIndex];
- for (int32 chunkIndex = 0; chunkIndex < TerrainPatch::CHUNKS_COUNT; chunkIndex++)
+ for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++)
{
patch->Chunks[chunkIndex].CacheNeighbors();
}
@@ -185,7 +185,7 @@ bool Terrain::RayCast(const Vector3& origin, const Vector3& direction, RayCastHi
return result;
}
-void Terrain::ClosestPoint(const Vector3& position, Vector3& result) const
+void Terrain::ClosestPoint(const Vector3& point, Vector3& result) const
{
Real minDistance = MAX_Real;
Vector3 tmp;
@@ -194,8 +194,8 @@ void Terrain::ClosestPoint(const Vector3& position, Vector3& result) const
const auto patch = _patches[pathIndex];
if (patch->HasCollision())
{
- patch->ClosestPoint(position, tmp);
- const auto distance = Vector3::DistanceSquared(position, tmp);
+ patch->ClosestPoint(point, tmp);
+ const auto distance = Vector3::DistanceSquared(point, tmp);
if (distance < minDistance)
{
minDistance = distance;
@@ -205,12 +205,17 @@ void Terrain::ClosestPoint(const Vector3& position, Vector3& result) const
}
}
+bool Terrain::ContainsPoint(const Vector3& point) const
+{
+ return false;
+}
+
void Terrain::DrawPatch(const RenderContext& renderContext, const Int2& patchCoord, MaterialBase* material, int32 lodIndex) const
{
auto patch = GetPatch(patchCoord);
if (patch)
{
- for (int32 i = 0; i < TerrainPatch::CHUNKS_COUNT; i++)
+ for (int32 i = 0; i < Terrain::ChunksCount; i++)
patch->Chunks[i].Draw(renderContext, material, lodIndex);
}
}
@@ -228,22 +233,6 @@ void Terrain::DrawChunk(const RenderContext& renderContext, const Int2& patchCoo
}
}
-void Terrain::OnPhysicalMaterialChanged()
-{
- if (_patches.IsEmpty())
- return;
-
- // Update the shapes material
- for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++)
- {
- const auto patch = _patches[pathIndex];
- if (patch->HasCollision())
- {
- PhysicsBackend::SetShapeMaterial(patch->_physicsShape, PhysicalMaterial.Get());
- }
- }
-}
-
#if TERRAIN_USE_PHYSICS_DEBUG
void Terrain::DrawPhysicsDebug(RenderView& view)
@@ -295,6 +284,21 @@ void Terrain::SetCollisionLOD(int32 value)
#endif
}
+void Terrain::SetPhysicalMaterials(const Array, FixedAllocation<8>>& value)
+{
+ _physicalMaterials = value;
+ _physicalMaterials.Resize(8);
+ JsonAsset* materials[8];
+ for (int32 i = 0;i<8;i++)
+ materials[i] = _physicalMaterials[i];
+ for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++)
+ {
+ const auto patch = _patches.Get()[pathIndex];
+ if (patch->HasCollision())
+ PhysicsBackend::SetShapeMaterials(patch->_physicsShape, ToSpan(materials, 8));
+ }
+}
+
TerrainPatch* Terrain::GetPatch(const Int2& patchCoord) const
{
return GetPatch(patchCoord.X, patchCoord.Y);
@@ -540,7 +544,7 @@ void Terrain::Draw(RenderContext& renderContext)
Matrix localToWorld, worldToLocal;
BoundingSphere chunkSphere;
BoundingBox localBounds;
- for (int32 chunkIndex = 0; chunkIndex < TerrainPatch::CHUNKS_COUNT; chunkIndex++)
+ for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++)
{
TerrainChunk* chunk = &patch->Chunks[chunkIndex];
chunk->GetTransform().GetWorld(localToWorld); // TODO: large-worlds
@@ -570,7 +574,7 @@ void Terrain::Draw(RenderContext& renderContext)
continue;
// Frustum vs Box culling for chunks
- for (int32 chunkIndex = 0; chunkIndex < TerrainPatch::CHUNKS_COUNT; chunkIndex++)
+ for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++)
{
auto chunk = &patch->Chunks[chunkIndex];
chunk->_cachedDrawLOD = 0;
@@ -588,7 +592,7 @@ void Terrain::Draw(RenderContext& renderContext)
else
{
// Reset cached LOD for chunks (prevent LOD transition from invisible chunks)
- for (int32 chunkIndex = 0; chunkIndex < TerrainPatch::CHUNKS_COUNT; chunkIndex++)
+ for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++)
{
auto chunk = &patch->Chunks[chunkIndex];
chunk->_cachedDrawLOD = 0;
@@ -616,10 +620,10 @@ void Terrain::OnDebugDrawSelected()
for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++)
{
const auto patch = _patches[pathIndex];
- for (int32 chunkIndex = 0; chunkIndex < TerrainPatch::CHUNKS_COUNT; chunkIndex++)
+ for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++)
{
auto chunk = &patch->Chunks[chunkIndex];
- DebugDraw::DrawBox(chunk->_bounds, Color(chunk->_x / (float)TerrainPatch::CHUNKS_COUNT_EDGE, 1.0f, chunk->_z / (float)TerrainPatch::CHUNKS_COUNT_EDGE));
+ DebugDraw::DrawBox(chunk->_bounds, Color(chunk->_x / (float)Terrain::ChunksCountEdge, 1.0f, chunk->_z / (float)Terrain::ChunksCountEdge));
}
}
*/
@@ -667,8 +671,8 @@ void Terrain::Serialize(SerializeStream& stream, const void* otherObj)
SERIALIZE_MEMBER(ScaleInLightmap, _scaleInLightmap);
SERIALIZE_MEMBER(BoundsExtent, _boundsExtent);
SERIALIZE_MEMBER(CollisionLOD, _collisionLod);
+ SERIALIZE_MEMBER(PhysicalMaterials, _physicalMaterials);
SERIALIZE(Material);
- SERIALIZE(PhysicalMaterial);
SERIALIZE(DrawModes);
SERIALIZE_MEMBER(LODCount, _lodCount);
@@ -714,8 +718,8 @@ void Terrain::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie
DESERIALIZE_MEMBER(LODDistribution, _lodDistribution);
DESERIALIZE_MEMBER(ScaleInLightmap, _scaleInLightmap);
DESERIALIZE_MEMBER(BoundsExtent, _boundsExtent);
+ DESERIALIZE_MEMBER(PhysicalMaterials, _physicalMaterials);
DESERIALIZE(Material);
- DESERIALIZE(PhysicalMaterial);
DESERIALIZE(DrawModes);
member = stream.FindMember("LODCount");
@@ -780,6 +784,15 @@ void Terrain::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie
// [Deprecated on 27.04.2022, expires on 27.04.2024]
if (modifier->EngineBuild <= 6331)
DrawModes |= DrawPass::GlobalSurfaceAtlas;
+
+ // [Deprecated on 15.02.2024, expires on 15.02.2026]
+ JsonAssetReference PhysicalMaterial;
+ DESERIALIZE(PhysicalMaterial);
+ if (PhysicalMaterial)
+ {
+ for (auto& e : _physicalMaterials)
+ e = PhysicalMaterial;
+ }
}
RigidBody* Terrain::GetAttachedRigidBody() const
diff --git a/Source/Engine/Terrain/Terrain.h b/Source/Engine/Terrain/Terrain.h
index 636f30206..5a1eb5e95 100644
--- a/Source/Engine/Terrain/Terrain.h
+++ b/Source/Engine/Terrain/Terrain.h
@@ -2,7 +2,7 @@
#pragma once
-#include "Engine/Content/JsonAsset.h"
+#include "Engine/Content/JsonAssetReference.h"
#include "Engine/Content/Assets/MaterialBase.h"
#include "Engine/Physics/Actors/PhysicsColliderActor.h"
@@ -10,6 +10,7 @@ class Terrain;
class TerrainChunk;
class TerrainPatch;
class TerrainManager;
+class PhysicalMaterial;
struct RayCastHit;
struct RenderView;
@@ -23,10 +24,7 @@ struct RenderView;
#define TERRAIN_EDITING 1
// Enable/disable terrain heightmap samples modification and gather. Used by the editor to modify the terrain with the brushes.
-#define TERRAIN_UPDATING (USE_EDITOR)
-
-// Enable/disable precise terrain geometry collision testing (with in-build vertex buffer caching, this will increase memory usage)
-#define USE_PRECISE_TERRAIN_INTERSECTS (USE_EDITOR)
+#define TERRAIN_UPDATING 1
// Enable/disable terrain physics collision drawing
#define TERRAIN_USE_PHYSICS_DEBUG (USE_EDITOR && 1)
@@ -41,13 +39,28 @@ struct RenderView;
///
API_CLASS(Sealed) class FLAXENGINE_API Terrain : public PhysicsColliderActor
{
-DECLARE_SCENE_OBJECT(Terrain);
+ DECLARE_SCENE_OBJECT(Terrain);
friend Terrain;
friend TerrainPatch;
friend TerrainChunk;
-private:
+ ///
+ /// Various defines regarding terrain configuration.
+ ///
+ API_ENUM() enum Config
+ {
+ ///
+ /// The maximum allowed amount of chunks per patch.
+ ///
+ ChunksCount = 16,
+ ///
+ /// The maximum allowed amount of chunks per chunk.
+ ///
+ ChunksCountEdge = 4,
+ };
+
+private:
char _lodBias;
char _forcedLod;
char _collisionLod;
@@ -60,28 +73,21 @@ private:
Float3 _cachedScale;
Array> _patches;
Array _drawChunks;
+ Array, FixedAllocation<8>> _physicalMaterials;
public:
-
///
/// Finalizes an instance of the class.
///
~Terrain();
public:
-
///
/// The default material used for terrain rendering (chunks can override this).
///
API_FIELD(Attributes="EditorOrder(100), DefaultValue(null), EditorDisplay(\"Terrain\")")
AssetReference Material;
- ///
- /// The physical material used to define the terrain collider physical properties.
- ///
- API_FIELD(Attributes="EditorOrder(520), DefaultValue(null), Limit(-1, 100, 0.1f), EditorDisplay(\"Collision\"), AssetReference(typeof(PhysicalMaterial), true)")
- AssetReference PhysicalMaterial;
-
///
/// The draw passes to use for rendering this object.
///
@@ -89,7 +95,6 @@ public:
DrawPass DrawModes = DrawPass::Default;
public:
-
///
/// Gets the terrain Level Of Detail bias value. Allows to increase or decrease rendered terrain quality.
///
@@ -180,6 +185,21 @@ public:
///
API_PROPERTY() void SetCollisionLOD(int32 value);
+ ///
+ /// Gets the list with physical materials used to define the terrain collider physical properties - each for terrain layer (layer index matches index in this array).
+ ///
+ API_PROPERTY(Attributes="EditorOrder(520), EditorDisplay(\"Collision\"), Collection(MinCount = 8, MaxCount = 8)")
+ FORCE_INLINE const Array, FixedAllocation<8>>& GetPhysicalMaterials() const
+ {
+ return _physicalMaterials;
+ }
+
+ ///
+ /// Sets the list with physical materials used to define the terrain collider physical properties - each for terrain layer (layer index matches index in this array).
+ ///
+ API_PROPERTY()
+ void SetPhysicalMaterials(const Array, FixedAllocation<8>>& value);
+
///
/// Gets the terrain Level Of Detail count.
///
@@ -219,7 +239,7 @@ public:
///
/// The patch location (x and z).
/// The patch.
- TerrainPatch* GetPatch(const Int2& patchCoord) const;
+ API_FUNCTION() TerrainPatch* GetPatch(API_PARAM(Ref) const Int2& patchCoord) const;
///
/// Gets the patch at the given location.
@@ -227,7 +247,7 @@ public:
/// The patch location x.
/// The patch location z.
/// The patch.
- TerrainPatch* GetPatch(int32 x, int32 z) const;
+ API_FUNCTION() TerrainPatch* GetPatch(int32 x, int32 z) const;
///
/// Gets the zero-based index of the terrain patch in the terrain patches collection.
@@ -241,7 +261,7 @@ public:
///
/// The index.
/// The patch.
- FORCE_INLINE TerrainPatch* GetPatch(int32 index) const
+ API_FUNCTION() FORCE_INLINE TerrainPatch* GetPatch(int32 index) const
{
return _patches[index];
}
@@ -311,9 +331,7 @@ public:
#endif
public:
-
#if TERRAIN_EDITING
-
///
/// Setups the terrain. Clears the existing data.
///
@@ -338,7 +356,6 @@ public:
///
/// The patch location (x and z).
API_FUNCTION() void RemovePatch(API_PARAM(Ref) const Int2& patchCoord);
-
#endif
///
@@ -362,17 +379,6 @@ public:
void RemoveLightmap();
public:
-
- ///
- /// Performs a raycast against this terrain collision shape.
- ///
- /// The origin of the ray.
- /// The normalized direction of the ray.
- /// The raycast result hit position distance from the ray origin. Valid only if raycast hits anything.
- /// The maximum distance the ray should check for collisions.
- /// True if ray hits an object, otherwise false.
- API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, float maxDistance = MAX_float) const;
-
///
/// Performs a raycast against this terrain collision shape. Returns the hit chunk.
///
@@ -382,7 +388,7 @@ public:
/// The raycast result hit chunk. Valid only if raycast hits anything.
/// The maximum distance the ray should check for collisions.
/// True if ray hits an object, otherwise false.
- bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, TerrainChunk*& resultChunk, float maxDistance = MAX_float) const;
+ API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, API_PARAM(Out) TerrainChunk*& resultChunk, float maxDistance = MAX_float) const;
///
/// Performs a raycast against this terrain collision shape. Returns the hit chunk.
@@ -395,23 +401,6 @@ public:
/// True if ray hits an object, otherwise false.
API_FUNCTION() bool RayCast(const Ray& ray, API_PARAM(Out) float& resultHitDistance, API_PARAM(Out) Int2& resultPatchCoord, API_PARAM(Out) Int2& resultChunkCoord, float maxDistance = MAX_float) const;
- ///
- /// Performs a raycast against terrain collision, returns results in a RayCastHit structure.
- ///
- /// The origin of the ray.
- /// The normalized direction of the ray.
- /// The result hit information. Valid only when method returns true.
- /// The maximum distance the ray should check for collisions.
- /// True if ray hits an object, otherwise false.
- API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float) const;
-
- ///
- /// Gets a point on the terrain collider that is closest to a given location. Can be used to find a hit location or position to apply explosion force or any other special effects.
- ///
- /// The position to find the closest point to it.
- /// The result point on the collider that is closest to the specified location.
- API_FUNCTION() void ClosestPoint(const Vector3& position, API_PARAM(Out) Vector3& result) const;
-
///
/// Draws the terrain patch.
///
@@ -432,14 +421,11 @@ public:
API_FUNCTION() void DrawChunk(API_PARAM(Ref) const RenderContext& renderContext, API_PARAM(Ref) const Int2& patchCoord, API_PARAM(Ref) const Int2& chunkCoord, MaterialBase* material, int32 lodIndex = 0) const;
private:
-
- void OnPhysicalMaterialChanged();
#if TERRAIN_USE_PHYSICS_DEBUG
- void DrawPhysicsDebug(RenderView& view);
+ void DrawPhysicsDebug(RenderView& view);
#endif
public:
-
// [PhysicsColliderActor]
void Draw(RenderContext& renderContext) override;
#if USE_EDITOR
@@ -450,9 +436,12 @@ public:
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
RigidBody* GetAttachedRigidBody() const override;
+ bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, float maxDistance = MAX_float) const final;
+ bool RayCast(const Vector3& origin, const Vector3& direction, RayCastHit& hitInfo, float maxDistance = MAX_float) const final;
+ void ClosestPoint(const Vector3& point, Vector3& result) const final;
+ bool ContainsPoint(const Vector3& point) const final;
protected:
-
// [PhysicsColliderActor]
void OnEnable() override;
void OnDisable() override;
diff --git a/Source/Engine/Terrain/TerrainChunk.cpp b/Source/Engine/Terrain/TerrainChunk.cpp
index 9bf1c1208..8ef32e58b 100644
--- a/Source/Engine/Terrain/TerrainChunk.cpp
+++ b/Source/Engine/Terrain/TerrainChunk.cpp
@@ -11,7 +11,14 @@
#include "Engine/Renderer/RenderList.h"
#include "Engine/Core/Math/OrientedBoundingBox.h"
#include "Engine/Level/Scene/Scene.h"
+#if USE_EDITOR
#include "Engine/Level/Prefabs/PrefabManager.h"
+#endif
+
+TerrainChunk::TerrainChunk(const SpawnParams& params)
+ : ScriptingObject(params)
+{
+}
void TerrainChunk::Init(TerrainPatch* patch, uint16 x, uint16 z)
{
@@ -21,7 +28,7 @@ void TerrainChunk::Init(TerrainPatch* patch, uint16 x, uint16 z)
_z = z;
_yOffset = 0;
_yHeight = 1;
- _heightmapUVScaleBias = Float4(1.0f, 1.0f, _x, _z) * (1.0f / TerrainPatch::CHUNKS_COUNT_EDGE);
+ _heightmapUVScaleBias = Float4(1.0f, 1.0f, _x, _z) * (1.0f / Terrain::ChunksCountEdge);
_perInstanceRandom = (_patch->_terrain->_id.C ^ _x ^ _z) * (1.0f / (float)MAX_uint32);
OverrideMaterial = nullptr;
}
@@ -51,8 +58,8 @@ bool TerrainChunk::PrepareDraw(const RenderContext& renderContext)
//lod = 0;
//lod = 10;
- //lod = (_x + _z + TerrainPatch::CHUNKS_COUNT_EDGE * (_patch->_x + _patch->_z));
- //lod = (int32)Vector2::Distance(Vector2(2, 2), Vector2(_patch->_x, _patch->_z) * TerrainPatch::CHUNKS_COUNT_EDGE + Vector2(_x, _z));
+ //lod = (_x + _z + Terrain::ChunksCountEdge * (_patch->_x + _patch->_z));
+ //lod = (int32)Vector2::Distance(Vector2(2, 2), Vector2(_patch->_x, _patch->_z) * Terrain::ChunksCountEdge + Vector2(_x, _z));
//lod = (int32)(Vector3::Distance(_bounds.GetCenter(), view.Position) / 10000.0f);
}
lod = Math::Clamp(lod, minStreamedLod, lodCount - 1);
@@ -93,7 +100,7 @@ void TerrainChunk::Draw(const RenderContext& renderContext) const
drawCall.ObjectRadius = _sphere.Radius;
drawCall.Terrain.Patch = _patch;
drawCall.Terrain.HeightmapUVScaleBias = _heightmapUVScaleBias;
- drawCall.Terrain.OffsetUV = Vector2((float)(_patch->_x * TerrainPatch::CHUNKS_COUNT_EDGE + _x), (float)(_patch->_z * TerrainPatch::CHUNKS_COUNT_EDGE + _z));
+ drawCall.Terrain.OffsetUV = Vector2((float)(_patch->_x * Terrain::ChunksCountEdge + _x), (float)(_patch->_z * Terrain::ChunksCountEdge + _z));
drawCall.Terrain.CurrentLOD = (float)lod;
drawCall.Terrain.ChunkSizeNextLOD = (float)(((chunkSize + 1) >> (lod + 1)) - 1);
drawCall.Terrain.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize;
@@ -151,7 +158,7 @@ void TerrainChunk::Draw(const RenderContext& renderContext, MaterialBase* materi
drawCall.ObjectRadius = _sphere.Radius;
drawCall.Terrain.Patch = _patch;
drawCall.Terrain.HeightmapUVScaleBias = _heightmapUVScaleBias;
- drawCall.Terrain.OffsetUV = Vector2((float)(_patch->_x * TerrainPatch::CHUNKS_COUNT_EDGE + _x), (float)(_patch->_z * TerrainPatch::CHUNKS_COUNT_EDGE + _z));
+ drawCall.Terrain.OffsetUV = Vector2((float)(_patch->_x * Terrain::ChunksCountEdge + _x), (float)(_patch->_z * Terrain::ChunksCountEdge + _z));
drawCall.Terrain.CurrentLOD = (float)lod;
drawCall.Terrain.ChunkSizeNextLOD = (float)(((chunkSize + 1) >> (lod + 1)) - 1);
drawCall.Terrain.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize;
@@ -232,46 +239,46 @@ void TerrainChunk::CacheNeighbors()
_neighbors[0] = this;
if (_z > 0)
{
- _neighbors[0] = &_patch->Chunks[(_z - 1) * TerrainPatch::CHUNKS_COUNT_EDGE + _x];
+ _neighbors[0] = &_patch->Chunks[(_z - 1) * Terrain::ChunksCountEdge + _x];
}
else
{
const auto patch = _patch->_terrain->GetPatch(_patch->_x, _patch->_z - 1);
if (patch)
- _neighbors[0] = &patch->Chunks[(TerrainPatch::CHUNKS_COUNT_EDGE - 1) * TerrainPatch::CHUNKS_COUNT_EDGE + _x];
+ _neighbors[0] = &patch->Chunks[(Terrain::ChunksCountEdge - 1) * Terrain::ChunksCountEdge + _x];
}
// 1: left
_neighbors[1] = this;
if (_x > 0)
{
- _neighbors[1] = &_patch->Chunks[_z * TerrainPatch::CHUNKS_COUNT_EDGE + (_x - 1)];
+ _neighbors[1] = &_patch->Chunks[_z * Terrain::ChunksCountEdge + (_x - 1)];
}
else
{
const auto patch = _patch->_terrain->GetPatch(_patch->_x - 1, _patch->_z);
if (patch)
- _neighbors[1] = &patch->Chunks[_z * TerrainPatch::CHUNKS_COUNT_EDGE + (TerrainPatch::CHUNKS_COUNT_EDGE - 1)];
+ _neighbors[1] = &patch->Chunks[_z * Terrain::ChunksCountEdge + (Terrain::ChunksCountEdge - 1)];
}
// 2: right
_neighbors[2] = this;
- if (_x < TerrainPatch::CHUNKS_COUNT_EDGE - 1)
+ if (_x < Terrain::ChunksCountEdge - 1)
{
- _neighbors[2] = &_patch->Chunks[_z * TerrainPatch::CHUNKS_COUNT_EDGE + (_x + 1)];
+ _neighbors[2] = &_patch->Chunks[_z * Terrain::ChunksCountEdge + (_x + 1)];
}
else
{
const auto patch = _patch->_terrain->GetPatch(_patch->_x + 1, _patch->_z);
if (patch)
- _neighbors[2] = &patch->Chunks[_z * TerrainPatch::CHUNKS_COUNT_EDGE];
+ _neighbors[2] = &patch->Chunks[_z * Terrain::ChunksCountEdge];
}
// 3: top
_neighbors[3] = this;
- if (_z < TerrainPatch::CHUNKS_COUNT_EDGE - 1)
+ if (_z < Terrain::ChunksCountEdge - 1)
{
- _neighbors[3] = &_patch->Chunks[(_z + 1) * TerrainPatch::CHUNKS_COUNT_EDGE + _x];
+ _neighbors[3] = &_patch->Chunks[(_z + 1) * Terrain::ChunksCountEdge + _x];
}
else
{
diff --git a/Source/Engine/Terrain/TerrainChunk.h b/Source/Engine/Terrain/TerrainChunk.h
index 884160b45..60f38ed0e 100644
--- a/Source/Engine/Terrain/TerrainChunk.h
+++ b/Source/Engine/Terrain/TerrainChunk.h
@@ -17,14 +17,14 @@ struct RenderContext;
///
/// Represents a single terrain chunk.
///
-class FLAXENGINE_API TerrainChunk : public ISerializable
+API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API TerrainChunk : public ScriptingObject, public ISerializable
{
+ DECLARE_SCRIPTING_TYPE(TerrainChunk);
friend Terrain;
friend TerrainPatch;
friend TerrainChunk;
private:
-
TerrainPatch* _patch;
uint16 _x, _z;
Float4 _heightmapUVScaleBias;
@@ -41,11 +41,10 @@ private:
void Init(TerrainPatch* patch, uint16 x, uint16 z);
public:
-
///
/// The material to override the terrain default one for this chunk.
///
- AssetReference OverrideMaterial;
+ API_FIELD() AssetReference OverrideMaterial;
///
/// The baked lightmap entry info for this chunk.
@@ -53,11 +52,10 @@ public:
LightmapEntry Lightmap;
public:
-
///
/// Gets the x coordinate.
///
- FORCE_INLINE int32 GetX() const
+ API_FUNCTION() FORCE_INLINE int32 GetX() const
{
return _x;
}
@@ -65,7 +63,7 @@ public:
///
/// Gets the z coordinate.
///
- FORCE_INLINE int32 GetZ() const
+ API_FUNCTION() FORCE_INLINE int32 GetZ() const
{
return _z;
}
@@ -73,7 +71,7 @@ public:
///
/// Gets the patch.
///
- FORCE_INLINE TerrainPatch* GetPatch() const
+ API_FUNCTION() FORCE_INLINE TerrainPatch* GetPatch() const
{
return _patch;
}
@@ -81,7 +79,7 @@ public:
///
/// Gets the chunk world bounds.
///
- FORCE_INLINE const BoundingBox& GetBounds() const
+ API_FUNCTION() FORCE_INLINE const BoundingBox& GetBounds() const
{
return _bounds;
}
@@ -89,7 +87,7 @@ public:
///
/// Gets the chunk transformation (world to local).
///
- FORCE_INLINE const Transform& GetTransform() const
+ API_FUNCTION() FORCE_INLINE const Transform& GetTransform() const
{
return _transform;
}
@@ -97,10 +95,9 @@ public:
///
/// Gets the scale (in XY) and bias (in ZW) applied to the vertex UVs to get the chunk coordinates.
///
- /// The result.
- FORCE_INLINE void GetHeightmapUVScaleBias(Float4* result) const
+ API_FUNCTION() FORCE_INLINE const Float4& GetHeightmapUVScaleBias() const
{
- *result = _heightmapUVScaleBias;
+ return _heightmapUVScaleBias;
}
///
@@ -120,7 +117,6 @@ public:
}
public:
-
///
/// Prepares for drawing chunk. Cached LOD and material.
///
@@ -140,7 +136,7 @@ public:
/// The rendering context.
/// The material to use for rendering.
/// The LOD index.
- void Draw(const RenderContext& renderContext, MaterialBase* material, int32 lodIndex = 0) const;
+ API_FUNCTION() void Draw(API_PARAM(Ref) const RenderContext& renderContext, MaterialBase* material, int32 lodIndex = 0) const;
///
/// Determines if there is an intersection between the terrain chunk and a point
@@ -148,7 +144,7 @@ public:
/// The ray.
/// The output distance.
/// True if chunk intersects with the ray, otherwise false.
- bool Intersects(const Ray& ray, Real& distance);
+ API_FUNCTION() bool Intersects(const Ray& ray, API_PARAM(Out) Real& distance);
///
/// Updates the cached bounds of the chunk.
@@ -166,7 +162,6 @@ public:
void CacheNeighbors();
public:
-
// [ISerializable]
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp
index 8e05062c8..c5d1ace05 100644
--- a/Source/Engine/Terrain/TerrainPatch.cpp
+++ b/Source/Engine/Terrain/TerrainPatch.cpp
@@ -27,6 +27,9 @@
#include "Engine/ContentImporters/AssetsImportingManager.h"
#endif
#endif
+#if TERRAIN_EDITING || TERRAIN_UPDATING
+#include "Engine/Core/Collections/ArrayExtensions.h"
+#endif
#if USE_EDITOR
#include "Engine/Debug/DebugDraw.h"
#endif
@@ -41,6 +44,11 @@ struct TerrainCollisionDataHeader
float ScaleXZ;
};
+TerrainPatch::TerrainPatch(const SpawnParams& params)
+ : ScriptingObject(params)
+{
+}
+
void TerrainPatch::Init(Terrain* terrain, int16 x, int16 z)
{
ScopeLock lock(_collisionLocker);
@@ -51,13 +59,13 @@ void TerrainPatch::Init(Terrain* terrain, int16 x, int16 z)
_physicsHeightField = nullptr;
_x = x;
_z = z;
- const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * CHUNKS_COUNT_EDGE;
+ const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::Terrain::ChunksCountEdge;
_offset = Float3(_x * size, 0.0f, _z * size);
_yOffset = 0.0f;
_yHeight = 1.0f;
- for (int32 i = 0; i < CHUNKS_COUNT; i++)
+ for (int32 i = 0; i < Terrain::ChunksCount; i++)
{
- Chunks[i].Init(this, i % CHUNKS_COUNT_EDGE, i / CHUNKS_COUNT_EDGE);
+ Chunks[i].Init(this, i % Terrain::Terrain::ChunksCountEdge, i / Terrain::Terrain::ChunksCountEdge);
}
Heightmap = nullptr;
for (int32 i = 0; i < TERRAIN_MAX_SPLATMAPS_COUNT; i++)
@@ -105,9 +113,10 @@ void TerrainPatch::RemoveLightmap()
void TerrainPatch::UpdateBounds()
{
+ PROFILE_CPU();
Chunks[0].UpdateBounds();
_bounds = Chunks[0]._bounds;
- for (int32 i = 1; i < CHUNKS_COUNT; i++)
+ for (int32 i = 1; i < Terrain::ChunksCount; i++)
{
Chunks[i].UpdateBounds();
BoundingBox::Merge(_bounds, Chunks[i]._bounds, _bounds);
@@ -116,6 +125,8 @@ void TerrainPatch::UpdateBounds()
void TerrainPatch::UpdateTransform()
{
+ PROFILE_CPU();
+
// Update physics
if (_physicsActor)
{
@@ -124,7 +135,7 @@ void TerrainPatch::UpdateTransform()
}
// Update chunks cache
- for (int32 i = 0; i < CHUNKS_COUNT; i++)
+ for (int32 i = 0; i < Terrain::ChunksCount; i++)
{
Chunks[i].UpdateTransform();
}
@@ -138,8 +149,14 @@ void TerrainPatch::UpdateTransform()
#if TERRAIN_EDITING || TERRAIN_UPDATING
+bool IsValidMaterial(const JsonAssetReference& e)
+{
+ return e;
+}
+
struct TerrainDataUpdateInfo
{
+ TerrainPatch* Patch;
int32 ChunkSize;
int32 VertexCountEdge;
int32 HeightmapSize;
@@ -147,6 +164,40 @@ struct TerrainDataUpdateInfo
int32 TextureSize;
float PatchOffset;
float PatchHeight;
+ Color32* SplatMaps[TERRAIN_MAX_SPLATMAPS_COUNT] = {};
+
+ TerrainDataUpdateInfo(TerrainPatch* patch, float patchOffset = 0.0f, float patchHeight = 1.0f)
+ : Patch(patch)
+ , PatchOffset(patchOffset)
+ , PatchHeight(patchHeight)
+ {
+ ChunkSize = patch->GetTerrain()->GetChunkSize();
+ VertexCountEdge = ChunkSize + 1;
+ HeightmapSize = ChunkSize * Terrain::ChunksCountEdge + 1;
+ HeightmapLength = HeightmapSize * HeightmapSize;
+ TextureSize = VertexCountEdge * Terrain::ChunksCountEdge;
+ }
+
+ bool UsePhysicalMaterials() const
+ {
+ return ArrayExtensions::Any>(Patch->GetTerrain()->GetPhysicalMaterials(), IsValidMaterial);
+ }
+
+ // When using physical materials, then get splatmaps data required for per-triangle material indices
+ void GetSplatMaps()
+ {
+#if TERRAIN_UPDATING
+ if (SplatMaps[0])
+ return;
+ if (UsePhysicalMaterials())
+ {
+ for (int32 i = 0; i < TERRAIN_MAX_SPLATMAPS_COUNT; i++)
+ SplatMaps[i] = Patch->GetSplatMapData(i);
+ }
+#else
+ LOG(Warning, "Splatmaps reading not implemented for physical layers updating.");
+#endif
+ }
};
// Shared data container for the terrain data updating shared by the normals and collision generation logic
@@ -185,7 +236,7 @@ FORCE_INLINE bool ReadIsHole(const Color32& raw)
return (raw.B + raw.A) >= (int32)(1.9f * MAX_uint8);
}
-void CalculateHeightmapRange(Terrain* terrain, TerrainDataUpdateInfo& info, const float* heightmap, float chunkOffsets[TerrainPatch::CHUNKS_COUNT], float chunkHeights[TerrainPatch::CHUNKS_COUNT])
+void CalculateHeightmapRange(Terrain* terrain, TerrainDataUpdateInfo& info, const float* heightmap, float chunkOffsets[Terrain::ChunksCount], float chunkHeights[Terrain::ChunksCount])
{
PROFILE_CPU_NAMED("Terrain.CalculateRange");
@@ -194,10 +245,10 @@ void CalculateHeightmapRange(Terrain* terrain, TerrainDataUpdateInfo& info, cons
float minPatchHeight = MAX_float;
float maxPatchHeight = MIN_float;
- for (int32 chunkIndex = 0; chunkIndex < TerrainPatch::CHUNKS_COUNT; chunkIndex++)
+ for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++)
{
- const int32 chunkX = (chunkIndex % TerrainPatch::CHUNKS_COUNT_EDGE) * info.ChunkSize;
- const int32 chunkZ = (chunkIndex / TerrainPatch::CHUNKS_COUNT_EDGE) * info.ChunkSize;
+ const int32 chunkX = (chunkIndex % Terrain::ChunksCountEdge) * info.ChunkSize;
+ const int32 chunkZ = (chunkIndex / Terrain::ChunksCountEdge) * info.ChunkSize;
float minHeight = MAX_float;
float maxHeight = MIN_float;
@@ -240,10 +291,10 @@ void UpdateHeightMap(const TerrainDataUpdateInfo& info, const float* heightmap,
const auto heightmapPtr = heightmap;
const auto ptr = (Color32*)data;
- for (int32 chunkIndex = 0; chunkIndex < TerrainPatch::CHUNKS_COUNT; chunkIndex++)
+ for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++)
{
- const int32 chunkX = (chunkIndex % TerrainPatch::CHUNKS_COUNT_EDGE);
- const int32 chunkZ = (chunkIndex / TerrainPatch::CHUNKS_COUNT_EDGE);
+ const int32 chunkX = (chunkIndex % Terrain::ChunksCountEdge);
+ const int32 chunkZ = (chunkIndex / Terrain::ChunksCountEdge);
const int32 chunkTextureX = chunkX * info.VertexCountEdge;
const int32 chunkTextureZ = chunkZ * info.VertexCountEdge;
@@ -282,10 +333,10 @@ void UpdateSplatMap(const TerrainDataUpdateInfo& info, const Color32* splatMap,
const auto splatPtr = splatMap;
const auto ptr = (Color32*)data;
- for (int32 chunkIndex = 0; chunkIndex < TerrainPatch::CHUNKS_COUNT; chunkIndex++)
+ for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++)
{
- const int32 chunkX = (chunkIndex % TerrainPatch::CHUNKS_COUNT_EDGE);
- const int32 chunkZ = (chunkIndex / TerrainPatch::CHUNKS_COUNT_EDGE);
+ const int32 chunkX = (chunkIndex % Terrain::ChunksCountEdge);
+ const int32 chunkZ = (chunkIndex / Terrain::ChunksCountEdge);
const int32 chunkTextureX = chunkX * info.VertexCountEdge;
const int32 chunkTextureZ = chunkZ * info.VertexCountEdge;
@@ -321,10 +372,9 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh
PROFILE_CPU_NAMED("Terrain.CalculateNormals");
// Expand the area for the normals to prevent issues on the edges (for the averaged normals)
- const int32 heightMapSize = info.HeightmapSize;
const Int2 modifiedEnd = modifiedOffset + modifiedSize;
const Int2 normalsStart = Int2::Max(Int2::Zero, modifiedOffset - 1);
- const Int2 normalsEnd = Int2::Min(heightMapSize, modifiedEnd + 1);
+ const Int2 normalsEnd = Int2::Min(info.HeightmapSize, modifiedEnd + 1);
const Int2 normalsSize = normalsEnd - normalsStart;
// Prepare memory
@@ -342,7 +392,7 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh
// Get four vertices from the quad
#define GET_VERTEX(a, b) \
int32 i##a##b = (z + (b) - normalsStart.Y) * normalsSize.X + (x + (a) - normalsStart.X); \
- int32 h##a##b = (z + (b)) * heightMapSize + (x + (a)); \
+ int32 h##a##b = (z + (b)) * info.HeightmapSize + (x + (a)); \
Float3 v##a##b; v##a##b.X = (x + (a)) * TERRAIN_UNITS_PER_VERTEX; \
v##a##b.Y = heightmap[h##a##b]; \
v##a##b.Z = (z + (b)) * TERRAIN_UNITS_PER_VERTEX
@@ -405,10 +455,10 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh
// Write back to the data container
const auto ptr = (Color32*)data;
- for (int32 chunkIndex = 0; chunkIndex < TerrainPatch::CHUNKS_COUNT; chunkIndex++)
+ for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++)
{
- const int32 chunkX = (chunkIndex % TerrainPatch::CHUNKS_COUNT_EDGE);
- const int32 chunkZ = (chunkIndex / TerrainPatch::CHUNKS_COUNT_EDGE);
+ const int32 chunkX = (chunkIndex % Terrain::ChunksCountEdge);
+ const int32 chunkZ = (chunkIndex / Terrain::ChunksCountEdge);
const int32 chunkTextureX = chunkX * info.VertexCountEdge;
const int32 chunkTextureZ = chunkZ * info.VertexCountEdge;
@@ -428,7 +478,7 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh
const int32 dz = chunkHeightmapZ + z - modifiedOffset.Y;
if (dz < 0 || dz >= modifiedSize.Y)
continue;
- const int32 hz = (chunkHeightmapZ + z) * heightMapSize;
+ const int32 hz = (chunkHeightmapZ + z) * info.HeightmapSize;
const int32 sz = (chunkHeightmapZ + z - normalsStart.Y) * normalsSize.X;
const int32 tz = (chunkTextureZ + z) * info.TextureSize;
@@ -498,9 +548,9 @@ void FixMips(const TerrainDataUpdateInfo& info, TextureBase::InitData* initData,
const int32 textureSizeMipHigher = textureSizeMip << 1;
// Make heightmap values on left edge the same as the left edge of the chunk on the higher LOD
- for (int32 chunkX = 0; chunkX < TerrainPatch::CHUNKS_COUNT_EDGE; chunkX++)
+ for (int32 chunkX = 0; chunkX < Terrain::ChunksCountEdge; chunkX++)
{
- for (int32 chunkZ = 0; chunkZ < TerrainPatch::CHUNKS_COUNT_EDGE; chunkZ++)
+ for (int32 chunkZ = 0; chunkZ < Terrain::ChunksCountEdge; chunkZ++)
{
const int32 chunkTextureX = chunkX * vertexCountEdgeMip;
const int32 chunkTextureZ = chunkZ * vertexCountEdgeMip;
@@ -513,11 +563,11 @@ void FixMips(const TerrainDataUpdateInfo& info, TextureBase::InitData* initData,
int32 x = 0, xCount = vertexCountEdgeMip;
if (chunkX == 0)
x = 1;
- else if (chunkX == TerrainPatch::CHUNKS_COUNT_EDGE - 1)
+ else if (chunkX == Terrain::ChunksCountEdge - 1)
xCount--;
if (chunkZ == 0)
z = 1;
- else if (chunkZ == TerrainPatch::CHUNKS_COUNT_EDGE - 1)
+ else if (chunkZ == Terrain::ChunksCountEdge - 1)
zCount--;
for (; z < zCount; z++)
@@ -546,15 +596,54 @@ void FixMips(const TerrainDataUpdateInfo& info, TextureBase::InitData* initData,
}
}
-bool CookCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* initData, int32 collisionLod, Array* collisionData)
+FORCE_INLINE byte GetPhysicalMaterial(const Color32& raw, const TerrainDataUpdateInfo& info, int32 chunkZ, int32 chunkX, int32 z, int32 x)
+{
+ byte result = 0;
+ if (ReadIsHole(raw))
+ {
+ // Hole
+ result = (uint8)PhysicsBackend::HeightFieldMaterial::Hole;
+ }
+ else if (info.SplatMaps[0])
+ {
+ // Use the layer with the highest influence (splatmap data is Mip0 so convert x/z coords back to LOD0)
+ uint8 layer = 0;
+ uint8 layerWeight = 0;
+ const int32 splatmapTextureIndex = (chunkZ * info.ChunkSize + z) * info.HeightmapSize + chunkX * info.ChunkSize + x;
+ for (int32 splatIndex = 0; splatIndex < TERRAIN_MAX_SPLATMAPS_COUNT; splatIndex++)
+ {
+ for (int32 channelIndex = 0; channelIndex < 4; channelIndex++)
+ {
+ // Assume splatmap data pitch matches the row size and shift by channel index to simply sample at R chanel
+ const Color32* splatmap = (const Color32*)((const byte*)info.SplatMaps[splatIndex] + channelIndex);
+ const uint8 splat = splatmap[splatmapTextureIndex].R;
+ if (splat > layerWeight)
+ {
+ layer = splatIndex * 4 + channelIndex;
+ layerWeight = splat;
+ if (layerWeight == MAX_uint8)
+ break;
+ }
+ }
+ if (layerWeight == MAX_uint8)
+ break;
+ }
+ result = layer;
+ }
+ return result;
+}
+
+bool CookCollision(TerrainDataUpdateInfo& info, TextureBase::InitData* initData, int32 collisionLod, Array* collisionData)
{
#if COMPILE_WITH_PHYSICS_COOKING
+ info.GetSplatMaps();
PROFILE_CPU_NAMED("Terrain.CookCollision");
// Prepare data
const int32 collisionLOD = Math::Clamp(collisionLod, 0, initData->Mips.Count() - 1);
+ const int32 collisionLODInv = (int32)Math::Pow(2.0f, (float)collisionLOD);
const int32 heightFieldChunkSize = ((info.ChunkSize + 1) >> collisionLOD) - 1;
- const int32 heightFieldSize = heightFieldChunkSize * TerrainPatch::CHUNKS_COUNT_EDGE + 1;
+ const int32 heightFieldSize = heightFieldChunkSize * Terrain::ChunksCountEdge + 1;
const int32 heightFieldLength = heightFieldSize * heightFieldSize;
GET_TERRAIN_SCRATCH_BUFFER(heightFieldData, heightFieldLength, PhysicsBackend::HeightFieldSample);
PhysicsBackend::HeightFieldSample sample;
@@ -562,36 +651,30 @@ bool CookCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* ini
Platform::MemoryClear(heightFieldData, sizeof(PhysicsBackend::HeightFieldSample) * heightFieldLength);
// Setup terrain collision information
- auto& mip = initData->Mips[collisionLOD];
+ const auto& mip = initData->Mips[collisionLOD];
const int32 vertexCountEdgeMip = info.VertexCountEdge >> collisionLOD;
const int32 textureSizeMip = info.TextureSize >> collisionLOD;
- for (int32 chunkX = 0; chunkX < TerrainPatch::CHUNKS_COUNT_EDGE; chunkX++)
+ for (int32 chunkX = 0; chunkX < Terrain::ChunksCountEdge; chunkX++)
{
const int32 chunkTextureX = chunkX * vertexCountEdgeMip;
const int32 chunkStartX = chunkX * heightFieldChunkSize;
-
- for (int32 chunkZ = 0; chunkZ < TerrainPatch::CHUNKS_COUNT_EDGE; chunkZ++)
+ for (int32 chunkZ = 0; chunkZ < Terrain::ChunksCountEdge; chunkZ++)
{
const int32 chunkTextureZ = chunkZ * vertexCountEdgeMip;
const int32 chunkStartZ = chunkZ * heightFieldChunkSize;
-
for (int32 z = 0; z < vertexCountEdgeMip; z++)
{
+ const int32 heightmapZ = chunkStartZ + z;
for (int32 x = 0; x < vertexCountEdgeMip; x++)
{
- const int32 textureIndex = (chunkTextureZ + z) * textureSizeMip + chunkTextureX + x;
-
- const Color32 raw = mip.Data.Get()[textureIndex];
- const float normalizedHeight = ReadNormalizedHeight(raw);
- const bool isHole = ReadIsHole(raw);
-
const int32 heightmapX = chunkStartX + x;
- const int32 heightmapZ = chunkStartZ + z;
+
+ const int32 textureIndex = (chunkTextureZ + z) * textureSizeMip + chunkTextureX + x;
+ const Color32 raw = mip.Data.Get()[textureIndex];
+ sample.Height = int16(TERRAIN_PATCH_COLLISION_QUANTIZATION * ReadNormalizedHeight(raw));
+ sample.MaterialIndex0 = sample.MaterialIndex1 = GetPhysicalMaterial(raw, info, chunkZ, chunkX, z * collisionLODInv, x * collisionLODInv);
+
const int32 dstIndex = (heightmapX * heightFieldSize) + heightmapZ;
-
- sample.Height = int16(TERRAIN_PATCH_COLLISION_QUANTIZATION * normalizedHeight);
- sample.MaterialIndex0 = sample.MaterialIndex1 = isHole ? (uint8)PhysicsBackend::HeightFieldMaterial::Hole : 0;
-
heightFieldData[dstIndex] = sample;
}
}
@@ -620,16 +703,18 @@ bool CookCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* ini
#endif
}
-bool ModifyCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* initData, int32 collisionLod, const Int2& modifiedOffset, const Int2& modifiedSize, void* heightField)
+bool ModifyCollision(TerrainDataUpdateInfo& info, TextureBase::InitData* initData, int32 collisionLod, const Int2& modifiedOffset, const Int2& modifiedSize, void* heightField)
{
+ info.GetSplatMaps();
PROFILE_CPU_NAMED("Terrain.ModifyCollision");
// Prepare data
const Vector2 modifiedOffsetRatio((float)modifiedOffset.X / info.HeightmapSize, (float)modifiedOffset.Y / info.HeightmapSize);
const Vector2 modifiedSizeRatio((float)modifiedSize.X / info.HeightmapSize, (float)modifiedSize.Y / info.HeightmapSize);
const int32 collisionLOD = Math::Clamp(collisionLod, 0, initData->Mips.Count() - 1);
+ const int32 collisionLODInv = (int32)Math::Pow(2.0f, (float)collisionLOD);
const int32 heightFieldChunkSize = ((info.ChunkSize + 1) >> collisionLOD) - 1;
- const int32 heightFieldSize = heightFieldChunkSize * TerrainPatch::CHUNKS_COUNT_EDGE + 1;
+ const int32 heightFieldSize = heightFieldChunkSize * Terrain::ChunksCountEdge + 1;
const Int2 samplesOffset(Vector2::Floor(modifiedOffsetRatio * (float)heightFieldSize));
Int2 samplesSize(Vector2::Ceil(modifiedSizeRatio * (float)heightFieldSize));
samplesSize.X = Math::Max(samplesSize.X, 1);
@@ -646,56 +731,45 @@ bool ModifyCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* i
Platform::MemoryClear(heightFieldData, sizeof(PhysicsBackend::HeightFieldSample) * heightFieldDataLength);
// Setup terrain collision information
- auto& mip = initData->Mips[collisionLOD];
+ const auto& mip = initData->Mips[collisionLOD];
const int32 vertexCountEdgeMip = info.VertexCountEdge >> collisionLOD;
const int32 textureSizeMip = info.TextureSize >> collisionLOD;
- for (int32 chunkX = 0; chunkX < TerrainPatch::CHUNKS_COUNT_EDGE; chunkX++)
+ for (int32 chunkX = 0; chunkX < Terrain::ChunksCountEdge; chunkX++)
{
const int32 chunkTextureX = chunkX * vertexCountEdgeMip;
const int32 chunkStartX = chunkX * heightFieldChunkSize;
-
- // Skip unmodified chunks
if (chunkStartX >= samplesEnd.X || chunkStartX + vertexCountEdgeMip < samplesOffset.X)
- continue;
+ continue; // Skip unmodified chunks
- for (int32 chunkZ = 0; chunkZ < TerrainPatch::CHUNKS_COUNT_EDGE; chunkZ++)
+ for (int32 chunkZ = 0; chunkZ < Terrain::ChunksCountEdge; chunkZ++)
{
const int32 chunkTextureZ = chunkZ * vertexCountEdgeMip;
const int32 chunkStartZ = chunkZ * heightFieldChunkSize;
-
- // Skip unmodified chunks
if (chunkStartZ >= samplesEnd.Y || chunkStartZ + vertexCountEdgeMip < samplesOffset.Y)
- continue;
+ continue; // Skip unmodified chunks
// TODO: adjust loop range to reduce iterations count for edge cases (skip checking unmodified samples)
for (int32 z = 0; z < vertexCountEdgeMip; z++)
{
- // Skip unmodified columns
const int32 heightmapZ = chunkStartZ + z;
const int32 heightmapLocalZ = heightmapZ - samplesOffset.Y;
if (heightmapLocalZ < 0 || heightmapLocalZ >= samplesSize.Y)
- continue;
+ continue; // Skip unmodified columns
// TODO: adjust loop range to reduce iterations count for edge cases (skip checking unmodified samples)
for (int32 x = 0; x < vertexCountEdgeMip; x++)
{
- // Skip unmodified rows
const int32 heightmapX = chunkStartX + x;
const int32 heightmapLocalX = heightmapX - samplesOffset.X;
if (heightmapLocalX < 0 || heightmapLocalX >= samplesSize.X)
- continue;
+ continue; // Skip unmodified rows
const int32 textureIndex = (chunkTextureZ + z) * textureSizeMip + chunkTextureX + x;
-
const Color32 raw = mip.Data.Get()[textureIndex];
- const float normalizedHeight = ReadNormalizedHeight(raw);
- const bool isHole = ReadIsHole(raw);
+ sample.Height = int16(TERRAIN_PATCH_COLLISION_QUANTIZATION * ReadNormalizedHeight(raw));
+ sample.MaterialIndex0 = sample.MaterialIndex1 = GetPhysicalMaterial(raw, info, chunkZ, chunkX, z * collisionLODInv, x * collisionLODInv);
const int32 dstIndex = (heightmapLocalX * samplesSize.Y) + heightmapLocalZ;
-
- sample.Height = int16(TERRAIN_PATCH_COLLISION_QUANTIZATION * normalizedHeight);
- sample.MaterialIndex0 = sample.MaterialIndex1 = isHole ? (uint8)PhysicsBackend::HeightFieldMaterial::Hole : 0;
-
heightFieldData[dstIndex] = sample;
}
}
@@ -718,49 +792,34 @@ bool ModifyCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* i
bool TerrainPatch::SetupHeightMap(int32 heightMapLength, const float* heightMap, const byte* holesMask, bool forceUseVirtualStorage)
{
- // Validate input
+ PROFILE_CPU_NAMED("Terrain.Setup");
if (heightMap == nullptr)
{
LOG(Warning, "Cannot create terrain without a heightmap specified.");
return true;
}
- const int32 chunkSize = _terrain->_chunkSize;
- const int32 vertexCountEdge = chunkSize + 1;
- const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1;
- if (heightMapLength != heightMapSize * heightMapSize)
+ TerrainDataUpdateInfo info(this);
+ if (heightMapLength != info.HeightmapLength)
{
- LOG(Warning, "Invalid heightmap length. Terrain of chunk size equal {0} uses heightmap of size {1}x{1} (heightmap array length must be {2}). Input heightmap has length {3}.", chunkSize, heightMapSize, heightMapSize * heightMapSize, heightMapLength);
+ LOG(Warning, "Invalid heightmap length. Terrain of chunk size equal {0} uses heightmap of size {1}x{1} (heightmap array length must be {2}). Input heightmap has length {3}.", info.ChunkSize, info.HeightmapSize, info.HeightmapLength, heightMapLength);
return true;
}
const PixelFormat pixelFormat = PixelFormat::R8G8B8A8_UNorm;
- PROFILE_CPU_NAMED("Terrain.Setup");
-
// Input heightmap data overlaps on chunk edges but it needs to be duplicated for chunks (each chunk has own scale-bias for height values normalization)
- const int32 textureSize = vertexCountEdge * CHUNKS_COUNT_EDGE;
const int32 pixelStride = PixelFormatExtensions::SizeInBytes(pixelFormat);
- const int32 lodCount = Math::Min(_terrain->_lodCount, MipLevelsCount(vertexCountEdge) - 2);
-
- // Setup patch data info
- TerrainDataUpdateInfo info;
- info.ChunkSize = chunkSize;
- info.VertexCountEdge = vertexCountEdge;
- info.HeightmapSize = heightMapSize;
- info.HeightmapLength = heightMapLength;
- info.TextureSize = textureSize;
- info.PatchOffset = 0.0f;
- info.PatchHeight = 1.0f;
+ const int32 lodCount = Math::Min(_terrain->_lodCount, MipLevelsCount(info.VertexCountEdge) - 2);
// Process heightmap to get per-patch height normalization values
- float chunkOffsets[CHUNKS_COUNT];
- float chunkHeights[CHUNKS_COUNT];
+ float chunkOffsets[Terrain::ChunksCount];
+ float chunkHeights[Terrain::ChunksCount];
CalculateHeightmapRange(_terrain, info, heightMap, chunkOffsets, chunkHeights);
// Prepare
#if USE_EDITOR
const bool useVirtualStorage = Editor::IsPlayMode || forceUseVirtualStorage;
#else
- const bool useVirtualStorage = true;
+ const bool useVirtualStorage = true;
#endif
#if USE_EDITOR
String heightMapPath, heightFieldPath;
@@ -782,18 +841,17 @@ bool TerrainPatch::SetupHeightMap(int32 heightMapLength, const float* heightMap,
// Create heightmap texture data source container
auto initData = New();
initData->Format = pixelFormat;
- initData->Width = textureSize;
- initData->Height = textureSize;
+ initData->Width = info.TextureSize;
+ initData->Height = info.TextureSize;
initData->ArraySize = 1;
initData->Mips.Resize(lodCount);
// Allocate top mip data
{
PROFILE_CPU_NAMED("Terrain.AllocateHeightmap");
-
auto& mip = initData->Mips[0];
- mip.RowPitch = textureSize * pixelStride;
- mip.SlicePitch = mip.RowPitch * textureSize;
+ mip.RowPitch = info.TextureSize * pixelStride;
+ mip.SlicePitch = mip.RowPitch * info.TextureSize;
mip.Data.Allocate(mip.SlicePitch);
}
@@ -857,11 +915,11 @@ bool TerrainPatch::SetupHeightMap(int32 heightMapLength, const float* heightMap,
}
}
#else
- else
- {
- // Not supported
- CRASH;
- }
+ else
+ {
+ // Not supported
+ CRASH;
+ }
#endif
// Prepare collision data destination container
@@ -922,7 +980,7 @@ bool TerrainPatch::SetupHeightMap(int32 heightMapLength, const float* heightMap,
// Update data
_yOffset = info.PatchOffset;
_yHeight = info.PatchHeight;
- for (int32 chunkIndex = 0; chunkIndex < CHUNKS_COUNT; chunkIndex++)
+ for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++)
{
auto& chunk = Chunks[chunkIndex];
chunk._yOffset = chunkOffsets[chunkIndex];
@@ -945,21 +1003,17 @@ bool TerrainPatch::SetupHeightMap(int32 heightMapLength, const float* heightMap,
bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color32* splatMap, bool forceUseVirtualStorage)
{
+ PROFILE_CPU_NAMED("Terrain.SetupSplatMap");
CHECK_RETURN(index >= 0 && index < TERRAIN_MAX_SPLATMAPS_COUNT, true);
-
- // Validate input
if (splatMap == nullptr)
{
LOG(Warning, "Cannot create terrain without any splatmap specified.");
return true;
}
- const int32 chunkSize = _terrain->_chunkSize;
- const int32 vertexCountEdge = chunkSize + 1;
- const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1;
- const int32 heightMapLength = heightMapSize * heightMapSize;
- if (splatMapLength != heightMapLength)
+ TerrainDataUpdateInfo info(this, _yOffset, _yHeight);
+ if (splatMapLength != info.HeightmapLength)
{
- LOG(Warning, "Invalid splatmap length. Terrain of chunk size equal {0} uses heightmap of size {1}x{1} (heightmap array length must be {2}). Input heightmap has length {3}.", chunkSize, heightMapSize, heightMapLength, splatMapLength);
+ LOG(Warning, "Invalid splatmap length. Terrain of chunk size equal {0} uses heightmap of size {1}x{1} (heightmap array length must be {2}). Input heightmap has length {3}.", info.ChunkSize, info.HeightmapSize, info.HeightmapLength, splatMapLength);
return true;
}
const PixelFormat pixelFormat = PixelFormat::R8G8B8A8_UNorm;
@@ -974,28 +1028,15 @@ bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color3
}
}
- PROFILE_CPU_NAMED("Terrain.SetupSplatMap");
-
// Input splatmap data overlaps on chunk edges but it needs to be duplicated for chunks
- const int32 textureSize = vertexCountEdge * CHUNKS_COUNT_EDGE;
const int32 pixelStride = PixelFormatExtensions::SizeInBytes(pixelFormat);
- const int32 lodCount = Math::Min(_terrain->_lodCount, MipLevelsCount(vertexCountEdge) - 2);
-
- // Setup patch data info
- TerrainDataUpdateInfo info;
- info.ChunkSize = chunkSize;
- info.VertexCountEdge = vertexCountEdge;
- info.HeightmapSize = heightMapSize;
- info.HeightmapLength = heightMapLength;
- info.TextureSize = textureSize;
- info.PatchOffset = _yOffset;
- info.PatchHeight = _yHeight;
+ const int32 lodCount = Math::Min(_terrain->_lodCount, MipLevelsCount(info.VertexCountEdge) - 2);
// Prepare
#if USE_EDITOR
const bool useVirtualStorage = Editor::IsPlayMode || forceUseVirtualStorage;
#else
- const bool useVirtualStorage = true;
+ const bool useVirtualStorage = true;
#endif
#if USE_EDITOR
String splatMapPath;
@@ -1016,18 +1057,17 @@ bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color3
// Create heightmap texture data source container
auto initData = New();
initData->Format = pixelFormat;
- initData->Width = textureSize;
- initData->Height = textureSize;
+ initData->Width = info.TextureSize;
+ initData->Height = info.TextureSize;
initData->ArraySize = 1;
initData->Mips.Resize(lodCount);
// Allocate top mip data
{
PROFILE_CPU_NAMED("Terrain.AllocateSplatmap");
-
auto& mip = initData->Mips[0];
- mip.RowPitch = textureSize * pixelStride;
- mip.SlicePitch = mip.RowPitch * textureSize;
+ mip.RowPitch = info.TextureSize * pixelStride;
+ mip.SlicePitch = mip.RowPitch * info.TextureSize;
mip.Data.Allocate(mip.SlicePitch);
}
@@ -1091,11 +1131,11 @@ bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color3
}
}
#else
- else
- {
- // Not supported
- CRASH;
- }
+ else
+ {
+ // Not supported
+ CRASH;
+ }
#endif
#if TERRAIN_UPDATING
@@ -1112,9 +1152,7 @@ bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color3
bool TerrainPatch::InitializeHeightMap()
{
PROFILE_CPU_NAMED("Terrain.InitializeHeightMap");
-
- // Initialize with flat heightmap data
- const auto heightmapSize = _terrain->GetChunkSize() * TerrainPatch::CHUNKS_COUNT_EDGE + 1;
+ const auto heightmapSize = _terrain->GetChunkSize() * Terrain::ChunksCountEdge + 1;
Array heightmap;
heightmap.Resize(heightmapSize * heightmapSize);
heightmap.SetAll(0.0f);
@@ -1179,6 +1217,7 @@ void TerrainPatch::ClearCache()
void TerrainPatch::CacheHeightData()
{
PROFILE_CPU_NAMED("Terrain.CacheHeightData");
+ const TerrainDataUpdateInfo info(this);
// Ensure that heightmap data is all loaded
// TODO: disable streaming for heightmap texture if it's being modified by the editor
@@ -1198,16 +1237,9 @@ void TerrainPatch::CacheHeightData()
return;
}
- // Get texture input (note: this must match Setup method)
- const int32 chunkSize = _terrain->_chunkSize;
- const int32 vertexCountEdge = chunkSize + 1;
- const int32 textureSize = vertexCountEdge * CHUNKS_COUNT_EDGE;
- const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1;
-
// Allocate data
- const int32 heightMapLength = heightMapSize * heightMapSize;
- _cachedHeightMap.Resize(heightMapLength);
- _cachedHolesMask.Resize(heightMapLength);
+ _cachedHeightMap.Resize(info.HeightmapLength);
+ _cachedHolesMask.Resize(info.HeightmapLength);
_wasHeightModified = false;
// Extract heightmap data and denormalize it to get the pure height field
@@ -1215,20 +1247,20 @@ void TerrainPatch::CacheHeightData()
const float patchHeight = _yHeight;
const auto heightmapPtr = _cachedHeightMap.Get();
const auto holesMaskPtr = _cachedHolesMask.Get();
- for (int32 chunkIndex = 0; chunkIndex < CHUNKS_COUNT; chunkIndex++)
+ for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++)
{
- const int32 chunkTextureX = Chunks[chunkIndex]._x * vertexCountEdge;
- const int32 chunkTextureZ = Chunks[chunkIndex]._z * vertexCountEdge;
+ const int32 chunkTextureX = Chunks[chunkIndex]._x * info.VertexCountEdge;
+ const int32 chunkTextureZ = Chunks[chunkIndex]._z * info.VertexCountEdge;
- const int32 chunkHeightmapX = Chunks[chunkIndex]._x * chunkSize;
- const int32 chunkHeightmapZ = Chunks[chunkIndex]._z * chunkSize;
+ const int32 chunkHeightmapX = Chunks[chunkIndex]._x * info.ChunkSize;
+ const int32 chunkHeightmapZ = Chunks[chunkIndex]._z * info.ChunkSize;
- for (int32 z = 0; z < vertexCountEdge; z++)
+ for (int32 z = 0; z < info.VertexCountEdge; z++)
{
- const int32 tz = (chunkTextureZ + z) * textureSize;
- const int32 sz = (chunkHeightmapZ + z) * heightMapSize;
+ const int32 tz = (chunkTextureZ + z) * info.TextureSize;
+ const int32 sz = (chunkHeightmapZ + z) * info.HeightmapSize;
- for (int32 x = 0; x < vertexCountEdge; x++)
+ for (int32 x = 0; x < info.VertexCountEdge; x++)
{
const int32 tx = chunkTextureX + x;
const int32 sx = chunkHeightmapX + x;
@@ -1249,18 +1281,14 @@ void TerrainPatch::CacheHeightData()
void TerrainPatch::CacheSplatData()
{
- // Prepare
- const int32 chunkSize = _terrain->_chunkSize;
- const int32 vertexCountEdge = chunkSize + 1;
- const int32 textureSize = vertexCountEdge * CHUNKS_COUNT_EDGE;
- const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1;
- const int32 heightMapLength = heightMapSize * heightMapSize;
+ PROFILE_CPU_NAMED("Terrain.CacheSplatData");
+ const TerrainDataUpdateInfo info(this);
// Cache all the splatmaps
for (int32 index = 0; index < TERRAIN_MAX_SPLATMAPS_COUNT; index++)
{
// Allocate data
- _cachedSplatMap[index].Resize(heightMapLength);
+ _cachedSplatMap[index].Resize(info.HeightmapLength);
_wasSplatmapModified[index] = false;
// Skip if has missing splatmap asset
@@ -1272,8 +1300,6 @@ void TerrainPatch::CacheSplatData()
continue;
}
- PROFILE_CPU_NAMED("Terrain.CacheSplatData");
-
// Ensure that splatmap data is all loaded
// TODO: disable streaming for heightmap texture if it's being modified by the editor
if (Splatmap[index]->WaitForLoaded())
@@ -1294,20 +1320,20 @@ void TerrainPatch::CacheSplatData()
// Extract splatmap data
const auto splatMapPtr = static_cast(_cachedSplatMap[index].Get());
- for (int32 chunkIndex = 0; chunkIndex < CHUNKS_COUNT; chunkIndex++)
+ for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++)
{
- const int32 chunkTextureX = Chunks[chunkIndex]._x * vertexCountEdge;
- const int32 chunkTextureZ = Chunks[chunkIndex]._z * vertexCountEdge;
+ const int32 chunkTextureX = Chunks[chunkIndex]._x * info.VertexCountEdge;
+ const int32 chunkTextureZ = Chunks[chunkIndex]._z * info.VertexCountEdge;
- const int32 chunkHeightmapX = Chunks[chunkIndex]._x * chunkSize;
- const int32 chunkHeightmapZ = Chunks[chunkIndex]._z * chunkSize;
+ const int32 chunkHeightmapX = Chunks[chunkIndex]._x * info.ChunkSize;
+ const int32 chunkHeightmapZ = Chunks[chunkIndex]._z * info.ChunkSize;
- for (int32 z = 0; z < vertexCountEdge; z++)
+ for (int32 z = 0; z < info.VertexCountEdge; z++)
{
- const int32 tz = (chunkTextureZ + z) * textureSize;
- const int32 sz = (chunkHeightmapZ + z) * heightMapSize;
+ const int32 tz = (chunkTextureZ + z) * info.TextureSize;
+ const int32 sz = (chunkHeightmapZ + z) * info.HeightmapSize;
- for (int32 x = 0; x < vertexCountEdge; x++)
+ for (int32 x = 0; x < info.VertexCountEdge; x++)
{
const int32 tx = chunkTextureX + x;
const int32 sx = chunkHeightmapX + x;
@@ -1324,9 +1350,7 @@ void TerrainPatch::CacheSplatData()
bool TerrainPatch::ModifyHeightMap(const float* samples, const Int2& modifiedOffset, const Int2& modifiedSize)
{
// Validate input samples range
- const int32 chunkSize = _terrain->_chunkSize;
- const int32 vertexCountEdge = chunkSize + 1;
- const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1;
+ TerrainDataUpdateInfo info(this);
if (samples == nullptr)
{
LOG(Warning, "Missing heightmap samples data.");
@@ -1334,13 +1358,12 @@ bool TerrainPatch::ModifyHeightMap(const float* samples, const Int2& modifiedOff
}
if (modifiedOffset.X < 0 || modifiedOffset.Y < 0 ||
modifiedSize.X <= 0 || modifiedSize.Y <= 0 ||
- modifiedOffset.X + modifiedSize.X > heightMapSize ||
- modifiedOffset.Y + modifiedSize.Y > heightMapSize)
+ modifiedOffset.X + modifiedSize.X > info.HeightmapSize ||
+ modifiedOffset.Y + modifiedSize.Y > info.HeightmapSize)
{
LOG(Warning, "Invalid heightmap samples range.");
return true;
}
-
PROFILE_CPU_NAMED("Terrain.ModifyHeightMap");
// Check if has no heightmap
@@ -1364,31 +1387,20 @@ bool TerrainPatch::ModifyHeightMap(const float* samples, const Int2& modifiedOff
// Modify heightmap data
{
PROFILE_CPU_NAMED("Terrain.WrtieCache");
-
for (int32 z = 0; z < modifiedSize.Y; z++)
{
// TODO: use batches row mem copy
for (int32 x = 0; x < modifiedSize.X; x++)
{
- heightMap[(z + modifiedOffset.Y) * heightMapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x];
+ heightMap[(z + modifiedOffset.Y) * info.HeightmapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x];
}
}
}
- // Setup patch data info
- TerrainDataUpdateInfo info;
- info.ChunkSize = chunkSize;
- info.VertexCountEdge = vertexCountEdge;
- info.HeightmapSize = heightMapSize;
- info.HeightmapLength = heightMapSize * heightMapSize;
- info.TextureSize = vertexCountEdge * CHUNKS_COUNT_EDGE;
- info.PatchOffset = 0.0f;
- info.PatchHeight = 1.0f;
-
// Process heightmap to get per-patch height normalization values
- float chunkOffsets[CHUNKS_COUNT];
- float chunkHeights[CHUNKS_COUNT];
+ float chunkOffsets[Terrain::ChunksCount];
+ float chunkHeights[Terrain::ChunksCount];
CalculateHeightmapRange(_terrain, info, heightMap, chunkOffsets, chunkHeights);
// TODO: maybe calculate chunk ranges for only modified chunks
const bool wasHeightRangeChanged = Math::NotNearEqual(_yOffset, info.PatchOffset) || Math::NotNearEqual(_yHeight, info.PatchHeight);
@@ -1418,7 +1430,7 @@ bool TerrainPatch::ModifyHeightMap(const float* samples, const Int2& modifiedOff
// Update all the stuff
_yOffset = info.PatchOffset;
_yHeight = info.PatchHeight;
- for (int32 chunkIndex = 0; chunkIndex < CHUNKS_COUNT; chunkIndex++)
+ for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++)
{
auto& chunk = Chunks[chunkIndex];
chunk._yOffset = chunkOffsets[chunkIndex];
@@ -1426,15 +1438,13 @@ bool TerrainPatch::ModifyHeightMap(const float* samples, const Int2& modifiedOff
chunk.UpdateTransform();
}
_terrain->UpdateBounds();
- return UpdateHeightData(info, modifiedOffset, modifiedSize, wasHeightRangeChanged);
+ return UpdateHeightData(info, modifiedOffset, modifiedSize, wasHeightRangeChanged, true);
}
bool TerrainPatch::ModifyHolesMask(const byte* samples, const Int2& modifiedOffset, const Int2& modifiedSize)
{
// Validate input samples range
- const int32 chunkSize = _terrain->_chunkSize;
- const int32 vertexCountEdge = chunkSize + 1;
- const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1;
+ TerrainDataUpdateInfo info(this, _yOffset, _yHeight);
if (samples == nullptr)
{
LOG(Warning, "Missing holes mask samples data.");
@@ -1442,13 +1452,12 @@ bool TerrainPatch::ModifyHolesMask(const byte* samples, const Int2& modifiedOffs
}
if (modifiedOffset.X < 0 || modifiedOffset.Y < 0 ||
modifiedSize.X <= 0 || modifiedSize.Y <= 0 ||
- modifiedOffset.X + modifiedSize.X > heightMapSize ||
- modifiedOffset.Y + modifiedSize.Y > heightMapSize)
+ modifiedOffset.X + modifiedSize.X > info.HeightmapSize ||
+ modifiedOffset.Y + modifiedSize.Y > info.HeightmapSize)
{
LOG(Warning, "Invalid holes mask samples range.");
return true;
}
-
PROFILE_CPU_NAMED("Terrain.ModifyHolesMask");
// Check if has no heightmap
@@ -1472,28 +1481,17 @@ bool TerrainPatch::ModifyHolesMask(const byte* samples, const Int2& modifiedOffs
// Modify holes mask data
{
PROFILE_CPU_NAMED("Terrain.WrtieCache");
-
for (int32 z = 0; z < modifiedSize.Y; z++)
{
// TODO: use batches row mem copy
for (int32 x = 0; x < modifiedSize.X; x++)
{
- holesMask[(z + modifiedOffset.Y) * heightMapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x];
+ holesMask[(z + modifiedOffset.Y) * info.HeightmapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x];
}
}
}
- // Setup patch data info
- TerrainDataUpdateInfo info;
- info.ChunkSize = chunkSize;
- info.VertexCountEdge = vertexCountEdge;
- info.HeightmapSize = heightMapSize;
- info.HeightmapLength = heightMapSize * heightMapSize;
- info.TextureSize = vertexCountEdge * CHUNKS_COUNT_EDGE;
- info.PatchOffset = _yOffset;
- info.PatchHeight = _yHeight;
-
// Check if has allocated texture
if (_dataHeightmap)
{
@@ -1505,7 +1503,7 @@ bool TerrainPatch::ModifyHolesMask(const byte* samples, const Int2& modifiedOffs
}
// Update all the stuff
- return UpdateHeightData(info, modifiedOffset, modifiedSize, false);
+ return UpdateHeightData(info, modifiedOffset, modifiedSize, false, true);
}
bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int2& modifiedOffset, const Int2& modifiedSize)
@@ -1523,9 +1521,7 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int
}
// Validate input samples range
- const int32 chunkSize = _terrain->_chunkSize;
- const int32 vertexCountEdge = chunkSize + 1;
- const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1;
+ TerrainDataUpdateInfo info(this, _yOffset, _yHeight);
if (samples == nullptr)
{
LOG(Warning, "Missing splatmap samples data.");
@@ -1533,13 +1529,12 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int
}
if (modifiedOffset.X < 0 || modifiedOffset.Y < 0 ||
modifiedSize.X <= 0 || modifiedSize.Y <= 0 ||
- modifiedOffset.X + modifiedSize.X > heightMapSize ||
- modifiedOffset.Y + modifiedSize.Y > heightMapSize)
+ modifiedOffset.X + modifiedSize.X > info.HeightmapSize ||
+ modifiedOffset.Y + modifiedSize.Y > info.HeightmapSize)
{
LOG(Warning, "Invalid heightmap samples range.");
return true;
}
-
PROFILE_CPU_NAMED("Terrain.ModifySplatMap");
// Get the current data to modify it
@@ -1552,14 +1547,13 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int
// Modify splat map data
{
PROFILE_CPU_NAMED("Terrain.WrtieCache");
-
for (int32 z = 0; z < modifiedSize.Y; z++)
{
// TODO: use batches row mem copy
for (int32 x = 0; x < modifiedSize.X; x++)
{
- splatMap[(z + modifiedOffset.Y) * heightMapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x];
+ splatMap[(z + modifiedOffset.Y) * info.HeightmapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x];
}
}
}
@@ -1570,7 +1564,6 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int
if (dataSplatmap == nullptr)
{
PROFILE_CPU_NAMED("Terrain.InitDataStorage");
-
if (Heightmap->WaitForLoaded())
{
LOG(Error, "Failed to load heightmap.");
@@ -1597,16 +1590,6 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int
mip.Data.Allocate(mip.SlicePitch);
}
- // Setup patch data info
- TerrainDataUpdateInfo info;
- info.ChunkSize = chunkSize;
- info.VertexCountEdge = vertexCountEdge;
- info.HeightmapSize = heightMapSize;
- info.HeightmapLength = heightMapSize * heightMapSize;
- info.TextureSize = vertexCountEdge * CHUNKS_COUNT_EDGE;
- info.PatchOffset = _yOffset;
- info.PatchHeight = _yHeight;
-
// Update splat map storage data
const bool hasSplatmap = splatmap;
const auto splatmapData = dataSplatmap->Mips[0].Data.Get();
@@ -1653,7 +1636,7 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int
#if USE_EDITOR
const bool useVirtualStorage = Editor::IsPlayMode || Heightmap->IsVirtual();
#else
- const bool useVirtualStorage = true;
+ const bool useVirtualStorage = true;
#endif
// Save the splatmap data to the asset
@@ -1697,11 +1680,11 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int
}
}
#else
- else
- {
- // Not supported
- CRASH;
- }
+ else
+ {
+ // Not supported
+ CRASH;
+ }
#endif
}
@@ -1712,12 +1695,18 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int
// TODO: disable splatmap dynamic streaming - data on a GPU was modified and we don't want to override it with the old data stored in the asset container
+ // Update heightfield to reflect physical materials layering
+ if (info.UsePhysicalMaterials() && HasCollision())
+ {
+ UpdateHeightData(info, modifiedOffset, modifiedSize, false, false);
+ }
+
return false;
}
-bool TerrainPatch::UpdateHeightData(const TerrainDataUpdateInfo& info, const Int2& modifiedOffset, const Int2& modifiedSize, bool wasHeightRangeChanged)
+bool TerrainPatch::UpdateHeightData(TerrainDataUpdateInfo& info, const Int2& modifiedOffset, const Int2& modifiedSize, bool wasHeightRangeChanged, bool wasHeightChanged)
{
- // Prepare
+ PROFILE_CPU();
float* heightMap = GetHeightmapData();
byte* holesMask = GetHolesMaskData();
ASSERT(heightMap && holesMask);
@@ -1753,9 +1742,7 @@ bool TerrainPatch::UpdateHeightData(const TerrainDataUpdateInfo& info, const Int
// Downscale mip data for all lower LODs
if (GenerateMips(_dataHeightmap))
- {
return true;
- }
// Fix generated mip maps to keep the same values for chunk edges (reduce cracks on continuous LOD transitions)
FixMips(info, _dataHeightmap, pixelStride);
@@ -1779,9 +1766,7 @@ bool TerrainPatch::UpdateHeightData(const TerrainDataUpdateInfo& info, const Int
}
const auto collisionData = &_heightfield->Data;
if (CookCollision(info, _dataHeightmap, _terrain->_collisionLod, collisionData))
- {
return true;
- }
UpdateCollision();
}
else
@@ -1789,7 +1774,8 @@ bool TerrainPatch::UpdateHeightData(const TerrainDataUpdateInfo& info, const Int
ScopeLock lock(_collisionLocker);
if (ModifyCollision(info, _dataHeightmap, _terrain->_collisionLod, modifiedOffset, modifiedSize, _physicsHeightField))
return true;
- UpdateCollisionScale();
+ if (wasHeightChanged)
+ UpdateCollisionScale();
}
#else
// Modify heightfield samples (without cooking collision which is done on a separate async task)
@@ -1811,6 +1797,9 @@ bool TerrainPatch::UpdateHeightData(const TerrainDataUpdateInfo& info, const Int
}
#endif
+ if (!wasHeightChanged)
+ return false;
+
// Invalidate cache
#if TERRAIN_USE_PHYSICS_DEBUG
_debugLines.Resize(0);
@@ -1843,18 +1832,8 @@ void TerrainPatch::SaveHeightData()
{
return;
}
-
PROFILE_CPU_NAMED("Terrain.Save");
-
- // Setup patch data info
- TerrainDataUpdateInfo info;
- info.ChunkSize = _terrain->_chunkSize;
- info.VertexCountEdge = info.ChunkSize + 1;
- info.HeightmapSize = info.ChunkSize * CHUNKS_COUNT_EDGE + 1;
- info.HeightmapLength = info.HeightmapSize * info.HeightmapSize;
- info.TextureSize = info.VertexCountEdge * CHUNKS_COUNT_EDGE;
- info.PatchOffset = _yOffset;
- info.PatchHeight = _yHeight;
+ TerrainDataUpdateInfo info(this, _yOffset, _yHeight);
// Save heightmap to asset
if (Heightmap->WaitForLoaded())
@@ -1913,7 +1892,6 @@ void TerrainPatch::SaveSplatData(int32 index)
{
return;
}
-
PROFILE_CPU_NAMED("Terrain.Save");
// Save splatmap to asset
@@ -1937,6 +1915,7 @@ void TerrainPatch::SaveSplatData(int32 index)
bool TerrainPatch::UpdateCollision()
{
+ PROFILE_CPU();
ScopeLock lock(_collisionLocker);
// Update collision
@@ -2013,7 +1992,7 @@ bool TerrainPatch::RayCast(const Vector3& origin, const Vector3& direction, floa
// Find hit chunk
resultChunk = nullptr;
const auto hitPoint = origin + direction * hitDistance;
- for (int32 chunkIndex = 0; chunkIndex < CHUNKS_COUNT; chunkIndex++)
+ for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++)
{
const auto box = Chunks[chunkIndex]._bounds;
if (box.Minimum.X <= hitPoint.X && box.Maximum.X >= hitPoint.X &&
@@ -2068,7 +2047,7 @@ void TerrainPatch::ClosestPoint(const Vector3& position, Vector3& result) const
void TerrainPatch::UpdatePostManualDeserialization()
{
// Update data
- for (int32 chunkIndex = 0; chunkIndex < CHUNKS_COUNT; chunkIndex++)
+ for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++)
{
auto& chunk = Chunks[chunkIndex];
chunk.UpdateTransform();
@@ -2111,6 +2090,7 @@ void TerrainPatch::UpdatePostManualDeserialization()
void TerrainPatch::CreateCollision()
{
+ PROFILE_CPU();
ASSERT(!HasCollision());
if (CreateHeightField())
return;
@@ -2125,7 +2105,10 @@ void TerrainPatch::CreateCollision()
shape.SetHeightField(_physicsHeightField, heightScale, rowScale, columnScale);
// Create shape
- _physicsShape = PhysicsBackend::CreateShape(_terrain, shape, _terrain->PhysicalMaterial.Get(), _terrain->IsActiveInHierarchy(), false);
+ JsonAsset* materials[8];
+ for (int32 i = 0; i < 8; i++)
+ materials[i] = _terrain->GetPhysicalMaterials()[i];
+ _physicsShape = PhysicsBackend::CreateShape(_terrain, shape, ToSpan(materials, 8), _terrain->IsActiveInHierarchy(), false);
PhysicsBackend::SetShapeLocalPose(_physicsShape, Vector3(0, _yOffset * terrainTransform.Scale.Y, 0), Quaternion::Identity);
// Create static actor
@@ -2137,6 +2120,7 @@ void TerrainPatch::CreateCollision()
bool TerrainPatch::CreateHeightField()
{
+ PROFILE_CPU();
ASSERT(_physicsHeightField == nullptr);
// Skip if height field data is missing but warn on loading failed
@@ -2162,6 +2146,7 @@ bool TerrainPatch::CreateHeightField()
void TerrainPatch::UpdateCollisionScale() const
{
+ PROFILE_CPU();
ASSERT(HasCollision());
// Create geometry
@@ -2179,6 +2164,7 @@ void TerrainPatch::UpdateCollisionScale() const
void TerrainPatch::DestroyCollision()
{
+ PROFILE_CPU();
ScopeLock lock(_collisionLocker);
ASSERT(HasCollision());
@@ -2205,6 +2191,7 @@ void TerrainPatch::DestroyCollision()
void TerrainPatch::CacheDebugLines()
{
+ PROFILE_CPU();
ASSERT(_debugLines.IsEmpty() && _physicsHeightField);
int32 rows, cols;
@@ -2213,12 +2200,21 @@ void TerrainPatch::CacheDebugLines()
_debugLines.Resize((rows - 1) * (cols - 1) * 6 + (cols + rows - 2) * 2);
Vector3* data = _debugLines.Get();
-#define GET_VERTEX(x, y) const Vector3 v##x##y((float)(row + (x)), PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, (float)(row + (x)), (float)(col + (y))) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)(col + (y)))
+#define GET_VERTEX(x, y) const Vector3 v##x##y((float)(row + (x)), PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, row + (x), col + (y)) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)(col + (y)))
for (int32 row = 0; row < rows - 1; row++)
{
for (int32 col = 0; col < cols - 1; col++)
{
+ // Skip holes
+ const auto sample = PhysicsBackend::GetHeightFieldSample(_physicsHeightField, row, col);
+ if (sample.MaterialIndex0 == (uint8)PhysicsBackend::HeightFieldMaterial::Hole)
+ {
+ for (int32 i = 0; i < 6; i++)
+ *data++ = Vector3::Zero;
+ continue;
+ }
+
GET_VERTEX(0, 0);
GET_VERTEX(0, 1);
GET_VERTEX(1, 0);
@@ -2294,6 +2290,7 @@ const Array& TerrainPatch::GetCollisionTriangles()
ScopeLock lock(_collisionLocker);
if (!_physicsShape || _collisionTriangles.HasItems())
return _collisionTriangles;
+ PROFILE_CPU();
int32 rows, cols;
PhysicsBackend::GetHeightFieldSize(_physicsHeightField, rows, cols);
@@ -2301,9 +2298,9 @@ const Array& TerrainPatch::GetCollisionTriangles()
_collisionTriangles.Resize((rows - 1) * (cols - 1) * 6);
Vector3* data = _collisionTriangles.Get();
-#define GET_VERTEX(x, y) Vector3 v##x##y((float)(row + (x)), PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, (float)(row + (x)), (float)(col + (y))) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)(col + (y))); Vector3::Transform(v##x##y, world, v##x##y)
+#define GET_VERTEX(x, y) Vector3 v##x##y((float)(row + (x)), PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, row + (x), col + (y)) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)(col + (y))); Vector3::Transform(v##x##y, world, v##x##y)
- const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * CHUNKS_COUNT_EDGE;
+ const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::Terrain::ChunksCountEdge;
const Transform terrainTransform = _terrain->_transform;
Transform localTransform(Vector3(_x * size, _yOffset, _z * size), Quaternion::Identity, Vector3(_collisionScaleXZ, _yHeight, _collisionScaleXZ));
const Matrix world = localTransform.GetWorld() * terrainTransform.GetWorld();
@@ -2312,6 +2309,15 @@ const Array& TerrainPatch::GetCollisionTriangles()
{
for (int32 col = 0; col < cols - 1; col++)
{
+ // Skip holes
+ const auto sample = PhysicsBackend::GetHeightFieldSample(_physicsHeightField, row, col);
+ if (sample.MaterialIndex0 == (uint8)PhysicsBackend::HeightFieldMaterial::Hole)
+ {
+ for (int32 i = 0; i < 6; i++)
+ *data++ = Vector3::Zero;
+ continue;
+ }
+
GET_VERTEX(0, 0);
GET_VERTEX(0, 1);
GET_VERTEX(1, 0);
@@ -2334,6 +2340,7 @@ const Array& TerrainPatch::GetCollisionTriangles()
void TerrainPatch::GetCollisionTriangles(const BoundingSphere& bounds, Array& result)
{
+ PROFILE_CPU();
result.Clear();
// Skip if no intersection with patch
@@ -2342,7 +2349,7 @@ void TerrainPatch::GetCollisionTriangles(const BoundingSphere& bounds, Array_chunkSize * TERRAIN_UNITS_PER_VERTEX * CHUNKS_COUNT_EDGE;
+ const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::Terrain::ChunksCountEdge;
Transform transform;
transform.Translation = _offset + Vector3(0, _yOffset, 0);
transform.Orientation = Quaternion::Identity;
@@ -2430,6 +2437,7 @@ void TerrainPatch::GetCollisionTriangles(const BoundingSphere& bounds, Array& vertexBuffer, Array& indexBuffer)
{
+ PROFILE_CPU();
vertexBuffer.Clear();
indexBuffer.Clear();
@@ -2447,7 +2455,7 @@ void TerrainPatch::ExtractCollisionGeometry(Array& vertexBuffer, Array_chunkSize * TERRAIN_UNITS_PER_VERTEX * CHUNKS_COUNT_EDGE;
+ const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::Terrain::ChunksCountEdge;
const Transform terrainTransform = _terrain->_transform;
const Transform localTransform(Vector3(_x * size, _yOffset, _z * size), Quaternion::Identity, Float3(_collisionScaleXZ, _yHeight, _collisionScaleXZ));
const Matrix world = localTransform.GetWorld() * terrainTransform.GetWorld();
@@ -2459,7 +2467,7 @@ void TerrainPatch::ExtractCollisionGeometry(Array& vertexBuffer, ArrayChunks[i] : nullptr);
@@ -2536,15 +2544,14 @@ void TerrainPatch::Deserialize(DeserializeStream& stream, ISerializeModifier* mo
DESERIALIZE_MEMBER(Heightfield, _heightfield);
// Update offset (x or/and z may be modified)
- const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * CHUNKS_COUNT_EDGE;
+ const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::ChunksCountEdge;
_offset = Vector3(_x * size, 0.0f, _z * size);
auto member = stream.FindMember("Chunks");
if (member != stream.MemberEnd() && member->value.IsArray())
{
auto& chunksData = member->value;
- const auto chunksCount = Math::Min((int32)chunksData.Size(), CHUNKS_COUNT);
-
+ const auto chunksCount = Math::Min((int32)chunksData.Size(), Terrain::ChunksCount);
for (int32 i = 0; i < chunksCount; i++)
{
Chunks[i].Deserialize(chunksData[i], modifier);
diff --git a/Source/Engine/Terrain/TerrainPatch.h b/Source/Engine/Terrain/TerrainPatch.h
index 689c629c5..f1a82dea1 100644
--- a/Source/Engine/Terrain/TerrainPatch.h
+++ b/Source/Engine/Terrain/TerrainPatch.h
@@ -15,22 +15,14 @@ class TerrainMaterialShader;
///
/// Represents single terrain patch made of 16 terrain chunks.
///
-class FLAXENGINE_API TerrainPatch : public ISerializable
+API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API TerrainPatch : public ScriptingObject, public ISerializable
{
+ DECLARE_SCRIPTING_TYPE(TerrainPatch);
friend Terrain;
friend TerrainPatch;
friend TerrainChunk;
-public:
-
- enum
- {
- CHUNKS_COUNT = 16,
- CHUNKS_COUNT_EDGE = 4,
- };
-
private:
-
Terrain* _terrain;
int16 _x, _z;
float _yOffset, _yHeight;
@@ -52,7 +44,7 @@ private:
TextureBase::InitData* _dataSplatmap[TERRAIN_MAX_SPLATMAPS_COUNT] = {};
#endif
#if TERRAIN_USE_PHYSICS_DEBUG
- Array _debugLines; // TODO: large-worlds
+ Array _debugLines; // TODO: large-worlds
#endif
#if USE_EDITOR
Array _collisionTriangles; // TODO: large-worlds
@@ -62,23 +54,21 @@ private:
void Init(Terrain* terrain, int16 x, int16 z);
public:
-
///
/// Finalizes an instance of the class.
///
~TerrainPatch();
public:
-
///
/// The chunks contained within the patch. Organized in 4x4 square.
///
- TerrainChunk Chunks[CHUNKS_COUNT];
+ TerrainChunk Chunks[Terrain::ChunksCount];
///
/// The heightmap texture.
///
- AssetReference Heightmap;
+ API_FIELD() AssetReference Heightmap;
///
/// The splatmap textures.
@@ -86,12 +76,10 @@ public:
AssetReference Splatmap[TERRAIN_MAX_SPLATMAPS_COUNT];
public:
-
///
/// Gets the Y axis heightmap offset from terrain origin.
///
- /// The offset.
- FORCE_INLINE float GetOffsetY() const
+ API_FUNCTION() FORCE_INLINE float GetOffsetY() const
{
return _yOffset;
}
@@ -99,8 +87,7 @@ public:
///
/// Gets the Y axis heightmap height.
///
- /// The height.
- FORCE_INLINE float GetHeightY() const
+ API_FUNCTION() FORCE_INLINE float GetHeightY() const
{
return _yHeight;
}
@@ -108,8 +95,7 @@ public:
///
/// Gets the x coordinate.
///
- /// The x position.
- FORCE_INLINE int32 GetX() const
+ API_FUNCTION() FORCE_INLINE int32 GetX() const
{
return _x;
}
@@ -117,8 +103,7 @@ public:
///
/// Gets the z coordinate.
///
- /// The z position.
- FORCE_INLINE int32 GetZ() const
+ API_FUNCTION() FORCE_INLINE int32 GetZ() const
{
return _z;
}
@@ -126,8 +111,7 @@ public:
///
/// Gets the terrain.
///
- /// The terrain,
- FORCE_INLINE Terrain* GetTerrain() const
+ API_FUNCTION() FORCE_INLINE Terrain* GetTerrain() const
{
return _terrain;
}
@@ -137,9 +121,9 @@ public:
///
/// The chunk zero-based index.
/// The chunk.
- TerrainChunk* GetChunk(int32 index)
+ API_FUNCTION() TerrainChunk* GetChunk(int32 index)
{
- if (index < 0 || index >= CHUNKS_COUNT)
+ if (index < 0 || index >= Terrain::ChunksCount)
return nullptr;
return &Chunks[index];
}
@@ -149,9 +133,9 @@ public:
///
/// The chunk location (x and z).
/// The chunk.
- TerrainChunk* GetChunk(const Int2& chunkCoord)
+ API_FUNCTION() TerrainChunk* GetChunk(API_PARAM(Ref) const Int2& chunkCoord)
{
- return GetChunk(chunkCoord.Y * CHUNKS_COUNT_EDGE + chunkCoord.X);
+ return GetChunk(chunkCoord.Y * Terrain::ChunksCountEdge + chunkCoord.X);
}
///
@@ -160,22 +144,43 @@ public:
/// The chunk location x.
/// The chunk location z.
/// The chunk.
- TerrainChunk* GetChunk(int32 x, int32 z)
+ API_FUNCTION() TerrainChunk* GetChunk(int32 x, int32 z)
{
- return GetChunk(z * CHUNKS_COUNT_EDGE + x);
+ return GetChunk(z * Terrain::ChunksCountEdge + x);
+ }
+
+ ///
+ /// Gets the splatmap assigned to this patch.
+ ///
+ /// The zero-based index of the splatmap.
+ /// The splatmap texture.
+ API_FUNCTION() AssetReference GetSplatmap(int32 index)
+ {
+ if (index < 0 || index >= TERRAIN_MAX_SPLATMAPS_COUNT)
+ return nullptr;
+ return Splatmap[index];
+ }
+
+ ///
+ /// Sets a splatmap to this patch.
+ ///
+ /// The zero-based index of the splatmap.
+ /// Splatmap texture.
+ API_FUNCTION() void SetSplatmap(int32 index, const AssetReference& splatMap)
+ {
+ if (index >= 0 && index < TERRAIN_MAX_SPLATMAPS_COUNT)
+ Splatmap[index] = splatMap;
}
///
/// Gets the patch world bounds.
///
- /// The bounding box.
- FORCE_INLINE const BoundingBox& GetBounds() const
+ API_FUNCTION() FORCE_INLINE const BoundingBox& GetBounds() const
{
return _bounds;
}
public:
-
///
/// Removes the lightmap data from the terrain patch.
///
@@ -192,12 +197,11 @@ public:
void UpdateTransform();
#if TERRAIN_EDITING
-
///
/// Initializes the patch heightmap and collision to the default flat level.
///
/// True if failed, otherwise false.
- bool InitializeHeightMap();
+ API_FUNCTION() bool InitializeHeightMap();
///
/// Setups the terrain patch using the specified heightmap data.
@@ -207,7 +211,7 @@ public:
/// The holes mask (optional). Normalized to 0-1 range values with holes mask per-vertex. Must match the heightmap dimensions.
/// If set to true patch will use virtual storage by force. Otherwise it can use normal texture asset storage on drive (valid only during Editor). Runtime-created terrain can only use virtual storage (in RAM).
/// True if failed, otherwise false.
- bool SetupHeightMap(int32 heightMapLength, const float* heightMap, const byte* holesMask = nullptr, bool forceUseVirtualStorage = false);
+ API_FUNCTION() bool SetupHeightMap(int32 heightMapLength, API_PARAM(Ref) const float* heightMap, API_PARAM(Ref) const byte* holesMask = nullptr, bool forceUseVirtualStorage = false);
///
/// Setups the terrain patch layer weights using the specified splatmaps data.
@@ -217,50 +221,48 @@ public:
/// The splat map. Each array item contains 4 layer weights.
/// If set to true patch will use virtual storage by force. Otherwise it can use normal texture asset storage on drive (valid only during Editor). Runtime-created terrain can only use virtual storage (in RAM).
/// True if failed, otherwise false.
- bool SetupSplatMap(int32 index, int32 splatMapLength, const Color32* splatMap, bool forceUseVirtualStorage = false);
-
+ API_FUNCTION() bool SetupSplatMap(int32 index, int32 splatMapLength, API_PARAM(Ref) const Color32* splatMap, bool forceUseVirtualStorage = false);
#endif
#if TERRAIN_UPDATING
-
///
/// Gets the raw pointer to the heightmap data.
///
/// The heightmap data.
- float* GetHeightmapData();
+ API_FUNCTION() float* GetHeightmapData();
///
/// Clears cache of the heightmap data.
///
- void ClearHeightmapCache();
+ API_FUNCTION() void ClearHeightmapCache();
///
/// Gets the raw pointer to the holes mask data.
///
/// The holes mask data.
- byte* GetHolesMaskData();
+ API_FUNCTION() byte* GetHolesMaskData();
///
/// Clears cache of the holes mask data.
///
- void ClearHolesMaskCache();
+ API_FUNCTION() void ClearHolesMaskCache();
///
/// Gets the raw pointer to the splat map data.
///
/// The zero-based index of the splatmap texture.
/// The splat map data.
- Color32* GetSplatMapData(int32 index);
+ API_FUNCTION() Color32* GetSplatMapData(int32 index);
///
/// Clears cache of the splat map data.
///
- void ClearSplatMapCache();
+ API_FUNCTION() void ClearSplatMapCache();
///
/// Clears all caches.
///
- void ClearCache();
+ API_FUNCTION() void ClearCache();
///
/// Modifies the terrain patch heightmap with the given samples.
@@ -269,7 +271,7 @@ public:
/// The offset from the first row and column of the heightmap data (offset destination x and z start position).
/// The size of the heightmap to modify (x and z). Amount of samples in each direction.
/// True if failed, otherwise false.
- bool ModifyHeightMap(const float* samples, const Int2& modifiedOffset, const Int2& modifiedSize);
+ API_FUNCTION() bool ModifyHeightMap(API_PARAM(Ref) const float* samples, API_PARAM(Ref) const Int2& modifiedOffset, API_PARAM(Ref) const Int2& modifiedSize);
///
/// Modifies the terrain patch holes mask with the given samples.
@@ -278,7 +280,7 @@ public:
/// The offset from the first row and column of the holes map data (offset destination x and z start position).
/// The size of the holes map to modify (x and z). Amount of samples in each direction.
/// True if failed, otherwise false.
- bool ModifyHolesMask(const byte* samples, const Int2& modifiedOffset, const Int2& modifiedSize);
+ API_FUNCTION() bool ModifyHolesMask(API_PARAM(Ref) const byte* samples, API_PARAM(Ref) const Int2& modifiedOffset, API_PARAM(Ref) const Int2& modifiedSize);
///
/// Modifies the terrain patch splat map (layers mask) with the given samples.
@@ -288,21 +290,18 @@ public:
/// The offset from the first row and column of the splat map data (offset destination x and z start position).
/// The size of the splat map to modify (x and z). Amount of samples in each direction.
/// True if failed, otherwise false.
- bool ModifySplatMap(int32 index, const Color32* samples, const Int2& modifiedOffset, const Int2& modifiedSize);
+ API_FUNCTION() bool ModifySplatMap(int32 index, API_PARAM(Ref) const Color32* samples, API_PARAM(Ref) const Int2& modifiedOffset, API_PARAM(Ref) const Int2& modifiedSize);
private:
-
- bool UpdateHeightData(const struct TerrainDataUpdateInfo& info, const Int2& modifiedOffset, const Int2& modifiedSize, bool wasHeightRangeChanged);
+ bool UpdateHeightData(struct TerrainDataUpdateInfo& info, const Int2& modifiedOffset, const Int2& modifiedSize, bool wasHeightRangeChanged, bool wasHeightChanged);
void SaveHeightData();
void CacheHeightData();
void SaveSplatData();
void SaveSplatData(int32 index);
void CacheSplatData();
-
#endif
public:
-
///
/// Performs a raycast against this terrain collision shape.
///
@@ -311,7 +310,7 @@ public:
/// The raycast result hit position distance from the ray origin. Valid only if raycast hits anything.
/// The maximum distance the ray should check for collisions.
/// True if ray hits an object, otherwise false.
- bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, float maxDistance = MAX_float) const;
+ API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, float maxDistance = MAX_float) const;
///
/// Performs a raycast against this terrain collision shape.
@@ -322,7 +321,7 @@ public:
/// The raycast result hit position normal vector. Valid only if raycast hits anything.
/// The maximum distance the ray should check for collisions.
/// True if ray hits an object, otherwise false.
- bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, Vector3& resultHitNormal, float maxDistance = MAX_float) const;
+ API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, API_PARAM(Out) Vector3& resultHitNormal, float maxDistance = MAX_float) const;
///
/// Performs a raycast against this terrain collision shape. Returns the hit chunk.
@@ -333,7 +332,7 @@ public:
/// The raycast result hit chunk. Valid only if raycast hits anything.
/// The maximum distance the ray should check for collisions.
/// True if ray hits an object, otherwise false.
- bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, TerrainChunk*& resultChunk, float maxDistance = MAX_float) const;
+ API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, API_PARAM(Out) TerrainChunk*& resultChunk, float maxDistance = MAX_float) const;
///
/// Performs a raycast against terrain collision, returns results in a RaycastHit structure.
@@ -343,28 +342,24 @@ public:
/// The result hit information. Valid only when method returns true.
/// The maximum distance the ray should check for collisions.
/// True if ray hits an object, otherwise false.
- bool RayCast(const Vector3& origin, const Vector3& direction, RayCastHit& hitInfo, float maxDistance = MAX_float) const;
+ API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float) const;
///
/// Gets a point on the terrain collider that is closest to a given location. Can be used to find a hit location or position to apply explosion force or any other special effects.
///
/// The position to find the closest point to it.
/// The result point on the collider that is closest to the specified location.
- void ClosestPoint(const Vector3& position, Vector3& result) const;
+ API_FUNCTION() void ClosestPoint(API_PARAM(Ref) const Vector3& position, API_PARAM(Out) Vector3& result) const;
#if USE_EDITOR
-
///
/// Updates the patch data after manual deserialization called at runtime (eg. by editor undo).
///
void UpdatePostManualDeserialization();
-
#endif
public:
-
#if USE_EDITOR
-
///
/// Gets the collision mesh triangles array (3 vertices per triangle in linear list). Cached internally to reuse data.
///
@@ -376,8 +371,7 @@ public:
///
/// The world-space bounds to find terrain triangles that intersect with it.
/// The result triangles that intersect with the given bounds (in world-space).
- void GetCollisionTriangles(const BoundingSphere& bounds, Array& result);
-
+ void GetCollisionTriangles(API_PARAM(Ref) const BoundingSphere& bounds, API_PARAM(Out) Array& result);
#endif
///
@@ -385,10 +379,9 @@ public:
///
/// The output vertex buffer.
/// The output index buffer.
- void ExtractCollisionGeometry(Array& vertexBuffer, Array& indexBuffer);
+ API_FUNCTION() void ExtractCollisionGeometry(API_PARAM(Out) Array& vertexBuffer, API_PARAM(Out) Array& indexBuffer);
private:
-
///
/// Determines whether this patch has created collision representation.
///
@@ -419,8 +412,8 @@ private:
void DestroyCollision();
#if TERRAIN_USE_PHYSICS_DEBUG
- void CacheDebugLines();
- void DrawPhysicsDebug(RenderView& view);
+ void CacheDebugLines();
+ void DrawPhysicsDebug(RenderView& view);
#endif
///
@@ -430,8 +423,8 @@ private:
bool UpdateCollision();
void OnPhysicsSceneChanged(PhysicsScene* previous);
-public:
+public:
// [ISerializable]
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp
index 53448ec27..a8cb0ecd8 100644
--- a/Source/Engine/Tools/ModelTool/ModelTool.cpp
+++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp
@@ -1468,7 +1468,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
Transform srcNode = data.Skeleton.Nodes[nodeIndex].LocalTransform;
auto& node = data.Skeleton.Nodes[nodeIndex];
if (auto* channel = animation.GetChannel(node.Name))
- channel->Evaluate(frame, &srcNode, false);
+ channel->Evaluate((float)frame, &srcNode, false);
pose.Nodes[nodeIndex] = srcNode;
}
@@ -1476,7 +1476,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
key = Float3::Zero;
for (int32 nodeIndex = 0; nodeIndex < nodes; nodeIndex++)
key += pose.GetNodeModelTransformation(data.Skeleton, nodeIndex).Translation;
- key /= nodes;
+ key /= (float)nodes;
}
// Calculate skeleton center of mass movement over the animation frames
@@ -1485,7 +1485,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
for (int32 frame = 0; frame < frames; frame++)
{
auto& key = rootChannel.Position[frame];
- key.Time = frame;
+ key.Time = (float)frame;
key.Value = centerOfMass[frame] - centerOfMassRefPose;
}
@@ -1531,7 +1531,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
Transform srcNode = data.Skeleton.Nodes[nodeIndex].LocalTransform;
auto& node = data.Skeleton.Nodes[nodeIndex];
if (auto* channel = animation.GetChannel(node.Name))
- channel->Evaluate(frame, &srcNode, false);
+ channel->Evaluate((float)frame, &srcNode, false);
pose.Nodes[nodeIndex] = srcNode;
}
diff --git a/Source/Engine/UI/GUI/Common/Slider.cs b/Source/Engine/UI/GUI/Common/Slider.cs
index 6dbd5082c..efffa6728 100644
--- a/Source/Engine/UI/GUI/Common/Slider.cs
+++ b/Source/Engine/UI/GUI/Common/Slider.cs
@@ -1,4 +1,6 @@
-using System;
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+using System;
namespace FlaxEngine.GUI;
@@ -7,6 +9,32 @@ namespace FlaxEngine.GUI;
///
public class Slider : ContainerControl
{
+ ///
+ /// The slider direction
+ ///
+ public enum SliderDirection
+ {
+ ///
+ /// Slider direction, horizontal right
+ ///
+ HorizontalRight,
+
+ ///
+ /// Slider direction, horizontal left
+ ///
+ HorizontalLeft,
+
+ ///
+ /// Slider direction, vertical up
+ ///
+ VerticalUp,
+
+ ///
+ /// Slider direction, vertical down
+ ///
+ VerticalDown,
+ }
+
///
/// The minimum value.
///
@@ -15,26 +43,7 @@ public class Slider : ContainerControl
///
/// The maximum value.
///
- protected float _maximum = 100f;
-
- ///
- /// Gets or sets the minimum value.
- ///
- [EditorOrder(20), Tooltip("The minimum value.")]
- public float Minimum
- {
- get => _minimum;
- set
- {
- if (value > _maximum)
- throw new ArgumentOutOfRangeException();
- if (WholeNumbers)
- value = Mathf.RoundToInt(value);
- _minimum = value;
- if (Value < _minimum)
- Value = _minimum;
- }
- }
+ protected float _maximum = 100;
///
/// Gets or sets the maximum value.
@@ -45,8 +54,6 @@ public class Slider : ContainerControl
get => _maximum;
set
{
- if (value < _minimum || Mathf.IsZero(value))
- throw new ArgumentOutOfRangeException();
if (WholeNumbers)
value = Mathf.RoundToInt(value);
_maximum = value;
@@ -55,6 +62,38 @@ public class Slider : ContainerControl
}
}
+ ///
+ /// Gets or sets the minimum value.
+ ///
+ [EditorOrder(20), Tooltip("The minimum value.")]
+ public float Minimum
+ {
+ get => _minimum;
+ set
+ {
+ if (WholeNumbers)
+ value = Mathf.RoundToInt(value);
+ _minimum = value;
+ if (Value < _minimum)
+ Value = _minimum;
+ }
+ }
+
+ ///
+ /// Gets or sets the slider direction.
+ ///
+ [EditorOrder(40), Tooltip("Slider Direction.")]
+ public SliderDirection Direction
+ {
+ get => _direction;
+ set
+ {
+ _direction = value;
+ UpdateThumb();
+ }
+ }
+
+ private SliderDirection _direction = SliderDirection.HorizontalRight;
private float _value = 100f;
private Rectangle _thumbRect;
private float _thumbCenter;
@@ -89,31 +128,60 @@ public class Slider : ContainerControl
/// The local position of the thumb center
///
[HideInEditor]
- public Float2 ThumbCenter => new(_thumbCenter, Height / 2);
+ public Float2 ThumbCenter => (Direction is SliderDirection.HorizontalLeft or SliderDirection.HorizontalRight) ? new Float2(_thumbCenter, Height / 2) : new Float2(Width / 2, _thumbCenter);
///
/// The local position of the beginning of the track.
///
[HideInEditor]
- public Float2 TrackBeginning => new(_thumbSize.X / 2, Height / 2);
+ public Float2 TrackBeginning
+ {
+ get
+ {
+ switch (Direction)
+ {
+ case SliderDirection.HorizontalRight: return new Float2(_thumbSize.X / 2, Height / 2);
+ case SliderDirection.HorizontalLeft: return new Float2(Width - _thumbSize.X / 2, Height / 2);
+ case SliderDirection.VerticalUp: return new Float2(Width / 2, Height - _thumbSize.Y / 2);
+ case SliderDirection.VerticalDown: return new Float2(Width / 2, _thumbSize.Y / 2);
+ default: break;
+ }
+ return Float2.Zero;
+ }
+ }
///
/// The local position of the end of the track.
///
[HideInEditor]
- public Float2 TrackEnd => new(Width - _thumbSize.X / 2, Height / 2);
-
+ public Float2 TrackEnd
+ {
+ get
+ {
+ switch (Direction)
+ {
+ case SliderDirection.HorizontalRight: return new Float2(Width - _thumbSize.X / 2, Height / 2);
+ case SliderDirection.HorizontalLeft: return new Float2(_thumbSize.X / 2, Height / 2);
+ case SliderDirection.VerticalUp: return new Float2(Width / 2, _thumbSize.Y / 2);
+ case SliderDirection.VerticalDown: return new Float2(Width / 2, Height - _thumbSize.Y / 2);
+ default: break;
+ }
+ return Float2.Zero;
+ }
+ }
+
///
/// The height of the track.
///
[EditorOrder(40), Tooltip("The track height.")]
- public int TrackHeight { get; set; } = 2;
+ public int TrackThickness { get; set; } = 2;
///
/// The thumb size.
///
[EditorOrder(41), Tooltip("The size of the thumb.")]
- public Float2 ThumbSize {
+ public Float2 ThumbSize
+ {
get => _thumbSize;
set
{
@@ -127,7 +195,7 @@ public class Slider : ContainerControl
///
[EditorOrder(42), Tooltip("Fill the track.")]
public bool FillTrack = true;
-
+
///
/// Whether to use whole numbers.
///
@@ -147,9 +215,14 @@ public class Slider : ContainerControl
public Color TrackFillLineColor { get; set; }
///
- /// Gets the size of the track.
+ /// Gets the width of the track.
///
- private float TrackWidth => Width;
+ private float TrackWidth => (Direction is SliderDirection.HorizontalLeft or SliderDirection.HorizontalRight) ? Width - _thumbSize.X : TrackThickness;
+
+ ///
+ /// Gets the height of the track.
+ ///
+ private float TrackHeight => (Direction is SliderDirection.HorizontalLeft or SliderDirection.HorizontalRight) ? TrackThickness : Height - _thumbSize.Y;
///
/// Gets or sets the brush used for slider track drawing.
@@ -168,7 +241,7 @@ public class Slider : ContainerControl
///
[EditorDisplay("Thumb Style"), EditorOrder(2030), Tooltip("The color of the slider thumb when it's not selected."), ExpandGroups]
public Color ThumbColor { get; set; }
-
+
///
/// The color of the slider thumb when it's highlighted.
///
@@ -202,12 +275,12 @@ public class Slider : ContainerControl
/// Occurs when sliding ends.
///
public event Action SlidingEnd;
-
+
///
/// Occurs when value gets changed.
///
public event Action ValueChanged;
-
+
///
/// Initializes a new instance of the class.
///
@@ -236,13 +309,32 @@ public class Slider : ContainerControl
private void UpdateThumb()
{
// Cache data
- float trackSize = TrackWidth;
+ var isHorizontal = Direction is SliderDirection.HorizontalRight or SliderDirection.HorizontalLeft;
+ float trackSize = isHorizontal ? Width : Height;
float range = Maximum - Minimum;
- float pixelRange = trackSize - _thumbSize.X;
+ float pixelRange = trackSize - (isHorizontal ? _thumbSize.X : _thumbSize.Y);
float perc = (_value - Minimum) / range;
float thumbPosition = (int)(perc * pixelRange);
- _thumbCenter = thumbPosition + _thumbSize.X / 2;
- _thumbRect = new Rectangle(thumbPosition, (Height - _thumbSize.Y) / 2, _thumbSize.X, _thumbSize.Y);
+ switch (Direction)
+ {
+ case SliderDirection.HorizontalRight:
+ _thumbCenter = thumbPosition + _thumbSize.X / 2;
+ _thumbRect = new Rectangle(thumbPosition, (Height - _thumbSize.Y) / 2, _thumbSize.X, _thumbSize.Y);
+ break;
+ case SliderDirection.VerticalDown:
+ _thumbCenter = thumbPosition + _thumbSize.Y / 2;
+ _thumbRect = new Rectangle((Width - _thumbSize.X) / 2, thumbPosition, _thumbSize.X, _thumbSize.Y);
+ break;
+ case SliderDirection.HorizontalLeft:
+ _thumbCenter = Width - thumbPosition - _thumbSize.X / 2;
+ _thumbRect = new Rectangle(Width - thumbPosition - _thumbSize.X, (Height - _thumbSize.Y) / 2, _thumbSize.X, _thumbSize.Y);
+ break;
+ case SliderDirection.VerticalUp:
+ _thumbCenter = Height - thumbPosition - _thumbSize.Y / 2;
+ _thumbRect = new Rectangle((Width - _thumbSize.X) / 2, Height - thumbPosition - _thumbSize.Y, _thumbSize.X, _thumbSize.Y);
+ break;
+ default: break;
+ }
}
private void EndSliding()
@@ -256,19 +348,36 @@ public class Slider : ContainerControl
public override void Draw()
{
base.Draw();
-
+
+ // Set rectangles
+ var lineRect = new Rectangle(_thumbSize.X / 2, (Height - TrackThickness) / 2, Width - _thumbSize.X, TrackThickness);
+ var fillLineRect = new Rectangle(_thumbSize.X / 2 - 1, (Height - TrackThickness - 2) / 2, Width - (Width - _thumbCenter) - _thumbSize.X / 2 + 1, TrackThickness + 2);
+ switch (Direction)
+ {
+ case SliderDirection.HorizontalRight: break;
+ case SliderDirection.VerticalDown:
+ lineRect = new Rectangle((Width - TrackThickness) / 2, _thumbSize.Y / 2, TrackThickness, Height - _thumbSize.Y);
+ fillLineRect = new Rectangle((Width - TrackThickness - 2) / 2, _thumbSize.Y / 2 - 1, TrackThickness + 2, Height - (Height - _thumbCenter) - _thumbSize.Y / 2 + 1);
+ break;
+ case SliderDirection.HorizontalLeft:
+ fillLineRect = new Rectangle(Width - (Width - _thumbCenter) - 1, (Height - TrackThickness - 2) / 2, Width - _thumbCenter + 1, TrackThickness + 2);
+ break;
+ case SliderDirection.VerticalUp:
+ lineRect = new Rectangle((Width - TrackThickness) / 2, _thumbSize.Y / 2, TrackThickness, Height - _thumbSize.Y);
+ fillLineRect = new Rectangle((Width - TrackThickness - 2) / 2, Height - (Height - _thumbCenter) - 1, TrackThickness + 2, Height - _thumbCenter + 1);
+ break;
+ default: break;
+ }
+
// Draw track line
- //var lineRect = new Rectangle(4, (Height - TrackHeight) / 2, Width - 8, TrackHeight);
- var lineRect = new Rectangle(_thumbSize.X / 2, (Height - TrackHeight) / 2, Width - _thumbSize.X, TrackHeight);
if (TrackBrush != null)
TrackBrush.Draw(lineRect, TrackLineColor);
else
Render2D.FillRectangle(lineRect, TrackLineColor);
-
+
// Draw track fill
if (FillTrack)
{
- var fillLineRect = new Rectangle(_thumbSize.X / 2 - 1, (Height - TrackHeight - 2) / 2, Width - (Width - _thumbCenter) - _thumbSize.X / 2, TrackHeight + 2);
Render2D.PushClip(ref fillLineRect);
if (FillTrackBrush != null)
FillTrackBrush.Draw(lineRect, TrackFillLineColor);
@@ -276,13 +385,13 @@ public class Slider : ContainerControl
Render2D.FillRectangle(lineRect, TrackFillLineColor);
Render2D.PopClip();
}
-
+
// Draw thumb
- var thumbColor = _isSliding ? ThumbColorSelected : (_mouseOverThumb ? ThumbColorHighlighted : ThumbColor);
+ var thumbColorV = _isSliding ? ThumbColorSelected : (_mouseOverThumb ? ThumbColorHighlighted : ThumbColor);
if (ThumbBrush != null)
- ThumbBrush.Draw(_thumbRect, thumbColor);
+ ThumbBrush.Draw(_thumbRect, thumbColorV);
else
- Render2D.FillRectangle(_thumbRect, thumbColor);
+ Render2D.FillRectangle(_thumbRect, thumbColorV);
}
///
@@ -302,7 +411,7 @@ public class Slider : ContainerControl
if (button == MouseButton.Left)
{
Focus();
- float mousePosition = location.X;
+ float mousePosition = Direction is SliderDirection.HorizontalRight or SliderDirection.HorizontalLeft ? location.X : location.Y;
if (_thumbRect.Contains(ref location))
{
@@ -315,7 +424,16 @@ public class Slider : ContainerControl
else
{
// Click change
- Value += (mousePosition < _thumbCenter ? -1 : 1) * 10;
+ switch (Direction)
+ {
+ case SliderDirection.HorizontalRight or SliderDirection.VerticalDown:
+ Value += (mousePosition < _thumbCenter ? -1 : 1) * 10;
+ break;
+ case SliderDirection.HorizontalLeft or SliderDirection.VerticalUp:
+ Value -= (mousePosition < _thumbCenter ? -1 : 1) * 10;
+ break;
+ default: break;
+ }
}
}
@@ -330,7 +448,22 @@ public class Slider : ContainerControl
{
// Update sliding
var slidePosition = location + Root.TrackingMouseOffset;
- Value = Mathf.Remap(slidePosition.X, 4, TrackWidth - 4, Minimum, Maximum);
+ switch (Direction)
+ {
+ case SliderDirection.HorizontalRight:
+ Value = Mathf.Remap(slidePosition.X, 4, Width - 4, Minimum, Maximum);
+ break;
+ case SliderDirection.VerticalDown:
+ Value = Mathf.Remap(slidePosition.Y, 4, Height - 4, Minimum, Maximum);
+ break;
+ case SliderDirection.HorizontalLeft:
+ Value = Mathf.Remap(slidePosition.X, Width - 4, 4, Minimum, Maximum);
+ break;
+ case SliderDirection.VerticalUp:
+ Value = Mathf.Remap(slidePosition.Y, Height - 4, 4, Minimum, Maximum);
+ break;
+ default: break;
+ }
}
else
{
diff --git a/Source/Engine/Visject/VisjectGraph.h b/Source/Engine/Visject/VisjectGraph.h
index 085c05de0..5054bd54d 100644
--- a/Source/Engine/Visject/VisjectGraph.h
+++ b/Source/Engine/Visject/VisjectGraph.h
@@ -101,7 +101,7 @@ public:
/// Visject graph parameter.
///
///
-API_CLASS() class VisjectGraphParameter : public GraphParameter
+API_CLASS() class FLAXENGINE_API VisjectGraphParameter : public GraphParameter
{
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(VisjectGraphParameter, GraphParameter);
public:
diff --git a/Source/Engine/Visject/VisjectMeta.cpp b/Source/Engine/Visject/VisjectMeta.cpp
index 37631df55..624345813 100644
--- a/Source/Engine/Visject/VisjectMeta.cpp
+++ b/Source/Engine/Visject/VisjectMeta.cpp
@@ -5,10 +5,6 @@
#include "Engine/Serialization/ReadStream.h"
#include "Engine/Serialization/WriteStream.h"
-VisjectMeta::VisjectMeta()
-{
-}
-
bool VisjectMeta::Load(ReadStream* stream, bool loadData)
{
Release();
diff --git a/Source/Engine/Visject/VisjectMeta.h b/Source/Engine/Visject/VisjectMeta.h
index c417b7030..b35add815 100644
--- a/Source/Engine/Visject/VisjectMeta.h
+++ b/Source/Engine/Visject/VisjectMeta.h
@@ -8,7 +8,7 @@
///
/// Visject metadata container
///
-class VisjectMeta
+class FLAXENGINE_API VisjectMeta
{
public:
///
@@ -27,19 +27,6 @@ public:
///
Array> Entries;
-public:
- ///
- /// Initializes a new instance of the class.
- ///
- VisjectMeta();
-
- ///
- /// Finalizes an instance of the class.
- ///
- ~VisjectMeta()
- {
- }
-
public:
///
/// Load from the stream
diff --git a/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs b/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs
index b0167c024..72653900c 100644
--- a/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs
+++ b/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs
@@ -22,7 +22,7 @@ namespace Flax.Build.Bindings
public string[] Comment;
public bool IsInBuild;
public bool IsDeprecated;
- public string MarshalAs;
+ public TypeInfo MarshalAs;
internal bool IsInited;
internal TypedefInfo Instigator;
diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs
index 4a0070710..1849513f9 100644
--- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs
+++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs
@@ -197,7 +197,7 @@ namespace Flax.Build.Bindings
if (apiType != null)
{
if (apiType.MarshalAs != null)
- return UsePassByReference(buildData, new TypeInfo(apiType.MarshalAs), caller);
+ return UsePassByReference(buildData, apiType.MarshalAs, caller);
// Skip for scripting objects
if (apiType.IsScriptingObject)
diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs
index 845e4e62b..e1d4df083 100644
--- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs
+++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs
@@ -270,7 +270,7 @@ namespace Flax.Build.Bindings
return value;
}
- private static string GenerateCSharpNativeToManaged(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller)
+ private static string GenerateCSharpNativeToManaged(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, bool marshalling = false)
{
string result;
if (typeInfo?.Type == null)
@@ -280,7 +280,7 @@ namespace Flax.Build.Bindings
if (typeInfo.IsArray)
{
typeInfo.IsArray = false;
- result = GenerateCSharpNativeToManaged(buildData, typeInfo, caller);
+ result = GenerateCSharpNativeToManaged(buildData, typeInfo, caller, marshalling);
typeInfo.IsArray = true;
return result + "[]";
}
@@ -307,7 +307,7 @@ namespace Flax.Build.Bindings
// Object reference property
if (typeInfo.IsObjectRef)
- return GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller);
+ return GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling);
if (typeInfo.Type == "SoftTypeReference" || typeInfo.Type == "SoftObjectReference")
return typeInfo.Type;
@@ -317,15 +317,25 @@ namespace Flax.Build.Bindings
#else
if ((typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "DataContainer") && typeInfo.GenericArgs != null)
#endif
- return GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller) + "[]";
+ {
+ var arrayTypeInfo = typeInfo.GenericArgs[0];
+ if (marshalling)
+ {
+ // Convert array that uses different type for marshalling
+ var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller);
+ if (arrayApiType != null && arrayApiType.MarshalAs != null)
+ arrayTypeInfo = arrayApiType.MarshalAs;
+ }
+ return GenerateCSharpNativeToManaged(buildData, arrayTypeInfo, caller) + "[]";
+ }
// Dictionary
if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null)
- return string.Format("System.Collections.Generic.Dictionary<{0}, {1}>", GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller), GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[1], caller));
+ return string.Format("System.Collections.Generic.Dictionary<{0}, {1}>", GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling), GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[1], caller, marshalling));
// HashSet
if (typeInfo.Type == "HashSet" && typeInfo.GenericArgs != null)
- return string.Format("System.Collections.Generic.HashSet<{0}>", GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller));
+ return string.Format("System.Collections.Generic.HashSet<{0}>", GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling));
// BitArray
if (typeInfo.Type == "BitArray" && typeInfo.GenericArgs != null)
@@ -348,16 +358,16 @@ namespace Flax.Build.Bindings
// TODO: generate delegates globally in the module namespace to share more code (smaller binary size)
var key = string.Empty;
for (int i = 0; i < typeInfo.GenericArgs.Count; i++)
- key += GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[i], caller);
+ key += GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[i], caller, marshalling);
if (!CSharpAdditionalCodeCache.TryGetValue(key, out var delegateName))
{
delegateName = "Delegate" + CSharpAdditionalCodeCache.Count;
- var signature = $"public delegate {GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller)} {delegateName}(";
+ var signature = $"public delegate {GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling)} {delegateName}(";
for (int i = 1; i < typeInfo.GenericArgs.Count; i++)
{
if (i != 1)
signature += ", ";
- signature += GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[i], caller);
+ signature += GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[i], caller, marshalling);
signature += $" arg{(i - 1)}";
}
signature += ");";
@@ -390,11 +400,14 @@ namespace Flax.Build.Bindings
{
typeName += '<';
foreach (var arg in typeInfo.GenericArgs)
- typeName += GenerateCSharpNativeToManaged(buildData, arg, caller);
+ typeName += GenerateCSharpNativeToManaged(buildData, arg, caller, marshalling);
typeName += '>';
}
if (apiType != null)
{
+ if (marshalling && apiType.MarshalAs != null)
+ return GenerateCSharpNativeToManaged(buildData, apiType.MarshalAs, caller);
+
// Add reference to the namespace
CSharpUsedNamespaces.Add(apiType.Namespace);
var apiTypeParent = apiType.Parent;
@@ -419,11 +432,11 @@ namespace Flax.Build.Bindings
return typeName;
}
- private static string GenerateCSharpManagedToNativeType(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller)
+ private static string GenerateCSharpManagedToNativeType(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, bool marshalling = false)
{
// Fixed-size array
if (typeInfo.IsArray)
- return GenerateCSharpNativeToManaged(buildData, typeInfo, caller);
+ return GenerateCSharpNativeToManaged(buildData, typeInfo, caller, marshalling);
// Find API type info
var apiType = FindApiTypeInfo(buildData, typeInfo, caller);
@@ -439,7 +452,7 @@ namespace Flax.Build.Bindings
}
if (apiType.MarshalAs != null)
- return GenerateCSharpManagedToNativeType(buildData, new TypeInfo(apiType.MarshalAs), caller);
+ return GenerateCSharpManagedToNativeType(buildData, apiType.MarshalAs, caller, marshalling);
if (apiType.IsScriptingObject || apiType.IsInterface)
return "IntPtr";
}
@@ -452,7 +465,7 @@ namespace Flax.Build.Bindings
if (typeInfo.Type == "Function" && typeInfo.GenericArgs != null)
return "IntPtr";
- return GenerateCSharpNativeToManaged(buildData, typeInfo, caller);
+ return GenerateCSharpNativeToManaged(buildData, typeInfo, caller, marshalling);
}
private static string GenerateCSharpManagedToNativeConverter(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller)
@@ -485,6 +498,18 @@ namespace Flax.Build.Bindings
case "Function":
// delegate
return "NativeInterop.GetFunctionPointerForDelegate({0})";
+ case "Array":
+ case "Span":
+ case "DataContainer":
+ if (typeInfo.GenericArgs != null)
+ {
+ // Convert array that uses different type for marshalling
+ var arrayTypeInfo = typeInfo.GenericArgs[0];
+ var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller);
+ if (arrayApiType != null && arrayApiType.MarshalAs != null)
+ return $"{{0}}.ConvertArray(x => ({GenerateCSharpNativeToManaged(buildData, arrayApiType.MarshalAs, caller)})x)";
+ }
+ return string.Empty;
default:
var apiType = FindApiTypeInfo(buildData, typeInfo, caller);
if (apiType != null)
@@ -531,9 +556,9 @@ namespace Flax.Build.Bindings
{
var apiType = FindApiTypeInfo(buildData, functionInfo.ReturnType, caller);
if (apiType != null && apiType.MarshalAs != null)
- returnValueType = GenerateCSharpNativeToManaged(buildData, new TypeInfo(apiType.MarshalAs), caller);
+ returnValueType = GenerateCSharpNativeToManaged(buildData, apiType.MarshalAs, caller, true);
else
- returnValueType = GenerateCSharpNativeToManaged(buildData, functionInfo.ReturnType, caller);
+ returnValueType = GenerateCSharpNativeToManaged(buildData, functionInfo.ReturnType, caller, true);
}
#if USE_NETCORE
@@ -594,7 +619,7 @@ namespace Flax.Build.Bindings
contents.Append(", ");
separator = true;
- var nativeType = GenerateCSharpManagedToNativeType(buildData, parameterInfo.Type, caller);
+ var nativeType = GenerateCSharpManagedToNativeType(buildData, parameterInfo.Type, caller, true);
#if USE_NETCORE
string parameterMarshalType = "";
if (nativeType == "System.Type")
@@ -643,7 +668,7 @@ namespace Flax.Build.Bindings
contents.Append(", ");
separator = true;
- var nativeType = GenerateCSharpManagedToNativeType(buildData, parameterInfo.Type, caller);
+ var nativeType = GenerateCSharpManagedToNativeType(buildData, parameterInfo.Type, caller, true);
#if USE_NETCORE
string parameterMarshalType = "";
if (parameterInfo.IsOut && parameterInfo.DefaultValue == "var __resultAsRef")
@@ -756,7 +781,16 @@ namespace Flax.Build.Bindings
}
}
- contents.Append(");");
+ contents.Append(')');
+ if ((functionInfo.ReturnType.Type == "Array" || functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "DataContainer") && functionInfo.ReturnType.GenericArgs != null)
+ {
+ // Convert array that uses different type for marshalling
+ var arrayTypeInfo = functionInfo.ReturnType.GenericArgs[0];
+ var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller);
+ if (arrayApiType != null && arrayApiType.MarshalAs != null)
+ contents.Append($".ConvertArray(x => ({GenerateCSharpNativeToManaged(buildData, arrayTypeInfo, caller)})x)");
+ }
+ contents.Append(';');
// Return result
if (functionInfo.Glue.UseReferenceForResult)
diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs
index 079a5839f..d78af861f 100644
--- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs
+++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs
@@ -19,7 +19,7 @@ namespace Flax.Build.Bindings
partial class BindingsGenerator
{
private static readonly Dictionary TypeCache = new Dictionary();
- private const int CacheVersion = 21;
+ private const int CacheVersion = 22;
internal static void Write(BinaryWriter writer, string e)
{
diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
index 4e632014a..35d1d32fa 100644
--- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
+++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
@@ -167,11 +167,7 @@ namespace Flax.Build.Bindings
return $"Variant(StringView({value}))";
if (typeInfo.Type == "StringAnsi")
return $"Variant(StringAnsiView({value}))";
- if (typeInfo.Type == "AssetReference" ||
- typeInfo.Type == "WeakAssetReference" ||
- typeInfo.Type == "SoftAssetReference" ||
- typeInfo.Type == "ScriptingObjectReference" ||
- typeInfo.Type == "SoftObjectReference")
+ if (typeInfo.IsObjectRef)
return $"Variant({value}.Get())";
if (typeInfo.IsArray)
{
@@ -227,10 +223,10 @@ namespace Flax.Build.Bindings
return $"(StringAnsiView){value}";
if (typeInfo.IsPtr && typeInfo.IsConst && typeInfo.Type == "Char")
return $"((StringView){value}).GetText()"; // (StringView)Variant, if not empty, is guaranteed to point to a null-terminated buffer.
- if (typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftAssetReference")
- return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((Asset*){value})";
if (typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "SoftObjectReference")
return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((ScriptingObject*){value})";
+ if (typeInfo.IsObjectRef)
+ return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((Asset*){value})";
if (typeInfo.IsArray)
throw new Exception($"Not supported type to convert from the Variant to fixed-size array '{typeInfo}[{typeInfo.ArraySize}]'.");
if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null)
@@ -309,7 +305,7 @@ namespace Flax.Build.Bindings
private static string GenerateCppGetMClass(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, FunctionInfo functionInfo)
{
// Optimal path for in-build types
- var managedType = GenerateCSharpNativeToManaged(buildData, typeInfo, caller);
+ var managedType = GenerateCSharpNativeToManaged(buildData, typeInfo, caller, true);
switch (managedType)
{
// In-built types (cached by the engine on startup)
@@ -392,7 +388,7 @@ namespace Flax.Build.Bindings
CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MClass.h");
// Optimal path for in-build types
- var managedType = GenerateCSharpNativeToManaged(buildData, typeInfo, caller);
+ var managedType = GenerateCSharpNativeToManaged(buildData, typeInfo, caller, true);
switch (managedType)
{
case "bool":
@@ -514,11 +510,7 @@ namespace Flax.Build.Bindings
return "MUtils::ToManaged({0})";
default:
// Object reference property
- if ((typeInfo.Type == "ScriptingObjectReference" ||
- typeInfo.Type == "AssetReference" ||
- typeInfo.Type == "WeakAssetReference" ||
- typeInfo.Type == "SoftAssetReference" ||
- typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null)
+ if (typeInfo.IsObjectRef)
{
type = "MObject*";
return "{0}.GetManagedInstance()";
@@ -527,16 +519,28 @@ namespace Flax.Build.Bindings
// Array or DataContainer
if ((typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "DataContainer") && typeInfo.GenericArgs != null)
{
+ var arrayTypeInfo = typeInfo.GenericArgs[0];
#if USE_NETCORE
// Boolean arrays does not support custom marshalling for some unknown reason
- if (typeInfo.GenericArgs[0].Type == "bool")
+ if (arrayTypeInfo.Type == "bool")
{
type = "bool*";
return "MUtils::ToBoolArray({0})";
}
+ var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller);
#endif
type = "MArray*";
- return "MUtils::ToArray({0}, " + GenerateCppGetMClass(buildData, typeInfo.GenericArgs[0], caller, functionInfo) + ")";
+ if (arrayApiType != null && arrayApiType.MarshalAs != null)
+ {
+ // Convert array that uses different type for marshalling
+ if (arrayApiType != null && arrayApiType.MarshalAs != null)
+ arrayTypeInfo = arrayApiType.MarshalAs; // Convert array that uses different type for marshalling
+ var genericArgs = arrayApiType.MarshalAs.GetFullNameNative(buildData, caller);
+ if (typeInfo.GenericArgs.Count != 1)
+ genericArgs += ", " + typeInfo.GenericArgs[1];
+ return "MUtils::ToArray(Array<" + genericArgs + ">({0}), " + GenerateCppGetMClass(buildData, arrayTypeInfo, caller, functionInfo) + ")";
+ }
+ return "MUtils::ToArray({0}, " + GenerateCppGetMClass(buildData, arrayTypeInfo, caller, functionInfo) + ")";
}
// Span
@@ -597,7 +601,7 @@ namespace Flax.Build.Bindings
CppReferencesFiles.Add(apiType.File);
if (apiType.MarshalAs != null)
- return GenerateCppWrapperNativeToManaged(buildData, new TypeInfo(apiType.MarshalAs), caller, out type, functionInfo);
+ return GenerateCppWrapperNativeToManaged(buildData, apiType.MarshalAs, caller, out type, functionInfo);
// Scripting Object
if (apiType.IsScriptingObject)
@@ -704,11 +708,7 @@ namespace Flax.Build.Bindings
return "MUtils::ToNative({0})";
default:
// Object reference property
- if ((typeInfo.Type == "ScriptingObjectReference" ||
- typeInfo.Type == "AssetReference" ||
- typeInfo.Type == "WeakAssetReference" ||
- typeInfo.Type == "SoftAssetReference" ||
- typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null)
+ if (typeInfo.IsObjectRef)
{
// For non-pod types converting only, other API converts managed to unmanaged object in C# wrapper code)
if (CppNonPodTypesConvertingGeneration)
@@ -731,11 +731,26 @@ namespace Flax.Build.Bindings
// Array
if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null)
{
- var T = typeInfo.GenericArgs[0].GetFullNameNative(buildData, caller);
- type = "MArray*";
+ var arrayTypeInfo = typeInfo.GenericArgs[0];
+ var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller);
+ if (arrayApiType != null && arrayApiType.MarshalAs != null)
+ arrayTypeInfo = arrayApiType.MarshalAs;
+ var genericArgs = arrayTypeInfo.GetFullNameNative(buildData, caller);
if (typeInfo.GenericArgs.Count != 1)
- return "MUtils::ToArray<" + T + ", " + typeInfo.GenericArgs[1] + ">({0})";
- return "MUtils::ToArray<" + T + ">({0})";
+ genericArgs += ", " + typeInfo.GenericArgs[1];
+
+ type = "MArray*";
+ var result = "MUtils::ToArray<" + genericArgs + ">({0})";
+
+ if (arrayApiType != null && arrayApiType.MarshalAs != null)
+ {
+ // Convert array that uses different type for marshalling
+ genericArgs = typeInfo.GenericArgs[0].GetFullNameNative(buildData, caller);
+ if (typeInfo.GenericArgs.Count != 1)
+ genericArgs += ", " + typeInfo.GenericArgs[1];
+ result = $"Array<{genericArgs}>({result})";
+ }
+ return result;
}
// Span or DataContainer
@@ -801,7 +816,7 @@ namespace Flax.Build.Bindings
if (apiType != null)
{
if (apiType.MarshalAs != null)
- return GenerateCppWrapperManagedToNative(buildData, new TypeInfo(apiType.MarshalAs), caller, out type, out apiType, functionInfo, out needLocalVariable);
+ return GenerateCppWrapperManagedToNative(buildData, apiType.MarshalAs, caller, out type, out apiType, functionInfo, out needLocalVariable);
// Scripting Object (for non-pod types converting only, other API converts managed to unmanaged object in C# wrapper code)
if (CppNonPodTypesConvertingGeneration && apiType.IsScriptingObject && typeInfo.IsPtr)
diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs
index 0b71af086..3db43edf0 100644
--- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs
+++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs
@@ -185,6 +185,11 @@ namespace Flax.Build.Bindings
tag.Value = tag.Value.Substring(1, tag.Value.Length - 2);
if (tag.Value.Contains("\\\""))
tag.Value = tag.Value.Replace("\\\"", "\"");
+ token = context.Tokenizer.NextToken();
+ if (token.Type == TokenType.Multiply)
+ tag.Value += token.Value;
+ else
+ context.Tokenizer.PreviousToken();
parameters.Add(tag);
break;
case TokenType.Whitespace:
@@ -647,7 +652,7 @@ namespace Flax.Build.Bindings
desc.Namespace = tag.Value;
break;
case "marshalas":
- desc.MarshalAs = tag.Value;
+ desc.MarshalAs = TypeInfo.FromString(tag.Value);
break;
case "tag":
ParseTag(ref desc.Tags, tag);
@@ -1236,7 +1241,7 @@ namespace Flax.Build.Bindings
desc.Namespace = tag.Value;
break;
case "marshalas":
- desc.MarshalAs = tag.Value;
+ desc.MarshalAs = TypeInfo.FromString(tag.Value);
break;
case "tag":
ParseTag(ref desc.Tags, tag);
diff --git a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs
index cdf293085..61946bae2 100644
--- a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs
+++ b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs
@@ -180,6 +180,17 @@ namespace Flax.Build.Bindings
return sb.ToString();
}
+ public static TypeInfo FromString(string text)
+ {
+ var result = new TypeInfo(text);
+ if (result.Type.EndsWith('*'))
+ {
+ result.IsPtr = true;
+ result.Type = result.Type.Substring(0, result.Type.Length - 1);
+ }
+ return result;
+ }
+
public string ToString(bool canRef = true)
{
var sb = new StringBuilder(64);