Merge branch 'FlaxEngine:master' into model-prefab-fix

This commit is contained in:
Menotdan
2024-02-18 12:29:45 -05:00
committed by GitHub
102 changed files with 1675 additions and 1097 deletions

View File

@@ -81,7 +81,7 @@ namespace FlaxEditor.Content.Create
switch (_options.Template) switch (_options.Template)
{ {
case Templates.Empty: case Templates.Empty:
return Editor.CreateAsset(Editor.NewAssetType.ParticleEmitter, ResultUrl); return Editor.CreateAsset("ParticleEmitter", ResultUrl);
case Templates.ConstantBurst: case Templates.ConstantBurst:
templateName = "Constant Burst"; templateName = "Constant Burst";
break; break;

View File

@@ -38,7 +38,7 @@ namespace FlaxEditor.Content
/// <inheritdoc /> /// <inheritdoc />
public override void Create(string outputPath, object arg) 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."); throw new Exception("Failed to create new asset.");
} }
} }

View File

@@ -38,7 +38,7 @@ namespace FlaxEditor.Content
/// <inheritdoc /> /// <inheritdoc />
public override void Create(string outputPath, object arg) 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."); throw new Exception("Failed to create new asset.");
} }
} }

View File

@@ -47,7 +47,7 @@ namespace FlaxEditor.Content
/// <inheritdoc /> /// <inheritdoc />
public override void Create(string outputPath, object arg) 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."); throw new Exception("Failed to create new asset.");
} }

View File

@@ -47,7 +47,7 @@ namespace FlaxEditor.Content
/// <inheritdoc /> /// <inheritdoc />
public override void Create(string outputPath, object arg) 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."); throw new Exception("Failed to create new asset.");
} }

View File

@@ -71,7 +71,7 @@ namespace FlaxEditor.Content
/// <inheritdoc /> /// <inheritdoc />
public override void Create(string outputPath, object arg) 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."); throw new Exception("Failed to create new asset.");
} }

View File

@@ -38,7 +38,7 @@ namespace FlaxEditor.Content
/// <inheritdoc /> /// <inheritdoc />
public override void Create(string outputPath, object arg) 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."); throw new Exception("Failed to create new asset.");
} }
} }

View File

@@ -43,7 +43,7 @@ namespace FlaxEditor.Content
/// <inheritdoc /> /// <inheritdoc />
public override void Create(string outputPath, object arg) 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."); throw new Exception("Failed to create new asset.");
} }

View File

@@ -44,7 +44,7 @@ namespace FlaxEditor.Content
/// <inheritdoc /> /// <inheritdoc />
public override void Create(string outputPath, object arg) 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."); throw new Exception("Failed to create new asset.");
} }

View File

@@ -38,7 +38,7 @@ namespace FlaxEditor.Content
/// <inheritdoc /> /// <inheritdoc />
public override void Create(string outputPath, object arg) 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."); throw new Exception("Failed to create new asset.");
} }
} }

View File

@@ -75,7 +75,7 @@ namespace FlaxEditor.Content
/// <inheritdoc /> /// <inheritdoc />
public override void Create(string outputPath, object arg) 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."); throw new Exception("Failed to create new asset.");
} }

View File

@@ -69,7 +69,7 @@ namespace FlaxEditor.Content
/// <inheritdoc /> /// <inheritdoc />
public override void Create(string outputPath, object arg) 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."); throw new Exception("Failed to create new asset.");
} }
} }

View File

@@ -38,7 +38,7 @@ namespace FlaxEditor.Content
/// <inheritdoc /> /// <inheritdoc />
public override void Create(string outputPath, object arg) 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."); throw new Exception("Failed to create new asset.");
} }
} }

View File

@@ -543,10 +543,11 @@ namespace FlaxEditor.CustomEditors
try try
{ {
string text; string text;
var value = Values[0];
if (ParentEditor is Dedicated.ScriptsEditor) if (ParentEditor is Dedicated.ScriptsEditor)
{ {
// Script // Script
text = JsonSerializer.Serialize(Values[0]); text = JsonSerializer.Serialize(value);
// Remove properties that should be ignored when copy/pasting data // Remove properties that should be ignored when copy/pasting data
if (text == null) if (text == null)
@@ -576,12 +577,12 @@ namespace FlaxEditor.CustomEditors
else if (ScriptType.FlaxObject.IsAssignableFrom(Values.Type)) else if (ScriptType.FlaxObject.IsAssignableFrom(Values.Type))
{ {
// Object reference // Object reference
text = JsonSerializer.GetStringID(Values[0] as FlaxEngine.Object); text = JsonSerializer.GetStringID(value as FlaxEngine.Object);
} }
else else
{ {
// Default // Default
text = JsonSerializer.Serialize(Values[0]); text = JsonSerializer.Serialize(value, TypeUtils.GetType(Values.Type));
} }
Clipboard.Text = text; Clipboard.Text = text;
} }

View File

@@ -591,14 +591,14 @@ namespace FlaxEditor.CustomEditors.Dedicated
var group = layout.Group(title, editor); var group = layout.Group(title, editor);
if ((Presenter.Features & FeatureFlags.CacheExpandedGroups) != 0) if ((Presenter.Features & FeatureFlags.CacheExpandedGroups) != 0)
{ {
if (Editor.Instance.ProjectCache.IsCollapsedGroup(title)) if (Editor.Instance.ProjectCache.IsGroupToggled(title))
group.Panel.Close(false); group.Panel.Close();
else else
group.Panel.Open(false); group.Panel.Open();
group.Panel.IsClosedChanged += panel => Editor.Instance.ProjectCache.SetCollapsedGroup(panel.HeaderText, panel.IsClosed); group.Panel.IsClosedChanged += panel => Editor.Instance.ProjectCache.SetGroupToggle(panel.HeaderText, panel.IsClosed);
} }
else else
group.Panel.Open(false); group.Panel.Open();
// Customize // Customize
group.Panel.TooltipText = Editor.Instance.CodeDocs.GetTooltip(scriptType); group.Panel.TooltipText = Editor.Instance.CodeDocs.GetTooltip(scriptType);

View File

@@ -51,10 +51,13 @@ namespace FlaxEditor.CustomEditors.Editors
return; return;
Picker = layout.Custom<AssetPicker>().CustomControl; Picker = layout.Custom<AssetPicker>().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; var assetType = _valueType;
if (assetType == typeof(string)) if (assetType == typeof(string))
assetType = new ScriptType(typeof(Asset)); 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; float height = 48;
var attributes = Values.GetAttributes(); var attributes = Values.GetAttributes();
@@ -102,6 +105,12 @@ namespace FlaxEditor.CustomEditors.Editors
SetValue(new SceneReference(Picker.Validator.SelectedID)); SetValue(new SceneReference(Picker.Validator.SelectedID));
else if (_valueType.Type == typeof(string)) else if (_valueType.Type == typeof(string))
SetValue(Picker.Validator.SelectedPath); 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 else
SetValue(Picker.Validator.SelectedAsset); SetValue(Picker.Validator.SelectedAsset);
} }
@@ -114,16 +123,19 @@ namespace FlaxEditor.CustomEditors.Editors
if (!HasDifferentValues) if (!HasDifferentValues)
{ {
_isRefreshing = true; _isRefreshing = true;
if (Values[0] is AssetItem assetItem) var value = Values[0];
if (value is AssetItem assetItem)
Picker.Validator.SelectedItem = assetItem; Picker.Validator.SelectedItem = assetItem;
else if (Values[0] is Guid guid) else if (value is Guid guid)
Picker.Validator.SelectedID = 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); 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; 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 else
Picker.Validator.SelectedAsset = Values[0] as Asset; Picker.Validator.SelectedAsset = value as Asset;
_isRefreshing = false; _isRefreshing = false;
} }
} }

View File

@@ -176,8 +176,8 @@ namespace FlaxEditor.CustomEditors.Editors
private IntValueBox _sizeBox; private IntValueBox _sizeBox;
private Color _background; private Color _background;
private int _elementsCount; private int _elementsCount, _minCount, _maxCount;
private bool _readOnly; private bool _canResize;
private bool _canReorderItems; private bool _canReorderItems;
private CollectionAttribute.DisplayType _displayType; private CollectionAttribute.DisplayType _displayType;
@@ -209,8 +209,10 @@ namespace FlaxEditor.CustomEditors.Editors
return; return;
var size = Count; var size = Count;
_readOnly = false; _canResize = true;
_canReorderItems = true; _canReorderItems = true;
_minCount = 0;
_maxCount = 0;
_background = FlaxEngine.GUI.Style.Current.CollectionBackgroundColor; _background = FlaxEngine.GUI.Style.Current.CollectionBackgroundColor;
_displayType = CollectionAttribute.DisplayType.Header; _displayType = CollectionAttribute.DisplayType.Header;
NotNullItems = false; NotNullItems = false;
@@ -222,7 +224,9 @@ namespace FlaxEditor.CustomEditors.Editors
var collection = (CollectionAttribute)attributes?.FirstOrDefault(x => x is CollectionAttribute); var collection = (CollectionAttribute)attributes?.FirstOrDefault(x => x is CollectionAttribute);
if (collection != null) if (collection != null)
{ {
_readOnly = collection.ReadOnly; _canResize = !collection.ReadOnly;
_minCount = collection.MinCount;
_maxCount = collection.MaxCount;
_canReorderItems = collection.CanReorderItems; _canReorderItems = collection.CanReorderItems;
NotNullItems = collection.NotNullItems; NotNullItems = collection.NotNullItems;
if (collection.BackgroundColor.HasValue) if (collection.BackgroundColor.HasValue)
@@ -231,6 +235,9 @@ namespace FlaxEditor.CustomEditors.Editors
spacing = collection.Spacing; spacing = collection.Spacing;
_displayType = collection.Display; _displayType = collection.Display;
} }
if (_maxCount == 0)
_maxCount = ushort.MaxValue;
_canResize &= _minCount < _maxCount;
var dragArea = layout.CustomContainer<DragAreaControl>(); var dragArea = layout.CustomContainer<DragAreaControl>();
dragArea.CustomControl.Editor = this; dragArea.CustomControl.Editor = this;
@@ -268,8 +275,8 @@ namespace FlaxEditor.CustomEditors.Editors
var y = -dropPanel.HeaderHeight + dropPanel.HeaderTextMargin.Top; var y = -dropPanel.HeaderHeight + dropPanel.HeaderTextMargin.Top;
_sizeBox = new IntValueBox(size) _sizeBox = new IntValueBox(size)
{ {
MinValue = 0, MinValue = _minCount,
MaxValue = ushort.MaxValue, MaxValue = _maxCount,
AnchorPreset = AnchorPresets.TopRight, AnchorPreset = AnchorPresets.TopRight,
Bounds = new Rectangle(-40 - dropPanel.ItemsMargin.Right, y, 40, height), Bounds = new Rectangle(-40 - dropPanel.ItemsMargin.Right, y, 40, height),
Parent = dropPanel, Parent = dropPanel,
@@ -283,7 +290,7 @@ namespace FlaxEditor.CustomEditors.Editors
Parent = dropPanel Parent = dropPanel
}; };
if (_readOnly || (NotNullItems && size == 0)) if (!_canResize || (NotNullItems && size == 0))
{ {
_sizeBox.IsReadOnly = true; _sizeBox.IsReadOnly = true;
_sizeBox.Enabled = false; _sizeBox.Enabled = false;
@@ -339,7 +346,7 @@ namespace FlaxEditor.CustomEditors.Editors
_elementsCount = size; _elementsCount = size;
// Add/Remove buttons // Add/Remove buttons
if (!_readOnly) if (_canResize)
{ {
var panel = dragArea.HorizontalPanel(); var panel = dragArea.HorizontalPanel();
panel.Panel.Size = new Float2(0, 20); panel.Panel.Size = new Float2(0, 20);
@@ -347,25 +354,23 @@ namespace FlaxEditor.CustomEditors.Editors
var removeButton = panel.Button("-", "Remove last item"); var removeButton = panel.Button("-", "Remove last item");
removeButton.Button.Size = new Float2(16, 16); removeButton.Button.Size = new Float2(16, 16);
removeButton.Button.Enabled = size > 0; removeButton.Button.Enabled = size > _minCount;
removeButton.Button.AnchorPreset = AnchorPresets.TopRight; removeButton.Button.AnchorPreset = AnchorPresets.TopRight;
removeButton.Button.Clicked += () => removeButton.Button.Clicked += () =>
{ {
if (IsSetBlocked) if (IsSetBlocked)
return; return;
Resize(Count - 1); Resize(Count - 1);
}; };
var addButton = panel.Button("+", "Add new item"); var addButton = panel.Button("+", "Add new item");
addButton.Button.Size = new Float2(16, 16); 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.AnchorPreset = AnchorPresets.TopRight;
addButton.Button.Clicked += () => addButton.Button.Clicked += () =>
{ {
if (IsSetBlocked) if (IsSetBlocked)
return; return;
Resize(Count + 1); Resize(Count + 1);
}; };
} }

View File

@@ -25,6 +25,11 @@ namespace FlaxEditor.CustomEditors
/// </summary> /// </summary>
internal bool isRootGroup = true; internal bool isRootGroup = true;
/// <summary>
/// Parent container who created this one.
/// </summary>
internal LayoutElementsContainer _parent;
/// <summary> /// <summary>
/// The children. /// The children.
/// </summary> /// </summary>
@@ -40,6 +45,24 @@ namespace FlaxEditor.CustomEditors
/// </summary> /// </summary>
public abstract ContainerControl ContainerControl { get; } public abstract ContainerControl ContainerControl { get; }
/// <summary>
/// Gets the Custom Editors layout presenter.
/// </summary>
internal CustomEditorPresenter Presenter
{
get
{
CustomEditorPresenter result;
var container = this;
do
{
result = container as CustomEditorPresenter;
container = container._parent;
} while (container != null);
return result;
}
}
/// <summary> /// <summary>
/// Adds new group element. /// Adds new group element.
/// </summary> /// </summary>
@@ -81,17 +104,31 @@ namespace FlaxEditor.CustomEditors
public GroupElement Group(string title, bool useTransparentHeader = false) public GroupElement Group(string title, bool useTransparentHeader = false)
{ {
var element = new GroupElement(); 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); // 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;
} }
else if (this is CustomEditorPresenter presenter && (presenter.Features & FeatureFlags.CacheExpandedGroups) != 0)
{ // Caching/restoring expanded groups (non-root groups cache expanded state so invert boolean expression)
if (Editor.Instance.ProjectCache.IsCollapsedGroup(title)) if (Editor.Instance.ProjectCache.IsGroupToggled(expandPath) ^ isSubGroup)
element.Panel.Close(false); element.Panel.Close();
element.Panel.IsClosedChanged += OnPanelIsClosedChanged; else
element.Panel.Open();
element.Panel.IsClosedChanged += panel => Editor.Instance.ProjectCache.SetGroupToggle(expandPath, panel.IsClosed ^ isSubGroup);
} }
element.isRootGroup = false; element.isRootGroup = false;
element._parent = this;
element.Panel.HeaderText = title; element.Panel.HeaderText = title;
if (useTransparentHeader) if (useTransparentHeader)
{ {
@@ -103,11 +140,6 @@ namespace FlaxEditor.CustomEditors
return element; return element;
} }
private void OnPanelIsClosedChanged(DropPanel panel)
{
Editor.Instance.ProjectCache.SetCollapsedGroup(panel.HeaderText, panel.IsClosed);
}
/// <summary> /// <summary>
/// Adds new horizontal panel element. /// Adds new horizontal panel element.
/// </summary> /// </summary>
@@ -627,7 +659,6 @@ namespace FlaxEditor.CustomEditors
if (style == DisplayStyle.Group) if (style == DisplayStyle.Group)
{ {
var group = Group(name, editor, true); var group = Group(name, editor, true);
group.Panel.Close(false);
group.Panel.TooltipText = tooltip; group.Panel.TooltipText = tooltip;
return group.Object(values, editor); return group.Object(values, editor);
} }
@@ -657,7 +688,6 @@ namespace FlaxEditor.CustomEditors
if (style == DisplayStyle.Group) if (style == DisplayStyle.Group)
{ {
var group = Group(label.Text, editor, true); var group = Group(label.Text, editor, true);
group.Panel.Close(false);
group.Panel.TooltipText = tooltip; group.Panel.TooltipText = tooltip;
return group.Object(values, editor); return group.Object(values, editor);
} }

View File

@@ -869,7 +869,9 @@ namespace FlaxEditor
/// <summary> /// <summary>
/// New asset types allowed to create. /// New asset types allowed to create.
/// [Deprecated in v1.8]
/// </summary> /// </summary>
[Obsolete("Use CreateAsset with named tag.")]
public enum NewAssetType public enum NewAssetType
{ {
/// <summary> /// <summary>
@@ -1046,12 +1048,59 @@ namespace FlaxEditor
/// <summary> /// <summary>
/// Creates new asset at the target location. /// Creates new asset at the target location.
/// [Deprecated in v1.8]
/// </summary> /// </summary>
/// <param name="type">New asset type.</param> /// <param name="type">New asset type.</param>
/// <param name="outputPath">Output asset path.</param> /// <param name="outputPath">Output asset path.</param>
[Obsolete("Use CreateAsset with named tag.")]
public static bool CreateAsset(NewAssetType type, string outputPath) 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);
} }
/// <summary> /// <summary>
@@ -1588,10 +1637,6 @@ namespace FlaxEditor
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CloseSplashScreen", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CloseSplashScreen", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
internal static partial void Internal_CloseSplashScreen(); 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))] [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CreateVisualScript", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
[return: MarshalAs(UnmanagedType.U1)] [return: MarshalAs(UnmanagedType.U1)]
internal static partial bool Internal_CreateVisualScript(string outputPath, string baseTypename); internal static partial bool Internal_CreateVisualScript(string outputPath, string baseTypename);

View File

@@ -170,78 +170,6 @@ DEFINE_INTERNAL_CALL(bool) EditorInternal_CloneAssetFile(MString* dstPathObj, MS
return Content::CloneAssetFile(dstPath, srcPath, *dstId); 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) DEFINE_INTERNAL_CALL(bool) EditorInternal_CreateVisualScript(MString* outputPathObj, MString* baseTypenameObj)
{ {
String outputPath; String outputPath;
@@ -639,8 +567,6 @@ bool ManagedEditor::TryRestoreImportOptions(ModelTool::Options& options, String
{ {
options.GenerateSDF = graphicsSettings->GenerateSDFOnModelImport; options.GenerateSDF = graphicsSettings->GenerateSDFOnModelImport;
} }
// Get options from model
FileSystem::NormalizePath(assetPath); FileSystem::NormalizePath(assetPath);
return ImportModel::TryGetImportOptions(assetPath, options); 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) bool ManagedEditor::TryRestoreImportOptions(AudioTool::Options& options, String assetPath)
{ {
// Get options from model
FileSystem::NormalizePath(assetPath); FileSystem::NormalizePath(assetPath);
return ImportAudio::TryGetImportOptions(assetPath, options); return ImportAudio::TryGetImportOptions(assetPath, options);
} }
bool ManagedEditor::CreateAsset(const String& tag, String outputPath)
{
FileSystem::NormalizePath(outputPath);
return AssetsImportingManager::Create(tag, outputPath);
}

View File

@@ -210,6 +210,13 @@ public:
API_FUNCTION() static bool TryRestoreImportOptions(API_PARAM(Ref) AudioTool::Options& options, String assetPath); API_FUNCTION() static bool TryRestoreImportOptions(API_PARAM(Ref) AudioTool::Options& options, String assetPath);
#endif #endif
/// <summary>
/// Creates a new asset at the target location.
/// </summary>
/// <param name="tag">New asset type.</param>
/// <param name="outputPath">Output asset path.</param>
API_FUNCTION() static bool CreateAsset(const String& tag, String outputPath);
public: public:
API_STRUCT(Internal, NoDefault) struct VisualScriptStackFrame API_STRUCT(Internal, NoDefault) struct VisualScriptStackFrame
{ {

View File

@@ -18,7 +18,7 @@ namespace FlaxEditor.Modules
private DateTime _lastSaveTime; private DateTime _lastSaveTime;
private readonly HashSet<Guid> _expandedActors = new HashSet<Guid>(); private readonly HashSet<Guid> _expandedActors = new HashSet<Guid>();
private readonly HashSet<string> _collapsedGroups = new HashSet<string>(); private readonly HashSet<string> _toggledGroups = new HashSet<string>();
private readonly Dictionary<string, string> _customData = new Dictionary<string, string>(); private readonly Dictionary<string, string> _customData = new Dictionary<string, string>();
/// <summary> /// <summary>
@@ -62,26 +62,26 @@ namespace FlaxEditor.Modules
} }
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
/// <param name="title">The group title.</param> /// <param name="title">The group title.</param>
/// <returns><c>true</c> if group is collapsed; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if group is toggled; otherwise, <c>false</c>.</returns>
public bool IsCollapsedGroup(string title) public bool IsGroupToggled(string title)
{ {
return _collapsedGroups.Contains(title); return _toggledGroups.Contains(title);
} }
/// <summary> /// <summary>
/// Sets the group collapsed cached value. /// Sets the group collapsed/opened cached value.
/// </summary> /// </summary>
/// <param name="title">The group title.</param> /// <param name="title">The group title.</param>
/// <param name="isCollapsed">If set to <c>true</c> group will be cached as an collapsed, otherwise false.</param> /// <param name="isToggled">If set to <c>true</c> group will be cached as a toggled, otherwise false.</param>
public void SetCollapsedGroup(string title, bool isCollapsed) public void SetGroupToggle(string title, bool isToggled)
{ {
if (isCollapsed) if (isToggled)
_collapsedGroups.Add(title); _toggledGroups.Add(title);
else else
_collapsedGroups.Remove(title); _toggledGroups.Remove(title);
_isDirty = true; _isDirty = true;
} }
@@ -160,7 +160,7 @@ namespace FlaxEditor.Modules
_expandedActors.Add(new Guid(bytes16)); _expandedActors.Add(new Guid(bytes16));
} }
_collapsedGroups.Clear(); _toggledGroups.Clear();
_customData.Clear(); _customData.Clear();
break; break;
@@ -176,7 +176,7 @@ namespace FlaxEditor.Modules
_expandedActors.Add(new Guid(bytes16)); _expandedActors.Add(new Guid(bytes16));
} }
_collapsedGroups.Clear(); _toggledGroups.Clear();
_customData.Clear(); _customData.Clear();
int customDataCount = reader.ReadInt32(); int customDataCount = reader.ReadInt32();
@@ -201,11 +201,9 @@ namespace FlaxEditor.Modules
} }
int collapsedGroupsCount = reader.ReadInt32(); int collapsedGroupsCount = reader.ReadInt32();
_collapsedGroups.Clear(); _toggledGroups.Clear();
for (int i = 0; i < collapsedGroupsCount; i++) for (int i = 0; i < collapsedGroupsCount; i++)
{ _toggledGroups.Add(reader.ReadString());
_collapsedGroups.Add(reader.ReadString());
}
_customData.Clear(); _customData.Clear();
int customDataCount = reader.ReadInt32(); int customDataCount = reader.ReadInt32();
@@ -259,11 +257,9 @@ namespace FlaxEditor.Modules
writer.Write(e.ToByteArray()); writer.Write(e.ToByteArray());
} }
writer.Write(_collapsedGroups.Count); writer.Write(_toggledGroups.Count);
foreach (var e in _collapsedGroups) foreach (var e in _toggledGroups)
{
writer.Write(e); writer.Write(e);
}
writer.Write(_customData.Count); writer.Write(_customData.Count);
foreach (var e in _customData) foreach (var e in _customData)
@@ -284,7 +280,6 @@ namespace FlaxEditor.Modules
try try
{ {
SaveGuarded(); SaveGuarded();
_isDirty = false; _isDirty = false;
} }
catch (Exception ex) catch (Exception ex)

View File

@@ -534,6 +534,41 @@ namespace FlaxEditor.Modules
Delete(); Delete();
} }
/// <summary>
/// Create parent for selected actors.
/// </summary>
public void CreateParentForSelectedActors()
{
Actor actor = new EmptyActor();
Editor.SceneEditing.Spawn(actor, null, false);
List<SceneGraphNode> 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);
}
/// <summary> /// <summary>
/// Duplicates the selected objects. Supports undo/redo. /// Duplicates the selected objects. Supports undo/redo.
/// </summary> /// </summary>

View File

@@ -50,6 +50,7 @@ namespace FlaxEditor.Modules
private ContextMenuButton _menuEditCut; private ContextMenuButton _menuEditCut;
private ContextMenuButton _menuEditCopy; private ContextMenuButton _menuEditCopy;
private ContextMenuButton _menuEditPaste; private ContextMenuButton _menuEditPaste;
private ContextMenuButton _menuCreateParentForSelectedActors;
private ContextMenuButton _menuEditDelete; private ContextMenuButton _menuEditDelete;
private ContextMenuButton _menuEditDuplicate; private ContextMenuButton _menuEditDuplicate;
private ContextMenuButton _menuEditSelectAll; private ContextMenuButton _menuEditSelectAll;
@@ -548,11 +549,11 @@ namespace FlaxEditor.Modules
_menuEditCut = cm.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut); _menuEditCut = cm.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut);
_menuEditCopy = cm.AddButton("Copy", inputOptions.Copy, Editor.SceneEditing.Copy); _menuEditCopy = cm.AddButton("Copy", inputOptions.Copy, Editor.SceneEditing.Copy);
_menuEditPaste = cm.AddButton("Paste", inputOptions.Paste, Editor.SceneEditing.Paste); _menuEditPaste = cm.AddButton("Paste", inputOptions.Paste, Editor.SceneEditing.Paste);
cm.AddSeparator();
_menuEditDelete = cm.AddButton("Delete", inputOptions.Delete, Editor.SceneEditing.Delete); _menuEditDelete = cm.AddButton("Delete", inputOptions.Delete, Editor.SceneEditing.Delete);
_menuEditDuplicate = cm.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate); _menuEditDuplicate = cm.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate);
cm.AddSeparator(); cm.AddSeparator();
_menuEditSelectAll = cm.AddButton("Select all", inputOptions.SelectAll, Editor.SceneEditing.SelectAllScenes); _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); _menuEditFind = cm.AddButton("Find", inputOptions.Search, Editor.Windows.SceneWin.Search);
cm.AddSeparator(); cm.AddSeparator();
cm.AddButton("Game Settings", () => cm.AddButton("Game Settings", () =>
@@ -858,6 +859,7 @@ namespace FlaxEditor.Modules
_menuEditCut.Enabled = hasSthSelected; _menuEditCut.Enabled = hasSthSelected;
_menuEditCopy.Enabled = hasSthSelected; _menuEditCopy.Enabled = hasSthSelected;
_menuEditPaste.Enabled = canEditScene; _menuEditPaste.Enabled = canEditScene;
_menuCreateParentForSelectedActors.Enabled = canEditScene && hasSthSelected;
_menuEditDelete.Enabled = hasSthSelected; _menuEditDelete.Enabled = hasSthSelected;
_menuEditDuplicate.Enabled = hasSthSelected; _menuEditDuplicate.Enabled = hasSthSelected;
_menuEditSelectAll.Enabled = Level.IsAnySceneLoaded; _menuEditSelectAll.Enabled = Level.IsAnySceneLoaded;

View File

@@ -23,7 +23,7 @@ namespace FlaxEditor.SceneGraph.Actors
base.OnDebugDraw(data); base.OnDebugDraw(data);
var transform = Actor.Transform; 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);
} }
} }
} }

View File

@@ -23,7 +23,7 @@ namespace FlaxEditor.SceneGraph.Actors
base.OnDebugDraw(data); base.OnDebugDraw(data);
var transform = Actor.Transform; 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);
} }
} }
} }

View File

@@ -482,7 +482,7 @@ namespace FlaxEditor.Surface.Archetypes
var startPos = PointToParent(ref center); var startPos = PointToParent(ref center);
targetState.GetConnectionEndPoint(ref startPos, out var endPos); targetState.GetConnectionEndPoint(ref startPos, out var endPos);
var color = style.Foreground; 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
/// <inheritdoc /> /// <inheritdoc />
public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color) 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);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -676,38 +676,6 @@ namespace FlaxEditor.Surface.Archetypes
{ {
} }
/// <summary>
/// Draws the connection between two state machine nodes.
/// </summary>
/// <param name="startPos">The start position.</param>
/// <param name="endPos">The end position.</param>
/// <param name="color">The line color.</param>
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);
}
/// <summary> /// <summary>
/// Gets the connection end point for the given input position. Puts the end point near the edge of the node bounds. /// Gets the connection end point for the given input position. Puts the end point near the edge of the node bounds.
/// </summary> /// </summary>
@@ -1308,7 +1276,7 @@ namespace FlaxEditor.Surface.Archetypes
isMouseOver = Float2.DistanceSquared(ref mousePosition, ref point) < 25.0f; isMouseOver = Float2.DistanceSquared(ref mousePosition, ref point) < 25.0f;
} }
var color = isMouseOver ? Color.Wheat : t.LineColor; 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
/// <inheritdoc /> /// <inheritdoc />
public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color) public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color)
{ {
DrawConnection(ref startPos, ref endPos, ref color); SurfaceStyle.DrawStraightConnection(startPos, endPos, color);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -31,7 +31,7 @@ namespace FlaxEditor.Surface
var editor = Editor.Instance; var editor = Editor.Instance;
var style = SurfaceStyle.CreateStyleHandler(editor); var style = SurfaceStyle.CreateStyleHandler(editor);
style.DrawBox = DrawBox; style.DrawBox = DrawBox;
style.DrawConnection = DrawConnection; style.DrawConnection = SurfaceStyle.DrawStraightConnection;
return style; return style;
} }
@@ -49,11 +49,6 @@ namespace FlaxEditor.Surface
Render2D.FillRectangle(rect, color); 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) private void OnActiveContextMenuVisibleChanged(Control activeCM)
{ {
_nodesCache.Wait(); _nodesCache.Wait();

View File

@@ -295,5 +295,38 @@ namespace FlaxEditor.Surface
Background = editor.UI.VisjectSurfaceBackground, Background = editor.UI.VisjectSurfaceBackground,
}; };
} }
/// <summary>
/// Draws a simple straight connection between two locations.
/// </summary>
/// <param name="startPos">The start position.</param>
/// <param name="endPos">The end position.</param>
/// <param name="color">The line color.</param>
/// <param name="thickness">The line thickness.</param>
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);
}
} }
} }

View File

@@ -20,8 +20,15 @@ namespace FlaxEditor.Surface
/// <summary> /// <summary>
/// Utility for easy nodes archetypes generation for Visject Surface based on scripting types. /// Utility for easy nodes archetypes generation for Visject Surface based on scripting types.
/// </summary> /// </summary>
internal class NodesCache [HideInEditor]
public class NodesCache
{ {
/// <summary>
/// Delegate for scripting types filtering into cache.
/// </summary>
/// <param name="scriptType">The input type to process.</param>
/// <param name="cache">Node groups cache that can be used for reusing groups for different nodes.</param>
/// <param name="version">The cache version number. Can be used to reject any cached data after <see cref="NodesCache"/> rebuilt.</param>
public delegate void IterateType(ScriptType scriptType, Dictionary<KeyValuePair<string, ushort>, GroupArchetype> cache, int version); public delegate void IterateType(ScriptType scriptType, Dictionary<KeyValuePair<string, ushort>, GroupArchetype> cache, int version);
internal static readonly List<NodesCache> Caches = new List<NodesCache>(8); internal static readonly List<NodesCache> Caches = new List<NodesCache>(8);
@@ -33,11 +40,18 @@ namespace FlaxEditor.Surface
private VisjectCM _taskContextMenu; private VisjectCM _taskContextMenu;
private Dictionary<KeyValuePair<string, ushort>, GroupArchetype> _cache; private Dictionary<KeyValuePair<string, ushort>, GroupArchetype> _cache;
/// <summary>
/// Initializes a new instance of the <see cref="NodesCache"/> class.
/// </summary>
/// <param name="iterator">The iterator callback to build node types from Scripting.</param>
public NodesCache(IterateType iterator) public NodesCache(IterateType iterator)
{ {
_iterator = iterator; _iterator = iterator;
} }
/// <summary>
/// Waits for the async caching job to finish.
/// </summary>
public void Wait() public void Wait()
{ {
if (_task != null) if (_task != null)
@@ -48,6 +62,9 @@ namespace FlaxEditor.Surface
} }
} }
/// <summary>
/// Clears cache.
/// </summary>
public void Clear() public void Clear()
{ {
Wait(); Wait();
@@ -62,6 +79,10 @@ namespace FlaxEditor.Surface
} }
} }
/// <summary>
/// Updates the Visject Context Menu to contain current nodes.
/// </summary>
/// <param name="contextMenu">The output context menu to setup.</param>
public void Get(VisjectCM contextMenu) public void Get(VisjectCM contextMenu)
{ {
Profiler.BeginEvent("Setup Context Menu"); Profiler.BeginEvent("Setup Context Menu");

View File

@@ -25,7 +25,7 @@ namespace FlaxEditor.Surface
/// The base interface for editor windows that use <see cref="FlaxEditor.Surface.VisjectSurface"/> for content editing. /// The base interface for editor windows that use <see cref="FlaxEditor.Surface.VisjectSurface"/> for content editing.
/// </summary> /// </summary>
/// <seealso cref="FlaxEditor.Surface.IVisjectSurfaceOwner" /> /// <seealso cref="FlaxEditor.Surface.IVisjectSurfaceOwner" />
interface IVisjectSurfaceWindow : IVisjectSurfaceOwner public interface IVisjectSurfaceWindow : IVisjectSurfaceOwner
{ {
/// <summary> /// <summary>
/// Gets the asset edited by the window. /// Gets the asset edited by the window.

View File

@@ -43,6 +43,19 @@ namespace FlaxEngine.Tools
/// Enables continuous painting, otherwise single paint on click. /// Enables continuous painting, otherwise single paint on click.
/// </summary> /// </summary>
public bool ContinuousPaint; public bool ContinuousPaint;
/// <summary>
/// Enables drawing cloth paint debugging with Depth Test enabled (skips occluded vertices).
/// </summary>
public bool DebugDrawDepthTest
{
get => Gizmo.Cloth?.DebugDrawDepthTest ?? true;
set
{
if (Gizmo.Cloth != null)
Gizmo.Cloth.DebugDrawDepthTest = value;
}
}
#pragma warning restore CS0649 #pragma warning restore CS0649
public override void Init(IGizmoOwner owner) public override void Init(IGizmoOwner owner)
@@ -62,6 +75,7 @@ namespace FlaxEngine.Tools
public override void Dispose() public override void Dispose()
{ {
Owner.Gizmos.Remove(Gizmo); Owner.Gizmos.Remove(Gizmo);
Gizmo = null;
base.Dispose(); base.Dispose();
} }
@@ -83,6 +97,7 @@ namespace FlaxEngine.Tools
private EditClothPaintAction _undoAction; private EditClothPaintAction _undoAction;
public bool IsPainting => _isPainting; public bool IsPainting => _isPainting;
public Cloth Cloth => _cloth;
public ClothPaintingGizmo(IGizmoOwner owner, ClothPaintingGizmoMode mode) public ClothPaintingGizmo(IGizmoOwner owner, ClothPaintingGizmoMode mode)
: base(owner) : base(owner)

View File

@@ -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.")] [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; 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.")] [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; public int CollisionLOD = -1;
@@ -152,7 +149,6 @@ namespace FlaxEditor.Tools.Terrain
terrain.Setup(_options.LODCount, (int)_options.ChunkSize); terrain.Setup(_options.LODCount, (int)_options.ChunkSize);
terrain.Transform = new Transform(_options.Position, _options.Orientation, _options.Scale); terrain.Transform = new Transform(_options.Position, _options.Orientation, _options.Scale);
terrain.Material = _options.Material; terrain.Material = _options.Material;
terrain.PhysicalMaterial = _options.PhysicalMaterial;
terrain.CollisionLOD = _options.CollisionLOD; terrain.CollisionLOD = _options.CollisionLOD;
if (_options.Heightmap) if (_options.Heightmap)
terrain.Position -= new Vector3(0, _options.HeightmapScale * 0.5f, 0); terrain.Position -= new Vector3(0, _options.HeightmapScale * 0.5f, 0);

View File

@@ -67,11 +67,13 @@ namespace FlaxEditor.Tools.Terrain.Paint
// Prepare // Prepare
var splatmapIndex = ActiveSplatmapIndex; var splatmapIndex = ActiveSplatmapIndex;
var splatmapIndexOther = (splatmapIndex + 1) % 2;
var chunkSize = terrain.ChunkSize; var chunkSize = terrain.ChunkSize;
var heightmapSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1; var heightmapSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1;
var heightmapLength = heightmapSize * heightmapSize; var heightmapLength = heightmapSize * heightmapSize;
var patchSize = chunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount; 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; var unitsPerVertexInv = 1.0f / FlaxEngine.Terrain.UnitsPerVertex;
ApplyParams p = new ApplyParams ApplyParams p = new ApplyParams
{ {
@@ -81,8 +83,10 @@ namespace FlaxEditor.Tools.Terrain.Paint
Options = options, Options = options,
Strength = strength, Strength = strength,
SplatmapIndex = splatmapIndex, SplatmapIndex = splatmapIndex,
SplatmapIndexOther = splatmapIndexOther,
HeightmapSize = heightmapSize, HeightmapSize = heightmapSize,
TempBuffer = tempBuffer, TempBuffer = tempBuffer,
TempBufferOther = tempBufferOther,
}; };
// Get brush bounds in terrain local space // Get brush bounds in terrain local space
@@ -132,10 +136,15 @@ namespace FlaxEditor.Tools.Terrain.Paint
if (sourceData == null) if (sourceData == null)
throw new Exception("Cannot modify terrain. Loading splatmap failed. See log for more info."); 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 // Record patch data before editing it
if (!gizmo.CurrentEditUndoAction.HashPatch(ref patch.PatchCoord)) if (!gizmo.CurrentEditUndoAction.HashPatch(ref patch.PatchCoord))
{ {
gizmo.CurrentEditUndoAction.AddPatch(ref patch.PatchCoord, splatmapIndex); gizmo.CurrentEditUndoAction.AddPatch(ref patch.PatchCoord, splatmapIndex);
gizmo.CurrentEditUndoAction.AddPatch(ref patch.PatchCoord, splatmapIndexOther);
} }
// Apply modification // Apply modification
@@ -144,6 +153,7 @@ namespace FlaxEditor.Tools.Terrain.Paint
p.PatchCoord = patch.PatchCoord; p.PatchCoord = patch.PatchCoord;
p.PatchPositionLocal = patchPositionLocal; p.PatchPositionLocal = patchPositionLocal;
p.SourceData = sourceData; p.SourceData = sourceData;
p.SourceDataOther = sourceDataOther;
Apply(ref p); Apply(ref p);
} }
} }
@@ -198,16 +208,32 @@ namespace FlaxEditor.Tools.Terrain.Paint
/// </summary> /// </summary>
public int SplatmapIndex; public int SplatmapIndex;
/// <summary>
/// The splatmap texture index. If <see cref="SplatmapIndex"/> is 0, this will be 1. If <see cref="SplatmapIndex"/> is 1, this will be 0.
/// </summary>
public int SplatmapIndexOther;
/// <summary> /// <summary>
/// The temporary data buffer (for modified data). /// The temporary data buffer (for modified data).
/// </summary> /// </summary>
public Color32* TempBuffer; public Color32* TempBuffer;
/// <summary>
/// The 'other' temporary data buffer (for modified data). If <see cref="TempBuffer"/> refersto the splatmap with index 0, this one will refer to the one with index 1.
/// </summary>
public Color32* TempBufferOther;
/// <summary> /// <summary>
/// The source data buffer. /// The source data buffer.
/// </summary> /// </summary>
public Color32* SourceData; public Color32* SourceData;
/// <summary>
/// The 'other' source data buffer. If <see cref="SourceData"/> refers
/// to the splatmap with index 0, this one will refer to the one with index 1.
/// </summary>
public Color32* SourceDataOther;
/// <summary> /// <summary>
/// The heightmap size (edge). /// The heightmap size (edge).
/// </summary> /// </summary>

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using FlaxEngine; using FlaxEngine;
namespace FlaxEditor.Tools.Terrain.Paint namespace FlaxEditor.Tools.Terrain.Paint
@@ -73,53 +72,53 @@ namespace FlaxEditor.Tools.Terrain.Paint
var strength = p.Strength; var strength = p.Strength;
var layer = (int)Layer; var layer = (int)Layer;
var brushPosition = p.Gizmo.CursorPosition; var brushPosition = p.Gizmo.CursorPosition;
var layerComponent = layer % 4; var c = layer % 4;
// Apply brush modification // Apply brush modification
Profiler.BeginEvent("Apply Brush"); Profiler.BeginEvent("Apply Brush");
bool otherModified = false;
for (int z = 0; z < p.ModifiedSize.Y; z++) for (int z = 0; z < p.ModifiedSize.Y; z++)
{ {
var zz = z + p.ModifiedOffset.Y; var zz = z + p.ModifiedOffset.Y;
for (int x = 0; x < p.ModifiedSize.X; x++) for (int x = 0; x < p.ModifiedSize.X; x++)
{ {
var xx = x + p.ModifiedOffset.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); 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); 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 // Paint on the active splatmap texture
byte* srcPtr = &src.R; src[c] = Mathf.Saturate(src[c] + paintAmount);
var srcWeight = *(srcPtr + layerComponent) / 255.0f; src[(c + 1) % 4] = Mathf.Saturate(src[(c + 1) % 4] - paintAmount);
src[(c + 2) % 4] = Mathf.Saturate(src[(c + 2) % 4] - paintAmount);
// Accumulate weight src[(c + 3) % 4] = Mathf.Saturate(src[(c + 3) % 4] - paintAmount);
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
p.TempBuffer[z * p.ModifiedSize.X + x] = src; 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(); Profiler.EndEvent();
// Update terrain patch // Update terrain patch
TerrainTools.ModifySplatMap(p.Terrain, ref p.PatchCoord, p.SplatmapIndex, p.TempBuffer, ref p.ModifiedOffset, ref p.ModifiedSize); 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);
} }
} }
} }

View File

@@ -36,9 +36,34 @@ namespace FlaxEditor.Tools.Terrain
"Layer 7", "Layer 7",
}; };
private IntPtr _cachedSplatmapData; private struct SplatmapData
private int _cachedSplatmapDataSize; {
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 EditTerrainMapAction _activeAction;
private SplatmapData[] _cachedSplatmapData = new SplatmapData[2];
/// <summary> /// <summary>
/// The terrain painting gizmo. /// 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. /// Gets the splatmap temporary scratch memory buffer used to modify terrain samples. Allocated memory is unmanaged by GC.
/// </summary> /// </summary>
/// <param name="size">The minimum buffer size (in bytes).</param> /// <param name="size">The minimum buffer size (in bytes).</param>
/// <param name="splatmapIndex">The splatmap index for which to return/create the temp buffer.</param>
/// <returns>The allocated memory using <see cref="Marshal"/> interface.</returns> /// <returns>The allocated memory using <see cref="Marshal"/> interface.</returns>
public IntPtr GetSplatmapTempBuffer(int size) public IntPtr GetSplatmapTempBuffer(int size, int splatmapIndex)
{ {
if (_cachedSplatmapDataSize < size) ref var splatmapData = ref _cachedSplatmapData[splatmapIndex];
{ splatmapData.EnsureCapacity(size);
if (_cachedSplatmapData != IntPtr.Zero) return splatmapData.DataPtr;
{
Marshal.FreeHGlobal(_cachedSplatmapData);
}
_cachedSplatmapData = Marshal.AllocHGlobal(size);
_cachedSplatmapDataSize = size;
}
return _cachedSplatmapData;
} }
/// <summary> /// <summary>
@@ -276,12 +294,8 @@ namespace FlaxEditor.Tools.Terrain
base.OnDeactivated(); base.OnDeactivated();
// Free temporary memory buffer // Free temporary memory buffer
if (_cachedSplatmapData != IntPtr.Zero) foreach (var splatmapData in _cachedSplatmapData)
{ splatmapData.Free();
Marshal.FreeHGlobal(_cachedSplatmapData);
_cachedSplatmapData = IntPtr.Zero;
_cachedSplatmapDataSize = 0;
}
} }
/// <summary> /// <summary>

View File

@@ -20,7 +20,7 @@ bool TerrainTools::TryGetPatchCoordToAdd(Terrain* terrain, const Ray& ray, Int2&
{ {
CHECK_RETURN(terrain, true); CHECK_RETURN(terrain, true);
result = Int2::Zero; 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 // Try to pick any of the patch edges
for (int32 patchIndex = 0; patchIndex < terrain->GetPatchesCount(); patchIndex++) for (int32 patchIndex = 0; patchIndex < terrain->GetPatchesCount(); patchIndex++)
@@ -179,7 +179,7 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches
terrain->AddPatches(numberOfPatches); terrain->AddPatches(numberOfPatches);
// Prepare data // Prepare data
const auto heightmapSize = terrain->GetChunkSize() * TerrainPatch::CHUNKS_COUNT_EDGE + 1; const auto heightmapSize = terrain->GetChunkSize() * Terrain::ChunksCountEdge + 1;
Array<float> heightmapData; Array<float> heightmapData;
heightmapData.Resize(heightmapSize * heightmapSize); heightmapData.Resize(heightmapSize * heightmapSize);
@@ -380,7 +380,7 @@ bool TerrainTools::ExportTerrain(Terrain* terrain, String outputFolder)
const auto firstPatch = terrain->GetPatch(0); const auto firstPatch = terrain->GetPatch(0);
// Calculate texture size // 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; const int32 patchVertexCount = patchEdgeVertexCount * patchEdgeVertexCount;
// Find size of heightmap in patches // Find size of heightmap in patches

View File

@@ -234,7 +234,6 @@ namespace FlaxEditor.Viewport
LocalOrientation = RootNode.RaycastNormalRotation(ref hitNormal), LocalOrientation = RootNode.RaycastNormalRotation(ref hitNormal),
Name = item.ShortName Name = item.ShortName
}; };
DebugDraw.DrawWireArrow(PostProcessSpawnedActorLocation(actor, ref hitNormal), actor.LocalOrientation, 1.0f, Color.Red, 1000000);
Spawn(actor, ref hitLocation, ref hitNormal); Spawn(actor, ref hitLocation, ref hitNormal);
} }
else if (hit is StaticModelNode staticModelNode) else if (hit is StaticModelNode staticModelNode)

View File

@@ -132,10 +132,13 @@ namespace FlaxEditor.Windows
b = contextMenu.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut); b = contextMenu.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut);
b.Enabled = canEditScene; b.Enabled = canEditScene;
// Prefab options // Create option
contextMenu.AddSeparator(); 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 = contextMenu.AddButton("Create Prefab", Editor.Prefabs.CreatePrefab);
b.Enabled = isSingleActorSelected && b.Enabled = isSingleActorSelected &&
((ActorNode)Editor.SceneEditing.Selection[0]).CanCreatePrefab && ((ActorNode)Editor.SceneEditing.Selection[0]).CanCreatePrefab &&

View File

@@ -2,7 +2,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Gizmo; using FlaxEditor.Gizmo;
using FlaxEditor.Content; using FlaxEditor.Content;
using FlaxEditor.GUI.Tree; using FlaxEditor.GUI.Tree;
@@ -14,7 +13,6 @@ using FlaxEditor.Scripting;
using FlaxEditor.States; using FlaxEditor.States;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using static FlaxEditor.GUI.ItemsListContextMenu;
namespace FlaxEditor.Windows namespace FlaxEditor.Windows
{ {
@@ -35,6 +33,11 @@ namespace FlaxEditor.Windows
private DragScriptItems _dragScriptItems; private DragScriptItems _dragScriptItems;
private DragHandlers _dragHandlers; private DragHandlers _dragHandlers;
/// <summary>
/// Scene tree panel.
/// </summary>
public Panel SceneTreePanel => _sceneTreePanel;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SceneTreeWindow"/> class. /// Initializes a new instance of the <see cref="SceneTreeWindow"/> class.
/// </summary> /// </summary>

View File

@@ -146,7 +146,7 @@ namespace FlaxEngine
public string Path; public string Path;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BehaviorKnowledgeSelectorAny"/> structure. /// Initializes a new instance of the <see cref="BehaviorKnowledgeSelector{T}"/> structure.
/// </summary> /// </summary>
/// <param name="path">The selector path.</param> /// <param name="path">The selector path.</param>
public BehaviorKnowledgeSelector(string path) public BehaviorKnowledgeSelector(string path)
@@ -155,7 +155,7 @@ namespace FlaxEngine
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BehaviorKnowledgeSelectorAny"/> structure. /// Initializes a new instance of the <see cref="BehaviorKnowledgeSelector{T}"/> structure.
/// </summary> /// </summary>
/// <param name="other">The other selector.</param> /// <param name="other">The other selector.</param>
public BehaviorKnowledgeSelector(BehaviorKnowledgeSelectorAny other) public BehaviorKnowledgeSelector(BehaviorKnowledgeSelectorAny other)

View File

@@ -91,6 +91,13 @@ API_STRUCT(InBuild, Template, MarshalAs=StringAnsi) struct FLAXENGINE_API Behavi
return false; return false;
} }
BehaviorKnowledgeSelector() = default;
BehaviorKnowledgeSelector(const StringAnsi& other)
{
Path = other;
}
BehaviorKnowledgeSelector& operator=(const StringAnsiView& other) noexcept BehaviorKnowledgeSelector& operator=(const StringAnsiView& other) noexcept
{ {
Path = other; Path = other;

View File

@@ -221,6 +221,7 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
context.NodePath.Clear(); context.NodePath.Clear();
context.Data = &data; context.Data = &data;
context.DeltaTime = dt; context.DeltaTime = dt;
context.StackOverFlow = false;
context.CurrentFrameIndex = ++data.CurrentFrame; context.CurrentFrameIndex = ++data.CurrentFrame;
context.CallStack.Clear(); context.CallStack.Clear();
context.Functions.Clear(); context.Functions.Clear();
@@ -411,9 +412,12 @@ VisjectExecutor::Value AnimGraphExecutor::eatBox(Node* caller, Box* box)
auto& context = *Context.Get(); auto& context = *Context.Get();
// Check if graph is looped or is too deep // Check if graph is looped or is too deep
if (context.StackOverFlow)
return Value::Zero;
if (context.CallStack.Count() >= ANIM_GRAPH_MAX_CALL_STACK) if (context.CallStack.Count() >= ANIM_GRAPH_MAX_CALL_STACK)
{ {
OnError(caller, box, TEXT("Graph is looped or too deep!")); OnError(caller, box, TEXT("Graph is looped or too deep!"));
context.StackOverFlow = true;
return Value::Zero; return Value::Zero;
} }
#if !BUILD_RELEASE #if !BUILD_RELEASE

View File

@@ -796,6 +796,7 @@ struct AnimGraphContext
AnimGraphInstanceData* Data; AnimGraphInstanceData* Data;
AnimGraphImpulse EmptyNodes; AnimGraphImpulse EmptyNodes;
AnimGraphTransitionData TransitionData; AnimGraphTransitionData TransitionData;
bool StackOverFlow;
Array<VisjectExecutor::Node*, FixedAllocation<ANIM_GRAPH_MAX_CALL_STACK>> CallStack; Array<VisjectExecutor::Node*, FixedAllocation<ANIM_GRAPH_MAX_CALL_STACK>> CallStack;
Array<VisjectExecutor::Graph*, FixedAllocation<32>> GraphStack; Array<VisjectExecutor::Graph*, FixedAllocation<32>> GraphStack;
Array<uint32, FixedAllocation<ANIM_GRAPH_MAX_CALL_STACK> > NodePath; Array<uint32, FixedAllocation<ANIM_GRAPH_MAX_CALL_STACK> > NodePath;

View File

@@ -277,6 +277,8 @@ bool Content::GetAssetInfo(const StringView& path, AssetInfo& info)
// Find asset in registry // Find asset in registry
if (Cache.FindAsset(path, info)) if (Cache.FindAsset(path, info))
return true; return true;
if (!FileSystem::FileExists(path))
return false;
PROFILE_CPU(); PROFILE_CPU();
const auto extension = FileSystem::GetExtension(path).ToLower(); const auto extension = FileSystem::GetExtension(path).ToLower();

View File

@@ -393,35 +393,58 @@ bool JsonAsset::CreateInstance()
if (typeHandle) if (typeHandle)
{ {
auto& type = typeHandle.GetType(); auto& type = typeHandle.GetType();
switch (type.Type)
{
case ScriptingTypes::Class:
{
// Ensure that object can deserialized // Ensure that object can deserialized
const ScriptingType::InterfaceImplementation* interface = type.GetInterface(ISerializable::TypeInitializer); const ScriptingType::InterfaceImplementation* interface = type.GetInterface(ISerializable::TypeInitializer);
if (!interface) if (!interface)
{ {
LOG(Warning, "Cannot deserialize {0} from Json Asset because it doesn't implement ISerializable interface.", type.ToString()); LOG(Warning, "Cannot deserialize {0} from Json Asset because it doesn't implement ISerializable interface.", type.ToString());
break; return false;
} }
auto modifier = Cache::ISerializeModifier.Get();
modifier->EngineBuild = DataEngineBuild;
// Allocate object // Create object
switch (type.Type)
{
case ScriptingTypes::Class:
case ScriptingTypes::Structure:
{
const auto instance = Allocator::Allocate(type.Size); const auto instance = Allocator::Allocate(type.Size);
if (!instance) if (!instance)
return true; return true;
Instance = instance; Instance = instance;
InstanceType = typeHandle; if (type.Type == ScriptingTypes::Class)
{
_dtor = type.Class.Dtor; _dtor = type.Class.Dtor;
type.Class.Ctor(instance); type.Class.Ctor(instance);
}
else
{
_dtor = type.Struct.Dtor;
type.Struct.Ctor(instance);
}
// Deserialize object // Deserialize object
auto modifier = Cache::ISerializeModifier.Get();
modifier->EngineBuild = DataEngineBuild;
((ISerializable*)((byte*)instance + interface->VTableOffset))->Deserialize(*Data, modifier.Value); ((ISerializable*)((byte*)instance + interface->VTableOffset))->Deserialize(*Data, modifier.Value);
break; 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<ISerializable>(instance)->Deserialize(*Data, modifier.Value);
break;
} }
} }
InstanceType = typeHandle;
}
return false; return false;
} }
@@ -441,13 +464,20 @@ void JsonAsset::DeleteInstance()
} }
// C++ instance // C++ instance
if (!Instance || !_dtor) if (!Instance)
return; return;
if (_dtor)
{
_dtor(Instance); _dtor(Instance);
InstanceType = ScriptingTypeHandle();
Allocator::Free(Instance);
Instance = nullptr;
_dtor = nullptr; _dtor = nullptr;
Allocator::Free(Instance);
}
else
{
Delete((ScriptingObject*)Instance);
}
InstanceType = ScriptingTypeHandle();
Instance = nullptr;
} }
#if USE_EDITOR #if USE_EDITOR

View File

@@ -139,7 +139,8 @@ public:
T* GetInstance() const T* GetInstance() const
{ {
const_cast<JsonAsset*>(this)->CreateInstance(); const_cast<JsonAsset*>(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: public:

View File

@@ -0,0 +1,133 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.Runtime.CompilerServices;
namespace FlaxEngine
{
/// <summary>
/// Json asset reference utility. References resource with a typed data type.
/// </summary>
/// <typeparam name="T">Type of the asset instance type.</typeparam>
#if FLAX_EDITOR
[CustomEditor(typeof(FlaxEditor.CustomEditors.Editors.AssetRefEditor))]
#endif
public struct JsonAssetReference<T> : IComparable, IComparable<JsonAssetReference<T>>, IEquatable<JsonAssetReference<T>>
{
/// <summary>
/// Gets or sets the referenced asset.
/// </summary>
public JsonAsset Asset;
/// <summary>
/// Gets the instance of the serialized object from the json asset data. Cached internally.
/// </summary>
public T Instance => (T)Asset?.Instance;
/// <summary>
/// Initializes a new instance of the <see cref="JsonAssetReference{T}"/> structure.
/// </summary>
/// <param name="asset">The Json Asset.</param>
public JsonAssetReference(JsonAsset asset)
{
Asset = asset;
}
/// <summary>
/// Implicit cast operator.
/// </summary>
public static implicit operator JsonAsset(JsonAssetReference<T> value)
{
return value.Asset;
}
/// <summary>
/// Implicit cast operator.
/// </summary>
public static implicit operator IntPtr(JsonAssetReference<T> value)
{
return Object.GetUnmanagedPtr(value.Asset);
}
/// <summary>
/// Implicit cast operator.
/// </summary>
public static implicit operator JsonAssetReference<T>(JsonAsset value)
{
return new JsonAssetReference<T>(value);
}
/// <summary>
/// Implicit cast operator.
/// </summary>
public static implicit operator JsonAssetReference<T>(IntPtr valuePtr)
{
return new JsonAssetReference<T>(Object.FromUnmanagedPtr(valuePtr) as JsonAsset);
}
/// <summary>
/// Checks if the object exists (reference is not null and the unmanaged object pointer is valid).
/// </summary>
/// <param name="obj">The object to check.</param>
/// <returns>True if object is valid, otherwise false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator bool(JsonAssetReference<T> obj)
{
return obj.Asset;
}
/// <summary>
/// Checks whether the two objects are equal.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(JsonAssetReference<T> left, JsonAssetReference<T> right)
{
return left.Asset == right.Asset;
}
/// <summary>
/// Checks whether the two objects are not equal.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(JsonAssetReference<T> left, JsonAssetReference<T> right)
{
return left.Asset != right.Asset;
}
/// <inheritdoc />
public bool Equals(JsonAssetReference<T> other)
{
return Asset == other.Asset;
}
/// <inheritdoc />
public int CompareTo(JsonAssetReference<T> other)
{
return Object.GetUnmanagedPtr(Asset).CompareTo(Object.GetUnmanagedPtr(other.Asset));
}
/// <inheritdoc />
public override bool Equals(object obj)
{
return obj is JsonAssetReference<T> other && Asset == other.Asset;
}
/// <inheritdoc />
public override string ToString()
{
return Asset?.ToString();
}
/// <inheritdoc />
public int CompareTo(object obj)
{
return obj is JsonAssetReference<T> other ? CompareTo(other) : 1;
}
/// <inheritdoc />
public override int GetHashCode()
{
return (Asset != null ? Asset.GetHashCode() : 0);
}
}
}

View File

@@ -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"
/// <summary>
/// Json asset reference utility. References resource with a typed data type.
/// </summary>
/// <typeparam name="T">Type of the asset instance type.</typeparam>
template<typename T>
API_STRUCT(NoDefault, Template, MarshalAs=JsonAsset*) struct JsonAssetReference : AssetReference<JsonAsset>
{
JsonAssetReference() = default;
JsonAssetReference(JsonAsset* asset)
{
OnSet(asset);
}
/// <summary>
/// Gets the deserialized native object instance of the given type. Returns null if asset is not loaded or loaded object has different type.
/// </summary>
/// <returns>The asset instance object or null.</returns>
FORCE_INLINE T* GetInstance() const
{
return _asset ? Get()->template GetInstance<T>() : nullptr;
}
JsonAssetReference& operator=(JsonAsset* asset) noexcept
{
OnSet(asset);
return *this;
}
operator JsonAsset*() const
{
return Get();
}
};

View File

@@ -234,7 +234,6 @@ bool AssetsImportingManager::Create(const String& tag, const StringView& outputP
LOG(Warning, "Cannot find asset creator object for tag \'{0}\'.", tag); LOG(Warning, "Cannot find asset creator object for tag \'{0}\'.", tag);
return true; return true;
} }
return Create(creator->Callback, outputPath, assetId, arg); return Create(creator->Callback, outputPath, assetId, arg);
} }

View File

@@ -113,7 +113,7 @@ private:
/// <summary> /// <summary>
/// Asset importer entry /// Asset importer entry
/// </summary> /// </summary>
struct AssetImporter struct FLAXENGINE_API AssetImporter
{ {
public: public:
/// <summary> /// <summary>
@@ -135,7 +135,7 @@ public:
/// <summary> /// <summary>
/// Asset creator entry /// Asset creator entry
/// </summary> /// </summary>
struct AssetCreator struct FLAXENGINE_API AssetCreator
{ {
public: public:
/// <summary> /// <summary>

View File

@@ -92,6 +92,21 @@ namespace FlaxEngine
/// </summary> /// </summary>
public float MaxColorComponent => Mathf.Max(Mathf.Max(R, G), B); public float MaxColorComponent => Mathf.Max(Mathf.Max(R, G), B);
/// <summary>
/// Gets a minimum component value (max of r,g,b,a).
/// </summary>
public float MinValue => Math.Min(R, Math.Min(G, Math.Min(B, A)));
/// <summary>
/// Gets a maximum component value (min of r,g,b,a).
/// </summary>
public float MaxValue => Math.Max(R, Math.Max(G, Math.Max(B, A)));
/// <summary>
/// Gets a sum of the component values.
/// </summary>
public float ValuesSum => R + G + B + A;
/// <summary> /// <summary>
/// Constructs a new Color with given r,g,b,a component. /// Constructs a new Color with given r,g,b,a component.
/// </summary> /// </summary>

View File

@@ -1940,15 +1940,15 @@ void DebugDraw::DrawWireArc(const Vector3& position, const Quaternion& orientati
DrawLine(prevPos, world.GetTranslation(), color, duration, depthTest); 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 direction, up, right;
Float3::Transform(Float3::Forward, orientation, direction); Float3::Transform(Float3::Forward, orientation, direction);
Float3::Transform(Float3::Up, orientation, up); Float3::Transform(Float3::Up, orientation, up);
Float3::Transform(Float3::Right, orientation, right); Float3::Transform(Float3::Right, orientation, right);
const Vector3 end = position + direction * (100.0f * scale); const Vector3 end = position + direction * (100.0f * scale);
const Vector3 capEnd = position + direction * (70.0f * scale); const Vector3 capEnd = end - (direction * (100 * Math::Min(capScale, scale * 0.5f)));
const float arrowSidesRatio = scale * 30.0f; const float arrowSidesRatio = Math::Min(capScale, scale * 0.5f) * 30.0f;
DrawLine(position, end, color, duration, depthTest); DrawLine(position, end, color, duration, depthTest);
DrawLine(end, capEnd + up * arrowSidesRatio, color, duration, depthTest); DrawLine(end, capEnd + up * arrowSidesRatio, color, duration, depthTest);

View File

@@ -218,10 +218,11 @@ namespace FlaxEngine
/// <param name="position">The arrow origin position.</param> /// <param name="position">The arrow origin position.</param>
/// <param name="orientation">The orientation (defines the arrow direction).</param> /// <param name="orientation">The orientation (defines the arrow direction).</param>
/// <param name="scale">The arrow scale (used to adjust the arrow size).</param> /// <param name="scale">The arrow scale (used to adjust the arrow size).</param>
/// <param name="capScale">The arrow cap scale.</param>
/// <param name="color">The color.</param> /// <param name="color">The color.</param>
/// <param name="duration">The duration (in seconds). Use 0 to draw it only once.</param> /// <param name="duration">The duration (in seconds). Use 0 to draw it only once.</param>
/// <param name="depthTest">If set to <c>true</c> depth test will be performed, otherwise depth will be ignored.</param> /// <param name="depthTest">If set to <c>true</c> depth test will be performed, otherwise depth will be ignored.</param>
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)
{ {
} }

View File

@@ -570,10 +570,11 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
/// <param name="position">The arrow origin position.</param> /// <param name="position">The arrow origin position.</param>
/// <param name="orientation">The orientation (defines the arrow direction).</param> /// <param name="orientation">The orientation (defines the arrow direction).</param>
/// <param name="scale">The arrow scale (used to adjust the arrow size).</param> /// <param name="scale">The arrow scale (used to adjust the arrow size).</param>
/// <param name="capScale">The arrow cap scale.</param>
/// <param name="color">The color.</param> /// <param name="color">The color.</param>
/// <param name="duration">The duration (in seconds). Use 0 to draw it only once.</param> /// <param name="duration">The duration (in seconds). Use 0 to draw it only once.</param>
/// <param name="depthTest">If set to <c>true</c> depth test will be performed, otherwise depth will be ignored.</param> /// <param name="depthTest">If set to <c>true</c> depth test will be performed, otherwise depth will be ignored.</param>
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);
/// <summary> /// <summary>
/// Draws the box. /// 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_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_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_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) #define DEBUG_DRAW_TEXT(text, position, color, size, duration) DebugDraw::DrawText(text, position, color, size, duration)
#else #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_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_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_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) #define DEBUG_DRAW_TEXT(text, position, color, size, duration)
#endif #endif

View File

@@ -214,9 +214,6 @@ int32 Engine::Main(const Char* cmdLine)
Time::OnEndDraw(); Time::OnEndDraw();
FrameMark; FrameMark;
} }
// Collect physics simulation results (does nothing if Simulate hasn't been called in the previous loop step)
Physics::CollectResults();
} }
// Call on exit event // Call on exit event
@@ -288,6 +285,9 @@ void Engine::OnLateFixedUpdate()
// Update services // Update services
EngineService::OnLateFixedUpdate(); EngineService::OnLateFixedUpdate();
// Collect physics simulation results (does nothing if Simulate hasn't been called in the previous loop step)
Physics::CollectResults();
} }
void Engine::OnUpdate() void Engine::OnUpdate()

View File

@@ -249,7 +249,7 @@ namespace FlaxEngine.Interop
/// <param name="src">The input array.</param> /// <param name="src">The input array.</param>
/// <param name="convertFunc">Converter callback.</param> /// <param name="convertFunc">Converter callback.</param>
/// <returns>The output array.</returns> /// <returns>The output array.</returns>
public static TDst[] ConvertArray<TSrc, TDst>(Span<TSrc> src, Func<TSrc, TDst> convertFunc) public static TDst[] ConvertArray<TSrc, TDst>(this Span<TSrc> src, Func<TSrc, TDst> convertFunc)
{ {
TDst[] dst = new TDst[src.Length]; TDst[] dst = new TDst[src.Length];
for (int i = 0; i < src.Length; i++) for (int i = 0; i < src.Length; i++)
@@ -265,7 +265,7 @@ namespace FlaxEngine.Interop
/// <param name="src">The input array.</param> /// <param name="src">The input array.</param>
/// <param name="convertFunc">Converter callback.</param> /// <param name="convertFunc">Converter callback.</param>
/// <returns>The output array.</returns> /// <returns>The output array.</returns>
public static TDst[] ConvertArray<TSrc, TDst>(TSrc[] src, Func<TSrc, TDst> convertFunc) public static TDst[] ConvertArray<TSrc, TDst>(this TSrc[] src, Func<TSrc, TDst> convertFunc)
{ {
TDst[] dst = new TDst[src.Length]; TDst[] dst = new TDst[src.Length];
for (int i = 0; i < src.Length; i++) for (int i = 0; i < src.Length; i++)

View File

@@ -12,6 +12,7 @@
#include "Engine/Debug/Exceptions/InvalidOperationException.h" #include "Engine/Debug/Exceptions/InvalidOperationException.h"
#include "Engine/Debug/Exceptions/ArgumentNullException.h" #include "Engine/Debug/Exceptions/ArgumentNullException.h"
#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h" #include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/Enums.h" #include "Engine/Scripting/Enums.h"
#include "Engine/Threading/ThreadPoolTask.h" #include "Engine/Threading/ThreadPoolTask.h"
#include "Engine/Threading/Threading.h" #include "Engine/Threading/Threading.h"
@@ -81,33 +82,10 @@ bool GPUBufferDescription::Equals(const GPUBufferDescription& other) const
String GPUBufferDescription::ToString() 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}"), return String::Format(TEXT("Size: {0}, Stride: {1}, Flags: {2}, Format: {3}, Usage: {4}"),
Size, Size,
Stride, Stride,
flags, ScriptingEnum::ToStringFlags(Flags),
ScriptingEnum::ToString(Format), ScriptingEnum::ToString(Format),
(int32)Usage); (int32)Usage);
} }
@@ -212,7 +190,7 @@ GPUBuffer* GPUBuffer::ToStagingUpload() const
bool GPUBuffer::Resize(uint32 newSize) bool GPUBuffer::Resize(uint32 newSize)
{ {
// Validate input PROFILE_CPU();
if (!IsAllocated()) if (!IsAllocated())
{ {
Log::InvalidOperationException(TEXT("Buffer.Resize")); 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."); LOG(Warning, "Cannot download GPU buffer data from an empty buffer.");
return true; return true;
} }
if (_desc.Usage == GPUResourceUsage::StagingReadback || _desc.Usage == GPUResourceUsage::Dynamic) if (_desc.Usage == GPUResourceUsage::StagingReadback || _desc.Usage == GPUResourceUsage::Dynamic)
{ {
// Use faster path for staging resources // Use faster path for staging resources
return GetData(result); return GetData(result);
} }
PROFILE_CPU();
// Ensure not running on main thread // Ensure not running on main thread
if (IsInMainThread()) if (IsInMainThread())
@@ -358,6 +336,7 @@ Task* GPUBuffer::DownloadDataAsync(BytesContainer& result)
bool GPUBuffer::GetData(BytesContainer& output) bool GPUBuffer::GetData(BytesContainer& output)
{ {
PROFILE_CPU();
void* mapped = Map(GPUResourceMapMode::Read); void* mapped = Map(GPUResourceMapMode::Read);
if (!mapped) if (!mapped)
return true; return true;
@@ -368,6 +347,7 @@ bool GPUBuffer::GetData(BytesContainer& output)
void GPUBuffer::SetData(const void* data, uint32 size) void GPUBuffer::SetData(const void* data, uint32 size)
{ {
PROFILE_CPU();
if (size == 0 || data == nullptr) if (size == 0 || data == nullptr)
{ {
Log::ArgumentNullException(TEXT("Buffer.SetData")); Log::ArgumentNullException(TEXT("Buffer.SetData"));

View File

@@ -15,6 +15,7 @@
#include "Engine/Graphics/GPULimits.h" #include "Engine/Graphics/GPULimits.h"
#include "Engine/Threading/ThreadPoolTask.h" #include "Engine/Threading/ThreadPoolTask.h"
#include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPUDevice.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/Enums.h" #include "Engine/Scripting/Enums.h"
namespace namespace
@@ -158,29 +159,6 @@ bool GPUTextureDescription::Equals(const GPUTextureDescription& other) const
String GPUTextureDescription::ToString() 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}"), return String::Format(TEXT("Size: {0}x{1}x{2}[{3}], Type: {4}, Mips: {5}, Format: {6}, MSAA: {7}, Flags: {8}, Usage: {9}"),
Width, Width,
Height, Height,
@@ -190,7 +168,7 @@ String GPUTextureDescription::ToString() const
MipLevels, MipLevels,
ScriptingEnum::ToString(Format), ScriptingEnum::ToString(Format),
::ToString(MultiSampleLevel), ::ToString(MultiSampleLevel),
flags, ScriptingEnum::ToStringFlags(Flags),
(int32)Usage); (int32)Usage);
} }
@@ -544,7 +522,7 @@ GPUTexture* GPUTexture::ToStagingUpload() const
bool GPUTexture::Resize(int32 width, int32 height, int32 depth, PixelFormat format) bool GPUTexture::Resize(int32 width, int32 height, int32 depth, PixelFormat format)
{ {
// Validate texture is created PROFILE_CPU();
if (!IsAllocated()) if (!IsAllocated())
{ {
LOG(Warning, "Cannot resize not created textures."); 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) GPUTask* GPUTexture::UploadMipMapAsync(const BytesContainer& data, int32 mipIndex, int32 rowPitch, int32 slicePitch, bool copyData)
{ {
PROFILE_CPU();
ASSERT(IsAllocated()); ASSERT(IsAllocated());
ASSERT(mipIndex < MipLevels() && data.IsValid()); ASSERT(mipIndex < MipLevels() && data.IsValid());
ASSERT(data.Length() >= slicePitch); ASSERT(data.Length() >= slicePitch);
@@ -699,6 +678,7 @@ bool GPUTexture::DownloadData(TextureData& result)
{ {
MISSING_CODE("support volume texture data downloading."); MISSING_CODE("support volume texture data downloading.");
} }
PROFILE_CPU();
// Use faster path for staging resources // Use faster path for staging resources
if (IsStaging()) if (IsStaging())
@@ -780,6 +760,7 @@ Task* GPUTexture::DownloadDataAsync(TextureData& result)
{ {
MISSING_CODE("support volume texture data downloading."); MISSING_CODE("support volume texture data downloading.");
} }
PROFILE_CPU();
// Use faster path for staging resources // Use faster path for staging resources
if (IsStaging()) if (IsStaging())

View File

@@ -386,5 +386,9 @@ namespace FlaxEngine
{ {
return $"{Name} ({GetType().Name})"; return $"{Name} ({GetType().Name})";
} }
#if FLAX_EDITOR
internal bool ShowTransform => !(this is UIControl);
#endif
} }
} }

View File

@@ -534,9 +534,7 @@ public:
/// <summary> /// <summary>
/// Gets actor direction vector (forward vector). /// Gets actor direction vector (forward vector).
/// </summary> /// </summary>
/// <returns>The result value.</returns> 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()); return Float3::Transform(Float3::Forward, GetOrientation());
} }
@@ -571,7 +569,7 @@ public:
/// <summary> /// <summary>
/// Gets local position of the actor in parent actor space. /// Gets local position of the actor in parent actor space.
/// </summary> /// </summary>
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 FORCE_INLINE Vector3 GetLocalPosition() const
{ {
return _localTransform.Translation; return _localTransform.Translation;
@@ -587,7 +585,7 @@ public:
/// Gets local rotation of the actor in parent actor space. /// Gets local rotation of the actor in parent actor space.
/// </summary> /// </summary>
/// <code>Actor.LocalOrientation *= Quaternion.Euler(0, 10 * Time.DeltaTime, 0)</code> /// <code>Actor.LocalOrientation *= Quaternion.Euler(0, 10 * Time.DeltaTime, 0)</code>
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 FORCE_INLINE Quaternion GetLocalOrientation() const
{ {
return _localTransform.Orientation; return _localTransform.Orientation;
@@ -602,7 +600,7 @@ public:
/// <summary> /// <summary>
/// Gets local scale vector of the actor in parent actor space. /// Gets local scale vector of the actor in parent actor space.
/// </summary> /// </summary>
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 FORCE_INLINE Float3 GetLocalScale() const
{ {
return _localTransform.Scale; return _localTransform.Scale;

View File

@@ -415,9 +415,9 @@ void Cloth::OnDebugDrawSelected()
c1 = Color::Lerp(Color::Red, Color::White, _paint[i1]); c1 = Color::Lerp(Color::Red, Color::White, _paint[i1]);
c2 = Color::Lerp(Color::Red, Color::White, _paint[i2]); c2 = Color::Lerp(Color::Red, Color::White, _paint[i2]);
} }
DebugDraw::DrawLine(v0, v1, c0, c1, 0, false); DebugDraw::DrawLine(v0, v1, c0, c1, 0, DebugDrawDepthTest);
DebugDraw::DrawLine(v1, v2, c1, c2, 0, false); DebugDraw::DrawLine(v1, v2, c1, c2, 0, DebugDrawDepthTest);
DebugDraw::DrawLine(v2, v0, c2, c0, 0, false); DebugDraw::DrawLine(v2, v0, c2, c0, 0, DebugDrawDepthTest);
} }
PhysicsBackend::UnlockClothParticles(_cloth); PhysicsBackend::UnlockClothParticles(_cloth);
} }

View File

@@ -332,6 +332,11 @@ public:
bool OnPreUpdate(); bool OnPreUpdate();
void OnPostUpdate(); void OnPostUpdate();
private:
#if USE_EDITOR
API_FIELD(Internal) bool DebugDrawDepthTest = true;
#endif
public: public:
// [Actor] // [Actor]
void Draw(RenderContext& renderContext) override; void Draw(RenderContext& renderContext) override;

View File

@@ -5,6 +5,7 @@
#include "Engine/Level/Actor.h" #include "Engine/Level/Actor.h"
#include "Engine/Physics/Collisions.h" #include "Engine/Physics/Collisions.h"
struct RayCastHit;
struct Collision; struct Collision;
/// <summary> /// <summary>
@@ -42,6 +43,40 @@ public:
/// <returns>The rigid body or null.</returns> /// <returns>The rigid body or null.</returns>
API_PROPERTY() virtual RigidBody* GetAttachedRigidBody() const = 0; API_PROPERTY() virtual RigidBody* GetAttachedRigidBody() const = 0;
/// <summary>
/// Performs a raycast against this collider shape.
/// </summary>
/// <param name="origin">The origin of the ray.</param>
/// <param name="direction">The normalized direction of the ray.</param>
/// <param name="resultHitDistance">The raycast result hit position distance from the ray origin. Valid only if raycast hits anything.</param>
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
/// <returns>True if ray hits an object, otherwise false.</returns>
API_FUNCTION(Sealed) virtual bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, float maxDistance = MAX_float) const = 0;
/// <summary>
/// Performs a raycast against this collider, returns results in a RaycastHit structure.
/// </summary>
/// <param name="origin">The origin of the ray.</param>
/// <param name="direction">The normalized direction of the ray.</param>
/// <param name="hitInfo">The result hit information. Valid only when method returns true.</param>
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
/// <returns>True if ray hits an object, otherwise false.</returns>
API_FUNCTION(Sealed) virtual bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float) const = 0;
/// <summary>
/// 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.
/// </summary>
/// <param name="point">The position to find the closest point to it.</param>
/// <param name="result">The result point on the collider that is closest to the specified location.</param>
API_FUNCTION(Sealed) virtual void ClosestPoint(const Vector3& point, API_PARAM(Out) Vector3& result) const = 0;
/// <summary>
/// Checks if a point is inside the collider.
/// </summary>
/// <param name="point">The point to check if is contained by the collider shape (in world-space).</param>
/// <returns>True if collider shape contains a given point, otherwise false.</returns>
API_FUNCTION(Sealed) virtual bool ContainsPoint(const Vector3& point) const = 0;
public: public:
/// <summary> /// <summary>
/// Called when a collision start gets registered for this collider (it collides with something). /// Called when a collision start gets registered for this collider (it collides with something).

View File

@@ -206,7 +206,7 @@ void CharacterController::CreateController()
_cachedScale = GetScale(); _cachedScale = GetScale();
const float scaling = _cachedScale.GetAbsolute().MaxValue(); const float scaling = _cachedScale.GetAbsolute().MaxValue();
const Vector3 position = _transform.LocalToWorld(_center); 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 // Setup
PhysicsBackend::SetControllerUpDirection(_controller, _upDirection); PhysicsBackend::SetControllerUpDirection(_controller, _upDirection);
@@ -280,12 +280,8 @@ void CharacterController::OnActiveTransformChanged()
// Change actor transform (but with locking) // Change actor transform (but with locking)
ASSERT(!_isUpdatingTransform); ASSERT(!_isUpdatingTransform);
_isUpdatingTransform = true; _isUpdatingTransform = true;
Transform transform; const Vector3 position = PhysicsBackend::GetControllerPosition(_controller) - _center;
PhysicsBackend::GetRigidActorPose(PhysicsBackend::GetShapeActor(_shape), transform.Translation, transform.Orientation); SetPosition(position);
transform.Translation -= _center;
transform.Orientation = _transform.Orientation;
transform.Scale = _transform.Scale;
SetTransform(transform);
_isUpdatingTransform = false; _isUpdatingTransform = false;
UpdateBounds(); UpdateBounds();

View File

@@ -201,7 +201,7 @@ void Collider::CreateShape()
// Create shape // Create shape
const bool isTrigger = _isTrigger && CanBeTrigger(); 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); PhysicsBackend::SetShapeContactOffset(_shape, _contactOffset);
UpdateLayerBits(); UpdateLayerBits();
} }
@@ -288,7 +288,7 @@ void Collider::OnMaterialChanged()
{ {
// Update the shape material // Update the shape material
if (_shape) if (_shape)
PhysicsBackend::SetShapeMaterial(_shape, Material.Get()); PhysicsBackend::SetShapeMaterial(_shape, Material);
} }
void Collider::BeginPlay(SceneBeginData* data) void Collider::BeginPlay(SceneBeginData* data)

View File

@@ -4,7 +4,7 @@
#include "Engine/Physics/Types.h" #include "Engine/Physics/Types.h"
#include "Engine/Content/JsonAsset.h" #include "Engine/Content/JsonAsset.h"
#include "Engine/Content/AssetReference.h" #include "Engine/Content/JsonAssetReference.h"
#include "Engine/Physics/Actors/PhysicsColliderActor.h" #include "Engine/Physics/Actors/PhysicsColliderActor.h"
struct RayCastHit; struct RayCastHit;
@@ -80,44 +80,10 @@ public:
/// <summary> /// <summary>
/// The physical material used to define the collider physical properties. /// The physical material used to define the collider physical properties.
/// </summary> /// </summary>
API_FIELD(Attributes="EditorOrder(2), DefaultValue(null), AssetReference(typeof(PhysicalMaterial), true), EditorDisplay(\"Collider\")") API_FIELD(Attributes="EditorOrder(2), DefaultValue(null), EditorDisplay(\"Collider\")")
AssetReference<JsonAsset> Material; JsonAssetReference<PhysicalMaterial> Material;
public: public:
/// <summary>
/// Performs a raycast against this collider shape.
/// </summary>
/// <param name="origin">The origin of the ray.</param>
/// <param name="direction">The normalized direction of the ray.</param>
/// <param name="resultHitDistance">The raycast result hit position distance from the ray origin. Valid only if raycast hits anything.</param>
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
/// <returns>True if ray hits an object, otherwise false.</returns>
API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, float maxDistance = MAX_float) const;
/// <summary>
/// Performs a raycast against this collider, returns results in a RaycastHit structure.
/// </summary>
/// <param name="origin">The origin of the ray.</param>
/// <param name="direction">The normalized direction of the ray.</param>
/// <param name="hitInfo">The result hit information. Valid only when method returns true.</param>
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
/// <returns>True if ray hits an object, otherwise false.</returns>
API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float) const;
/// <summary>
/// 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.
/// </summary>
/// <param name="point">The position to find the closest point to it.</param>
/// <param name="result">The result point on the collider that is closest to the specified location.</param>
API_FUNCTION() void ClosestPoint(const Vector3& point, API_PARAM(Out) Vector3& result) const;
/// <summary>
/// Checks if a point is inside the collider.
/// </summary>
/// <param name="point">The point to check if is contained by the collider shape (in world-space).</param>
/// <returns>True if collider shape contains a given point, otherwise false.</returns>
API_FUNCTION() bool ContainsPoint(const Vector3& point) const;
/// <summary> /// <summary>
/// Computes minimum translational distance between two geometry objects. /// 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. /// 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: public:
// [PhysicsColliderActor] // [PhysicsColliderActor]
RigidBody* GetAttachedRigidBody() const 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: protected:
// [PhysicsColliderActor] // [PhysicsColliderActor]

View File

@@ -58,9 +58,7 @@ API_STRUCT(NoDefault) struct FLAXENGINE_API Collision
/// <summary> /// <summary>
/// The total impulse applied to this contact pair to resolve the collision. /// The total impulse applied to this contact pair to resolve the collision.
/// </summary> /// </summary>
/// <remarks> /// <remarks>The total impulse is obtained by summing up impulses applied at all contact points in this collision pair.</remarks>
/// The total impulse is obtained by summing up impulses applied at all contact points in this collision pair.
/// </remarks>
API_FIELD() Vector3 Impulse; API_FIELD() Vector3 Impulse;
/// <summary> /// <summary>
@@ -87,9 +85,7 @@ public:
/// <summary> /// <summary>
/// Gets the relative linear velocity of the two colliding objects. /// Gets the relative linear velocity of the two colliding objects.
/// </summary> /// </summary>
/// <remarks> /// <remarks>Can be used to detect stronger collisions. </remarks>
/// Can be used to detect stronger collisions.
/// </remarks>
Vector3 GetRelativeVelocity() const Vector3 GetRelativeVelocity() const
{ {
return ThisVelocity - OtherVelocity; return ThisVelocity - OtherVelocity;

View File

@@ -159,7 +159,8 @@ void D6Joint::OnDebugDrawSelected()
const float twistSize = 9.0f; const float twistSize = 9.0f;
const Color swingColor = Color::Green.AlphaMultiplied(0.6f); const Color swingColor = Color::Green.AlphaMultiplied(0.6f);
const Color twistColor = Color::Yellow.AlphaMultiplied(0.5f); 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) if (_motion[(int32)D6JointAxis::SwingY] == D6JointMotion::Locked && _motion[(int32)D6JointAxis::SwingZ] == D6JointMotion::Locked)
{ {
// Swing is locked // Swing is locked

View File

@@ -63,8 +63,9 @@ void HingeJoint::OnDebugDrawSelected()
const Quaternion targetRotation = GetTargetOrientation() * xRotation; const Quaternion targetRotation = GetTargetOrientation() * xRotation;
const float size = 15.0f; const float size = 15.0f;
const Color color = Color::Green.AlphaMultiplied(0.6f); const Color color = Color::Green.AlphaMultiplied(0.6f);
DEBUG_DRAW_WIRE_ARROW(source, sourceRotation, size / 100.0f * 0.5f, Color::Red, 0, false); const float arrowSize = size / 100.0f * 0.5f;
DEBUG_DRAW_WIRE_ARROW(target, targetRotation, size / 100.0f * 0.5f, Color::Blue, 0, false); 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)) if (EnumHasAnyFlags(_flags, HingeJointFlag::Limit))
{ {
const float upper = Math::Max(_limit.Upper, _limit.Lower); const float upper = Math::Max(_limit.Upper, _limit.Lower);

View File

@@ -38,8 +38,9 @@ void SphericalJoint::OnDebugDrawSelected()
const Vector3 source = GetPosition(); const Vector3 source = GetPosition();
const Vector3 target = GetTargetPosition(); const Vector3 target = GetTargetPosition();
const float size = 15.0f; const float size = 15.0f;
const float arrowSize = size / 100.0f * 0.5f;
const Color color = Color::Green.AlphaMultiplied(0.6f); 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)) if (EnumHasAnyFlags(_flags, SphericalJointFlag::Limit))
{ {
DEBUG_DRAW_CONE(source, GetOrientation(), size, _limit.YLimitAngle * DegreesToRadians, _limit.ZLimitAngle * DegreesToRadians, color, 0, false); DEBUG_DRAW_CONE(source, GetOrientation(), size, _limit.YLimitAngle * DegreesToRadians, _limit.ZLimitAngle * DegreesToRadians, color, 0, false);

View File

@@ -228,9 +228,7 @@ class QueryFilterPhysX : public PxQueryFilterCallback
// Check mask // Check mask
const PxFilterData shapeFilter = shape->getQueryFilterData(); const PxFilterData shapeFilter = shape->getQueryFilterData();
if ((filterData.word0 & shapeFilter.word0) == 0) if ((filterData.word0 & shapeFilter.word0) == 0)
{
return PxQueryHitType::eNONE; return PxQueryHitType::eNONE;
}
// Check if skip triggers // Check if skip triggers
const bool hitTriggers = filterData.word2 != 0; 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; \ #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; \ PxQueryFilterData filterData; \
filterData.flags |= PxQueryFlag::ePREFILTER; \ filterData.flags |= PxQueryFlag::ePREFILTER; \
filterData.data.word0 = layerMask; \ filterData.data.word0 = layerMask; \
@@ -644,6 +644,19 @@ void GetShapeGeometry(const CollisionShape& shape, PxGeometryHolder& geometry)
} }
} }
void GetShapeMaterials(Array<PxMaterial*, InlinedAllocation<1>>& materialsPhysX, Span<JsonAsset*> 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( PxFilterFlags FilterShader(
PxFilterObjectAttributes attributes0, PxFilterData filterData0, PxFilterObjectAttributes attributes0, PxFilterData filterData0,
PxFilterObjectAttributes attributes1, PxFilterData filterData1, PxFilterObjectAttributes attributes1, PxFilterData filterData1,
@@ -1735,8 +1748,6 @@ void PhysicsBackend::EndSimulateScene(void* scene)
{ {
PROFILE_CPU_NAMED("Physics.SendEvents"); PROFILE_CPU_NAMED("Physics.SendEvents");
scenePhysX->EventsCallback.CollectResults();
scenePhysX->EventsCallback.SendTriggerEvents(); scenePhysX->EventsCallback.SendTriggerEvents();
scenePhysX->EventsCallback.SendCollisionEvents(); scenePhysX->EventsCallback.SendCollisionEvents();
scenePhysX->EventsCallback.SendJointEvents(); scenePhysX->EventsCallback.SendJointEvents();
@@ -1880,14 +1891,14 @@ bool PhysicsBackend::RayCast(void* scene, const Vector3& origin, const Vector3&
{ {
SCENE_QUERY_SETUP(true); SCENE_QUERY_SETUP(true);
PxRaycastBuffer buffer; 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) bool PhysicsBackend::RayCast(void* scene, const Vector3& origin, const Vector3& direction, RayCastHit& hitInfo, const float maxDistance, uint32 layerMask, bool hitTriggers)
{ {
SCENE_QUERY_SETUP(true); SCENE_QUERY_SETUP(true);
PxRaycastBuffer buffer; 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; return false;
SCENE_QUERY_COLLECT_SINGLE(); SCENE_QUERY_COLLECT_SINGLE();
return true; return true;
@@ -1897,7 +1908,7 @@ bool PhysicsBackend::RayCastAll(void* scene, const Vector3& origin, const Vector
{ {
SCENE_QUERY_SETUP(false); SCENE_QUERY_SETUP(false);
DynamicHitBuffer<PxRaycastHit> buffer; DynamicHitBuffer<PxRaycastHit> 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; return false;
SCENE_QUERY_COLLECT_ALL(); SCENE_QUERY_COLLECT_ALL();
return true; return true;
@@ -1908,7 +1919,7 @@ bool PhysicsBackend::BoxCast(void* scene, const Vector3& center, const Vector3&
SCENE_QUERY_SETUP_SWEEP_1(); SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation));
const PxBoxGeometry geometry(C2P(halfExtents)); 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) 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(); SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation));
const PxBoxGeometry geometry(C2P(halfExtents)); 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; return false;
SCENE_QUERY_COLLECT_SINGLE(); SCENE_QUERY_COLLECT_SINGLE();
return true; return true;
@@ -1927,7 +1938,7 @@ bool PhysicsBackend::BoxCastAll(void* scene, const Vector3& center, const Vector
SCENE_QUERY_SETUP_SWEEP(); SCENE_QUERY_SETUP_SWEEP();
const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation));
const PxBoxGeometry geometry(C2P(halfExtents)); 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; return false;
SCENE_QUERY_COLLECT_ALL(); SCENE_QUERY_COLLECT_ALL();
return true; return true;
@@ -1938,7 +1949,7 @@ bool PhysicsBackend::SphereCast(void* scene, const Vector3& center, const float
SCENE_QUERY_SETUP_SWEEP_1(); SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center - scenePhysX->Origin)); const PxTransform pose(C2P(center - scenePhysX->Origin));
const PxSphereGeometry geometry(radius); 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) 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(); SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center - scenePhysX->Origin)); const PxTransform pose(C2P(center - scenePhysX->Origin));
const PxSphereGeometry geometry(radius); 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; return false;
SCENE_QUERY_COLLECT_SINGLE(); SCENE_QUERY_COLLECT_SINGLE();
return true; return true;
@@ -1957,7 +1968,7 @@ bool PhysicsBackend::SphereCastAll(void* scene, const Vector3& center, const flo
SCENE_QUERY_SETUP_SWEEP(); SCENE_QUERY_SETUP_SWEEP();
const PxTransform pose(C2P(center - scenePhysX->Origin)); const PxTransform pose(C2P(center - scenePhysX->Origin));
const PxSphereGeometry geometry(radius); 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; return false;
SCENE_QUERY_COLLECT_ALL(); SCENE_QUERY_COLLECT_ALL();
return true; return true;
@@ -1968,7 +1979,7 @@ bool PhysicsBackend::CapsuleCast(void* scene, const Vector3& center, const float
SCENE_QUERY_SETUP_SWEEP_1(); SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation));
const PxCapsuleGeometry geometry(radius, height * 0.5f); 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) 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(); SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation));
const PxCapsuleGeometry geometry(radius, height * 0.5f); 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; return false;
SCENE_QUERY_COLLECT_SINGLE(); SCENE_QUERY_COLLECT_SINGLE();
return true; return true;
@@ -1987,7 +1998,7 @@ bool PhysicsBackend::CapsuleCastAll(void* scene, const Vector3& center, const fl
SCENE_QUERY_SETUP_SWEEP(); SCENE_QUERY_SETUP_SWEEP();
const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation));
const PxCapsuleGeometry geometry(radius, height * 0.5f); 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; return false;
SCENE_QUERY_COLLECT_ALL(); SCENE_QUERY_COLLECT_ALL();
return true; return true;
@@ -1999,7 +2010,7 @@ bool PhysicsBackend::ConvexCast(void* scene, const Vector3& center, const Collis
SCENE_QUERY_SETUP_SWEEP_1(); SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation));
const PxConvexMeshGeometry geometry((PxConvexMesh*)convexMesh->GetConvex(), PxMeshScale(C2P(scale))); 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) 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(); SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation));
const PxConvexMeshGeometry geometry((PxConvexMesh*)convexMesh->GetConvex(), PxMeshScale(C2P(scale))); 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; return false;
SCENE_QUERY_COLLECT_SINGLE(); SCENE_QUERY_COLLECT_SINGLE();
return true; return true;
@@ -2020,7 +2031,7 @@ bool PhysicsBackend::ConvexCastAll(void* scene, const Vector3& center, const Col
SCENE_QUERY_SETUP_SWEEP(); SCENE_QUERY_SETUP_SWEEP();
const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation));
const PxConvexMeshGeometry geometry((PxConvexMesh*)convexMesh->GetConvex(), PxMeshScale(C2P(scale))); 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; return false;
SCENE_QUERY_COLLECT_ALL(); SCENE_QUERY_COLLECT_ALL();
return true; return true;
@@ -2451,17 +2462,14 @@ void PhysicsBackend::AddRigidDynamicActorTorque(void* actor, const Vector3& torq
actorPhysX->addTorque(C2P(torque), static_cast<PxForceMode::Enum>(mode)); actorPhysX->addTorque(C2P(torque), static_cast<PxForceMode::Enum>(mode));
} }
void* PhysicsBackend::CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, JsonAsset* material, bool enabled, bool trigger) void* PhysicsBackend::CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, Span<JsonAsset*> materials, bool enabled, bool trigger)
{ {
const PxShapeFlags shapeFlags = GetShapeFlags(trigger, enabled); const PxShapeFlags shapeFlags = GetShapeFlags(trigger, enabled);
PxMaterial* materialPhysX = DefaultMaterial; Array<PxMaterial*, InlinedAllocation<1>> materialsPhysX;
if (material && !material->WaitForLoaded() && material->Instance) GetShapeMaterials(materialsPhysX, materials);
{
materialPhysX = (PxMaterial*)((PhysicalMaterial*)material->Instance)->GetPhysicsMaterial();
}
PxGeometryHolder geometryPhysX; PxGeometryHolder geometryPhysX;
GetShapeGeometry(geometry, 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; shapePhysX->userData = collider;
#if PHYSX_DEBUG_NAMING #if PHYSX_DEBUG_NAMING
shapePhysX->setName("Shape"); shapePhysX->setName("Shape");
@@ -2551,15 +2559,12 @@ void PhysicsBackend::SetShapeContactOffset(void* shape, float value)
shapePhysX->setContactOffset(Math::Max(shapePhysX->getRestOffset() + ZeroTolerance, value)); shapePhysX->setContactOffset(Math::Max(shapePhysX->getRestOffset() + ZeroTolerance, value));
} }
void PhysicsBackend::SetShapeMaterial(void* shape, JsonAsset* material) void PhysicsBackend::SetShapeMaterials(void* shape, Span<JsonAsset*> materials)
{ {
auto shapePhysX = (PxShape*)shape; auto shapePhysX = (PxShape*)shape;
PxMaterial* materialPhysX = DefaultMaterial; Array<PxMaterial*, InlinedAllocation<1>> materialsPhysX;
if (material && !material->WaitForLoaded() && material->Instance) GetShapeMaterials(materialsPhysX, materials);
{ shapePhysX->setMaterials(materialsPhysX.Get(), materialsPhysX.Count());
materialPhysX = (PxMaterial*)((PhysicalMaterial*)material->Instance)->GetPhysicsMaterial();
}
shapePhysX->setMaterials(&materialPhysX, 1);
} }
void PhysicsBackend::SetShapeGeometry(void* shape, const CollisionShape& geometry) 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; auto shapePhysX = (PxShape*)shape;
const Vector3 sceneOrigin = SceneOrigins[shapePhysX->getActor() ? shapePhysX->getActor()->getScene() : nullptr]; const Vector3 sceneOrigin = SceneOrigins[shapePhysX->getActor() ? shapePhysX->getActor()->getScene() : nullptr];
const PxTransform trans(C2P(position - sceneOrigin), C2P(orientation)); const PxTransform trans(C2P(position - sceneOrigin), C2P(orientation));
const PxHitFlags hitFlags = (PxHitFlags)0;
PxRaycastHit hit; 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; resultHitDistance = hit.distance;
return true; return true;
@@ -2623,10 +2627,10 @@ bool PhysicsBackend::RayCastShape(void* shape, const Vector3& position, const Qu
auto shapePhysX = (PxShape*)shape; auto shapePhysX = (PxShape*)shape;
const Vector3 sceneOrigin = SceneOrigins[shapePhysX->getActor() ? shapePhysX->getActor()->getScene() : nullptr]; const Vector3 sceneOrigin = SceneOrigins[shapePhysX->getActor() ? shapePhysX->getActor()->getScene() : nullptr];
const PxTransform trans(C2P(position - sceneOrigin), C2P(orientation)); const PxTransform trans(C2P(position - sceneOrigin), C2P(orientation));
const PxHitFlags hitFlags = PxHitFlag::ePOSITION | PxHitFlag::eNORMAL | PxHitFlag::eFACE_INDEX | PxHitFlag::eUV;
PxRaycastHit hit; 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; return false;
hit.shape = shapePhysX;
P2C(hit, hitInfo); P2C(hit, hitInfo);
hitInfo.Point += sceneOrigin; hitInfo.Point += sceneOrigin;
return true; return true;
@@ -3004,7 +3008,7 @@ void* PhysicsBackend::CreateController(void* scene, IPhysicsActor* actor, Physic
desc.material = DefaultMaterial; desc.material = DefaultMaterial;
const float minSize = 0.001f; const float minSize = 0.001f;
desc.height = Math::Max(height, minSize); 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); desc.stepOffset = Math::Min(stepOffset, desc.height + desc.radius * 2.0f - minSize);
auto controllerPhysX = (PxCapsuleController*)scenePhysX->ControllerManager->createController(desc); auto controllerPhysX = (PxCapsuleController*)scenePhysX->ControllerManager->createController(desc);
PxRigidActor* actorPhysX = controllerPhysX->getActor(); PxRigidActor* actorPhysX = controllerPhysX->getActor();
@@ -4081,10 +4085,17 @@ void PhysicsBackend::GetHeightFieldSize(void* heightField, int32& rows, int32& c
columns = (int32)heightFieldPhysX->getNbColumns(); 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; 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) bool PhysicsBackend::ModifyHeightField(void* heightField, int32 startCol, int32 startRow, int32 cols, int32 rows, const HeightFieldSample* data)

View File

@@ -38,10 +38,6 @@ void SimulationEventCallback::Clear()
BrokenJoints.Clear(); BrokenJoints.Clear();
} }
void SimulationEventCallback::CollectResults()
{
}
void SimulationEventCallback::SendCollisionEvents() void SimulationEventCallback::SendCollisionEvents()
{ {
for (auto& c : RemovedCollisions) 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 PxU32 flippedContacts = (pair.flags & PxContactPairFlag::eINTERNAL_CONTACTS_ARE_FLIPPED);
const bool hasImpulses = pair.flags.isSet(PxContactPairFlag::eINTERNAL_HAS_IMPULSES); const bool hasImpulses = pair.flags.isSet(PxContactPairFlag::eINTERNAL_HAS_IMPULSES);
const bool hasPostVelocities = !pair.flags.isSet(PxContactPairFlag::eACTOR_PAIR_LOST_TOUCH); const bool hasPostVelocities = !pair.flags.isSet(PxContactPairFlag::eACTOR_PAIR_LOST_TOUCH);
PxU32 nbContacts = 0;
PxVec3 totalImpulse(0.0f); PxVec3 totalImpulse(0.0f);
c.ThisActor = static_cast<PhysicsColliderActor*>(pair.shapes[0]->userData); c.ThisActor = static_cast<PhysicsColliderActor*>(pair.shapes[0]->userData);
@@ -144,48 +139,38 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c
} }
// Extract contact points // Extract contact points
c.ContactsCount = 0;
while (i.hasNextPatch()) while (i.hasNextPatch())
{ {
i.nextPatch(); i.nextPatch();
while (i.hasNextContact() && nbContacts < COLLISION_NAX_CONTACT_POINTS) while (i.hasNextContact() && c.ContactsCount < COLLISION_NAX_CONTACT_POINTS)
{ {
i.nextContact(); i.nextContact();
const PxVec3 point = i.getContactPoint(); const PxVec3 point = i.getContactPoint();
const PxVec3 normal = i.getContactNormal(); const PxVec3 normal = i.getContactNormal();
if (hasImpulses) if (hasImpulses)
totalImpulse += normal * impulses[nbContacts]; totalImpulse += normal * impulses[c.ContactsCount];
//PxU32 internalFaceIndex0 = flippedContacts ? iter.getFaceIndex1() : iter.getFaceIndex0(); ContactPoint& contact = c.Contacts[c.ContactsCount++];
//PxU32 internalFaceIndex1 = flippedContacts ? iter.getFaceIndex0() : iter.getFaceIndex1();
ContactPoint& contact = c.Contacts[nbContacts];
contact.Point = P2C(point); contact.Point = P2C(point);
contact.Normal = P2C(normal); contact.Normal = P2C(normal);
contact.Separation = i.getSeparation(); contact.Separation = i.getSeparation();
nbContacts++;
} }
} }
c.Impulse = P2C(totalImpulse);
// Extract velocities // Extract velocities
c.ThisVelocity = c.OtherVelocity = Vector3::Zero; c.ThisVelocity = c.OtherVelocity = Vector3::Zero;
if (hasPostVelocities && j.nextItemSet()) if (hasPostVelocities && j.nextItemSet())
{ {
ASSERT(j.contactPairIndex == pairIndex); ASSERT_LOW_LAYER(j.contactPairIndex == pairIndex);
if (j.postSolverVelocity) if (j.postSolverVelocity)
{ {
const PxVec3 linearVelocityActor0 = j.postSolverVelocity->linearVelocity[0]; c.ThisVelocity = P2C(j.postSolverVelocity->linearVelocity[0]);
const PxVec3 linearVelocityActor1 = j.postSolverVelocity->linearVelocity[1]; c.OtherVelocity = P2C(j.postSolverVelocity->linearVelocity[1]);
c.ThisVelocity = P2C(linearVelocityActor0);
c.OtherVelocity = P2C(linearVelocityActor1);
} }
} }
c.ContactsCount = nbContacts;
c.Impulse = P2C(totalImpulse);
if (pair.flags & PxContactPairFlag::eACTOR_PAIR_HAS_FIRST_TOUCH) if (pair.flags & PxContactPairFlag::eACTOR_PAIR_HAS_FIRST_TOUCH)
{ {
NewCollisions.Add(c); NewCollisions.Add(c);

View File

@@ -49,11 +49,6 @@ public:
/// </summary> /// </summary>
void Clear(); void Clear();
/// <summary>
/// Generates the new/old/removed collisions and a valid trigger pairs.
/// </summary>
void CollectResults();
/// <summary> /// <summary>
/// Sends the collision events to the managed objects. /// Sends the collision events to the managed objects.
/// </summary> /// </summary>

View File

@@ -17,6 +17,7 @@
#include <ThirdParty/PhysX/foundation/PxBounds3.h> #include <ThirdParty/PhysX/foundation/PxBounds3.h>
#include <ThirdParty/PhysX/characterkinematic/PxExtended.h> #include <ThirdParty/PhysX/characterkinematic/PxExtended.h>
#include <ThirdParty/PhysX/PxShape.h> #include <ThirdParty/PhysX/PxShape.h>
#include <ThirdParty/PhysX/PxMaterial.h>
#include <ThirdParty/PhysX/PxQueryReport.h> #include <ThirdParty/PhysX/PxQueryReport.h>
namespace physx namespace physx
@@ -233,12 +234,28 @@ inline float RadPerSToRpm(float v)
return v * (30.0f / PI); 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) inline void P2C(const PxRaycastHit& hit, RayCastHit& result)
{ {
result.Point = P2C(hit.position); result.Point = P2C(hit.position);
result.Normal = P2C(hit.normal); result.Normal = P2C(hit.normal);
result.Distance = hit.distance; result.Distance = hit.distance;
result.Collider = hit.shape ? static_cast<PhysicsColliderActor*>(hit.shape->userData) : nullptr; result.Collider = hit.shape ? static_cast<PhysicsColliderActor*>(hit.shape->userData) : nullptr;
result.Material = hit.shape ? GetMaterial(hit.shape, hit.faceIndex) : nullptr;
result.FaceIndex = hit.faceIndex; result.FaceIndex = hit.faceIndex;
result.UV.X = hit.u; result.UV.X = hit.u;
result.UV.Y = hit.v; result.UV.Y = hit.v;
@@ -250,6 +267,7 @@ inline void P2C(const PxSweepHit& hit, RayCastHit& result)
result.Normal = P2C(hit.normal); result.Normal = P2C(hit.normal);
result.Distance = hit.distance; result.Distance = hit.distance;
result.Collider = hit.shape ? static_cast<PhysicsColliderActor*>(hit.shape->userData) : nullptr; result.Collider = hit.shape ? static_cast<PhysicsColliderActor*>(hit.shape->userData) : nullptr;
result.Material = hit.shape ? GetMaterial(hit.shape, hit.faceIndex) : nullptr;
result.FaceIndex = hit.faceIndex; result.FaceIndex = hit.faceIndex;
result.UV = Vector2::Zero; result.UV = Vector2::Zero;
} }

View File

@@ -4,27 +4,21 @@
#include "Types.h" #include "Types.h"
#include "Engine/Core/ISerializable.h" #include "Engine/Core/ISerializable.h"
#include "Engine/Scripting/ScriptingObject.h"
#include "Engine/Level/Tags.h" #include "Engine/Level/Tags.h"
/// <summary> /// <summary>
/// Physical materials are used to define the response of a physical object when interacting dynamically with the world. /// Physical materials are used to define the response of a physical object when interacting dynamically with the world.
/// </summary> /// </summary>
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(); API_AUTO_SERIALIZATION();
DECLARE_SCRIPTING_TYPE_MINIMAL(PhysicalMaterial); DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(PhysicalMaterial, ScriptingObject);
private: private:
void* _material; void* _material = nullptr;
public: public:
/// <summary>
/// Initializes a new instance of the <see cref="PhysicalMaterial"/> class.
/// </summary>
PhysicalMaterial();
/// <summary>
/// Finalizes an instance of the <see cref="PhysicalMaterial"/> class.
/// </summary>
~PhysicalMaterial(); ~PhysicalMaterial();
public: public:

View File

@@ -78,11 +78,6 @@ void PhysicsSettings::Deserialize(DeserializeStream& stream, ISerializeModifier*
} }
} }
PhysicalMaterial::PhysicalMaterial()
: _material(nullptr)
{
}
PhysicalMaterial::~PhysicalMaterial() PhysicalMaterial::~PhysicalMaterial()
{ {
if (_material) if (_material)

View File

@@ -4,6 +4,7 @@
#include "Physics.h" #include "Physics.h"
#include "PhysicsSettings.h" #include "PhysicsSettings.h"
#include "Engine/Core/Types/Span.h"
struct HingeJointDrive; struct HingeJointDrive;
struct SpringParameters; struct SpringParameters;
@@ -182,7 +183,7 @@ public:
static void AddRigidDynamicActorTorque(void* actor, const Vector3& torque, ForceMode mode); static void AddRigidDynamicActorTorque(void* actor, const Vector3& torque, ForceMode mode);
// Shapes // Shapes
static void* CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, JsonAsset* material, bool enabled, bool trigger); static void* CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, Span<JsonAsset*> materials, bool enabled, bool trigger);
static void SetShapeState(void* shape, bool enabled, bool trigger); static void SetShapeState(void* shape, bool enabled, bool trigger);
static void SetShapeFilterMask(void* shape, uint32 mask0, uint32 mask1); static void SetShapeFilterMask(void* shape, uint32 mask0, uint32 mask1);
static void* GetShapeActor(void* shape); static void* GetShapeActor(void* shape);
@@ -191,7 +192,7 @@ public:
static void GetShapeLocalPose(void* shape, Vector3& position, Quaternion& orientation); static void GetShapeLocalPose(void* shape, Vector3& position, Quaternion& orientation);
static void SetShapeLocalPose(void* shape, const Vector3& position, const Quaternion& orientation); static void SetShapeLocalPose(void* shape, const Vector3& position, const Quaternion& orientation);
static void SetShapeContactOffset(void* shape, float value); static void SetShapeContactOffset(void* shape, float value);
static void SetShapeMaterial(void* shape, JsonAsset* material); static void SetShapeMaterials(void* shape, Span<JsonAsset*> materials);
static void SetShapeGeometry(void* shape, const CollisionShape& geometry); static void SetShapeGeometry(void* shape, const CollisionShape& geometry);
static void AttachShape(void* shape, void* actor); static void AttachShape(void* shape, void* actor);
static void DetachShape(void* shape, void* actor); static void DetachShape(void* shape, void* actor);
@@ -303,7 +304,8 @@ public:
static void GetTriangleMeshTriangles(void* triangleMesh, Array<Float3, HeapAllocation>& vertexBuffer, Array<int32, HeapAllocation>& indexBuffer); static void GetTriangleMeshTriangles(void* triangleMesh, Array<Float3, HeapAllocation>& vertexBuffer, Array<int32, HeapAllocation>& indexBuffer);
static const uint32* GetTriangleMeshRemap(void* triangleMesh, uint32& count); static const uint32* GetTriangleMeshRemap(void* triangleMesh, uint32& count);
static void GetHeightFieldSize(void* heightField, int32& rows, int32& columns); 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 bool ModifyHeightField(void* heightField, int32 startCol, int32 startRow, int32 cols, int32 rows, const HeightFieldSample* data);
static void FlushRequests(); static void FlushRequests();
static void FlushRequests(void* scene); static void FlushRequests(void* scene);
@@ -330,6 +332,14 @@ public:
flags = (RigidDynamicFlags)(((uint32)flags & ~(uint32)flag) | (value ? (uint32)flag : 0)); flags = (RigidDynamicFlags)(((uint32)flags & ~(uint32)flag) | (value ? (uint32)flag : 0));
SetRigidDynamicActorFlags(actor, flags); SetRigidDynamicActorFlags(actor, flags);
} }
FORCE_INLINE static void* CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, JsonAsset* material, bool enabled, bool trigger)
{
return CreateShape(collider, geometry, Span<JsonAsset*>(&material, 1), enabled, trigger);
}
FORCE_INLINE static void SetShapeMaterial(void* shape, JsonAsset* material)
{
SetShapeMaterials(shape, Span<JsonAsset*>(&material, 1));
}
}; };
DECLARE_ENUM_OPERATORS(PhysicsBackend::ActorFlags); DECLARE_ENUM_OPERATORS(PhysicsBackend::ActorFlags);

View File

@@ -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<JsonAsset*> materials, bool enabled, bool trigger)
{ {
return DUMY_HANDLE; 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<JsonAsset*> materials)
{ {
} }
@@ -826,11 +826,16 @@ void PhysicsBackend::GetHeightFieldSize(void* heightField, int32& rows, int32& c
columns = 0; columns = 0;
} }
float PhysicsBackend::GetHeightFieldHeight(void* heightField, float x, float z) float PhysicsBackend::GetHeightFieldHeight(void* heightField, int32 x, int32 z)
{ {
return 0.0f; 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) bool PhysicsBackend::ModifyHeightField(void* heightField, int32 startCol, int32 startRow, int32 cols, int32 rows, const HeightFieldSample* data)
{ {
return true; return true;

View File

@@ -10,6 +10,7 @@
struct PhysicsStatistics; struct PhysicsStatistics;
class PhysicsColliderActor; class PhysicsColliderActor;
class PhysicsScene; class PhysicsScene;
class PhysicalMaterial;
class Joint; class Joint;
class Collider; class Collider;
class CollisionData; class CollisionData;
@@ -132,7 +133,7 @@ DECLARE_ENUM_OPERATORS(RigidbodyConstraints);
/// <summary> /// <summary>
/// Raycast hit result data. /// Raycast hit result data.
/// </summary> /// </summary>
API_STRUCT() struct RayCastHit API_STRUCT(NoDefault) struct RayCastHit
{ {
DECLARE_SCRIPTING_TYPE_NO_SPAWN(RayCastHit); DECLARE_SCRIPTING_TYPE_NO_SPAWN(RayCastHit);
@@ -141,6 +142,11 @@ API_STRUCT() struct RayCastHit
/// </summary> /// </summary>
API_FIELD() PhysicsColliderActor* Collider = nullptr; API_FIELD() PhysicsColliderActor* Collider = nullptr;
/// <summary>
/// The physical material of the surface that was hit.
/// </summary>
API_FIELD() PhysicalMaterial* Material = nullptr;
/// <summary> /// <summary>
/// The normal of the surface the ray hit. /// The normal of the surface the ray hit.
/// </summary> /// </summary>
@@ -151,17 +157,17 @@ API_STRUCT() struct RayCastHit
/// </summary> /// </summary>
API_FIELD() float Distance; API_FIELD() float Distance;
/// <summary>
/// The point in the world space where ray hit the collider.
/// </summary>
API_FIELD() Vector3 Point;
/// <summary> /// <summary>
/// The index of the face that was hit. Valid only for convex mesh (polygon index), triangle mesh (triangle index) and height field (triangle index). /// The index of the face that was hit. Valid only for convex mesh (polygon index), triangle mesh (triangle index) and height field (triangle index).
/// </summary> /// </summary>
/// <seealso cref="CollisionData.GetModelTriangle" /> /// <seealso cref="CollisionData.GetModelTriangle" />
API_FIELD() uint32 FaceIndex; API_FIELD() uint32 FaceIndex;
/// <summary>
/// The point in the world space where ray hit the collider.
/// </summary>
API_FIELD() Vector3 Point;
/// <summary> /// <summary>
/// The barycentric coordinates of hit triangle. Valid only for triangle mesh and height field. /// The barycentric coordinates of hit triangle. Valid only for triangle mesh and height field.
/// </summary> /// </summary>

View File

@@ -61,6 +61,16 @@ namespace FlaxEngine
/// </summary> /// </summary>
public float Spacing; public float Spacing;
/// <summary>
/// The minimum size of the collection.
/// </summary>
public int MinCount;
/// <summary>
/// The maximum size of the collection. Zero if unlimited.
/// </summary>
public int MaxCount;
/// <summary> /// <summary>
/// The collection background color. /// The collection background color.
/// </summary> /// </summary>

View File

@@ -68,4 +68,30 @@ public:
{ {
return FromString<EnumType>(StringAnsi(name)); return FromString<EnumType>(StringAnsi(name));
} }
// Gets the name of the enum value as separated flags
template<class EnumType>
static String ToStringFlags(EnumType value, Char separator = '|')
{
String result;
if (const auto items = GetItems<EnumType>())
{
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<EnumType>(value, (EnumType)itemValue))
{
if (result.HasChars())
result += separator;
result += items[i].Name;
}
}
}
return result;
}
}; };

View File

@@ -84,7 +84,7 @@ bool cacheStaticGeometryTree(Actor* actor, ShadowsOfMordor::Builder::SceneBuildC
{ {
auto patch = terrain->GetPatch(patchIndex); auto patch = terrain->GetPatch(patchIndex);
entry.AsTerrain.PatchIndex = 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]; auto chunk = patch->Chunks[chunkIndex];
entry.AsTerrain.ChunkIndex = chunkIndex; entry.AsTerrain.ChunkIndex = chunkIndex;

View File

@@ -165,7 +165,7 @@ void ShadowsOfMordor::Builder::onJobRender(GPUContext* context)
Matrix::Transpose(world, shaderData.WorldMatrix); Matrix::Transpose(world, shaderData.WorldMatrix);
shaderData.LightmapArea = chunk->Lightmap.UVsArea; shaderData.LightmapArea = chunk->Lightmap.UVsArea;
shaderData.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize; shaderData.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize;
chunk->GetHeightmapUVScaleBias(&shaderData.HeightmapUVScaleBias); shaderData.HeightmapUVScaleBias = chunk->GetHeightmapUVScaleBias();
// Extract per axis scales from LocalToWorld transform // Extract per axis scales from LocalToWorld transform
const float scaleX = Float3(world.M11, world.M12, world.M13).Length(); const float scaleX = Float3(world.M11, world.M12, world.M13).Length();

View File

@@ -29,7 +29,7 @@ Terrain::Terrain(const SpawnParams& params)
, _cachedScale(1.0f) , _cachedScale(1.0f)
{ {
_drawCategory = SceneRendering::SceneDrawAsync; _drawCategory = SceneRendering::SceneDrawAsync;
PhysicalMaterial.Changed.Bind<Terrain, &Terrain::OnPhysicalMaterialChanged>(this); _physicalMaterials.Resize(8);
} }
Terrain::~Terrain() Terrain::~Terrain()
@@ -59,7 +59,7 @@ void Terrain::CacheNeighbors()
for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++) for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++)
{ {
const auto patch = _patches[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(); patch->Chunks[chunkIndex].CacheNeighbors();
} }
@@ -185,7 +185,7 @@ bool Terrain::RayCast(const Vector3& origin, const Vector3& direction, RayCastHi
return result; return result;
} }
void Terrain::ClosestPoint(const Vector3& position, Vector3& result) const void Terrain::ClosestPoint(const Vector3& point, Vector3& result) const
{ {
Real minDistance = MAX_Real; Real minDistance = MAX_Real;
Vector3 tmp; Vector3 tmp;
@@ -194,8 +194,8 @@ void Terrain::ClosestPoint(const Vector3& position, Vector3& result) const
const auto patch = _patches[pathIndex]; const auto patch = _patches[pathIndex];
if (patch->HasCollision()) if (patch->HasCollision())
{ {
patch->ClosestPoint(position, tmp); patch->ClosestPoint(point, tmp);
const auto distance = Vector3::DistanceSquared(position, tmp); const auto distance = Vector3::DistanceSquared(point, tmp);
if (distance < minDistance) if (distance < minDistance)
{ {
minDistance = distance; 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 void Terrain::DrawPatch(const RenderContext& renderContext, const Int2& patchCoord, MaterialBase* material, int32 lodIndex) const
{ {
auto patch = GetPatch(patchCoord); auto patch = GetPatch(patchCoord);
if (patch) 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); 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 #if TERRAIN_USE_PHYSICS_DEBUG
void Terrain::DrawPhysicsDebug(RenderView& view) void Terrain::DrawPhysicsDebug(RenderView& view)
@@ -295,6 +284,21 @@ void Terrain::SetCollisionLOD(int32 value)
#endif #endif
} }
void Terrain::SetPhysicalMaterials(const Array<JsonAssetReference<PhysicalMaterial>, 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 TerrainPatch* Terrain::GetPatch(const Int2& patchCoord) const
{ {
return GetPatch(patchCoord.X, patchCoord.Y); return GetPatch(patchCoord.X, patchCoord.Y);
@@ -540,7 +544,7 @@ void Terrain::Draw(RenderContext& renderContext)
Matrix localToWorld, worldToLocal; Matrix localToWorld, worldToLocal;
BoundingSphere chunkSphere; BoundingSphere chunkSphere;
BoundingBox localBounds; BoundingBox localBounds;
for (int32 chunkIndex = 0; chunkIndex < TerrainPatch::CHUNKS_COUNT; chunkIndex++) for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++)
{ {
TerrainChunk* chunk = &patch->Chunks[chunkIndex]; TerrainChunk* chunk = &patch->Chunks[chunkIndex];
chunk->GetTransform().GetWorld(localToWorld); // TODO: large-worlds chunk->GetTransform().GetWorld(localToWorld); // TODO: large-worlds
@@ -570,7 +574,7 @@ void Terrain::Draw(RenderContext& renderContext)
continue; continue;
// Frustum vs Box culling for chunks // 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]; auto chunk = &patch->Chunks[chunkIndex];
chunk->_cachedDrawLOD = 0; chunk->_cachedDrawLOD = 0;
@@ -588,7 +592,7 @@ void Terrain::Draw(RenderContext& renderContext)
else else
{ {
// Reset cached LOD for chunks (prevent LOD transition from invisible chunks) // 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]; auto chunk = &patch->Chunks[chunkIndex];
chunk->_cachedDrawLOD = 0; chunk->_cachedDrawLOD = 0;
@@ -616,10 +620,10 @@ void Terrain::OnDebugDrawSelected()
for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++) for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++)
{ {
const auto patch = _patches[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]; 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(ScaleInLightmap, _scaleInLightmap);
SERIALIZE_MEMBER(BoundsExtent, _boundsExtent); SERIALIZE_MEMBER(BoundsExtent, _boundsExtent);
SERIALIZE_MEMBER(CollisionLOD, _collisionLod); SERIALIZE_MEMBER(CollisionLOD, _collisionLod);
SERIALIZE_MEMBER(PhysicalMaterials, _physicalMaterials);
SERIALIZE(Material); SERIALIZE(Material);
SERIALIZE(PhysicalMaterial);
SERIALIZE(DrawModes); SERIALIZE(DrawModes);
SERIALIZE_MEMBER(LODCount, _lodCount); SERIALIZE_MEMBER(LODCount, _lodCount);
@@ -714,8 +718,8 @@ void Terrain::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie
DESERIALIZE_MEMBER(LODDistribution, _lodDistribution); DESERIALIZE_MEMBER(LODDistribution, _lodDistribution);
DESERIALIZE_MEMBER(ScaleInLightmap, _scaleInLightmap); DESERIALIZE_MEMBER(ScaleInLightmap, _scaleInLightmap);
DESERIALIZE_MEMBER(BoundsExtent, _boundsExtent); DESERIALIZE_MEMBER(BoundsExtent, _boundsExtent);
DESERIALIZE_MEMBER(PhysicalMaterials, _physicalMaterials);
DESERIALIZE(Material); DESERIALIZE(Material);
DESERIALIZE(PhysicalMaterial);
DESERIALIZE(DrawModes); DESERIALIZE(DrawModes);
member = stream.FindMember("LODCount"); 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] // [Deprecated on 27.04.2022, expires on 27.04.2024]
if (modifier->EngineBuild <= 6331) if (modifier->EngineBuild <= 6331)
DrawModes |= DrawPass::GlobalSurfaceAtlas; DrawModes |= DrawPass::GlobalSurfaceAtlas;
// [Deprecated on 15.02.2024, expires on 15.02.2026]
JsonAssetReference<PhysicalMaterial> PhysicalMaterial;
DESERIALIZE(PhysicalMaterial);
if (PhysicalMaterial)
{
for (auto& e : _physicalMaterials)
e = PhysicalMaterial;
}
} }
RigidBody* Terrain::GetAttachedRigidBody() const RigidBody* Terrain::GetAttachedRigidBody() const

View File

@@ -2,7 +2,7 @@
#pragma once #pragma once
#include "Engine/Content/JsonAsset.h" #include "Engine/Content/JsonAssetReference.h"
#include "Engine/Content/Assets/MaterialBase.h" #include "Engine/Content/Assets/MaterialBase.h"
#include "Engine/Physics/Actors/PhysicsColliderActor.h" #include "Engine/Physics/Actors/PhysicsColliderActor.h"
@@ -10,6 +10,7 @@ class Terrain;
class TerrainChunk; class TerrainChunk;
class TerrainPatch; class TerrainPatch;
class TerrainManager; class TerrainManager;
class PhysicalMaterial;
struct RayCastHit; struct RayCastHit;
struct RenderView; struct RenderView;
@@ -23,10 +24,7 @@ struct RenderView;
#define TERRAIN_EDITING 1 #define TERRAIN_EDITING 1
// Enable/disable terrain heightmap samples modification and gather. Used by the editor to modify the terrain with the brushes. // Enable/disable terrain heightmap samples modification and gather. Used by the editor to modify the terrain with the brushes.
#define TERRAIN_UPDATING (USE_EDITOR) #define TERRAIN_UPDATING 1
// 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)
// Enable/disable terrain physics collision drawing // Enable/disable terrain physics collision drawing
#define TERRAIN_USE_PHYSICS_DEBUG (USE_EDITOR && 1) #define TERRAIN_USE_PHYSICS_DEBUG (USE_EDITOR && 1)
@@ -41,13 +39,28 @@ struct RenderView;
/// <seealso cref="PhysicsColliderActor" /> /// <seealso cref="PhysicsColliderActor" />
API_CLASS(Sealed) class FLAXENGINE_API Terrain : public PhysicsColliderActor API_CLASS(Sealed) class FLAXENGINE_API Terrain : public PhysicsColliderActor
{ {
DECLARE_SCENE_OBJECT(Terrain); DECLARE_SCENE_OBJECT(Terrain);
friend Terrain; friend Terrain;
friend TerrainPatch; friend TerrainPatch;
friend TerrainChunk; friend TerrainChunk;
private: /// <summary>
/// Various defines regarding terrain configuration.
/// </summary>
API_ENUM() enum Config
{
/// <summary>
/// The maximum allowed amount of chunks per patch.
/// </summary>
ChunksCount = 16,
/// <summary>
/// The maximum allowed amount of chunks per chunk.
/// </summary>
ChunksCountEdge = 4,
};
private:
char _lodBias; char _lodBias;
char _forcedLod; char _forcedLod;
char _collisionLod; char _collisionLod;
@@ -60,28 +73,21 @@ private:
Float3 _cachedScale; Float3 _cachedScale;
Array<TerrainPatch*, InlinedAllocation<64>> _patches; Array<TerrainPatch*, InlinedAllocation<64>> _patches;
Array<TerrainChunk*> _drawChunks; Array<TerrainChunk*> _drawChunks;
Array<JsonAssetReference<PhysicalMaterial>, FixedAllocation<8>> _physicalMaterials;
public: public:
/// <summary> /// <summary>
/// Finalizes an instance of the <see cref="Terrain"/> class. /// Finalizes an instance of the <see cref="Terrain"/> class.
/// </summary> /// </summary>
~Terrain(); ~Terrain();
public: public:
/// <summary> /// <summary>
/// The default material used for terrain rendering (chunks can override this). /// The default material used for terrain rendering (chunks can override this).
/// </summary> /// </summary>
API_FIELD(Attributes="EditorOrder(100), DefaultValue(null), EditorDisplay(\"Terrain\")") API_FIELD(Attributes="EditorOrder(100), DefaultValue(null), EditorDisplay(\"Terrain\")")
AssetReference<MaterialBase> Material; AssetReference<MaterialBase> Material;
/// <summary>
/// The physical material used to define the terrain collider physical properties.
/// </summary>
API_FIELD(Attributes="EditorOrder(520), DefaultValue(null), Limit(-1, 100, 0.1f), EditorDisplay(\"Collision\"), AssetReference(typeof(PhysicalMaterial), true)")
AssetReference<JsonAsset> PhysicalMaterial;
/// <summary> /// <summary>
/// The draw passes to use for rendering this object. /// The draw passes to use for rendering this object.
/// </summary> /// </summary>
@@ -89,7 +95,6 @@ public:
DrawPass DrawModes = DrawPass::Default; DrawPass DrawModes = DrawPass::Default;
public: public:
/// <summary> /// <summary>
/// Gets the terrain Level Of Detail bias value. Allows to increase or decrease rendered terrain quality. /// Gets the terrain Level Of Detail bias value. Allows to increase or decrease rendered terrain quality.
/// </summary> /// </summary>
@@ -180,6 +185,21 @@ public:
/// </summary> /// </summary>
API_PROPERTY() void SetCollisionLOD(int32 value); API_PROPERTY() void SetCollisionLOD(int32 value);
/// <summary>
/// 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).
/// </summary>
API_PROPERTY(Attributes="EditorOrder(520), EditorDisplay(\"Collision\"), Collection(MinCount = 8, MaxCount = 8)")
FORCE_INLINE const Array<JsonAssetReference<PhysicalMaterial>, FixedAllocation<8>>& GetPhysicalMaterials() const
{
return _physicalMaterials;
}
/// <summary>
/// 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).
/// </summary>
API_PROPERTY()
void SetPhysicalMaterials(const Array<JsonAssetReference<PhysicalMaterial>, FixedAllocation<8>>& value);
/// <summary> /// <summary>
/// Gets the terrain Level Of Detail count. /// Gets the terrain Level Of Detail count.
/// </summary> /// </summary>
@@ -219,7 +239,7 @@ public:
/// </summary> /// </summary>
/// <param name="patchCoord">The patch location (x and z).</param> /// <param name="patchCoord">The patch location (x and z).</param>
/// <returns>The patch.</returns> /// <returns>The patch.</returns>
TerrainPatch* GetPatch(const Int2& patchCoord) const; API_FUNCTION() TerrainPatch* GetPatch(API_PARAM(Ref) const Int2& patchCoord) const;
/// <summary> /// <summary>
/// Gets the patch at the given location. /// Gets the patch at the given location.
@@ -227,7 +247,7 @@ public:
/// <param name="x">The patch location x.</param> /// <param name="x">The patch location x.</param>
/// <param name="z">The patch location z.</param> /// <param name="z">The patch location z.</param>
/// <returns>The patch.</returns> /// <returns>The patch.</returns>
TerrainPatch* GetPatch(int32 x, int32 z) const; API_FUNCTION() TerrainPatch* GetPatch(int32 x, int32 z) const;
/// <summary> /// <summary>
/// Gets the zero-based index of the terrain patch in the terrain patches collection. /// Gets the zero-based index of the terrain patch in the terrain patches collection.
@@ -241,7 +261,7 @@ public:
/// </summary> /// </summary>
/// <param name="index">The index.</param> /// <param name="index">The index.</param>
/// <returns>The patch.</returns> /// <returns>The patch.</returns>
FORCE_INLINE TerrainPatch* GetPatch(int32 index) const API_FUNCTION() FORCE_INLINE TerrainPatch* GetPatch(int32 index) const
{ {
return _patches[index]; return _patches[index];
} }
@@ -311,9 +331,7 @@ public:
#endif #endif
public: public:
#if TERRAIN_EDITING #if TERRAIN_EDITING
/// <summary> /// <summary>
/// Setups the terrain. Clears the existing data. /// Setups the terrain. Clears the existing data.
/// </summary> /// </summary>
@@ -338,7 +356,6 @@ public:
/// </summary> /// </summary>
/// <param name="patchCoord">The patch location (x and z).</param> /// <param name="patchCoord">The patch location (x and z).</param>
API_FUNCTION() void RemovePatch(API_PARAM(Ref) const Int2& patchCoord); API_FUNCTION() void RemovePatch(API_PARAM(Ref) const Int2& patchCoord);
#endif #endif
/// <summary> /// <summary>
@@ -362,17 +379,6 @@ public:
void RemoveLightmap(); void RemoveLightmap();
public: public:
/// <summary>
/// Performs a raycast against this terrain collision shape.
/// </summary>
/// <param name="origin">The origin of the ray.</param>
/// <param name="direction">The normalized direction of the ray.</param>
/// <param name="resultHitDistance">The raycast result hit position distance from the ray origin. Valid only if raycast hits anything.</param>
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
/// <returns>True if ray hits an object, otherwise false.</returns>
API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, float maxDistance = MAX_float) const;
/// <summary> /// <summary>
/// Performs a raycast against this terrain collision shape. Returns the hit chunk. /// Performs a raycast against this terrain collision shape. Returns the hit chunk.
/// </summary> /// </summary>
@@ -382,7 +388,7 @@ public:
/// <param name="resultChunk">The raycast result hit chunk. Valid only if raycast hits anything.</param> /// <param name="resultChunk">The raycast result hit chunk. Valid only if raycast hits anything.</param>
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param> /// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
/// <returns>True if ray hits an object, otherwise false.</returns> /// <returns>True if ray hits an object, otherwise false.</returns>
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;
/// <summary> /// <summary>
/// Performs a raycast against this terrain collision shape. Returns the hit chunk. /// Performs a raycast against this terrain collision shape. Returns the hit chunk.
@@ -395,23 +401,6 @@ public:
/// <returns>True if ray hits an object, otherwise false.</returns> /// <returns>True if ray hits an object, otherwise false.</returns>
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; 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;
/// <summary>
/// Performs a raycast against terrain collision, returns results in a RayCastHit structure.
/// </summary>
/// <param name="origin">The origin of the ray.</param>
/// <param name="direction">The normalized direction of the ray.</param>
/// <param name="hitInfo">The result hit information. Valid only when method returns true.</param>
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
/// <returns>True if ray hits an object, otherwise false.</returns>
API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float) const;
/// <summary>
/// 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.
/// </summary>
/// <param name="position">The position to find the closest point to it.</param>
/// <param name="result">The result point on the collider that is closest to the specified location.</param>
API_FUNCTION() void ClosestPoint(const Vector3& position, API_PARAM(Out) Vector3& result) const;
/// <summary> /// <summary>
/// Draws the terrain patch. /// Draws the terrain patch.
/// </summary> /// </summary>
@@ -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; 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: private:
void OnPhysicalMaterialChanged();
#if TERRAIN_USE_PHYSICS_DEBUG #if TERRAIN_USE_PHYSICS_DEBUG
void DrawPhysicsDebug(RenderView& view); void DrawPhysicsDebug(RenderView& view);
#endif #endif
public: public:
// [PhysicsColliderActor] // [PhysicsColliderActor]
void Draw(RenderContext& renderContext) override; void Draw(RenderContext& renderContext) override;
#if USE_EDITOR #if USE_EDITOR
@@ -450,9 +436,12 @@ public:
void Serialize(SerializeStream& stream, const void* otherObj) override; void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
RigidBody* GetAttachedRigidBody() const 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: protected:
// [PhysicsColliderActor] // [PhysicsColliderActor]
void OnEnable() override; void OnEnable() override;
void OnDisable() override; void OnDisable() override;

View File

@@ -11,7 +11,14 @@
#include "Engine/Renderer/RenderList.h" #include "Engine/Renderer/RenderList.h"
#include "Engine/Core/Math/OrientedBoundingBox.h" #include "Engine/Core/Math/OrientedBoundingBox.h"
#include "Engine/Level/Scene/Scene.h" #include "Engine/Level/Scene/Scene.h"
#if USE_EDITOR
#include "Engine/Level/Prefabs/PrefabManager.h" #include "Engine/Level/Prefabs/PrefabManager.h"
#endif
TerrainChunk::TerrainChunk(const SpawnParams& params)
: ScriptingObject(params)
{
}
void TerrainChunk::Init(TerrainPatch* patch, uint16 x, uint16 z) void TerrainChunk::Init(TerrainPatch* patch, uint16 x, uint16 z)
{ {
@@ -21,7 +28,7 @@ void TerrainChunk::Init(TerrainPatch* patch, uint16 x, uint16 z)
_z = z; _z = z;
_yOffset = 0; _yOffset = 0;
_yHeight = 1; _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); _perInstanceRandom = (_patch->_terrain->_id.C ^ _x ^ _z) * (1.0f / (float)MAX_uint32);
OverrideMaterial = nullptr; OverrideMaterial = nullptr;
} }
@@ -51,8 +58,8 @@ bool TerrainChunk::PrepareDraw(const RenderContext& renderContext)
//lod = 0; //lod = 0;
//lod = 10; //lod = 10;
//lod = (_x + _z + TerrainPatch::CHUNKS_COUNT_EDGE * (_patch->_x + _patch->_z)); //lod = (_x + _z + Terrain::ChunksCountEdge * (_patch->_x + _patch->_z));
//lod = (int32)Vector2::Distance(Vector2(2, 2), Vector2(_patch->_x, _patch->_z) * TerrainPatch::CHUNKS_COUNT_EDGE + Vector2(_x, _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 = (int32)(Vector3::Distance(_bounds.GetCenter(), view.Position) / 10000.0f);
} }
lod = Math::Clamp(lod, minStreamedLod, lodCount - 1); lod = Math::Clamp(lod, minStreamedLod, lodCount - 1);
@@ -93,7 +100,7 @@ void TerrainChunk::Draw(const RenderContext& renderContext) const
drawCall.ObjectRadius = _sphere.Radius; drawCall.ObjectRadius = _sphere.Radius;
drawCall.Terrain.Patch = _patch; drawCall.Terrain.Patch = _patch;
drawCall.Terrain.HeightmapUVScaleBias = _heightmapUVScaleBias; 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.CurrentLOD = (float)lod;
drawCall.Terrain.ChunkSizeNextLOD = (float)(((chunkSize + 1) >> (lod + 1)) - 1); drawCall.Terrain.ChunkSizeNextLOD = (float)(((chunkSize + 1) >> (lod + 1)) - 1);
drawCall.Terrain.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize; 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.ObjectRadius = _sphere.Radius;
drawCall.Terrain.Patch = _patch; drawCall.Terrain.Patch = _patch;
drawCall.Terrain.HeightmapUVScaleBias = _heightmapUVScaleBias; 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.CurrentLOD = (float)lod;
drawCall.Terrain.ChunkSizeNextLOD = (float)(((chunkSize + 1) >> (lod + 1)) - 1); drawCall.Terrain.ChunkSizeNextLOD = (float)(((chunkSize + 1) >> (lod + 1)) - 1);
drawCall.Terrain.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize; drawCall.Terrain.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize;
@@ -232,46 +239,46 @@ void TerrainChunk::CacheNeighbors()
_neighbors[0] = this; _neighbors[0] = this;
if (_z > 0) if (_z > 0)
{ {
_neighbors[0] = &_patch->Chunks[(_z - 1) * TerrainPatch::CHUNKS_COUNT_EDGE + _x]; _neighbors[0] = &_patch->Chunks[(_z - 1) * Terrain::ChunksCountEdge + _x];
} }
else else
{ {
const auto patch = _patch->_terrain->GetPatch(_patch->_x, _patch->_z - 1); const auto patch = _patch->_terrain->GetPatch(_patch->_x, _patch->_z - 1);
if (patch) 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 // 1: left
_neighbors[1] = this; _neighbors[1] = this;
if (_x > 0) if (_x > 0)
{ {
_neighbors[1] = &_patch->Chunks[_z * TerrainPatch::CHUNKS_COUNT_EDGE + (_x - 1)]; _neighbors[1] = &_patch->Chunks[_z * Terrain::ChunksCountEdge + (_x - 1)];
} }
else else
{ {
const auto patch = _patch->_terrain->GetPatch(_patch->_x - 1, _patch->_z); const auto patch = _patch->_terrain->GetPatch(_patch->_x - 1, _patch->_z);
if (patch) 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 // 2: right
_neighbors[2] = this; _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 else
{ {
const auto patch = _patch->_terrain->GetPatch(_patch->_x + 1, _patch->_z); const auto patch = _patch->_terrain->GetPatch(_patch->_x + 1, _patch->_z);
if (patch) if (patch)
_neighbors[2] = &patch->Chunks[_z * TerrainPatch::CHUNKS_COUNT_EDGE]; _neighbors[2] = &patch->Chunks[_z * Terrain::ChunksCountEdge];
} }
// 3: top // 3: top
_neighbors[3] = this; _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 else
{ {

View File

@@ -17,14 +17,14 @@ struct RenderContext;
/// <summary> /// <summary>
/// Represents a single terrain chunk. /// Represents a single terrain chunk.
/// </summary> /// </summary>
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 Terrain;
friend TerrainPatch; friend TerrainPatch;
friend TerrainChunk; friend TerrainChunk;
private: private:
TerrainPatch* _patch; TerrainPatch* _patch;
uint16 _x, _z; uint16 _x, _z;
Float4 _heightmapUVScaleBias; Float4 _heightmapUVScaleBias;
@@ -41,11 +41,10 @@ private:
void Init(TerrainPatch* patch, uint16 x, uint16 z); void Init(TerrainPatch* patch, uint16 x, uint16 z);
public: public:
/// <summary> /// <summary>
/// The material to override the terrain default one for this chunk. /// The material to override the terrain default one for this chunk.
/// </summary> /// </summary>
AssetReference<MaterialBase> OverrideMaterial; API_FIELD() AssetReference<MaterialBase> OverrideMaterial;
/// <summary> /// <summary>
/// The baked lightmap entry info for this chunk. /// The baked lightmap entry info for this chunk.
@@ -53,11 +52,10 @@ public:
LightmapEntry Lightmap; LightmapEntry Lightmap;
public: public:
/// <summary> /// <summary>
/// Gets the x coordinate. /// Gets the x coordinate.
/// </summary> /// </summary>
FORCE_INLINE int32 GetX() const API_FUNCTION() FORCE_INLINE int32 GetX() const
{ {
return _x; return _x;
} }
@@ -65,7 +63,7 @@ public:
/// <summary> /// <summary>
/// Gets the z coordinate. /// Gets the z coordinate.
/// </summary> /// </summary>
FORCE_INLINE int32 GetZ() const API_FUNCTION() FORCE_INLINE int32 GetZ() const
{ {
return _z; return _z;
} }
@@ -73,7 +71,7 @@ public:
/// <summary> /// <summary>
/// Gets the patch. /// Gets the patch.
/// </summary> /// </summary>
FORCE_INLINE TerrainPatch* GetPatch() const API_FUNCTION() FORCE_INLINE TerrainPatch* GetPatch() const
{ {
return _patch; return _patch;
} }
@@ -81,7 +79,7 @@ public:
/// <summary> /// <summary>
/// Gets the chunk world bounds. /// Gets the chunk world bounds.
/// </summary> /// </summary>
FORCE_INLINE const BoundingBox& GetBounds() const API_FUNCTION() FORCE_INLINE const BoundingBox& GetBounds() const
{ {
return _bounds; return _bounds;
} }
@@ -89,7 +87,7 @@ public:
/// <summary> /// <summary>
/// Gets the chunk transformation (world to local). /// Gets the chunk transformation (world to local).
/// </summary> /// </summary>
FORCE_INLINE const Transform& GetTransform() const API_FUNCTION() FORCE_INLINE const Transform& GetTransform() const
{ {
return _transform; return _transform;
} }
@@ -97,10 +95,9 @@ public:
/// <summary> /// <summary>
/// Gets the scale (in XY) and bias (in ZW) applied to the vertex UVs to get the chunk coordinates. /// Gets the scale (in XY) and bias (in ZW) applied to the vertex UVs to get the chunk coordinates.
/// </summary> /// </summary>
/// <param name="result">The result.</param> API_FUNCTION() FORCE_INLINE const Float4& GetHeightmapUVScaleBias() const
FORCE_INLINE void GetHeightmapUVScaleBias(Float4* result) const
{ {
*result = _heightmapUVScaleBias; return _heightmapUVScaleBias;
} }
/// <summary> /// <summary>
@@ -120,7 +117,6 @@ public:
} }
public: public:
/// <summary> /// <summary>
/// Prepares for drawing chunk. Cached LOD and material. /// Prepares for drawing chunk. Cached LOD and material.
/// </summary> /// </summary>
@@ -140,7 +136,7 @@ public:
/// <param name="renderContext">The rendering context.</param> /// <param name="renderContext">The rendering context.</param>
/// <param name="material">The material to use for rendering.</param> /// <param name="material">The material to use for rendering.</param>
/// <param name="lodIndex">The LOD index.</param> /// <param name="lodIndex">The LOD index.</param>
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;
/// <summary> /// <summary>
/// Determines if there is an intersection between the terrain chunk and a point /// Determines if there is an intersection between the terrain chunk and a point
@@ -148,7 +144,7 @@ public:
/// <param name="ray">The ray.</param> /// <param name="ray">The ray.</param>
/// <param name="distance">The output distance.</param> /// <param name="distance">The output distance.</param>
/// <returns>True if chunk intersects with the ray, otherwise false.</returns> /// <returns>True if chunk intersects with the ray, otherwise false.</returns>
bool Intersects(const Ray& ray, Real& distance); API_FUNCTION() bool Intersects(const Ray& ray, API_PARAM(Out) Real& distance);
/// <summary> /// <summary>
/// Updates the cached bounds of the chunk. /// Updates the cached bounds of the chunk.
@@ -166,7 +162,6 @@ public:
void CacheNeighbors(); void CacheNeighbors();
public: public:
// [ISerializable] // [ISerializable]
void Serialize(SerializeStream& stream, const void* otherObj) override; void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;

File diff suppressed because it is too large Load Diff

View File

@@ -15,22 +15,14 @@ class TerrainMaterialShader;
/// <summary> /// <summary>
/// Represents single terrain patch made of 16 terrain chunks. /// Represents single terrain patch made of 16 terrain chunks.
/// </summary> /// </summary>
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 Terrain;
friend TerrainPatch; friend TerrainPatch;
friend TerrainChunk; friend TerrainChunk;
public:
enum
{
CHUNKS_COUNT = 16,
CHUNKS_COUNT_EDGE = 4,
};
private: private:
Terrain* _terrain; Terrain* _terrain;
int16 _x, _z; int16 _x, _z;
float _yOffset, _yHeight; float _yOffset, _yHeight;
@@ -62,23 +54,21 @@ private:
void Init(Terrain* terrain, int16 x, int16 z); void Init(Terrain* terrain, int16 x, int16 z);
public: public:
/// <summary> /// <summary>
/// Finalizes an instance of the <see cref="TerrainPatch"/> class. /// Finalizes an instance of the <see cref="TerrainPatch"/> class.
/// </summary> /// </summary>
~TerrainPatch(); ~TerrainPatch();
public: public:
/// <summary> /// <summary>
/// The chunks contained within the patch. Organized in 4x4 square. /// The chunks contained within the patch. Organized in 4x4 square.
/// </summary> /// </summary>
TerrainChunk Chunks[CHUNKS_COUNT]; TerrainChunk Chunks[Terrain::ChunksCount];
/// <summary> /// <summary>
/// The heightmap texture. /// The heightmap texture.
/// </summary> /// </summary>
AssetReference<Texture> Heightmap; API_FIELD() AssetReference<Texture> Heightmap;
/// <summary> /// <summary>
/// The splatmap textures. /// The splatmap textures.
@@ -86,12 +76,10 @@ public:
AssetReference<Texture> Splatmap[TERRAIN_MAX_SPLATMAPS_COUNT]; AssetReference<Texture> Splatmap[TERRAIN_MAX_SPLATMAPS_COUNT];
public: public:
/// <summary> /// <summary>
/// Gets the Y axis heightmap offset from terrain origin. /// Gets the Y axis heightmap offset from terrain origin.
/// </summary> /// </summary>
/// <returns>The offset.</returns> API_FUNCTION() FORCE_INLINE float GetOffsetY() const
FORCE_INLINE float GetOffsetY() const
{ {
return _yOffset; return _yOffset;
} }
@@ -99,8 +87,7 @@ public:
/// <summary> /// <summary>
/// Gets the Y axis heightmap height. /// Gets the Y axis heightmap height.
/// </summary> /// </summary>
/// <returns>The height.</returns> API_FUNCTION() FORCE_INLINE float GetHeightY() const
FORCE_INLINE float GetHeightY() const
{ {
return _yHeight; return _yHeight;
} }
@@ -108,8 +95,7 @@ public:
/// <summary> /// <summary>
/// Gets the x coordinate. /// Gets the x coordinate.
/// </summary> /// </summary>
/// <returns>The x position.</returns> API_FUNCTION() FORCE_INLINE int32 GetX() const
FORCE_INLINE int32 GetX() const
{ {
return _x; return _x;
} }
@@ -117,8 +103,7 @@ public:
/// <summary> /// <summary>
/// Gets the z coordinate. /// Gets the z coordinate.
/// </summary> /// </summary>
/// <returns>The z position.</returns> API_FUNCTION() FORCE_INLINE int32 GetZ() const
FORCE_INLINE int32 GetZ() const
{ {
return _z; return _z;
} }
@@ -126,8 +111,7 @@ public:
/// <summary> /// <summary>
/// Gets the terrain. /// Gets the terrain.
/// </summary> /// </summary>
/// <returns>The terrain,</returns> API_FUNCTION() FORCE_INLINE Terrain* GetTerrain() const
FORCE_INLINE Terrain* GetTerrain() const
{ {
return _terrain; return _terrain;
} }
@@ -137,9 +121,9 @@ public:
/// </summary> /// </summary>
/// <param name="index">The chunk zero-based index.</param> /// <param name="index">The chunk zero-based index.</param>
/// <returns>The chunk.</returns> /// <returns>The chunk.</returns>
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 nullptr;
return &Chunks[index]; return &Chunks[index];
} }
@@ -149,9 +133,9 @@ public:
/// </summary> /// </summary>
/// <param name="chunkCoord">The chunk location (x and z).</param> /// <param name="chunkCoord">The chunk location (x and z).</param>
/// <returns>The chunk.</returns> /// <returns>The chunk.</returns>
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);
} }
/// <summary> /// <summary>
@@ -160,22 +144,43 @@ public:
/// <param name="x">The chunk location x.</param> /// <param name="x">The chunk location x.</param>
/// <param name="z">The chunk location z.</param> /// <param name="z">The chunk location z.</param>
/// <returns>The chunk.</returns> /// <returns>The chunk.</returns>
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);
}
/// <summary>
/// Gets the splatmap assigned to this patch.
/// </summary>
/// <param name="index">The zero-based index of the splatmap.</param>
/// <returns>The splatmap texture.</returns>
API_FUNCTION() AssetReference<Texture> GetSplatmap(int32 index)
{
if (index < 0 || index >= TERRAIN_MAX_SPLATMAPS_COUNT)
return nullptr;
return Splatmap[index];
}
/// <summary>
/// Sets a splatmap to this patch.
/// </summary>
/// <param name="index">The zero-based index of the splatmap.</param>
/// <param name="splatMap">Splatmap texture.</param>
API_FUNCTION() void SetSplatmap(int32 index, const AssetReference<Texture>& splatMap)
{
if (index >= 0 && index < TERRAIN_MAX_SPLATMAPS_COUNT)
Splatmap[index] = splatMap;
} }
/// <summary> /// <summary>
/// Gets the patch world bounds. /// Gets the patch world bounds.
/// </summary> /// </summary>
/// <returns>The bounding box.</returns> API_FUNCTION() FORCE_INLINE const BoundingBox& GetBounds() const
FORCE_INLINE const BoundingBox& GetBounds() const
{ {
return _bounds; return _bounds;
} }
public: public:
/// <summary> /// <summary>
/// Removes the lightmap data from the terrain patch. /// Removes the lightmap data from the terrain patch.
/// </summary> /// </summary>
@@ -192,12 +197,11 @@ public:
void UpdateTransform(); void UpdateTransform();
#if TERRAIN_EDITING #if TERRAIN_EDITING
/// <summary> /// <summary>
/// Initializes the patch heightmap and collision to the default flat level. /// Initializes the patch heightmap and collision to the default flat level.
/// </summary> /// </summary>
/// <returns>True if failed, otherwise false.</returns> /// <returns>True if failed, otherwise false.</returns>
bool InitializeHeightMap(); API_FUNCTION() bool InitializeHeightMap();
/// <summary> /// <summary>
/// Setups the terrain patch using the specified heightmap data. /// Setups the terrain patch using the specified heightmap data.
@@ -207,7 +211,7 @@ public:
/// <param name="holesMask">The holes mask (optional). Normalized to 0-1 range values with holes mask per-vertex. Must match the heightmap dimensions.</param> /// <param name="holesMask">The holes mask (optional). Normalized to 0-1 range values with holes mask per-vertex. Must match the heightmap dimensions.</param>
/// <param name="forceUseVirtualStorage">If set to <c>true</c> 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).</param> /// <param name="forceUseVirtualStorage">If set to <c>true</c> 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).</param>
/// <returns>True if failed, otherwise false.</returns> /// <returns>True if failed, otherwise false.</returns>
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);
/// <summary> /// <summary>
/// Setups the terrain patch layer weights using the specified splatmaps data. /// Setups the terrain patch layer weights using the specified splatmaps data.
@@ -217,50 +221,48 @@ public:
/// <param name="splatMap">The splat map. Each array item contains 4 layer weights.</param> /// <param name="splatMap">The splat map. Each array item contains 4 layer weights.</param>
/// <param name="forceUseVirtualStorage">If set to <c>true</c> 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).</param> /// <param name="forceUseVirtualStorage">If set to <c>true</c> 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).</param>
/// <returns>True if failed, otherwise false.</returns> /// <returns>True if failed, otherwise false.</returns>
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 #endif
#if TERRAIN_UPDATING #if TERRAIN_UPDATING
/// <summary> /// <summary>
/// Gets the raw pointer to the heightmap data. /// Gets the raw pointer to the heightmap data.
/// </summary> /// </summary>
/// <returns>The heightmap data.</returns> /// <returns>The heightmap data.</returns>
float* GetHeightmapData(); API_FUNCTION() float* GetHeightmapData();
/// <summary> /// <summary>
/// Clears cache of the heightmap data. /// Clears cache of the heightmap data.
/// </summary> /// </summary>
void ClearHeightmapCache(); API_FUNCTION() void ClearHeightmapCache();
/// <summary> /// <summary>
/// Gets the raw pointer to the holes mask data. /// Gets the raw pointer to the holes mask data.
/// </summary> /// </summary>
/// <returns>The holes mask data.</returns> /// <returns>The holes mask data.</returns>
byte* GetHolesMaskData(); API_FUNCTION() byte* GetHolesMaskData();
/// <summary> /// <summary>
/// Clears cache of the holes mask data. /// Clears cache of the holes mask data.
/// </summary> /// </summary>
void ClearHolesMaskCache(); API_FUNCTION() void ClearHolesMaskCache();
/// <summary> /// <summary>
/// Gets the raw pointer to the splat map data. /// Gets the raw pointer to the splat map data.
/// </summary> /// </summary>
/// <param name="index">The zero-based index of the splatmap texture.</param> /// <param name="index">The zero-based index of the splatmap texture.</param>
/// <returns>The splat map data.</returns> /// <returns>The splat map data.</returns>
Color32* GetSplatMapData(int32 index); API_FUNCTION() Color32* GetSplatMapData(int32 index);
/// <summary> /// <summary>
/// Clears cache of the splat map data. /// Clears cache of the splat map data.
/// </summary> /// </summary>
void ClearSplatMapCache(); API_FUNCTION() void ClearSplatMapCache();
/// <summary> /// <summary>
/// Clears all caches. /// Clears all caches.
/// </summary> /// </summary>
void ClearCache(); API_FUNCTION() void ClearCache();
/// <summary> /// <summary>
/// Modifies the terrain patch heightmap with the given samples. /// Modifies the terrain patch heightmap with the given samples.
@@ -269,7 +271,7 @@ public:
/// <param name="modifiedOffset">The offset from the first row and column of the heightmap data (offset destination x and z start position).</param> /// <param name="modifiedOffset">The offset from the first row and column of the heightmap data (offset destination x and z start position).</param>
/// <param name="modifiedSize">The size of the heightmap to modify (x and z). Amount of samples in each direction.</param> /// <param name="modifiedSize">The size of the heightmap to modify (x and z). Amount of samples in each direction.</param>
/// <returns>True if failed, otherwise false.</returns> /// <returns>True if failed, otherwise false.</returns>
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);
/// <summary> /// <summary>
/// Modifies the terrain patch holes mask with the given samples. /// Modifies the terrain patch holes mask with the given samples.
@@ -278,7 +280,7 @@ public:
/// <param name="modifiedOffset">The offset from the first row and column of the holes map data (offset destination x and z start position).</param> /// <param name="modifiedOffset">The offset from the first row and column of the holes map data (offset destination x and z start position).</param>
/// <param name="modifiedSize">The size of the holes map to modify (x and z). Amount of samples in each direction.</param> /// <param name="modifiedSize">The size of the holes map to modify (x and z). Amount of samples in each direction.</param>
/// <returns>True if failed, otherwise false.</returns> /// <returns>True if failed, otherwise false.</returns>
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);
/// <summary> /// <summary>
/// Modifies the terrain patch splat map (layers mask) with the given samples. /// Modifies the terrain patch splat map (layers mask) with the given samples.
@@ -288,21 +290,18 @@ public:
/// <param name="modifiedOffset">The offset from the first row and column of the splat map data (offset destination x and z start position).</param> /// <param name="modifiedOffset">The offset from the first row and column of the splat map data (offset destination x and z start position).</param>
/// <param name="modifiedSize">The size of the splat map to modify (x and z). Amount of samples in each direction.</param> /// <param name="modifiedSize">The size of the splat map to modify (x and z). Amount of samples in each direction.</param>
/// <returns>True if failed, otherwise false.</returns> /// <returns>True if failed, otherwise false.</returns>
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: private:
bool UpdateHeightData(struct TerrainDataUpdateInfo& info, const Int2& modifiedOffset, const Int2& modifiedSize, bool wasHeightRangeChanged, bool wasHeightChanged);
bool UpdateHeightData(const struct TerrainDataUpdateInfo& info, const Int2& modifiedOffset, const Int2& modifiedSize, bool wasHeightRangeChanged);
void SaveHeightData(); void SaveHeightData();
void CacheHeightData(); void CacheHeightData();
void SaveSplatData(); void SaveSplatData();
void SaveSplatData(int32 index); void SaveSplatData(int32 index);
void CacheSplatData(); void CacheSplatData();
#endif #endif
public: public:
/// <summary> /// <summary>
/// Performs a raycast against this terrain collision shape. /// Performs a raycast against this terrain collision shape.
/// </summary> /// </summary>
@@ -311,7 +310,7 @@ public:
/// <param name="resultHitDistance">The raycast result hit position distance from the ray origin. Valid only if raycast hits anything.</param> /// <param name="resultHitDistance">The raycast result hit position distance from the ray origin. Valid only if raycast hits anything.</param>
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param> /// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
/// <returns>True if ray hits an object, otherwise false.</returns> /// <returns>True if ray hits an object, otherwise false.</returns>
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;
/// <summary> /// <summary>
/// Performs a raycast against this terrain collision shape. /// Performs a raycast against this terrain collision shape.
@@ -322,7 +321,7 @@ public:
/// <param name="resultHitNormal">The raycast result hit position normal vector. Valid only if raycast hits anything.</param> /// <param name="resultHitNormal">The raycast result hit position normal vector. Valid only if raycast hits anything.</param>
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param> /// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
/// <returns>True if ray hits an object, otherwise false.</returns> /// <returns>True if ray hits an object, otherwise false.</returns>
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;
/// <summary> /// <summary>
/// Performs a raycast against this terrain collision shape. Returns the hit chunk. /// Performs a raycast against this terrain collision shape. Returns the hit chunk.
@@ -333,7 +332,7 @@ public:
/// <param name="resultChunk">The raycast result hit chunk. Valid only if raycast hits anything.</param> /// <param name="resultChunk">The raycast result hit chunk. Valid only if raycast hits anything.</param>
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param> /// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
/// <returns>True if ray hits an object, otherwise false.</returns> /// <returns>True if ray hits an object, otherwise false.</returns>
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;
/// <summary> /// <summary>
/// Performs a raycast against terrain collision, returns results in a RaycastHit structure. /// Performs a raycast against terrain collision, returns results in a RaycastHit structure.
@@ -343,28 +342,24 @@ public:
/// <param name="hitInfo">The result hit information. Valid only when method returns true.</param> /// <param name="hitInfo">The result hit information. Valid only when method returns true.</param>
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param> /// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
/// <returns>True if ray hits an object, otherwise false.</returns> /// <returns>True if ray hits an object, otherwise false.</returns>
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;
/// <summary> /// <summary>
/// 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. /// 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.
/// </summary> /// </summary>
/// <param name="position">The position to find the closest point to it.</param> /// <param name="position">The position to find the closest point to it.</param>
/// <param name="result">The result point on the collider that is closest to the specified location.</param> /// <param name="result">The result point on the collider that is closest to the specified location.</param>
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 #if USE_EDITOR
/// <summary> /// <summary>
/// Updates the patch data after manual deserialization called at runtime (eg. by editor undo). /// Updates the patch data after manual deserialization called at runtime (eg. by editor undo).
/// </summary> /// </summary>
void UpdatePostManualDeserialization(); void UpdatePostManualDeserialization();
#endif #endif
public: public:
#if USE_EDITOR #if USE_EDITOR
/// <summary> /// <summary>
/// Gets the collision mesh triangles array (3 vertices per triangle in linear list). Cached internally to reuse data. /// Gets the collision mesh triangles array (3 vertices per triangle in linear list). Cached internally to reuse data.
/// </summary> /// </summary>
@@ -376,8 +371,7 @@ public:
/// </summary> /// </summary>
/// <param name="bounds">The world-space bounds to find terrain triangles that intersect with it.</param> /// <param name="bounds">The world-space bounds to find terrain triangles that intersect with it.</param>
/// <param name="result">The result triangles that intersect with the given bounds (in world-space).</param> /// <param name="result">The result triangles that intersect with the given bounds (in world-space).</param>
void GetCollisionTriangles(const BoundingSphere& bounds, Array<Vector3>& result); void GetCollisionTriangles(API_PARAM(Ref) const BoundingSphere& bounds, API_PARAM(Out) Array<Vector3>& result);
#endif #endif
/// <summary> /// <summary>
@@ -385,10 +379,9 @@ public:
/// </summary> /// </summary>
/// <param name="vertexBuffer">The output vertex buffer.</param> /// <param name="vertexBuffer">The output vertex buffer.</param>
/// <param name="indexBuffer">The output index buffer.</param> /// <param name="indexBuffer">The output index buffer.</param>
void ExtractCollisionGeometry(Array<Float3>& vertexBuffer, Array<int32>& indexBuffer); API_FUNCTION() void ExtractCollisionGeometry(API_PARAM(Out) Array<Float3>& vertexBuffer, API_PARAM(Out) Array<int32>& indexBuffer);
private: private:
/// <summary> /// <summary>
/// Determines whether this patch has created collision representation. /// Determines whether this patch has created collision representation.
/// </summary> /// </summary>
@@ -430,8 +423,8 @@ private:
bool UpdateCollision(); bool UpdateCollision();
void OnPhysicsSceneChanged(PhysicsScene* previous); void OnPhysicsSceneChanged(PhysicsScene* previous);
public:
public:
// [ISerializable] // [ISerializable]
void Serialize(SerializeStream& stream, const void* otherObj) override; void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;

View File

@@ -1468,7 +1468,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
Transform srcNode = data.Skeleton.Nodes[nodeIndex].LocalTransform; Transform srcNode = data.Skeleton.Nodes[nodeIndex].LocalTransform;
auto& node = data.Skeleton.Nodes[nodeIndex]; auto& node = data.Skeleton.Nodes[nodeIndex];
if (auto* channel = animation.GetChannel(node.Name)) if (auto* channel = animation.GetChannel(node.Name))
channel->Evaluate(frame, &srcNode, false); channel->Evaluate((float)frame, &srcNode, false);
pose.Nodes[nodeIndex] = srcNode; pose.Nodes[nodeIndex] = srcNode;
} }
@@ -1476,7 +1476,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
key = Float3::Zero; key = Float3::Zero;
for (int32 nodeIndex = 0; nodeIndex < nodes; nodeIndex++) for (int32 nodeIndex = 0; nodeIndex < nodes; nodeIndex++)
key += pose.GetNodeModelTransformation(data.Skeleton, nodeIndex).Translation; key += pose.GetNodeModelTransformation(data.Skeleton, nodeIndex).Translation;
key /= nodes; key /= (float)nodes;
} }
// Calculate skeleton center of mass movement over the animation frames // 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++) for (int32 frame = 0; frame < frames; frame++)
{ {
auto& key = rootChannel.Position[frame]; auto& key = rootChannel.Position[frame];
key.Time = frame; key.Time = (float)frame;
key.Value = centerOfMass[frame] - centerOfMassRefPose; 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; Transform srcNode = data.Skeleton.Nodes[nodeIndex].LocalTransform;
auto& node = data.Skeleton.Nodes[nodeIndex]; auto& node = data.Skeleton.Nodes[nodeIndex];
if (auto* channel = animation.GetChannel(node.Name)) if (auto* channel = animation.GetChannel(node.Name))
channel->Evaluate(frame, &srcNode, false); channel->Evaluate((float)frame, &srcNode, false);
pose.Nodes[nodeIndex] = srcNode; pose.Nodes[nodeIndex] = srcNode;
} }

View File

@@ -1,4 +1,6 @@
using System; // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
namespace FlaxEngine.GUI; namespace FlaxEngine.GUI;
@@ -7,6 +9,32 @@ namespace FlaxEngine.GUI;
/// </summary> /// </summary>
public class Slider : ContainerControl public class Slider : ContainerControl
{ {
/// <summary>
/// The slider direction
/// </summary>
public enum SliderDirection
{
/// <summary>
/// Slider direction, horizontal right
/// </summary>
HorizontalRight,
/// <summary>
/// Slider direction, horizontal left
/// </summary>
HorizontalLeft,
/// <summary>
/// Slider direction, vertical up
/// </summary>
VerticalUp,
/// <summary>
/// Slider direction, vertical down
/// </summary>
VerticalDown,
}
/// <summary> /// <summary>
/// The minimum value. /// The minimum value.
/// </summary> /// </summary>
@@ -15,26 +43,7 @@ public class Slider : ContainerControl
/// <summary> /// <summary>
/// The maximum value. /// The maximum value.
/// </summary> /// </summary>
protected float _maximum = 100f; protected float _maximum = 100;
/// <summary>
/// Gets or sets the minimum value.
/// </summary>
[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;
}
}
/// <summary> /// <summary>
/// Gets or sets the maximum value. /// Gets or sets the maximum value.
@@ -45,8 +54,6 @@ public class Slider : ContainerControl
get => _maximum; get => _maximum;
set set
{ {
if (value < _minimum || Mathf.IsZero(value))
throw new ArgumentOutOfRangeException();
if (WholeNumbers) if (WholeNumbers)
value = Mathf.RoundToInt(value); value = Mathf.RoundToInt(value);
_maximum = value; _maximum = value;
@@ -55,6 +62,38 @@ public class Slider : ContainerControl
} }
} }
/// <summary>
/// Gets or sets the minimum value.
/// </summary>
[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;
}
}
/// <summary>
/// Gets or sets the slider direction.
/// </summary>
[EditorOrder(40), Tooltip("Slider Direction.")]
public SliderDirection Direction
{
get => _direction;
set
{
_direction = value;
UpdateThumb();
}
}
private SliderDirection _direction = SliderDirection.HorizontalRight;
private float _value = 100f; private float _value = 100f;
private Rectangle _thumbRect; private Rectangle _thumbRect;
private float _thumbCenter; private float _thumbCenter;
@@ -89,31 +128,60 @@ public class Slider : ContainerControl
/// The local position of the thumb center /// The local position of the thumb center
/// </summary> /// </summary>
[HideInEditor] [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);
/// <summary> /// <summary>
/// The local position of the beginning of the track. /// The local position of the beginning of the track.
/// </summary> /// </summary>
[HideInEditor] [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;
}
}
/// <summary> /// <summary>
/// The local position of the end of the track. /// The local position of the end of the track.
/// </summary> /// </summary>
[HideInEditor] [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;
}
}
/// <summary> /// <summary>
/// The height of the track. /// The height of the track.
/// </summary> /// </summary>
[EditorOrder(40), Tooltip("The track height.")] [EditorOrder(40), Tooltip("The track height.")]
public int TrackHeight { get; set; } = 2; public int TrackThickness { get; set; } = 2;
/// <summary> /// <summary>
/// The thumb size. /// The thumb size.
/// </summary> /// </summary>
[EditorOrder(41), Tooltip("The size of the thumb.")] [EditorOrder(41), Tooltip("The size of the thumb.")]
public Float2 ThumbSize { public Float2 ThumbSize
{
get => _thumbSize; get => _thumbSize;
set set
{ {
@@ -147,9 +215,14 @@ public class Slider : ContainerControl
public Color TrackFillLineColor { get; set; } public Color TrackFillLineColor { get; set; }
/// <summary> /// <summary>
/// Gets the size of the track. /// Gets the width of the track.
/// </summary> /// </summary>
private float TrackWidth => Width; private float TrackWidth => (Direction is SliderDirection.HorizontalLeft or SliderDirection.HorizontalRight) ? Width - _thumbSize.X : TrackThickness;
/// <summary>
/// Gets the height of the track.
/// </summary>
private float TrackHeight => (Direction is SliderDirection.HorizontalLeft or SliderDirection.HorizontalRight) ? TrackThickness : Height - _thumbSize.Y;
/// <summary> /// <summary>
/// Gets or sets the brush used for slider track drawing. /// Gets or sets the brush used for slider track drawing.
@@ -236,13 +309,32 @@ public class Slider : ContainerControl
private void UpdateThumb() private void UpdateThumb()
{ {
// Cache data // Cache data
float trackSize = TrackWidth; var isHorizontal = Direction is SliderDirection.HorizontalRight or SliderDirection.HorizontalLeft;
float trackSize = isHorizontal ? Width : Height;
float range = Maximum - Minimum; float range = Maximum - Minimum;
float pixelRange = trackSize - _thumbSize.X; float pixelRange = trackSize - (isHorizontal ? _thumbSize.X : _thumbSize.Y);
float perc = (_value - Minimum) / range; float perc = (_value - Minimum) / range;
float thumbPosition = (int)(perc * pixelRange); float thumbPosition = (int)(perc * pixelRange);
switch (Direction)
{
case SliderDirection.HorizontalRight:
_thumbCenter = thumbPosition + _thumbSize.X / 2; _thumbCenter = thumbPosition + _thumbSize.X / 2;
_thumbRect = new Rectangle(thumbPosition, (Height - _thumbSize.Y) / 2, _thumbSize.X, _thumbSize.Y); _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() private void EndSliding()
@@ -257,9 +349,27 @@ public class Slider : ContainerControl
{ {
base.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 // 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) if (TrackBrush != null)
TrackBrush.Draw(lineRect, TrackLineColor); TrackBrush.Draw(lineRect, TrackLineColor);
else else
@@ -268,7 +378,6 @@ public class Slider : ContainerControl
// Draw track fill // Draw track fill
if (FillTrack) 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); Render2D.PushClip(ref fillLineRect);
if (FillTrackBrush != null) if (FillTrackBrush != null)
FillTrackBrush.Draw(lineRect, TrackFillLineColor); FillTrackBrush.Draw(lineRect, TrackFillLineColor);
@@ -278,11 +387,11 @@ public class Slider : ContainerControl
} }
// Draw thumb // Draw thumb
var thumbColor = _isSliding ? ThumbColorSelected : (_mouseOverThumb ? ThumbColorHighlighted : ThumbColor); var thumbColorV = _isSliding ? ThumbColorSelected : (_mouseOverThumb ? ThumbColorHighlighted : ThumbColor);
if (ThumbBrush != null) if (ThumbBrush != null)
ThumbBrush.Draw(_thumbRect, thumbColor); ThumbBrush.Draw(_thumbRect, thumbColorV);
else else
Render2D.FillRectangle(_thumbRect, thumbColor); Render2D.FillRectangle(_thumbRect, thumbColorV);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -302,7 +411,7 @@ public class Slider : ContainerControl
if (button == MouseButton.Left) if (button == MouseButton.Left)
{ {
Focus(); Focus();
float mousePosition = location.X; float mousePosition = Direction is SliderDirection.HorizontalRight or SliderDirection.HorizontalLeft ? location.X : location.Y;
if (_thumbRect.Contains(ref location)) if (_thumbRect.Contains(ref location))
{ {
@@ -315,7 +424,16 @@ public class Slider : ContainerControl
else else
{ {
// Click change // Click change
switch (Direction)
{
case SliderDirection.HorizontalRight or SliderDirection.VerticalDown:
Value += (mousePosition < _thumbCenter ? -1 : 1) * 10; 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 // Update sliding
var slidePosition = location + Root.TrackingMouseOffset; 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 else
{ {

View File

@@ -101,7 +101,7 @@ public:
/// Visject graph parameter. /// Visject graph parameter.
/// </summary> /// </summary>
/// <seealso cref="GraphParameter" /> /// <seealso cref="GraphParameter" />
API_CLASS() class VisjectGraphParameter : public GraphParameter API_CLASS() class FLAXENGINE_API VisjectGraphParameter : public GraphParameter
{ {
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(VisjectGraphParameter, GraphParameter); DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(VisjectGraphParameter, GraphParameter);
public: public:

View File

@@ -5,10 +5,6 @@
#include "Engine/Serialization/ReadStream.h" #include "Engine/Serialization/ReadStream.h"
#include "Engine/Serialization/WriteStream.h" #include "Engine/Serialization/WriteStream.h"
VisjectMeta::VisjectMeta()
{
}
bool VisjectMeta::Load(ReadStream* stream, bool loadData) bool VisjectMeta::Load(ReadStream* stream, bool loadData)
{ {
Release(); Release();

View File

@@ -8,7 +8,7 @@
/// <summary> /// <summary>
/// Visject metadata container /// Visject metadata container
/// </summary> /// </summary>
class VisjectMeta class FLAXENGINE_API VisjectMeta
{ {
public: public:
/// <summary> /// <summary>
@@ -27,19 +27,6 @@ public:
/// </summary> /// </summary>
Array<Entry, FixedAllocation<8>> Entries; Array<Entry, FixedAllocation<8>> Entries;
public:
/// <summary>
/// Initializes a new instance of the <see cref="VisjectMeta"/> class.
/// </summary>
VisjectMeta();
/// <summary>
/// Finalizes an instance of the <see cref="VisjectMeta"/> class.
/// </summary>
~VisjectMeta()
{
}
public: public:
/// <summary> /// <summary>
/// Load from the stream /// Load from the stream

View File

@@ -22,7 +22,7 @@ namespace Flax.Build.Bindings
public string[] Comment; public string[] Comment;
public bool IsInBuild; public bool IsInBuild;
public bool IsDeprecated; public bool IsDeprecated;
public string MarshalAs; public TypeInfo MarshalAs;
internal bool IsInited; internal bool IsInited;
internal TypedefInfo Instigator; internal TypedefInfo Instigator;

View File

@@ -197,7 +197,7 @@ namespace Flax.Build.Bindings
if (apiType != null) if (apiType != null)
{ {
if (apiType.MarshalAs != null) if (apiType.MarshalAs != null)
return UsePassByReference(buildData, new TypeInfo(apiType.MarshalAs), caller); return UsePassByReference(buildData, apiType.MarshalAs, caller);
// Skip for scripting objects // Skip for scripting objects
if (apiType.IsScriptingObject) if (apiType.IsScriptingObject)

View File

@@ -270,7 +270,7 @@ namespace Flax.Build.Bindings
return value; 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; string result;
if (typeInfo?.Type == null) if (typeInfo?.Type == null)
@@ -280,7 +280,7 @@ namespace Flax.Build.Bindings
if (typeInfo.IsArray) if (typeInfo.IsArray)
{ {
typeInfo.IsArray = false; typeInfo.IsArray = false;
result = GenerateCSharpNativeToManaged(buildData, typeInfo, caller); result = GenerateCSharpNativeToManaged(buildData, typeInfo, caller, marshalling);
typeInfo.IsArray = true; typeInfo.IsArray = true;
return result + "[]"; return result + "[]";
} }
@@ -307,7 +307,7 @@ namespace Flax.Build.Bindings
// Object reference property // Object reference property
if (typeInfo.IsObjectRef) 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") if (typeInfo.Type == "SoftTypeReference" || typeInfo.Type == "SoftObjectReference")
return typeInfo.Type; return typeInfo.Type;
@@ -317,15 +317,25 @@ namespace Flax.Build.Bindings
#else #else
if ((typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "DataContainer") && typeInfo.GenericArgs != null) if ((typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "DataContainer") && typeInfo.GenericArgs != null)
#endif #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 // Dictionary
if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null) 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 // HashSet
if (typeInfo.Type == "HashSet" && typeInfo.GenericArgs != null) 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 // BitArray
if (typeInfo.Type == "BitArray" && typeInfo.GenericArgs != null) 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) // TODO: generate delegates globally in the module namespace to share more code (smaller binary size)
var key = string.Empty; var key = string.Empty;
for (int i = 0; i < typeInfo.GenericArgs.Count; i++) 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)) if (!CSharpAdditionalCodeCache.TryGetValue(key, out var delegateName))
{ {
delegateName = "Delegate" + CSharpAdditionalCodeCache.Count; 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++) for (int i = 1; i < typeInfo.GenericArgs.Count; i++)
{ {
if (i != 1) if (i != 1)
signature += ", "; signature += ", ";
signature += GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[i], caller); signature += GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[i], caller, marshalling);
signature += $" arg{(i - 1)}"; signature += $" arg{(i - 1)}";
} }
signature += ");"; signature += ");";
@@ -390,11 +400,14 @@ namespace Flax.Build.Bindings
{ {
typeName += '<'; typeName += '<';
foreach (var arg in typeInfo.GenericArgs) foreach (var arg in typeInfo.GenericArgs)
typeName += GenerateCSharpNativeToManaged(buildData, arg, caller); typeName += GenerateCSharpNativeToManaged(buildData, arg, caller, marshalling);
typeName += '>'; typeName += '>';
} }
if (apiType != null) if (apiType != null)
{ {
if (marshalling && apiType.MarshalAs != null)
return GenerateCSharpNativeToManaged(buildData, apiType.MarshalAs, caller);
// Add reference to the namespace // Add reference to the namespace
CSharpUsedNamespaces.Add(apiType.Namespace); CSharpUsedNamespaces.Add(apiType.Namespace);
var apiTypeParent = apiType.Parent; var apiTypeParent = apiType.Parent;
@@ -419,11 +432,11 @@ namespace Flax.Build.Bindings
return typeName; 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 // Fixed-size array
if (typeInfo.IsArray) if (typeInfo.IsArray)
return GenerateCSharpNativeToManaged(buildData, typeInfo, caller); return GenerateCSharpNativeToManaged(buildData, typeInfo, caller, marshalling);
// Find API type info // Find API type info
var apiType = FindApiTypeInfo(buildData, typeInfo, caller); var apiType = FindApiTypeInfo(buildData, typeInfo, caller);
@@ -439,7 +452,7 @@ namespace Flax.Build.Bindings
} }
if (apiType.MarshalAs != null) if (apiType.MarshalAs != null)
return GenerateCSharpManagedToNativeType(buildData, new TypeInfo(apiType.MarshalAs), caller); return GenerateCSharpManagedToNativeType(buildData, apiType.MarshalAs, caller, marshalling);
if (apiType.IsScriptingObject || apiType.IsInterface) if (apiType.IsScriptingObject || apiType.IsInterface)
return "IntPtr"; return "IntPtr";
} }
@@ -452,7 +465,7 @@ namespace Flax.Build.Bindings
if (typeInfo.Type == "Function" && typeInfo.GenericArgs != null) if (typeInfo.Type == "Function" && typeInfo.GenericArgs != null)
return "IntPtr"; return "IntPtr";
return GenerateCSharpNativeToManaged(buildData, typeInfo, caller); return GenerateCSharpNativeToManaged(buildData, typeInfo, caller, marshalling);
} }
private static string GenerateCSharpManagedToNativeConverter(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller) private static string GenerateCSharpManagedToNativeConverter(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller)
@@ -485,6 +498,18 @@ namespace Flax.Build.Bindings
case "Function": case "Function":
// delegate // delegate
return "NativeInterop.GetFunctionPointerForDelegate({0})"; 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: default:
var apiType = FindApiTypeInfo(buildData, typeInfo, caller); var apiType = FindApiTypeInfo(buildData, typeInfo, caller);
if (apiType != null) if (apiType != null)
@@ -531,9 +556,9 @@ namespace Flax.Build.Bindings
{ {
var apiType = FindApiTypeInfo(buildData, functionInfo.ReturnType, caller); var apiType = FindApiTypeInfo(buildData, functionInfo.ReturnType, caller);
if (apiType != null && apiType.MarshalAs != null) if (apiType != null && apiType.MarshalAs != null)
returnValueType = GenerateCSharpNativeToManaged(buildData, new TypeInfo(apiType.MarshalAs), caller); returnValueType = GenerateCSharpNativeToManaged(buildData, apiType.MarshalAs, caller, true);
else else
returnValueType = GenerateCSharpNativeToManaged(buildData, functionInfo.ReturnType, caller); returnValueType = GenerateCSharpNativeToManaged(buildData, functionInfo.ReturnType, caller, true);
} }
#if USE_NETCORE #if USE_NETCORE
@@ -594,7 +619,7 @@ namespace Flax.Build.Bindings
contents.Append(", "); contents.Append(", ");
separator = true; separator = true;
var nativeType = GenerateCSharpManagedToNativeType(buildData, parameterInfo.Type, caller); var nativeType = GenerateCSharpManagedToNativeType(buildData, parameterInfo.Type, caller, true);
#if USE_NETCORE #if USE_NETCORE
string parameterMarshalType = ""; string parameterMarshalType = "";
if (nativeType == "System.Type") if (nativeType == "System.Type")
@@ -643,7 +668,7 @@ namespace Flax.Build.Bindings
contents.Append(", "); contents.Append(", ");
separator = true; separator = true;
var nativeType = GenerateCSharpManagedToNativeType(buildData, parameterInfo.Type, caller); var nativeType = GenerateCSharpManagedToNativeType(buildData, parameterInfo.Type, caller, true);
#if USE_NETCORE #if USE_NETCORE
string parameterMarshalType = ""; string parameterMarshalType = "";
if (parameterInfo.IsOut && parameterInfo.DefaultValue == "var __resultAsRef") 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 // Return result
if (functionInfo.Glue.UseReferenceForResult) if (functionInfo.Glue.UseReferenceForResult)

View File

@@ -19,7 +19,7 @@ namespace Flax.Build.Bindings
partial class BindingsGenerator partial class BindingsGenerator
{ {
private static readonly Dictionary<string, Type> TypeCache = new Dictionary<string, Type>(); private static readonly Dictionary<string, Type> TypeCache = new Dictionary<string, Type>();
private const int CacheVersion = 21; private const int CacheVersion = 22;
internal static void Write(BinaryWriter writer, string e) internal static void Write(BinaryWriter writer, string e)
{ {

View File

@@ -167,11 +167,7 @@ namespace Flax.Build.Bindings
return $"Variant(StringView({value}))"; return $"Variant(StringView({value}))";
if (typeInfo.Type == "StringAnsi") if (typeInfo.Type == "StringAnsi")
return $"Variant(StringAnsiView({value}))"; return $"Variant(StringAnsiView({value}))";
if (typeInfo.Type == "AssetReference" || if (typeInfo.IsObjectRef)
typeInfo.Type == "WeakAssetReference" ||
typeInfo.Type == "SoftAssetReference" ||
typeInfo.Type == "ScriptingObjectReference" ||
typeInfo.Type == "SoftObjectReference")
return $"Variant({value}.Get())"; return $"Variant({value}.Get())";
if (typeInfo.IsArray) if (typeInfo.IsArray)
{ {
@@ -227,10 +223,10 @@ namespace Flax.Build.Bindings
return $"(StringAnsiView){value}"; return $"(StringAnsiView){value}";
if (typeInfo.IsPtr && typeInfo.IsConst && typeInfo.Type == "Char") 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. 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") if (typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "SoftObjectReference")
return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((ScriptingObject*){value})"; return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((ScriptingObject*){value})";
if (typeInfo.IsObjectRef)
return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((Asset*){value})";
if (typeInfo.IsArray) if (typeInfo.IsArray)
throw new Exception($"Not supported type to convert from the Variant to fixed-size array '{typeInfo}[{typeInfo.ArraySize}]'."); 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) 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) private static string GenerateCppGetMClass(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, FunctionInfo functionInfo)
{ {
// Optimal path for in-build types // Optimal path for in-build types
var managedType = GenerateCSharpNativeToManaged(buildData, typeInfo, caller); var managedType = GenerateCSharpNativeToManaged(buildData, typeInfo, caller, true);
switch (managedType) switch (managedType)
{ {
// In-built types (cached by the engine on startup) // In-built types (cached by the engine on startup)
@@ -392,7 +388,7 @@ namespace Flax.Build.Bindings
CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MClass.h"); CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MClass.h");
// Optimal path for in-build types // Optimal path for in-build types
var managedType = GenerateCSharpNativeToManaged(buildData, typeInfo, caller); var managedType = GenerateCSharpNativeToManaged(buildData, typeInfo, caller, true);
switch (managedType) switch (managedType)
{ {
case "bool": case "bool":
@@ -514,11 +510,7 @@ namespace Flax.Build.Bindings
return "MUtils::ToManaged({0})"; return "MUtils::ToManaged({0})";
default: default:
// Object reference property // Object reference property
if ((typeInfo.Type == "ScriptingObjectReference" || if (typeInfo.IsObjectRef)
typeInfo.Type == "AssetReference" ||
typeInfo.Type == "WeakAssetReference" ||
typeInfo.Type == "SoftAssetReference" ||
typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null)
{ {
type = "MObject*"; type = "MObject*";
return "{0}.GetManagedInstance()"; return "{0}.GetManagedInstance()";
@@ -527,16 +519,28 @@ namespace Flax.Build.Bindings
// Array or DataContainer // Array or DataContainer
if ((typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "DataContainer") && typeInfo.GenericArgs != null) if ((typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "DataContainer") && typeInfo.GenericArgs != null)
{ {
var arrayTypeInfo = typeInfo.GenericArgs[0];
#if USE_NETCORE #if USE_NETCORE
// Boolean arrays does not support custom marshalling for some unknown reason // Boolean arrays does not support custom marshalling for some unknown reason
if (typeInfo.GenericArgs[0].Type == "bool") if (arrayTypeInfo.Type == "bool")
{ {
type = "bool*"; type = "bool*";
return "MUtils::ToBoolArray({0})"; return "MUtils::ToBoolArray({0})";
} }
var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller);
#endif #endif
type = "MArray*"; 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 // Span
@@ -597,7 +601,7 @@ namespace Flax.Build.Bindings
CppReferencesFiles.Add(apiType.File); CppReferencesFiles.Add(apiType.File);
if (apiType.MarshalAs != null) 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 // Scripting Object
if (apiType.IsScriptingObject) if (apiType.IsScriptingObject)
@@ -704,11 +708,7 @@ namespace Flax.Build.Bindings
return "MUtils::ToNative({0})"; return "MUtils::ToNative({0})";
default: default:
// Object reference property // Object reference property
if ((typeInfo.Type == "ScriptingObjectReference" || if (typeInfo.IsObjectRef)
typeInfo.Type == "AssetReference" ||
typeInfo.Type == "WeakAssetReference" ||
typeInfo.Type == "SoftAssetReference" ||
typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null)
{ {
// For non-pod types converting only, other API converts managed to unmanaged object in C# wrapper code) // For non-pod types converting only, other API converts managed to unmanaged object in C# wrapper code)
if (CppNonPodTypesConvertingGeneration) if (CppNonPodTypesConvertingGeneration)
@@ -731,11 +731,26 @@ namespace Flax.Build.Bindings
// Array // Array
if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null) if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null)
{ {
var T = typeInfo.GenericArgs[0].GetFullNameNative(buildData, caller); var arrayTypeInfo = typeInfo.GenericArgs[0];
type = "MArray*"; 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) if (typeInfo.GenericArgs.Count != 1)
return "MUtils::ToArray<" + T + ", " + typeInfo.GenericArgs[1] + ">({0})"; genericArgs += ", " + typeInfo.GenericArgs[1];
return "MUtils::ToArray<" + T + ">({0})";
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 // Span or DataContainer
@@ -801,7 +816,7 @@ namespace Flax.Build.Bindings
if (apiType != null) if (apiType != null)
{ {
if (apiType.MarshalAs != 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) // 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) if (CppNonPodTypesConvertingGeneration && apiType.IsScriptingObject && typeInfo.IsPtr)

Some files were not shown because too many files have changed in this diff Show More