Merge remote-tracking branch 'origin/master' into 1.8

This commit is contained in:
Wojtek Figat
2023-11-08 17:30:34 +01:00
164 changed files with 6623 additions and 2807 deletions

View File

@@ -0,0 +1,292 @@
using System;
using System.IO;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.Utilities;
namespace FlaxEditor.Content;
/// <summary>
/// Manages and converts the selected content item to the appropriate types. Useful for drag operations.
/// </summary>
public class AssetPickerValidator : IContentItemOwner
{
private Asset _selected;
private ContentItem _selectedItem;
private ScriptType _type;
private string _fileExtension;
/// <summary>
/// Gets or sets the selected item.
/// </summary>
public ContentItem SelectedItem
{
get => _selectedItem;
set
{
if (_selectedItem == value)
return;
if (value == null)
{
if (_selected == null && _selectedItem is SceneItem)
{
// Deselect scene reference
_selectedItem.RemoveReference(this);
_selectedItem = null;
_selected = null;
OnSelectedItemChanged();
return;
}
// Deselect
_selectedItem?.RemoveReference(this);
_selectedItem = null;
_selected = null;
OnSelectedItemChanged();
}
else if (value is SceneItem item)
{
if (_selectedItem == item)
return;
if (!IsValid(item))
item = null;
// Change value to scene reference (cannot load asset because scene can be already loaded - duplicated ID issue)
_selectedItem?.RemoveReference(this);
_selectedItem = item;
_selected = null;
_selectedItem?.AddReference(this);
OnSelectedItemChanged();
}
else if (value is AssetItem assetItem)
{
SelectedAsset = FlaxEngine.Content.LoadAsync(assetItem.ID);
}
else
{
// Change value
_selectedItem?.RemoveReference(this);
_selectedItem = value;
_selected = null;
OnSelectedItemChanged();
}
}
}
/// <summary>
/// Gets or sets the selected asset identifier.
/// </summary>
public Guid SelectedID
{
get
{
if (_selected != null)
return _selected.ID;
if (_selectedItem is AssetItem assetItem)
return assetItem.ID;
return Guid.Empty;
}
set => SelectedItem = Editor.Instance.ContentDatabase.FindAsset(value);
}
/// <summary>
/// Gets or sets the selected content item path.
/// </summary>
public string SelectedPath
{
get
{
string path = _selectedItem?.Path ?? _selected?.Path;
if (path != null)
{
// Convert into path relative to the project (cross-platform)
var projectFolder = Globals.ProjectFolder;
if (path.StartsWith(projectFolder))
path = path.Substring(projectFolder.Length + 1);
}
return path;
}
set
{
if (string.IsNullOrEmpty(value))
{
SelectedItem = null;
}
else
{
var path = StringUtils.IsRelative(value) ? Path.Combine(Globals.ProjectFolder, value) : value;
SelectedItem = Editor.Instance.ContentDatabase.Find(path);
}
}
}
/// <summary>
/// Gets or sets the selected asset object.
/// </summary>
public Asset SelectedAsset
{
get => _selected;
set
{
// Check if value won't change
if (value == _selected)
return;
// Find item from content database and check it
var item = value ? Editor.Instance.ContentDatabase.FindAsset(value.ID) : null;
if (item != null && !IsValid(item))
item = null;
// Change value
_selectedItem?.RemoveReference(this);
_selectedItem = item;
_selected = value;
_selectedItem?.AddReference(this);
OnSelectedItemChanged();
}
}
/// <summary>
/// Gets or sets the assets types that this picker accepts (it supports types derived from the given type). Use <see cref="ScriptType.Null"/> for generic file picker.
/// </summary>
public ScriptType AssetType
{
get => _type;
set
{
if (_type != value)
{
_type = value;
// Auto deselect if the current value is invalid
if (_selectedItem != null && !IsValid(_selectedItem))
SelectedItem = null;
}
}
}
/// <summary>
/// Gets or sets the content items extensions filter. Null if unused.
/// </summary>
public string FileExtension
{
get => _fileExtension;
set
{
if (_fileExtension != value)
{
_fileExtension = value;
// Auto deselect if the current value is invalid
if (_selectedItem != null && !IsValid(_selectedItem))
SelectedItem = null;
}
}
}
/// <summary>
/// Occurs when selected item gets changed.
/// </summary>
public event Action SelectedItemChanged;
/// <summary>
/// The custom callback for assets validation. Cane be used to implement a rule for assets to pick.
/// </summary>
public Func<ContentItem, bool> CheckValid;
/// <summary>
/// Returns whether item is valid.
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public bool IsValid(ContentItem item)
{
if (_fileExtension != null && !item.Path.EndsWith(_fileExtension))
return false;
if (CheckValid != null && !CheckValid(item))
return false;
if (_type == ScriptType.Null)
return true;
if (item is AssetItem assetItem)
{
// Faster path for binary items (in-built)
if (assetItem is BinaryAssetItem binaryItem)
return _type.IsAssignableFrom(new ScriptType(binaryItem.Type));
// Type filter
var type = TypeUtils.GetType(assetItem.TypeName);
if (_type.IsAssignableFrom(type))
return true;
// Json assets can contain any type of the object defined by the C# type (data oriented design)
if (assetItem is JsonAssetItem && (_type.Type == typeof(JsonAsset) || _type.Type == typeof(Asset)))
return true;
// Special case for scene asset references
if (_type.Type == typeof(SceneReference) && assetItem is SceneItem)
return true;
}
return false;
}
/// <summary>
/// Initializes a new instance of the <see cref="AssetPickerValidator"/> class.
/// </summary>
public AssetPickerValidator()
: this(new ScriptType(typeof(Asset)))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AssetPickerValidator"/> class.
/// </summary>
/// <param name="assetType">The assets types that this picker accepts.</param>
public AssetPickerValidator(ScriptType assetType)
{
_type = assetType;
}
/// <summary>
/// Called when selected item gets changed.
/// </summary>
protected virtual void OnSelectedItemChanged()
{
SelectedItemChanged?.Invoke();
}
/// <inheritdoc />
public void OnItemDeleted(ContentItem item)
{
// Deselect item
SelectedItem = null;
}
/// <inheritdoc />
public void OnItemRenamed(ContentItem item)
{
}
/// <inheritdoc />
public void OnItemReimported(ContentItem item)
{
}
/// <inheritdoc />
public void OnItemDispose(ContentItem item)
{
// Deselect item
SelectedItem = null;
}
/// <summary>
/// Call to remove reference from the selected item.
/// </summary>
public void OnDestroy()
{
_selectedItem?.RemoveReference(this);
_selectedItem = null;
_selected = null;
}
}

View File

@@ -157,6 +157,12 @@ namespace FlaxEditor.CustomEditors
var values = _values;
var presenter = _presenter;
var layout = _layout;
if (layout.Editors.Count != 1)
{
// There are more editors using the same layout so rebuild parent editor to prevent removing others editors
_parent?.RebuildLayout();
return;
}
var control = layout.ContainerControl;
var parent = _parent;
var parentScrollV = (_presenter?.Panel.Parent as Panel)?.VScrollBar?.Value ?? -1;

View File

@@ -34,7 +34,7 @@ namespace FlaxEditor.CustomEditors.Editors
value = 0;
// If selected is single actor that has children, ask if apply layer to the sub objects as well
if (Values.IsSingleObject && (int)Values[0] != value && ParentEditor.Values[0] is Actor actor && actor.HasChildren)
if (Values.IsSingleObject && (int)Values[0] != value && ParentEditor.Values[0] is Actor actor && actor.HasChildren && !Editor.IsPlayMode)
{
var valueText = comboBox.SelectedItem;

View File

@@ -71,7 +71,7 @@ namespace FlaxEditor.CustomEditors.Editors
{
// Generic file picker
assetType = ScriptType.Null;
Picker.FileExtension = assetReference.TypeName;
Picker.Validator.FileExtension = assetReference.TypeName;
}
else
{
@@ -85,7 +85,7 @@ namespace FlaxEditor.CustomEditors.Editors
}
}
Picker.AssetType = assetType;
Picker.Validator.AssetType = assetType;
Picker.Height = height;
Picker.SelectedItemChanged += OnSelectedItemChanged;
}
@@ -95,15 +95,15 @@ namespace FlaxEditor.CustomEditors.Editors
if (_isRefreshing)
return;
if (typeof(AssetItem).IsAssignableFrom(_valueType.Type))
SetValue(Picker.SelectedItem);
SetValue(Picker.Validator.SelectedItem);
else if (_valueType.Type == typeof(Guid))
SetValue(Picker.SelectedID);
SetValue(Picker.Validator.SelectedID);
else if (_valueType.Type == typeof(SceneReference))
SetValue(new SceneReference(Picker.SelectedID));
SetValue(new SceneReference(Picker.Validator.SelectedID));
else if (_valueType.Type == typeof(string))
SetValue(Picker.SelectedPath);
SetValue(Picker.Validator.SelectedPath);
else
SetValue(Picker.SelectedAsset);
SetValue(Picker.Validator.SelectedAsset);
}
/// <inheritdoc />
@@ -115,15 +115,15 @@ namespace FlaxEditor.CustomEditors.Editors
{
_isRefreshing = true;
if (Values[0] is AssetItem assetItem)
Picker.SelectedItem = assetItem;
Picker.Validator.SelectedItem = assetItem;
else if (Values[0] is Guid guid)
Picker.SelectedID = guid;
Picker.Validator.SelectedID = guid;
else if (Values[0] is SceneReference sceneAsset)
Picker.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID);
Picker.Validator.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID);
else if (Values[0] is string path)
Picker.SelectedPath = path;
Picker.Validator.SelectedPath = path;
else
Picker.SelectedAsset = Values[0] as Asset;
Picker.Validator.SelectedAsset = Values[0] as Asset;
_isRefreshing = false;
}
}

View File

@@ -3,9 +3,12 @@
using System;
using System.Collections;
using System.Linq;
using FlaxEditor.Content;
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.CustomEditors.GUI;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Drag;
using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -135,14 +138,43 @@ namespace FlaxEditor.CustomEditors.Editors
spacing = collection.Spacing;
}
var dragArea = layout.CustomContainer<DragAreaControl>();
dragArea.CustomControl.Editor = this;
dragArea.CustomControl.ElementType = ElementType;
// Check for the AssetReferenceAttribute. In JSON assets, it can be used to filter
// which scripts can be dragged over and dropped on this collection editor.
var assetReference = (AssetReferenceAttribute)attributes?.FirstOrDefault(x => x is AssetReferenceAttribute);
if (assetReference != null)
{
if (string.IsNullOrEmpty(assetReference.TypeName))
{
}
else if (assetReference.TypeName.Length > 1 && assetReference.TypeName[0] == '.')
{
dragArea.CustomControl.ElementType = ScriptType.Null;
dragArea.CustomControl.FileExtension = assetReference.TypeName;
}
else
{
var customType = TypeUtils.GetType(assetReference.TypeName);
if (customType != ScriptType.Null)
dragArea.CustomControl.ElementType = customType;
else if (!Content.Settings.GameSettings.OptionalPlatformSettings.Contains(assetReference.TypeName))
Debug.LogWarning(string.Format("Unknown asset type '{0}' to use for drag and drop filter.", assetReference.TypeName));
else
dragArea.CustomControl.ElementType = ScriptType.Void;
}
}
// Size
if (_readOnly || (NotNullItems && size == 0))
{
layout.Label("Size", size.ToString());
dragArea.Label("Size", size.ToString());
}
else
{
_size = layout.IntegerValue("Size");
_size = dragArea.IntegerValue("Size");
_size.IntValue.MinValue = 0;
_size.IntValue.MaxValue = ushort.MaxValue;
_size.IntValue.Value = size;
@@ -152,7 +184,7 @@ namespace FlaxEditor.CustomEditors.Editors
// Elements
if (size > 0)
{
var panel = layout.VerticalPanel();
var panel = dragArea.VerticalPanel();
panel.Panel.BackgroundColor = _background;
var elementType = ElementType;
@@ -212,37 +244,33 @@ namespace FlaxEditor.CustomEditors.Editors
// Add/Remove buttons
if (!_readOnly)
{
var area = layout.Space(20);
var addButton = new Button(area.ContainerControl.Width - (16 + 16 + 2 + 2), 2, 16, 16)
{
Text = "+",
TooltipText = "Add new item",
AnchorPreset = AnchorPresets.TopRight,
Parent = area.ContainerControl,
Enabled = !NotNullItems || size > 0,
};
addButton.Clicked += () =>
{
if (IsSetBlocked)
return;
var panel = dragArea.HorizontalPanel();
panel.Panel.Size = new Float2(0, 20);
panel.Panel.Margin = new Margin(2);
Resize(Count + 1);
};
var removeButton = new Button(addButton.Right + 2, addButton.Y, 16, 16)
{
Text = "-",
TooltipText = "Remove last item",
AnchorPreset = AnchorPresets.TopRight,
Parent = area.ContainerControl,
Enabled = size > 0,
};
removeButton.Clicked += () =>
var removeButton = panel.Button("-", "Remove last item");
removeButton.Button.Size = new Float2(16, 16);
removeButton.Button.Enabled = size > 0;
removeButton.Button.AnchorPreset = AnchorPresets.TopRight;
removeButton.Button.Clicked += () =>
{
if (IsSetBlocked)
return;
Resize(Count - 1);
};
var addButton = panel.Button("+", "Add new item");
addButton.Button.Size = new Float2(16, 16);
addButton.Button.Enabled = !NotNullItems || size > 0;
addButton.Button.AnchorPreset = AnchorPresets.TopRight;
addButton.Button.Clicked += () =>
{
if (IsSetBlocked)
return;
Resize(Count + 1);
};
}
}
@@ -369,5 +397,232 @@ namespace FlaxEditor.CustomEditors.Editors
}
return base.OnDirty(editor, value, token);
}
private class DragAreaControl : VerticalPanel
{
private DragItems _dragItems;
private DragActors _dragActors;
private DragHandlers _dragHandlers;
private AssetPickerValidator _pickerValidator;
public ScriptType ElementType
{
get => _pickerValidator?.AssetType ?? ScriptType.Null;
set => _pickerValidator = new AssetPickerValidator(value);
}
public CollectionEditor Editor { get; set; }
public string FileExtension
{
set => _pickerValidator.FileExtension = value;
}
/// <inheritdoc />
public override void Draw()
{
if (_dragHandlers is { HasValidDrag: true })
{
var area = new Rectangle(Float2.Zero, Size);
Render2D.FillRectangle(area, Color.Orange * 0.5f);
Render2D.DrawRectangle(area, Color.Black);
}
base.Draw();
}
public override void OnDestroy()
{
_pickerValidator.OnDestroy();
}
private bool ValidateActors(ActorNode node)
{
return node.Actor.GetScript(ElementType.Type) || ElementType.Type.IsAssignableTo(typeof(Actor));
}
/// <inheritdoc />
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
{
var result = base.OnDragEnter(ref location, data);
if (result != DragDropEffect.None)
return result;
if (_dragHandlers == null)
{
_dragItems = new DragItems(_pickerValidator.IsValid);
_dragActors = new DragActors(ValidateActors);
_dragHandlers = new DragHandlers
{
_dragActors,
_dragItems
};
}
return _dragHandlers.OnDragEnter(data);
}
/// <inheritdoc />
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
{
var result = base.OnDragMove(ref location, data);
if (result != DragDropEffect.None)
return result;
return _dragHandlers.Effect;
}
/// <inheritdoc />
public override void OnDragLeave()
{
_dragHandlers.OnDragLeave();
base.OnDragLeave();
}
/// <inheritdoc />
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
{
var result = base.OnDragDrop(ref location, data);
if (result != DragDropEffect.None)
{
_dragHandlers.OnDragDrop(null);
return result;
}
if (_dragHandlers.HasValidDrag)
{
if (_dragItems.HasValidDrag)
{
var list = Editor.CloneValues();
if (list == null)
{
if (Editor.Values.Type.IsArray)
{
list = TypeUtils.CreateArrayInstance(Editor.Values.Type.GetElementType(), 0);
}
else
{
list = Editor.Values.Type.CreateInstance() as IList;
}
}
if (list.IsFixedSize)
{
var oldSize = list.Count;
var newSize = list.Count + _dragItems.Objects.Count;
var type = Editor.Values.Type.GetElementType();
var array = TypeUtils.CreateArrayInstance(type, newSize);
list.CopyTo(array, 0);
for (var i = oldSize; i < newSize; i++)
{
var validator = new AssetPickerValidator
{
FileExtension = _pickerValidator.FileExtension,
AssetType = _pickerValidator.AssetType,
SelectedItem = _dragItems.Objects[i - oldSize],
};
if (typeof(AssetItem).IsAssignableFrom(ElementType.Type))
array.SetValue(validator.SelectedItem, i);
else if (ElementType.Type == typeof(Guid))
array.SetValue(validator.SelectedID, i);
else if (ElementType.Type == typeof(SceneReference))
array.SetValue(new SceneReference(validator.SelectedID), i);
else if (ElementType.Type == typeof(string))
array.SetValue(validator.SelectedPath, i);
else
array.SetValue(validator.SelectedAsset, i);
validator.OnDestroy();
}
Editor.SetValue(array);
}
else
{
foreach (var item in _dragItems.Objects)
{
var validator = new AssetPickerValidator
{
FileExtension = _pickerValidator.FileExtension,
AssetType = _pickerValidator.AssetType,
SelectedItem = item,
};
if (typeof(AssetItem).IsAssignableFrom(ElementType.Type))
list.Add(validator.SelectedItem);
else if (ElementType.Type == typeof(Guid))
list.Add(validator.SelectedID);
else if (ElementType.Type == typeof(SceneReference))
list.Add(new SceneReference(validator.SelectedID));
else if (ElementType.Type == typeof(string))
list.Add(validator.SelectedPath);
else
list.Add(validator.SelectedAsset);
validator.OnDestroy();
}
Editor.SetValue(list);
}
}
else if (_dragActors.HasValidDrag)
{
var list = Editor.CloneValues();
if (list == null)
{
if (Editor.Values.Type.IsArray)
{
list = TypeUtils.CreateArrayInstance(Editor.Values.Type.GetElementType(), 0);
}
else
{
list = Editor.Values.Type.CreateInstance() as IList;
}
}
if (list.IsFixedSize)
{
var oldSize = list.Count;
var newSize = list.Count + _dragActors.Objects.Count;
var type = Editor.Values.Type.GetElementType();
var array = TypeUtils.CreateArrayInstance(type, newSize);
list.CopyTo(array, 0);
for (var i = oldSize; i < newSize; i++)
{
var actor = _dragActors.Objects[i - oldSize].Actor;
if (ElementType.Type.IsAssignableTo(typeof(Actor)))
{
array.SetValue(actor, i);
}
else
{
array.SetValue(actor.GetScript(ElementType.Type), i);
}
}
Editor.SetValue(array);
}
else
{
foreach (var actorNode in _dragActors.Objects)
{
if (ElementType.Type.IsAssignableTo(typeof(Actor)))
{
list.Add(actorNode.Actor);
}
else
{
list.Add(actorNode.Actor.GetScript(ElementType.Type));
}
}
Editor.SetValue(list);
}
}
_dragHandlers.OnDragDrop(null);
}
return result;
}
}
}
}

View File

@@ -72,14 +72,14 @@ namespace FlaxEditor.CustomEditors.Editors
return;
_isRefreshing = true;
var slots = _modelInstance.MaterialSlots;
var material = _materialEditor.Picker.SelectedAsset as MaterialBase;
var material = _materialEditor.Picker.Validator.SelectedAsset as MaterialBase;
var defaultMaterial = GPUDevice.Instance.DefaultMaterial;
var value = (ModelInstanceEntry)Values[0];
var prevMaterial = value.Material;
if (!material)
{
// Fallback to default material
_materialEditor.Picker.SelectedAsset = defaultMaterial;
_materialEditor.Picker.Validator.SelectedAsset = defaultMaterial;
value.Material = defaultMaterial;
}
else if (material == slots[_entryIndex].Material)

View File

@@ -5,6 +5,7 @@ using System.IO;
using FlaxEditor.Content;
using FlaxEditor.GUI.Drag;
using FlaxEditor.Scripting;
using FlaxEditor.Utilities;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
@@ -17,189 +18,21 @@ namespace FlaxEditor.GUI
/// <seealso cref="Control" />
/// <seealso cref="IContentItemOwner" />
[HideInEditor]
public class AssetPicker : Control, IContentItemOwner
public class AssetPicker : Control
{
private const float DefaultIconSize = 64;
private const float ButtonsOffset = 2;
private const float ButtonsSize = 12;
private Asset _selected;
private ContentItem _selectedItem;
private ScriptType _type;
private string _fileExtension;
private bool _isMouseDown;
private Float2 _mouseDownPos;
private Float2 _mousePos;
private DragItems _dragOverElement;
/// <summary>
/// Gets or sets the selected item.
/// The asset validator. Used to ensure only appropriate items can be picked.
/// </summary>
public ContentItem SelectedItem
{
get => _selectedItem;
set
{
if (_selectedItem == value)
return;
if (value == null)
{
if (_selected == null && _selectedItem is SceneItem)
{
// Deselect scene reference
_selectedItem.RemoveReference(this);
_selectedItem = null;
_selected = null;
OnSelectedItemChanged();
return;
}
// Deselect
_selectedItem?.RemoveReference(this);
_selectedItem = null;
_selected = null;
OnSelectedItemChanged();
}
else if (value is SceneItem item)
{
if (_selectedItem == item)
return;
if (!IsValid(item))
item = null;
// Change value to scene reference (cannot load asset because scene can be already loaded - duplicated ID issue)
_selectedItem?.RemoveReference(this);
_selectedItem = item;
_selected = null;
_selectedItem?.AddReference(this);
OnSelectedItemChanged();
}
else if (value is AssetItem assetItem)
{
SelectedAsset = FlaxEngine.Content.LoadAsync(assetItem.ID);
}
else
{
// Change value
_selectedItem?.RemoveReference(this);
_selectedItem = value;
_selected = null;
OnSelectedItemChanged();
}
}
}
/// <summary>
/// Gets or sets the selected asset identifier.
/// </summary>
public Guid SelectedID
{
get
{
if (_selected != null)
return _selected.ID;
if (_selectedItem is AssetItem assetItem)
return assetItem.ID;
return Guid.Empty;
}
set => SelectedItem = Editor.Instance.ContentDatabase.FindAsset(value);
}
/// <summary>
/// Gets or sets the selected content item path.
/// </summary>
public string SelectedPath
{
get
{
string path = _selectedItem?.Path ?? _selected?.Path;
if (path != null)
{
// Convert into path relative to the project (cross-platform)
var projectFolder = Globals.ProjectFolder;
if (path.StartsWith(projectFolder))
path = path.Substring(projectFolder.Length + 1);
}
return path;
}
set
{
if (string.IsNullOrEmpty(value))
{
SelectedItem = null;
}
else
{
var path = StringUtils.IsRelative(value) ? Path.Combine(Globals.ProjectFolder, value) : value;
SelectedItem = Editor.Instance.ContentDatabase.Find(path);
}
}
}
/// <summary>
/// Gets or sets the selected asset object.
/// </summary>
public Asset SelectedAsset
{
get => _selected;
set
{
// Check if value won't change
if (value == _selected)
return;
// Find item from content database and check it
var item = value ? Editor.Instance.ContentDatabase.FindAsset(value.ID) : null;
if (item != null && !IsValid(item))
item = null;
// Change value
_selectedItem?.RemoveReference(this);
_selectedItem = item;
_selected = value;
_selectedItem?.AddReference(this);
OnSelectedItemChanged();
}
}
/// <summary>
/// Gets or sets the assets types that this picker accepts (it supports types derived from the given type). Use <see cref="ScriptType.Null"/> for generic file picker.
/// </summary>
public ScriptType AssetType
{
get => _type;
set
{
if (_type != value)
{
_type = value;
// Auto deselect if the current value is invalid
if (_selectedItem != null && !IsValid(_selectedItem))
SelectedItem = null;
}
}
}
/// <summary>
/// Gets or sets the content items extensions filter. Null if unused.
/// </summary>
public string FileExtension
{
get => _fileExtension;
set
{
if (_fileExtension != value)
{
_fileExtension = value;
// Auto deselect if the current value is invalid
if (_selectedItem != null && !IsValid(_selectedItem))
SelectedItem = null;
}
}
}
public AssetPickerValidator Validator { get; }
/// <summary>
/// Occurs when selected item gets changed.
@@ -216,38 +49,6 @@ namespace FlaxEditor.GUI
/// </summary>
public bool CanEdit = true;
private bool IsValid(ContentItem item)
{
if (_fileExtension != null && !item.Path.EndsWith(_fileExtension))
return false;
if (CheckValid != null && !CheckValid(item))
return false;
if (_type == ScriptType.Null)
return true;
if (item is AssetItem assetItem)
{
// Faster path for binary items (in-built)
if (assetItem is BinaryAssetItem binaryItem)
return _type.IsAssignableFrom(new ScriptType(binaryItem.Type));
// Type filter
var type = TypeUtils.GetType(assetItem.TypeName);
if (_type.IsAssignableFrom(type))
return true;
// Json assets can contain any type of the object defined by the C# type (data oriented design)
if (assetItem is JsonAssetItem && (_type.Type == typeof(JsonAsset) || _type.Type == typeof(Asset)))
return true;
// Special case for scene asset references
if (_type.Type == typeof(SceneReference) && assetItem is SceneItem)
return true;
}
return false;
}
/// <summary>
/// Initializes a new instance of the <see cref="AssetPicker"/> class.
/// </summary>
@@ -264,7 +65,8 @@ namespace FlaxEditor.GUI
public AssetPicker(ScriptType assetType, Float2 location)
: base(location, new Float2(DefaultIconSize + ButtonsOffset + ButtonsSize, DefaultIconSize))
{
_type = assetType;
Validator = new AssetPickerValidator(assetType);
Validator.SelectedItemChanged += OnSelectedItemChanged;
_mousePos = Float2.Minimum;
}
@@ -275,10 +77,10 @@ namespace FlaxEditor.GUI
{
// Update tooltip
string tooltip;
if (_selectedItem is AssetItem assetItem)
if (Validator.SelectedItem is AssetItem assetItem)
tooltip = assetItem.NamePath;
else
tooltip = SelectedPath;
tooltip = Validator.SelectedPath;
TooltipText = tooltip;
SelectedItemChanged?.Invoke();
@@ -289,37 +91,13 @@ namespace FlaxEditor.GUI
// Do the drag drop operation if has selected element
if (new Rectangle(Float2.Zero, Size).Contains(ref _mouseDownPos))
{
if (_selected != null)
DoDragDrop(DragAssets.GetDragData(_selected));
else if (_selectedItem != null)
DoDragDrop(DragItems.GetDragData(_selectedItem));
if (Validator.SelectedAsset != null)
DoDragDrop(DragAssets.GetDragData(Validator.SelectedAsset));
else if (Validator.SelectedItem != null)
DoDragDrop(DragItems.GetDragData(Validator.SelectedItem));
}
}
/// <inheritdoc />
public void OnItemDeleted(ContentItem item)
{
// Deselect item
SelectedItem = null;
}
/// <inheritdoc />
public void OnItemRenamed(ContentItem item)
{
}
/// <inheritdoc />
public void OnItemReimported(ContentItem item)
{
}
/// <inheritdoc />
public void OnItemDispose(ContentItem item)
{
// Deselect item
SelectedItem = null;
}
private Rectangle IconRect => new Rectangle(0, 0, Height, Height);
private Rectangle Button1Rect => new Rectangle(Height + ButtonsOffset, 0, ButtonsSize, ButtonsSize);
@@ -341,10 +119,10 @@ namespace FlaxEditor.GUI
if (CanEdit)
Render2D.DrawSprite(style.ArrowDown, button1Rect, button1Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
if (_selectedItem != null)
if (Validator.SelectedItem != null)
{
// Draw item preview
_selectedItem.DrawThumbnail(ref iconRect);
Validator.SelectedItem.DrawThumbnail(ref iconRect);
// Draw buttons
if (CanEdit)
@@ -363,7 +141,7 @@ namespace FlaxEditor.GUI
{
Render2D.DrawText(
style.FontSmall,
_selectedItem.ShortName,
Validator.SelectedItem.ShortName,
new Rectangle(button1Rect.Right + 2, 0, sizeForTextLeft, ButtonsSize),
style.Foreground,
TextAlignment.Near,
@@ -371,7 +149,7 @@ namespace FlaxEditor.GUI
}
}
// Check if has no item but has an asset (eg. virtual asset)
else if (_selected)
else if (Validator.SelectedAsset)
{
// Draw remove button
Render2D.DrawSprite(style.Cross, button3Rect, button3Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
@@ -380,8 +158,8 @@ namespace FlaxEditor.GUI
float sizeForTextLeft = Width - button1Rect.Right;
if (sizeForTextLeft > 30)
{
var name = _selected.GetType().Name;
if (_selected.IsVirtual)
var name = Validator.SelectedAsset.GetType().Name;
if (Validator.SelectedAsset.IsVirtual)
name += " (virtual)";
Render2D.DrawText(
style.FontSmall,
@@ -407,9 +185,7 @@ namespace FlaxEditor.GUI
/// <inheritdoc />
public override void OnDestroy()
{
_selectedItem?.RemoveReference(this);
_selectedItem = null;
_selected = null;
Validator.OnDestroy();
base.OnDestroy();
}
@@ -463,57 +239,57 @@ namespace FlaxEditor.GUI
// Buttons logic
if (!CanEdit)
{
if (Button1Rect.Contains(location) && _selectedItem != null)
if (Button1Rect.Contains(location) && Validator.SelectedItem != null)
{
// Select asset
Editor.Instance.Windows.ContentWin.Select(_selectedItem);
Editor.Instance.Windows.ContentWin.Select(Validator.SelectedItem);
}
}
else if (Button1Rect.Contains(location))
{
Focus();
if (_type != ScriptType.Null)
if (Validator.AssetType != ScriptType.Null)
{
// Show asset picker popup
var popup = AssetSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, item =>
var popup = AssetSearchPopup.Show(this, Button1Rect.BottomLeft, Validator.IsValid, item =>
{
SelectedItem = item;
Validator.SelectedItem = item;
RootWindow.Focus();
Focus();
});
if (_selected != null)
if (Validator.SelectedAsset != null)
{
var selectedAssetName = Path.GetFileNameWithoutExtension(_selected.Path);
var selectedAssetName = Path.GetFileNameWithoutExtension(Validator.SelectedAsset.Path);
popup.ScrollToAndHighlightItemByName(selectedAssetName);
}
}
else
{
// Show content item picker popup
var popup = ContentSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, item =>
var popup = ContentSearchPopup.Show(this, Button1Rect.BottomLeft, Validator.IsValid, item =>
{
SelectedItem = item;
Validator.SelectedItem = item;
RootWindow.Focus();
Focus();
});
if (_selectedItem != null)
if (Validator.SelectedItem != null)
{
popup.ScrollToAndHighlightItemByName(_selectedItem.ShortName);
popup.ScrollToAndHighlightItemByName(Validator.SelectedItem.ShortName);
}
}
}
else if (_selected != null || _selectedItem != null)
else if (Validator.SelectedAsset != null || Validator.SelectedItem != null)
{
if (Button2Rect.Contains(location) && _selectedItem != null)
if (Button2Rect.Contains(location) && Validator.SelectedItem != null)
{
// Select asset
Editor.Instance.Windows.ContentWin.Select(_selectedItem);
Editor.Instance.Windows.ContentWin.Select(Validator.SelectedItem);
}
else if (Button3Rect.Contains(location))
{
// Deselect asset
Focus();
SelectedItem = null;
Validator.SelectedItem = null;
}
}
}
@@ -540,10 +316,10 @@ namespace FlaxEditor.GUI
{
Focus();
if (_selectedItem != null && IconRect.Contains(location))
if (Validator.SelectedItem != null && IconRect.Contains(location))
{
// Open it
Editor.Instance.ContentEditing.Open(_selectedItem);
Editor.Instance.ContentEditing.Open(Validator.SelectedItem);
}
// Handled
@@ -557,7 +333,7 @@ namespace FlaxEditor.GUI
// Check if drop asset
if (_dragOverElement == null)
_dragOverElement = new DragItems(IsValid);
_dragOverElement = new DragItems(Validator.IsValid);
if (CanEdit && _dragOverElement.OnDragEnter(data))
{
}
@@ -590,7 +366,7 @@ namespace FlaxEditor.GUI
if (CanEdit && _dragOverElement.HasValidDrag)
{
// Select element
SelectedItem = _dragOverElement.Objects[0];
Validator.SelectedItem = _dragOverElement.Objects[0];
}
// Clear cache

View File

@@ -39,6 +39,11 @@ namespace FlaxEditor.GUI.Dialogs
/// </summary>
public DialogResult Result => _result;
/// <summary>
/// Returns the size of the dialog.
/// </summary>
public Float2 DialogSize => _dialogSize;
/// <summary>
/// Initializes a new instance of the <see cref="Dialog"/> class.
/// </summary>

View File

@@ -465,36 +465,47 @@ namespace FlaxEditor.GUI.Docking
{
if (Parent.Parent is SplitPanel splitter)
{
// Check if has any child panels
var childPanel = new List<DockPanel>(_childPanels);
for (int i = 0; i < childPanel.Count; i++)
// Check if there is another nested dock panel inside this dock panel and extract it here
var childPanels = _childPanels.ToArray();
if (childPanels.Length != 0)
{
// Undock all tabs
var panel = childPanel[i];
int count = panel.TabsCount;
while (count-- > 0)
// Move tabs from child panels into this one
DockWindow selectedTab = null;
foreach (var childPanel in childPanels)
{
panel.GetTab(0).Close();
var childPanelTabs = childPanel.Tabs.ToArray();
for (var i = 0; i < childPanelTabs.Length; i++)
{
var childPanelTab = childPanelTabs[i];
if (selectedTab == null && childPanelTab.IsSelected)
selectedTab = childPanelTab;
childPanel.UndockWindow(childPanelTab);
AddTab(childPanelTab, false);
}
}
if (selectedTab != null)
SelectTab(selectedTab);
}
// Unlink splitter
var splitterParent = splitter.Parent;
Assert.IsNotNull(splitterParent);
splitter.Parent = null;
// Move controls from second split panel to the split panel parent
var scrPanel = Parent == splitter.Panel2 ? splitter.Panel1 : splitter.Panel2;
var srcPanelChildrenCount = scrPanel.ChildrenCount;
for (int i = srcPanelChildrenCount - 1; i >= 0 && scrPanel.ChildrenCount > 0; i--)
else
{
scrPanel.GetChild(i).Parent = splitterParent;
}
Assert.IsTrue(scrPanel.ChildrenCount == 0);
Assert.IsTrue(splitterParent.ChildrenCount == srcPanelChildrenCount);
// Unlink splitter
var splitterParent = splitter.Parent;
Assert.IsNotNull(splitterParent);
splitter.Parent = null;
// Delete
splitter.Dispose();
// Move controls from second split panel to the split panel parent
var scrPanel = Parent == splitter.Panel2 ? splitter.Panel1 : splitter.Panel2;
var srcPanelChildrenCount = scrPanel.ChildrenCount;
for (int i = srcPanelChildrenCount - 1; i >= 0 && scrPanel.ChildrenCount > 0; i--)
{
scrPanel.GetChild(i).Parent = splitterParent;
}
Assert.IsTrue(scrPanel.ChildrenCount == 0);
Assert.IsTrue(splitterParent.ChildrenCount == srcPanelChildrenCount);
// Delete
splitter.Dispose();
}
}
else if (!IsMaster)
{
@@ -582,19 +593,17 @@ namespace FlaxEditor.GUI.Docking
/// Adds the tab.
/// </summary>
/// <param name="window">The window to insert as a tab.</param>
protected virtual void AddTab(DockWindow window)
/// <param name="autoSelect">True if auto-select newly added tab.</param>
protected virtual void AddTab(DockWindow window, bool autoSelect = true)
{
// Dock
_tabs.Add(window);
window.ParentDockPanel = this;
// Select tab
SelectTab(window);
if (autoSelect)
SelectTab(window);
}
private void CreateTabsProxy()
{
// Check if has no tabs proxy created
if (_tabsProxy == null)
{
// Create proxy and make set simple full dock

View File

@@ -72,7 +72,7 @@ namespace FlaxEditor.GUI.Docking
settings.Size = size;
settings.Position = location;
settings.MinimumSize = new Float2(1);
settings.MaximumSize = new Float2(4096);
settings.MaximumSize = Float2.Zero; // Unlimited size
settings.Fullscreen = false;
settings.HasBorder = true;
settings.SupportsTransparency = false;

View File

@@ -13,6 +13,8 @@ namespace FlaxEditor.GUI.Tabs
[HideInEditor]
public class Tab : ContainerControl
{
internal Tabs _selectedInTabs;
/// <summary>
/// Gets or sets the text.
/// </summary>
@@ -86,5 +88,25 @@ namespace FlaxEditor.GUI.Tabs
{
return new Tabs.TabHeader((Tabs)Parent, this);
}
/// <inheritdoc />
protected override void OnParentChangedInternal()
{
if (_selectedInTabs != null)
_selectedInTabs.SelectedTab = null;
base.OnParentChangedInternal();
}
/// <inheritdoc />
public override void OnDestroy()
{
if (IsDisposing)
return;
if (_selectedInTabs != null)
_selectedInTabs.SelectedTab = null;
base.OnDestroy();
}
}
}

View File

@@ -263,7 +263,12 @@ namespace FlaxEditor.GUI.Tabs
// Check if index will change
if (_selectedIndex != index)
{
SelectedTab?.OnDeselected();
var prev = SelectedTab;
if (prev != null)
{
prev._selectedInTabs = null;
prev.OnDeselected();
}
_selectedIndex = index;
PerformLayout();
OnSelectedTabChanged();
@@ -342,8 +347,13 @@ namespace FlaxEditor.GUI.Tabs
/// </summary>
protected virtual void OnSelectedTabChanged()
{
var selectedTab = SelectedTab;
SelectedTabChanged?.Invoke(this);
SelectedTab?.OnSelected();
if (selectedTab != null)
{
selectedTab._selectedInTabs = this;
selectedTab.OnSelected();
}
}
/// <inheritdoc />

View File

@@ -39,7 +39,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
if (AssetID == value?.ID)
return;
AssetID = value?.ID ?? Guid.Empty;
_picker.SelectedAsset = value;
_picker.Validator.SelectedAsset = value;
OnAssetChanged();
Timeline?.MarkAsEdited();
}
@@ -63,10 +63,10 @@ namespace FlaxEditor.GUI.Timeline.Tracks
private void OnPickerSelectedItemChanged()
{
if (Asset == (TAsset)_picker.SelectedAsset)
if (Asset == (TAsset)_picker.Validator.SelectedAsset)
return;
using (new TrackUndoBlock(this))
Asset = (TAsset)_picker.SelectedAsset;
Asset = (TAsset)_picker.Validator.SelectedAsset;
}
/// <summary>

View File

@@ -266,6 +266,19 @@ namespace FlaxEditor.Modules
Editor.StateMachine.ChangingScenesState.LoadScene(sceneId, additive);
}
/// <summary>
/// Reload all loaded scenes.
/// </summary>
public void ReloadScenes()
{
foreach (var scene in Level.Scenes)
{
var sceneId = scene.ID;
if (!Level.UnloadScene(scene))
Level.LoadScene(sceneId);
}
}
/// <summary>
/// Closes scene (async).
/// </summary>

View File

@@ -147,6 +147,17 @@ namespace FlaxEditor.Modules.SourceCodeEditing
}
if (key != null)
xml.TryGetValue(key, out text);
// Customize tooltips for properties to be more human-readable in UI
if (text != null && memberType.HasFlag(MemberTypes.Property) && text.StartsWith("Gets or sets ", StringComparison.Ordinal))
{
text = text.Substring(13);
unsafe
{
fixed (char* e = text)
e[0] = char.ToUpper(e[0]);
}
}
}
}

View File

@@ -39,6 +39,7 @@ namespace FlaxEditor.Modules
ContextMenuSingleSelectGroup<int> _numberOfClientsGroup = new ContextMenuSingleSelectGroup<int>();
private ContextMenuButton _menuFileSaveScenes;
private ContextMenuButton _menuFileReloadScenes;
private ContextMenuButton _menuFileCloseScenes;
private ContextMenuButton _menuFileOpenScriptsProject;
private ContextMenuButton _menuFileGenerateScriptsProjectFiles;
@@ -470,13 +471,13 @@ namespace FlaxEditor.Modules
// Place dialog nearby the target control
var targetControlDesktopCenter = targetControl.PointToScreen(targetControl.Size * 0.5f);
var desktopSize = Platform.GetMonitorBounds(targetControlDesktopCenter);
var pos = targetControlDesktopCenter + new Float2(10.0f, -dialog.Height * 0.5f);
var dialogEnd = pos + dialog.Size;
var pos = targetControlDesktopCenter + new Float2(10.0f, -dialog.DialogSize.Y * 0.5f);
var dialogEnd = pos + dialog.DialogSize;
var desktopEnd = desktopSize.BottomRight - new Float2(10.0f);
if (dialogEnd.X >= desktopEnd.X || dialogEnd.Y >= desktopEnd.Y)
pos = targetControl.PointToScreen(Float2.Zero) - new Float2(10.0f + dialog.Width, dialog.Height);
pos = targetControl.PointToScreen(Float2.Zero) - new Float2(10.0f + dialog.DialogSize.X, dialog.DialogSize.Y);
var desktopBounds = Platform.VirtualDesktopBounds;
pos = Float2.Clamp(pos, desktopBounds.UpperLeft, desktopBounds.BottomRight - dialog.Size);
pos = Float2.Clamp(pos, desktopBounds.UpperLeft, desktopBounds.BottomRight - dialog.DialogSize);
dialog.RootWindow.Window.Position = pos;
// Register for context menu (prevent auto-closing context menu when selecting color)
@@ -527,6 +528,7 @@ namespace FlaxEditor.Modules
_menuFileSaveAll = cm.AddButton("Save All", inputOptions.Save, Editor.SaveAll);
_menuFileSaveScenes = cm.AddButton("Save scenes", inputOptions.SaveScenes, Editor.Scene.SaveScenes);
_menuFileCloseScenes = cm.AddButton("Close scenes", inputOptions.CloseScenes, Editor.Scene.CloseAllScenes);
_menuFileReloadScenes = cm.AddButton("Reload scenes", Editor.Scene.ReloadScenes);
cm.AddSeparator();
_menuFileOpenScriptsProject = cm.AddButton("Open scripts project", inputOptions.OpenScriptsProject, Editor.CodeEditing.OpenSolution);
_menuFileGenerateScriptsProjectFiles = cm.AddButton("Generate scripts project files", inputOptions.GenerateScriptsProject, Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync);
@@ -830,6 +832,7 @@ namespace FlaxEditor.Modules
_menuFileSaveScenes.Enabled = hasOpenedScene;
_menuFileCloseScenes.Enabled = hasOpenedScene;
_menuFileReloadScenes.Enabled = hasOpenedScene;
_menuFileGenerateScriptsProjectFiles.Enabled = !Editor.ProgressReporting.GenerateScriptsProjectFiles.IsActive;
c.PerformLayout();

View File

@@ -171,9 +171,13 @@ namespace FlaxEditor.Modules
var mainWindow = MainWindow;
if (mainWindow)
{
var projectPath = Globals.ProjectFolder.Replace('/', '\\');
var platformBit = Platform.Is64BitApp ? "64" : "32";
var title = string.Format("Flax Editor - \'{0}\' ({1}-bit)", projectPath, platformBit);
var projectPath = Globals.ProjectFolder;
#if PLATFORM_WINDOWS
projectPath = projectPath.Replace('/', '\\');
#endif
var engineVersion = Editor.EngineProject.Version;
var engineVersionText = engineVersion.Revision > 0 ? $"{engineVersion.Major}.{engineVersion.Minor}.{engineVersion.Revision}" : $"{engineVersion.Major}.{engineVersion.Minor}";
var title = $"Flax Editor {engineVersionText} - \'{projectPath}\'";
mainWindow.Title = title;
}
}
@@ -735,7 +739,6 @@ namespace FlaxEditor.Modules
settings.Size = Platform.DesktopSize * 0.75f;
settings.StartPosition = WindowStartPosition.CenterScreen;
settings.ShowAfterFirstPaint = true;
#if PLATFORM_WINDOWS
if (!Editor.Instance.Options.Options.Interface.UseNativeWindowSystem)
{
@@ -747,12 +750,9 @@ namespace FlaxEditor.Modules
#elif PLATFORM_LINUX
settings.HasBorder = false;
#endif
MainWindow = Platform.CreateWindow(ref settings);
if (MainWindow == null)
{
// Error
Editor.LogError("Failed to create editor main window!");
return;
}

View File

@@ -76,6 +76,10 @@ namespace FlaxEditor.Options
[EditorDisplay("Common"), EditorOrder(230)]
public InputBinding RotateSelection = new InputBinding(KeyboardKeys.R);
[DefaultValue(typeof(InputBinding), "F11")]
[EditorDisplay("Common"), EditorOrder(240)]
public InputBinding ToggleFullscreen = new InputBinding(KeyboardKeys.F11);
#endregion
#region File
@@ -208,16 +212,20 @@ namespace FlaxEditor.Options
[EditorDisplay("Debugger", "Continue"), EditorOrder(810)]
public InputBinding DebuggerContinue = new InputBinding(KeyboardKeys.F5);
[DefaultValue(typeof(InputBinding), "Shift+F11")]
[EditorDisplay("Debugger", "Unlock mouse in Play Mode"), EditorOrder(820)]
public InputBinding DebuggerUnlockMouse = new InputBinding(KeyboardKeys.F11, KeyboardKeys.Shift);
[DefaultValue(typeof(InputBinding), "F10")]
[EditorDisplay("Debugger", "Step Over"), EditorOrder(820)]
[EditorDisplay("Debugger", "Step Over"), EditorOrder(830)]
public InputBinding DebuggerStepOver = new InputBinding(KeyboardKeys.F10);
[DefaultValue(typeof(InputBinding), "F11")]
[EditorDisplay("Debugger", "Step Into"), EditorOrder(830)]
[EditorDisplay("Debugger", "Step Into"), EditorOrder(840)]
public InputBinding DebuggerStepInto = new InputBinding(KeyboardKeys.F11);
[DefaultValue(typeof(InputBinding), "Shift+F11")]
[EditorDisplay("Debugger", "Step Out"), EditorOrder(840)]
[EditorDisplay("Debugger", "Step Out"), EditorOrder(850)]
public InputBinding DebuggerStepOut = new InputBinding(KeyboardKeys.F11, KeyboardKeys.Shift);
#endregion

View File

@@ -7,6 +7,7 @@ using Real = System.Single;
#endif
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.SceneGraph.Actors
{
@@ -30,6 +31,13 @@ namespace FlaxEditor.SceneGraph.Actors
// Rotate to match the space (GUI uses upper left corner as a root)
Actor.LocalOrientation = Quaternion.Euler(0, -180, -180);
var uiControl = new UIControl
{
Name = "Canvas Scalar",
Transform = Actor.Transform,
Control = new CanvasScaler()
};
Root.Spawn(uiControl, Actor);
}
/// <inheritdoc />

View File

@@ -266,7 +266,7 @@ namespace FlaxEditor.SceneGraph.GUI
/// <summary>
/// Starts the actor renaming action.
/// </summary>
public void StartRenaming(EditorWindow window)
public void StartRenaming(EditorWindow window, Panel treePanel = null)
{
// Block renaming during scripts reload
if (Editor.Instance.ProgressReporting.CompileScripts.IsActive)
@@ -281,7 +281,13 @@ namespace FlaxEditor.SceneGraph.GUI
(window as PrefabWindow).ScrollingOnTreeView(false);
// Start renaming the actor
var dialog = RenamePopup.Show(this, TextRect, _actorNode.Name, false);
var rect = TextRect;
if (treePanel != null)
{
treePanel.ScrollViewTo(this, true);
rect.Size = new Float2(treePanel.Width - TextRect.Location.X, TextRect.Height);
}
var dialog = RenamePopup.Show(this, rect, _actorNode.Name, false);
dialog.Renamed += OnRenamed;
dialog.Closed += popup =>
{

View File

@@ -56,12 +56,14 @@ namespace FlaxEditor.States
else if (Editor.Options.Options.General.ForceScriptCompilationOnStartup && !skipCompile)
{
// Generate project files when Cache is missing or was cleared previously
if (!Directory.Exists(Path.Combine(Editor.GameProject?.ProjectFolderPath, "Cache", "Intermediate")) ||
!Directory.Exists(Path.Combine(Editor.GameProject?.ProjectFolderPath, "Cache", "Projects")))
var projectFolderPath = Editor.GameProject?.ProjectFolderPath;
if (!Directory.Exists(Path.Combine(projectFolderPath, "Cache", "Intermediate")) ||
!Directory.Exists(Path.Combine(projectFolderPath, "Cache", "Projects")))
{
var customArgs = Editor.Instance.CodeEditing.SelectedEditor.GenerateProjectCustomArgs;
var customArgs = Editor.CodeEditing.SelectedEditor?.GenerateProjectCustomArgs;
ScriptsBuilder.GenerateProject(customArgs);
}
// Compile scripts before loading any scenes, then we load them and can open scenes
ScriptsBuilder.Compile();
}

View File

@@ -465,7 +465,7 @@ namespace FlaxEditor.Surface.Archetypes
if (selectedIndex != -1)
{
var index = 5 + selectedIndex * 2;
SetValue(index, _animationPicker.SelectedID);
SetValue(index, _animationPicker.Validator.SelectedID);
}
}
@@ -495,7 +495,7 @@ namespace FlaxEditor.Surface.Archetypes
{
if (isValid)
{
_animationPicker.SelectedID = data1;
_animationPicker.Validator.SelectedID = data1;
_animationSpeed.Value = data0.W;
var path = string.Empty;
@@ -505,7 +505,7 @@ namespace FlaxEditor.Surface.Archetypes
}
else
{
_animationPicker.SelectedID = Guid.Empty;
_animationPicker.Validator.SelectedID = Guid.Empty;
_animationSpeed.Value = 1.0f;
}
_animationPicker.Enabled = isValid;

View File

@@ -60,7 +60,7 @@ namespace FlaxEditor.Surface.Archetypes
Op1(1, "Bitwise NOT", "Negates the value using bitwise operation", new[] { "!", "~" }),
Op2(2, "Bitwise AND", "Performs a bitwise conjunction on two values", new[] { "&" }),
Op2(3, "Bitwise OR", "", new[] { "|" }),
Op2(4, "Bitwise XOR", ""),
Op2(4, "Bitwise XOR", "", new[] { "^" }),
};
}
}

View File

@@ -60,7 +60,7 @@ namespace FlaxEditor.Surface.Archetypes
Op1(1, "Boolean NOT", "Negates the boolean value", new[] { "!", "~" }),
Op2(2, "Boolean AND", "Performs a logical conjunction on two values", new[] { "&&" }),
Op2(3, "Boolean OR", "Returns true if either (or both) of its operands is true", new[] { "||" }),
Op2(4, "Boolean XOR", ""),
Op2(4, "Boolean XOR", "", new [] { "^" } ),
Op2(5, "Boolean NOR", ""),
Op2(6, "Boolean NAND", ""),
};

View File

@@ -14,7 +14,7 @@ namespace FlaxEditor.Surface.Archetypes
[HideInEditor]
public static class Comparisons
{
private static NodeArchetype Op(ushort id, string title, string desc)
private static NodeArchetype Op(ushort id, string title, string desc, string[] altTitles = null)
{
return new NodeArchetype
{
@@ -22,6 +22,7 @@ namespace FlaxEditor.Surface.Archetypes
Title = title,
Description = desc,
Flags = NodeFlags.AllGraphs,
AlternativeTitles = altTitles,
ConnectionsHints = ConnectionsHint.Value,
Size = new Float2(100, 40),
IndependentBoxes = new[]
@@ -170,12 +171,12 @@ namespace FlaxEditor.Surface.Archetypes
/// </summary>
public static NodeArchetype[] Nodes =
{
Op(1, "==", "Determines whether two values are equal"),
Op(2, "!=", "Determines whether two values are not equal"),
Op(3, ">", "Determines whether the first value is greater than the other"),
Op(4, "<", "Determines whether the first value is less than the other"),
Op(5, "<=", "Determines whether the first value is less or equal to the other"),
Op(6, ">=", "Determines whether the first value is greater or equal to the other"),
Op(1, "==", "Determines whether two values are equal", new[] { "equals" }),
Op(2, "!=", "Determines whether two values are not equal", new[] { "not equals" }),
Op(3, ">", "Determines whether the first value is greater than the other", new[] { "greater than", "larger than", "bigger than" }),
Op(4, "<", "Determines whether the first value is less than the other", new[] { "less than", "smaller than" }),
Op(5, "<=", "Determines whether the first value is less or equal to the other", new[] { "less equals than", "smaller equals than" }),
Op(6, ">=", "Determines whether the first value is greater or equal to the other", new[] { "greater equals than", "larger equals than", "bigger equals than" }),
new NodeArchetype
{
TypeID = 7,

View File

@@ -7,11 +7,12 @@ using Real = System.Single;
#endif
using System;
using System.Reflection;
using System.Linq;
using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.GUI;
using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
using FlaxEditor.Surface.Undo;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
@@ -24,6 +25,109 @@ namespace FlaxEditor.Surface.Archetypes
[HideInEditor]
public static class Constants
{
/// <summary>
/// A special type of node that adds the functionality to convert nodes to parameters.
/// </summary>
internal class ConvertToParameterNode : SurfaceNode
{
private readonly ScriptType _type;
private readonly Func<object[], object> _convertFunction;
/// <inheritdoc />
public ConvertToParameterNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch, ScriptType type, Func<object[], object> convertFunction = null)
: base(id, context, nodeArch, groupArch)
{
_type = type;
_convertFunction = convertFunction;
}
/// <inheritdoc />
public override void OnShowSecondaryContextMenu(FlaxEditor.GUI.ContextMenu.ContextMenu menu, Float2 location)
{
base.OnShowSecondaryContextMenu(menu, location);
menu.AddSeparator();
menu.AddButton("Convert to Parameter", OnConvertToParameter);
}
private void OnConvertToParameter()
{
if (Surface.Owner is not IVisjectSurfaceWindow window)
throw new Exception("Surface owner is not a Visject Surface Window");
Asset asset = Surface.Owner.SurfaceAsset;
if (asset == null || !asset.IsLoaded)
{
Editor.LogError("Asset is null or not loaded");
return;
}
// Add parameter to editor
var paramIndex = Surface.Parameters.Count;
var initValue = _convertFunction == null ? Values[0] : _convertFunction.Invoke(Values);
var paramAction = new AddRemoveParamAction
{
Window = window,
IsAdd = true,
Name = Utilities.Utils.IncrementNameNumber("New parameter", OnParameterRenameValidate),
Type = _type,
Index = paramIndex,
InitValue = initValue,
};
paramAction.Do();
Surface.AddBatchedUndoAction(paramAction);
// Spawn Get Parameter Node based on the added parameter
Guid parameterGuid = Surface.Parameters[paramIndex].ID;
bool undoEnabled = Surface.Undo.Enabled;
Surface.Undo.Enabled = false;
NodeArchetype arch = Surface.GetParameterGetterNodeArchetype(out var groupId);
SurfaceNode node = Surface.Context.SpawnNode(groupId, arch.TypeID, Location, new object[] { parameterGuid });
Surface.Undo.Enabled = undoEnabled;
if (node is not Parameters.SurfaceNodeParamsGet getNode)
throw new Exception("Node is not a ParamsGet node!");
Surface.AddBatchedUndoAction(new AddRemoveNodeAction(getNode, true));
// Recreate connections of constant node
// Constant nodes and parameter nodes should have the same ports, so we can just iterate through the connections
var editConnectionsAction1 = new EditNodeConnections(Context, this);
var editConnectionsAction2 = new EditNodeConnections(Context, node);
var boxes = GetBoxes();
for (int i = 0; i < boxes.Count; i++)
{
Box box = boxes[i];
if (!box.HasAnyConnection)
continue;
if (!getNode.TryGetBox(box.ID, out Box paramBox))
continue;
// Iterating backwards, because the CreateConnection method deletes entries from the box connections when target box IsSingle is set to true
for (int k = box.Connections.Count - 1; k >= 0; k--)
{
Box connectedBox = box.Connections[k];
paramBox.CreateConnection(connectedBox);
}
}
editConnectionsAction1.End();
editConnectionsAction2.End();
Surface.AddBatchedUndoAction(editConnectionsAction1);
Surface.AddBatchedUndoAction(editConnectionsAction2);
// Add undo actions and remove constant node
var removeConstantAction = new AddRemoveNodeAction(this, false);
Surface.AddBatchedUndoAction(removeConstantAction);
removeConstantAction.Do();
Surface.MarkAsEdited();
}
private bool OnParameterRenameValidate(string value)
{
if (Surface.Owner is not IVisjectSurfaceWindow window)
throw new Exception("Surface owner is not a Visject Surface Window");
return !string.IsNullOrWhiteSpace(value) && window.VisjectSurface.Parameters.All(x => x.Name != value);
}
}
private class EnumNode : SurfaceNode
{
private EnumComboBox _picker;
@@ -356,6 +460,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 1,
Title = "Bool",
Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(bool))),
Description = "Constant boolean value",
Flags = NodeFlags.AllGraphs,
Size = new Float2(110, 20),
@@ -388,6 +493,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 2,
Title = "Integer",
Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(int))),
Description = "Constant integer value",
Flags = NodeFlags.AllGraphs,
Size = new Float2(110, 20),
@@ -415,6 +521,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 3,
Title = "Float",
Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(float))),
Description = "Constant floating point",
Flags = NodeFlags.AllGraphs,
Size = new Float2(110, 20),
@@ -442,6 +549,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 4,
Title = "Float2",
Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(Float2))),
Description = "Constant Float2",
Flags = NodeFlags.AllGraphs,
Size = new Float2(130, 60),
@@ -472,6 +580,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 5,
Title = "Float3",
Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(Float3))),
Description = "Constant Float3",
Flags = NodeFlags.AllGraphs,
Size = new Float2(130, 80),
@@ -504,6 +613,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 6,
Title = "Float4",
Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(Float4))),
Description = "Constant Float4",
Flags = NodeFlags.AllGraphs,
Size = new Float2(130, 100),
@@ -538,6 +648,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 7,
Title = "Color",
Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(Color))),
Description = "RGBA color",
Flags = NodeFlags.AllGraphs,
Size = new Float2(70, 100),
@@ -570,6 +681,8 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 8,
Title = "Rotation",
Create = (id, context, arch, groupArch) =>
new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(Quaternion)), values => Quaternion.Euler((float)values[0], (float)values[1], (float)values[2])),
Description = "Euler angle rotation",
Flags = NodeFlags.AnimGraph | NodeFlags.VisualScriptGraph | NodeFlags.ParticleEmitterGraph,
Size = new Float2(110, 60),
@@ -594,6 +707,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 9,
Title = "String",
Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(string))),
Description = "Text",
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph,
Size = new Float2(200, 20),
@@ -644,6 +758,8 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 12,
Title = "Unsigned Integer",
AlternativeTitles = new[] { "UInt", "U Int" },
Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(uint))),
Description = "Constant unsigned integer value",
Flags = NodeFlags.AllGraphs,
Size = new Float2(170, 20),
@@ -683,6 +799,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 15,
Title = "Double",
Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(double))),
Description = "Constant floating point",
Flags = NodeFlags.AllGraphs,
Size = new Float2(110, 20),
@@ -700,6 +817,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 16,
Title = "Vector2",
Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(Vector2))),
Description = "Constant Vector2",
Flags = NodeFlags.AllGraphs,
Size = new Float2(130, 60),
@@ -720,6 +838,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 17,
Title = "Vector3",
Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(Vector3))),
Description = "Constant Vector3",
Flags = NodeFlags.AllGraphs,
Size = new Float2(130, 80),
@@ -742,6 +861,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 18,
Title = "Vector4",
Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(Vector4))),
Description = "Constant Vector4",
Flags = NodeFlags.AllGraphs,
Size = new Float2(130, 100),

View File

@@ -95,7 +95,7 @@ namespace FlaxEditor.Surface.Archetypes
private void OnAssetPickerSelectedItemChanged()
{
SetValue(0, _assetPicker.SelectedID);
SetValue(0, _assetPicker.Validator.SelectedID);
}
private void TryRestoreConnections(Box box, Box[] prevBoxes, ref NodeElementArchetype arch)
@@ -133,7 +133,7 @@ namespace FlaxEditor.Surface.Archetypes
var prevOutputs = _outputs;
// Extract function signature parameters (inputs and outputs packed)
_asset = LoadSignature(_assetPicker.SelectedID, out var typeNames, out var names);
_asset = LoadSignature(_assetPicker.Validator.SelectedID, out var typeNames, out var names);
if (typeNames != null && names != null)
{
var types = new Type[typeNames.Length];
@@ -174,7 +174,7 @@ namespace FlaxEditor.Surface.Archetypes
_outputs[i] = box;
}
Title = _assetPicker.SelectedItem.ShortName;
Title = _assetPicker.Validator.SelectedItem.ShortName;
}
else
{
@@ -2470,6 +2470,7 @@ namespace FlaxEditor.Surface.Archetypes
Title = string.Empty,
Description = "Overrides the base class method with custom implementation",
Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI | NodeFlags.NoSpawnViaPaste,
SortScore = 10,
IsInputCompatible = MethodOverrideNode.IsInputCompatible,
IsOutputCompatible = MethodOverrideNode.IsOutputCompatible,
Size = new Float2(240, 60),

View File

@@ -13,6 +13,11 @@ namespace FlaxEditor.Surface.Archetypes
public static class Math
{
private static NodeArchetype Op1(ushort id, string title, string desc, ConnectionsHint hints = ConnectionsHint.Numeric, Type type = null)
{
return Op1(id, title, desc, null, hints, type);
}
private static NodeArchetype Op1(ushort id, string title, string desc, string[] altTitles, ConnectionsHint hints = ConnectionsHint.Numeric, Type type = null)
{
return new NodeArchetype
{
@@ -20,6 +25,7 @@ namespace FlaxEditor.Surface.Archetypes
Title = title,
Description = desc,
Flags = NodeFlags.AllGraphs,
AlternativeTitles = altTitles,
Size = new Float2(110, 20),
DefaultType = new ScriptType(type),
ConnectionsHints = hints,
@@ -92,6 +98,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 11,
Title = "Length",
AlternativeTitles = new[] { "Magnitude", "Mag" },
Description = "Returns the length of A vector",
Flags = NodeFlags.AllGraphs,
Size = new Float2(110, 20),
@@ -107,10 +114,10 @@ namespace FlaxEditor.Surface.Archetypes
Op1(13, "Round", "Rounds A to the nearest integer"),
Op1(14, "Saturate", "Clamps A to the range [0, 1]"),
Op1(15, "Sine", "Returns sine of A"),
Op1(16, "Sqrt", "Returns square root of A"),
Op1(16, "Sqrt", "Returns square root of A", new [] { "Square Root", "Square", "Root" }),
Op1(17, "Tangent", "Returns tangent of A"),
Op2(18, "Cross", "Returns the cross product of A and B", ConnectionsHint.None, typeof(Float3)),
Op2(19, "Distance", "Returns a distance scalar between A and B", ConnectionsHint.Vector, null, typeof(float), false),
Op2(19, "Distance", "Returns a distance scalar between A and B", new [] { "Magnitude", "Mag", "Length" }, ConnectionsHint.Vector, null, typeof(float), false),
Op2(20, "Dot", "Returns the dot product of A and B", ConnectionsHint.Vector, null, typeof(float), false),
Op2(21, "Max", "Selects the greater of A and B"),
Op2(22, "Min", "Selects the lesser of A and B"),
@@ -185,7 +192,7 @@ namespace FlaxEditor.Surface.Archetypes
}
},
//
Op1(27, "Negate", "Returns opposite value"),
Op1(27, "Negate", "Returns opposite value", new [] { "Invert" }),
Op1(28, "One Minus", "Returns 1 - value"),
//
new NodeArchetype
@@ -225,6 +232,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 31,
Title = "Mad",
AlternativeTitles = new [] { "Multiply", "Add", "*+" },
Description = "Performs value multiplication and addition at once",
Flags = NodeFlags.AllGraphs,
Size = new Float2(160, 60),

View File

@@ -3,6 +3,7 @@
using System;
using FlaxEditor.Content.Settings;
using FlaxEditor.GUI;
using FlaxEditor.Scripting;
using FlaxEngine;
namespace FlaxEditor.Surface.Archetypes
@@ -95,6 +96,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 1,
Title = "Texture",
Create = (id, context, arch, groupArch) => new Constants.ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(Texture))),
Description = "Two dimensional texture object",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(140, 120),
@@ -131,6 +133,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 3,
Title = "Cube Texture",
Create = (id, context, arch, groupArch) => new Constants.ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(CubeTexture))),
Description = "Set of 6 textures arranged in a cube",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(140, 120),
@@ -154,6 +157,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 4,
Title = "Normal Map",
Create = (id, context, arch, groupArch) => new Constants.ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(NormalMap))),
Description = "Two dimensional texture object sampled as a normal map",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(140, 120),

View File

@@ -1483,7 +1483,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 11,
Title = "Comment",
AlternativeTitles = new[] { "//" },
AlternativeTitles = new[] { "//" , "Group" },
TryParseText = (string filterText, out object[] data) =>
{
data = null;
@@ -1510,6 +1510,7 @@ namespace FlaxEditor.Surface.Archetypes
"Comment", // Title
new Color(1.0f, 1.0f, 1.0f, 0.2f), // Color
new Float2(400.0f, 400.0f), // Size
-1, // Order
},
},
CurveNode<float>.GetArchetype(12, "Curve", typeof(float), 0.0f, 1.0f),
@@ -1638,6 +1639,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 22,
Title = "As",
AlternativeTitles = new [] { "Cast" },
Create = (id, context, arch, groupArch) => new AsNode(id, context, arch, groupArch),
Description = "Casts the object to a different type. Returns null if cast fails.",
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph,

View File

@@ -78,6 +78,7 @@ namespace FlaxEditor.Surface.ContextMenu
if (!Visible)
return;
SortScore += _archetype.SortScore;
if (selectedBox != null && CanConnectTo(selectedBox))
SortScore += 1;
if (Data != null)

View File

@@ -38,13 +38,13 @@ namespace FlaxEditor.Surface.Elements
private void OnNodeValuesChanged()
{
SelectedID = (Guid)ParentNode.Values[Archetype.ValueIndex];
Validator.SelectedID = (Guid)ParentNode.Values[Archetype.ValueIndex];
}
/// <inheritdoc />
protected override void OnSelectedItemChanged()
{
var selectedId = SelectedID;
var selectedId = Validator.SelectedID;
if (ParentNode != null && (Guid)ParentNode.Values[Archetype.ValueIndex] != selectedId)
{
ParentNode.SetValue(Archetype.ValueIndex, selectedId);

View File

@@ -149,6 +149,11 @@ namespace FlaxEditor.Surface
/// </summary>
public object Tag;
/// <summary>
/// Custom score value to use when sorting node archetypes in Editor. If positive (eg. 1, 2) can be used to add more importance for a specific node type.
/// </summary>
public float SortScore;
/// <summary>
/// Default node values. This array supports types: bool, int, float, Vector2, Vector3, Vector4, Color, Rectangle, Guid, string, Matrix and byte[].
/// </summary>
@@ -204,14 +209,17 @@ namespace FlaxEditor.Surface
Size = Size,
Flags = Flags,
Title = Title,
Description = Title,
SubTitle = SubTitle,
Description = Description,
AlternativeTitles = (string[])AlternativeTitles?.Clone(),
Tag = Tag,
SortScore = SortScore,
DefaultValues = (object[])DefaultValues?.Clone(),
DefaultType = DefaultType,
ConnectionsHints = ConnectionsHints,
IndependentBoxes = (int[])IndependentBoxes?.Clone(),
DependentBoxes = (int[])DependentBoxes?.Clone(),
DependentBoxFilter = DependentBoxFilter,
Elements = (NodeElementArchetype[])Elements?.Clone(),
TryParseText = TryParseText,
};

View File

@@ -93,7 +93,7 @@ namespace FlaxEditor.Surface
}
/// <inheritdoc />
protected override NodeArchetype GetParameterGetterNodeArchetype(out ushort groupId)
protected internal override NodeArchetype GetParameterGetterNodeArchetype(out ushort groupId)
{
groupId = 6;
return Archetypes.Parameters.Nodes[1];

View File

@@ -2,6 +2,7 @@
using System;
using FlaxEditor.GUI;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -52,6 +53,12 @@ namespace FlaxEditor.Surface
set => SetValue(2, value, false);
}
private int OrderValue
{
get => (int)Values[3];
set => SetValue(3, value, false);
}
/// <inheritdoc />
public SurfaceComment(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
@@ -67,6 +74,19 @@ namespace FlaxEditor.Surface
Title = TitleValue;
Color = ColorValue;
Size = SizeValue;
// Order
// Backwards compatibility - When opening with an older version send the old comments to the back
if (Values.Length < 4)
{
if (IndexInParent > 0)
IndexInParent = 0;
OrderValue = IndexInParent;
}
else if(OrderValue != -1)
{
IndexInParent = OrderValue;
}
}
/// <inheritdoc />
@@ -76,6 +96,10 @@ namespace FlaxEditor.Surface
// Randomize color
Color = ColorValue = Color.FromHSV(new Random().NextFloat(0, 360), 0.7f, 0.25f, 0.8f);
if(OrderValue == -1)
OrderValue = Context.CommentCount - 1;
IndexInParent = OrderValue;
}
/// <inheritdoc />
@@ -314,5 +338,38 @@ namespace FlaxEditor.Surface
Color = ColorValue = color;
Surface.MarkAsEdited(false);
}
/// <inheritdoc />
public override void OnShowSecondaryContextMenu(FlaxEditor.GUI.ContextMenu.ContextMenu menu, Float2 location)
{
base.OnShowSecondaryContextMenu(menu, location);
menu.AddSeparator();
ContextMenuChildMenu cmOrder = menu.AddChildMenu("Order");
{
cmOrder.ContextMenu.AddButton("Bring Forward", () =>
{
if(IndexInParent < Context.CommentCount-1)
IndexInParent++;
OrderValue = IndexInParent;
});
cmOrder.ContextMenu.AddButton("Bring to Front", () =>
{
IndexInParent = Context.CommentCount-1;
OrderValue = IndexInParent;
});
cmOrder.ContextMenu.AddButton("Send Backward", () =>
{
if(IndexInParent > 0)
IndexInParent--;
OrderValue = IndexInParent;
});
cmOrder.ContextMenu.AddButton("Send to Back", () =>
{
IndexInParent = 0;
OrderValue = IndexInParent;
});
}
}
}
}

View File

@@ -3,7 +3,6 @@
using System;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.Utilities;
namespace FlaxEditor.Surface
{
@@ -27,7 +26,7 @@ namespace FlaxEditor.Surface
/// <summary>
/// Parameter unique ID
/// </summary>
public Guid ID;
public Guid ID = Guid.Empty;
/// <summary>
/// Parameter name
@@ -49,23 +48,5 @@ namespace FlaxEditor.Surface
/// </summary>
[NoSerialize, HideInEditor]
public readonly SurfaceMeta Meta = new SurfaceMeta();
/// <summary>
/// Creates the new parameter of the given type.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="name">The name.</param>
/// <returns>The created parameter.</returns>
public static SurfaceParameter Create(ScriptType type, string name)
{
return new SurfaceParameter
{
ID = Guid.NewGuid(),
IsPublic = true,
Name = name,
Type = type,
Value = TypeUtils.GetDefaultValue(type),
};
}
}
}

View File

@@ -151,7 +151,7 @@ namespace FlaxEditor.Surface
/// </summary>
/// <param name="groupId">The group ID.</param>
/// <returns>The node archetype.</returns>
protected virtual NodeArchetype GetParameterGetterNodeArchetype(out ushort groupId)
protected internal virtual NodeArchetype GetParameterGetterNodeArchetype(out ushort groupId)
{
groupId = 6;
return Archetypes.Parameters.Nodes[0];

View File

@@ -716,7 +716,18 @@ namespace FlaxEditor.Surface
return null;
Rectangle surfaceArea = GetNodesBounds(selection).MakeExpanded(80.0f);
return _context.CreateComment(ref surfaceArea, string.IsNullOrEmpty(text) ? "Comment" : text, new Color(1.0f, 1.0f, 1.0f, 0.2f));
// Order below other selected comments
bool hasCommentsSelected = false;
int lowestCommentOrder = int.MaxValue;
for (int i = 0; i < selection.Count; i++)
{
if (selection[i] is not SurfaceComment || selection[i].IndexInParent >= lowestCommentOrder)
continue;
hasCommentsSelected = true;
lowestCommentOrder = selection[i].IndexInParent;
}
return _context.CreateComment(ref surfaceArea, string.IsNullOrEmpty(text) ? "Comment" : text, new Color(1.0f, 1.0f, 1.0f, 0.2f), hasCommentsSelected ? lowestCommentOrder : -1);
}
private static Rectangle GetNodesBounds(List<SurfaceNode> nodes)

View File

@@ -920,12 +920,6 @@ namespace FlaxEditor.Surface
// Link control
control.OnLoaded(action);
control.Parent = RootControl;
if (control is SurfaceComment)
{
// Move comments to the background
control.IndexInParent = 0;
}
}
/// <summary>

View File

@@ -85,6 +85,27 @@ namespace FlaxEditor.Surface
}
}
/// <summary>
/// Gets the amount of surface comments
/// </summary>
/// <remarks>
/// This is used as an alternative to <see cref="Comments"/>, if only the amount of comments is important.
/// Is faster and doesn't allocate as much memory
/// </remarks>
public int CommentCount
{
get
{
int count = 0;
for (int i = 0; i < RootControl.Children.Count; i++)
{
if (RootControl.Children[i] is SurfaceComment)
count++;
}
return count;
}
}
/// <summary>
/// Gets a value indicating whether this context is modified (needs saving and flushing with surface data context source).
/// </summary>
@@ -285,14 +306,16 @@ namespace FlaxEditor.Surface
/// <param name="surfaceArea">The surface area to create comment.</param>
/// <param name="title">The comment title.</param>
/// <param name="color">The comment color.</param>
/// <param name="customOrder">The comment order or -1 to use default.</param>
/// <returns>The comment object</returns>
public virtual SurfaceComment SpawnComment(ref Rectangle surfaceArea, string title, Color color)
public virtual SurfaceComment SpawnComment(ref Rectangle surfaceArea, string title, Color color, int customOrder = -1)
{
var values = new object[]
{
title, // Title
color, // Color
surfaceArea.Size, // Size
customOrder, // Order
};
return (SurfaceComment)SpawnNode(7, 11, surfaceArea.Location, values);
}
@@ -303,11 +326,12 @@ namespace FlaxEditor.Surface
/// <param name="surfaceArea">The surface area to create comment.</param>
/// <param name="title">The comment title.</param>
/// <param name="color">The comment color.</param>
/// <param name="customOrder">The comment order or -1 to use default.</param>
/// <returns>The comment object</returns>
public SurfaceComment CreateComment(ref Rectangle surfaceArea, string title, Color color)
public SurfaceComment CreateComment(ref Rectangle surfaceArea, string title, Color color, int customOrder = -1)
{
// Create comment
var comment = SpawnComment(ref surfaceArea, title, color);
var comment = SpawnComment(ref surfaceArea, title, color, customOrder);
if (comment == null)
{
Editor.LogWarning("Failed to create comment.");

View File

@@ -17,6 +17,7 @@ using FlaxEditor.Viewport.Previews;
using FlaxEditor.Windows.Assets;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
namespace FlaxEditor.Surface
{
@@ -258,6 +259,11 @@ namespace FlaxEditor.Surface
/// </summary>
public IVisjectSurfaceWindow Window;
/// <summary>
/// The identifier of the parameter. Empty to auto generate it.
/// </summary>
public Guid Id = Guid.NewGuid();
/// <summary>
/// True if adding, false if removing parameter.
/// </summary>
@@ -278,6 +284,11 @@ namespace FlaxEditor.Surface
/// </summary>
public ScriptType Type;
/// <summary>
/// The value to initialize the parameter with. Can be null to use default one for the parameter type.
/// </summary>
public object InitValue;
/// <inheritdoc />
public string ActionString => IsAdd ? "Add parameter" : "Remove parameter";
@@ -304,7 +315,14 @@ namespace FlaxEditor.Surface
var type = Type;
if (IsAdd && type.Type == typeof(NormalMap))
type = new ScriptType(typeof(Texture));
var param = SurfaceParameter.Create(type, Name);
var param = new SurfaceParameter
{
ID = Id,
IsPublic = true,
Name = Name,
Type = type,
Value = InitValue ?? TypeUtils.GetDefaultValue(type),
};
if (IsAdd && Type.Type == typeof(NormalMap))
param.Value = FlaxEngine.Content.LoadAsyncInternal<Texture>("Engine/Textures/NormalTexture");
Window.VisjectSurface.Parameters.Insert(Index, param);
@@ -725,6 +743,8 @@ namespace FlaxEditor.Surface
protected VisjectSurfaceWindow(Editor editor, AssetItem item, bool useTabs = false)
: base(editor, item)
{
var inputOptions = Editor.Options.Options.Input;
// Undo
_undo = new FlaxEditor.Undo();
_undo.UndoDone += OnUndoRedo;
@@ -775,10 +795,10 @@ namespace FlaxEditor.Surface
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
_undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
_redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
_undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
_redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
_toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip("Open content search tool (Ctrl+F)");
_toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip($"Open content search tool ({inputOptions.Search})");
_toolstrip.AddButton(editor.Icons.CenterView64, ShowWholeGraph).LinkTooltip("Show whole graph");
// Setup input actions
@@ -1058,7 +1078,6 @@ namespace FlaxEditor.Surface
public virtual void OnParamRemoveUndo()
{
_refreshPropertiesOnLoad = true;
//_propertiesEditor.BuildLayoutOnUpdate();
_propertiesEditor.BuildLayout();
}

View File

@@ -144,7 +144,7 @@ namespace FlaxEditor.Surface
}
/// <inheritdoc />
protected override NodeArchetype GetParameterGetterNodeArchetype(out ushort groupId)
protected internal override NodeArchetype GetParameterGetterNodeArchetype(out ushort groupId)
{
groupId = 6;
return Archetypes.Parameters.Nodes[2];

View File

@@ -290,7 +290,7 @@ namespace FlaxEditor.Tools.Terrain
var patchCoord = Gizmo.SelectedPatchCoord;
var chunkCoord = Gizmo.SelectedChunkCoord;
var action = new EditChunkMaterialAction(CarveTab.SelectedTerrain, ref patchCoord, ref chunkCoord, _chunkOverrideMaterial.SelectedAsset as MaterialBase);
var action = new EditChunkMaterialAction(CarveTab.SelectedTerrain, ref patchCoord, ref chunkCoord, _chunkOverrideMaterial.Validator.SelectedAsset as MaterialBase);
action.Do();
CarveTab.Editor.Undo.AddAction(action);
}
@@ -336,12 +336,12 @@ namespace FlaxEditor.Tools.Terrain
_isUpdatingUI = true;
if (terrain.HasPatch(ref patchCoord))
{
_chunkOverrideMaterial.SelectedAsset = terrain.GetChunkOverrideMaterial(ref patchCoord, ref chunkCoord);
_chunkOverrideMaterial.Validator.SelectedAsset = terrain.GetChunkOverrideMaterial(ref patchCoord, ref chunkCoord);
_chunkOverrideMaterial.Enabled = true;
}
else
{
_chunkOverrideMaterial.SelectedAsset = null;
_chunkOverrideMaterial.Validator.SelectedAsset = null;
_chunkOverrideMaterial.Enabled = false;
}
_isUpdatingUI = false;

View File

@@ -46,14 +46,14 @@ namespace FlaxEditor.Windows
if (asset != null)
{
var path = asset.Path;
picker.SelectedAsset = asset;
picker.Validator.SelectedAsset = asset;
Title = System.IO.Path.GetFileNameWithoutExtension(path);
TooltipText = asset.TypeName + '\n' + path;
}
else
{
picker.SelectedID = AssetId;
var assetItem = picker.SelectedItem as AssetItem;
picker.Validator.SelectedID = AssetId;
var assetItem = picker.Validator.SelectedItem as AssetItem;
if (assetItem != null)
{
Title = assetItem.ShortName;

View File

@@ -230,6 +230,8 @@ namespace FlaxEditor.Windows.Assets
public AnimationWindow(Editor editor, AssetItem item)
: base(editor, item)
{
var inputOptions = Editor.Options.Options.Input;
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
@@ -265,8 +267,8 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
_undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
_redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
_undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
_redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/animation/animation/index.html")).LinkTooltip("See documentation to learn more");

View File

@@ -130,6 +130,8 @@ namespace FlaxEditor.Windows.Assets
public BehaviorTreeWindow(Editor editor, BinaryAssetItem item)
: base(editor, item)
{
var inputOptions = Editor.Options.Options.Input;
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
@@ -172,10 +174,10 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
_undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
_redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
_undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
_redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
_toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip("Open content search tool (Ctrl+F)");
_toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip($"Open content search tool ({inputOptions.Search})");
_toolstrip.AddButton(editor.Icons.CenterView64, _surface.ShowWholeGraph).LinkTooltip("Show whole graph");
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/scripting/ai/behavior-trees/index.html")).LinkTooltip("See documentation to learn more");

View File

@@ -395,6 +395,8 @@ namespace FlaxEditor.Windows.Assets
public GameplayGlobalsWindow(Editor editor, AssetItem item)
: base(editor, item)
{
var inputOptions = Editor.Options.Options.Input;
_undo = new Undo();
_undo.ActionDone += OnUndo;
_undo.UndoDone += OnUndo;
@@ -411,10 +413,10 @@ namespace FlaxEditor.Windows.Assets
_proxy = new PropertiesProxy();
_propertiesEditor.Select(_proxy);
_saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save64, Save).LinkTooltip("Save asset");
_saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save64, Save).LinkTooltip($"Save asset ({inputOptions.Save})");
_toolstrip.AddSeparator();
_undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
_redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
_undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
_redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
_resetButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Rotate32, Reset).LinkTooltip("Resets the variables values to the default values");

View File

@@ -34,6 +34,8 @@ namespace FlaxEditor.Windows.Assets
public JsonAssetWindow(Editor editor, AssetItem item)
: base(editor, item)
{
var inputOptions = Editor.Options.Options.Input;
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
@@ -43,8 +45,8 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
_undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
_redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
_undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
_redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
// Panel
var panel = new Panel(ScrollBars.Vertical)

View File

@@ -126,6 +126,8 @@ namespace FlaxEditor.Windows.Assets
public LocalizedStringTableWindow(Editor editor, AssetItem item)
: base(editor, item)
{
var inputOptions = Editor.Options.Options.Input;
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
@@ -135,8 +137,8 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
_undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
_redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
_undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
_redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
_toolstrip.AddButton(Editor.Icons.Up64, OnExport).LinkTooltip("Export localization table entries for translation to .pot file");

View File

@@ -375,6 +375,8 @@ namespace FlaxEditor.Windows.Assets
public MaterialInstanceWindow(Editor editor, AssetItem item)
: base(editor, item)
{
var inputOptions = Editor.Options.Options.Input;
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
@@ -384,8 +386,8 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
_undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
_redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
_undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
_redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
_toolstrip.AddButton(Editor.Icons.Rotate64, OnRevertAllParameters).LinkTooltip("Revert all the parameters to the default values");
_toolstrip.AddSeparator();

View File

@@ -41,8 +41,6 @@ namespace FlaxEditor.Windows.Assets
new ScriptType(typeof(Vector3)),
new ScriptType(typeof(Vector4)),
new ScriptType(typeof(Color)),
new ScriptType(typeof(Quaternion)),
new ScriptType(typeof(Transform)),
new ScriptType(typeof(Matrix)),
};

View File

@@ -306,6 +306,8 @@ namespace FlaxEditor.Windows.Assets
public ParticleSystemWindow(Editor editor, AssetItem item)
: base(editor, item)
{
var inputOptions = Editor.Options.Options.Input;
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
@@ -359,8 +361,8 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
_undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
_redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
_undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
_redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/particles/index.html")).LinkTooltip("See documentation to learn more");

View File

@@ -339,7 +339,7 @@ namespace FlaxEditor.Windows.Assets
{
if (selection.Count != 0)
Select(actor);
actor.TreeNode.StartRenaming(this);
actor.TreeNode.StartRenaming(this, _treePanel);
}
}

View File

@@ -94,6 +94,8 @@ namespace FlaxEditor.Windows.Assets
public PrefabWindow(Editor editor, AssetItem item)
: base(editor, item)
{
var inputOptions = Editor.Options.Options.Input;
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoEvent;
@@ -176,12 +178,12 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
_toolStripUndo = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
_toolStripRedo = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
_toolStripUndo = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
_toolStripRedo = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
_toolStripTranslate = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Translate32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate).LinkTooltip("Change Gizmo tool mode to Translate (1)");
_toolStripRotate = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Rotate32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate).LinkTooltip("Change Gizmo tool mode to Rotate (2)");
_toolStripScale = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Scale32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip("Change Gizmo tool mode to Scale (3)");
_toolStripTranslate = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Translate32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate).LinkTooltip($"Change Gizmo tool mode to Translate ({inputOptions.TranslateMode})");
_toolStripRotate = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Rotate32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate).LinkTooltip($"Change Gizmo tool mode to Rotate ({inputOptions.RotateMode})");
_toolStripScale = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Scale32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip($"Change Gizmo tool mode to Scale ({inputOptions.ScaleMode})");
_toolstrip.AddSeparator();
_toolStripLiveReload = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Refresh64, () => LiveReload = !LiveReload).SetChecked(true).SetAutoCheck(true).LinkTooltip("Live changes preview (applies prefab changes on modification by auto)");

View File

@@ -627,6 +627,8 @@ namespace FlaxEditor.Windows.Assets
public SceneAnimationWindow(Editor editor, AssetItem item)
: base(editor, item)
{
var inputOptions = Editor.Options.Options.Input;
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
@@ -652,8 +654,8 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
_undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
_redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
_undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
_redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
_previewButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Refresh64, OnPreviewButtonClicked).SetAutoCheck(true).LinkTooltip("If checked, enables live-preview of the animation on a scene while editing");
_renderButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Build64, OnRenderButtonClicked).LinkTooltip("Open the scene animation rendering utility...");

View File

@@ -837,7 +837,7 @@ namespace FlaxEditor.Windows.Assets
sourceAssetPicker.CheckValid = CheckSourceAssetValid;
sourceAssetPicker.SelectedItemChanged += () =>
{
proxy.Setups.Add(sourceAssetPicker.SelectedAsset, new SetupProxy());
proxy.Setups.Add(sourceAssetPicker.Validator.SelectedAsset, new SetupProxy());
proxy.Window.MarkAsEdited();
RebuildLayout();
};
@@ -856,7 +856,7 @@ namespace FlaxEditor.Windows.Assets
// Source asset picker
var sourceAssetPicker = setupGroup.AddPropertyItem("Source Asset").Custom<AssetPicker>().CustomControl;
sourceAssetPicker.SelectedAsset = sourceAsset;
sourceAssetPicker.Validator.SelectedAsset = sourceAsset;
sourceAssetPicker.CanEdit = false;
sourceAssetPicker.Height = 48;
@@ -916,12 +916,12 @@ namespace FlaxEditor.Windows.Assets
{
// Show skeleton asset picker
var sourceSkeletonPicker = setupGroup.AddPropertyItem("Skeleton", "Skinned model that contains a skeleton for this animation retargeting.").Custom<AssetPicker>().CustomControl;
sourceSkeletonPicker.AssetType = new ScriptType(typeof(SkinnedModel));
sourceSkeletonPicker.SelectedAsset = setup.Value.Skeleton;
sourceSkeletonPicker.Validator.AssetType = new ScriptType(typeof(SkinnedModel));
sourceSkeletonPicker.Validator.SelectedAsset = setup.Value.Skeleton;
sourceSkeletonPicker.Height = 48;
sourceSkeletonPicker.SelectedItemChanged += () =>
{
setup.Value.Skeleton = (SkinnedModel)sourceSkeletonPicker.SelectedAsset;
setup.Value.Skeleton = (SkinnedModel)sourceSkeletonPicker.Validator.SelectedAsset;
proxy.Window.MarkAsEdited();
};
}

View File

@@ -61,6 +61,8 @@ namespace FlaxEditor.Windows.Assets
protected VisjectFunctionSurfaceWindow(Editor editor, AssetItem item)
: base(editor, item)
{
var inputOptions = Editor.Options.Options.Input;
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
@@ -70,10 +72,10 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
_undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
_redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
_undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
_redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
_toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip("Open content search tool (Ctrl+F)");
_toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip($"Open content search tool ({inputOptions.Search})");
_toolstrip.AddButton(editor.Icons.CenterView64, ShowWholeGraph).LinkTooltip("Show whole graph");
// Panel

View File

@@ -561,6 +561,7 @@ namespace FlaxEditor.Windows.Assets
: base(editor, item)
{
var isPlayMode = Editor.IsPlayMode;
var inputOptions = Editor.Options.Options.Input;
// Undo
_undo = new Undo();
@@ -598,21 +599,21 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
_undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
_redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
_undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
_redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
_toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip("Open content search tool (Ctrl+F)");
_toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip($"Open content search tool ({inputOptions.Search})");
_toolstrip.AddButton(editor.Icons.CenterView64, ShowWholeGraph).LinkTooltip("Show whole graph");
_toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/scripting/visual/index.html")).LinkTooltip("See documentation to learn more");
_debugToolstripControls = new[]
{
_toolstrip.AddSeparator(),
_toolstrip.AddButton(editor.Icons.Play64, OnDebuggerContinue).LinkTooltip("Continue (F5)"),
_toolstrip.AddButton(editor.Icons.Play64, OnDebuggerContinue).LinkTooltip($"Continue ({inputOptions.DebuggerContinue})"),
_toolstrip.AddButton(editor.Icons.Search64, OnDebuggerNavigateToCurrentNode).LinkTooltip("Navigate to the current stack trace node"),
_toolstrip.AddButton(editor.Icons.Right64, OnDebuggerStepOver).LinkTooltip("Step Over (F10)"),
_toolstrip.AddButton(editor.Icons.Down64, OnDebuggerStepInto).LinkTooltip("Step Into (F11)"),
_toolstrip.AddButton(editor.Icons.Up64, OnDebuggerStepOut).LinkTooltip("Step Out (Shift+F11)"),
_toolstrip.AddButton(editor.Icons.Right64, OnDebuggerStepOver).LinkTooltip($"Step Over ({inputOptions.DebuggerStepOver})"),
_toolstrip.AddButton(editor.Icons.Down64, OnDebuggerStepInto).LinkTooltip($"Step Into ({inputOptions.DebuggerStepInto})"),
_toolstrip.AddButton(editor.Icons.Up64, OnDebuggerStepOut).LinkTooltip($"Step Out ({inputOptions.DebuggerStepOut})"),
_toolstrip.AddButton(editor.Icons.Stop64, OnDebuggerStop).LinkTooltip("Stop debugging"),
};
foreach (var control in _debugToolstripControls)

View File

@@ -187,12 +187,12 @@ namespace FlaxEditor.Windows
continue;
// Get context proxy
ContentProxy p;
ContentProxy p = null;
if (type.Type.IsSubclassOf(typeof(ContentProxy)))
{
p = Editor.ContentDatabase.Proxy.Find(x => x.GetType() == type.Type);
}
else
else if (type.CanCreateInstance)
{
// User can use attribute to put their own assets into the content context menu
var generic = typeof(SpawnableJsonAssetProxy<>).MakeGenericType(type.Type);

View File

@@ -271,8 +271,6 @@ namespace FlaxEditor.Windows
Title = "Game";
AutoFocus = true;
FlaxEditor.Utilities.Utils.SetupCommonInputActions(this);
var task = MainRenderTask.Instance;
// Setup viewport
@@ -304,6 +302,12 @@ namespace FlaxEditor.Windows
// Link editor options
Editor.Options.OptionsChanged += OnOptionsChanged;
OnOptionsChanged(Editor.Options.Options);
InputActions.Add(options => options.TakeScreenshot, () => Screenshot.Capture(string.Empty));
InputActions.Add(options => options.DebuggerUnlockMouse, UnlockMouseInPlay);
InputActions.Add(options => options.ToggleFullscreen, () => { if (Editor.IsPlayMode) IsMaximized = !IsMaximized; });
FlaxEditor.Utilities.Utils.SetupCommonInputActions(this);
}
private void ChangeViewportRatio(ViewportScaleOptions v)
@@ -945,27 +949,6 @@ namespace FlaxEditor.Windows
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
switch (key)
{
case KeyboardKeys.F12:
Screenshot.Capture(string.Empty);
return true;
case KeyboardKeys.F11:
if (Root.GetKey(KeyboardKeys.Shift))
{
// Unlock mouse in game mode
UnlockMouseInPlay();
return true;
}
else if (Editor.IsPlayMode)
{
// Maximized game window toggle
IsMaximized = !IsMaximized;
return true;
}
break;
}
// Prevent closing the game window tab during a play session
if (Editor.StateMachine.IsPlayMode && Editor.Options.Options.Input.CloseTab.Process(this, key))
{

View File

@@ -467,6 +467,7 @@ namespace FlaxEditor.Windows
if (_isDirty)
{
_isDirty = false;
var wasEmpty = _output.TextLength == 0;
// Cache fonts
_output.DefaultStyle.Font.GetFont();
@@ -589,7 +590,7 @@ namespace FlaxEditor.Windows
// Update the output
var cachedScrollValue = _vScroll.Value;
var cachedSelection = _output.SelectionRange;
var isBottomScroll = _vScroll.Value >= _vScroll.Maximum - 20.0f;
var isBottomScroll = _vScroll.Value >= _vScroll.Maximum - 20.0f || wasEmpty;
_output.Text = _textBuffer.ToString();
_textBufferCount = _entries.Count;
if (!_vScroll.IsThumbClicked)

View File

@@ -142,7 +142,7 @@ namespace FlaxEditor.Windows
{
if (selection.Count != 0)
Editor.SceneEditing.Select(actor);
actor.TreeNode.StartRenaming(this);
actor.TreeNode.StartRenaming(this, _sceneTreePanel);
}
}

View File

@@ -399,6 +399,8 @@ namespace FlaxEditor.Windows
{
Title = "Visual Script Debugger";
var inputOptions = editor.Options.Options.Input;
var toolstrip = new ToolStrip
{
Parent = this
@@ -407,7 +409,7 @@ namespace FlaxEditor.Windows
_debugToolstripControls = new[]
{
toolstrip.AddSeparator(),
toolstrip.AddButton(editor.Icons.Play64, OnDebuggerContinue).LinkTooltip("Continue (F5)"),
toolstrip.AddButton(editor.Icons.Play64, OnDebuggerContinue).LinkTooltip($"Continue ({inputOptions.DebuggerContinue})"),
toolstrip.AddButton(editor.Icons.Search64, OnDebuggerNavigateToCurrentNode).LinkTooltip("Navigate to the current stack trace node"),
toolstrip.AddButton(editor.Icons.Stop64, OnDebuggerStop).LinkTooltip("Stop debugging"),
};

View File

@@ -2109,7 +2109,8 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
bucket.LoopsLeft--;
bucket.LoopsDone++;
}
value = SampleAnimation(node, loop, length, 0.0f, bucket.TimePosition, newTimePos, anim, slot.Speed);
// Speed is accounted for in the new time pos, so keep sample speed at 1
value = SampleAnimation(node, loop, length, 0.0f, bucket.TimePosition, newTimePos, anim, 1);
bucket.TimePosition = newTimePos;
if (bucket.LoopsLeft == 0 && slot.BlendOutTime > 0.0f && length - slot.BlendOutTime < bucket.TimePosition)
{

View File

@@ -16,11 +16,13 @@
AssetReferenceBase::~AssetReferenceBase()
{
if (_asset)
Asset* asset = _asset;
if (asset)
{
_asset->OnLoaded.Unbind<AssetReferenceBase, &AssetReferenceBase::OnLoaded>(this);
_asset->OnUnloaded.Unbind<AssetReferenceBase, &AssetReferenceBase::OnUnloaded>(this);
_asset->RemoveReference();
_asset = nullptr;
asset->OnLoaded.Unbind<AssetReferenceBase, &AssetReferenceBase::OnLoaded>(this);
asset->OnUnloaded.Unbind<AssetReferenceBase, &AssetReferenceBase::OnUnloaded>(this);
asset->RemoveReference();
}
}
@@ -70,8 +72,12 @@ void AssetReferenceBase::OnUnloaded(Asset* asset)
WeakAssetReferenceBase::~WeakAssetReferenceBase()
{
if (_asset)
_asset->OnUnloaded.Unbind<WeakAssetReferenceBase, &WeakAssetReferenceBase::OnUnloaded>(this);
Asset* asset = _asset;
if (asset)
{
_asset = nullptr;
asset->OnUnloaded.Unbind<WeakAssetReferenceBase, &WeakAssetReferenceBase::OnUnloaded>(this);
}
}
String WeakAssetReferenceBase::ToString() const
@@ -101,6 +107,20 @@ void WeakAssetReferenceBase::OnUnloaded(Asset* asset)
_asset = nullptr;
}
SoftAssetReferenceBase::~SoftAssetReferenceBase()
{
Asset* asset = _asset;
if (asset)
{
_asset = nullptr;
asset->OnUnloaded.Unbind<SoftAssetReferenceBase, &SoftAssetReferenceBase::OnUnloaded>(this);
asset->RemoveReference();
}
#if !BUILD_RELEASE
_id = Guid::Empty;
#endif
}
String SoftAssetReferenceBase::ToString() const
{
return _asset ? _asset->ToString() : (_id.IsValid() ? _id.ToString() : TEXT("<null>"));

View File

@@ -9,9 +9,6 @@
/// </summary>
class FLAXENGINE_API AssetReferenceBase
{
public:
typedef Delegate<> EventType;
protected:
Asset* _asset = nullptr;
@@ -19,17 +16,17 @@ public:
/// <summary>
/// The asset loaded event (fired when asset gets loaded or is already loaded after change).
/// </summary>
EventType Loaded;
Action Loaded;
/// <summary>
/// The asset unloading event (should cleanup refs to it).
/// </summary>
EventType Unload;
Action Unload;
/// <summary>
/// Action fired when field gets changed (link a new asset or change to the another value).
/// </summary>
EventType Changed;
Action Changed;
public:
NON_COPYABLE(AssetReferenceBase);

View File

@@ -1428,6 +1428,10 @@ Asset::LoadResult VisualScript::load()
#if USE_EDITOR
if (_instances.HasItems())
{
// Mark as already loaded so any WaitForLoaded checks during GetDefaultInstance bellow will handle this Visual Script as ready to use
_loadFailed = false;
_isLoaded = true;
// Setup scripting type
CacheScriptingType();
@@ -1512,7 +1516,7 @@ void VisualScript::unload(bool isReloading)
// Note: preserve the registered scripting type but invalidate the locally cached handle
if (_scriptingTypeHandle)
{
VisualScriptingModule.Locker.Lock();
VisualScriptingBinaryModule::Locker.Lock();
auto& type = VisualScriptingModule.Types[_scriptingTypeHandle.TypeIndex];
if (type.Script.DefaultInstance)
{
@@ -1523,7 +1527,7 @@ void VisualScript::unload(bool isReloading)
VisualScriptingModule.Scripts[_scriptingTypeHandle.TypeIndex] = nullptr;
_scriptingTypeHandleCached = _scriptingTypeHandle;
_scriptingTypeHandle = ScriptingTypeHandle();
VisualScriptingModule.Locker.Unlock();
VisualScriptingBinaryModule::Locker.Unlock();
}
}
@@ -1534,8 +1538,8 @@ AssetChunksFlag VisualScript::getChunksToPreload() const
void VisualScript::CacheScriptingType()
{
ScopeLock lock(VisualScriptingBinaryModule::Locker);
auto& binaryModule = VisualScriptingModule;
ScopeLock lock(binaryModule.Locker);
// Find base type
const StringAnsi baseTypename(Meta.BaseTypename);

View File

@@ -30,9 +30,7 @@ public:
/// <summary>
/// Finalizes an instance of the <see cref="SoftAssetReferenceBase"/> class.
/// </summary>
~SoftAssetReferenceBase()
{
}
~SoftAssetReferenceBase();
public:
/// <summary>

View File

@@ -22,6 +22,16 @@ private:
int32 _capacity;
AllocationData _allocation;
FORCE_INLINE static int32 ToItemCount(int32 size)
{
return Math::DivideAndRoundUp<int32>(size, sizeof(ItemType));
}
FORCE_INLINE static int32 ToItemCapacity(int32 size)
{
return Math::Max<int32>(Math::DivideAndRoundUp<int32>(size, sizeof(ItemType)), 1);
}
public:
/// <summary>
/// Initializes a new instance of the <see cref="BitArray"/> class.
@@ -41,7 +51,7 @@ public:
, _capacity(capacity)
{
if (capacity > 0)
_allocation.Allocate(Math::Max<ItemType>(capacity / sizeof(ItemType), 1));
_allocation.Allocate(ToItemCapacity(capacity));
}
/// <summary>
@@ -53,7 +63,7 @@ public:
_count = _capacity = other.Count();
if (_capacity > 0)
{
const uint64 itemsCapacity = Math::Max<ItemType>(_capacity / sizeof(ItemType), 1);
const int32 itemsCapacity = ToItemCapacity(_capacity);
_allocation.Allocate(itemsCapacity);
Platform::MemoryCopy(Get(), other.Get(), itemsCapacity * sizeof(ItemType));
}
@@ -69,7 +79,7 @@ public:
_count = _capacity = other.Count();
if (_capacity > 0)
{
const uint64 itemsCapacity = Math::Max<ItemType>(_capacity / sizeof(ItemType), 1);
const int32 itemsCapacity = ToItemCapacity(_capacity);
_allocation.Allocate(itemsCapacity);
Platform::MemoryCopy(Get(), other.Get(), itemsCapacity * sizeof(ItemType));
}
@@ -101,7 +111,7 @@ public:
{
_allocation.Free();
_capacity = other._count;
const uint64 itemsCapacity = Math::Max<ItemType>(_capacity / sizeof(ItemType), 1);
const int32 itemsCapacity = ToItemCapacity(_capacity);
_allocation.Allocate(itemsCapacity);
Platform::MemoryCopy(Get(), other.Get(), itemsCapacity * sizeof(ItemType));
}
@@ -246,7 +256,7 @@ public:
return;
ASSERT(capacity >= 0);
const int32 count = preserveContents ? (_count < capacity ? _count : capacity) : 0;
_allocation.Relocate(Math::Max<ItemType>(capacity / sizeof(ItemType), 1), _count / sizeof(ItemType), count / sizeof(ItemType));
_allocation.Relocate(ToItemCapacity(capacity), ToItemCount(_count), ToItemCount(count));
_capacity = capacity;
_count = count;
}
@@ -272,7 +282,7 @@ public:
{
if (_capacity < minCapacity)
{
const int32 capacity = _allocation.CalculateCapacityGrow(Math::Max<int32>(_capacity / sizeof(ItemType), 1), minCapacity);
const int32 capacity = _allocation.CalculateCapacityGrow(ToItemCapacity(_capacity), minCapacity);
SetCapacity(capacity, preserveContents);
}
}
@@ -284,7 +294,7 @@ public:
void SetAll(const bool value)
{
if (_count != 0)
Platform::MemorySet(_allocation.Get(), Math::Max<ItemType>(_count / sizeof(ItemType), 1), value ? MAX_int32 : 0);
Platform::MemorySet(_allocation.Get(), ToItemCount(_count) * sizeof(ItemType), value ? MAX_uint32 : 0);
}
/// <summary>

View File

@@ -2,13 +2,26 @@
#pragma once
/// <summary>
/// Default capacity for the dictionaries (amount of space for the elements)
/// </summary>
#define DICTIONARY_DEFAULT_CAPACITY 256
#include "Engine/Platform/Defines.h"
/// <summary>
/// Function for dictionary that tells how change hash index during iteration (size param is a buckets table size)
/// Default capacity for the dictionaries (amount of space for the elements).
/// </summary>
#ifndef DICTIONARY_DEFAULT_CAPACITY
#if PLATFORM_DESKTOP
#define DICTIONARY_DEFAULT_CAPACITY 256
#else
#define DICTIONARY_DEFAULT_CAPACITY 64
#endif
#endif
/// <summary>
/// Default slack space divider for the dictionaries.
/// </summary>
#define DICTIONARY_DEFAULT_SLACK_SCALE 3
/// <summary>
/// Function for dictionary that tells how change hash index during iteration (size param is a buckets table size).
/// </summary>
#define DICTIONARY_PROB_FUNC(size, numChecks) (numChecks)
//#define DICTIONARY_PROB_FUNC(size, numChecks) (1)

View File

@@ -40,7 +40,7 @@ public:
private:
State _state;
void Free()
FORCE_INLINE void Free()
{
if (_state == Occupied)
{
@@ -50,7 +50,7 @@ public:
_state = Empty;
}
void Delete()
FORCE_INLINE void Delete()
{
_state = Deleted;
Memory::DestructItem(&Key);
@@ -58,7 +58,7 @@ public:
}
template<typename KeyComparableType>
void Occupy(const KeyComparableType& key)
FORCE_INLINE void Occupy(const KeyComparableType& key)
{
Memory::ConstructItems(&Key, &key, 1);
Memory::ConstructItem(&Value);
@@ -66,7 +66,7 @@ public:
}
template<typename KeyComparableType>
void Occupy(const KeyComparableType& key, const ValueType& value)
FORCE_INLINE void Occupy(const KeyComparableType& key, const ValueType& value)
{
Memory::ConstructItems(&Key, &key, 1);
Memory::ConstructItems(&Value, &value, 1);
@@ -74,7 +74,7 @@ public:
}
template<typename KeyComparableType>
void Occupy(const KeyComparableType& key, ValueType&& value)
FORCE_INLINE void Occupy(const KeyComparableType& key, ValueType&& value)
{
Memory::ConstructItems(&Key, &key, 1);
Memory::MoveItems(&Value, &value, 1);
@@ -132,9 +132,6 @@ public:
/// </summary>
/// <param name="other">The other collection to move.</param>
Dictionary(Dictionary&& other) noexcept
: _elementsCount(other._elementsCount)
, _deletedCount(other._deletedCount)
, _size(other._size)
{
_elementsCount = other._elementsCount;
_deletedCount = other._deletedCount;
@@ -375,8 +372,12 @@ public:
template<typename KeyComparableType>
ValueType& At(const KeyComparableType& key)
{
// Check if need to rehash elements (prevent many deleted elements that use too much of capacity)
if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE)
Compact();
// Ensure to have enough memory for the next item (in case of new element insertion)
EnsureCapacity(_elementsCount + _deletedCount + 1);
EnsureCapacity((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount);
// Find location of the item or place to insert it
FindPositionResult pos;
@@ -388,9 +389,9 @@ public:
// Insert
ASSERT(pos.FreeSlotIndex != -1);
_elementsCount++;
Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex];
bucket.Occupy(key);
_elementsCount++;
return bucket.Value;
}
@@ -493,7 +494,7 @@ public:
for (Iterator i = Begin(); i.IsNotEnd(); ++i)
{
if (i->Value)
Delete(i->Value);
::Delete(i->Value);
}
Clear();
}
@@ -533,13 +534,22 @@ public:
}
_size = capacity;
Bucket* oldData = oldAllocation.Get();
if (oldElementsCount != 0 && preserveContents)
if (oldElementsCount != 0 && capacity != 0 && preserveContents)
{
// TODO; move keys and values on realloc
FindPositionResult pos;
for (int32 i = 0; i < oldSize; i++)
{
if (oldData[i].IsOccupied())
Add(oldData[i].Key, MoveTemp(oldData[i].Value));
Bucket& oldBucket = oldData[i];
if (oldBucket.IsOccupied())
{
FindPosition(oldBucket.Key, pos);
ASSERT(pos.FreeSlotIndex != -1);
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
Memory::MoveItems(&bucket->Key, &oldBucket.Key, 1);
Memory::MoveItems(&bucket->Value, &oldBucket.Value, 1);
bucket->_state = Bucket::Occupied;
_elementsCount++;
}
}
}
if (oldElementsCount != 0)
@@ -558,9 +568,9 @@ public:
{
if (_size >= minCapacity)
return;
if (minCapacity < DICTIONARY_DEFAULT_CAPACITY)
minCapacity = DICTIONARY_DEFAULT_CAPACITY;
const int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
if (capacity < DICTIONARY_DEFAULT_CAPACITY)
capacity = DICTIONARY_DEFAULT_CAPACITY;
SetCapacity(capacity, preserveContents);
}
@@ -584,24 +594,10 @@ public:
/// <param name="value">The value.</param>
/// <returns>Weak reference to the stored bucket.</returns>
template<typename KeyComparableType>
Bucket* Add(const KeyComparableType& key, const ValueType& value)
FORCE_INLINE Bucket* Add(const KeyComparableType& key, const ValueType& value)
{
// Ensure to have enough memory for the next item (in case of new element insertion)
EnsureCapacity(_elementsCount + _deletedCount + 1);
// Find location of the item or place to insert it
FindPositionResult pos;
FindPosition(key, pos);
// Ensure key is unknown
ASSERT(pos.ObjectIndex == -1 && "That key has been already added to the dictionary.");
// Insert
ASSERT(pos.FreeSlotIndex != -1);
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
Bucket* bucket = OnAdd(key);
bucket->Occupy(key, value);
_elementsCount++;
return bucket;
}
@@ -612,24 +608,10 @@ public:
/// <param name="value">The value.</param>
/// <returns>Weak reference to the stored bucket.</returns>
template<typename KeyComparableType>
Bucket* Add(const KeyComparableType& key, ValueType&& value)
FORCE_INLINE Bucket* Add(const KeyComparableType& key, ValueType&& value)
{
// Ensure to have enough memory for the next item (in case of new element insertion)
EnsureCapacity(_elementsCount + _deletedCount + 1);
// Find location of the item or place to insert it
FindPositionResult pos;
FindPosition(key, pos);
// Ensure key is unknown
ASSERT(pos.ObjectIndex == -1 && "That key has been already added to the dictionary.");
// Insert
ASSERT(pos.FreeSlotIndex != -1);
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
Bucket* bucket = OnAdd(key);
bucket->Occupy(key, MoveTemp(value));
_elementsCount++;
return bucket;
}
@@ -851,7 +833,7 @@ public:
return Iterator(this, _size);
}
protected:
private:
/// <summary>
/// The result container of the dictionary item lookup searching.
/// </summary>
@@ -911,4 +893,66 @@ protected:
result.ObjectIndex = -1;
result.FreeSlotIndex = insertPos;
}
template<typename KeyComparableType>
Bucket* OnAdd(const KeyComparableType& key)
{
// Check if need to rehash elements (prevent many deleted elements that use too much of capacity)
if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE)
Compact();
// Ensure to have enough memory for the next item (in case of new element insertion)
EnsureCapacity((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount);
// Find location of the item or place to insert it
FindPositionResult pos;
FindPosition(key, pos);
// Ensure key is unknown
ASSERT(pos.ObjectIndex == -1 && "That key has been already added to the dictionary.");
// Insert
ASSERT(pos.FreeSlotIndex != -1);
_elementsCount++;
return &_allocation.Get()[pos.FreeSlotIndex];
}
void Compact()
{
if (_elementsCount == 0)
{
// Fast path if it's empty
Bucket* data = _allocation.Get();
for (int32 i = 0; i < _size; i++)
data[i]._state = Bucket::Empty;
}
else
{
// Rebuild entire table completely
AllocationData oldAllocation;
oldAllocation.Swap(_allocation);
_allocation.Allocate(_size);
Bucket* data = _allocation.Get();
for (int32 i = 0; i < _size; i++)
data[i]._state = Bucket::Empty;
Bucket* oldData = oldAllocation.Get();
FindPositionResult pos;
for (int32 i = 0; i < _size; i++)
{
Bucket& oldBucket = oldData[i];
if (oldBucket.IsOccupied())
{
FindPosition(oldBucket.Key, pos);
ASSERT(pos.FreeSlotIndex != -1);
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
Memory::MoveItems(&bucket->Key, &oldBucket.Key, 1);
Memory::MoveItems(&bucket->Value, &oldBucket.Value, 1);
bucket->_state = Bucket::Occupied;
}
}
for (int32 i = 0; i < _size; i++)
oldData[i].Free();
}
_deletedCount = 0;
}
};

View File

@@ -37,26 +37,33 @@ public:
private:
State _state;
void Free()
FORCE_INLINE void Free()
{
if (_state == Occupied)
Memory::DestructItem(&Item);
_state = Empty;
}
void Delete()
FORCE_INLINE void Delete()
{
_state = Deleted;
Memory::DestructItem(&Item);
}
template<typename ItemType>
void Occupy(const ItemType& item)
FORCE_INLINE void Occupy(const ItemType& item)
{
Memory::ConstructItems(&Item, &item, 1);
_state = Occupied;
}
template<typename ItemType>
FORCE_INLINE void Occupy(ItemType& item)
{
Memory::MoveItems(&Item, &item, 1);
_state = Occupied;
}
FORCE_INLINE bool IsEmpty() const
{
return _state == Empty;
@@ -82,6 +89,7 @@ public:
private:
int32 _elementsCount = 0;
int32 _deletedCount = 0;
int32 _size = 0;
AllocationData _allocation;
@@ -107,12 +115,12 @@ public:
/// </summary>
/// <param name="other">The other collection to move.</param>
HashSet(HashSet&& other) noexcept
: _elementsCount(other._elementsCount)
, _size(other._size)
{
_elementsCount = other._elementsCount;
_deletedCount = other._deletedCount;
_size = other._size;
other._elementsCount = 0;
other._deletedCount = 0;
other._size = 0;
_allocation.Swap(other._allocation);
}
@@ -150,8 +158,10 @@ public:
Clear();
_allocation.Free();
_elementsCount = other._elementsCount;
_deletedCount = other._deletedCount;
_size = other._size;
other._elementsCount = 0;
other._deletedCount = 0;
other._size = 0;
_allocation.Swap(other._allocation);
}
@@ -163,7 +173,7 @@ public:
/// </summary>
~HashSet()
{
SetCapacity(0, false);
Clear();
}
public:
@@ -210,6 +220,7 @@ public:
HashSet* _collection;
int32 _index;
public:
Iterator(HashSet* collection, const int32 index)
: _collection(collection)
, _index(index)
@@ -222,7 +233,12 @@ public:
{
}
public:
Iterator()
: _collection(nullptr)
, _index(-1)
{
}
Iterator(const Iterator& i)
: _collection(i._collection)
, _index(i._index)
@@ -236,6 +252,11 @@ public:
}
public:
FORCE_INLINE int32 Index() const
{
return _index;
}
FORCE_INLINE bool IsEnd() const
{
return _index == _collection->_size;
@@ -331,12 +352,12 @@ public:
/// </summary>
void Clear()
{
if (_elementsCount != 0)
if (_elementsCount + _deletedCount != 0)
{
Bucket* data = _allocation.Get();
for (int32 i = 0; i < _size; i++)
data[i].Free();
_elementsCount = 0;
_elementsCount = _deletedCount = 0;
}
}
@@ -371,7 +392,7 @@ public:
oldAllocation.Swap(_allocation);
const int32 oldSize = _size;
const int32 oldElementsCount = _elementsCount;
_elementsCount = 0;
_deletedCount = _elementsCount = 0;
if (capacity != 0 && (capacity & (capacity - 1)) != 0)
{
// Align capacity value to the next power of two (http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2)
@@ -392,13 +413,21 @@ public:
}
_size = capacity;
Bucket* oldData = oldAllocation.Get();
if (oldElementsCount != 0 && preserveContents)
if (oldElementsCount != 0 && capacity != 0 && preserveContents)
{
// TODO; move keys and values on realloc
FindPositionResult pos;
for (int32 i = 0; i < oldSize; i++)
{
if (oldData[i].IsOccupied())
Add(oldData[i].Item);
Bucket& oldBucket = oldData[i];
if (oldBucket.IsOccupied())
{
FindPosition(oldBucket.Item, pos);
ASSERT(pos.FreeSlotIndex != -1);
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
Memory::MoveItems(&bucket->Item, &oldBucket.Item, 1);
bucket->_state = Bucket::Occupied;
_elementsCount++;
}
}
}
if (oldElementsCount != 0)
@@ -415,14 +444,26 @@ public:
/// <param name="preserveContents">True if preserve collection data when changing its size, otherwise collection after resize will be empty.</param>
void EnsureCapacity(int32 minCapacity, bool preserveContents = true)
{
if (Capacity() >= minCapacity)
if (_size >= minCapacity)
return;
if (minCapacity < DICTIONARY_DEFAULT_CAPACITY)
minCapacity = DICTIONARY_DEFAULT_CAPACITY;
const int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
if (capacity < DICTIONARY_DEFAULT_CAPACITY)
capacity = DICTIONARY_DEFAULT_CAPACITY;
SetCapacity(capacity, preserveContents);
}
/// <summary>
/// Swaps the contents of collection with the other object without copy operation. Performs fast internal data exchange.
/// </summary>
/// <param name="other">The other collection.</param>
void Swap(HashSet& other)
{
::Swap(_elementsCount, other._elementsCount);
::Swap(_deletedCount, other._deletedCount);
::Swap(_size, other._size);
_allocation.Swap(other._allocation);
}
public:
/// <summary>
/// Add element to the collection.
@@ -432,24 +473,23 @@ public:
template<typename ItemType>
bool Add(const ItemType& item)
{
// Ensure to have enough memory for the next item (in case of new element insertion)
EnsureCapacity(_elementsCount + 1);
Bucket* bucket = OnAdd(item);
if (bucket)
bucket->Occupy(item);
return bucket != nullptr;
}
// Find location of the item or place to insert it
FindPositionResult pos;
FindPosition(item, pos);
// Check if object has been already added
if (pos.ObjectIndex != -1)
return false;
// Insert
ASSERT(pos.FreeSlotIndex != -1);
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
bucket->Occupy(item);
_elementsCount++;
return true;
/// <summary>
/// Add element to the collection.
/// </summary>
/// <param name="item">The element to add to the set.</param>
/// <returns>True if element has been added to the collection, otherwise false if the element is already present.</returns>
bool Add(T&& item)
{
Bucket* bucket = OnAdd(item);
if (bucket)
bucket->Occupy(MoveTemp(item));
return bucket != nullptr;
}
/// <summary>
@@ -479,6 +519,7 @@ public:
{
_allocation.Get()[pos.ObjectIndex].Delete();
_elementsCount--;
_deletedCount++;
return true;
}
return false;
@@ -497,6 +538,7 @@ public:
ASSERT(_allocation.Get()[i._index].IsOccupied());
_allocation.Get()[i._index].Delete();
_elementsCount--;
_deletedCount++;
return true;
}
return false;
@@ -585,7 +627,7 @@ public:
return Iterator(this, _size);
}
protected:
private:
/// <summary>
/// The result container of the set item lookup searching.
/// </summary>
@@ -646,4 +688,66 @@ protected:
result.ObjectIndex = -1;
result.FreeSlotIndex = insertPos;
}
template<typename ItemType>
Bucket* OnAdd(const ItemType& key)
{
// Check if need to rehash elements (prevent many deleted elements that use too much of capacity)
if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE)
Compact();
// Ensure to have enough memory for the next item (in case of new element insertion)
EnsureCapacity((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount);
// Find location of the item or place to insert it
FindPositionResult pos;
FindPosition(key, pos);
// Check if object has been already added
if (pos.ObjectIndex != -1)
return nullptr;
// Insert
ASSERT(pos.FreeSlotIndex != -1);
_elementsCount++;
return &_allocation.Get()[pos.FreeSlotIndex];
}
void Compact()
{
if (_elementsCount == 0)
{
// Fast path if it's empty
Bucket* data = _allocation.Get();
for (int32 i = 0; i < _size; i++)
data[i]._state = Bucket::Empty;
}
else
{
// Rebuild entire table completely
AllocationData oldAllocation;
oldAllocation.Swap(_allocation);
_allocation.Allocate(_size);
Bucket* data = _allocation.Get();
for (int32 i = 0; i < _size; i++)
data[i]._state = Bucket::Empty;
Bucket* oldData = oldAllocation.Get();
FindPositionResult pos;
for (int32 i = 0; i < _size; i++)
{
Bucket& oldBucket = oldData[i];
if (oldBucket.IsOccupied())
{
FindPosition(oldBucket.Item, pos);
ASSERT(pos.FreeSlotIndex != -1);
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
Memory::MoveItems(&bucket->Item, &oldBucket.Item, 1);
bucket->_state = Bucket::Occupied;
}
}
for (int32 i = 0; i < _size; i++)
oldData[i].Free();
}
_deletedCount = 0;
}
};

View File

@@ -226,7 +226,7 @@ public:
/// <returns>Function result</returns>
FORCE_INLINE ReturnType operator()(Params... params) const
{
ASSERT(_function);
ASSERT_LOW_LAYER(_function);
return _function(_callee, Forward<Params>(params)...);
}
@@ -289,8 +289,13 @@ protected:
intptr volatile _ptr = 0;
intptr volatile _size = 0;
#else
HashSet<FunctionType>* _functions = nullptr;
CriticalSection* _locker = nullptr;
struct Data
{
HashSet<FunctionType> Functions;
CriticalSection Locker;
};
// Holds pointer to Data with Functions and Locker. Thread-safe access via atomic operations.
intptr volatile _data = 0;
#endif
typedef void (*StubSignature)(void*, Params...);
@@ -314,15 +319,12 @@ public:
_ptr = (intptr)newBindings;
_size = newSize;
#else
if (other._functions == nullptr)
Data* otherData = (Data*)Platform::AtomicRead(&_data);
if (otherData == nullptr)
return;
_functions = New<HashSet<FunctionType>>(*other._functions);
for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
{
if (i->Item._function && i->Item._lambda)
i->Item.LambdaCtor();
}
_locker = other._locker;
ScopeLock lock(otherData->Locker);
for (auto i = otherData->Functions.Begin(); i.IsNotEnd(); ++i)
Bind(i->Item);
#endif
}
@@ -334,10 +336,8 @@ public:
other._ptr = 0;
other._size = 0;
#else
_functions = other._functions;
_locker = other._locker;
other._functions = nullptr;
other._locker = nullptr;
_data = other._data;
other._data = 0;
#endif
}
@@ -356,20 +356,11 @@ public:
Allocator::Free((void*)_ptr);
}
#else
if (_locker != nullptr)
Data* data = (Data*)_data;
if (data)
{
Allocator::Free(_locker);
_locker = nullptr;
}
if (_functions != nullptr)
{
for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
{
if (i->Item._lambda)
i->Item.LambdaCtor();
}
Allocator::Free(_functions);
_functions = nullptr;
_data = 0;
Delete(data);
}
#endif
}
@@ -385,8 +376,13 @@ public:
for (intptr i = 0; i < size; i++)
Bind(bindings[i]);
#else
for (auto i = other._functions->Begin(); i.IsNotEnd(); ++i)
Bind(i->Item);
Data* otherData = (Data*)Platform::AtomicRead(&_data);
if (otherData != nullptr)
{
ScopeLock lock(otherData->Locker);
for (auto i = otherData->Functions.Begin(); i.IsNotEnd(); ++i)
Bind(i->Item);
}
#endif
}
return *this;
@@ -402,10 +398,8 @@ public:
other._ptr = 0;
other._size = 0;
#else
_functions = other._functions;
_locker = other._locker;
other._functions = nullptr;
other._locker = nullptr;
_data = other._data;
other._data = 0;
#endif
}
return *this;
@@ -507,12 +501,20 @@ public:
Allocator::Free(bindings);
}
#else
if (_locker == nullptr)
_locker = New<CriticalSection>();
ScopeLock lock(*_locker);
if (_functions == nullptr)
_functions = New<HashSet<FunctionType>>();
_functions->Add(f);
Data* data = (Data*)Platform::AtomicRead(&_data);
while (!data)
{
Data* newData = New<Data>();
Data* oldData = (Data*)Platform::InterlockedCompareExchange(&_data, (intptr)newData, (intptr)data);
if (oldData != data)
{
// Other thread already set the new data so free it and try again
Delete(newData);
}
data = (Data*)Platform::AtomicRead(&_data);
}
ScopeLock lock(data->Locker);
data->Functions.Add(f);
#endif
}
@@ -568,13 +570,22 @@ public:
}
}
#else
if (_locker == nullptr)
_locker = New<CriticalSection>();
ScopeLock lock(*_locker);
if (_functions && _functions->Contains(f))
return;
Data* data = (Data*)Platform::AtomicRead(&_data);
if (data)
{
data->Locker.Lock();
if (data->Functions.Contains(f))
{
data->Locker.Unlock();
return;
}
}
#endif
Bind(f);
#if !DELEGATE_USE_ATOMIC
if (data)
data->Locker.Unlock();
#endif
}
/// <summary>
@@ -640,10 +651,11 @@ public:
Unbind(f);
}
#else
if (_functions == nullptr)
Data* data = (Data*)Platform::AtomicRead(&_data);
if (!data)
return;
ScopeLock lock(*_locker);
_functions->Remove(f);
ScopeLock lock(data->Locker);
data->Functions.Remove(f);
#endif
}
@@ -666,15 +678,11 @@ public:
Platform::AtomicStore((intptr volatile*)&bindings[i]._callee, 0);
}
#else
if (_functions == nullptr)
Data* data = (Data*)Platform::AtomicRead(&_data);
if (!data)
return;
ScopeLock lock(*_locker);
for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
{
if (i->Item._lambda)
i->Item.LambdaDtor();
}
_functions->Clear();
ScopeLock lock(data->Locker);
data->Functions.Clear();
#endif
}
@@ -684,22 +692,24 @@ public:
/// <returns>The bound functions count.</returns>
int32 Count() const
{
int32 result = 0;
#if DELEGATE_USE_ATOMIC
int32 count = 0;
const intptr size = Platform::AtomicRead((intptr volatile*)&_size);
FunctionType* bindings = (FunctionType*)Platform::AtomicRead((intptr volatile*)&_ptr);
for (intptr i = 0; i < size; i++)
{
if (Platform::AtomicRead((intptr volatile*)&bindings[i]._function) != 0)
count++;
result++;
}
return count;
#else
if (_functions == nullptr)
return 0;
ScopeLock lock(*_locker);
return _functions->Count();
Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
if (data)
{
ScopeLock lock(data->Locker);
result = data->Functions.Count();
}
#endif
return result;
}
/// <summary>
@@ -710,10 +720,14 @@ public:
#if DELEGATE_USE_ATOMIC
return (int32)Platform::AtomicRead((intptr volatile*)&_size);
#else
if (_functions == nullptr)
return 0;
ScopeLock lock(*_locker);
return _functions->Capacity();
int32 result = 0;
Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
if (data)
{
ScopeLock lock(data->Locker);
result = data->Functions.Capacity();
}
return result;
#endif
}
@@ -733,10 +747,14 @@ public:
}
return false;
#else
if (_functions == nullptr)
return false;
ScopeLock lock(*_locker);
return _functions->Count() > 0;
bool result = false;
Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
if (data)
{
ScopeLock lock(data->Locker);
result = data->Functions.Count() != 0;
}
return result;
#endif
}
@@ -765,18 +783,13 @@ public:
}
}
#else
if (_functions == nullptr)
return 0;
ScopeLock lock(*_locker);
for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
if (data)
{
if (i->Item._function != nullptr)
ScopeLock lock(data->Locker);
for (auto i = data->Functions.Begin(); i.IsNotEnd(); ++i)
{
buffer[count]._function = (StubSignature)i->Item._function;
buffer[count]._callee = (void*)i->Item._callee;
buffer[count]._lambda = (typename FunctionType::Lambda*)i->Item._lambda;
if (buffer[count]._lambda)
buffer[count].LambdaCtor();
new(buffer + count) FunctionType((const FunctionType&)i->Item);
count++;
}
}
@@ -802,15 +815,15 @@ public:
++bindings;
}
#else
if (_functions == nullptr)
Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
if (!data)
return;
ScopeLock lock(*_locker);
for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
ScopeLock lock(data->Locker);
for (auto i = data->Functions.Begin(); i.IsNotEnd(); ++i)
{
auto function = (StubSignature)(i->Item._function);
auto callee = (void*)(i->Item._callee);
if (function != nullptr)
function(callee, Forward<Params>(params)...);
const FunctionType& item = i->Item;
ASSERT_LOW_LAYER(item._function);
item._function(item._callee, Forward<Params>(params)...);
}
#endif
}

View File

@@ -382,9 +382,8 @@ void Quaternion::GetRotationFromTo(const Float3& from, const Float3& to, Quatern
v0.Normalize();
v1.Normalize();
const float d = Float3::Dot(v0, v1);
// If dot == 1, vectors are the same
const float d = Float3::Dot(v0, v1);
if (d >= 1.0f)
{
result = Identity;

View File

@@ -1077,6 +1077,115 @@ namespace FlaxEngine
}
}
/// <summary>
/// Gets the shortest arc quaternion to rotate this vector to the destination vector.
/// </summary>
/// <param name="from">The source vector.</param>
/// <param name="to">The destination vector.</param>
/// <param name="result">The result.</param>
/// <param name="fallbackAxis">The fallback axis.</param>
public static void GetRotationFromTo(ref Float3 from, ref Float3 to, out Quaternion result, ref Float3 fallbackAxis)
{
// Based on Stan Melax's article in Game Programming Gems
Float3 v0 = from;
Float3 v1 = to;
v0.Normalize();
v1.Normalize();
// If dot == 1, vectors are the same
float d = Float3.Dot(ref v0, ref v1);
if (d >= 1.0f)
{
result = Identity;
return;
}
if (d < 1e-6f - 1.0f)
{
if (fallbackAxis != Float3.Zero)
{
// Rotate 180 degrees about the fallback axis
RotationAxis(ref fallbackAxis, Mathf.Pi, out result);
}
else
{
// Generate an axis
Float3 axis = Float3.Cross(Float3.UnitX, from);
if (axis.LengthSquared < Mathf.Epsilon) // Pick another if colinear
axis = Float3.Cross(Float3.UnitY, from);
axis.Normalize();
RotationAxis(ref axis, Mathf.Pi, out result);
}
}
else
{
float s = Mathf.Sqrt((1 + d) * 2);
float invS = 1 / s;
Float3.Cross(ref v0, ref v1, out var c);
result.X = c.X * invS;
result.Y = c.Y * invS;
result.Z = c.Z * invS;
result.W = s * 0.5f;
result.Normalize();
}
}
/// <summary>
/// Gets the shortest arc quaternion to rotate this vector to the destination vector.
/// </summary>
/// <param name="from">The source vector.</param>
/// <param name="to">The destination vector.</param>
/// <param name="fallbackAxis">The fallback axis.</param>
/// <returns>The rotation.</returns>
public static Quaternion GetRotationFromTo(Float3 from, Float3 to, Float3 fallbackAxis)
{
GetRotationFromTo(ref from, ref to, out var result, ref fallbackAxis);
return result;
}
/// <summary>
/// Gets the quaternion that will rotate vector from into vector to, around their plan perpendicular axis.The input vectors don't need to be normalized.
/// </summary>
/// <param name="from">The source vector.</param>
/// <param name="to">The destination vector.</param>
/// <param name="result">The result.</param>
public static void FindBetween(ref Float3 from, ref Float3 to, out Quaternion result)
{
// http://lolengine.net/blog/2014/02/24/quaternion-from-two-vectors-final
float normFromNormTo = Mathf.Sqrt(from.LengthSquared * to.LengthSquared);
if (normFromNormTo < Mathf.Epsilon)
{
result = Identity;
return;
}
float w = normFromNormTo + Float3.Dot(from, to);
if (w < 1.0-6f * normFromNormTo)
{
result = Mathf.Abs(from.X) > Mathf.Abs(from.Z)
? new Quaternion(-from.Y, from.X, 0.0f, 0.0f)
: new Quaternion(0.0f, -from.Z, from.Y, 0.0f);
}
else
{
Float3 cross = Float3.Cross(from, to);
result = new Quaternion(cross.X, cross.Y, cross.Z, w);
}
result.Normalize();
}
/// <summary>
/// Gets the quaternion that will rotate vector from into vector to, around their plan perpendicular axis.The input vectors don't need to be normalized.
/// </summary>
/// <param name="from">The source vector.</param>
/// <param name="to">The destination vector.</param>
/// <returns>The rotation.</returns>
public static Quaternion FindBetween(Float3 from, Float3 to)
{
FindBetween(ref from, ref to, out var result);
return result;
}
/// <summary>
/// Creates a left-handed spherical billboard that rotates around a specified object position.
/// </summary>

View File

@@ -43,14 +43,14 @@ public:
return Capacity;
}
FORCE_INLINE void Allocate(uint64 capacity)
FORCE_INLINE void Allocate(int32 capacity)
{
#if ENABLE_ASSERTION_LOW_LAYERS
ASSERT(capacity <= Capacity);
#endif
}
FORCE_INLINE void Relocate(uint64 capacity, int32 oldCount, int32 newCount)
FORCE_INLINE void Relocate(int32 capacity, int32 oldCount, int32 newCount)
{
#if ENABLE_ASSERTION_LOW_LAYERS
ASSERT(capacity <= Capacity);
@@ -120,12 +120,15 @@ public:
capacity |= capacity >> 4;
capacity |= capacity >> 8;
capacity |= capacity >> 16;
capacity = (capacity + 1) * 2;
uint64 capacity64 = (uint64)(capacity + 1) * 2;
if (capacity64 > MAX_int32)
capacity64 = MAX_int32;
capacity = (int32)capacity64;
}
return capacity;
}
FORCE_INLINE void Allocate(uint64 capacity)
FORCE_INLINE void Allocate(int32 capacity)
{
#if ENABLE_ASSERTION_LOW_LAYERS
ASSERT(!_data);
@@ -137,7 +140,7 @@ public:
#endif
}
FORCE_INLINE void Relocate(uint64 capacity, int32 oldCount, int32 newCount)
FORCE_INLINE void Relocate(int32 capacity, int32 oldCount, int32 newCount)
{
T* newData = capacity != 0 ? (T*)Allocator::Allocate(capacity * sizeof(T)) : nullptr;
#if !BUILD_RELEASE
@@ -210,7 +213,7 @@ public:
return minCapacity <= Capacity ? Capacity : _other.CalculateCapacityGrow(capacity, minCapacity);
}
FORCE_INLINE void Allocate(uint64 capacity)
FORCE_INLINE void Allocate(int32 capacity)
{
if (capacity > Capacity)
{
@@ -219,7 +222,7 @@ public:
}
}
FORCE_INLINE void Relocate(uint64 capacity, int32 oldCount, int32 newCount)
FORCE_INLINE void Relocate(int32 capacity, int32 oldCount, int32 newCount)
{
// Check if the new allocation will fit into inlined storage
if (capacity <= Capacity)

View File

@@ -327,6 +327,18 @@ public:
bool operator!=(const String& other) const;
public:
using StringViewBase::StartsWith;
FORCE_INLINE bool StartsWith(const StringView& prefix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
{
return StringViewBase::StartsWith(prefix, searchCase);
}
using StringViewBase::EndsWith;
FORCE_INLINE bool EndsWith(const StringView& suffix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
{
return StringViewBase::EndsWith(suffix, searchCase);
}
/// <summary>
/// Gets the left most given number of characters.
/// </summary>
@@ -511,6 +523,18 @@ public:
bool operator!=(const StringAnsi& other) const;
public:
using StringViewBase::StartsWith;
FORCE_INLINE bool StartsWith(const StringAnsiView& prefix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
{
return StringViewBase::StartsWith(prefix, searchCase);
}
using StringViewBase::EndsWith;
FORCE_INLINE bool EndsWith(const StringAnsiView& suffix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
{
return StringViewBase::EndsWith(suffix, searchCase);
}
/// <summary>
/// Retrieves substring created from characters starting from startIndex to the String end.
/// </summary>

View File

@@ -696,12 +696,15 @@ void* DebugDraw::AllocateContext()
void DebugDraw::FreeContext(void* context)
{
ASSERT(context);
Memory::DestructItem((DebugDrawContext*)context);
Allocator::Free(context);
}
void DebugDraw::UpdateContext(void* context, float deltaTime)
{
if (!context)
context = &GlobalContext;
((DebugDrawContext*)context)->DebugDrawDefault.Update(deltaTime);
((DebugDrawContext*)context)->DebugDrawDepthTest.Update(deltaTime);
}

View File

@@ -20,7 +20,6 @@
#include "Engine/Threading/MainThreadTask.h"
#include "Engine/Threading/ThreadRegistry.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Scripting/ManagedCLR/MCore.h"
#include "Engine/Scripting/ScriptingType.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/JsonAsset.h"
@@ -327,14 +326,6 @@ void Engine::OnUpdate()
// Update services
EngineService::OnUpdate();
#ifdef USE_NETCORE
// Force GC to run in background periodically to avoid large blocking collections causing hitches
if (Time::Update.TicksCount % 60 == 0)
{
MCore::GC::Collect(MCore::GC::MaxGeneration(), MGCCollectionMode::Forced, false, false);
}
#endif
}
void Engine::OnLateUpdate()

View File

@@ -26,12 +26,12 @@ public:
, _srcResource(src)
, _dstResource(dst)
{
_srcResource.OnUnload.Bind<GPUCopyResourceTask, &GPUCopyResourceTask::OnResourceUnload>(this);
_dstResource.OnUnload.Bind<GPUCopyResourceTask, &GPUCopyResourceTask::OnResourceUnload>(this);
_srcResource.Released.Bind<GPUCopyResourceTask, &GPUCopyResourceTask::OnResourceReleased>(this);
_dstResource.Released.Bind<GPUCopyResourceTask, &GPUCopyResourceTask::OnResourceReleased>(this);
}
private:
void OnResourceUnload(GPUResourceReference* ref)
void OnResourceReleased()
{
Cancel();
}
@@ -47,14 +47,11 @@ protected:
// [GPUTask]
Result run(GPUTasksContext* context) override
{
if (_srcResource.IsMissing() || _dstResource.IsMissing())
if (!_srcResource || !_dstResource)
return Result::MissingResources;
context->GPU->CopyResource(_dstResource, _srcResource);
return Result::Ok;
}
void OnEnd() override
{
_srcResource.Unlink();

View File

@@ -31,12 +31,12 @@ public:
, _srcSubresource(srcSubresource)
, _dstSubresource(dstSubresource)
{
_srcResource.OnUnload.Bind<GPUCopySubresourceTask, &GPUCopySubresourceTask::OnResourceUnload>(this);
_dstResource.OnUnload.Bind<GPUCopySubresourceTask, &GPUCopySubresourceTask::OnResourceUnload>(this);
_srcResource.Released.Bind<GPUCopySubresourceTask, &GPUCopySubresourceTask::OnResourceReleased>(this);
_dstResource.Released.Bind<GPUCopySubresourceTask, &GPUCopySubresourceTask::OnResourceReleased>(this);
}
private:
void OnResourceUnload(GPUResourceReference* ref)
void OnResourceReleased()
{
Cancel();
}
@@ -52,14 +52,11 @@ protected:
// [GPUTask]
Result run(GPUTasksContext* context) override
{
if (_srcResource.IsMissing() || _dstResource.IsMissing())
if (!_srcResource || !_dstResource)
return Result::MissingResources;
context->GPU->CopySubresource(_dstResource, _dstSubresource, _srcResource, _srcSubresource);
return Result::Ok;
}
void OnEnd() override
{
_srcResource.Unlink();

View File

@@ -31,7 +31,7 @@ public:
, _buffer(buffer)
, _offset(offset)
{
_buffer.OnUnload.Bind<GPUUploadBufferTask, &GPUUploadBufferTask::OnResourceUnload>(this);
_buffer.Released.Bind<GPUUploadBufferTask, &GPUUploadBufferTask::OnResourceReleased>(this);
if (copyData)
_data.Copy(data);
@@ -40,7 +40,7 @@ public:
}
private:
void OnResourceUnload(BufferReference* ref)
void OnResourceReleased()
{
Cancel();
}
@@ -56,14 +56,11 @@ protected:
// [GPUTask]
Result run(GPUTasksContext* context) override
{
if (_buffer.IsMissing())
if (!_buffer)
return Result::MissingResources;
context->GPU->UpdateBuffer(_buffer, _data.Get(), _data.Length(), _offset);
return Result::Ok;
}
void OnEnd() override
{
_buffer.Unlink();

View File

@@ -35,7 +35,7 @@ public:
, _rowPitch(rowPitch)
, _slicePitch(slicePitch)
{
_texture.OnUnload.Bind<GPUUploadTextureMipTask, &GPUUploadTextureMipTask::OnResourceUnload>(this);
_texture.Released.Bind<GPUUploadTextureMipTask, &GPUUploadTextureMipTask::OnResourceReleased>(this);
if (copyData)
_data.Copy(data);
@@ -44,7 +44,7 @@ public:
}
private:
void OnResourceUnload(GPUTextureReference* ref)
void OnResourceReleased()
{
Cancel();
}

View File

@@ -3,6 +3,7 @@
#include "GPUDevice.h"
#include "RenderTargetPool.h"
#include "GPUPipelineState.h"
#include "GPUResourceProperty.h"
#include "GPUSwapChain.h"
#include "RenderTask.h"
#include "RenderTools.h"
@@ -25,6 +26,39 @@
#include "Engine/Renderer/RenderList.h"
#include "Engine/Scripting/Enums.h"
GPUResourcePropertyBase::~GPUResourcePropertyBase()
{
const auto e = _resource;
if (e)
{
_resource = nullptr;
e->Releasing.Unbind<GPUResourcePropertyBase, &GPUResourcePropertyBase::OnReleased>(this);
}
}
void GPUResourcePropertyBase::OnSet(GPUResource* resource)
{
auto e = _resource;
if (e != resource)
{
if (e)
e->Releasing.Unbind<GPUResourcePropertyBase, &GPUResourcePropertyBase::OnReleased>(this);
_resource = e = resource;
if (e)
e->Releasing.Bind<GPUResourcePropertyBase, &GPUResourcePropertyBase::OnReleased>(this);
}
}
void GPUResourcePropertyBase::OnReleased()
{
auto e = _resource;
if (e)
{
_resource = nullptr;
e->Releasing.Unbind<GPUResourcePropertyBase, &GPUResourcePropertyBase::OnReleased>(this);
}
}
GPUPipelineState* GPUPipelineState::Spawn(const SpawnParams& params)
{
return GPUDevice::Instance->CreatePipelineState();

View File

@@ -8,28 +8,39 @@
/// <summary>
/// GPU Resource container utility object.
/// </summary>
template<typename T = GPUResource>
class GPUResourceProperty
class FLAXENGINE_API GPUResourcePropertyBase
{
private:
T* _resource;
protected:
GPUResource* _resource = nullptr;
private:
// Disable copy actions
GPUResourceProperty(const GPUResourceProperty& other) = delete;
public:
NON_COPYABLE(GPUResourcePropertyBase);
GPUResourcePropertyBase() = default;
~GPUResourcePropertyBase();
public:
/// <summary>
/// Action fired when resource gets unloaded (reference gets cleared bu async tasks should stop execution).
/// Action fired when resource gets released (reference gets cleared bu async tasks should stop execution).
/// </summary>
Delegate<GPUResourceProperty*> OnUnload;
Action Released;
protected:
void OnSet(GPUResource* resource);
void OnReleased();
};
/// <summary>
/// GPU Resource container utility object.
/// </summary>
template<typename T = GPUResource>
class GPUResourceProperty : public GPUResourcePropertyBase
{
public:
/// <summary>
/// Initializes a new instance of the <see cref="GPUResourceProperty"/> class.
/// </summary>
GPUResourceProperty()
: _resource(nullptr)
{
}
@@ -38,9 +49,37 @@ public:
/// </summary>
/// <param name="resource">The resource.</param>
GPUResourceProperty(T* resource)
: _resource(nullptr)
{
Set(resource);
OnSet(resource);
}
/// <summary>
/// Initializes a new instance of the <see cref="GPUResourceProperty"/> class.
/// </summary>
/// <param name="other">The other value.</param>
GPUResourceProperty(const GPUResourceProperty& other)
{
OnSet(other.Get());
}
/// <summary>
/// Initializes a new instance of the <see cref="GPUResourceProperty"/> class.
/// </summary>
/// <param name="other">The other value.</param>
GPUResourceProperty(GPUResourceProperty&& other)
{
OnSet(other.Get());
other.OnSet(nullptr);
}
GPUResourceProperty& operator=(GPUResourceProperty&& other)
{
if (&other != this)
{
OnSet(other._resource);
other.OnSet(nullptr);
}
return *this;
}
/// <summary>
@@ -48,13 +87,6 @@ public:
/// </summary>
~GPUResourceProperty()
{
// Check if object has been binded
if (_resource)
{
// Unlink
_resource->Releasing.template Unbind<GPUResourceProperty, &GPUResourceProperty::onResourceUnload>(this);
_resource = nullptr;
}
}
public:
@@ -63,43 +95,34 @@ public:
return Get() == other;
}
FORCE_INLINE bool operator==(GPUResourceProperty& other) const
FORCE_INLINE bool operator==(const GPUResourceProperty& other) const
{
return Get() == other.Get();
}
GPUResourceProperty& operator=(const GPUResourceProperty& other)
{
if (this != &other)
Set(other.Get());
return *this;
}
FORCE_INLINE GPUResourceProperty& operator=(T& other)
{
Set(&other);
OnSet(&other);
return *this;
}
FORCE_INLINE GPUResourceProperty& operator=(T* other)
{
Set(other);
OnSet(other);
return *this;
}
/// <summary>
/// Implicit conversion to GPU Resource
/// </summary>
/// <returns>Resource</returns>
FORCE_INLINE operator T*() const
{
return _resource;
return (T*)_resource;
}
/// <summary>
/// Implicit conversion to resource
/// </summary>
/// <returns>True if resource has been binded, otherwise false</returns>
FORCE_INLINE operator bool() const
{
return _resource != nullptr;
@@ -108,37 +131,17 @@ public:
/// <summary>
/// Implicit conversion to resource
/// </summary>
/// <returns>Resource</returns>
FORCE_INLINE T* operator->() const
{
return _resource;
return (T*)_resource;
}
/// <summary>
/// Gets linked resource
/// </summary>
/// <returns>Resource</returns>
FORCE_INLINE T* Get() const
{
return _resource;
}
/// <summary>
/// Checks if resource has been binded
/// </summary>
/// <returns>True if resource has been binded, otherwise false</returns>
FORCE_INLINE bool IsBinded() const
{
return _resource != nullptr;
}
/// <summary>
/// Checks if resource is missing
/// </summary>
/// <returns>True if resource is missing, otherwise false</returns>
FORCE_INLINE bool IsMissing() const
{
return _resource == nullptr;
return (T*)_resource;
}
public:
@@ -148,19 +151,7 @@ public:
/// <param name="value">Value to assign</param>
void Set(T* value)
{
if (_resource != value)
{
// Remove reference from the old one
if (_resource)
_resource->Releasing.template Unbind<GPUResourceProperty, &GPUResourceProperty::onResourceUnload>(this);
// Change referenced object
_resource = value;
// Add reference to the new one
if (_resource)
_resource->Releasing.template Bind<GPUResourceProperty, &GPUResourceProperty::onResourceUnload>(this);
}
OnSet(value);
}
/// <summary>
@@ -168,22 +159,7 @@ public:
/// </summary>
void Unlink()
{
if (_resource)
{
// Remove reference from the old one
_resource->Releasing.template Unbind<GPUResourceProperty, &GPUResourceProperty::onResourceUnload>(this);
_resource = nullptr;
}
}
private:
void onResourceUnload()
{
if (_resource)
{
_resource = nullptr;
OnUnload(this);
}
OnSet(nullptr);
}
};

View File

@@ -90,6 +90,21 @@ public:
/// </summary>
Array<BlendShape> BlendShapes;
/// <summary>
/// Global translation for this mesh to be at it's local origin.
/// </summary>
Vector3 OriginTranslation = Vector3::Zero;
/// <summary>
/// Orientation for this mesh at it's local origin.
/// </summary>
Quaternion OriginOrientation = Quaternion::Identity;
/// <summary>
/// Meshes scaling.
/// </summary>
Vector3 Scaling = Vector3::One;
public:
/// <summary>
/// Determines whether this instance has any mesh data.

View File

@@ -5,6 +5,7 @@
#include "Engine/Core/Math/Vector3.h"
#include "Engine/Core/Math/Vector4.h"
#include "Engine/Content/AssetReference.h"
#include "Engine/Content/SoftAssetReference.h"
#include "Engine/Core/ISerializable.h"
#include "Engine/Content/Assets/Texture.h"
#include "Engine/Content/Assets/MaterialBase.h"
@@ -850,7 +851,7 @@ API_STRUCT() struct FLAXENGINE_API ColorGradingSettings : ISerializable
/// The Lookup Table (LUT) used to perform color correction.
/// </summary>
API_FIELD(Attributes="DefaultValue(null), EditorOrder(22), PostProcessSetting((int)ColorGradingSettingsOverride.LutTexture)")
AssetReference<Texture> LutTexture;
SoftAssetReference<Texture> LutTexture;
/// <summary>
/// The LUT blending weight (normalized to range 0-1). Default is 1.0.
@@ -1277,7 +1278,7 @@ API_STRUCT() struct FLAXENGINE_API LensFlaresSettings : ISerializable
/// Fullscreen lens dirt texture.
/// </summary>
API_FIELD(Attributes="DefaultValue(null), EditorOrder(8), PostProcessSetting((int)LensFlaresSettingsOverride.LensDirt)")
AssetReference<Texture> LensDirt;
SoftAssetReference<Texture> LensDirt;
/// <summary>
/// Fullscreen lens dirt intensity parameter. Allows to tune dirt visibility.
@@ -1289,13 +1290,13 @@ API_STRUCT() struct FLAXENGINE_API LensFlaresSettings : ISerializable
/// Custom lens color texture (1D) used for lens color spectrum.
/// </summary>
API_FIELD(Attributes="DefaultValue(null), EditorOrder(10), PostProcessSetting((int)LensFlaresSettingsOverride.LensColor)")
AssetReference<Texture> LensColor;
SoftAssetReference<Texture> LensColor;
/// <summary>
/// Custom lens star texture sampled by lens flares.
/// </summary>
API_FIELD(Attributes="DefaultValue(null), EditorOrder(11), PostProcessSetting((int)LensFlaresSettingsOverride.LensStar)")
AssetReference<Texture> LensStar;
SoftAssetReference<Texture> LensStar;
public:
/// <summary>
@@ -1487,7 +1488,7 @@ API_STRUCT() struct FLAXENGINE_API DepthOfFieldSettings : ISerializable
/// If BokehShape is set to Custom, then this texture will be used for the bokeh shapes. For best performance, use small, compressed, grayscale textures (for instance 32px).
/// </summary>
API_FIELD(Attributes="DefaultValue(null), EditorOrder(11), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehShapeCustom)")
AssetReference<Texture> BokehShapeCustom;
SoftAssetReference<Texture> BokehShapeCustom;
/// <summary>
/// The minimum pixel brightness to create bokeh. Pixels with lower brightness will be skipped.

View File

@@ -22,10 +22,10 @@ TextureHeader::TextureHeader()
TextureGroup = -1;
}
TextureHeader::TextureHeader(TextureHeader_Deprecated& old)
TextureHeader::TextureHeader(const TextureHeader_Deprecated& old)
{
Platform::MemoryClear(this, sizeof(*this));
Width = old.Width;;
Width = old.Width;
Height = old.Height;
MipLevels = old.MipLevels;
Format = old.Format;
@@ -49,7 +49,7 @@ StreamingTexture::StreamingTexture(ITextureOwner* parent, const String& name)
, _texture(nullptr)
, _isBlockCompressed(false)
{
ASSERT(_owner != nullptr);
ASSERT(parent != nullptr);
// Always have created texture object
ASSERT(GPUDevice::Instance);
@@ -329,11 +329,11 @@ public:
, _dataLock(_streamingTexture->GetOwner()->LockData())
{
_streamingTexture->_streamingTasks.Add(this);
_texture.OnUnload.Bind<StreamTextureMipTask, &StreamTextureMipTask::onResourceUnload2>(this);
_texture.Released.Bind<StreamTextureMipTask, &StreamTextureMipTask::OnResourceReleased2>(this);
}
private:
void onResourceUnload2(GPUTextureReference* ref)
void OnResourceReleased2()
{
// Unlink texture
if (_streamingTexture)

View File

@@ -106,5 +106,5 @@ struct FLAXENGINE_API TextureHeader
byte CustomData[10];
TextureHeader();
TextureHeader(TextureHeader_Deprecated& old);
TextureHeader(const TextureHeader_Deprecated& old);
};

View File

@@ -1406,7 +1406,7 @@ Script* Actor::FindScript(const MClass* type) const
CHECK_RETURN(type, nullptr);
for (auto script : Scripts)
{
if (script->GetClass()->IsSubClassOf(type))
if (script->GetClass()->IsSubClassOf(type) || script->GetClass()->HasInterface(type))
return script;
}
for (auto child : Children)

View File

@@ -66,7 +66,7 @@ public:
/// <summary>
/// Gets the value indicating if camera should use perspective rendering mode, otherwise it will use orthographic projection.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(true), EditorDisplay(\"Camera\"), Tooltip(\"Enables perspective projection mode, otherwise uses orthographic.\")")
API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(true), EditorDisplay(\"Camera\")")
bool GetUsePerspective() const;
/// <summary>
@@ -77,7 +77,7 @@ public:
/// <summary>
/// Gets the camera's field of view (in degrees).
/// </summary>
API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(60.0f), Limit(0, 179), EditorDisplay(\"Camera\", \"Field Of View\"), Tooltip(\"Field of view angle in degrees.\")")
API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(60.0f), Limit(0, 179), EditorDisplay(\"Camera\", \"Field Of View\"), VisibleIf(nameof(UsePerspective))")
float GetFieldOfView() const;
/// <summary>
@@ -88,7 +88,7 @@ public:
/// <summary>
/// Gets the custom aspect ratio. 0 if not use custom value.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(50), DefaultValue(0.0f), Limit(0, 10, 0.01f), EditorDisplay(\"Camera\"), Tooltip(\"Custom aspect ratio to use. Set to 0 to disable.\")")
API_PROPERTY(Attributes="EditorOrder(50), DefaultValue(0.0f), Limit(0, 10, 0.01f), EditorDisplay(\"Camera\"), VisibleIf(nameof(UsePerspective))")
float GetCustomAspectRatio() const;
/// <summary>
@@ -99,7 +99,7 @@ public:
/// <summary>
/// Gets camera's near plane distance.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(30), DefaultValue(10.0f), Limit(0, 1000, 0.05f), EditorDisplay(\"Camera\"), Tooltip(\"Near clipping plane distance\")")
API_PROPERTY(Attributes="EditorOrder(30), DefaultValue(10.0f), Limit(0, 1000, 0.05f), EditorDisplay(\"Camera\")")
float GetNearPlane() const;
/// <summary>
@@ -110,7 +110,7 @@ public:
/// <summary>
/// Gets camera's far plane distance.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(40000.0f), Limit(0, float.MaxValue, 5), EditorDisplay(\"Camera\"), Tooltip(\"Far clipping plane distance\")")
API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(40000.0f), Limit(0, float.MaxValue, 5), EditorDisplay(\"Camera\")")
float GetFarPlane() const;
/// <summary>
@@ -121,7 +121,7 @@ public:
/// <summary>
/// Gets the orthographic projection scale.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(60), DefaultValue(1.0f), Limit(0.0001f, 1000, 0.01f), EditorDisplay(\"Camera\"), Tooltip(\"Orthographic projection scale\")")
API_PROPERTY(Attributes="EditorOrder(60), DefaultValue(1.0f), Limit(0.0001f, 1000, 0.01f), EditorDisplay(\"Camera\"), VisibleIf(nameof(UsePerspective), true)")
float GetOrthographicScale() const;
/// <summary>

View File

@@ -24,7 +24,7 @@ private:
public:
/// <summary>
/// Light source bulb radius
/// Light source bulb radius.
/// </summary>
API_FIELD(Attributes="EditorOrder(2), DefaultValue(0.0f), EditorDisplay(\"Light\"), Limit(0, 1000, 0.01f)")
float SourceRadius = 0.0f;
@@ -42,54 +42,51 @@ public:
float FallOffExponent = 8.0f;
/// <summary>
/// IES texture (light profiles from real world measured data)
/// IES texture (light profiles from real world measured data).
/// </summary>
API_FIELD(Attributes="EditorOrder(211), DefaultValue(null), EditorDisplay(\"IES Profile\", \"IES Texture\")")
AssetReference<IESProfile> IESTexture;
/// <summary>
/// Enable/disable using light brightness from IES profile
/// Enable/disable using light brightness from IES profile.
/// </summary>
API_FIELD(Attributes="EditorOrder(212), DefaultValue(false), EditorDisplay(\"IES Profile\", \"Use IES Brightness\")")
bool UseIESBrightness = false;
/// <summary>
/// Global scale for IES brightness contribution
/// Global scale for IES brightness contribution.
/// </summary>
API_FIELD(Attributes="EditorOrder(213), DefaultValue(1.0f), Limit(0, 10000, 0.01f), EditorDisplay(\"IES Profile\", \"Brightness Scale\")")
float IESBrightnessScale = 1.0f;
public:
/// <summary>
/// Computes light brightness value
/// Computes light brightness value.
/// </summary>
/// <returns>Brightness</returns>
float ComputeBrightness() const;
/// <summary>
/// Gets scaled light radius
/// Gets scaled light radius.
/// </summary>
float GetScaledRadius() const;
/// <summary>
/// Gets light radius
/// Gets light radius.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(1), DefaultValue(1000.0f), EditorDisplay(\"Light\"), Tooltip(\"Light radius\"), Limit(0, 10000, 0.1f)")
API_PROPERTY(Attributes="EditorOrder(1), DefaultValue(1000.0f), EditorDisplay(\"Light\"), Limit(0, 10000, 0.1f)")
FORCE_INLINE float GetRadius() const
{
return _radius;
}
/// <summary>
/// Sets light radius
/// Sets light radius.
/// </summary>
/// <param name="value">New radius</param>
API_PROPERTY() void SetRadius(float value);
/// <summary>
/// Gets the spot light's outer cone angle (in degrees)
/// Gets the spot light's outer cone angle (in degrees).
/// </summary>
/// <returns>Outer angle (in degrees)</returns>
API_PROPERTY(Attributes="EditorOrder(22), DefaultValue(43.0f), EditorDisplay(\"Light\"), Limit(1, 89, 0.1f)")
FORCE_INLINE float GetOuterConeAngle() const
{
@@ -97,15 +94,13 @@ public:
}
/// <summary>
/// Sets the spot light's outer cone angle (in degrees)
/// Sets the spot light's outer cone angle (in degrees).
/// </summary>
/// <param name="value">Value to assign</param>
API_PROPERTY() void SetOuterConeAngle(float value);
/// <summary>
/// Sets the spot light's inner cone angle (in degrees)
/// Sets the spot light's inner cone angle (in degrees).
/// </summary>
/// <returns>Inner angle (in degrees)</returns>
API_PROPERTY(Attributes="EditorOrder(21), DefaultValue(10.0f), EditorDisplay(\"Light\"), Limit(1, 89, 0.1f)")
FORCE_INLINE float GetInnerConeAngle() const
{
@@ -113,9 +108,8 @@ public:
}
/// <summary>
/// Sets the spot light's inner cone angle (in degrees)
/// Sets the spot light's inner cone angle (in degrees).
/// </summary>
/// <param name="value">Value to assign</param>
API_PROPERTY() void SetInnerConeAngle(float value);
private:

View File

@@ -19,7 +19,7 @@ API_CLASS(Static) class FLAXENGINE_API LargeWorlds
/// <summary>
/// Defines the size of a single chunk. Large world (64-bit) gets divided into smaller chunks so all the math operations (32-bit) can be performed relative to the chunk origin without precision loss.
/// </summary>
API_FIELD() static constexpr Real ChunkSize = 262144;
API_FIELD() static constexpr Real ChunkSize = 8192;
/// <summary>
/// Updates the large world origin to match the input position. The origin is snapped to the best matching chunk location.

View File

@@ -30,6 +30,20 @@ typedef struct
} MonoCultureInfo;
#endif
namespace
{
const CultureInfoEntry* FindEntry(const StringAnsiView& name)
{
for (int32 i = 0; i < NUM_CULTURE_ENTRIES; i++)
{
const CultureInfoEntry& e = culture_entries[i];
if (name == idx2string(e.name))
return &e;
}
return nullptr;
}
};
CultureInfo::CultureInfo(int32 lcid)
{
_lcid = lcid;
@@ -80,23 +94,24 @@ CultureInfo::CultureInfo(const StringAnsiView& name)
_englishName = TEXT("Invariant Culture");
return;
}
for (int32 i = 0; i < NUM_CULTURE_ENTRIES; i++)
const CultureInfoEntry* e = FindEntry(name);
if (!e && name.Find('-') != -1)
{
auto& e = culture_entries[i];
if (name == idx2string(e.name))
{
_data = (void*)&e;
_lcid = (int32)e.lcid;
_lcidParent = (int32)e.parent_lcid;
_name.SetUTF8(name.Get(), name.Length());
const char* nativename = idx2string(e.nativename);
_nativeName.SetUTF8(nativename, StringUtils::Length(nativename));
const char* englishname = idx2string(e.englishname);
_englishName.SetUTF8(englishname, StringUtils::Length(englishname));
break;
}
e = FindEntry(name.Substring(0, name.Find('-')));
}
if (!_data)
if (e)
{
_data = (void*)e;
_lcid = (int32)e->lcid;
_lcidParent = (int32)e->parent_lcid;
const char* ename = idx2string(e->name);
_name.SetUTF8(ename, StringUtils::Length(ename));
const char* nativename = idx2string(e->nativename);
_nativeName.SetUTF8(nativename, StringUtils::Length(nativename));
const char* englishname = idx2string(e->englishname);
_englishName.SetUTF8(englishname, StringUtils::Length(englishname));
}
else
{
_lcid = 127;
_lcidParent = 0;

View File

@@ -4,6 +4,9 @@
#include "IOnlinePlatform.h"
#include "Engine/Core/Log.h"
#include "Engine/Engine/EngineService.h"
#if USE_EDITOR
#include "Engine/Scripting/Scripting.h"
#endif
#include "Engine/Scripting/ScriptingObject.h"
class OnlineService : public EngineService
@@ -25,6 +28,16 @@ IOnlinePlatform* Online::Platform = nullptr;
Action Online::PlatformChanged;
OnlineService OnlineServiceInstance;
#if USE_EDITOR
void OnOnlineScriptsReloading()
{
// Dispose any active platform
Online::Initialize(nullptr);
}
#endif
bool Online::Initialize(IOnlinePlatform* platform)
{
if (Platform == platform)
@@ -34,6 +47,9 @@ bool Online::Initialize(IOnlinePlatform* platform)
if (Platform)
{
#if USE_EDITOR
Scripting::ScriptsReloading.Unbind(OnOnlineScriptsReloading);
#endif
Platform->Deinitialize();
}
Platform = platform;
@@ -45,6 +61,9 @@ bool Online::Initialize(IOnlinePlatform* platform)
LOG(Error, "Failed to initialize online platform.");
return true;
}
#if USE_EDITOR
Scripting::ScriptsReloading.Bind(OnOnlineScriptsReloading);
#endif
}
return false;

View File

@@ -41,19 +41,8 @@ protected:
public:
/// <summary>
/// Enables kinematic mode for the rigidbody.
/// Enables kinematic mode for the rigidbody. Kinematic rigidbodies are special dynamic actors that are not influenced by forces(such as gravity), and have no momentum. They are considered to have infinite mass and can push regular dynamic actors out of the way. Kinematics will not collide with static or other kinematic objects but are great for moving platforms or characters, where direct motion control is desired.
/// </summary>
/// <remarks>
/// Kinematic rigidbodies are special dynamic actors that are not influenced by forces(such as gravity), and have no momentum.
/// They are considered to have infinite mass and can push regular dynamic actors out of the way.
/// Kinematics will not collide with static or other kinematic objects.
/// <para>
/// Kinematic rigidbodies are great for moving platforms or characters, where direct motion control is desired.
/// </para>
/// <para>
/// Kinematic rigidbodies are incompatible with CCD.
/// </para>
/// </remarks>
API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(false), EditorDisplay(\"Rigid Body\")")
FORCE_INLINE bool GetIsKinematic() const
{
@@ -61,26 +50,13 @@ public:
}
/// <summary>
/// Enables kinematic mode for the rigidbody.
/// Enables kinematic mode for the rigidbody. Kinematic rigidbodies are special dynamic actors that are not influenced by forces(such as gravity), and have no momentum. They are considered to have infinite mass and can push regular dynamic actors out of the way. Kinematics will not collide with static or other kinematic objects but are great for moving platforms or characters, where direct motion control is desired.
/// </summary>
/// <remarks>
/// Kinematic rigidbodies are special dynamic actors that are not influenced by forces(such as gravity), and have no momentum.
/// They are considered to have infinite mass and can push regular dynamic actors out of the way.
/// Kinematics will not collide with static or other kinematic objects.
/// <para>
/// Kinematic rigidbodies are great for moving platforms or characters, where direct motion control is desired.
/// </para>
/// <para>
/// Kinematic rigidbodies are incompatible with CCD.
/// </para>
/// </remarks>
/// <param name="value">The value.</param>
API_PROPERTY() void SetIsKinematic(const bool value);
/// <summary>
/// Gets the 'drag' force added to reduce linear movement.
/// Gets the 'drag' force added to reduce linear movement. Linear damping can be used to slow down an object. The higher the drag the more the object slows down.
/// </summary>
/// <remarks>Linear damping can be used to slow down an object. The higher the drag the more the object slows down.</remarks>
API_PROPERTY(Attributes="EditorOrder(60), DefaultValue(0.01f), Limit(0), EditorDisplay(\"Rigid Body\")")
FORCE_INLINE float GetLinearDamping() const
{
@@ -88,16 +64,13 @@ public:
}
/// <summary>
/// Sets the 'drag' force added to reduce linear movement.
/// Sets the 'drag' force added to reduce linear movement. Linear damping can be used to slow down an object. The higher the drag the more the object slows down.
/// </summary>
/// <remarks>Linear damping can be used to slow down an object. The higher the drag the more the object slows down.</remarks>
/// <param name="value">The value.</param>
API_PROPERTY() void SetLinearDamping(float value);
/// <summary>
/// Gets the 'drag' force added to reduce angular movement.
/// Gets the 'drag' force added to reduce angular movement. Angular damping can be used to slow down the rotation of an object. The higher the drag the more the rotation slows down.
/// </summary>
/// <remarks>Angular damping can be used to slow down the rotation of an object. The higher the drag the more the rotation slows down.</remarks>
API_PROPERTY(Attributes="EditorOrder(70), DefaultValue(0.05f), Limit(0), EditorDisplay(\"Rigid Body\")")
FORCE_INLINE float GetAngularDamping() const
{
@@ -105,9 +78,8 @@ public:
}
/// <summary>
/// Sets the 'drag' force added to reduce angular movement.
/// Sets the 'drag' force added to reduce angular movement. Angular damping can be used to slow down the rotation of an object. The higher the drag the more the rotation slows down.
/// </summary>
/// <remarks>Angular damping can be used to slow down the rotation of an object. The higher the drag the more the rotation slows down.</remarks>
/// <param name="value">The value.</param>
API_PROPERTY() void SetAngularDamping(float value);
@@ -123,7 +95,6 @@ public:
/// <summary>
/// If true simulation and collisions detection will be enabled for the rigidbody.
/// </summary>
/// <param name="value">The value.</param>
API_PROPERTY() void SetEnableSimulation(bool value);
/// <summary>
@@ -138,7 +109,6 @@ public:
/// <summary>
/// If true Continuous Collision Detection (CCD) will be used for this component.
/// </summary>
/// <param name="value">The value.</param>
API_PROPERTY() void SetUseCCD(const bool value);
/// <summary>
@@ -153,7 +123,6 @@ public:
/// <summary>
/// If object should have the force of gravity applied.
/// </summary>
/// <param name="value">The value.</param>
API_PROPERTY() void SetEnableGravity(bool value);
/// <summary>
@@ -168,7 +137,6 @@ public:
/// <summary>
/// If object should start awake, or if it should initially be sleeping.
/// </summary>
/// <param name="value">The value.</param>
API_PROPERTY() void SetStartAwake(bool value);
/// <summary>
@@ -183,16 +151,11 @@ public:
/// <summary>
/// If true, it will update mass when actor scale changes.
/// </summary>
/// <param name="value">The value.</param>
API_PROPERTY() void SetUpdateMassWhenScaleChanges(bool value);
/// <summary>
/// Gets the maximum angular velocity that a simulated object can achieve.
/// Gets the maximum angular velocity that a simulated object can achieve. The angular velocity of rigidbodies is clamped to MaxAngularVelocity to avoid numerical instability with fast rotating bodies. Because this may prevent intentional fast rotations on objects such as wheels, you can override this value per rigidbody.
/// </summary>
/// <remarks>
/// The angular velocity of rigidbodies is clamped to MaxAngularVelocity to avoid numerical instability with fast rotating bodies.
/// Because this may prevent intentional fast rotations on objects such as wheels, you can override this value per rigidbody.
/// </remarks>
API_PROPERTY(Attributes="EditorOrder(90), DefaultValue(7.0f), Limit(0), EditorDisplay(\"Rigid Body\")")
FORCE_INLINE float GetMaxAngularVelocity() const
{
@@ -200,13 +163,8 @@ public:
}
/// <summary>
/// Sets the maximum angular velocity that a simulated object can achieve.
/// Sets the maximum angular velocity that a simulated object can achieve. The angular velocity of rigidbodies is clamped to MaxAngularVelocity to avoid numerical instability with fast rotating bodies. Because this may prevent intentional fast rotations on objects such as wheels, you can override this value per rigidbody.
/// </summary>
/// <remarks>
/// The angular velocity of rigidbodies is clamped to MaxAngularVelocity to avoid numerical instability with fast rotating bodies.
/// Because this may prevent intentional fast rotations on objects such as wheels, you can override this value per rigidbody.
/// </remarks>
/// <param name="value">The value.</param>
API_PROPERTY() void SetMaxAngularVelocity(float value);
/// <summary>
@@ -218,7 +176,6 @@ public:
/// <summary>
/// Override the auto computed mass.
/// </summary>
/// <param name="value">The value.</param>
API_PROPERTY() void SetOverrideMass(bool value);
/// <summary>
@@ -230,8 +187,6 @@ public:
/// <summary>
/// Sets the mass value measured in kilograms (use override value only if OverrideMass is checked).
/// </summary>
/// <remarks>If set auto enables mass override.</remarks>
/// <param name="value">The value.</param>
API_PROPERTY() void SetMass(float value);
/// <summary>
@@ -243,7 +198,6 @@ public:
/// <summary>
/// Sets the per-instance scaling of the mass.
/// </summary>
/// <param name="value">The value.</param>
API_PROPERTY() void SetMassScale(float value);
/// <summary>
@@ -258,7 +212,6 @@ public:
/// <summary>
/// Sets the user specified offset for the center of mass of this object, from the calculated location.
/// </summary>
/// <param name="value">The value.</param>
API_PROPERTY() void SetCenterOfMassOffset(const Float3& value);
/// <summary>
@@ -273,28 +226,27 @@ public:
/// <summary>
/// Sets the object movement constraint flags that define degrees of freedom are allowed for the simulation of object.
/// </summary>
/// <param name="value">The value.</param>
API_PROPERTY() void SetConstraints(const RigidbodyConstraints value);
public:
/// <summary>
/// Gets the linear velocity of the rigidbody.
/// </summary>
/// <remarks>It's used mostly to get the current velocity. Manual modifications may result in unrealistic behaviour. </remarks>
/// <remarks>It's used mostly to get the current velocity. Manual modifications may result in unrealistic behaviour.</remarks>
API_PROPERTY(Attributes="HideInEditor")
Vector3 GetLinearVelocity() const;
/// <summary>
/// Sets the linear velocity of the rigidbody.
/// </summary>
/// <remarks>It's used mostly to get the current velocity. Manual modifications may result in unrealistic behaviour. </remarks>
/// <remarks>It's used mostly to get the current velocity. Manual modifications may result in unrealistic behaviour.</remarks>
/// <param name="value">The value.</param>
API_PROPERTY() void SetLinearVelocity(const Vector3& value) const;
/// <summary>
/// Gets the angular velocity of the rigidbody measured in radians per second.
/// </summary>
/// <remarks>It's used mostly to get the current angular velocity. Manual modifications may result in unrealistic behaviour. </remarks>
/// <remarks>It's used mostly to get the current angular velocity. Manual modifications may result in unrealistic behaviour.</remarks>
API_PROPERTY(Attributes="HideInEditor")
Vector3 GetAngularVelocity() const;

View File

@@ -81,6 +81,13 @@ void BoxCollider::OnDebugDrawSelected()
const Color color = Color::GreenYellow;
DEBUG_DRAW_WIRE_BOX(_bounds, color * 0.3f, 0, false);
if (_contactOffset > 0)
{
OrientedBoundingBox contactBounds = _bounds;
contactBounds.Extents += Vector3(_contactOffset) / contactBounds.Transformation.Scale;
DEBUG_DRAW_WIRE_BOX(contactBounds, Color::Blue.AlphaMultiplied(0.2f), 0, false);
}
Vector3 corners[8];
_bounds.GetCorners(corners);
const float margin = 1.0f;

View File

@@ -42,27 +42,33 @@ void CapsuleCollider::DrawPhysicsDebug(RenderView& view)
const BoundingSphere sphere(_sphere.Center - view.Origin, _sphere.Radius);
if (!view.CullingFrustum.Intersects(sphere))
return;
Quaternion rot;
Quaternion::Multiply(_transform.Orientation, Quaternion::Euler(0, 90, 0), rot);
Quaternion rotation;
Quaternion::Multiply(_transform.Orientation, Quaternion::Euler(0, 90, 0), rotation);
const float scaling = _cachedScale.GetAbsolute().MaxValue();
const float minSize = 0.001f;
const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize);
const float height = Math::Max(Math::Abs(_height) * scaling, minSize);
if (view.Mode == ViewMode::PhysicsColliders && !GetIsTrigger())
DEBUG_DRAW_TUBE(_transform.LocalToWorld(_center), rot, radius, height, _staticActor ? Color::CornflowerBlue : Color::Orchid, 0, true);
DEBUG_DRAW_TUBE(_transform.LocalToWorld(_center), rotation, radius, height, _staticActor ? Color::CornflowerBlue : Color::Orchid, 0, true);
else
DEBUG_DRAW_WIRE_TUBE(_transform.LocalToWorld(_center), rot, radius, height, Color::GreenYellow * 0.8f, 0, true);
DEBUG_DRAW_WIRE_TUBE(_transform.LocalToWorld(_center), rotation, radius, height, Color::GreenYellow * 0.8f, 0, true);
}
void CapsuleCollider::OnDebugDrawSelected()
{
Quaternion rot;
Quaternion::Multiply(_transform.Orientation, Quaternion::Euler(0, 90, 0), rot);
Quaternion rotation;
Quaternion::Multiply(_transform.Orientation, Quaternion::Euler(0, 90, 0), rotation);
const float scaling = _cachedScale.GetAbsolute().MaxValue();
const float minSize = 0.001f;
const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize);
const float height = Math::Max(Math::Abs(_height) * scaling, minSize);
DEBUG_DRAW_WIRE_TUBE(_transform.LocalToWorld(_center), rot, radius, height, Color::GreenYellow, 0, false);
const Vector3 position = _transform.LocalToWorld(_center);
DEBUG_DRAW_WIRE_TUBE(position, rotation, radius, height, Color::GreenYellow, 0, false);
if (_contactOffset > 0)
{
DEBUG_DRAW_WIRE_TUBE(position, rotation, radius + _contactOffset, height, Color::Blue.AlphaMultiplied(0.2f), 0, false);
}
// Base
Collider::OnDebugDrawSelected();

View File

@@ -23,6 +23,7 @@ CharacterController::CharacterController(const SpawnParams& params)
, _nonWalkableMode(NonWalkableModes::PreventClimbing)
, _lastFlags(CollisionFlags::None)
{
_contactOffset = 10.0f;
}
float CharacterController::GetRadius() const

View File

@@ -19,7 +19,7 @@ Collider::Collider(const SpawnParams& params)
, _shape(nullptr)
, _staticActor(nullptr)
, _cachedScale(1.0f)
, _contactOffset(10.0f)
, _contactOffset(2.0f)
{
Material.Changed.Bind<Collider, &Collider::OnMaterialChanged>(this);
}

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