Skip to content

Commit 02c4c28

Browse files
Merge pull request #614 from Unity-Technologies/bugfix/edge-hover-selection-fix
Fixed issue where selecting edge would select another edge
2 parents 8dfd61f + 1dec924 commit 02c4c28

File tree

4 files changed

+343
-24
lines changed

4 files changed

+343
-24
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
99

1010
### Fixed
1111

12+
- [PBLD-242] Fixed a bug where edges were being incorrectly selected when one or both vertices were behind the camera's near plane, causing flipped lines and inconsistent selection behavior.
1213
- [PBLD-226] Fixed a bug where ProBuilder faces could not selected when obscured by another GameObject
1314

1415
## [6.0.6] - 2025-07-01

Editor/EditorCore/EditorSceneViewPicker.cs

Lines changed: 101 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -684,31 +684,35 @@ static float EdgeRaycast(Vector3 mousePosition, ScenePickerPreferences pickerPre
684684
int x = edge.a;
685685
int y = edge.b;
686686

687-
float d = UHandleUtility.DistanceToLine(
688-
trs.TransformPoint(positions[x]),
689-
trs.TransformPoint(positions[y]));
687+
Vector3 worldPosA = trs.TransformPoint(positions[x]);
688+
Vector3 worldPosB = trs.TransformPoint(positions[y]);
690689

691-
d *= distMultiplier;
690+
if (ProcessEdgePoints(Camera.current, worldPosA, worldPosB, out Vector3 guiPointA, out Vector3 guiPointB))
691+
{
692+
// Calculate distance from the mouse position to the line
693+
float d = HandleUtility.DistancePointLine(mousePosition, guiPointA, guiPointB);
694+
d *= distMultiplier;
692695

693-
// best distance isn't set to maxPointerDistance because we want to preserve an unselected
694-
// gameobject over a selected gameobject with an out of bounds edge.
695-
if (d > ScenePickerPreferences.maxPointerDistance)
696-
continue;
696+
// best distance isn't set to maxPointerDistance because we want to preserve an unselected
697+
// gameobject over a selected gameobject with an out of bounds edge.
698+
if (d > ScenePickerPreferences.maxPointerDistance)
699+
continue;
697700

698-
// account for stacked edges
699-
if (Mathf.Approximately(d, bestDistance))
700-
{
701-
s_EdgeBuffer.Add(new Edge(x, y));
702-
}
703-
else if (d < bestDistance)
704-
{
705-
s_EdgeBuffer.Clear();
706-
s_EdgeBuffer.Add(new Edge(x, y));
701+
// account for stacked edges
702+
if (Mathf.Approximately(d, bestDistance))
703+
{
704+
s_EdgeBuffer.Add(new Edge(x, y));
705+
}
706+
else if (d < bestDistance)
707+
{
708+
s_EdgeBuffer.Clear();
709+
s_EdgeBuffer.Add(new Edge(x, y));
707710

708-
selection.gameObject = mesh.gameObject;
709-
selection.mesh = mesh;
710-
selection.SetSingleEdge(new Edge(x, y));
711-
bestDistance = d;
711+
selection.gameObject = mesh.gameObject;
712+
selection.mesh = mesh;
713+
selection.SetSingleEdge(new Edge(x, y));
714+
bestDistance = d;
715+
}
712716
}
713717
}
714718
}
@@ -722,6 +726,20 @@ static float EdgeRaycast(Vector3 mousePosition, ScenePickerPreferences pickerPre
722726
return selection.gameObject != null ? bestDistance : Mathf.Infinity;
723727
}
724728

729+
730+
/// <summary>
731+
/// Function to clip a point to the near plane.
732+
/// </summary>
733+
/// <param name="pointBehind"> the point behind the plane</param>
734+
/// <param name="pointInFront"> the point in front of te plane</param>
735+
/// <param name="nearPlaneZ"> the camera near plane z</param>
736+
/// <returns>The intersection point between the line and the near plane</returns>
737+
static Vector3 ClipPointToNearPlane(Vector3 pointBehind, Vector3 pointInFront, float nearPlaneZ)
738+
{
739+
float t = (nearPlaneZ - pointBehind.z) / (pointInFront.z - pointBehind.z);
740+
return pointBehind + t * (pointInFront - pointBehind);
741+
}
742+
725743
static Edge GetClosestEdgeToCamera(Vector3[] positions, IEnumerable<Edge> edges)
726744
{
727745
var camPos = SceneView.lastActiveSceneView.camera.transform.position;
@@ -813,12 +831,71 @@ static EdgeAndDistance GetNearestEdgeOnMesh(ProBuilderMesh mesh, Vector3 mousePo
813831
}
814832

815833
if (res.edge.IsValid())
816-
res.distance = UHandleUtility.DistanceToLine(
817-
mesh.transform.TransformPoint(v[res.edge.a]),
818-
mesh.transform.TransformPoint(v[res.edge.b]));
834+
{
835+
Vector3 worldPosA = mesh.transform.TransformPoint(v[res.edge.a]);
836+
Vector3 worldPosB = mesh.transform.TransformPoint(v[res.edge.b]);
837+
838+
if (ProcessEdgePoints(Camera.current, worldPosA, worldPosB, out Vector3 guiPointA, out Vector3 guiPointB))
839+
{
840+
// Calculate distance from the mouse position to the line
841+
res.distance = HandleUtility.DistancePointLine(mousePosition, guiPointA, guiPointB);
842+
}
843+
else
844+
{
845+
// If both points are behind the camera, skip this edge
846+
res.distance = Mathf.Infinity;
847+
}
848+
}
819849
}
820850

821851
return res;
822852
}
853+
854+
static bool ProcessEdgePoints(Camera camera, Vector3 worldPosA, Vector3 worldPosB, out Vector3 guiPointA, out Vector3 guiPointB)
855+
{
856+
guiPointA = Vector3.zero;
857+
guiPointB = Vector3.zero;
858+
859+
// Check if the points are in front of the camera
860+
bool pointAInFront = Vector3.Dot(camera.transform.forward, worldPosA - camera.transform.position) > 0;
861+
bool pointBInFront = Vector3.Dot(camera.transform.forward, worldPosB - camera.transform.position) > 0;
862+
863+
// If both points are behind the camera, skip this edge
864+
if (!pointAInFront && !pointBInFront)
865+
{
866+
return false;
867+
}
868+
869+
// Check if either point is behind the camera
870+
if (!pointAInFront || !pointBInFront)
871+
{
872+
// Clip points against the near plane
873+
Vector3 cameraSpacePointA = camera.transform.InverseTransformPoint(worldPosA);
874+
Vector3 cameraSpacePointB = camera.transform.InverseTransformPoint(worldPosB);
875+
876+
float nearPlaneZ = camera.nearClipPlane;
877+
878+
if (cameraSpacePointA.z < nearPlaneZ)
879+
{
880+
cameraSpacePointA = ClipPointToNearPlane(cameraSpacePointA, cameraSpacePointB, nearPlaneZ);
881+
}
882+
883+
if (cameraSpacePointB.z < nearPlaneZ)
884+
{
885+
cameraSpacePointB = ClipPointToNearPlane(cameraSpacePointB, cameraSpacePointA, nearPlaneZ);
886+
}
887+
888+
// Transform clipped points back to screen space
889+
guiPointA = HandleUtility.WorldToGUIPoint(camera.transform.TransformPoint(cameraSpacePointA));
890+
guiPointB = HandleUtility.WorldToGUIPoint(camera.transform.TransformPoint(cameraSpacePointB));
891+
}
892+
else // Both points are in front of the camera
893+
{
894+
guiPointA = HandleUtility.WorldToGUIPoint(worldPosA);
895+
guiPointB = HandleUtility.WorldToGUIPoint(worldPosB);
896+
}
897+
898+
return true;
899+
}
823900
}
824901
}

0 commit comments

Comments
 (0)