Merge remote-tracking branch 'origin/1.10' into sdl_platform_1.10

This commit is contained in:
2025-03-13 18:43:06 +02:00
42 changed files with 919 additions and 134 deletions

View File

@@ -213,38 +213,59 @@ bool DeployDataStep::Perform(CookingData& data)
}
FileSystem::NormalizePath(srcDotnet);
LOG(Info, "Using .NET Runtime {} at {}", TEXT("Host"), srcDotnet);
const bool srcDotnetFromEngine = srcDotnet.Contains(TEXT("Source/Platforms"));
String packFolder = srcDotnet / TEXT("../../../");
// Get major Version
Array<String> pathParts;
srcDotnet.Split('/', pathParts);
// Get .NET runtime version
String version;
for (int i = 0; i < pathParts.Count(); i++)
{
if (pathParts[i] == TEXT("runtimes"))
// Detect from path provided by build tool
Array<String> pathParts;
srcDotnet.Split('/', pathParts);
for (int32 i = 1; i < pathParts.Count(); i++)
{
Array<String> versionParts;
pathParts[i - 1].Split('.', versionParts);
if (!versionParts.IsEmpty())
if (pathParts[i] == TEXT("runtimes"))
{
const String majorVersion = versionParts[0].TrimTrailing();
int32 versionNum;
StringUtils::Parse(*majorVersion, majorVersion.Length(), &versionNum);
if (Math::IsInRange(versionNum, GAME_BUILD_DOTNET_RUNTIME_MIN_VER, GAME_BUILD_DOTNET_RUNTIME_MAX_VER)) // Check for major part
version = majorVersion;
Array<String> versionParts;
pathParts[i - 1].Split('.', versionParts);
if (!versionParts.IsEmpty())
{
const String majorVersion = versionParts[0].TrimTrailing();
int32 versionNum;
StringUtils::Parse(*majorVersion, majorVersion.Length(), &versionNum);
if (Math::IsInRange(versionNum, GAME_BUILD_DOTNET_RUNTIME_MIN_VER, GAME_BUILD_DOTNET_RUNTIME_MAX_VER)) // Check for major part
version = majorVersion;
}
}
}
if (version.IsEmpty())
{
if (srcDotnetFromEngine)
{
// Detect version from runtime files inside Engine Platform folder
for (int32 i = GAME_BUILD_DOTNET_RUNTIME_MAX_VER; i >= GAME_BUILD_DOTNET_RUNTIME_MIN_VER; i--)
{
// Check runtime files inside Engine Platform folder
String testPath1 = srcDotnet / String::Format(TEXT("lib/net{}.0"), i);
String testPath2 = packFolder / String::Format(TEXT("Dotnet/lib/net{}.0"), i);
if ((FileSystem::DirectoryExists(testPath1) && FileSystem::GetDirectorySize(testPath1) > 0) ||
(FileSystem::DirectoryExists(testPath2) && FileSystem::GetDirectorySize(testPath2) > 0))
{
version = StringUtils::ToString(i);
break;
}
}
}
if (version.IsEmpty())
{
data.Error(TEXT("Failed to find supported .NET version for the current host platform."));
return true;
}
}
}
if (version.IsEmpty())
{
data.Error(TEXT("Failed to find supported .NET version for the current host platform."));
return true;
}
// Deploy runtime files
const Char* corlibPrivateName = TEXT("System.Private.CoreLib.dll");
const bool srcDotnetFromEngine = srcDotnet.Contains(TEXT("Source/Platforms"));
String packFolder = srcDotnet / TEXT("../../../");
String dstDotnetLibs = dstDotnet, srcDotnetLibs = srcDotnet;
StringUtils::PathRemoveRelativeParts(packFolder);
if (usAOT)

View File

@@ -95,7 +95,7 @@ namespace FlaxEditor.CustomEditors.Editors
if (assetReference != null)
{
if (assetReference.UseSmallPicker)
height = 32;
height = 36;
if (string.IsNullOrEmpty(assetReference.TypeName))
{
}

View File

@@ -204,7 +204,7 @@ namespace FlaxEditor.CustomEditors.Editors
var frameRect = new Rectangle(0, 0, Width, 16);
if (isSelected)
frameRect.Width -= 16;
if (_supportsPickDropDown)
if (_supportsPickDropDown && isEnabled)
frameRect.Width -= 16;
var nameRect = new Rectangle(2, 1, frameRect.Width - 4, 14);
var button1Rect = new Rectangle(nameRect.Right + 2, 1, 14, 14);
@@ -240,7 +240,7 @@ namespace FlaxEditor.CustomEditors.Editors
}
// Draw picker button
if (_supportsPickDropDown)
if (_supportsPickDropDown && isEnabled)
{
var pickerRect = isSelected ? button2Rect : button1Rect;
Render2D.DrawSprite(style.ArrowDown, pickerRect, isEnabled && pickerRect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);

View File

@@ -36,7 +36,7 @@ namespace FlaxEditor.CustomEditors.Editors
float height = 48;
if (assetReference.UseSmallPicker)
height = 32;
height = 36;
if (string.IsNullOrEmpty(assetReference.TypeName))
{

View File

@@ -35,12 +35,12 @@ namespace FlaxEditor.Gizmo
/// <summary>
/// The inner minimum of the multiscale
/// </summary>
private const float InnerExtend = AxisOffset + 0.5f;
private const float InnerExtend = AxisOffset;
/// <summary>
/// The outer maximum of the multiscale
/// </summary>
private const float OuterExtend = AxisOffset * 3.5f;
private const float OuterExtend = AxisOffset + 1.25f;
// Cube with the size AxisThickness, then moves it along the axis (AxisThickness) and finally makes it really long (AxisLength)
private BoundingBox XAxisBox = new BoundingBox(new Vector3(-AxisThickness), new Vector3(AxisThickness)).MakeOffsetted(AxisOffset * Vector3.UnitX).Merge(AxisLength * Vector3.UnitX);
@@ -75,6 +75,11 @@ namespace FlaxEditor.Gizmo
/// </summary>
public bool ScaleSnapEnabled = false;
/// <summary>
/// True if enable absolute grid snapping (snaps objects to world-space grid, not the one relative to gizmo location)
/// </summary>
public bool AbsoluteSnapEnabled = false;
/// <summary>
/// Translation snap value
/// </summary>

View File

@@ -2,8 +2,10 @@
#if USE_LARGE_WORLDS
using Real = System.Double;
using Mathr = FlaxEngine.Mathd;
#else
using Real = System.Single;
using Mathr = FlaxEngine.Mathf;
#endif
using System;
@@ -40,8 +42,10 @@ namespace FlaxEditor.Gizmo
private Vector3 _intersectPosition;
private bool _isActive;
private bool _isDuplicating;
private bool _hasAbsoluteSnapped;
private bool _isTransforming;
private bool _isSelected;
private Vector3 _lastIntersectionPosition;
private Quaternion _rotationDelta = Quaternion.Identity;
@@ -269,7 +273,17 @@ namespace FlaxEditor.Gizmo
_intersectPosition = ray.GetPoint(intersection);
if (!_lastIntersectionPosition.IsZero)
_tDelta = _intersectPosition - _lastIntersectionPosition;
delta = new Vector3(0, _tDelta.Y, _tDelta.Z);
if (isScaling)
{
var tDeltaAbs = Vector3.Abs(_tDelta);
var maxDelta = Math.Max(tDeltaAbs.Y, tDeltaAbs.Z);
var sign = Math.Sign(tDeltaAbs.Y > tDeltaAbs.Z ? _tDelta.Y : _tDelta.Z);
delta = new Vector3(0, maxDelta * sign, maxDelta * sign);
}
else
{
delta = new Vector3(0, _tDelta.Y, _tDelta.Z);
}
}
break;
}
@@ -280,7 +294,17 @@ namespace FlaxEditor.Gizmo
_intersectPosition = ray.GetPoint(intersection);
if (!_lastIntersectionPosition.IsZero)
_tDelta = _intersectPosition - _lastIntersectionPosition;
delta = new Vector3(_tDelta.X, _tDelta.Y, 0);
if (isScaling)
{
var tDeltaAbs = Vector3.Abs(_tDelta);
var maxDelta = Math.Max(tDeltaAbs.X, tDeltaAbs.Y);
var sign = Math.Sign(tDeltaAbs.X > tDeltaAbs.Y ? _tDelta.X : _tDelta.Y);
delta = new Vector3(maxDelta * sign, maxDelta * sign, 0);
}
else
{
delta = new Vector3(_tDelta.X, _tDelta.Y, 0);
}
}
break;
}
@@ -291,7 +315,17 @@ namespace FlaxEditor.Gizmo
_intersectPosition = ray.GetPoint(intersection);
if (!_lastIntersectionPosition.IsZero)
_tDelta = _intersectPosition - _lastIntersectionPosition;
delta = new Vector3(_tDelta.X, 0, _tDelta.Z);
if (isScaling)
{
var tDeltaAbs = Vector3.Abs(_tDelta);
var maxDelta = Math.Max(tDeltaAbs.X, tDeltaAbs.Z);
var sign = Math.Sign(tDeltaAbs.X > tDeltaAbs.Z ? _tDelta.X : _tDelta.Z);
delta = new Vector3(maxDelta * sign, 0, maxDelta * sign);
}
else
{
delta = new Vector3(_tDelta.X, 0, _tDelta.Z);
}
}
break;
}
@@ -305,7 +339,24 @@ namespace FlaxEditor.Gizmo
if (!_lastIntersectionPosition.IsZero)
_tDelta = _intersectPosition - _lastIntersectionPosition;
}
delta = _tDelta;
if (isScaling)
{
var tDeltaAbs = Vector3.Abs(_tDelta);
var maxDelta = Math.Max(tDeltaAbs.X, tDeltaAbs.Y);
maxDelta = Math.Max(maxDelta, tDeltaAbs.Z);
Real sign = 0;
if (Mathf.NearEqual(maxDelta, tDeltaAbs.X))
sign = Math.Sign(_tDelta.X);
else if (Mathf.NearEqual(maxDelta, tDeltaAbs.Y))
sign = Math.Sign(_tDelta.Y);
else if (Mathf.NearEqual(maxDelta, tDeltaAbs.Z))
sign = Math.Sign(_tDelta.Z);
delta = new Vector3(maxDelta * sign);
}
else
{
delta = _tDelta;
}
break;
}
}
@@ -318,6 +369,7 @@ namespace FlaxEditor.Gizmo
if ((isScaling ? ScaleSnapEnabled : TranslationSnapEnable) || Owner.UseSnapping)
{
var snapValue = new Vector3(isScaling ? ScaleSnapValue : TranslationSnapValue);
_translationScaleSnapDelta += delta;
if (!isScaling && snapValue.X < 0.0f)
{
@@ -336,11 +388,29 @@ namespace FlaxEditor.Gizmo
else
snapValue.Z = (Real)b.Minimum.Z - b.Maximum.Z;
}
Vector3 absoluteDelta = Vector3.Zero;
if (!_hasAbsoluteSnapped && AbsoluteSnapEnabled && ActiveTransformSpace == TransformSpace.World)
{
// Remove delta to offset local-space grid into the world-space grid
_hasAbsoluteSnapped = true;
Vector3 currentTranslationScale = isScaling ? GetSelectedTransform(0).Scale : GetSelectedTransform(0).Translation;
absoluteDelta = currentTranslationScale - new Vector3(
Mathr.Round(currentTranslationScale.X / snapValue.X) * snapValue.X,
Mathr.Round(currentTranslationScale.Y / snapValue.Y) * snapValue.Y,
Mathr.Round(currentTranslationScale.Z / snapValue.Z) * snapValue.Z);
}
delta = new Vector3(
(int)(_translationScaleSnapDelta.X / snapValue.X) * snapValue.X,
(int)(_translationScaleSnapDelta.Y / snapValue.Y) * snapValue.Y,
(int)(_translationScaleSnapDelta.Z / snapValue.Z) * snapValue.Z);
_translationScaleSnapDelta -= delta;
delta -= absoluteDelta;
}
else
{
_hasAbsoluteSnapped = false;
}
if (_activeMode == Mode.Translate)
@@ -370,12 +440,33 @@ namespace FlaxEditor.Gizmo
if (RotationSnapEnabled || Owner.UseSnapping)
{
float snapValue = RotationSnapValue * Mathf.DegreesToRadians;
float absoluteDelta = 0.0f;
if (!_hasAbsoluteSnapped && AbsoluteSnapEnabled && ActiveTransformSpace == TransformSpace.World)
{
// Remove delta to offset world-space grid into the local-space grid
_hasAbsoluteSnapped = true;
float currentAngle = 0.0f;
switch (_activeAxis)
{
case Axis.X: currentAngle = GetSelectedTransform(0).Orientation.EulerAngles.X; break;
case Axis.Y: currentAngle = GetSelectedTransform(0).Orientation.EulerAngles.Y; break;
case Axis.Z: currentAngle = GetSelectedTransform(0).Orientation.EulerAngles.Z; break;
}
absoluteDelta = currentAngle - (Mathf.Round(currentAngle / RotationSnapValue) * RotationSnapValue);
}
_rotationSnapDelta += delta;
float snapped = Mathf.Round(_rotationSnapDelta / snapValue) * snapValue;
_rotationSnapDelta -= snapped;
delta = snapped;
delta -= absoluteDelta * Mathf.DegreesToRadians;
}
else
{
_hasAbsoluteSnapped = false;
}
switch (_activeAxis)
@@ -408,7 +499,7 @@ namespace FlaxEditor.Gizmo
}
/// <inheritdoc />
public override bool IsControllingMouse => _isTransforming;
public override bool IsControllingMouse => _isTransforming || _isSelected;
/// <inheritdoc />
public override void Update(float dt)
@@ -433,6 +524,7 @@ namespace FlaxEditor.Gizmo
// Check if user is holding left mouse button and any axis is selected
if (isLeftBtnDown && _activeAxis != Axis.None)
{
_isSelected = true; // setting later is too late, need to set here for rubber band selection in GizmoViewport
switch (_activeMode)
{
case Mode.Translate:
@@ -450,6 +542,7 @@ namespace FlaxEditor.Gizmo
}
else
{
_isSelected = false;
// If nothing selected, try to select any axis
if (!isLeftBtnDown && !Owner.IsRightMouseButtonDown)
{
@@ -517,6 +610,7 @@ namespace FlaxEditor.Gizmo
// Clear cache
_accMoveDelta = Vector3.Zero;
_lastIntersectionPosition = _intersectPosition = Vector3.Zero;
_isSelected = false;
EndTransforming();
}
}

View File

@@ -0,0 +1,277 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using FlaxEditor.Gizmo;
using FlaxEditor.SceneGraph;
using FlaxEditor.Viewport;
using FlaxEngine.GUI;
namespace FlaxEngine.Gizmo;
/// <summary>
/// Class for adding viewport rubber band selection.
/// </summary>
public class ViewportRubberBandSelector
{
private bool _isMosueCaptured;
private bool _isRubberBandSpanning;
private bool _tryStartRubberBand;
private Float2 _cachedStartingMousePosition;
private Rectangle _rubberBandRect;
private Rectangle _lastRubberBandRect;
private List<ActorNode> _nodesCache;
private List<SceneGraphNode> _hitsCache;
private IGizmoOwner _owner;
/// <summary>
/// Constructs a rubber band selector with a designated gizmo owner.
/// </summary>
/// <param name="owner">The gizmo owner.</param>
public ViewportRubberBandSelector(IGizmoOwner owner)
{
_owner = owner;
}
/// <summary>
/// Triggers the start of a rubber band selection.
/// </summary>
/// <returns>True if selection started, otherwise false.</returns>
public bool TryStartingRubberBandSelection()
{
if (!_isRubberBandSpanning && !_owner.Gizmos.Active.IsControllingMouse && !_owner.IsRightMouseButtonDown)
{
_tryStartRubberBand = true;
return true;
}
return false;
}
/// <summary>
/// Release the rubber band selection.
/// </summary>
/// <returns>Returns true if rubber band is currently spanning</returns>
public bool ReleaseRubberBandSelection()
{
if (_isMosueCaptured)
{
_isMosueCaptured = false;
_owner.Viewport.EndMouseCapture();
}
if (_tryStartRubberBand)
{
_tryStartRubberBand = false;
}
if (_isRubberBandSpanning)
{
_isRubberBandSpanning = false;
return true;
}
return false;
}
/// <summary>
/// Tries to create a rubber band selection.
/// </summary>
/// <param name="canStart">Whether the creation can start.</param>
/// <param name="mousePosition">The current mouse position.</param>
/// <param name="viewFrustum">The view frustum.</param>
public void TryCreateRubberBand(bool canStart, Float2 mousePosition, BoundingFrustum viewFrustum)
{
if (_isRubberBandSpanning && !canStart)
{
_isRubberBandSpanning = false;
return;
}
if (_tryStartRubberBand && (Mathf.Abs(_owner.MouseDelta.X) > 0.1f || Mathf.Abs(_owner.MouseDelta.Y) > 0.1f) && canStart)
{
_isRubberBandSpanning = true;
_cachedStartingMousePosition = mousePosition;
_rubberBandRect = new Rectangle(_cachedStartingMousePosition, Float2.Zero);
_tryStartRubberBand = false;
}
else if (_isRubberBandSpanning && !_owner.Gizmos.Active.IsControllingMouse && !_owner.IsRightMouseButtonDown)
{
_rubberBandRect.Width = mousePosition.X - _cachedStartingMousePosition.X;
_rubberBandRect.Height = mousePosition.Y - _cachedStartingMousePosition.Y;
if (_lastRubberBandRect != _rubberBandRect)
{
if (!_isMosueCaptured)
{
_isMosueCaptured = true;
_owner.Viewport.StartMouseCapture();
}
UpdateRubberBand(ref viewFrustum);
}
}
}
private struct ViewportProjection
{
private Viewport _viewport;
private Matrix _viewProjection;
public void Init(EditorViewport editorViewport)
{
// Inline EditorViewport.ProjectPoint to save on calculation for large set of points
_viewport = new Viewport(0, 0, editorViewport.Width, editorViewport.Height);
var frustum = editorViewport.ViewFrustum;
_viewProjection = frustum.Matrix;
}
public void ProjectPoint(ref Vector3 worldSpaceLocation, out Float2 viewportSpaceLocation)
{
_viewport.Project(ref worldSpaceLocation, ref _viewProjection, out var projected);
viewportSpaceLocation = new Float2((float)projected.X, (float)projected.Y);
}
}
private void UpdateRubberBand(ref BoundingFrustum viewFrustum)
{
Profiler.BeginEvent("UpdateRubberBand");
// Select rubberbanded rect actor nodes
var adjustedRect = _rubberBandRect;
_lastRubberBandRect = _rubberBandRect;
if (adjustedRect.Width < 0 || adjustedRect.Height < 0)
{
// Make sure we have a well-formed rectangle i.e. size is positive and X/Y is upper left corner
var size = adjustedRect.Size;
adjustedRect.X = Mathf.Min(adjustedRect.X, adjustedRect.X + adjustedRect.Width);
adjustedRect.Y = Mathf.Min(adjustedRect.Y, adjustedRect.Y + adjustedRect.Height);
size.X = Mathf.Abs(size.X);
size.Y = Mathf.Abs(size.Y);
adjustedRect.Size = size;
}
// Get hits from graph nodes
if (_nodesCache == null)
_nodesCache = new List<ActorNode>();
else
_nodesCache.Clear();
var nodes = _nodesCache;
_owner.SceneGraphRoot.GetAllChildActorNodes(nodes);
if (_hitsCache == null)
_hitsCache = new List<SceneGraphNode>();
else
_hitsCache.Clear();
var hits = _hitsCache;
// Process all nodes
var projection = new ViewportProjection();
projection.Init(_owner.Viewport);
foreach (var node in nodes)
{
// Check for custom can select code
if (!node.CanSelectActorNodeWithSelector())
continue;
var a = node.Actor;
// Skip actor if outside of view frustum
var actorBox = a.EditorBox;
if (viewFrustum.Contains(actorBox) == ContainmentType.Disjoint)
continue;
// Get valid selection points
var points = node.GetActorSelectionPoints();
if (LoopOverPoints(points, ref adjustedRect, ref projection))
{
if (a.HasPrefabLink && _owner is not PrefabWindowViewport)
hits.Add(_owner.SceneGraphRoot.Find(a.GetPrefabRoot()));
else
hits.Add(node);
}
}
// Process selection
if (_owner.IsControlDown)
{
var newSelection = new List<SceneGraphNode>();
var currentSelection = new List<SceneGraphNode>(_owner.SceneGraphRoot.SceneContext.Selection);
newSelection.AddRange(currentSelection);
foreach (var hit in hits)
{
if (currentSelection.Contains(hit))
newSelection.Remove(hit);
else
newSelection.Add(hit);
}
_owner.Select(newSelection);
}
else if (Input.GetKey(KeyboardKeys.Shift))
{
var newSelection = new List<SceneGraphNode>();
var currentSelection = new List<SceneGraphNode>(_owner.SceneGraphRoot.SceneContext.Selection);
newSelection.AddRange(hits);
newSelection.AddRange(currentSelection);
_owner.Select(newSelection);
}
else
{
_owner.Select(hits);
}
Profiler.EndEvent();
}
private bool LoopOverPoints(Vector3[] points, ref Rectangle adjustedRect, ref ViewportProjection projection)
{
Profiler.BeginEvent("LoopOverPoints");
bool containsAllPoints = points.Length != 0;
for (int i = 0; i < points.Length; i++)
{
projection.ProjectPoint(ref points[i], out var loc);
if (!adjustedRect.Contains(loc))
{
containsAllPoints = false;
break;
}
}
Profiler.EndEvent();
return containsAllPoints;
}
/// <summary>
/// Used to draw the rubber band. Begins render 2D.
/// </summary>
/// <param name="context">The GPU Context.</param>
/// <param name="target">The GPU texture target.</param>
/// <param name="targetDepth">The GPU texture target depth.</param>
public void Draw(GPUContext context, GPUTexture target, GPUTexture targetDepth)
{
// Draw RubberBand for rect selection
if (!_isRubberBandSpanning)
return;
Render2D.Begin(context, target, targetDepth);
Draw2D();
Render2D.End();
}
/// <summary>
/// Used to draw the rubber band. Use if already rendering 2D context.
/// </summary>
public void Draw2D()
{
if (!_isRubberBandSpanning)
return;
Render2D.FillRectangle(_rubberBandRect, Style.Current.Selection);
Render2D.DrawRectangle(_rubberBandRect, Style.Current.SelectionBorder);
}
/// <summary>
/// Immediately stops the rubber band.
/// </summary>
/// <returns>True if rubber band was active before stopping.</returns>
public bool StopRubberBand()
{
if (_isMosueCaptured)
{
_isMosueCaptured = false;
_owner.Viewport.EndMouseCapture();
}
var result = _tryStartRubberBand;
_isRubberBandSpanning = false;
_tryStartRubberBand = false;
return result;
}
}

View File

@@ -606,6 +606,11 @@ void ManagedEditor::WipeOutLeftoverSceneObjects()
{
if (sceneObject->HasParent())
continue; // Skip sub-objects
auto* actor = Cast<Actor>(sceneObject);
if (!actor)
actor = sceneObject->GetParent();
if (actor && actor->HasTag(TEXT("__EditorInternal")))
continue; // Skip internal objects used by Editor (eg. EditorScene)
LOG(Error, "Object '{}' (ID={}, Type={}) is still in memory after play end but should be destroyed (memory leak).", sceneObject->GetNamePath(), sceneObject->GetID(), sceneObject->GetType().ToString());
sceneObject->DeleteObject();

View File

@@ -182,6 +182,54 @@ namespace FlaxEditor.SceneGraph
return null;
}
/// <summary>
/// Get all nested actor nodes under this actor node.
/// </summary>
/// <returns>An array of ActorNodes</returns>
public ActorNode[] GetAllChildActorNodes()
{
var nodes = new List<ActorNode>();
GetAllChildActorNodes(nodes);
return nodes.ToArray();
}
/// <summary>
/// Get all nested actor nodes under this actor node.
/// </summary>
/// <param name="nodes">The output list to fill with results.</param>
public void GetAllChildActorNodes(List<ActorNode> nodes)
{
var children = ChildNodes;
if (children == null)
return;
for (int i = 0; i < children.Count; i++)
{
if (children[i] is ActorNode node)
{
nodes.Add(node);
node.GetAllChildActorNodes(nodes);
}
}
}
/// <summary>
/// Whether an actor node can be selected with a selector.
/// </summary>
/// <returns>True if the actor node can be selected</returns>
public virtual bool CanSelectActorNodeWithSelector()
{
return Actor && Actor.HideFlags is not (HideFlags.DontSelect or HideFlags.FullyHidden) && Actor is not EmptyActor && IsActive;
}
/// <summary>
/// The selection points used to check if an actor node can be selected.
/// </summary>
/// <returns>The points to use if the actor can be selected.</returns>
public virtual Vector3[] GetActorSelectionPoints()
{
return Actor.EditorBox.GetCorners();
}
/// <summary>
/// Gets a value indicating whether this actor can be used to create prefab from it (as a root).
/// </summary>

View File

@@ -58,5 +58,11 @@ namespace FlaxEditor.SceneGraph.Actors
return Camera.Internal_IntersectsItselfEditor(FlaxEngine.Object.GetUnmanagedPtr(_actor), ref ray.Ray, out distance);
}
/// <inheritdoc />
public override Vector3[] GetActorSelectionPoints()
{
return [Actor.Position];
}
}
}

View File

@@ -33,6 +33,12 @@ namespace FlaxEditor.SceneGraph.Actors
}
}
/// <inheritdoc />
public override bool CanSelectActorNodeWithSelector()
{
return false;
}
/// <summary>
/// Gets the scene.
/// </summary>

View File

@@ -24,6 +24,9 @@ namespace FlaxEditor.SceneGraph.Actors
public sealed class StaticModelNode : ActorNode
{
private Dictionary<IntPtr, Float3[]> _vertices;
private Vector3[] _selectionPoints;
private Transform _selectionPointsTransform;
private Model _selectionPointsModel;
/// <inheritdoc />
public StaticModelNode(Actor actor)
@@ -31,6 +34,16 @@ namespace FlaxEditor.SceneGraph.Actors
{
}
/// <inheritdoc />
public override void OnDispose()
{
_vertices = null;
_selectionPoints = null;
_selectionPointsModel = null;
base.OnDispose();
}
/// <inheritdoc />
public override bool OnVertexSnap(ref Ray ray, Real hitDistance, out Vector3 result)
{
@@ -91,6 +104,45 @@ namespace FlaxEditor.SceneGraph.Actors
contextMenu.AddButton("Add collider", () => OnAddMeshCollider(window)).Enabled = ((StaticModel)Actor).Model != null;
}
/// <inheritdoc />
public override Vector3[] GetActorSelectionPoints()
{
if (Actor is StaticModel sm && sm.Model)
{
// Try to use cache
var model = sm.Model;
var transform = Actor.Transform;
if (_selectionPoints != null &&
_selectionPointsTransform == transform &&
_selectionPointsModel == model)
return _selectionPoints;
Profiler.BeginEvent("GetActorSelectionPoints");
// Check collision proxy points for more accurate selection
var vecPoints = new List<Vector3>();
var m = model.LODs[0];
foreach (var mesh in m.Meshes)
{
var points = mesh.GetCollisionProxyPoints();
vecPoints.EnsureCapacity(vecPoints.Count + points.Length);
for (int i = 0; i < points.Length; i++)
{
vecPoints.Add(transform.LocalToWorld(points[i]));
}
}
Profiler.EndEvent();
if (vecPoints.Count != 0)
{
_selectionPoints = vecPoints.ToArray();
_selectionPointsTransform = transform;
_selectionPointsModel = model;
return _selectionPoints;
}
}
return base.GetActorSelectionPoints();
}
private void OnAddMeshCollider(EditorWindow window)
{
// Allow collider to be added to evey static model selection

View File

@@ -78,5 +78,11 @@ namespace FlaxEditor.SceneGraph.Actors
if (Actor is UICanvas uiCanvas && uiCanvas.Is3D)
DebugDraw.DrawWireBox(uiCanvas.Bounds, Color.BlueViolet);
}
/// <inheritdoc />
public override bool CanSelectActorNodeWithSelector()
{
return Actor is UICanvas uiCanvas && uiCanvas.Is3D && base.CanSelectActorNodeWithSelector();
}
}
}

View File

@@ -40,5 +40,31 @@ namespace FlaxEditor.SceneGraph.Actors
control.PerformLayout();
}
}
/// <inheritdoc />
public override bool CanSelectActorNodeWithSelector()
{
// Check if control and skip if canvas is 2D
if (Actor is not UIControl uiControl)
return false;
UICanvas canvas = null;
var controlParent = uiControl.Parent;
while (controlParent != null && controlParent is not Scene)
{
if (controlParent is UICanvas uiCanvas)
{
canvas = uiCanvas;
break;
}
controlParent = controlParent.Parent;
}
if (canvas != null)
{
if (canvas.Is2D)
return false;
}
return base.CanSelectActorNodeWithSelector();
}
}
}

View File

@@ -166,7 +166,7 @@ namespace FlaxEditor.SceneGraph.GUI
/// <param name="filterText">The filter text.</param>
public void UpdateFilter(string filterText)
{
// SKip hidden actors
// Skip hidden actors
var actor = Actor;
if (actor != null && (actor.HideFlags & HideFlags.HideInHierarchy) != 0)
return;
@@ -238,7 +238,7 @@ namespace FlaxEditor.SceneGraph.GUI
}
else
{
if (Actor !=null)
if (Actor != null)
{
var actorTypeText = trimmedFilter.Replace("a:", "", StringComparison.OrdinalIgnoreCase).Trim();
var name = TypeUtils.GetTypeDisplayName(Actor.GetType());
@@ -248,6 +248,26 @@ namespace FlaxEditor.SceneGraph.GUI
}
}
}
// Check for control type
else if (trimmedFilter.Contains("c:", StringComparison.OrdinalIgnoreCase))
{
if (trimmedFilter.Equals("c:", StringComparison.OrdinalIgnoreCase))
{
if (Actor != null)
hasFilter = true;
}
else
{
if (Actor != null && Actor is UIControl uic && uic.Control != null)
{
var controlTypeText = trimmedFilter.Replace("c:", "", StringComparison.OrdinalIgnoreCase).Trim();
var name = TypeUtils.GetTypeDisplayName(uic.Control.GetType());
var nameNoSpaces = name.Replace(" ", "");
if (name.Contains(controlTypeText, StringComparison.OrdinalIgnoreCase) || nameNoSpaces.Contains(controlTypeText, StringComparison.OrdinalIgnoreCase))
hasFilter = true;
}
}
}
// Match text
else
{

View File

@@ -419,8 +419,8 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(0, "Texture", true, typeof(FlaxEngine.Object), 0),
NodeElementArchetype.Factory.Input(1, "Scale", true, typeof(float), 1, 0),
NodeElementArchetype.Factory.Input(2, "Blend", true, typeof(float), 2, 1),
NodeElementArchetype.Factory.Input(3, "Offset", true, typeof(Float2), 3, 2),
NodeElementArchetype.Factory.Output(0, "Color", typeof(Float4), 5),
NodeElementArchetype.Factory.Input(3, "Offset", true, typeof(Float2), 6, 2),
NodeElementArchetype.Factory.Output(0, "Color", typeof(Float4), 3),
NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 4, "Sampler"),
NodeElementArchetype.Factory.ComboBox(50, Surface.Constants.LayoutOffsetY * 4 - 1, 100, 3, typeof(CommonSamplerType)),
NodeElementArchetype.Factory.Text(155, Surface.Constants.LayoutOffsetY * 4, "Local"),
@@ -485,8 +485,8 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(0, "Texture", true, typeof(FlaxEngine.Object), 0),
NodeElementArchetype.Factory.Input(1, "Scale", true, typeof(float), 1, 0),
NodeElementArchetype.Factory.Input(2, "Blend", true, typeof(float), 2, 1),
NodeElementArchetype.Factory.Input(3, "Offset", true, typeof(Float2), 3, 2),
NodeElementArchetype.Factory.Output(0, "Vector", typeof(Float3), 5),
NodeElementArchetype.Factory.Input(3, "Offset", true, typeof(Float2), 6, 2),
NodeElementArchetype.Factory.Output(0, "Vector", typeof(Float3), 3),
NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 4, "Sampler"),
NodeElementArchetype.Factory.ComboBox(50, Surface.Constants.LayoutOffsetY * 4 - 1, 100, 3, typeof(CommonSamplerType)),
NodeElementArchetype.Factory.Text(155, Surface.Constants.LayoutOffsetY * 4, "Local"),

View File

@@ -10,6 +10,9 @@ EditorScene::EditorScene(const SpawnParams& params)
SceneBeginData beginData;
EditorScene::BeginPlay(&beginData);
beginData.OnDone();
// Mark as internal to prevent collection in ManagedEditor::WipeOutLeftoverSceneObjects
Tags.Add(Tags::Get(TEXT("__EditorInternal")));
}
void EditorScene::Update()

View File

@@ -138,7 +138,9 @@ namespace FlaxEditor.Viewport
if (useProjectCache)
{
// Initialize snapping enabled from cached values
if (editor.ProjectCache.TryGetCustomData("TranslateSnapState", out bool cachedBool))
if (editor.ProjectCache.TryGetCustomData("AbsoluteSnapState", out bool cachedBool))
transformGizmo.AbsoluteSnapEnabled = cachedBool;
if (editor.ProjectCache.TryGetCustomData("TranslateSnapState", out cachedBool))
transformGizmo.TranslationSnapEnable = cachedBool;
if (editor.ProjectCache.TryGetCustomData("RotationSnapState", out cachedBool))
transformGizmo.RotationSnapEnabled = cachedBool;
@@ -162,13 +164,31 @@ namespace FlaxEditor.Viewport
TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})",
Parent = transformSpaceWidget
};
transformSpaceWidget.Parent = viewport;
// Absolute snapping widget
var absoluteSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var enableAbsoluteSnapping = new ViewportWidgetButton("A", SpriteHandle.Invalid, null, true)
{
Checked = transformGizmo.AbsoluteSnapEnabled,
TooltipText = "Enable absolute grid snapping (world-space absolute grid, rather than object-relative grid)",
Parent = absoluteSnappingWidget
};
enableAbsoluteSnapping.Toggled += _ =>
{
transformGizmo.AbsoluteSnapEnabled = !transformGizmo.AbsoluteSnapEnabled;
if (useProjectCache)
editor.ProjectCache.SetCustomData("AbsoluteSnapState", transformGizmo.AbsoluteSnapEnabled);
};
absoluteSnappingWidget.Parent = viewport;
transformSpaceToggle.Toggled += _ =>
{
transformGizmo.ToggleTransformSpace();
if (useProjectCache)
editor.ProjectCache.SetCustomData("TransformSpaceState", transformGizmo.ActiveTransformSpace.ToString());
absoluteSnappingWidget.Visible = transformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World;
};
transformSpaceWidget.Parent = viewport;
// Scale snapping widget
var scaleSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
@@ -383,17 +403,17 @@ namespace FlaxEditor.Viewport
gizmoModeRotate.Checked = mode == TransformGizmoBase.Mode.Rotate;
gizmoModeScale.Checked = mode == TransformGizmoBase.Mode.Scale;
};
// Setup input actions
viewport.InputActions.Add(options => options.TranslateMode, () =>
viewport.InputActions.Add(options => options.TranslateMode, () =>
{
viewport.GetInput(out var input);
if (input.IsMouseRightDown)
return;
transformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate;
});
viewport.InputActions.Add(options => options.RotateMode, () =>
viewport.InputActions.Add(options => options.RotateMode, () =>
{
viewport.GetInput(out var input);
if (input.IsMouseRightDown)

View File

@@ -7,10 +7,14 @@ using FlaxEditor.Gizmo;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
using FlaxEditor.Tools;
using FlaxEditor.Viewport.Modes;
using FlaxEditor.Windows;
using FlaxEngine;
using FlaxEngine.Gizmo;
using FlaxEngine.GUI;
using FlaxEngine.Tools;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Viewport
@@ -107,6 +111,7 @@ namespace FlaxEditor.Viewport
private double _lockedFocusOffset;
private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32);
private EditorSpritesRenderer _editorSpritesRenderer;
private ViewportRubberBandSelector _rubberBandSelector;
/// <summary>
/// Drag and drop handlers
@@ -213,6 +218,9 @@ namespace FlaxEditor.Viewport
TransformGizmo.ApplyTransformation += ApplyTransform;
TransformGizmo.Duplicate += _editor.SceneEditing.Duplicate;
Gizmos.Active = TransformGizmo;
// Add rubber band selector
_rubberBandSelector = new ViewportRubberBandSelector(this);
// Add grid
Grid = new GridGizmo(this);
@@ -367,7 +375,10 @@ namespace FlaxEditor.Viewport
{
Gizmos[i].Draw(ref renderContext);
}
// Draw RubberBand for rect selection
_rubberBandSelector.Draw(context, target, targetDepth);
// Draw selected objects debug shapes and visuals
if (DrawDebugDraw && (renderContext.View.Flags & ViewFlags.DebugDraw) == ViewFlags.DebugDraw)
{
@@ -478,6 +489,22 @@ namespace FlaxEditor.Viewport
TransformGizmo.EndTransforming();
}
/// <inheritdoc />
public override void OnLostFocus()
{
base.OnLostFocus();
_rubberBandSelector.StopRubberBand();
}
/// <inheritdoc />
public override void OnMouseLeave()
{
base.OnMouseLeave();
_rubberBandSelector.StopRubberBand();
}
/// <summary>
/// Focuses the viewport on the current selection of the gizmo.
/// </summary>
@@ -576,6 +603,24 @@ namespace FlaxEditor.Viewport
base.OrientViewport(ref orientation);
}
/// <inheritdoc />
public override void OnMouseMove(Float2 location)
{
base.OnMouseMove(location);
// Don't allow rubber band selection when gizmo is controlling mouse, vertex painting mode, or cloth painting is enabled
bool canStart = !((Gizmos.Active.IsControllingMouse || Gizmos.Active is VertexPaintingGizmo || Gizmos.Active is ClothPaintingGizmo) || IsControllingMouse || IsRightMouseButtonDown || IsAltKeyDown);
_rubberBandSelector.TryCreateRubberBand(canStart, _viewMousePos, ViewFrustum);
}
/// <inheritdoc />
protected override void OnLeftMouseButtonDown()
{
base.OnLeftMouseButtonDown();
_rubberBandSelector.TryStartingRubberBandSelection();
}
/// <inheritdoc />
protected override void OnLeftMouseButtonUp()
{
@@ -583,8 +628,12 @@ namespace FlaxEditor.Viewport
if (_prevInput.IsControllingMouse || !Bounds.Contains(ref _viewMousePos))
return;
// Try to pick something with the current gizmo
Gizmos.Active?.Pick();
// Select rubberbanded rect actor nodes or pick with gizmo
if (!_rubberBandSelector.ReleaseRubberBandSelection())
{
// Try to pick something with the current gizmo
Gizmos.Active?.Pick();
}
// Keep focus
Focus();
@@ -592,6 +641,22 @@ namespace FlaxEditor.Viewport
base.OnLeftMouseButtonUp();
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (base.OnMouseUp(location, button))
return true;
// Handle mouse going up when using rubber band with mouse capture that click up outside the view
if (button == MouseButton.Left && !new Rectangle(Float2.Zero, Size).Contains(ref location))
{
_rubberBandSelector.ReleaseRubberBandSelection();
return true;
}
return false;
}
/// <inheritdoc />
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
{

View File

@@ -32,6 +32,8 @@ namespace FlaxEditor.Windows.Assets
private readonly AnimationWindow _window;
private AnimationGraph _animGraph;
public SkinnedModel BaseModel;
public Preview(AnimationWindow window)
: base(true)
{
@@ -46,10 +48,11 @@ namespace FlaxEditor.Windows.Assets
Object.Destroy(ref _animGraph);
if (!model)
return;
var baseModel = BaseModel ?? model;
// Use virtual animation graph to playback the animation
_animGraph = FlaxEngine.Content.CreateVirtualAsset<AnimationGraph>();
_animGraph.InitAsAnimation(model, _window.Asset, true, true);
_animGraph.InitAsAnimation(baseModel, _window.Asset, true, true);
PreviewActor.AnimationGraph = _animGraph;
}
@@ -69,6 +72,7 @@ namespace FlaxEditor.Windows.Assets
/// <inheritdoc />
public override void OnDestroy()
{
BaseModel = null;
Object.Destroy(ref _animGraph);
base.OnDestroy();
@@ -148,6 +152,24 @@ namespace FlaxEditor.Windows.Assets
}
}
private bool ShowBaseModel => PreviewModel != null;
[EditorDisplay("Preview"), NoSerialize, AssetReference(true), VisibleIf(nameof(ShowBaseModel))]
[Tooltip("The skinned model to use as a retarget source. Animation will be played using its skeleton and retarget into the Preview Model.")]
public SkinnedModel BaseModel
{
get => Window?._preview?.BaseModel;
set
{
if (Window == null || PreviewModel == value)
return;
// Reinit
Window._preview.BaseModel = value;
Window._preview.SetModel(PreviewModel);
}
}
public void OnLoad(AnimationWindow window)
{
// Link
@@ -230,7 +252,7 @@ namespace FlaxEditor.Windows.Assets
private ToolStripButton _undoButton;
private ToolStripButton _redoButton;
private bool _isWaitingForTimelineLoad;
private SkinnedModel _initialPreviewModel;
private SkinnedModel _initialPreviewModel, _initialBaseModel;
private float _initialPanel2Splitter = 0.6f;
/// <summary>
@@ -322,7 +344,12 @@ namespace FlaxEditor.Windows.Assets
_properties.PreviewModel = _initialPreviewModel;
_panel2.SplitterValue = _initialPanel2Splitter;
_initialPreviewModel = null;
if (_initialBaseModel)
{
_properties.BaseModel = _initialBaseModel;
}
}
_initialBaseModel = null;
base.OnAssetLoaded();
}
@@ -412,6 +439,8 @@ namespace FlaxEditor.Windows.Assets
writer.WriteAttributeString("ShowPreviewValues", _timeline.ShowPreviewValues.ToString());
if (_properties.PreviewModel)
writer.WriteAttributeString("PreviewModel", _properties.PreviewModel.ID.ToString());
if (_properties.BaseModel)
writer.WriteAttributeString("BaseModel", _properties.BaseModel.ID.ToString());
}
/// <inheritdoc />
@@ -427,6 +456,8 @@ namespace FlaxEditor.Windows.Assets
_timeline.ShowPreviewValues = value3;
if (Guid.TryParse(node.GetAttribute("PreviewModel"), out Guid value4))
_initialPreviewModel = FlaxEngine.Content.LoadAsync<SkinnedModel>(value4);
if (Guid.TryParse(node.GetAttribute("BaseModel"), out value4))
_initialBaseModel = FlaxEngine.Content.LoadAsync<SkinnedModel>(value4);
}
/// <inheritdoc />

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System.IO;
using System.Xml;
using FlaxEditor.Content;
using FlaxEditor.Content.Import;

View File

@@ -21,7 +21,7 @@ namespace FlaxEditor.Windows
{
private readonly RenderOutputControl _viewport;
private readonly GameRoot _guiRoot;
private bool _showGUI = true;
private bool _showGUI = true, _editGUI = true;
private bool _showDebugDraw = false;
private bool _audioMuted = false;
private float _audioVolume = 1;
@@ -84,6 +84,22 @@ namespace FlaxEditor.Windows
}
}
/// <summary>
/// Gets or sets a value indicating whether allow editing game GUI in the view or keep it visible-only.
/// </summary>
public bool EditGUI
{
get => _editGUI;
set
{
if (value != _editGUI)
{
_editGUI = value;
_guiRoot.Editable = value;
}
}
}
/// <summary>
/// Gets or sets a value indicating whether show Debug Draw shapes in the view or keep it hidden.
/// </summary>
@@ -275,8 +291,9 @@ namespace FlaxEditor.Windows
/// </summary>
private class GameRoot : UIEditorRoot
{
public override bool EnableInputs => !Time.GamePaused && Editor.IsPlayMode;
public override bool EnableSelecting => !Editor.IsPlayMode || Time.GamePaused;
internal bool Editable = true;
public override bool EnableInputs => !Time.GamePaused && Editor.IsPlayMode && Editable;
public override bool EnableSelecting => (!Editor.IsPlayMode || Time.GamePaused) && Editable;
public override TransformGizmo TransformGizmo => Editor.Instance.MainTransformGizmo;
}
@@ -670,6 +687,13 @@ namespace FlaxEditor.Windows
checkbox.StateChanged += x => ShowGUI = x.Checked;
}
// Edit GUI
{
var button = menu.AddButton("Edit GUI");
var checkbox = new CheckBox(140, 2, EditGUI) { Parent = button };
checkbox.StateChanged += x => EditGUI = x.Checked;
}
// Show Debug Draw
{
var button = menu.AddButton("Show Debug Draw");
@@ -1196,6 +1220,7 @@ namespace FlaxEditor.Windows
public override void OnLayoutSerialize(XmlWriter writer)
{
writer.WriteAttributeString("ShowGUI", ShowGUI.ToString());
writer.WriteAttributeString("EditGUI", EditGUI.ToString());
writer.WriteAttributeString("ShowDebugDraw", ShowDebugDraw.ToString());
writer.WriteAttributeString("DefaultViewportScaling", JsonSerializer.Serialize(_defaultViewportScaling));
writer.WriteAttributeString("CustomViewportScaling", JsonSerializer.Serialize(_customViewportScaling));
@@ -1206,6 +1231,8 @@ namespace FlaxEditor.Windows
{
if (bool.TryParse(node.GetAttribute("ShowGUI"), out bool value1))
ShowGUI = value1;
if (bool.TryParse(node.GetAttribute("EditGUI"), out value1))
EditGUI = value1;
if (bool.TryParse(node.GetAttribute("ShowDebugDraw"), out value1))
ShowDebugDraw = value1;
if (node.HasAttribute("CustomViewportScaling"))
@@ -1231,6 +1258,7 @@ namespace FlaxEditor.Windows
public override void OnLayoutDeserialize()
{
ShowGUI = true;
EditGUI = true;
ShowDebugDraw = false;
}
}

View File

@@ -74,6 +74,11 @@ namespace FlaxEditor.Windows
if (Level.ScenesCount > 1)
return;
_actorScrollValues.Clear();
if (LockObjects)
{
LockObjects = false;
Presenter.Deselect();
}
}
private void OnScrollValueChanged()

View File

@@ -280,7 +280,7 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
context.EmptyNodes.Length = 0.0f;
context.EmptyNodes.Nodes.Resize(_skeletonNodesCount, false);
for (int32 i = 0; i < _skeletonNodesCount; i++)
context.EmptyNodes.Nodes[i] = skeleton.Nodes[i].LocalTransform;
context.EmptyNodes.Nodes.Get()[i] = skeleton.Nodes.Get()[i].LocalTransform;
}
// Update the animation graph and gather skeleton nodes transformations in nodes local space

View File

@@ -65,6 +65,7 @@ namespace ALC
struct SourceData
{
AudioDataInfo Format;
float Pan;
bool Spatial;
};
@@ -106,6 +107,26 @@ namespace ALC
namespace Source
{
void SetupSpatial(uint32 sourceID, float pan, bool spatial)
{
alSourcei(sourceID, AL_SOURCE_RELATIVE, !spatial); // Non-spatial sounds use AL_POSITION for panning
#ifdef AL_SOFT_source_spatialize
alSourcei(sourceID, AL_SOURCE_SPATIALIZE_SOFT, spatial || Math::Abs(pan) > ZeroTolerance ? AL_TRUE : AL_FALSE); // Fix multi-channel sources played as spatial or non-spatial sources played with panning
#endif
if (spatial)
{
#ifdef AL_EXT_STEREO_ANGLES
const float panAngle = pan * PI_HALF;
const ALfloat panAngles[2] = { (ALfloat)(PI / 6.0 - panAngle), (ALfloat)(-PI / 6.0 - panAngle) }; // Angles are specified counter-clockwise in radians
alSourcefv(sourceID, AL_STEREO_ANGLES, panAngles);
#endif
}
else
{
alSource3f(sourceID, AL_POSITION, pan, 0, -sqrtf(1.0f - pan * pan));
}
}
void Rebuild(uint32& sourceID, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler)
{
ASSERT_LOW_LAYER(sourceID == 0);
@@ -116,11 +137,8 @@ namespace ALC
alSourcef(sourceID, AL_PITCH, pitch);
alSourcef(sourceID, AL_SEC_OFFSET, 0.0f);
alSourcei(sourceID, AL_LOOPING, loop);
alSourcei(sourceID, AL_SOURCE_RELATIVE, !spatial); // Non-spatial sounds use AL_POSITION for panning
alSourcei(sourceID, AL_BUFFER, 0);
#ifdef AL_SOFT_source_spatialize
alSourcei(sourceID, AL_SOURCE_SPATIALIZE_SOFT, AL_TRUE); // Always spatialize, fixes multi-channel played as spatial
#endif
SetupSpatial(sourceID, pan, spatial);
if (spatial)
{
alSourcef(sourceID, AL_ROLLOFF_FACTOR, attenuation);
@@ -128,18 +146,12 @@ namespace ALC
alSourcef(sourceID, AL_REFERENCE_DISTANCE, FLAX_DST_TO_OAL(minDistance));
alSource3f(sourceID, AL_POSITION, FLAX_POS_TO_OAL(position));
alSource3f(sourceID, AL_VELOCITY, FLAX_VEL_TO_OAL(Vector3::Zero));
#ifdef AL_EXT_STEREO_ANGLES
const float panAngle = pan * PI_HALF;
const ALfloat panAngles[2] = { (ALfloat)(PI / 6.0 - panAngle), (ALfloat)(-PI / 6.0 - panAngle) }; // Angles are specified counter-clockwise in radians
alSourcefv(sourceID, AL_STEREO_ANGLES, panAngles);
#endif
}
else
{
alSourcef(sourceID, AL_ROLLOFF_FACTOR, 0.0f);
alSourcef(sourceID, AL_DOPPLER_FACTOR, 1.0f);
alSourcef(sourceID, AL_REFERENCE_DISTANCE, 0.0f);
alSource3f(sourceID, AL_POSITION, pan, 0, -sqrtf(1.0f - pan * pan));
alSource3f(sourceID, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
}
}
@@ -160,12 +172,11 @@ namespace ALC
if (Device == nullptr)
return;
ALCint attrsHrtf[] = { ALC_HRTF_SOFT, ALC_TRUE };
const ALCint* attrList = nullptr;
ALCint attrList[] = { ALC_HRTF_SOFT, ALC_FALSE };
if (Audio::GetEnableHRTF())
{
LOG(Info, "Enabling OpenAL HRTF");
attrList = attrsHrtf;
attrList[1] = ALC_TRUE;
}
Context = alcCreateContext(Device, attrList);
@@ -318,6 +329,7 @@ uint32 AudioBackendOAL::Source_Add(const AudioDataInfo& format, const Vector3& p
auto& data = ALC::SourcesData[sourceID];
data.Format = format;
data.Spatial = spatial;
data.Pan = pan;
ALC::Locker.Unlock();
return sourceID;
@@ -370,20 +382,11 @@ void AudioBackendOAL::Source_PitchChanged(uint32 sourceID, float pitch)
void AudioBackendOAL::Source_PanChanged(uint32 sourceID, float pan)
{
ALC::Locker.Lock();
const bool spatial = ALC::SourcesData[sourceID].Spatial;
auto& e = ALC::SourcesData[sourceID];
e.Pan = pan;
const bool spatial = e.Spatial;
ALC::Locker.Unlock();
if (spatial)
{
#ifdef AL_EXT_STEREO_ANGLES
const float panAngle = pan * PI_HALF;
const ALfloat panAngles[2] = { (ALfloat)(PI / 6.0 - panAngle), (ALfloat)(-PI / 6.0 - panAngle) }; // Angles are specified counter-clockwise in radians
alSourcefv(sourceID, AL_STEREO_ANGLES, panAngles);
#endif
}
else
{
alSource3f(sourceID, AL_POSITION, pan, 0, -sqrtf(1.0f - pan * pan));
}
ALC::Source::SetupSpatial(sourceID, pan, spatial);
}
void AudioBackendOAL::Source_IsLoopingChanged(uint32 sourceID, bool loop)
@@ -393,6 +396,9 @@ void AudioBackendOAL::Source_IsLoopingChanged(uint32 sourceID, bool loop)
void AudioBackendOAL::Source_SpatialSetupChanged(uint32 sourceID, bool spatial, float attenuation, float minDistance, float doppler)
{
ALC::Locker.Lock();
const bool pan = ALC::SourcesData[sourceID].Spatial;
ALC::Locker.Unlock();
if (spatial)
{
alSourcef(sourceID, AL_ROLLOFF_FACTOR, attenuation);
@@ -405,6 +411,7 @@ void AudioBackendOAL::Source_SpatialSetupChanged(uint32 sourceID, bool spatial,
alSourcef(sourceID, AL_DOPPLER_FACTOR, 1.0f);
alSourcef(sourceID, AL_REFERENCE_DISTANCE, 0.0f);
}
ALC::Source::SetupSpatial(sourceID, pan, spatial);
}
void AudioBackendOAL::Source_Play(uint32 sourceID)
@@ -602,7 +609,7 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferID, byte* samples, const AudioDa
if (!format)
{
LOG(Error, "Not suppported audio data format for OpenAL device: BitDepth={}, NumChannels={}", info.BitDepth, info.NumChannels);
LOG(Error, "Not supported audio data format for OpenAL device: BitDepth={}, NumChannels={}", info.BitDepth, info.NumChannels);
}
}

View File

@@ -159,19 +159,19 @@ CreateAssetResult ImportAudio::Import(CreateAssetContext& context, AudioDecoder&
else
{
// Split audio data into a several chunks (uniform data spread)
const int32 minChunkSize = 1 * 1024 * 1024; // 1 MB
const int32 dataAlignment = info.NumChannels * bytesPerSample; // Ensure to never split samples in-between (eg. 24-bit that uses 3 bytes)
const int32 chunkSize = Math::Max<int32>(minChunkSize, (int32)Math::AlignUp<uint32>(bufferSize / ASSET_FILE_DATA_CHUNKS, dataAlignment));
const int32 chunksCount = Math::CeilToInt((float)bufferSize / chunkSize);
const uint32 minChunkSize = 1 * 1024 * 1024; // 1 MB
const uint32 dataAlignment = info.NumChannels * bytesPerSample * ASSET_FILE_DATA_CHUNKS; // Ensure to never split samples in-between (eg. 24-bit that uses 3 bytes)
const uint32 chunkSize = Math::AlignUp(Math::Max(minChunkSize, bufferSize / ASSET_FILE_DATA_CHUNKS), dataAlignment);
const int32 chunksCount = Math::CeilToInt((float)bufferSize / (float)chunkSize);
ASSERT(chunksCount > 0 && chunksCount <= ASSET_FILE_DATA_CHUNKS);
byte* ptr = sampleBuffer.Get();
int32 size = bufferSize;
uint32 size = bufferSize;
for (int32 chunkIndex = 0; chunkIndex < chunksCount; chunkIndex++)
{
if (context.AllocateChunk(chunkIndex))
return CreateAssetResult::CannotAllocateChunk;
const int32 t = Math::Min(size, chunkSize);
const uint32 t = Math::Min(size, chunkSize);
WRITE_DATA(chunkIndex, ptr, t);

View File

@@ -520,4 +520,24 @@ PRAGMA_ENABLE_DEPRECATION_WARNINGS
return result;
}
#if USE_EDITOR
Array<Vector3> Mesh::GetCollisionProxyPoints() const
{
PROFILE_CPU();
Array<Vector3> result;
#if USE_PRECISE_MESH_INTERSECTS
for (int32 i = 0; i < _collisionProxy.Triangles.Count(); i++)
{
auto triangle = _collisionProxy.Triangles.Get()[i];
result.Add(triangle.V0);
result.Add(triangle.V1);
result.Add(triangle.V2);
}
#endif
return result;
}
#endif
#endif

View File

@@ -536,5 +536,16 @@ namespace FlaxEngine
return result;
}
#if FLAX_EDITOR
/// <summary>
/// Gets the collision proxy points for the mesh.
/// </summary>
/// <returns>The triangle points in the collision proxy.</returns>
internal Vector3[] GetCollisionProxyPoints()
{
return Internal_GetCollisionProxyPoints(__unmanagedPtr, out _);
}
#endif
}
}

View File

@@ -171,5 +171,8 @@ private:
API_FUNCTION(NoProxy) bool UpdateMeshUInt(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj);
API_FUNCTION(NoProxy) bool UpdateMeshUShort(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj);
API_FUNCTION(NoProxy) MArray* DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI);
#if USE_EDITOR
API_FUNCTION(NoProxy) Array<Vector3> GetCollisionProxyPoints() const;
#endif
#endif
};

View File

@@ -91,7 +91,7 @@ public:
int32 LightmapUVsIndex = -1;
/// <summary>
/// Global translation for this mesh to be at its local origin.
/// Local translation for this mesh to be at it's local origin.
/// </summary>
Vector3 OriginTranslation = Vector3::Zero;

View File

@@ -9,6 +9,11 @@ ModelInstanceActor::ModelInstanceActor(const SpawnParams& params)
{
}
String ModelInstanceActor::MeshReference::ToString() const
{
return String::Format(TEXT("Actor={},LOD={},Mesh={}"), Actor ? Actor->GetNamePath() : String::Empty, LODIndex, MeshIndex);
}
void ModelInstanceActor::SetEntries(const Array<ModelInstanceEntry>& value)
{
WaitForModelLoad();

View File

@@ -27,6 +27,8 @@ API_CLASS(Abstract) class FLAXENGINE_API ModelInstanceActor : public Actor
API_FIELD() int32 LODIndex = 0;
// Index of the mesh (within the LOD).
API_FIELD() int32 MeshIndex = 0;
String ToString() const;
};
protected:

View File

@@ -647,6 +647,7 @@ void Cloth::CalculateInvMasses(Array<float>& invMasses)
if (_paint.Count() != verticesCount)
{
// Fix incorrect paint data
LOG(Warning, "Incorrect cloth '{}' paint size {} for mesh '{}' that has {} vertices", GetNamePath(), _paint.Count(), mesh.ToString(), verticesCount);
int32 countBefore = _paint.Count();
_paint.Resize(verticesCount);
for (int32 i = countBefore; i < verticesCount; i++)
@@ -795,7 +796,10 @@ bool Cloth::OnPreUpdate()
if (!positionStream.IsValid() || !blendIndicesStream.IsValid() || !blendWeightsStream.IsValid())
return false;
if (verticesCount != _paint.Count())
{
LOG(Warning, "Incorrect cloth '{}' paint size {} for mesh '{}' that has {} vertices", GetNamePath(), _paint.Count(), mesh.ToString(), verticesCount);
return false;
}
PROFILE_CPU_NAMED("Skinned Pose");
PhysicsBackend::LockClothParticles(_cloth);
const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth);

View File

@@ -399,7 +399,7 @@ public:
/// </summary>
/// <param name="text">The input text to test.</param>
/// <param name="layout">The layout properties.</param>
/// <returns>The minimum size for that text and fot to render properly.</returns>
/// <returns>The minimum size for that text and font to render properly.</returns>
API_FUNCTION() Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout);
/// <summary>
@@ -408,7 +408,7 @@ public:
/// <param name="text">The input text to test.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
/// <param name="layout">The layout properties.</param>
/// <returns>The minimum size for that text and fot to render properly.</returns>
/// <returns>The minimum size for that text and font to render properly.</returns>
API_FUNCTION() Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout)
{
return MeasureText(textRange.Substring(text), layout);
@@ -418,7 +418,7 @@ public:
/// Measures minimum size of the rectangle that will be needed to draw given text
/// </summary>.
/// <param name="text">The input text to test.</param>
/// <returns>The minimum size for that text and fot to render properly.</returns>
/// <returns>The minimum size for that text and font to render properly.</returns>
API_FUNCTION() FORCE_INLINE Float2 MeasureText(const StringView& text)
{
return MeasureText(text, TextLayoutOptions());
@@ -429,7 +429,7 @@ public:
/// </summary>.
/// <param name="text">The input text to test.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
/// <returns>The minimum size for that text and fot to render properly.</returns>
/// <returns>The minimum size for that text and font to render properly.</returns>
API_FUNCTION() FORCE_INLINE Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextRange& textRange)
{
return MeasureText(textRange.Substring(text), TextLayoutOptions());

View File

@@ -482,7 +482,6 @@ bool ShadowsPass::Init()
// Select format for shadow maps
_shadowMapFormat = PixelFormat::Unknown;
#if !PLATFORM_SWITCH // TODO: fix shadows performance issue on Switch
for (const PixelFormat format : { PixelFormat::D16_UNorm, PixelFormat::D24_UNorm_S8_UInt, PixelFormat::D32_Float })
{
const auto formatTexture = PixelFormatExtensions::FindShaderResourceFormat(format, false);
@@ -495,7 +494,6 @@ bool ShadowsPass::Init()
break;
}
}
#endif
if (_shadowMapFormat == PixelFormat::Unknown)
LOG(Warning, "GPU doesn't support shadows rendering");

View File

@@ -732,7 +732,12 @@ Array<ScriptingObject*, HeapAllocation> Scripting::GetObjects()
{
Array<ScriptingObject*> objects;
_objectsLocker.Lock();
#if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING
for (const auto& e : _objectsDictionary)
objects.Add(e.Value.Ptr);
#else
_objectsDictionary.GetValues(objects);
#endif
_objectsLocker.Unlock();
return objects;
}

View File

@@ -185,7 +185,7 @@ Ray JsonTools::GetRay(const Value& value)
{
return Ray(
GetVector3(value, "Position", Vector3::Zero),
GetVector3(value, "Direction", Vector3::One)
GetVector3(value, "Direction", Vector3::Forward)
);
}

View File

@@ -4,6 +4,7 @@
#include "AudioTool.h"
#include "Engine/Core/Core.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Core/Memory/Allocation.h"
#if USE_EDITOR
#include "Engine/Serialization/Serialization.h"
@@ -307,8 +308,9 @@ void AudioTool::ConvertFromFloat(const float* input, int32* output, uint32 numSa
{
for (uint32 i = 0; i < numSamples; i++)
{
const float sample = *(float*)input;
output[i] = static_cast<int32>(sample * 2147483647.0f);
float sample = *(float*)input;
sample = Math::Clamp(sample, -1.0f + ZeroTolerance, +1.0f - ZeroTolerance);
output[i] = static_cast<int32>(sample * 2147483648.0f);
input++;
}
}

View File

@@ -706,11 +706,11 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
const auto texture = eatBox(textureBox->GetParent<Node>(), textureBox->FirstConnection());
const auto scale = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat();
const auto blend = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat();
const auto offset = tryGetValue(node->GetBox(3), node->Values.Count() > 2 ? node->Values[2] : Float2::Zero).AsFloat2();
const auto offset = tryGetValue(node->TryGetBox(6), node->Values.Count() >= 3 ? node->Values[2] : Float2::Zero).AsFloat2();
const bool local = node->Values.Count() >= 5 ? node->Values[4].AsBool : false;
const Char* samplerName;
const int32 samplerIndex = node->Values[3].AsInt;
const int32 samplerIndex = node->Values.Count() >= 4 ? node->Values[3].AsInt : LinearWrap;
if (samplerIndex == TextureGroup)
{
auto& textureGroupSampler = findOrAddTextureGroupSampler(node->Values[3].AsInt);
@@ -795,7 +795,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
const auto texture = eatBox(textureBox->GetParent<Node>(), textureBox->FirstConnection());
const auto scale = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat();
const auto blend = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat();
const auto offset = tryGetValue(node->GetBox(3), node->Values[2]).AsFloat2();
const auto offset = tryGetValue(node->GetBox(6), node->Values[2]).AsFloat2();
const bool local = node->Values.Count() >= 5 ? node->Values[4].AsBool : false;
const Char* samplerName;

View File

@@ -597,24 +597,10 @@ bool ImportMesh(int32 index, ModelData& result, AssimpImporterData& data, String
// Link mesh
meshData->NodeIndex = nodeIndex;
AssimpNode* curNode = &data.Nodes[meshData->NodeIndex];
Vector3 translation = Vector3::Zero;
Vector3 scale = Vector3::One;
Quaternion rotation = Quaternion::Identity;
while (true)
{
translation += curNode->LocalTransform.Translation;
scale *= curNode->LocalTransform.Scale;
rotation *= curNode->LocalTransform.Orientation;
if (curNode->ParentIndex == -1)
break;
curNode = &data.Nodes[curNode->ParentIndex];
}
meshData->OriginTranslation = translation;
meshData->OriginOrientation = rotation;
meshData->Scaling = scale;
meshData->OriginTranslation = curNode->LocalTransform.Translation;
meshData->OriginOrientation = curNode->LocalTransform.Orientation;
meshData->Scaling = curNode->LocalTransform.Scale;
if (result.LODs.Count() <= lodIndex)
result.LODs.Resize(lodIndex + 1);

View File

@@ -992,7 +992,8 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh*
}*/
// Get local transform for origin shifting translation
auto translation = ToMatrix(aMesh->getGlobalTransform()).GetTranslation();
auto translation = Float3((float)aMesh->getLocalTranslation().x, (float)aMesh->getLocalTranslation().y, (float)aMesh->getLocalTranslation().z);
auto scale = data.GlobalSettings.UnitScaleFactor;
if (data.GlobalSettings.CoordAxis == ofbx::CoordSystem_RightHanded)
mesh.OriginTranslation = scale * Vector3(translation.X, translation.Y, -translation.Z);

View File

@@ -1549,23 +1549,25 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
// Prepare import transformation
Transform importTransform(options.Translation, options.Rotation, Float3(options.Scale));
if (options.UseLocalOrigin && data.LODs.HasItems() && data.LODs[0].Meshes.HasItems())
{
importTransform.Translation -= importTransform.Orientation * data.LODs[0].Meshes[0]->OriginTranslation * importTransform.Scale;
}
if (options.CenterGeometry && data.LODs.HasItems() && data.LODs[0].Meshes.HasItems())
{
// Calculate the bounding box (use LOD0 as a reference)
BoundingBox box = data.LODs[0].GetBox();
auto center = data.LODs[0].Meshes[0]->OriginOrientation * importTransform.Orientation * box.GetCenter() * importTransform.Scale * data.LODs[0].Meshes[0]->Scaling;
importTransform.Translation -= center;
}
// Apply the import transformation
if (!importTransform.IsIdentity() && data.Nodes.HasItems())
if ((!importTransform.IsIdentity() || options.UseLocalOrigin || options.CenterGeometry) && data.Nodes.HasItems())
{
if (options.Type == ModelType::SkinnedModel)
{
// Setup other transform options
if (options.UseLocalOrigin && data.LODs.HasItems() && data.LODs[0].Meshes.HasItems())
{
importTransform.Translation -= importTransform.Orientation * data.LODs[0].Meshes[0]->OriginTranslation * importTransform.Scale;
}
if (options.CenterGeometry && data.LODs.HasItems() && data.LODs[0].Meshes.HasItems())
{
// Calculate the bounding box (use LOD0 as a reference)
BoundingBox box = data.LODs[0].GetBox();
auto center = data.LODs[0].Meshes[0]->OriginOrientation * importTransform.Orientation * box.GetCenter() * importTransform.Scale * data.LODs[0].Meshes[0]->Scaling;
importTransform.Translation -= center;
}
// Transform the root node using the import transformation
auto& root = data.Skeleton.RootNode();
Transform meshTransform = root.LocalTransform.WorldToLocal(importTransform).LocalToWorld(root.LocalTransform);
@@ -1596,9 +1598,31 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
}
else
{
// Transform the root node using the import transformation
auto& root = data.Nodes[0];
root.LocalTransform = importTransform.LocalToWorld(root.LocalTransform);
// Transform the nodes using the import transformation
if (data.LODs.HasItems() && data.LODs[0].Meshes.HasItems())
{
for (int i = 0; i < data.LODs[0].Meshes.Count(); ++i)
{
auto* meshData = data.LODs[0].Meshes[i];
Transform transform = importTransform;
if (options.UseLocalOrigin)
{
transform.Translation -= transform.Orientation * meshData->OriginTranslation * transform.Scale;
}
if (options.CenterGeometry)
{
// Calculate the bounding box (use LOD0 as a reference)
BoundingBox box = data.LODs[0].GetBox();
auto center = meshData->OriginOrientation * transform.Orientation * box.GetCenter() * transform.Scale * meshData->Scaling;
transform.Translation -= center;
}
int32 nodeIndex = meshData->NodeIndex;
auto& node = data.Nodes[nodeIndex];
node.LocalTransform = transform.LocalToWorld(node.LocalTransform);
}
}
}
}