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);