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

# Conflicts:
#	Source/Engine/Content/Storage/FlaxStorage.cpp
#	Source/Engine/Renderer/GBufferPass.cpp
This commit is contained in:
Wojtek Figat
2024-05-15 23:49:05 +02:00
54 changed files with 902 additions and 360 deletions

View File

@@ -467,26 +467,36 @@ namespace FlaxEditor.CustomEditors.Dedicated
// Setup transform
if (Presenter is LayoutElementsContainer l)
{
for (int i = 0; i < l.Children.Count; i++)
{
if (l.Children[i] is GroupElement g && g.Panel.HeaderText.Equals("Transform", StringComparison.Ordinal))
{
l.Children.Remove(g);
l.ContainerControl.Children.Remove(g.Panel);
break;
}
}
var transformGroup = l.Group("Transform");
VerticalPanelElement mainHor = VerticalPanelWithoutMargin(transformGroup);
CreateTransformElements(mainHor, ValuesTypes);
ScriptMemberInfo scaleInfo = ValuesTypes[0].GetProperty("Scale");
ItemInfo scaleItem = new ItemInfo(scaleInfo);
transformGroup.Property("Scale", scaleItem.GetValues(Values));
ScriptMemberInfo pivotInfo = ValuesTypes[0].GetProperty("Pivot");
ItemInfo pivotItem = new ItemInfo(pivotInfo);
transformGroup.Property("Pivot", pivotItem.GetValues(Values));
ScriptMemberInfo shearInfo = ValuesTypes[0].GetProperty("Shear");
ItemInfo shearItem = new ItemInfo(shearInfo);
transformGroup.Property("Shear", shearItem.GetValues(Values));
ScriptMemberInfo rotationInfo = ValuesTypes[0].GetProperty("Rotation");
ItemInfo rotationItem = new ItemInfo(rotationInfo);
transformGroup.Property("Rotation", rotationItem.GetValues(Values));
// Get position of general tab
for (int i = 0; i < l.Children.Count; i++)
{

View File

@@ -57,17 +57,18 @@ namespace FlaxEditor.CustomEditors.Editors
menu.ItemsContainer.RemoveChildren();
menu.AddButton("Copy", linkedEditor.Copy);
var paste = menu.AddButton("Paste", linkedEditor.Paste);
paste.Enabled = linkedEditor.CanPaste;
var b = menu.AddButton("Paste", linkedEditor.Paste);
b.Enabled = linkedEditor.CanPaste && !Editor._readOnly;
menu.AddSeparator();
var moveUpButton = menu.AddButton("Move up", OnMoveUpClicked);
moveUpButton.Enabled = Index > 0;
b = menu.AddButton("Move up", OnMoveUpClicked);
b.Enabled = Index > 0 && !Editor._readOnly;
var moveDownButton = menu.AddButton("Move down", OnMoveDownClicked);
moveDownButton.Enabled = Index + 1 < Editor.Count;
menu.AddButton("Remove", OnRemoveClicked);
b = menu.AddButton("Move down", OnMoveDownClicked);
b.Enabled = Index + 1 < Editor.Count && !Editor._readOnly;
b = menu.AddButton("Remove", OnRemoveClicked);
b.Enabled = !Editor._readOnly;
}
private void OnMoveUpClicked()
@@ -177,6 +178,7 @@ namespace FlaxEditor.CustomEditors.Editors
private IntValueBox _sizeBox;
private Color _background;
private int _elementsCount, _minCount, _maxCount;
private bool _readOnly;
private bool _canResize;
private bool _canReorderItems;
private CollectionAttribute.DisplayType _displayType;
@@ -209,6 +211,7 @@ namespace FlaxEditor.CustomEditors.Editors
return;
var size = Count;
_readOnly = false;
_canResize = true;
_canReorderItems = true;
_minCount = 0;
@@ -225,6 +228,7 @@ namespace FlaxEditor.CustomEditors.Editors
if (collection != null)
{
_canResize = !collection.ReadOnly;
_readOnly = collection.ReadOnly;
_minCount = collection.MinCount;
_maxCount = collection.MaxCount;
_canReorderItems = collection.CanReorderItems;
@@ -235,6 +239,12 @@ namespace FlaxEditor.CustomEditors.Editors
spacing = collection.Spacing;
_displayType = collection.Display;
}
if (attributes != null && attributes.Any(x => x is ReadOnlyAttribute))
{
_readOnly = true;
_canResize = false;
_canReorderItems = false;
}
if (_maxCount == 0)
_maxCount = ushort.MaxValue;
_canResize &= _minCount < _maxCount;
@@ -243,8 +253,7 @@ namespace FlaxEditor.CustomEditors.Editors
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.
// 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)
{
@@ -333,6 +342,8 @@ namespace FlaxEditor.CustomEditors.Editors
var property = panel.AddPropertyItem(itemLabel);
var itemLayout = (LayoutElementsContainer)property;
itemLabel.LinkedEditor = itemLayout.Object(new ListValueContainer(elementType, i, Values, attributes), overrideEditor);
if (_readOnly && itemLayout.Children.Count > 0)
GenericEditor.OnReadOnlyProperty(itemLayout);
}
else if (_displayType == CollectionAttribute.DisplayType.Header || (_displayType == CollectionAttribute.DisplayType.Default && !single))
{
@@ -340,13 +351,15 @@ namespace FlaxEditor.CustomEditors.Editors
cdp.CustomControl.Setup(this, i, _canReorderItems);
var itemLayout = cdp.VerticalPanel();
cdp.CustomControl.LinkedEditor = itemLayout.Object(new ListValueContainer(elementType, i, Values, attributes), overrideEditor);
if (_readOnly && itemLayout.Children.Count > 0)
GenericEditor.OnReadOnlyProperty(itemLayout);
}
}
}
_elementsCount = size;
// Add/Remove buttons
if (_canResize)
if (_canResize && !_readOnly)
{
var panel = dragArea.HorizontalPanel();
panel.Panel.Size = new Float2(0, 20);

View File

@@ -131,7 +131,7 @@ namespace FlaxEditor.CustomEditors.Editors
/// <inheritdoc />
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
{
if (button == MouseButton.Left)
if (button == MouseButton.Left && _editor._canEditKeys)
{
OnEditClicked(null);
return true;
@@ -197,6 +197,11 @@ namespace FlaxEditor.CustomEditors.Editors
spacing = collection.Spacing;
_displayType = collection.Display;
}
if (attributes != null && attributes.Any(x => x is ReadOnlyAttribute))
{
_readOnly = true;
_canEditKeys = false;
}
// Size
if (layout.ContainerControl is DropPanel dropPanel)
@@ -239,14 +244,6 @@ namespace FlaxEditor.CustomEditors.Editors
var keysEnumerable = ((IDictionary)Values[0]).Keys.OfType<object>();
var keys = keysEnumerable as object[] ?? keysEnumerable.ToArray();
var valuesType = new ScriptType(valueType);
bool single = valuesType.IsPrimitive ||
valuesType.Equals(new ScriptType(typeof(string))) ||
valuesType.IsEnum ||
(valuesType.GetFields().Length == 1 && valuesType.GetProperties().Length == 0) ||
(valuesType.GetProperties().Length == 1 && valuesType.GetFields().Length == 0) ||
valuesType.Equals(new ScriptType(typeof(JsonAsset))) ||
valuesType.Equals(new ScriptType(typeof(SettingsBase)));
// Use separate layout cells for each collection items to improve layout updates for them in separation
var useSharedLayout = valueType.IsPrimitive || valueType.IsEnum;
@@ -263,6 +260,8 @@ namespace FlaxEditor.CustomEditors.Editors
var property = panel.AddPropertyItem(new DictionaryItemLabel(this, key));
var itemLayout = useSharedLayout ? (LayoutElementsContainer)property : property.VerticalPanel();
itemLayout.Object(new DictionaryValueContainer(valuesType, key, Values), overrideEditor);
if (_readOnly && itemLayout.Children.Count > 0)
GenericEditor.OnReadOnlyProperty(itemLayout);
}
}
_elementsCount = size;

View File

@@ -581,6 +581,43 @@ namespace FlaxEditor.CustomEditors.Editors
return layout;
}
internal static void OnReadOnlyProperty(LayoutElementsContainer itemLayout, int labelIndex = -1)
{
PropertiesListElement list = null;
int firstChildControlIndex = 0;
bool disableSingle = true;
var control = itemLayout.Children[itemLayout.Children.Count - 1];
if (control is GroupElement group && group.Children.Count > 0)
{
list = group.Children[0] as PropertiesListElement;
disableSingle = false; // Disable all nested editors
}
else if (control is PropertiesListElement list1 && labelIndex != -1)
{
list = list1;
firstChildControlIndex = list.Labels[labelIndex].FirstChildControlIndex;
}
else if (control?.Control != null)
{
control.Control.Enabled = false;
}
if (list != null)
{
// Disable controls added to the editor
var count = list.Properties.Children.Count;
for (int j = firstChildControlIndex; j < count; j++)
{
var child = list.Properties.Children[j];
if (disableSingle && child is PropertyNameLabel)
break;
if (child != null)
child.Enabled = false;
}
}
}
/// <summary>
/// Evaluate the <see cref="VisibleIfAttribute"/> cache for a given property item.
/// </summary>
@@ -660,35 +697,7 @@ namespace FlaxEditor.CustomEditors.Editors
if (item.IsReadOnly && itemLayout.Children.Count > 0)
{
PropertiesListElement list = null;
int firstChildControlIndex = 0;
bool disableSingle = true;
var control = itemLayout.Children[itemLayout.Children.Count - 1];
if (control is GroupElement group && group.Children.Count > 0)
{
list = group.Children[0] as PropertiesListElement;
disableSingle = false; // Disable all nested editors
}
else if (control is PropertiesListElement list1)
{
list = list1;
firstChildControlIndex = list.Labels[labelIndex].FirstChildControlIndex;
}
if (list != null)
{
// Disable controls added to the editor
var count = list.Properties.Children.Count;
for (int j = firstChildControlIndex; j < count; j++)
{
var child = list.Properties.Children[j];
if (disableSingle && child is PropertyNameLabel)
break;
if (child != null)
child.Enabled = false;
}
}
OnReadOnlyProperty(itemLayout, labelIndex);
}
EvaluateVisibleIf(itemLayout, item, labelIndex);

View File

@@ -371,9 +371,25 @@ namespace FlaxEditor.GUI.Dialogs
Render2D.DrawText(style.FontMedium, "Hex", hex, textColor, TextAlignment.Near, TextAlignment.Center);
// Color difference
var newRect = new Rectangle(_cOK.X, _cHex.Bottom + PickerMargin, _cCancel.Right - _cOK.Left, 0);
newRect.Size.Y = _cValue.Bottom - newRect.Y;
Render2D.FillRectangle(newRect, _value * _value.A);
var newRect = new Rectangle(_cOK.X - 3, _cHex.Bottom + PickerMargin, 130, 0);
newRect.Size.Y = 50;
Render2D.FillRectangle(newRect, Color.White);
var smallRectSize = 10;
var numHor = Mathf.FloorToInt(newRect.Width / smallRectSize);
var numVer = Mathf.FloorToInt(newRect.Height / smallRectSize);
// Draw checkerboard for background of color to help with transparency
for (int i = 0; i < numHor; i++)
{
for (int j = 0; j < numVer; j++)
{
if ((i + j) % 2 == 0 )
{
var rect = new Rectangle(newRect.X + smallRectSize * i, newRect.Y + smallRectSize * j, new Float2(smallRectSize));
Render2D.FillRectangle(rect, Color.Gray);
}
}
}
Render2D.FillRectangle(newRect, _value);
}
/// <inheritdoc />

View File

@@ -418,9 +418,19 @@ namespace FlaxEditor.GUI.Tabs
{
// If scroll bar is visible it covers part of the tab header so include this in tab size to improve usability
if (_orientation == Orientation.Horizontal && TabsPanel.HScrollBar.Visible)
{
tabsSize.Y += TabsPanel.HScrollBar.Height;
var style = Style.Current;
TabsPanel.HScrollBar.TrackColor = style.Background;
TabsPanel.HScrollBar.ThumbColor = style.ForegroundGrey;
}
else if (_orientation == Orientation.Vertical && TabsPanel.VScrollBar.Visible)
{
tabsSize.X += TabsPanel.VScrollBar.Width;
var style = Style.Current;
TabsPanel.VScrollBar.TrackColor = style.Background;
TabsPanel.VScrollBar.ThumbColor = style.ForegroundGrey;
}
}
// Fit the tabs panel

View File

@@ -698,6 +698,38 @@ namespace FlaxEditor.GUI.Tree
}
}
// Show tree guide lines
if (Editor.Instance.Options.Options.Interface.ShowTreeLines)
{
TreeNode parentNode = Parent as TreeNode;
bool thisNodeIsLast = false;
while (parentNode != null && parentNode != ParentTree.Children[0])
{
float bottomOffset = 0;
float topOffset = 0;
if (Parent == parentNode && this == Parent.Children[0])
topOffset = 2;
if (thisNodeIsLast && parentNode.Children.Count == 1)
bottomOffset = topOffset != 0 ? 4 : 2;
if (Parent == parentNode && this == Parent.Children[Parent.Children.Count - 1] && !_opened)
{
thisNodeIsLast = true;
bottomOffset = topOffset != 0 ? 4 : 2;
}
float leftOffset = 9;
// Adjust offset for icon image
if (_iconCollaped.IsValid)
leftOffset += 18;
var lineRect1 = new Rectangle(parentNode.TextRect.Left - leftOffset, parentNode.HeaderRect.Top + topOffset, 1, parentNode.HeaderRect.Height - bottomOffset);
Render2D.FillRectangle(lineRect1, isSelected ? style.ForegroundGrey : style.LightBackground);
parentNode = parentNode.Parent as TreeNode;
}
}
// Base
if (_opened)
{
@@ -729,7 +761,7 @@ namespace FlaxEditor.GUI.Tree
// Try to estimate the rough location of the first node, assuming the node height is constant
var firstChildGlobalRect = GetChildGlobalRectangle(children[0], ref globalTransform);
var firstVisibleChild = Math.Clamp((int)Math.Floor((globalClipping.Y - firstChildGlobalRect.Top) / firstChildGlobalRect.Height) + 1, 0, children.Count - 1);
var firstVisibleChild = Math.Clamp((int)Math.Floor((globalClipping.Y - firstChildGlobalRect.Top) / _headerHeight) + 1, 0, children.Count - 1);
if (GetChildGlobalRectangle(children[firstVisibleChild], ref globalTransform).Top > globalClipping.Top || !children[firstVisibleChild].Visible)
{
// Estimate overshoot, either it's partially visible or hidden in the tree

View File

@@ -499,6 +499,15 @@ namespace FlaxEditor
bool drawAnySelectedControl = false;
var transformGizmo = TransformGizmo;
var mousePos = PointFromWindow(RootWindow.MousePosition);
if (EnableSelecting && !_mouseMovesControl && !_mouseMovesWidget && IsMouseOver)
{
// Highlight control under mouse for easier selecting (except if already selected)
if (RayCastControl(ref mousePos, out var hitControl) &&
(transformGizmo == null || !transformGizmo.Selection.Any(x => x.EditableObject is UIControl controlActor && controlActor.Control == hitControl)))
{
DrawControl(null, hitControl, false, ref mousePos, ref drawAnySelectedControl);
}
}
if (transformGizmo != null)
{
// Selected UI controls outline
@@ -511,15 +520,6 @@ namespace FlaxEditor
}
}
}
if (EnableSelecting && !_mouseMovesControl && !_mouseMovesWidget && IsMouseOver)
{
// Highlight control under mouse for easier selecting (except if already selected)
if (RayCastControl(ref mousePos, out var hitControl) &&
(transformGizmo == null || !transformGizmo.Selection.Any(x => x.EditableObject is UIControl controlActor && controlActor.Control == hitControl)))
{
DrawControl(null, hitControl, false, ref mousePos, ref drawAnySelectedControl);
}
}
if (drawAnySelectedControl)
Render2D.PopTransform();
@@ -617,40 +617,39 @@ namespace FlaxEditor
// Draw sizing widgets
if (_widgets == null)
_widgets = new List<Widget>();
var widgetSize = 8.0f;
var widgetSize = 10.0f;
var viewScale = ViewScale;
if (viewScale < 0.7f)
widgetSize *= viewScale;
var controlSize = control.Size.Absolute.MinValue / 50.0f;
if (controlSize < 1.0f)
widgetSize *= Mathf.Clamp(controlSize + 0.1f, 0.1f, 1.0f);
var cornerSize = new Float2(widgetSize);
DrawControlWidget(uiControl, ref ul, ref mousePos, ref cornerSize, new Float2(-1, -1), CursorType.SizeNWSE);
DrawControlWidget(uiControl, ref ur, ref mousePos, ref cornerSize, new Float2(1, -1), CursorType.SizeNESW);
DrawControlWidget(uiControl, ref bl, ref mousePos, ref cornerSize, new Float2(-1, 1), CursorType.SizeNESW);
DrawControlWidget(uiControl, ref br, ref mousePos, ref cornerSize, new Float2(1, 1), CursorType.SizeNWSE);
var edgeSizeV = new Float2(widgetSize * 2, widgetSize);
var edgeSizeH = new Float2(edgeSizeV.Y, edgeSizeV.X);
var widgetHandleSize = new Float2(widgetSize);
DrawControlWidget(uiControl, ref ul, ref mousePos, ref widgetHandleSize, viewScale, new Float2(-1, -1), CursorType.SizeNWSE);
DrawControlWidget(uiControl, ref ur, ref mousePos, ref widgetHandleSize, viewScale, new Float2(1, -1), CursorType.SizeNESW);
DrawControlWidget(uiControl, ref bl, ref mousePos, ref widgetHandleSize, viewScale, new Float2(-1, 1), CursorType.SizeNESW);
DrawControlWidget(uiControl, ref br, ref mousePos, ref widgetHandleSize, viewScale, new Float2(1, 1), CursorType.SizeNWSE);
Float2.Lerp(ref ul, ref bl, 0.5f, out var el);
Float2.Lerp(ref ur, ref br, 0.5f, out var er);
Float2.Lerp(ref ul, ref ur, 0.5f, out var eu);
Float2.Lerp(ref bl, ref br, 0.5f, out var eb);
DrawControlWidget(uiControl, ref el, ref mousePos, ref edgeSizeH, new Float2(-1, 0), CursorType.SizeWE);
DrawControlWidget(uiControl, ref er, ref mousePos, ref edgeSizeH, new Float2(1, 0), CursorType.SizeWE);
DrawControlWidget(uiControl, ref eu, ref mousePos, ref edgeSizeV, new Float2(0, -1), CursorType.SizeNS);
DrawControlWidget(uiControl, ref eb, ref mousePos, ref edgeSizeV, new Float2(0, 1), CursorType.SizeNS);
DrawControlWidget(uiControl, ref el, ref mousePos, ref widgetHandleSize, viewScale, new Float2(-1, 0), CursorType.SizeWE);
DrawControlWidget(uiControl, ref er, ref mousePos, ref widgetHandleSize, viewScale, new Float2(1, 0), CursorType.SizeWE);
DrawControlWidget(uiControl, ref eu, ref mousePos, ref widgetHandleSize, viewScale, new Float2(0, -1), CursorType.SizeNS);
DrawControlWidget(uiControl, ref eb, ref mousePos, ref widgetHandleSize, viewScale, new Float2(0, 1), CursorType.SizeNS);
// TODO: draw anchors
}
}
private void DrawControlWidget(UIControl uiControl, ref Float2 pos, ref Float2 mousePos, ref Float2 size, Float2 resizeAxis, CursorType cursor)
private void DrawControlWidget(UIControl uiControl, ref Float2 pos, ref Float2 mousePos, ref Float2 size,float scale, Float2 resizeAxis, CursorType cursor)
{
var style = Style.Current;
var rect = new Rectangle(pos - size * 0.5f, size);
var rect = new Rectangle((pos + resizeAxis * 10 * scale) - size * 0.5f, size);
if (rect.Contains(ref mousePos))
{
Render2D.FillRectangle(rect, style.Foreground);
Render2D.DrawRectangle(rect, style.SelectionBorder);
}
else
{

View File

@@ -210,6 +210,13 @@ namespace FlaxEditor.Options
[EditorDisplay("Interface"), EditorOrder(310)]
public bool SeparateValueAndUnit { get; set; }
/// <summary>
/// Gets or sets the option to put a space between numbers and units for unit formatting.
/// </summary>
[DefaultValue(true)]
[EditorDisplay("Interface"), EditorOrder(320)]
public bool ShowTreeLines { get; set; } = true;
/// <summary>
/// Gets or sets the timestamps prefix mode for output log messages.
/// </summary>

View File

@@ -15,6 +15,7 @@ using FlaxEditor.Windows.Assets;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
using Object = FlaxEngine.Object;
namespace FlaxEditor.SceneGraph.GUI
{
@@ -625,6 +626,7 @@ namespace FlaxEditor.SceneGraph.GUI
{
var item = _dragScriptItems.Objects[i];
var actorType = Editor.Instance.CodeEditing.Actors.Get(item);
var scriptType = Editor.Instance.CodeEditing.Scripts.Get(item);
if (actorType != ScriptType.Null)
{
var actor = actorType.CreateInstance() as Actor;
@@ -639,6 +641,18 @@ namespace FlaxEditor.SceneGraph.GUI
ActorNode.Root.Spawn(actor, spawnParent);
actor.OrderInParent = newOrder;
}
else if (scriptType != ScriptType.Null)
{
if (DragOverMode == DragItemPositioning.Above || DragOverMode == DragItemPositioning.Below)
{
Editor.LogWarning("Failed to spawn script of type " + actorType.TypeName);
continue;
}
IUndoAction action = new AddRemoveScript(true, newParent, scriptType);
Select();
ActorNode.Root.Undo?.AddAction(action);
action.Do();
}
}
result = DragDropEffect.Move;
}
@@ -699,9 +713,9 @@ namespace FlaxEditor.SceneGraph.GUI
return Editor.Instance.CodeEditing.Controls.Get().Contains(controlType);
}
private static bool ValidateDragScriptItem(ScriptItem script)
private bool ValidateDragScriptItem(ScriptItem script)
{
return Editor.Instance.CodeEditing.Actors.Get(script) != ScriptType.Null;
return Editor.Instance.CodeEditing.Actors.Get(script) != ScriptType.Null || Editor.Instance.CodeEditing.Scripts.Get(script) != ScriptType.Null;
}
/// <inheritdoc />

View File

@@ -351,6 +351,8 @@ namespace FlaxEditor.Viewport
private void OnCollectDrawCalls(ref RenderContext renderContext)
{
if (renderContext.View.Pass == DrawPass.Depth)
return;
DragHandlers.CollectDrawCalls(_debugDrawData, ref renderContext);
if (ShowNavigation)
Editor.Internal_DrawNavMesh();
@@ -620,12 +622,12 @@ namespace FlaxEditor.Viewport
private static bool ValidateDragActorType(ScriptType actorType)
{
return Level.IsAnySceneLoaded;
return Level.IsAnySceneLoaded && Editor.Instance.CodeEditing.Actors.Get().Contains(actorType);
}
private static bool ValidateDragScriptItem(ScriptItem script)
{
return Editor.Instance.CodeEditing.Actors.Get(script) != ScriptType.Null;
return Level.IsAnySceneLoaded && Editor.Instance.CodeEditing.Actors.Get(script) != ScriptType.Null;
}
/// <inheritdoc />

View File

@@ -98,7 +98,6 @@ namespace FlaxEditor.Viewport
ShowDebugDraw = true;
ShowEditorPrimitives = true;
Gizmos = new GizmosCollection(this);
var inputOptions = window.Editor.Options.Options.Input;
// Prepare rendering task
Task.ActorsSource = ActorsSources.CustomActors;
@@ -219,6 +218,8 @@ namespace FlaxEditor.Viewport
private void OnCollectDrawCalls(ref RenderContext renderContext)
{
if (renderContext.View.Pass == DrawPass.Depth)
return;
DragHandlers.CollectDrawCalls(_debugDrawData, ref renderContext);
_debugDrawData.OnDraw(ref renderContext);
}
@@ -498,7 +499,7 @@ namespace FlaxEditor.Viewport
private static bool ValidateDragActorType(ScriptType actorType)
{
return true;
return Editor.Instance.CodeEditing.Actors.Get().Contains(actorType);
}
private static bool ValidateDragScriptItem(ScriptItem script)

View File

@@ -102,8 +102,8 @@ void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float
Swap(eventTimeMin, eventTimeMax);
}
}
const float eventTime = animPos / static_cast<float>(anim->Data.FramesPerSecond);
const float eventDeltaTime = (animPos - animPrevPos) / static_cast<float>(anim->Data.FramesPerSecond);
const float eventTime = (float)(animPos / anim->Data.FramesPerSecond);
const float eventDeltaTime = (float)((animPos - animPrevPos) / anim->Data.FramesPerSecond);
for (const auto& track : anim->Events)
{
for (const auto& k : track.Second.GetKeyframes())
@@ -211,7 +211,7 @@ float GetAnimSamplePos(float length, Animation* anim, float pos, float speed)
}
if (animPos < 0)
animPos = animLength + animPos;
animPos *= static_cast<float>(anim->Data.FramesPerSecond);
animPos = (float)(animPos * anim->Data.FramesPerSecond);
return animPos;
}
@@ -265,7 +265,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
float nestedAnimPrevPos = animPrevPos - nestedAnim.Time;
const float nestedAnimLength = nestedAnim.Anim->GetLength();
const float nestedAnimSpeed = nestedAnim.Speed * speed;
const float frameRateMatchScale = nestedAnimSpeed / (float)anim->Data.FramesPerSecond;
const float frameRateMatchScale = (float)(nestedAnimSpeed / anim->Data.FramesPerSecond);
nestedAnimPos = nestedAnimPos * frameRateMatchScale;
nestedAnimPrevPos = nestedAnimPrevPos * frameRateMatchScale;
GetAnimSamplePos(nestedAnim.Loop, nestedAnimLength, nestedAnim.StartTime, nestedAnimPrevPos, nestedAnimPos, nestedAnimPos, nestedAnimPrevPos);
@@ -363,8 +363,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
// Check if animation looped
if (animPos < animPrevPos)
{
const float endPos = anim->GetLength() * static_cast<float>(anim->Data.FramesPerSecond);
const float timeToEnd = endPos - animPrevPos;
const float endPos = (float)(anim->GetLength() * anim->Data.FramesPerSecond);
Transform rootBegin = refPose;
rootChannel.Evaluate(0, &rootBegin, false);
@@ -372,16 +371,13 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
Transform rootEnd = refPose;
rootChannel.Evaluate(endPos, &rootEnd, false);
//rootChannel.Evaluate(animPos - timeToEnd, &rootNow, true);
// Complex motion calculation to preserve the looped movement
// (end - before + now - begin)
// It sums the motion since the last update to anim end and since the start to now
if (motionPosition)
srcNode.Translation = (rootEnd.Translation - rootBefore.Translation + rootNode.Translation - rootBegin.Translation) * motionPositionMask;
if (motionRotation)
srcNode.Orientation = rootEnd.Orientation * rootBefore.Orientation.Conjugated() * (rootNode.Orientation * rootBegin.Orientation.Conjugated());
//srcNode.Orientation = Quaternion::Identity;
srcNode.Orientation = (rootBefore.Orientation.Conjugated() * rootEnd.Orientation) * (rootBegin.Orientation.Conjugated() * rootNode.Orientation);
}
else
{
@@ -1165,21 +1161,21 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
const auto nodes = node->GetNodes(this);
const auto basePoseNodes = static_cast<AnimGraphImpulse*>(valueA.AsPointer);
const auto blendPoseNodes = static_cast<AnimGraphImpulse*>(valueB.AsPointer);
const auto& refrenceNodes = _graph.BaseModel.Get()->GetNodes();
Transform t, basePoseTransform, blendPoseTransform, refrenceTransform;
const auto& refNodes = _graph.BaseModel.Get()->GetNodes();
Transform t, basePoseTransform, blendPoseTransform, refTransform;
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
{
basePoseTransform = basePoseNodes->Nodes[i];
blendPoseTransform = blendPoseNodes->Nodes[i];
refrenceTransform = refrenceNodes[i].LocalTransform;
refTransform = refNodes[i].LocalTransform;
// base + (blend - refrence) = transform
t.Translation = basePoseTransform.Translation + (blendPoseTransform.Translation - refrenceTransform.Translation);
auto diff = Quaternion::Invert(refrenceTransform.Orientation) * blendPoseTransform.Orientation;
// base + (blend - reference)
t.Translation = basePoseTransform.Translation + (blendPoseTransform.Translation - refTransform.Translation);
auto diff = Quaternion::Invert(refTransform.Orientation) * blendPoseTransform.Orientation;
t.Orientation = basePoseTransform.Orientation * diff;
t.Scale = basePoseTransform.Scale + (blendPoseTransform.Scale - refrenceTransform.Scale);
t.Scale = basePoseTransform.Scale + (blendPoseTransform.Scale - refTransform.Scale);
//lerp base and transform
// Lerp base and transform
Transform::Lerp(basePoseTransform, t, alpha, nodes->Nodes[i]);
}
Transform::Lerp(basePoseNodes->RootMotion, basePoseNodes->RootMotion + blendPoseNodes->RootMotion, alpha, nodes->RootMotion);

View File

@@ -886,7 +886,8 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value&
instance = eatBox(node, box->FirstConnection());
else
instance.SetObject(object);
if (!instance.AsObject)
ScriptingObject* instanceObj = (ScriptingObject*)instance;
if (!instanceObj)
{
LOG(Error, "Cannot bind event to null object.");
PrintStack(LogType::Error);
@@ -928,13 +929,13 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value&
}
eventBinding->BindedMethods.Add(method);
if (eventBinding->BindedMethods.Count() == 1)
(*eventBinder)(instance.AsObject, object, true);
(*eventBinder)(instanceObj, object, true);
}
else if (eventBinding)
{
// Unbind from the event
if (eventBinding->BindedMethods.Count() == 1)
(*eventBinder)(instance.AsObject, object, false);
(*eventBinder)(instanceObj, object, false);
eventBinding->BindedMethods.Remove(method);
}
}

View File

@@ -218,10 +218,7 @@ const Char* TypeId2TypeName(const uint32 typeId)
}
FlaxStorage::FlaxStorage(const StringView& path)
: _refCount(0)
, _chunksLock(0)
, _version(0)
, _path(path)
: _path(path)
{
}
@@ -242,6 +239,7 @@ FlaxStorage::~FlaxStorage()
if (stream)
Delete(stream);
}
Platform::AtomicStore(&_files, 0);
#endif
}
@@ -1327,6 +1325,7 @@ FileReadStream* FlaxStorage::OpenFile()
LOG(Error, "Cannot open Flax Storage file \'{0}\'.", _path);
return nullptr;
}
Platform::InterlockedIncrement(&_files);
// Create file reading stream
stream = New<FileReadStream>(file);
@@ -1336,11 +1335,13 @@ FileReadStream* FlaxStorage::OpenFile()
bool FlaxStorage::CloseFileHandles()
{
// Early out if no handles are opened
Array<FileReadStream*> streams;
_file.GetValues(streams);
if (streams.IsEmpty() && Platform::AtomicRead(&_chunksLock) == 0)
if (Platform::AtomicRead(&_chunksLock) == 0 && Platform::AtomicRead(&_files) == 0)
{
Array<FileReadStream*, InlinedAllocation<8>> streams;
_file.GetValues(streams);
ASSERT(streams.Count() == 0);
return false;
}
PROFILE_CPU();
// Note: this is usually called by the content manager when this file is not used or on exit
@@ -1372,7 +1373,7 @@ bool FlaxStorage::CloseFileHandles()
return true; // Failed, someone is still accessing the file
// Close file handles (from all threads)
streams.Clear();
Array<FileReadStream*, InlinedAllocation<8>> streams;
_file.GetValues(streams);
for (FileReadStream* stream : streams)
{
@@ -1380,6 +1381,7 @@ bool FlaxStorage::CloseFileHandles()
Delete(stream);
}
_file.Clear();
Platform::AtomicStore(&_files, 0);
return false;
}

View File

@@ -87,8 +87,9 @@ public:
protected:
// State
int64 _refCount;
int64 _chunksLock;
int64 _refCount = 0;
int64 _chunksLock = 0;
int64 _files = 0;
double _lastRefLostTime;
CriticalSection _loadLocker;
@@ -97,7 +98,7 @@ protected:
Array<FlaxChunk*> _chunks;
// Metadata
uint32 _version;
uint32 _version = 0;
String _path;
protected:

View File

@@ -404,7 +404,7 @@ void Mesh::Draw(const RenderContext& renderContext, MaterialBase* material, cons
drawCall.Material = material;
drawCall.World = world;
drawCall.ObjectPosition = drawCall.World.GetTranslation();
drawCall.ObjectRadius = _sphere.Radius * drawCall.World.GetScaleVector().GetAbsolute().MaxValue();
drawCall.ObjectRadius = (float)_sphere.Radius * drawCall.World.GetScaleVector().GetAbsolute().MaxValue();
drawCall.Surface.GeometrySize = _box.GetSize();
drawCall.Surface.PrevWorld = world;
drawCall.Surface.Lightmap = nullptr;
@@ -472,7 +472,7 @@ void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float
drawCall.Material = material;
drawCall.World = *info.World;
drawCall.ObjectPosition = drawCall.World.GetTranslation();
drawCall.ObjectRadius = info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition?
drawCall.ObjectRadius = (float)info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition?
drawCall.Surface.GeometrySize = _box.GetSize();
drawCall.Surface.PrevWorld = info.DrawState->PrevWorld;
drawCall.Surface.Lightmap = (info.Flags & StaticFlags::Lightmap) != StaticFlags::None ? info.Lightmap : nullptr;
@@ -534,7 +534,7 @@ void Mesh::Draw(const RenderContextBatch& renderContextBatch, const DrawInfo& in
drawCall.Material = material;
drawCall.World = *info.World;
drawCall.ObjectPosition = drawCall.World.GetTranslation();
drawCall.ObjectRadius = info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition?
drawCall.ObjectRadius = (float)info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition?
drawCall.Surface.GeometrySize = _box.GetSize();
drawCall.Surface.PrevWorld = info.DrawState->PrevWorld;
drawCall.Surface.Lightmap = (info.Flags & StaticFlags::Lightmap) != StaticFlags::None ? info.Lightmap : nullptr;

View File

@@ -246,7 +246,7 @@ void SkinnedMesh::Draw(const RenderContext& renderContext, const DrawInfo& info,
drawCall.Material = material;
drawCall.World = *info.World;
drawCall.ObjectPosition = drawCall.World.GetTranslation();
drawCall.ObjectRadius = info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition?
drawCall.ObjectRadius = (float)info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition?
drawCall.Surface.GeometrySize = _box.GetSize();
drawCall.Surface.PrevWorld = info.DrawState->PrevWorld;
drawCall.Surface.Lightmap = nullptr;
@@ -289,7 +289,7 @@ void SkinnedMesh::Draw(const RenderContextBatch& renderContextBatch, const DrawI
drawCall.Material = material;
drawCall.World = *info.World;
drawCall.ObjectPosition = drawCall.World.GetTranslation();
drawCall.ObjectRadius = info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition?
drawCall.ObjectRadius = (float)info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition?
drawCall.Surface.GeometrySize = _box.GetSize();
drawCall.Surface.PrevWorld = info.DrawState->PrevWorld;
drawCall.Surface.Lightmap = nullptr;

View File

@@ -4,9 +4,9 @@
#include "Engine/Core/Math/Vector3.h"
#include "Engine/Core/Math/Vector4.h"
#include "Engine/Core/ISerializable.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"
@@ -526,13 +526,13 @@ API_STRUCT() struct FLAXENGINE_API ToneMappingSettings : ISerializable
ToneMappingSettingsOverride OverrideFlags = Override::None;
/// <summary>
/// Adjusts the white balance in relation to the temperature of the light in the scene. When the light temperature and this one match the light will appear white. When a value is used that is higher than the light in the scene it will yield a "warm" or yellow color, and, conversely, if the value is lower, it would yield a "cool" or blue color. The default value is `6500`.
/// Adjusts the white balance in relation to the temperature of the light in the scene. When the light temperature and this one match the light will appear white. When a value is used that is higher than the light in the scene it will yield a "warm" or yellow color, and, conversely, if the value is lower, it would yield a "cool" or blue color.
/// </summary>
API_FIELD(Attributes="Limit(1500, 15000), EditorOrder(0), PostProcessSetting((int)ToneMappingSettingsOverride.WhiteTemperature)")
float WhiteTemperature = 6500.0f;
/// <summary>
/// Adjusts the white balance temperature tint for the scene by adjusting the cyan and magenta color ranges. Ideally, this setting should be used once you've adjusted the white balance temperature to get accurate colors. Under some light temperatures, the colors may appear to be more yellow or blue. This can be used to balance the resulting color to look more natural. The default value is `0`.
/// Adjusts the white balance temperature tint for the scene by adjusting the cyan and magenta color ranges. Ideally, this setting should be used once you've adjusted the white balance temperature to get accurate colors. Under some light temperatures, the colors may appear to be more yellow or blue. This can be used to balance the resulting color to look more natural.
/// </summary>
API_FIELD(Attributes="Limit(-1, 1, 0.001f), EditorOrder(1), PostProcessSetting((int)ToneMappingSettingsOverride.WhiteTint)")
float WhiteTint = 0.0f;
@@ -1079,7 +1079,7 @@ API_STRUCT() struct FLAXENGINE_API CameraArtifactsSettings : ISerializable
CameraArtifactsSettingsOverride OverrideFlags = Override::None;
/// <summary>
/// Strength of the vignette effect. Value 0 hides it. The default value is 0.4.
/// Strength of the vignette effect. Value 0 hides it.
/// </summary>
API_FIELD(Attributes="Limit(0, 2, 0.001f), EditorOrder(0), PostProcessSetting((int)CameraArtifactsSettingsOverride.VignetteIntensity)")
float VignetteIntensity = 0.4f;
@@ -1091,19 +1091,19 @@ API_STRUCT() struct FLAXENGINE_API CameraArtifactsSettings : ISerializable
Float3 VignetteColor = Float3(0, 0, 0.001f);
/// <summary>
/// Controls the shape of the vignette. Values near 0 produce a rectangular shape. Higher values result in a rounder shape. The default value is 0.125.
/// Controls the shape of the vignette. Values near 0 produce a rectangular shape. Higher values result in a rounder shape.
/// </summary>
API_FIELD(Attributes="Limit(0.0001f, 2.0f, 0.001f), EditorOrder(2), PostProcessSetting((int)CameraArtifactsSettingsOverride.VignetteShapeFactor)")
float VignetteShapeFactor = 0.125f;
/// <summary>
/// Intensity of the grain filter. A value of 0 hides it. The default value is 0.005.
/// Intensity of the grain filter. A value of 0 hides it.
/// </summary>
API_FIELD(Attributes="Limit(0.0f, 2.0f, 0.005f), EditorOrder(3), PostProcessSetting((int)CameraArtifactsSettingsOverride.GrainAmount)")
float GrainAmount = 0.006f;
/// <summary>
/// Size of the grain particles. The default value is 1.6.
/// Size of the grain particles.
/// </summary>
API_FIELD(Attributes="Limit(1.0f, 3.0f, 0.01f), EditorOrder(4), PostProcessSetting((int)CameraArtifactsSettingsOverride.GrainParticleSize)")
float GrainParticleSize = 1.6f;
@@ -1731,49 +1731,49 @@ API_STRUCT() struct FLAXENGINE_API ScreenSpaceReflectionsSettings : ISerializabl
ReflectionsTraceMode TraceMode = ReflectionsTraceMode::ScreenTracing;
/// <summary>
/// The depth buffer downscale option to optimize raycast performance. Full gives better quality, but half improves performance. The default value is half.
/// The depth buffer downscale option to optimize raycast performance. Full gives better quality, but half improves performance.
/// </summary>
API_FIELD(Attributes="EditorOrder(2), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.DepthResolution)")
ResolutionMode DepthResolution = ResolutionMode::Half;
/// <summary>
/// The raycast resolution. Full gives better quality, but half improves performance. The default value is half.
/// The raycast resolution. Full gives better quality, but half improves performance.
/// </summary>
API_FIELD(Attributes="EditorOrder(3), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.RayTracePassResolution)")
ResolutionMode RayTracePassResolution = ResolutionMode::Half;
/// <summary>
/// The reflection spread parameter. This value controls source roughness effect on reflections blur. Smaller values produce wider reflections spread but also introduce more noise. Higher values provide more mirror-like reflections. Default value is 0.82.
/// The reflection spread parameter. This value controls source roughness effect on reflections blur. Smaller values produce wider reflections spread but also introduce more noise. Higher values provide more mirror-like reflections.
/// </summary>
API_FIELD(Attributes="Limit(0, 1.0f, 0.01f), EditorOrder(10), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.BRDFBias), EditorDisplay(null, \"BRDF Bias\")")
float BRDFBias = 0.82f;
/// <summary>
/// The maximum amount of roughness a material must have to reflect the scene. For example, if this value is set to 0.4, only materials with a roughness value of 0.4 or below reflect the scene. The default value is 0.45.
/// The maximum amount of roughness a material must have to reflect the scene. For example, if this value is set to 0.4, only materials with a roughness value of 0.4 or below reflect the scene.
/// </summary>
API_FIELD(Attributes="Limit(0, 1.0f, 0.01f), EditorOrder(15), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.RoughnessThreshold)")
float RoughnessThreshold = 0.45f;
/// <summary>
/// The offset of the raycast origin. Lower values produce more correct reflection placement, but produce more artifacts. We recommend values of 0.3 or lower. The default value is 0.1.
/// The offset of the raycast origin. Lower values produce more correct reflection placement, but produce more artifacts. We recommend values of 0.3 or lower.
/// </summary>
API_FIELD(Attributes="Limit(0, 10.0f, 0.01f), EditorOrder(20), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.WorldAntiSelfOcclusionBias)")
float WorldAntiSelfOcclusionBias = 0.1f;
/// <summary>
/// The raycast resolution. Full gives better quality, but half improves performance. The default value is half.
/// The raycast resolution. Full gives better quality, but half improves performance.
/// </summary>
API_FIELD(Attributes="EditorOrder(25), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.ResolvePassResolution)")
ResolutionMode ResolvePassResolution = ResolutionMode::Full;
/// <summary>
/// The number of rays used to resolve the reflection color. Higher values provide better quality but reduce effect performance. Default value is 4. Use 1 for the highest speed.
/// The number of rays used to resolve the reflection color. Higher values provide better quality but reduce effect performance. Use value of 1 for the best performance at cost of quality.
/// </summary>
API_FIELD(Attributes="Limit(1, 8), EditorOrder(26), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.ResolveSamples)")
int32 ResolveSamples = 4;
/// <summary>
/// The point at which the far edges of the reflection begin to fade. Has no effect on performance. The default value is 0.1.
/// The point at which the far edges of the reflection begin to fade. Has no effect on performance.
/// </summary>
API_FIELD(Attributes="Limit(0, 1.0f, 0.02f), EditorOrder(30), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.EdgeFadeFactor)")
float EdgeFadeFactor = 0.1f;
@@ -1803,13 +1803,13 @@ API_STRUCT() struct FLAXENGINE_API ScreenSpaceReflectionsSettings : ISerializabl
bool TemporalEffect = true;
/// <summary>
/// The intensity of the temporal effect. Lower values produce reflections faster, but more noise. The default value is 8.
/// The intensity of the temporal effect. Lower values produce reflections faster, but more noise.
/// </summary>
API_FIELD(Attributes="Limit(0, 20.0f, 0.5f), EditorOrder(55), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.TemporalScale)")
float TemporalScale = 8.0f;
/// <summary>
/// Defines how quickly reflections blend between the reflection in the current frame and the history buffer. Lower values produce reflections faster, but with more jittering. If the camera in your game doesn't move much, we recommend values closer to 1. The default value is 0.8.
/// Defines how quickly reflections blend between the reflection in the current frame and the history buffer. Lower values produce reflections faster, but with more jittering. If the camera in your game doesn't move much, we recommend values closer to 1.
/// </summary>
API_FIELD(Attributes="Limit(0.05f, 1.0f, 0.01f), EditorOrder(60), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.TemporalResponse)")
float TemporalResponse = 0.8f;
@@ -2015,7 +2015,7 @@ API_STRUCT() struct FLAXENGINE_API PostProcessSettings : ISerializable
ScreenSpaceReflectionsSettings ScreenSpaceReflections;
/// <summary>
/// The anti-aliasing effect settings.
/// The antialiasing effect settings.
/// </summary>
API_FIELD(Attributes="EditorDisplay(\"Anti Aliasing\"), EditorOrder(1100), JsonProperty(\"AA\")")
AntiAliasingSettings AntiAliasing;

View File

@@ -354,9 +354,9 @@ public:
// Applies the render origin to the transformation instance matrix.
FORCE_INLINE void GetWorldMatrix(Matrix& world) const
{
world.M41 -= Origin.X;
world.M42 -= Origin.Y;
world.M43 -= Origin.Z;
world.M41 -= (float)Origin.X;
world.M42 -= (float)Origin.Y;
world.M43 -= (float)Origin.Z;
}
};

View File

@@ -609,7 +609,10 @@ void Actor::SetIsActive(bool value)
void Actor::SetStaticFlags(StaticFlags value)
{
if (_staticFlags == value)
return;
_staticFlags = value;
OnStaticFlagsChanged();
}
void Actor::SetTransform(const Transform& value)
@@ -1229,6 +1232,14 @@ void Actor::OnOrderInParentChanged()
Level::callActorEvent(Level::ActorEventType::OnActorOrderInParentChanged, this, nullptr);
}
void Actor::OnStaticFlagsChanged()
{
}
void Actor::OnLayerChanged()
{
}
BoundingBox Actor::GetBoxWithChildren() const
{
BoundingBox result = GetBox();

View File

@@ -886,14 +886,12 @@ public:
/// Gets rotation of the actor oriented towards the specified world position with upwards direction.
/// </summary>
/// <param name="worldPos">The world position to orient towards.</param>
/// <param name="worldUp">The up direction that Constrains y axis orientation to a plane this vector lies on. This rule might be broken if forward and up direction are nearly parallel.</param>
/// <param name="worldUp">The up direction that constrains up axis orientation to a plane this vector lies on. This rule might be broken if forward and up direction are nearly parallel.</param>
API_FUNCTION() Quaternion LookingAt(const Vector3& worldPos, const Vector3& worldUp) const;
public:
/// <summary>
/// Execute custom action on actors tree.
/// Action should returns false to stop calling deeper.
/// First action argument is current actor object.
/// </summary>
/// <param name="action">Actor to call on every actor in the tree. Returns true if keep calling deeper.</param>
/// <param name="args">Custom arguments for the function</param>
@@ -903,14 +901,12 @@ public:
if (action(this, args...))
{
for (int32 i = 0; i < Children.Count(); i++)
Children[i]->TreeExecute<Params...>(action, args...);
Children.Get()[i]->TreeExecute<Params...>(action, args...);
}
}
/// <summary>
/// Execute custom action on actor children tree.
/// Action should returns false to stop calling deeper.
/// First action argument is current actor object.
/// </summary>
/// <param name="action">Actor to call on every actor in the tree. Returns true if keep calling deeper.</param>
/// <param name="args">Custom arguments for the function</param>
@@ -918,7 +914,7 @@ public:
void TreeExecuteChildren(Function<bool(Actor*, Params ...)>& action, Params ... args)
{
for (int32 i = 0; i < Children.Count(); i++)
Children[i]->TreeExecute<Params...>(action, args...);
Children.Get()[i]->TreeExecute<Params...>(action, args...);
}
public:
@@ -1016,12 +1012,15 @@ public:
/// </summary>
virtual void OnOrderInParentChanged();
/// <summary>
/// Called when actor static flag gets changed.
/// </summary>
virtual void OnStaticFlagsChanged();
/// <summary>
/// Called when layer gets changed.
/// </summary>
virtual void OnLayerChanged()
{
}
virtual void OnLayerChanged();
/// <summary>
/// Called when adding object to the game.

View File

@@ -33,7 +33,7 @@ void PostFxVolume::Collect(RenderContext& renderContext)
}
}
if (weight > ZeroTolerance)
if (weight > ZeroTolerance && renderContext.View.RenderLayersMask.HasLayer(GetLayer()))
{
const float totalSizeSqrt = (_transform.Scale * _size).LengthSquared();
renderContext.List->AddSettingsBlend((IPostFxSettingsProvider*)this, weight, _priority, totalSizeSqrt);

View File

@@ -99,7 +99,7 @@ void Skybox::ApplySky(GPUContext* context, RenderContext& renderContext, const M
DrawCall drawCall;
drawCall.World = world;
drawCall.ObjectPosition = drawCall.World.GetTranslation();
drawCall.ObjectRadius = _sphere.Radius;
drawCall.ObjectRadius = (float)_sphere.Radius;
drawCall.Surface.GeometrySize = _box.GetSize();
drawCall.WorldDeterminantSign = Math::FloatSelect(world.RotDeterminant(), 1, -1);
drawCall.PerInstanceRandom = GetPerInstanceRandom();

View File

@@ -410,7 +410,7 @@ void SplineModel::Draw(RenderContext& renderContext)
const Transform splineTransform = GetTransform();
renderContext.View.GetWorldMatrix(splineTransform, drawCall.World);
drawCall.ObjectPosition = drawCall.World.GetTranslation() + drawCall.Deformable.LocalMatrix.GetTranslation();
drawCall.ObjectRadius = _sphere.Radius; // TODO: use radius for the spline chunk rather than whole spline
drawCall.ObjectRadius = (float)_sphere.Radius; // TODO: use radius for the spline chunk rather than whole spline
const float worldDeterminantSign = drawCall.World.RotDeterminant() * drawCall.Deformable.LocalMatrix.RotDeterminant();
for (int32 segment = 0; segment < _instances.Count(); segment++)
{

View File

@@ -30,6 +30,12 @@ bool SceneAsset::IsInternalType() const
return true;
}
void SceneNavigation::Clear()
{
Volumes.Clear();
Actors.Clear();
}
BoundingBox SceneNavigation::GetNavigationBounds()
{
if (Volumes.IsEmpty())
@@ -373,6 +379,7 @@ void Scene::EndPlay()
// Improve scene cleanup performance by removing all data from scene rendering and ticking containers
Ticking.Clear();
Rendering.Clear();
Navigation.Clear();
// Base
Actor::EndPlay();

View File

@@ -23,6 +23,17 @@ public:
/// </summary>
Array<NavMesh*> Meshes;
/// <summary>
/// The list of registered navigation-relevant actors (on the scene).
/// </summary>
Array<Actor*> Actors;
public:
/// <summary>
/// Clears this instance data.
/// </summary>
void Clear();
/// <summary>
/// Gets the total navigation volumes bounds.
/// </summary>

View File

@@ -18,7 +18,7 @@ class FLAXENGINE_API SceneQuery
{
public:
/// <summary>
/// Try to find actor hit by the given ray
/// Try to find actor hit by the given ray.
/// </summary>
/// <param name="ray">Ray to test</param>
/// <returns>Hit actor or nothing</returns>
@@ -55,19 +55,16 @@ public:
public:
/// <summary>
/// Execute custom action on actors tree.
/// Action should returns false to stop calling deeper.
/// First action argument is current actor object.
/// </summary>
/// <param name="action">Actor to call on every actor in the tree. Returns true if keep calling deeper.</param>
/// <param name="args">Custom arguments for the function</param>
template<typename... Params>
static void TreeExecute(Function<bool(Actor*, Params ...)>& action, Params ... args)
static void TreeExecute(Function<bool(Actor*, Params...)>& action, Params... args)
{
#if SCENE_QUERIES_WITH_LOCK
ScopeLock lock(Level::ScenesLock);
#endif
for (int32 i = 0; i < Level::Scenes.Count(); i++)
Level::Scenes[i]->TreeExecute<Params...>(action, args...);
Level::Scenes.Get()[i]->TreeExecute<Params...>(action, args...);
}
};

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "NavLink.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Serialization/Serialization.h"
NavLink::NavLink(const SpawnParams& params)
@@ -62,6 +63,20 @@ void NavLink::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie
DESERIALIZE(BiDirectional);
}
void NavLink::OnEnable()
{
GetScene()->Navigation.Actors.Add(this);
Actor::OnEnable();
}
void NavLink::OnDisable()
{
Actor::OnDisable();
GetScene()->Navigation.Actors.Remove(this);
}
void NavLink::OnTransformChanged()
{
// Base

View File

@@ -46,6 +46,8 @@ public:
#endif
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
void OnEnable() override;
void OnDisable() override;
protected:
// [Actor]

View File

@@ -23,7 +23,6 @@
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Level/Level.h"
#include "Engine/Level/SceneQuery.h"
#include <ThirdParty/recastnavigation/Recast.h>
#include <ThirdParty/recastnavigation/DetourNavMeshBuilder.h>
#include <ThirdParty/recastnavigation/DetourNavMesh.h>
@@ -68,7 +67,7 @@ struct Modifier
NavAreaProperties* NavArea;
};
struct NavigationSceneRasterization
struct NavSceneRasterizer
{
NavMesh* NavMesh;
BoundingBox TileBoundsNavMesh;
@@ -83,7 +82,7 @@ struct NavigationSceneRasterization
Array<Modifier>* Modifiers;
const bool IsWorldToNavMeshIdentity;
NavigationSceneRasterization(::NavMesh* navMesh, const BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh, rcContext* context, rcConfig* config, rcHeightfield* heightfield, Array<OffMeshLink>* offMeshLinks, Array<Modifier>* modifiers)
NavSceneRasterizer(::NavMesh* navMesh, const BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh, rcContext* context, rcConfig* config, rcHeightfield* heightfield, Array<OffMeshLink>* offMeshLinks, Array<Modifier>* modifiers)
: TileBoundsNavMesh(tileBoundsNavMesh)
, WorldToNavMesh(worldToNavMesh)
, IsWorldToNavMeshIdentity(worldToNavMesh.IsIdentity())
@@ -103,35 +102,20 @@ struct NavigationSceneRasterization
auto& ib = IndexBuffer;
if (vb.IsEmpty() || ib.IsEmpty())
return;
PROFILE_CPU();
// Rasterize triangles
const Float3* vbData = vb.Get();
const int32* ibData = ib.Get();
Float3 v0, v1, v2;
if (IsWorldToNavMeshIdentity)
{
// Faster path
for (int32 i0 = 0; i0 < ib.Count();)
{
auto v0 = vb[ib[i0++]];
auto v1 = vb[ib[i0++]];
auto v2 = vb[ib[i0++]];
#if NAV_MESH_BUILD_DEBUG_DRAW_GEOMETRY
DEBUG_DRAW_TRIANGLE(v0, v1, v2, Color::Orange.AlphaMultiplied(0.3f), 1.0f, true);
#endif
auto n = Float3::Cross(v0 - v1, v0 - v2);
n.Normalize();
const char area = n.Y > WalkableThreshold ? RC_WALKABLE_AREA : 0;
rcRasterizeTriangle(Context, &v0.X, &v1.X, &v2.X, area, *Heightfield);
}
}
else
{
// Transform vertices from world space into the navmesh space
const Matrix worldToNavMesh = WorldToNavMesh;
for (int32 i0 = 0; i0 < ib.Count();)
{
auto v0 = Float3::Transform(vb[ib[i0++]], worldToNavMesh);
auto v1 = Float3::Transform(vb[ib[i0++]], worldToNavMesh);
auto v2 = Float3::Transform(vb[ib[i0++]], worldToNavMesh);
v0 = vbData[ibData[i0++]];
v1 = vbData[ibData[i0++]];
v2 = vbData[ibData[i0++]];
#if NAV_MESH_BUILD_DEBUG_DRAW_GEOMETRY
DEBUG_DRAW_TRIANGLE(v0, v1, v2, Color::Orange.AlphaMultiplied(0.3f), 1.0f, true);
#endif
@@ -142,6 +126,29 @@ struct NavigationSceneRasterization
rcRasterizeTriangle(Context, &v0.X, &v1.X, &v2.X, area, *Heightfield);
}
}
else
{
// Transform vertices from world space into the navmesh space
const Matrix worldToNavMesh = WorldToNavMesh;
for (int32 i0 = 0; i0 < ib.Count();)
{
Float3::Transform(vbData[ibData[i0++]], worldToNavMesh, v0);
Float3::Transform(vbData[ibData[i0++]], worldToNavMesh, v1);
Float3::Transform(vbData[ibData[i0++]], worldToNavMesh, v2);
#if NAV_MESH_BUILD_DEBUG_DRAW_GEOMETRY
DEBUG_DRAW_TRIANGLE(v0, v1, v2, Color::Orange.AlphaMultiplied(0.3f), 1.0f, true);
#endif
auto n = Float3::Cross(v0 - v1, v0 - v2);
n.Normalize();
const char area = n.Y > WalkableThreshold ? RC_WALKABLE_AREA : RC_NULL_AREA;
rcRasterizeTriangle(Context, &v0.X, &v1.X, &v2.X, area, *Heightfield);
}
}
// Clear after use
vb.Clear();
ib.Clear();
}
static void TriangulateBox(Array<Float3>& vb, Array<int32>& ib, const OrientedBoundingBox& box)
@@ -215,88 +222,67 @@ struct NavigationSceneRasterization
}
}
static bool Walk(Actor* actor, NavigationSceneRasterization& e)
void Rasterize(Actor* actor)
{
// Early out if object is not intersecting with the tile bounds or is not using navigation
if (!actor->GetIsActive() || !(actor->GetStaticFlags() & StaticFlags::Navigation))
return true;
BoundingBox actorBoxNavMesh;
BoundingBox::Transform(actor->GetBox(), e.WorldToNavMesh, actorBoxNavMesh);
if (!actorBoxNavMesh.Intersects(e.TileBoundsNavMesh))
return true;
// Prepare buffers (for triangles)
auto& vb = e.VertexBuffer;
auto& ib = e.IndexBuffer;
vb.Clear();
ib.Clear();
// Extract data from the actor
if (const auto* boxCollider = dynamic_cast<BoxCollider*>(actor))
{
if (boxCollider->GetIsTrigger())
return true;
return;
PROFILE_CPU_NAMED("BoxCollider");
const OrientedBoundingBox box = boxCollider->GetOrientedBox();
TriangulateBox(vb, ib, box);
e.RasterizeTriangles();
TriangulateBox(VertexBuffer, IndexBuffer, box);
RasterizeTriangles();
}
else if (const auto* sphereCollider = dynamic_cast<SphereCollider*>(actor))
{
if (sphereCollider->GetIsTrigger())
return true;
return;
PROFILE_CPU_NAMED("SphereCollider");
const BoundingSphere sphere = sphereCollider->GetSphere();
TriangulateSphere(vb, ib, sphere);
e.RasterizeTriangles();
TriangulateSphere(VertexBuffer, IndexBuffer, sphere);
RasterizeTriangles();
}
else if (const auto* capsuleCollider = dynamic_cast<CapsuleCollider*>(actor))
{
if (capsuleCollider->GetIsTrigger())
return true;
return;
PROFILE_CPU_NAMED("CapsuleCollider");
const BoundingBox box = capsuleCollider->GetBox();
TriangulateBox(vb, ib, box);
e.RasterizeTriangles();
TriangulateBox(VertexBuffer, IndexBuffer, box);
RasterizeTriangles();
}
else if (const auto* meshCollider = dynamic_cast<MeshCollider*>(actor))
{
if (meshCollider->GetIsTrigger())
return true;
return;
PROFILE_CPU_NAMED("MeshCollider");
auto collisionData = meshCollider->CollisionData.Get();
if (!collisionData || collisionData->WaitForLoaded())
return true;
collisionData->ExtractGeometry(vb, ib);
return;
collisionData->ExtractGeometry(VertexBuffer, IndexBuffer);
Matrix meshColliderToWorld;
meshCollider->GetLocalToWorldMatrix(meshColliderToWorld);
for (auto& v : vb)
for (auto& v : VertexBuffer)
Float3::Transform(v, meshColliderToWorld, v);
e.RasterizeTriangles();
RasterizeTriangles();
}
else if (const auto* splineCollider = dynamic_cast<SplineCollider*>(actor))
{
if (splineCollider->GetIsTrigger())
return true;
return;
PROFILE_CPU_NAMED("SplineCollider");
auto collisionData = splineCollider->CollisionData.Get();
if (!collisionData || collisionData->WaitForLoaded())
return true;
return;
splineCollider->ExtractGeometry(vb, ib);
e.RasterizeTriangles();
splineCollider->ExtractGeometry(VertexBuffer, IndexBuffer);
RasterizeTriangles();
}
else if (const auto* terrain = dynamic_cast<Terrain*>(actor))
{
@@ -306,13 +292,13 @@ struct NavigationSceneRasterization
{
const auto patch = terrain->GetPatch(patchIndex);
BoundingBox patchBoundsNavMesh;
BoundingBox::Transform(patch->GetBounds(), e.WorldToNavMesh, patchBoundsNavMesh);
if (!patchBoundsNavMesh.Intersects(e.TileBoundsNavMesh))
BoundingBox::Transform(patch->GetBounds(), WorldToNavMesh, patchBoundsNavMesh);
if (!patchBoundsNavMesh.Intersects(TileBoundsNavMesh))
continue;
patch->ExtractCollisionGeometry(vb, ib);
e.RasterizeTriangles();
// TODO: get collision only from tile area
patch->ExtractCollisionGeometry(VertexBuffer, IndexBuffer);
RasterizeTriangles();
}
}
else if (const auto* navLink = dynamic_cast<NavLink*>(actor))
@@ -321,44 +307,33 @@ struct NavigationSceneRasterization
OffMeshLink link;
link.Start = navLink->GetTransform().LocalToWorld(navLink->Start);
Float3::Transform(link.Start, e.WorldToNavMesh, link.Start);
Float3::Transform(link.Start, WorldToNavMesh, link.Start);
link.End = navLink->GetTransform().LocalToWorld(navLink->End);
Float3::Transform(link.End, e.WorldToNavMesh, link.End);
Float3::Transform(link.End, WorldToNavMesh, link.End);
link.Radius = navLink->Radius;
link.BiDir = navLink->BiDirectional;
link.Id = GetHash(navLink->GetID());
e.OffMeshLinks->Add(link);
OffMeshLinks->Add(link);
}
else if (const auto* navModifierVolume = dynamic_cast<NavModifierVolume*>(actor))
{
if (navModifierVolume->AgentsMask.IsNavMeshSupported(e.NavMesh->Properties))
if (navModifierVolume->AgentsMask.IsNavMeshSupported(NavMesh->Properties))
{
PROFILE_CPU_NAMED("NavModifierVolume");
Modifier modifier;
OrientedBoundingBox bounds = navModifierVolume->GetOrientedBox();
bounds.Transform(e.WorldToNavMesh);
bounds.Transform(WorldToNavMesh);
bounds.GetBoundingBox(modifier.Bounds);
modifier.NavArea = navModifierVolume->GetNavArea();
e.Modifiers->Add(modifier);
Modifiers->Add(modifier);
}
}
return true;
}
};
void RasterizeGeometry(NavMesh* navMesh, const BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh, rcContext* context, rcConfig* config, rcHeightfield* heightfield, Array<OffMeshLink>* offMeshLinks, Array<Modifier>* modifiers)
{
PROFILE_CPU_NAMED("RasterizeGeometry");
NavigationSceneRasterization rasterization(navMesh, tileBoundsNavMesh, worldToNavMesh, context, config, heightfield, offMeshLinks, modifiers);
Function<bool(Actor*, NavigationSceneRasterization&)> treeWalkFunction(NavigationSceneRasterization::Walk);
SceneQuery::TreeExecute<NavigationSceneRasterization&>(treeWalkFunction, rasterization);
}
// Builds navmesh tile bounds and check if there are any valid navmesh volumes at that tile location
// Returns true if tile is intersecting with any navmesh bounds volume actor - which means tile is in use
bool GetNavMeshTileBounds(Scene* scene, NavMesh* navMesh, int32 x, int32 y, float tileSize, BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh)
@@ -455,11 +430,44 @@ bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, B
Array<OffMeshLink> offMeshLinks;
Array<Modifier> modifiers;
RasterizeGeometry(navMesh, tileBoundsNavMesh, worldToNavMesh, &context, &config, heightfield, &offMeshLinks, &modifiers);
{
PROFILE_CPU_NAMED("RasterizeGeometry");
NavSceneRasterizer rasterizer(navMesh, tileBoundsNavMesh, worldToNavMesh, &context, &config, heightfield, &offMeshLinks, &modifiers);
rcFilterLowHangingWalkableObstacles(&context, config.walkableClimb, *heightfield);
rcFilterLedgeSpans(&context, config.walkableHeight, config.walkableClimb, *heightfield);
rcFilterWalkableLowHeightSpans(&context, config.walkableHeight, *heightfield);
// Collect actors to rasterize
Array<Actor*> actors;
{
PROFILE_CPU_NAMED("CollectActors");
ScopeLock lock(Level::ScenesLock);
for (Scene* scene : Level::Scenes)
{
for (Actor* actor : scene->Navigation.Actors)
{
BoundingBox actorBoxNavMesh;
BoundingBox::Transform(actor->GetBox(), rasterizer.WorldToNavMesh, actorBoxNavMesh);
if (actorBoxNavMesh.Intersects(rasterizer.TileBoundsNavMesh) &&
actor->IsActiveInHierarchy() &&
EnumHasAllFlags(actor->GetStaticFlags(), StaticFlags::Navigation))
{
actors.Add(actor);
}
}
}
}
// Rasterize actors
for (Actor* actor : actors)
{
rasterizer.Rasterize(actor);
}
}
{
PROFILE_CPU_NAMED("FilterHeightfield");
rcFilterLowHangingWalkableObstacles(&context, config.walkableClimb, *heightfield);
rcFilterLedgeSpans(&context, config.walkableHeight, config.walkableClimb, *heightfield);
rcFilterWalkableLowHeightSpans(&context, config.walkableHeight, *heightfield);
}
rcCompactHeightfield* compactHeightfield = rcAllocCompactHeightfield();
if (!compactHeightfield)
@@ -467,39 +475,51 @@ bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, B
LOG(Warning, "Could not generate navmesh: Out of memory compact heightfield.");
return true;
}
if (!rcBuildCompactHeightfield(&context, config.walkableHeight, config.walkableClimb, *heightfield, *compactHeightfield))
{
LOG(Warning, "Could not generate navmesh: Could not build compact data.");
return true;
PROFILE_CPU_NAMED("CompactHeightfield");
if (!rcBuildCompactHeightfield(&context, config.walkableHeight, config.walkableClimb, *heightfield, *compactHeightfield))
{
LOG(Warning, "Could not generate navmesh: Could not build compact data.");
return true;
}
}
rcFreeHeightField(heightfield);
if (!rcErodeWalkableArea(&context, config.walkableRadius, *compactHeightfield))
{
LOG(Warning, "Could not generate navmesh: Could not erode.");
return true;
PROFILE_CPU_NAMED("ErodeWalkableArea");
if (!rcErodeWalkableArea(&context, config.walkableRadius, *compactHeightfield))
{
LOG(Warning, "Could not generate navmesh: Could not erode.");
return true;
}
}
// Mark areas
for (auto& modifier : modifiers)
{
const unsigned char areaId = modifier.NavArea ? modifier.NavArea->Id : RC_NULL_AREA;
Float3 bMin = modifier.Bounds.Minimum;
Float3 bMax = modifier.Bounds.Maximum;
rcMarkBoxArea(&context, &bMin.X, &bMax.X, areaId, *compactHeightfield);
PROFILE_CPU_NAMED("MarkModifiers");
for (auto& modifier : modifiers)
{
const unsigned char areaId = modifier.NavArea ? modifier.NavArea->Id : RC_NULL_AREA;
Float3 bMin = modifier.Bounds.Minimum;
Float3 bMax = modifier.Bounds.Maximum;
rcMarkBoxArea(&context, &bMin.X, &bMax.X, areaId, *compactHeightfield);
}
}
if (!rcBuildDistanceField(&context, *compactHeightfield))
{
LOG(Warning, "Could not generate navmesh: Could not build distance field.");
return true;
PROFILE_CPU_NAMED("BuildDistanceField");
if (!rcBuildDistanceField(&context, *compactHeightfield))
{
LOG(Warning, "Could not generate navmesh: Could not build distance field.");
return true;
}
}
if (!rcBuildRegions(&context, *compactHeightfield, config.borderSize, config.minRegionArea, config.mergeRegionArea))
{
LOG(Warning, "Could not generate navmesh: Could not build regions.");
return true;
PROFILE_CPU_NAMED("BuildRegions");
if (!rcBuildRegions(&context, *compactHeightfield, config.borderSize, config.minRegionArea, config.mergeRegionArea))
{
LOG(Warning, "Could not generate navmesh: Could not build regions.");
return true;
}
}
rcContourSet* contourSet = rcAllocContourSet();
@@ -508,10 +528,13 @@ bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, B
LOG(Warning, "Could not generate navmesh: Out of memory for contour set.");
return true;
}
if (!rcBuildContours(&context, *compactHeightfield, config.maxSimplificationError, config.maxEdgeLen, *contourSet))
{
LOG(Warning, "Could not generate navmesh: Could not create contours.");
return true;
PROFILE_CPU_NAMED("BuildContours");
if (!rcBuildContours(&context, *compactHeightfield, config.maxSimplificationError, config.maxEdgeLen, *contourSet))
{
LOG(Warning, "Could not generate navmesh: Could not create contours.");
return true;
}
}
rcPolyMesh* polyMesh = rcAllocPolyMesh();
@@ -520,10 +543,13 @@ bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, B
LOG(Warning, "Could not generate navmesh: Out of memory for poly mesh.");
return true;
}
if (!rcBuildPolyMesh(&context, *contourSet, config.maxVertsPerPoly, *polyMesh))
{
LOG(Warning, "Could not generate navmesh: Could not triangulate contours.");
return true;
PROFILE_CPU_NAMED("BuildPolyMesh");
if (!rcBuildPolyMesh(&context, *contourSet, config.maxVertsPerPoly, *polyMesh))
{
LOG(Warning, "Could not generate navmesh: Could not triangulate contours.");
return true;
}
}
rcPolyMeshDetail* detailMesh = rcAllocPolyMeshDetail();
@@ -532,20 +558,20 @@ bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, B
LOG(Warning, "Could not generate navmesh: Out of memory for detail mesh.");
return true;
}
if (!rcBuildPolyMeshDetail(&context, *polyMesh, *compactHeightfield, config.detailSampleDist, config.detailSampleMaxError, *detailMesh))
{
LOG(Warning, "Could not generate navmesh: Could not build detail mesh.");
return true;
PROFILE_CPU_NAMED("BuildPolyMeshDetail");
if (!rcBuildPolyMeshDetail(&context, *polyMesh, *compactHeightfield, config.detailSampleDist, config.detailSampleMaxError, *detailMesh))
{
LOG(Warning, "Could not generate navmesh: Could not build detail mesh.");
return true;
}
}
rcFreeCompactHeightfield(compactHeightfield);
rcFreeContourSet(contourSet);
for (int i = 0; i < polyMesh->npolys; i++)
{
polyMesh->flags[i] = polyMesh->areas[i] != RC_NULL_AREA ? 1 : 0;
}
if (polyMesh->nverts == 0)
{
// Empty tile
@@ -623,15 +649,18 @@ bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, B
// Generate navmesh tile data
unsigned char* navData = nullptr;
int navDataSize = 0;
if (!dtCreateNavMeshData(&params, &navData, &navDataSize))
{
LOG(Warning, "Could not build Detour navmesh.");
return true;
PROFILE_CPU_NAMED("CreateNavMeshData");
if (!dtCreateNavMeshData(&params, &navData, &navDataSize))
{
LOG(Warning, "Could not build Detour navmesh.");
return true;
}
}
ASSERT_LOW_LAYER(navDataSize > 4 && *(uint32*)navData == DT_NAVMESH_MAGIC); // Sanity check for Detour header
{
PROFILE_CPU_NAMED("Navigation.CreateTile");
PROFILE_CPU_NAMED("CreateTiles");
ScopeLock lock(runtime->Locker);
@@ -729,17 +758,13 @@ public:
bool Run() override
{
PROFILE_CPU_NAMED("BuildNavMeshTile");
const auto navMesh = NavMesh.Get();
if (!navMesh)
{
return false;
}
if (GenerateTile(NavMesh, Runtime, X, Y, TileBoundsNavMesh, WorldToNavMesh, TileSize, Config))
{
LOG(Warning, "Failed to generate navmesh tile at {0}x{1}.", X, Y);
}
return false;
}
@@ -776,7 +801,7 @@ void OnSceneUnloading(Scene* scene, const Guid& sceneId)
{
NavBuildTasksLocker.Unlock();
// Cancel task but without locking queue from this thread to prevent dead-locks
// Cancel task but without locking queue from this thread to prevent deadlocks
task->Cancel();
NavBuildTasksLocker.Lock();
@@ -815,7 +840,7 @@ float NavMeshBuilder::GetNavMeshBuildingProgress()
return result;
}
void BuildTileAsync(NavMesh* navMesh, int32 x, int32 y, rcConfig& config, const BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh, float tileSize)
void BuildTileAsync(NavMesh* navMesh, const int32 x, const int32 y, const rcConfig& config, const BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh, float tileSize)
{
NavMeshRuntime* runtime = navMesh->GetRuntime();
NavBuildTasksLocker.Lock();
@@ -1108,7 +1133,7 @@ void NavMeshBuilder::Build(Scene* scene, const BoundingBox& dirtyBounds, float t
if (!scene)
{
LOG(Warning, "Could not generate navmesh without scene.");
return;
return;
}
// Early out if scene is not using navigation

View File

@@ -47,6 +47,20 @@ void NavModifierVolume::Deserialize(DeserializeStream& stream, ISerializeModifie
DESERIALIZE(AreaName);
}
void NavModifierVolume::OnEnable()
{
GetScene()->Navigation.Actors.Add(this);
BoxVolume::OnEnable();
}
void NavModifierVolume::OnDisable()
{
BoxVolume::OnDisable();
GetScene()->Navigation.Actors.Remove(this);
}
void NavModifierVolume::OnBoundsChanged(const BoundingBox& prevBounds)
{
#if COMPILE_WITH_NAV_MESH_BUILDER

View File

@@ -34,6 +34,8 @@ public:
// [BoxVolume]
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
void OnEnable() override;
void OnDisable() override;
protected:
// [BoxVolume]

View File

@@ -939,7 +939,7 @@ void Particles::DrawParticles(RenderContext& renderContext, ParticleEffect* effe
DrawCall drawCall;
drawCall.PerInstanceRandom = effect->GetPerInstanceRandom();
drawCall.ObjectPosition = effect->GetSphere().Center - view.Origin;
drawCall.ObjectRadius = effect->GetSphere().Radius;
drawCall.ObjectRadius = (float)effect->GetSphere().Radius;
// Draw all emitters
for (int32 emitterIndex = 0; emitterIndex < effect->Instance.Emitters.Count(); emitterIndex++)

View File

@@ -2,6 +2,7 @@
#include "Collider.h"
#include "Engine/Core/Log.h"
#include "Engine/Level/Scene/Scene.h"
#if USE_EDITOR
#include "Engine/Level/Scene/SceneRendering.h"
#endif
@@ -35,6 +36,13 @@ void Collider::SetIsTrigger(bool value)
_isTrigger = value;
if (_shape)
PhysicsBackend::SetShapeState(_shape, IsActiveInHierarchy(), _isTrigger && CanBeTrigger());
if (EnumHasAnyFlags(_staticFlags, StaticFlags::Navigation) && _isEnabled)
{
if (_isTrigger)
GetScene()->Navigation.Actors.Remove(this);
else
GetScene()->Navigation.Actors.Add(this);
}
}
void Collider::SetCenter(const Vector3& value)
@@ -43,13 +51,9 @@ void Collider::SetCenter(const Vector3& value)
return;
_center = value;
if (_staticActor)
{
PhysicsBackend::SetShapeLocalPose(_shape, _center, Quaternion::Identity);
}
else if (const RigidBody* rigidBody = GetAttachedRigidBody())
{
PhysicsBackend::SetShapeLocalPose(_shape, (_localTransform.Translation + _localTransform.Orientation * _center) * rigidBody->GetScale(), _localTransform.Orientation);
}
UpdateBounds();
}
@@ -134,25 +138,27 @@ RigidBody* Collider::GetAttachedRigidBody() const
return nullptr;
}
#if USE_EDITOR
void Collider::OnEnable()
{
if (EnumHasAnyFlags(_staticFlags, StaticFlags::Navigation) && !_isTrigger)
GetScene()->Navigation.Actors.Add(this);
#if USE_EDITOR
GetSceneRendering()->AddPhysicsDebug<Collider, &Collider::DrawPhysicsDebug>(this);
#endif
// Base
Actor::OnEnable();
PhysicsColliderActor::OnEnable();
}
void Collider::OnDisable()
{
// Base
Actor::OnDisable();
PhysicsColliderActor::OnDisable();
if (EnumHasAnyFlags(_staticFlags, StaticFlags::Navigation) && !_isTrigger)
GetScene()->Navigation.Actors.Remove(this);
#if USE_EDITOR
GetSceneRendering()->RemovePhysicsDebug<Collider, &Collider::DrawPhysicsDebug>(this);
}
#endif
}
void Collider::Attach(RigidBody* rigidBody)
{
@@ -432,6 +438,19 @@ void Collider::OnLayerChanged()
UpdateLayerBits();
}
void Collider::OnStaticFlagsChanged()
{
PhysicsColliderActor::OnStaticFlagsChanged();
if (!_isTrigger && _isEnabled)
{
if (EnumHasAnyFlags(_staticFlags, StaticFlags::Navigation))
GetScene()->Navigation.Actors.AddUnique(this);
else
GetScene()->Navigation.Actors.Remove(this);
}
}
void Collider::OnPhysicsSceneChanged(PhysicsScene* previous)
{
PhysicsColliderActor::OnPhysicsSceneChanged(previous);

View File

@@ -171,15 +171,14 @@ public:
protected:
// [PhysicsColliderActor]
#if USE_EDITOR
void OnEnable() override;
void OnDisable() override;
#endif
void BeginPlay(SceneBeginData* data) override;
void EndPlay() override;
void OnActiveInTreeChanged() override;
void OnParentChanged() override;
void OnTransformChanged() override;
void OnLayerChanged() override;
void OnStaticFlagsChanged() override;
void OnPhysicsSceneChanged(PhysicsScene* previous) override;
};

View File

@@ -8,6 +8,7 @@
#include "Engine/Physics/PhysicsScene.h"
#include "Engine/Physics/PhysicsBackend.h"
#include "Engine/Physics/CollisionCooking.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Threading/Threading.h"
REGISTER_BINARY_ASSET(CollisionData, "FlaxEngine.CollisionData", true);
@@ -33,6 +34,7 @@ bool CollisionData::CookCollision(CollisionDataType type, ModelBase* modelObj, i
LOG(Error, "Cannot cook collision data for virtual models on a main thread (virtual models data is stored on GPU only). Use thread pool or async task.");
return true;
}
PROFILE_CPU();
// Prepare
CollisionCooking::Argument arg;
@@ -61,6 +63,7 @@ bool CollisionData::CookCollision(CollisionDataType type, ModelBase* modelObj, i
bool CollisionData::CookCollision(CollisionDataType type, const Span<Float3>& vertices, const Span<uint32>& triangles, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit)
{
PROFILE_CPU();
CHECK_RETURN(vertices.Length() != 0, true);
CHECK_RETURN(triangles.Length() != 0 && triangles.Length() % 3 == 0, true);
ModelData modelData;
@@ -74,6 +77,7 @@ bool CollisionData::CookCollision(CollisionDataType type, const Span<Float3>& ve
bool CollisionData::CookCollision(CollisionDataType type, const Span<Float3>& vertices, const Span<int32>& triangles, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit)
{
PROFILE_CPU();
CHECK_RETURN(vertices.Length() != 0, true);
CHECK_RETURN(triangles.Length() != 0 && triangles.Length() % 3 == 0, true);
ModelData modelData;
@@ -89,12 +93,12 @@ bool CollisionData::CookCollision(CollisionDataType type, const Span<Float3>& ve
bool CollisionData::CookCollision(CollisionDataType type, ModelData* modelData, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit)
{
// Validate state
if (!IsVirtual())
{
LOG(Warning, "Only virtual assets can be modified at runtime.");
return true;
}
PROFILE_CPU();
// Prepare
CollisionCooking::Argument arg;
@@ -107,18 +111,14 @@ bool CollisionData::CookCollision(CollisionDataType type, ModelData* modelData,
SerializedOptions options;
BytesContainer outputData;
if (CollisionCooking::CookCollision(arg, options, outputData))
{
return true;
}
// Clear state
unload(true);
// Load data
if (load(&options, outputData.Get(), outputData.Length()) != LoadResult::Ok)
{
return true;
}
// Mark as loaded (eg. Mesh Colliders using this asset will update shape for physics simulation)
onLoaded();
@@ -133,6 +133,7 @@ bool CollisionData::GetModelTriangle(uint32 faceIndex, MeshBase*& mesh, uint32&
meshTriangleIndex = MAX_uint32;
if (!IsLoaded())
return false;
PROFILE_CPU();
ScopeLock lock(Locker);
if (_triangleMesh)
{
@@ -178,6 +179,7 @@ bool CollisionData::GetModelTriangle(uint32 faceIndex, MeshBase*& mesh, uint32&
void CollisionData::ExtractGeometry(Array<Float3>& vertexBuffer, Array<int32>& indexBuffer) const
{
PROFILE_CPU();
vertexBuffer.Clear();
indexBuffer.Clear();
@@ -194,6 +196,7 @@ const Array<Float3>& CollisionData::GetDebugLines()
{
if (_hasMissingDebugLines && IsLoaded())
{
PROFILE_CPU();
ScopeLock lock(Locker);
_hasMissingDebugLines = false;

View File

@@ -4265,6 +4265,7 @@ void* PhysicsBackend::CreateHeightField(byte* data, int32 dataSize)
void PhysicsBackend::GetConvexMeshTriangles(void* contextMesh, Array<Float3, HeapAllocation>& vertexBuffer, Array<int, HeapAllocation>& indexBuffer)
{
PROFILE_CPU();
auto contextMeshPhysX = (PxConvexMesh*)contextMesh;
uint32 numIndices = 0;
uint32 numVertices = contextMeshPhysX->getNbVertices();
@@ -4304,6 +4305,7 @@ void PhysicsBackend::GetConvexMeshTriangles(void* contextMesh, Array<Float3, Hea
void PhysicsBackend::GetTriangleMeshTriangles(void* triangleMesh, Array<Float3>& vertexBuffer, Array<int32, HeapAllocation>& indexBuffer)
{
PROFILE_CPU();
auto triangleMeshPhysX = (PxTriangleMesh*)triangleMesh;
uint32 numVertices = triangleMeshPhysX->getNbVertices();
uint32 numIndices = triangleMeshPhysX->getNbTriangles() * 3;

View File

@@ -134,7 +134,7 @@ public:
static ScriptingObject* ToNative(MObject* obj);
static MObject* ToManaged(ScriptingObject* obj)
FORCE_INLINE static MObject* ToManaged(const ScriptingObject* obj)
{
return obj ? obj->GetOrCreateManagedInstance() : nullptr;
}

View File

@@ -12,6 +12,7 @@
#include "Engine/Graphics/RenderView.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/Textures/GPUTexture.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Physics/PhysicsScene.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Renderer/GlobalSignDistanceFieldPass.h"
@@ -804,6 +805,7 @@ RigidBody* Terrain::GetAttachedRigidBody() const
void Terrain::OnEnable()
{
GetScene()->Navigation.Actors.Add(this);
GetSceneRendering()->AddActor(this, _sceneRenderingKey);
#if TERRAIN_USE_PHYSICS_DEBUG
GetSceneRendering()->AddPhysicsDebug<Terrain, &Terrain::DrawPhysicsDebug>(this);
@@ -824,6 +826,7 @@ void Terrain::OnEnable()
void Terrain::OnDisable()
{
GetScene()->Navigation.Actors.Remove(this);
GetSceneRendering()->RemoveActor(this, _sceneRenderingKey);
#if TERRAIN_USE_PHYSICS_DEBUG
GetSceneRendering()->RemovePhysicsDebug<Terrain, &Terrain::DrawPhysicsDebug>(this);

View File

@@ -97,7 +97,7 @@ void TerrainChunk::Draw(const RenderContext& renderContext) const
drawCall.Material = _cachedDrawMaterial;
renderContext.View.GetWorldMatrix(_transform, drawCall.World);
drawCall.ObjectPosition = drawCall.World.GetTranslation();
drawCall.ObjectRadius = _sphere.Radius;
drawCall.ObjectRadius = (float)_sphere.Radius;
drawCall.Terrain.Patch = _patch;
drawCall.Terrain.HeightmapUVScaleBias = _heightmapUVScaleBias;
drawCall.Terrain.OffsetUV = Vector2((float)(_patch->_x * Terrain::ChunksCountEdge + _x), (float)(_patch->_z * Terrain::ChunksCountEdge + _z));
@@ -155,7 +155,7 @@ void TerrainChunk::Draw(const RenderContext& renderContext, MaterialBase* materi
drawCall.Material = material;
renderContext.View.GetWorldMatrix(_transform, drawCall.World);
drawCall.ObjectPosition = drawCall.World.GetTranslation();
drawCall.ObjectRadius = _sphere.Radius;
drawCall.ObjectRadius = (float)_sphere.Radius;
drawCall.Terrain.Patch = _patch;
drawCall.Terrain.HeightmapUVScaleBias = _heightmapUVScaleBias;
drawCall.Terrain.OffsetUV = Vector2((float)(_patch->_x * Terrain::ChunksCountEdge + _x), (float)(_patch->_z * Terrain::ChunksCountEdge + _z));

View File

@@ -2474,7 +2474,7 @@ void TerrainPatch::ExtractCollisionGeometry(Array<Float3>& vertexBuffer, Array<i
if (_collisionVertices.IsEmpty())
{
// Prevent race conditions
ScopeLock lock(Level::ScenesLock);
ScopeLock sceneLock(Level::ScenesLock);
if (_collisionVertices.IsEmpty())
{
const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::Terrain::ChunksCountEdge;

View File

@@ -7,6 +7,16 @@
#include "Engine/Scripting/ManagedCLR/MUtils.h"
#include <ThirdParty/catch2/catch.hpp>
TestNesting::TestNesting(const SpawnParams& params)
: SerializableScriptingObject(params)
{
}
TestNesting2::TestNesting2(const SpawnParams& params)
: SerializableScriptingObject(params)
{
}
TestClassNative::TestClassNative(const SpawnParams& params)
: ScriptingObject(params)
{

View File

@@ -6,6 +6,59 @@
#include "Engine/Core/Math/Vector3.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Scripting/ScriptingObject.h"
#include "Engine/Scripting/SerializableScriptingObject.h"
// Test compilation with nested types.
API_CLASS() class TestNesting : public SerializableScriptingObject
{
DECLARE_SCRIPTING_TYPE(TestNesting);
API_AUTO_SERIALIZATION();
// Structure
API_STRUCT() struct TestAttribute : public ISerializable
{
DECLARE_SCRIPTING_TYPE_MINIMAL(TestAttribute);
API_AUTO_SERIALIZATION();
// Enumeration
API_ENUM() enum TestEnum
{
E1, E2,
};
// Enum
API_FIELD() TestEnum Enum = E1;
};
// Attributes
API_FIELD() Array<TestAttribute> Attributes;
// Enum
API_FIELD() TestAttribute::TestEnum Enum = TestAttribute::E1;
};
// Test compilation with nested types.
API_CLASS() class TestNesting2 : public SerializableScriptingObject
{
DECLARE_SCRIPTING_TYPE(TestNesting2);
API_AUTO_SERIALIZATION();
// Structure
API_STRUCT() struct TestAttribute
{
DECLARE_SCRIPTING_TYPE_MINIMAL(TestAttribute);
// Enumeration
API_ENUM() enum TestEnum
{
E1, E2,
};
};
// Attributes
API_FIELD() Array<TestNesting::TestAttribute> Attributes;
// Enum
API_FIELD() TestNesting::TestAttribute::TestEnum Enum = TestNesting::TestAttribute::E1;
};
// Test structure.
API_STRUCT(NoDefault) struct TestStruct : public ISerializable

View File

@@ -4,6 +4,27 @@ using System.ComponentModel;
namespace FlaxEngine.GUI
{
/// <summary>
/// Options for text case
/// </summary>
public enum TextCaseOptions
{
/// <summary>
/// No text case.
/// </summary>
None,
/// <summary>
/// Uppercase.
/// </summary>
Uppercase,
/// <summary>
/// Lowercase
/// </summary>
Lowercase
}
/// <summary>
/// The basic GUI label control.
/// </summary>
@@ -45,6 +66,24 @@ namespace FlaxEngine.GUI
}
}
/// <summary>
/// The text case.
/// </summary>
[EditorDisplay("Text Style"), EditorOrder(2000), Tooltip("The case of the text.")]
public TextCaseOptions CaseOption { get; set; } = TextCaseOptions.None;
/// <summary>
/// Whether to bold the text.
/// </summary>
[EditorDisplay("Text Style"), EditorOrder(2001), Tooltip("Bold the text.")]
public bool Bold { get; set; } = false;
/// <summary>
/// Whether to italicize the text.
/// </summary>
[EditorDisplay("Text Style"), EditorOrder(2002), Tooltip("Italicize the text.")]
public bool Italic { get; set; } = false;
/// <summary>
/// Gets or sets the color of the text.
/// </summary>
@@ -234,18 +273,49 @@ namespace FlaxEngine.GUI
}
}
Render2D.DrawText(_font.GetFont(), Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale);
Font font = GetFont();
var text = ConvertedText();
Render2D.DrawText(font, Material, text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale);
if (ClipText)
Render2D.PopClip();
}
private Font GetFont()
{
Font font;
if (Bold)
font = Italic ? _font.GetBold().GetItalic().GetFont() : _font.GetBold().GetFont();
else if (Italic)
font = _font.GetItalic().GetFont();
else
font = _font.GetFont();
return font;
}
private LocalizedString ConvertedText()
{
LocalizedString text = _text;
switch (CaseOption)
{
case TextCaseOptions.Uppercase:
text = text.ToString().ToUpper();
break;
case TextCaseOptions.Lowercase:
text = text.ToString().ToLower();
break;
}
return text;
}
/// <inheritdoc />
protected override void PerformLayoutBeforeChildren()
{
if (_autoWidth || _autoHeight || _autoFitText)
{
var font = _font.GetFont();
Font font = GetFont();
var text = ConvertedText();
if (font)
{
// Calculate text size
@@ -255,7 +325,7 @@ namespace FlaxEngine.GUI
layout.Bounds.Size.X = Width - Margin.Width;
else if (_autoWidth && !_autoHeight)
layout.Bounds.Size.Y = Height - Margin.Height;
_textSize = font.MeasureText(_text, ref layout);
_textSize = font.MeasureText(text, ref layout);
_textSize.Y *= BaseLinesGapScale;
// Check if size is controlled via text

View File

@@ -24,11 +24,49 @@ namespace FlaxEngine.GUI
get => _watermarkText;
set => _watermarkText = value;
}
/// <summary>
/// The text case.
/// </summary>
[EditorDisplay("Text Style"), EditorOrder(2000), Tooltip("The case of the text.")]
public TextCaseOptions CaseOption { get; set; } = TextCaseOptions.None;
/// <summary>
/// Whether to bold the text.
/// </summary>
[EditorDisplay("Text Style"), EditorOrder(2001), Tooltip("Bold the text.")]
public bool Bold { get; set; } = false;
/// <summary>
/// Whether to italicize the text.
/// </summary>
[EditorDisplay("Text Style"), EditorOrder(2002), Tooltip("Italicize the text.")]
public bool Italic { get; set; } = false;
/// <summary>
/// The vertical alignment of the text.
/// </summary>
[EditorDisplay("Text Style"), EditorOrder(2023), Tooltip("The vertical alignment of the text.")]
public TextAlignment VerticalAlignment
{
get => _layout.VerticalAlignment;
set => _layout.VerticalAlignment = value;
}
/// <summary>
/// The vertical alignment of the text.
/// </summary>
[EditorDisplay("Text Style"), EditorOrder(2024), Tooltip("The horizontal alignment of the text.")]
public TextAlignment HorizontalAlignment
{
get => _layout.HorizontalAlignment;
set => _layout.HorizontalAlignment = value;
}
/// <summary>
/// Gets or sets the text wrapping within the control bounds.
/// </summary>
[EditorDisplay("Text Style"), EditorOrder(2023), Tooltip("The text wrapping within the control bounds.")]
[EditorDisplay("Text Style"), EditorOrder(2025), Tooltip("The text wrapping within the control bounds.")]
public TextWrapping Wrapping
{
get => _layout.TextWrapping;
@@ -38,13 +76,13 @@ namespace FlaxEngine.GUI
/// <summary>
/// Gets or sets the font.
/// </summary>
[EditorDisplay("Text Style"), EditorOrder(2024)]
[EditorDisplay("Text Style"), EditorOrder(2026)]
public FontReference Font { get; set; }
/// <summary>
/// Gets or sets the custom material used to render the text. It must has domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data.
/// </summary>
[EditorDisplay("Text Style"), EditorOrder(2025), Tooltip("Custom material used to render the text. It must has domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data.")]
[EditorDisplay("Text Style"), EditorOrder(2027), Tooltip("Custom material used to render the text. It must has domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data.")]
public MaterialBase TextMaterial { get; set; }
/// <summary>
@@ -98,19 +136,46 @@ namespace FlaxEngine.GUI
/// <inheritdoc />
public override Float2 GetTextSize()
{
var font = Font.GetFont();
var font = GetFont();
if (font == null)
{
return Float2.Zero;
}
return font.MeasureText(_text, ref _layout);
return font.MeasureText(ConvertedText(), ref _layout);
}
private Font GetFont()
{
Font font;
if (Bold)
font = Italic ? Font.GetBold().GetItalic().GetFont() : Font.GetBold().GetFont();
else if (Italic)
font = Font.GetItalic().GetFont();
else
font = Font.GetFont();
return font;
}
private string ConvertedText()
{
string text = _text;
switch (CaseOption)
{
case TextCaseOptions.Uppercase:
text = text.ToUpper();
break;
case TextCaseOptions.Lowercase:
text = text.ToLower();
break;
}
return text;
}
/// <inheritdoc />
public override Float2 GetCharPosition(int index, out float height)
{
var font = Font.GetFont();
var font = GetFont();
if (font == null)
{
height = Height;
@@ -118,19 +183,19 @@ namespace FlaxEngine.GUI
}
height = font.Height / DpiScale;
return font.GetCharPosition(_text, index, ref _layout);
return font.GetCharPosition(ConvertedText(), index, ref _layout);
}
/// <inheritdoc />
public override int HitTestText(Float2 location)
{
var font = Font.GetFont();
var font = GetFont();
if (font == null)
{
return 0;
}
return font.HitTestText(_text, location, ref _layout);
return font.HitTestText(ConvertedText(), location, ref _layout);
}
/// <inheritdoc />
@@ -147,7 +212,7 @@ namespace FlaxEngine.GUI
// Cache data
var rect = new Rectangle(Float2.Zero, Size);
bool enabled = EnabledInHierarchy;
var font = Font.GetFont();
var font = GetFont();
if (!font)
return;
@@ -166,11 +231,13 @@ namespace FlaxEngine.GUI
if (useViewOffset)
Render2D.PushTransform(Matrix3x3.Translation2D(-_viewOffset));
var text = ConvertedText();
// Check if sth is selected to draw selection
if (HasSelection)
{
var leftEdge = font.GetCharPosition(_text, SelectionLeft, ref _layout);
var rightEdge = font.GetCharPosition(_text, SelectionRight, ref _layout);
var leftEdge = font.GetCharPosition(text, SelectionLeft, ref _layout);
var rightEdge = font.GetCharPosition(text, SelectionRight, ref _layout);
var fontHeight = font.Height;
var textHeight = fontHeight / DpiScale;
@@ -207,12 +274,12 @@ namespace FlaxEngine.GUI
}
// Text or watermark
if (_text.Length > 0)
if (text.Length > 0)
{
var color = TextColor;
if (!enabled)
color *= 0.6f;
Render2D.DrawText(font, _text, color, ref _layout, TextMaterial);
Render2D.DrawText(font, text, color, ref _layout, TextMaterial);
}
else if (!string.IsNullOrEmpty(_watermarkText))
{
@@ -224,7 +291,25 @@ namespace FlaxEngine.GUI
{
float alpha = Mathf.Saturate(Mathf.Cos(_animateTime * CaretFlashSpeed) * 0.5f + 0.7f);
alpha = alpha * alpha * alpha * alpha * alpha * alpha;
Render2D.FillRectangle(CaretBounds, CaretColor * alpha);
if (CaretPosition == 0)
{
var bounds = CaretBounds;
if (_layout.VerticalAlignment == TextAlignment.Center)
bounds.Y = _layout.Bounds.Y + _layout.Bounds.Height * 0.5f - bounds.Height * 0.5f;
else if (_layout.VerticalAlignment == TextAlignment.Far)
bounds.Y = _layout.Bounds.Y + _layout.Bounds.Height - bounds.Height;
if (_layout.HorizontalAlignment == TextAlignment.Center)
bounds.X = _layout.Bounds.X + _layout.Bounds.Width * 0.5f - bounds.Width * 0.5f;
else if (_layout.HorizontalAlignment == TextAlignment.Far)
bounds.X = _layout.Bounds.X + _layout.Bounds.Width - bounds.Width;
Render2D.FillRectangle(bounds, CaretColor * alpha);
}
else
{
Render2D.FillRectangle(CaretBounds, CaretColor * alpha);
}
}
// Restore rendering state

View File

@@ -17,6 +17,9 @@ namespace FlaxEngine.GUI
private ScrollBars _scrollBars;
private float _scrollBarsSize = ScrollBar.DefaultSize;
private Margin _scrollMargin;
private Color _scrollbarTrackColor;
private Color _scrollbarThumbColor;
private Color _scrollbarThumbSelectedColor;
/// <summary>
/// The cached scroll area bounds. Used to scroll contents of the panel control. Cached during performing layout.
@@ -49,7 +52,7 @@ namespace FlaxEngine.GUI
/// <summary>
/// Gets or sets the scroll bars usage by this panel.
/// </summary>
[EditorOrder(0), Tooltip("The scroll bars usage.")]
[EditorDisplay("Scrollbar Style"), EditorOrder(1500), Tooltip("The scroll bars usage.")]
public ScrollBars ScrollBars
{
get => _scrollBars;
@@ -73,6 +76,12 @@ namespace FlaxEngine.GUI
//VScrollBar.X += VScrollBar.Width;
VScrollBar.ValueChanged += () => SetViewOffset(Orientation.Vertical, VScrollBar.Value);
}
if (VScrollBar != null)
{
VScrollBar.TrackColor = _scrollbarTrackColor;
VScrollBar.ThumbColor = _scrollbarThumbColor;
VScrollBar.ThumbSelectedColor = _scrollbarThumbSelectedColor;
}
}
else if (VScrollBar != null)
{
@@ -94,6 +103,12 @@ namespace FlaxEngine.GUI
//HScrollBar.Offsets += new Margin(0, 0, HScrollBar.Height * 0.5f, 0);
HScrollBar.ValueChanged += () => SetViewOffset(Orientation.Horizontal, HScrollBar.Value);
}
if (HScrollBar != null)
{
HScrollBar.TrackColor = _scrollbarTrackColor;
HScrollBar.ThumbColor = _scrollbarThumbColor;
HScrollBar.ThumbSelectedColor = _scrollbarThumbSelectedColor;
}
}
else if (HScrollBar != null)
{
@@ -108,7 +123,7 @@ namespace FlaxEngine.GUI
/// <summary>
/// Gets or sets the size of the scroll bars.
/// </summary>
[EditorOrder(5), Tooltip("Scroll bars size.")]
[EditorDisplay("Scrollbar Style"), EditorOrder(1501), Tooltip("Scroll bars size.")]
public float ScrollBarsSize
{
get => _scrollBarsSize;
@@ -124,7 +139,7 @@ namespace FlaxEngine.GUI
/// <summary>
/// Gets or sets a value indicating whether always show scrollbars. Otherwise show them only if scrolling is available.
/// </summary>
[EditorOrder(10), Tooltip("Whether always show scrollbars. Otherwise show them only if scrolling is available.")]
[EditorDisplay("Scrollbar Style"), EditorOrder(1502), Tooltip("Whether always show scrollbars. Otherwise show them only if scrolling is available.")]
public bool AlwaysShowScrollbars
{
get => _alwaysShowScrollbars;
@@ -157,7 +172,7 @@ namespace FlaxEngine.GUI
/// <summary>
/// Gets or sets the scroll margin applies to the child controls area. Can be used to expand the scroll area bounds by adding a margin.
/// </summary>
[EditorOrder(20), Tooltip("Scroll margin applies to the child controls area. Can be used to expand the scroll area bounds by adding a margin.")]
[EditorDisplay("Scrollbar Style"), EditorOrder(1503), Tooltip("Scroll margin applies to the child controls area. Can be used to expand the scroll area bounds by adding a margin.")]
public Margin ScrollMargin
{
get => _scrollMargin;
@@ -171,6 +186,57 @@ namespace FlaxEngine.GUI
}
}
/// <summary>
/// The color of the scroll bar track.
/// </summary>
[EditorDisplay("Scrollbar Style"), EditorOrder(1600), ExpandGroups]
public Color ScrollbarTrackColor
{
get => _scrollbarTrackColor;
set
{
_scrollbarTrackColor = value;
if (VScrollBar != null)
VScrollBar.TrackColor = _scrollbarTrackColor;
if (HScrollBar != null)
HScrollBar.TrackColor = _scrollbarTrackColor;
}
}
/// <summary>
/// The color of the scroll bar thumb.
/// </summary>
[EditorDisplay("Scrollbar Style"), EditorOrder(1601)]
public Color ScrollbarThumbColor
{
get => _scrollbarThumbColor;
set
{
_scrollbarThumbColor = value;
if (VScrollBar != null)
VScrollBar.ThumbColor = _scrollbarThumbColor;
if (HScrollBar != null)
HScrollBar.ThumbColor = _scrollbarThumbColor;
}
}
/// <summary>
/// The color of the scroll bar thumb when selected.
/// </summary>
[EditorDisplay("Scrollbar Style"), EditorOrder(1602)]
public Color ScrollbarThumbSelectedColor
{
get => _scrollbarThumbSelectedColor;
set
{
_scrollbarThumbSelectedColor = value;
if (VScrollBar != null)
VScrollBar.ThumbSelectedColor = _scrollbarThumbSelectedColor;
if (HScrollBar != null)
HScrollBar.ThumbSelectedColor = _scrollbarThumbSelectedColor;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="Panel"/> class.
/// </summary>
@@ -187,6 +253,10 @@ namespace FlaxEngine.GUI
public Panel(ScrollBars scrollBars, bool autoFocus = false)
{
AutoFocus = autoFocus;
var style = Style.Current;
_scrollbarTrackColor = style.BackgroundHighlighted;
_scrollbarThumbColor = style.BackgroundNormal;
_scrollbarThumbSelectedColor = style.BackgroundSelected;
ScrollBars = scrollBars;
}

View File

@@ -74,6 +74,21 @@ namespace FlaxEngine.GUI
/// </summary>
public bool EnableSmoothing { get; set; } = true;
/// <summary>
/// The track color.
/// </summary>
public Color TrackColor;
/// <summary>
/// The thumb color.
/// </summary>
public Color ThumbColor;
/// <summary>
/// The selected thumb color.
/// </summary>
public Color ThumbSelectedColor;
/// <summary>
/// Gets or sets the minimum value.
/// </summary>
@@ -209,6 +224,10 @@ namespace FlaxEngine.GUI
AutoFocus = false;
_orientation = orientation;
var style = Style.Current;
TrackColor = style.BackgroundHighlighted;
ThumbColor = style.BackgroundNormal;
ThumbSelectedColor = style.BackgroundSelected;
}
/// <summary>
@@ -377,8 +396,8 @@ namespace FlaxEngine.GUI
base.Draw();
var style = Style.Current;
Render2D.FillRectangle(_trackRect, style.BackgroundHighlighted * _thumbOpacity);
Render2D.FillRectangle(_thumbRect, (_thumbClicked ? style.BackgroundSelected : style.BackgroundNormal) * _thumbOpacity);
Render2D.FillRectangle(_trackRect, TrackColor * _thumbOpacity);
Render2D.FillRectangle(_thumbRect, (_thumbClicked ? ThumbSelectedColor : ThumbColor) * _thumbOpacity);
}
/// <inheritdoc />

View File

@@ -234,11 +234,16 @@ namespace FlaxEngine.GUI
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), Color.Lerp(style.BackgroundSelected, style.Background, 0.6f));
Render2D.FillRectangle(new Rectangle(1.1f, 1.1f, Width - 2, Height - 2), style.Background);
// Padding for text
var textRect = GetClientArea();
textRect.X += 5;
textRect.Width -= 10;
// Tooltip text
Render2D.DrawText(
style.FontMedium,
_currentText,
GetClientArea(),
textRect,
style.Foreground,
TextAlignment.Center,
TextAlignment.Center,

View File

@@ -366,7 +366,7 @@ void TextRender::Draw(RenderContext& renderContext)
DrawCall drawCall;
drawCall.World = world;
drawCall.ObjectPosition = drawCall.World.GetTranslation();
drawCall.ObjectRadius = _sphere.Radius;
drawCall.ObjectRadius = (float)_sphere.Radius;
drawCall.Surface.GeometrySize = _localBox.GetSize();
drawCall.Surface.PrevWorld = _drawState.PrevWorld;
drawCall.Surface.Lightmap = nullptr;

View File

@@ -196,6 +196,10 @@ protected:
class rcScopedTimer
{
public:
#if 1
// Disable timer functionality
inline rcScopedTimer(rcContext* ctx, const rcTimerLabel label) { }
#else
/// Constructs an instance and starts the timer.
/// @param[in] ctx The context to use.
/// @param[in] label The category of the timer.
@@ -209,6 +213,7 @@ private:
rcContext* const m_ctx;
const rcTimerLabel m_label;
#endif
};
/// Specifies a configuration to use when performing Recast builds.

View File

@@ -98,7 +98,7 @@ namespace Flax.Build.Bindings
{
var result = NativeName;
if (Parent != null && !(Parent is FileInfo))
result = Parent.FullNameNative + '_' + result;
result = Parent.FullNameNative.Replace("::", "_") + '_' + result;
return result;
}
}

View File

@@ -267,13 +267,21 @@ namespace Flax.Build.Bindings
if (value.Contains('(') && value.Contains(')'))
return "new " + value;
// Special case for non-strongly typed enums
if (valueType != null && !value.Contains('.', StringComparison.Ordinal))
{
apiType = FindApiTypeInfo(buildData, valueType, caller);
if (apiType is EnumInfo)
return $"{apiType.Name}.{value}";
}
return value;
}
private static string GenerateCSharpNativeToManaged(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, bool marshalling = false)
{
string result;
if (typeInfo?.Type == null)
if (typeInfo == null || typeInfo.Type == null)
throw new ArgumentNullException();
// Use dynamic array as wrapper container for fixed-size native arrays
@@ -1641,8 +1649,9 @@ namespace Flax.Build.Bindings
if (internalType)
{
// Marshal blittable array elements back to original non-blittable elements
string originalElementTypeMarshaller = originalElementType + "Marshaller";
string internalElementType = $"{originalElementTypeMarshaller}.{originalElementType}Internal";
string originalElementTypeMarshaller = $"{originalElementType}Marshaller";
string originalElementTypeName = originalElementType.Substring(originalElementType.LastIndexOf('.') + 1); // Strip namespace
string internalElementType = $"{originalElementTypeMarshaller}.{originalElementTypeName}Internal";
toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.ConvertArray((Unsafe.As<ManagedArray>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target)).ToSpan<{internalElementType}>(), {originalElementTypeMarshaller}.ToManaged) : null;");
toNativeContent.AppendLine($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(NativeInterop.ConvertArray(managed.{fieldInfo.Name}, {originalElementTypeMarshaller}.ToNative)), GCHandleType.Weak) : IntPtr.Zero;");
freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<{internalElementType}> values = (Unsafe.As<ManagedArray>(handle.Target)).ToSpan<{internalElementType}>(); foreach (var value in values) {{ {originalElementTypeMarshaller}.Free(value); }} (Unsafe.As<ManagedArray>(handle.Target)).Free(); handle.Free(); }}");

View File

@@ -1991,13 +1991,8 @@ namespace Flax.Build.Bindings
}
contents.Append(')').AppendLine();
contents.Append(" {").AppendLine();
if (buildData.Target.IsEditor && false)
contents.Append(" MMethod* method = nullptr;").AppendLine(); // TODO: find a better way to cache event method in editor and handle C# hot-reload
else
contents.Append(" static MMethod* method = nullptr;").AppendLine();
contents.Append(" if (!method)").AppendLine();
contents.AppendFormat(" method = {1}::TypeInitializer.GetClass()->GetMethod(\"Internal_{0}_Invoke\", {2});", eventInfo.Name, classTypeNameNative, paramsCount).AppendLine();
contents.Append(" CHECK(method);").AppendLine();
contents.Append(" static MMethod* method = nullptr;").AppendLine();
contents.AppendFormat(" if (!method) {{ method = {1}::TypeInitializer.GetClass()->GetMethod(\"Internal_{0}_Invoke\", {2}); CHECK(method); }}", eventInfo.Name, classTypeNameNative, paramsCount).AppendLine();
contents.Append(" MObject* exception = nullptr;").AppendLine();
if (paramsCount == 0)
contents.AppendLine(" void** params = nullptr;");