diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs
index 456697f13..92846c907 100644
--- a/Source/Editor/Gizmo/TransformGizmoBase.cs
+++ b/Source/Editor/Gizmo/TransformGizmoBase.cs
@@ -42,6 +42,7 @@ namespace FlaxEditor.Gizmo
private bool _isDuplicating;
private bool _isTransforming;
+ private bool _isSelected;
private Vector3 _lastIntersectionPosition;
private Quaternion _rotationDelta = Quaternion.Identity;
@@ -455,7 +456,7 @@ namespace FlaxEditor.Gizmo
}
///
- public override bool IsControllingMouse => _isTransforming;
+ public override bool IsControllingMouse => _isTransforming || _isSelected;
///
public override void Update(float dt)
@@ -480,6 +481,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:
@@ -497,6 +499,7 @@ namespace FlaxEditor.Gizmo
}
else
{
+ _isSelected = false;
// If nothing selected, try to select any axis
if (!isLeftBtnDown && !Owner.IsRightMouseButtonDown)
{
@@ -564,6 +567,7 @@ namespace FlaxEditor.Gizmo
// Clear cache
_accMoveDelta = Vector3.Zero;
_lastIntersectionPosition = _intersectPosition = Vector3.Zero;
+ _isSelected = false;
EndTransforming();
}
}
diff --git a/Source/Editor/Gizmo/ViewportRubberBandSelector.cs b/Source/Editor/Gizmo/ViewportRubberBandSelector.cs
new file mode 100644
index 000000000..8b0fb1e97
--- /dev/null
+++ b/Source/Editor/Gizmo/ViewportRubberBandSelector.cs
@@ -0,0 +1,262 @@
+// 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;
+
+///
+/// Class for adding viewport rubber band selection.
+///
+public class ViewportRubberBandSelector
+{
+ private bool _isRubberBandSpanning;
+ private bool _tryStartRubberBand;
+ private Float2 _cachedStartingMousePosition;
+ private Rectangle _rubberBandRect;
+ private Rectangle _lastRubberBandRect;
+ private List _nodesCache;
+ private List _hitsCache;
+ private IGizmoOwner _owner;
+
+ ///
+ /// Constructs a rubber band selector with a designated gizmo owner.
+ ///
+ /// The gizmo owner.
+ public ViewportRubberBandSelector(IGizmoOwner owner)
+ {
+ _owner = owner;
+ }
+
+ ///
+ /// Triggers the start of a rubber band selection.
+ ///
+ /// True if selection started, otherwise false.
+ public bool TryStartingRubberBandSelection()
+ {
+ if (!_isRubberBandSpanning && !_owner.Gizmos.Active.IsControllingMouse && !_owner.IsRightMouseButtonDown)
+ {
+ _tryStartRubberBand = true;
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// Release the rubber band selection.
+ ///
+ /// Returns true if rubber band is currently spanning
+ public bool ReleaseRubberBandSelection()
+ {
+ if (_tryStartRubberBand)
+ {
+ _tryStartRubberBand = false;
+ }
+
+ if (_isRubberBandSpanning)
+ {
+ _isRubberBandSpanning = false;
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// Tries to create a rubber band selection.
+ ///
+ /// Whether the creation can start.
+ /// The current mouse position.
+ /// The view frustum.
+ 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)
+ {
+ 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();
+ else
+ _nodesCache.Clear();
+ var nodes = _nodesCache;
+ _owner.SceneGraphRoot.GetAllChildActorNodes(nodes);
+ if (_hitsCache == null)
+ _hitsCache = new List();
+ 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();
+ var currentSelection = new List(_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();
+ var currentSelection = new List(_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;
+ }
+
+ ///
+ /// Used to draw the rubber band. Begins render 2D.
+ ///
+ /// The GPU Context.
+ /// The GPU texture target.
+ /// The GPU texture target depth.
+ 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();
+ }
+
+ ///
+ /// Used to draw the rubber band. Use if already rendering 2D context.
+ ///
+ public void Draw2D()
+ {
+ if (!_isRubberBandSpanning)
+ return;
+ Render2D.FillRectangle(_rubberBandRect, Style.Current.Selection);
+ Render2D.DrawRectangle(_rubberBandRect, Style.Current.SelectionBorder);
+ }
+
+ ///
+ /// Immediately stops the rubber band.
+ ///
+ /// True if rubber band was active before stopping.
+ public bool StopRubberBand()
+ {
+ var result = _tryStartRubberBand;
+ _isRubberBandSpanning = false;
+ _tryStartRubberBand = false;
+ return result;
+ }
+}
diff --git a/Source/Editor/SceneGraph/ActorNode.cs b/Source/Editor/SceneGraph/ActorNode.cs
index a1e1643a8..0740fb84f 100644
--- a/Source/Editor/SceneGraph/ActorNode.cs
+++ b/Source/Editor/SceneGraph/ActorNode.cs
@@ -182,6 +182,54 @@ namespace FlaxEditor.SceneGraph
return null;
}
+ ///
+ /// Get all nested actor nodes under this actor node.
+ ///
+ /// An array of ActorNodes
+ public ActorNode[] GetAllChildActorNodes()
+ {
+ var nodes = new List();
+ GetAllChildActorNodes(nodes);
+ return nodes.ToArray();
+ }
+
+ ///
+ /// Get all nested actor nodes under this actor node.
+ ///
+ /// The output list to fill with results.
+ public void GetAllChildActorNodes(List 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);
+ }
+ }
+ }
+
+ ///
+ /// Whether an actor node can be selected with a selector.
+ ///
+ /// True if the actor node can be selected
+ public virtual bool CanSelectActorNodeWithSelector()
+ {
+ return Actor && Actor.HideFlags is not (HideFlags.DontSelect or HideFlags.FullyHidden) && Actor is not EmptyActor && IsActive;
+ }
+
+ ///
+ /// The selection points used to check if an actor node can be selected.
+ ///
+ /// The points to use if the actor can be selected.
+ public virtual Vector3[] GetActorSelectionPoints()
+ {
+ return Actor.EditorBox.GetCorners();
+ }
+
///
/// Gets a value indicating whether this actor can be used to create prefab from it (as a root).
///
diff --git a/Source/Editor/SceneGraph/Actors/CameraNode.cs b/Source/Editor/SceneGraph/Actors/CameraNode.cs
index 6c485314d..cada3364c 100644
--- a/Source/Editor/SceneGraph/Actors/CameraNode.cs
+++ b/Source/Editor/SceneGraph/Actors/CameraNode.cs
@@ -58,5 +58,11 @@ namespace FlaxEditor.SceneGraph.Actors
return Camera.Internal_IntersectsItselfEditor(FlaxEngine.Object.GetUnmanagedPtr(_actor), ref ray.Ray, out distance);
}
+
+ ///
+ public override Vector3[] GetActorSelectionPoints()
+ {
+ return [Actor.Position];
+ }
}
}
diff --git a/Source/Editor/SceneGraph/Actors/SceneNode.cs b/Source/Editor/SceneGraph/Actors/SceneNode.cs
index bb5998ef4..9e380ff91 100644
--- a/Source/Editor/SceneGraph/Actors/SceneNode.cs
+++ b/Source/Editor/SceneGraph/Actors/SceneNode.cs
@@ -33,6 +33,12 @@ namespace FlaxEditor.SceneGraph.Actors
}
}
+ ///
+ public override bool CanSelectActorNodeWithSelector()
+ {
+ return false;
+ }
+
///
/// Gets the scene.
///
diff --git a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs
index 885cbd5b5..b024c0287 100644
--- a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs
+++ b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs
@@ -24,6 +24,9 @@ namespace FlaxEditor.SceneGraph.Actors
public sealed class StaticModelNode : ActorNode
{
private Dictionary _vertices;
+ private Vector3[] _selectionPoints;
+ private Transform _selectionPointsTransform;
+ private Model _selectionPointsModel;
///
public StaticModelNode(Actor actor)
@@ -31,6 +34,16 @@ namespace FlaxEditor.SceneGraph.Actors
{
}
+ ///
+ public override void OnDispose()
+ {
+ _vertices = null;
+ _selectionPoints = null;
+ _selectionPointsModel = null;
+
+ base.OnDispose();
+ }
+
///
public override bool OnVertexSnap(ref Ray ray, Real hitDistance, out Vector3 result)
{
@@ -88,6 +101,45 @@ namespace FlaxEditor.SceneGraph.Actors
contextMenu.AddButton("Add collider", () => OnAddMeshCollider(window)).Enabled = ((StaticModel)Actor).Model != null;
}
+ ///
+ 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();
+ 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
diff --git a/Source/Editor/SceneGraph/Actors/UICanvasNode.cs b/Source/Editor/SceneGraph/Actors/UICanvasNode.cs
index 91bd6c530..ef7663e5d 100644
--- a/Source/Editor/SceneGraph/Actors/UICanvasNode.cs
+++ b/Source/Editor/SceneGraph/Actors/UICanvasNode.cs
@@ -78,5 +78,11 @@ namespace FlaxEditor.SceneGraph.Actors
if (Actor is UICanvas uiCanvas && uiCanvas.Is3D)
DebugDraw.DrawWireBox(uiCanvas.Bounds, Color.BlueViolet);
}
+
+ ///
+ public override bool CanSelectActorNodeWithSelector()
+ {
+ return Actor is UICanvas uiCanvas && uiCanvas.Is3D && base.CanSelectActorNodeWithSelector();
+ }
}
}
diff --git a/Source/Editor/SceneGraph/Actors/UIControlNode.cs b/Source/Editor/SceneGraph/Actors/UIControlNode.cs
index 1d912db48..57a956665 100644
--- a/Source/Editor/SceneGraph/Actors/UIControlNode.cs
+++ b/Source/Editor/SceneGraph/Actors/UIControlNode.cs
@@ -40,5 +40,31 @@ namespace FlaxEditor.SceneGraph.Actors
control.PerformLayout();
}
}
+
+ ///
+ 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();
+ }
}
}
diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs
index cbb35e155..a1dc61b4a 100644
--- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs
+++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs
@@ -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;
///
/// 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,24 @@ namespace FlaxEditor.Viewport
TransformGizmo.EndTransforming();
}
+ ///
+ public override void OnLostFocus()
+ {
+ base.OnLostFocus();
+
+ if (_rubberBandSelector.StopRubberBand())
+ EndMouseCapture();
+ }
+
+ ///
+ public override void OnMouseLeave()
+ {
+ base.OnMouseLeave();
+
+ if (_rubberBandSelector.StopRubberBand())
+ EndMouseCapture();
+ }
+
///
/// Focuses the viewport on the current selection of the gizmo.
///
@@ -576,6 +605,27 @@ namespace FlaxEditor.Viewport
base.OrientViewport(ref orientation);
}
+ ///
+ 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);
+ _rubberBandSelector.TryCreateRubberBand(canStart, _viewMousePos, ViewFrustum);
+ }
+
+ ///
+ protected override void OnLeftMouseButtonDown()
+ {
+ base.OnLeftMouseButtonDown();
+
+ if (_rubberBandSelector.TryStartingRubberBandSelection())
+ {
+ StartMouseCapture();
+ }
+ }
+
///
protected override void OnLeftMouseButtonUp()
{
@@ -583,8 +633,14 @@ 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())
+ {
+ EndMouseCapture();
+
+ // Try to pick something with the current gizmo
+ Gizmos.Active?.Pick();
+ }
// Keep focus
Focus();
diff --git a/Source/Engine/Graphics/Mesh.cs b/Source/Engine/Graphics/Mesh.cs
index 42604c872..1b8c16dbe 100644
--- a/Source/Engine/Graphics/Mesh.cs
+++ b/Source/Engine/Graphics/Mesh.cs
@@ -633,5 +633,16 @@ namespace FlaxEngine
throw new Exception("Failed to download mesh data.");
return result;
}
+
+#if FLAX_EDITOR
+ ///
+ /// Gets the collision proxy points for the mesh.
+ ///
+ /// The triangle points in the collision proxy.
+ internal Vector3[] GetCollisionProxyPoints()
+ {
+ return Internal_GetCollisionProxyPoints(__unmanagedPtr, out _);
+ }
+#endif
}
}
diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp
index 77c2089e1..721aaffd8 100644
--- a/Source/Engine/Graphics/Models/Mesh.cpp
+++ b/Source/Engine/Graphics/Models/Mesh.cpp
@@ -847,4 +847,24 @@ MArray* Mesh::DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI
return result;
}
+#if USE_EDITOR
+
+Array Mesh::GetCollisionProxyPoints() const
+{
+ PROFILE_CPU();
+ Array 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
diff --git a/Source/Engine/Graphics/Models/Mesh.h b/Source/Engine/Graphics/Models/Mesh.h
index 813836b85..78c60ec50 100644
--- a/Source/Engine/Graphics/Models/Mesh.h
+++ b/Source/Engine/Graphics/Models/Mesh.h
@@ -320,5 +320,8 @@ private:
API_FUNCTION(NoProxy) bool UpdateTrianglesUInt(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);
+#if USE_EDITOR
+ API_FUNCTION(NoProxy) Array GetCollisionProxyPoints() const;
+#endif
#endif
};