From f3466fb5ddd0073f2f92932b8ed0ceaeb842b750 Mon Sep 17 00:00:00 2001 From: naam Date: Mon, 22 Jul 2024 12:34:15 +0200 Subject: [PATCH] Feature/better hover detection (#24) * Better hover detection, unoptimised * optimised by caching the matrix * cleanup * formatting --- Assets/Scripts/model/core/MMesh.cs | 34 +++++++++++++++++++++-- Assets/Scripts/model/core/SpatialIndex.cs | 30 ++++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/Assets/Scripts/model/core/MMesh.cs b/Assets/Scripts/model/core/MMesh.cs index 1bbbc3fd..1ac4b6cf 100644 --- a/Assets/Scripts/model/core/MMesh.cs +++ b/Assets/Scripts/model/core/MMesh.cs @@ -96,6 +96,11 @@ public partial class MMesh /// public Bounds bounds { get; private set; } + /// + /// Bounds of this mesh in local Mesh coordinates. + /// + public Bounds localBounds { get; private set; } + /// /// ID of the group to which this mesh belongs, or GROUP_NONE if this mesh is ungrouped. /// Meshes with the same groupId belong to the same group and stay together during @@ -139,7 +144,7 @@ public MMesh(int id, Vector3 offset, Quaternion rotation, public MMesh(int id, Vector3 offset, Quaternion rotation, Dictionary verticesById, Dictionary facesById, - Bounds bounds, Dictionary> reverseTable, int groupId = GROUP_NONE, + Bounds bounds, Bounds localBounds, Dictionary> reverseTable, int groupId = GROUP_NONE, HashSet remixIds = null) { _id = id; @@ -153,6 +158,7 @@ public MMesh(int id, Vector3 offset, Quaternion rotation, this.verticesById = verticesById; this.facesById = facesById; this.bounds = bounds; + this.localBounds = localBounds; this.reverseTable = reverseTable; this.groupId = groupId; this.remixIds = remixIds; @@ -186,6 +192,7 @@ public MMesh Clone() verticesCloned, facesCloned, bounds, + localBounds, reverseTableCloned, groupId, remixIdsCloned); @@ -220,6 +227,7 @@ public MMesh CloneWithNewIdAndGroup(int newId, int newGroupId) new Dictionary(verticesById), facesCloned, bounds, + localBounds, new Dictionary>(reverseTable), newGroupId, remixIds == null ? null : new HashSet(remixIds)); @@ -300,12 +308,21 @@ private void FinishOperation() /// /// Get the bounds for this mesh. /// - /// The bounds, in model coordinates. + /// The bounds, in model (ie: world) coordinates. public Bounds GetBounds() { return bounds; } + /// + /// Get the local bounds for this mesh. + /// + /// The bounds, in mesh coordinates. + public Bounds GetLocalBounds() + { + return localBounds; + } + /// /// Calculates and returns the bounds of a face in this mesh, in model space. /// @@ -462,19 +479,32 @@ public void RecalcBounds() // is an extreme hotspot. float minX = float.MaxValue, minY = float.MaxValue, minZ = float.MaxValue; float maxX = float.MinValue, maxY = float.MinValue, maxZ = float.MinValue; + float minXL = float.MaxValue, minYL = float.MaxValue, minZL = float.MaxValue; + float maxXL = float.MinValue, maxYL = float.MinValue, maxZL = float.MinValue; foreach (Vertex vert in verticesById.Values) { Vector3 loc = MeshCoordsToModelCoords(vert.loc); + Vector3 locL = vert.loc; minX = Mathf.Min(minX, loc.x); minY = Mathf.Min(minY, loc.y); minZ = Mathf.Min(minZ, loc.z); maxX = Mathf.Max(maxX, loc.x); maxY = Mathf.Max(maxY, loc.y); maxZ = Mathf.Max(maxZ, loc.z); + + minXL = Mathf.Min(minXL, locL.x); + minYL = Mathf.Min(minYL, locL.y); + minZL = Mathf.Min(minZL, locL.z); + maxXL = Mathf.Max(maxXL, locL.x); + maxYL = Mathf.Max(maxYL, locL.y); + maxZL = Mathf.Max(maxZL, locL.z); } bounds = new Bounds( /* center */ new Vector3((minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2), /* size */ new Vector3(maxX - minX, maxY - minY, maxZ - minZ)); + localBounds = new Bounds( + /* center */ new Vector3((minXL + maxXL) / 2, (minYL + maxYL) / 2, (minZL + maxZL) / 2), + /* size */ new Vector3(maxXL - minXL, maxYL - minYL, maxZL - minZL)); } /// diff --git a/Assets/Scripts/model/core/SpatialIndex.cs b/Assets/Scripts/model/core/SpatialIndex.cs index 8f295307..31f48a27 100644 --- a/Assets/Scripts/model/core/SpatialIndex.cs +++ b/Assets/Scripts/model/core/SpatialIndex.cs @@ -80,6 +80,9 @@ public class SpatialIndex private Dictionary faceInfo; private Dictionary edgeInfo; + private Dictionary worldToLocalMatrices; + private Dictionary meshBoundsLocal; + // A reference to the model, which is the single point of truth as to whether an item exists, despite the fact // that this spatial index contains collections of meshes and other items. // The reason we wish to treat the model as a single point of truth is that changes to the model happen on the @@ -115,6 +118,9 @@ private void Setup(Bounds bounds) faceInfo = new Dictionary(); edgeInfo = new Dictionary(); + + worldToLocalMatrices = new Dictionary(); + meshBoundsLocal = new Dictionary(); } /// @@ -174,6 +180,14 @@ public void RemoveMesh(MMesh mesh) { meshBounds.Remove(mesh.id); } + if (worldToLocalMatrices.ContainsKey(mesh.id)) + { + worldToLocalMatrices.Remove(mesh.id); + } + if (meshBoundsLocal.ContainsKey(mesh.id)) + { + meshBoundsLocal.Remove(mesh.id); + } if (meshes.HasItem(mesh.id)) { meshes.Remove(mesh.id); @@ -249,6 +263,7 @@ public bool FindMeshesClosestTo(Vector3 point, float radius, out List meshIds; @@ -277,6 +292,17 @@ public bool FindNearestMeshTo(Vector3 point, float radius, out int? nearestMesh, { continue; } + + // we've probed the world-aligned bounds, but meshes can be rotated. So we check now + // if the point lies within the rotated and offset bounds. + Bounds localBounds = meshBoundsLocal[meshId]; + Vector3 localPoint = worldToLocalMatrices[meshId] * point4; + if (!localBounds.Contains(localPoint)) + { + continue; + } + + float distanceToPoint = Vector3.Distance(bounds.center, point); if (distanceToPoint < minDistance) { @@ -846,6 +872,10 @@ private void LoadMeshIntoIndex(MMesh mesh, } meshes.Add(mesh.id, mesh.bounds); meshBounds.Add(mesh.id, mesh.bounds); + + worldToLocalMatrices.Add(mesh.id, Matrix4x4.TRS(mesh.offset, mesh.rotation, Vector3.one).inverse); + meshBoundsLocal.Add(mesh.id, mesh.localBounds); + foreach (KeyValuePair pair in faceInfos) { faces.Add(pair.Key, pair.Value.bounds);