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. ///