@@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using FlaxEditor;
|
|
||||||
using FlaxEditor.Gizmo;
|
using FlaxEditor.Gizmo;
|
||||||
using FlaxEditor.SceneGraph;
|
using FlaxEditor.SceneGraph;
|
||||||
using FlaxEditor.Viewport;
|
using FlaxEditor.Viewport;
|
||||||
@@ -20,7 +18,8 @@ public class ViewportRubberBandSelector
|
|||||||
private Float2 _cachedStartingMousePosition;
|
private Float2 _cachedStartingMousePosition;
|
||||||
private Rectangle _rubberBandRect;
|
private Rectangle _rubberBandRect;
|
||||||
private Rectangle _lastRubberBandRect;
|
private Rectangle _lastRubberBandRect;
|
||||||
|
private List<ActorNode> _nodesCache;
|
||||||
|
private List<SceneGraphNode> _hitsCache;
|
||||||
private IGizmoOwner _owner;
|
private IGizmoOwner _owner;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -87,15 +86,43 @@ public class ViewportRubberBandSelector
|
|||||||
{
|
{
|
||||||
_rubberBandRect.Width = mousePosition.X - _cachedStartingMousePosition.X;
|
_rubberBandRect.Width = mousePosition.X - _cachedStartingMousePosition.X;
|
||||||
_rubberBandRect.Height = mousePosition.Y - _cachedStartingMousePosition.Y;
|
_rubberBandRect.Height = mousePosition.Y - _cachedStartingMousePosition.Y;
|
||||||
|
|
||||||
if (_lastRubberBandRect != _rubberBandRect)
|
if (_lastRubberBandRect != _rubberBandRect)
|
||||||
{
|
{
|
||||||
|
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
|
// Select rubberbanded rect actor nodes
|
||||||
var adjustedRect = _rubberBandRect;
|
var adjustedRect = _rubberBandRect;
|
||||||
_lastRubberBandRect = _rubberBandRect;
|
_lastRubberBandRect = _rubberBandRect;
|
||||||
if (adjustedRect.Width < 0 || adjustedRect.Height < 0)
|
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
|
// Make sure we have a well-formed rectangle i.e. size is positive and X/Y is upper left corner
|
||||||
var size = adjustedRect.Size;
|
var size = adjustedRect.Size;
|
||||||
adjustedRect.X = Mathf.Min(adjustedRect.X, adjustedRect.X + adjustedRect.Width);
|
adjustedRect.X = Mathf.Min(adjustedRect.X, adjustedRect.X + adjustedRect.Width);
|
||||||
adjustedRect.Y = Mathf.Min(adjustedRect.Y, adjustedRect.Y + adjustedRect.Height);
|
adjustedRect.Y = Mathf.Min(adjustedRect.Y, adjustedRect.Y + adjustedRect.Height);
|
||||||
@@ -104,16 +131,29 @@ public class ViewportRubberBandSelector
|
|||||||
adjustedRect.Size = size;
|
adjustedRect.Size = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get hits from graph nodes.
|
// Get hits from graph nodes
|
||||||
List<SceneGraphNode> hits = new List<SceneGraphNode>();
|
if (_nodesCache == null)
|
||||||
var nodes = _owner.SceneGraphRoot.GetAllChildActorNodes();
|
_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)
|
foreach (var node in nodes)
|
||||||
{
|
{
|
||||||
// Check for custom can select code
|
// Check for custom can select code
|
||||||
if (!node.CanSelectActorNodeWithSelector())
|
if (!node.CanSelectActorNodeWithSelector())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var a = node.Actor;
|
var a = node.Actor;
|
||||||
|
|
||||||
// Skip actor if outside of view frustum
|
// Skip actor if outside of view frustum
|
||||||
var actorBox = a.EditorBox;
|
var actorBox = a.EditorBox;
|
||||||
if (viewFrustum.Contains(actorBox) == ContainmentType.Disjoint)
|
if (viewFrustum.Contains(actorBox) == ContainmentType.Disjoint)
|
||||||
@@ -121,17 +161,7 @@ public class ViewportRubberBandSelector
|
|||||||
|
|
||||||
// Get valid selection points
|
// Get valid selection points
|
||||||
var points = node.GetActorSelectionPoints();
|
var points = node.GetActorSelectionPoints();
|
||||||
bool containsAllPoints = points.Length != 0;
|
if (LoopOverPoints(points, ref adjustedRect, ref projection))
|
||||||
foreach (var point in points)
|
|
||||||
{
|
|
||||||
_owner.Viewport.ProjectPoint(point, out var loc);
|
|
||||||
if (!adjustedRect.Contains(loc))
|
|
||||||
{
|
|
||||||
containsAllPoints = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (containsAllPoints)
|
|
||||||
{
|
{
|
||||||
if (a.HasPrefabLink && _owner is not PrefabWindowViewport)
|
if (a.HasPrefabLink && _owner is not PrefabWindowViewport)
|
||||||
hits.Add(_owner.SceneGraphRoot.Find(a.GetPrefabRoot()));
|
hits.Add(_owner.SceneGraphRoot.Find(a.GetPrefabRoot()));
|
||||||
@@ -140,11 +170,11 @@ public class ViewportRubberBandSelector
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var editor = Editor.Instance;
|
// Process selection
|
||||||
if (_owner.IsControlDown)
|
if (_owner.IsControlDown)
|
||||||
{
|
{
|
||||||
var newSelection = new List<SceneGraphNode>();
|
var newSelection = new List<SceneGraphNode>();
|
||||||
var currentSelection = _owner.SceneGraphRoot.Selection;
|
var currentSelection = new List<SceneGraphNode>(_owner.SceneGraphRoot.SceneContext.Selection);
|
||||||
newSelection.AddRange(currentSelection);
|
newSelection.AddRange(currentSelection);
|
||||||
foreach (var hit in hits)
|
foreach (var hit in hits)
|
||||||
{
|
{
|
||||||
@@ -158,7 +188,7 @@ public class ViewportRubberBandSelector
|
|||||||
else if (Input.GetKey(KeyboardKeys.Shift))
|
else if (Input.GetKey(KeyboardKeys.Shift))
|
||||||
{
|
{
|
||||||
var newSelection = new List<SceneGraphNode>();
|
var newSelection = new List<SceneGraphNode>();
|
||||||
var currentSelection = _owner.SceneGraphRoot.Selection;
|
var currentSelection = new List<SceneGraphNode>(_owner.SceneGraphRoot.SceneContext.Selection);
|
||||||
newSelection.AddRange(hits);
|
newSelection.AddRange(hits);
|
||||||
newSelection.AddRange(currentSelection);
|
newSelection.AddRange(currentSelection);
|
||||||
_owner.Select(newSelection);
|
_owner.Select(newSelection);
|
||||||
@@ -167,8 +197,25 @@ public class ViewportRubberBandSelector
|
|||||||
{
|
{
|
||||||
_owner.Select(hits);
|
_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>
|
/// <summary>
|
||||||
|
|||||||
@@ -188,26 +188,29 @@ namespace FlaxEditor.SceneGraph
|
|||||||
/// <returns>An array of ActorNodes</returns>
|
/// <returns>An array of ActorNodes</returns>
|
||||||
public ActorNode[] GetAllChildActorNodes()
|
public ActorNode[] GetAllChildActorNodes()
|
||||||
{
|
{
|
||||||
// Check itself
|
|
||||||
if (ChildNodes == null || ChildNodes.Count == 0)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
// Check deeper
|
|
||||||
var nodes = new List<ActorNode>();
|
var nodes = new List<ActorNode>();
|
||||||
for (int i = 0; i < ChildNodes.Count; i++)
|
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)
|
||||||
{
|
{
|
||||||
if (ChildNodes[i] is ActorNode node)
|
var children = ChildNodes;
|
||||||
|
if (children == null)
|
||||||
|
return;
|
||||||
|
for (int i = 0; i < children.Count; i++)
|
||||||
|
{
|
||||||
|
if (children[i] is ActorNode node)
|
||||||
{
|
{
|
||||||
nodes.Add(node);
|
nodes.Add(node);
|
||||||
var childNodes = node.GetAllChildActorNodes();
|
node.GetAllChildActorNodes(nodes);
|
||||||
if (childNodes.Length > 0)
|
|
||||||
{
|
|
||||||
nodes.AddRange(childNodes);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nodes.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether an actor node can be selected with a selector.
|
/// Whether an actor node can be selected with a selector.
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ namespace FlaxEditor.SceneGraph.Actors
|
|||||||
public sealed class StaticModelNode : ActorNode
|
public sealed class StaticModelNode : ActorNode
|
||||||
{
|
{
|
||||||
private Dictionary<IntPtr, Mesh.Vertex[]> _vertices;
|
private Dictionary<IntPtr, Mesh.Vertex[]> _vertices;
|
||||||
|
private Vector3[] _selectionPoints;
|
||||||
|
private Transform _selectionPointsTransform;
|
||||||
|
private Model _selectionPointsModel;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public StaticModelNode(Actor actor)
|
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 />
|
/// <inheritdoc />
|
||||||
public override bool OnVertexSnap(ref Ray ray, Real hitDistance, out Vector3 result)
|
public override bool OnVertexSnap(ref Ray ray, Real hitDistance, out Vector3 result)
|
||||||
{
|
{
|
||||||
@@ -91,25 +104,40 @@ namespace FlaxEditor.SceneGraph.Actors
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override Vector3[] GetActorSelectionPoints()
|
public override Vector3[] GetActorSelectionPoints()
|
||||||
{
|
{
|
||||||
if (Actor is not StaticModel sm || !sm.Model)
|
if (Actor is StaticModel sm && sm.Model)
|
||||||
return base.GetActorSelectionPoints();
|
{
|
||||||
|
// 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.
|
// Check collision proxy points for more accurate selection
|
||||||
var vecPoints = new List<Vector3>();
|
var vecPoints = new List<Vector3>();
|
||||||
var m = sm.Model.LODs[0];
|
var m = model.LODs[0];
|
||||||
foreach (var mesh in m.Meshes)
|
foreach (var mesh in m.Meshes)
|
||||||
{
|
{
|
||||||
var points = mesh.GetCollisionProxyPoints();
|
var points = mesh.GetCollisionProxyPoints();
|
||||||
foreach (var point in points)
|
vecPoints.EnsureCapacity(vecPoints.Count + points.Length);
|
||||||
|
for (int i = 0; i < points.Length; i++)
|
||||||
{
|
{
|
||||||
vecPoints.Add(Actor.Transform.LocalToWorld(point));
|
vecPoints.Add(transform.LocalToWorld(points[i]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fall back to base actor editor box if no points from collision proxy.
|
Profiler.EndEvent();
|
||||||
if (vecPoints.Count == 0)
|
if (vecPoints.Count != 0)
|
||||||
|
{
|
||||||
|
_selectionPoints = vecPoints.ToArray();
|
||||||
|
_selectionPointsTransform = transform;
|
||||||
|
_selectionPointsModel = model;
|
||||||
|
return _selectionPoints;
|
||||||
|
}
|
||||||
|
}
|
||||||
return base.GetActorSelectionPoints();
|
return base.GetActorSelectionPoints();
|
||||||
return vecPoints.ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnAddMeshCollider(EditorWindow window)
|
private void OnAddMeshCollider(EditorWindow window)
|
||||||
|
|||||||
@@ -111,7 +111,6 @@ namespace FlaxEditor.Viewport
|
|||||||
private double _lockedFocusOffset;
|
private double _lockedFocusOffset;
|
||||||
private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32);
|
private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32);
|
||||||
private EditorSpritesRenderer _editorSpritesRenderer;
|
private EditorSpritesRenderer _editorSpritesRenderer;
|
||||||
|
|
||||||
private ViewportRubberBandSelector _rubberBandSelector;
|
private ViewportRubberBandSelector _rubberBandSelector;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -607,9 +606,9 @@ namespace FlaxEditor.Viewport
|
|||||||
{
|
{
|
||||||
base.OnMouseMove(location);
|
base.OnMouseMove(location);
|
||||||
|
|
||||||
// Dont allow rubber band selection when gizmo is controlling mouse, vertex painting mode, or cloth painting is enabled
|
// Don't allow rubber band selection when gizmo is controlling mouse, vertex painting mode, or cloth painting is enabled
|
||||||
_rubberBandSelector.TryCreateRubberBand(!((Gizmos.Active.IsControllingMouse || Gizmos.Active is VertexPaintingGizmo || Gizmos.Active is ClothPaintingGizmo) || IsControllingMouse || IsRightMouseButtonDown),
|
bool canStart = !((Gizmos.Active.IsControllingMouse || Gizmos.Active is VertexPaintingGizmo || Gizmos.Active is ClothPaintingGizmo) || IsControllingMouse || IsRightMouseButtonDown);
|
||||||
_viewMousePos, ViewFrustum);
|
_rubberBandSelector.TryCreateRubberBand(canStart, _viewMousePos, ViewFrustum);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@@ -634,13 +634,15 @@ namespace FlaxEngine
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if FLAX_EDITOR
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the collision proxy points for the mesh.
|
/// Gets the collision proxy points for the mesh.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The triangle points in the collision proxy.</returns>
|
/// <returns>The triangle points in the collision proxy.</returns>
|
||||||
internal Float3[] GetCollisionProxyPoints()
|
internal Vector3[] GetCollisionProxyPoints()
|
||||||
{
|
{
|
||||||
return Internal_GetCollisionProxyPoints(__unmanagedPtr, out _);
|
return Internal_GetCollisionProxyPoints(__unmanagedPtr, out _);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -847,13 +847,16 @@ MArray* Mesh::DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Array<Float3> Mesh::GetCollisionProxyPoints() const
|
#if USE_EDITOR
|
||||||
|
|
||||||
|
Array<Vector3> Mesh::GetCollisionProxyPoints() const
|
||||||
{
|
{
|
||||||
|
PROFILE_CPU();
|
||||||
Array<Vector3> result;
|
Array<Vector3> result;
|
||||||
#if USE_PRECISE_MESH_INTERSECTS
|
#if USE_PRECISE_MESH_INTERSECTS
|
||||||
for (int i = 0; i < _collisionProxy.Triangles.Count(); ++i)
|
for (int32 i = 0; i < _collisionProxy.Triangles.Count(); i++)
|
||||||
{
|
{
|
||||||
auto triangle = _collisionProxy.Triangles[i];
|
auto triangle = _collisionProxy.Triangles.Get()[i];
|
||||||
result.Add(triangle.V0);
|
result.Add(triangle.V0);
|
||||||
result.Add(triangle.V1);
|
result.Add(triangle.V1);
|
||||||
result.Add(triangle.V2);
|
result.Add(triangle.V2);
|
||||||
@@ -863,3 +866,5 @@ Array<Float3> Mesh::GetCollisionProxyPoints() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -320,6 +320,8 @@ private:
|
|||||||
API_FUNCTION(NoProxy) bool UpdateTrianglesUInt(int32 triangleCount, const MArray* trianglesObj);
|
API_FUNCTION(NoProxy) bool UpdateTrianglesUInt(int32 triangleCount, const MArray* trianglesObj);
|
||||||
API_FUNCTION(NoProxy) bool UpdateTrianglesUShort(int32 triangleCount, const MArray* trianglesObj);
|
API_FUNCTION(NoProxy) bool UpdateTrianglesUShort(int32 triangleCount, const MArray* trianglesObj);
|
||||||
API_FUNCTION(NoProxy) MArray* DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI);
|
API_FUNCTION(NoProxy) MArray* DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI);
|
||||||
API_FUNCTION(NoProxy) Array<Float3> GetCollisionProxyPoints() const;
|
#if USE_EDITOR
|
||||||
|
API_FUNCTION(NoProxy) Array<Vector3> GetCollisionProxyPoints() const;
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user