diff --git a/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs b/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs
index cb7a7600c..a182fd149 100644
--- a/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs
+++ b/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs
@@ -202,21 +202,22 @@ namespace FlaxEditor.Gizmo
}
// Vertex snapping
- if (verts != null && SelectedModel != null && selectedvert != -1)
+ if (_vertexSnapObject != null)
{
if (!_modelCube || !_modelCube.IsLoaded)
return;
cubeMesh = _modelCube.LODs[0].Meshes[0];
- Transform t = SelectedModel.Transform;
- Vector3 selected = ((verts[selectedvert].Position * t.Orientation) * t.Scale) + t.Translation;
- Matrix matrix = new Transform(selected, t.Orientation, new Float3(gizmoModelsScale2RealGizmoSize)).GetWorld();
+ Transform t = _vertexSnapObject.Transform;
+ Vector3 p = t.LocalToWorld(_vertexSnapPoint);
+ Matrix matrix = new Transform(p, t.Orientation, new Float3(gizmoModelsScale2RealGizmoSize)).GetWorld();
cubeMesh.Draw(ref renderContext, _materialSphere, ref matrix);
- if (otherVerts != null && otherSelectedvert != -1)
+ if (_vertexSnapObjectTo != null)
{
- t = otherTransform;
- matrix = new Transform(selected, t.Orientation, new Float3(gizmoModelsScale2RealGizmoSize)).GetWorld();
+ t = _vertexSnapObjectTo.Transform;
+ p = t.LocalToWorld(_vertexSnapPointTo);
+ matrix = new Transform(p, t.Orientation, new Float3(gizmoModelsScale2RealGizmoSize)).GetWorld();
cubeMesh.Draw(ref renderContext, _materialSphere, ref matrix);
}
}
diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs
index 3e788d28f..bb6522047 100644
--- a/Source/Editor/Gizmo/TransformGizmoBase.cs
+++ b/Source/Editor/Gizmo/TransformGizmoBase.cs
@@ -53,14 +53,8 @@ namespace FlaxEditor.Gizmo
private Vector3 _translationDelta;
private Vector3 _translationScaleSnapDelta;
- //vertex snaping stff
- private Mesh.Vertex[] verts;
- private Mesh.Vertex[] otherVerts;
- private Transform otherTransform;
- private StaticModel SelectedModel;
- private bool hasSelectedVertex;
- private int selectedvert;
- private int otherSelectedvert;
+ private SceneGraphNode _vertexSnapObject, _vertexSnapObjectTo;
+ private Vector3 _vertexSnapPoint, _vertexSnapPointTo;
///
/// Gets the gizmo position.
@@ -160,11 +154,10 @@ namespace FlaxEditor.Gizmo
}
// Apply vertex snapping
- if (verts != null && SelectedModel != null)
+ if (_vertexSnapObject != null)
{
- Transform t = SelectedModel.Transform;
- Vector3 selected = ((verts[selectedvert].Position * t.Orientation) * t.Scale) + t.Translation;
- Position += -(Position - selected);
+ Vector3 vertexSnapPoint = _vertexSnapObject.Transform.LocalToWorld(_vertexSnapPoint);
+ Position += vertexSnapPoint - Position;
}
// Apply current movement
@@ -556,126 +549,93 @@ namespace FlaxEditor.Gizmo
Ray = Owner.MouseRay,
};
var closestDistance = Real.MaxValue;
- StaticModel closestModel = null;
+ SceneGraphNode closestObject = null;
for (int i = 0; i < SelectionCount; i++)
{
var obj = GetSelectedObject(i);
- if (obj.EditableObject is StaticModel model)
+ if (obj.CanVertexSnap && obj.RayCastSelf(ref ray, out var distance, out _) && distance < closestDistance)
{
- if (obj.RayCastSelf(ref ray, out var distance, out var normal) && distance < closestDistance)
- {
- closestDistance = distance;
- closestModel = model;
- }
+ closestDistance = distance;
+ closestObject = obj;
}
}
- if (closestModel == null)
+ if (closestObject == null)
{
// Find the closest object in selection (in case ray didn't hit anything)
for (int i = 0; i < SelectionCount; i++)
{
var obj = GetSelectedObject(i);
- if (obj.EditableObject is StaticModel model)
+ if (obj.CanVertexSnap)
{
- var bounds = model.Box;
+ GetSelectedObjectsBounds(out var bounds, out _);
CollisionsHelper.ClosestPointBoxPoint(ref bounds, ref ray.Ray.Position, out var point);
var distance = Vector3.Distance(ref point, ref ray.Ray.Position);
if (distance < closestDistance)
{
closestDistance = distance;
- closestModel = model;
+ closestObject = obj;
}
}
}
}
- SelectedModel = closestModel;
- if (closestModel == null)
+ _vertexSnapObject = closestObject;
+ if (closestObject == null)
return;
// Find the closest vertex to bounding box point (collision detection approximation)
- // TODO: replace this with collision detection which supports concave shapes (compute shader)
- var hitPoint = SelectedModel.Transform.WorldToLocal(ray.Ray.GetPoint(closestDistance));
- // TODO: support multi-mesh models
- verts = closestModel.Model.LODs[0].Meshes[0].DownloadVertexBuffer();
- closestDistance = Vector3.Distance(hitPoint, verts[0].Position);
- for (int j = 0; j < verts.Length; j++)
+ var closestPoint = ray.Ray.GetPoint(closestDistance);
+ if (!closestObject.OnVertexSnap(ref closestPoint, out _vertexSnapPoint))
{
- var distance = Vector3.Distance(hitPoint, verts[j].Position);
- if (distance <= closestDistance)
- {
- closestDistance = distance;
- selectedvert = j;
- }
+ // Failed to get the closest point
+ _vertexSnapPoint = closestPoint;
}
+
+ // Transform back to the local space of the object to work when moving it
+ _vertexSnapPoint = closestObject.Transform.WorldToLocal(_vertexSnapPoint);
}
private void EndVertexSnapping()
{
// Clear current vertex snapping data
- SelectedModel = null;
- verts = null;
- otherVerts = null;
+ _vertexSnapObject = null;
+ _vertexSnapObjectTo = null;
+ _vertexSnapPoint = _vertexSnapPointTo = Vector3.Zero;
}
private void UpdateVertexSnapping()
{
- if (Owner.SceneGraphRoot == null)
+ _vertexSnapObjectTo = null;
+ if (Owner.SceneGraphRoot == null || _vertexSnapObject == null)
return;
Profiler.BeginEvent("VertexSnap");
- // Ray cast others
- if (verts != null)
+ // Raycast nearby objects to snap to (excluding selection)
+ var rayCast = new SceneGraphNode.RayCastData
{
- var ray = Owner.MouseRay;
- var rayCast = new SceneGraphNode.RayCastData
+ Ray = Owner.MouseRay,
+ Flags = SceneGraphNode.RayCastData.FlagTypes.SkipColliders | SceneGraphNode.RayCastData.FlagTypes.SkipEditorPrimitives,
+ ExcludeObjects = new(),
+ };
+ for (int i = 0; i < SelectionCount; i++)
+ rayCast.ExcludeObjects.Add(GetSelectedObject(i));
+ var hit = Owner.SceneGraphRoot.RayCast(ref rayCast, out var distance, out var _);
+ if (hit != null && hit.CanVertexSnap)
+ {
+ var point = rayCast.Ray.GetPoint(distance);
+ if (hit.OnVertexSnap(ref point, out var pointSnapped)
+ //&& Vector3.Distance(point, pointSnapped) <= 25.0f
+ )
{
- Ray = ray,
- Flags = SceneGraphNode.RayCastData.FlagTypes.SkipColliders | SceneGraphNode.RayCastData.FlagTypes.SkipEditorPrimitives,
- ExcludeObjects = new(),
- };
- for (int i = 0; i < SelectionCount; i++)
- rayCast.ExcludeObjects.Add(GetSelectedObject(i));
+ _vertexSnapObjectTo = hit;
+ _vertexSnapPointTo = hit.Transform.WorldToLocal(pointSnapped);
- // Raycast objects
- var hit = Owner.SceneGraphRoot.RayCast(ref rayCast, out var distance, out var _);
- if (hit != null && hit.EditableObject is StaticModel model)
- {
- otherTransform = model.Transform;
- Vector3 point = rayCast.Ray.Position + (rayCast.Ray.Direction * distance);
-
- //[To Do] comlite this there is not suport for multy mesh model
- otherVerts = model.Model.LODs[0].Meshes[0].DownloadVertexBuffer();
-
- //find closest vertex to bounding box point (collision detection approximation)
- //[ToDo] replace this with collision detection with is suporting concave shapes (compute shader)
- point = hit.Transform.WorldToLocal(point);
- var closestDistance = Vector3.Distance(point, otherVerts[0].Position);
- for (int i = 0; i < otherVerts.Length; i++)
- {
- distance = Vector3.Distance(point, otherVerts[i].Position);
- if (distance < closestDistance)
- {
- closestDistance = distance;
- otherSelectedvert = i;
- }
- }
-
- if (closestDistance > 25)
- {
- otherSelectedvert = -1;
- otherVerts = null;
- }
+ // Snap current vertex to the other vertex
+ Vector3 selected = _vertexSnapObject.Transform.LocalToWorld(_vertexSnapPoint);
+ Vector3 other = _vertexSnapObjectTo.Transform.LocalToWorld(_vertexSnapPointTo);
+ _translationDelta = other - selected;
}
}
- if (verts != null && SelectedModel != null && otherVerts != null)
- {
- // Snap current vertex to the other vertex
- Vector3 selected = SelectedModel.Transform.LocalToWorld(verts[selectedvert].Position);
- Vector3 other = otherTransform.LocalToWorld(otherVerts[otherSelectedvert].Position);
- _translationDelta = other - selected;
- }
-
Profiler.EndEvent();
}
diff --git a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs
index 8de1a7f22..3acf34fba 100644
--- a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs
+++ b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
+using System.Collections.Generic;
using FlaxEditor.Content;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Windows;
@@ -15,12 +16,61 @@ namespace FlaxEditor.SceneGraph.Actors
[HideInEditor]
public sealed class StaticModelNode : ActorNode
{
+ private Dictionary _vertices;
+
///
public StaticModelNode(Actor actor)
: base(actor)
{
}
+ ///
+ public override bool OnVertexSnap(ref Vector3 point, out Vector3 result)
+ {
+ result = point;
+ var model = ((StaticModel)Actor).Model;
+ if (model && !model.WaitForLoaded())
+ {
+ // TODO: move to C++ and use cached vertex buffer internally inside the Mesh
+ if (_vertices == null)
+ _vertices = new();
+ var pointLocal = (Float3)Actor.Transform.WorldToLocal(point);
+ var minDistance = float.MaxValue;
+ foreach (var lod in model.LODs)
+ {
+ var hit = false;
+ foreach (var mesh in lod.Meshes)
+ {
+ var key = FlaxEngine.Object.GetUnmanagedPtr(mesh);
+ if (!_vertices.TryGetValue(key, out var verts))
+ {
+ verts = mesh.DownloadVertexBuffer();
+ if (verts == null)
+ continue;
+ _vertices.Add(key, verts);
+ }
+ for (int i = 0; i < verts.Length; i++)
+ {
+ var v = verts[i].Position;
+ var distance = Float3.DistanceSquared(ref pointLocal, ref v);
+ if (distance <= minDistance)
+ {
+ hit = true;
+ minDistance = distance;
+ result = v;
+ }
+ }
+ }
+ if (hit)
+ {
+ result = Actor.Transform.LocalToWorld(result);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
///
public override void OnContextMenu(ContextMenu contextMenu, EditorWindow window)
{
diff --git a/Source/Editor/SceneGraph/SceneGraphNode.cs b/Source/Editor/SceneGraph/SceneGraphNode.cs
index a8a26a1b3..cab2e9b6a 100644
--- a/Source/Editor/SceneGraph/SceneGraphNode.cs
+++ b/Source/Editor/SceneGraph/SceneGraphNode.cs
@@ -94,6 +94,18 @@ namespace FlaxEditor.SceneGraph
///
public virtual bool CanTransform => true;
+ ///
+ /// Gets a value indicating whether this node can be used for the vertex snapping feature.
+ ///
+ public bool CanVertexSnap
+ {
+ get
+ {
+ var v = Vector3.Zero;
+ return OnVertexSnap(ref v, out _);
+ }
+ }
+
///
/// Gets a value indicating whether this is active.
///
@@ -359,6 +371,18 @@ namespace FlaxEditor.SceneGraph
}
}
+ ///
+ /// Performs the vertex snapping of a given point on the object surface that is closest to a given location.
+ ///
+ /// The position to snap.
+ /// The result point on the object mesh that is closest to the specified location.
+ /// True if got a valid result value, otherwise false (eg. if missing data or not initialized).
+ public virtual bool OnVertexSnap(ref Vector3 point, out Vector3 result)
+ {
+ result = point;
+ return false;
+ }
+
///
/// Called when selected nodes should draw debug shapes using interface.
///