Skip to content

Commit 2bdac28

Browse files
author
Julia Schwarz
authored
Merge pull request #7016 from julenka/poke3
PokePointer ignore colliders that are not visible
2 parents c7f38a6 + 69e3683 commit 2bdac28

File tree

7 files changed

+266
-93
lines changed

7 files changed

+266
-93
lines changed

Assets/MixedRealityToolkit.SDK/Features/UX/Scripts/Pointers/PokePointer.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License. See LICENSE in the project root for license information.
33

44
using Microsoft.MixedReality.Toolkit.Physics;
5+
using Microsoft.MixedReality.Toolkit.Utilities;
56
using UnityEngine;
67

78
namespace Microsoft.MixedReality.Toolkit.Input
@@ -51,6 +52,25 @@ public class PokePointer : BaseControllerPointer, IMixedRealityNearPointer
5152
/// </summary>
5253
public int SceneQueryBufferSize => sceneQueryBufferSize;
5354

55+
[SerializeField]
56+
[Tooltip("Whether to ignore colliders that may be near the pointer, but not actually in the visual FOV. " +
57+
"This can prevent accidental touches, and will allow hand rays to turn on when you may be near a " +
58+
"touchable but cannot see it. Visual FOV is defined by cone centered about display center, " +
59+
"radius equal to half display height.")]
60+
private bool ignoreCollidersNotInFOV = true;
61+
62+
/// <summary>
63+
/// Whether to ignore colliders that may be near the pointer, but not actually in the visual FOV.
64+
/// This can prevent accidental touches, and will allow hand rays to turn on when you may be near
65+
/// a touchable but cannot see it. Visual FOV is defined by cone centered about display center,
66+
/// radius equal to half display height.
67+
/// </summary>
68+
public bool IgnoreCollidersNotInFOV
69+
{
70+
get => ignoreCollidersNotInFOV;
71+
set => ignoreCollidersNotInFOV = value;
72+
}
73+
5474
[SerializeField]
5575
[Tooltip("The LayerMasks, in prioritized order, that are used to determine the touchable objects.")]
5676
private LayerMask[] pokeLayerMasks = { UnityEngine.Physics.DefaultRaycastLayers };
@@ -183,11 +203,17 @@ private bool FindClosestTouchableForLayerMask(LayerMask layerMask, out BaseNearI
183203
Debug.LogWarning($"Maximum number of {numColliders} colliders found in PokePointer overlap query. Consider increasing the query buffer size in the input system settings.");
184204
}
185205

206+
Camera mainCam = CameraCache.Main;
186207
for (int i = 0; i < numColliders; ++i)
187208
{
188-
var touchable = queryBuffer[i].GetComponent<BaseNearInteractionTouchable>();
209+
var collider = queryBuffer[i];
210+
var touchable = collider.GetComponent<BaseNearInteractionTouchable>();
189211
if (touchable)
190212
{
213+
if (IgnoreCollidersNotInFOV && !mainCam.IsInFOVConeCached(collider))
214+
{
215+
continue;
216+
}
191217
float distance = touchable.DistanceToTouchable(Position, out Vector3 normal);
192218
if (distance < closestDistance)
193219
{

Assets/MixedRealityToolkit.SDK/Features/UX/Scripts/Pointers/SpherePointer.cs

Lines changed: 3 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License. See LICENSE in the project root for license information.
33

4-
using System.Collections.Generic;
4+
using Microsoft.MixedReality.Toolkit;
55
using Microsoft.MixedReality.Toolkit.Physics;
66
using Microsoft.MixedReality.Toolkit.Utilities;
77
using UnityEngine;
@@ -228,15 +228,6 @@ public bool TryGetNormalToNearestSurface(out Vector3 normal)
228228
/// </summary>
229229
private class SpherePointerQueryInfo
230230
{
231-
// List of corners shared across all sphere pointer query instances --
232-
// used to store list of corners for a bounds. Shared and static
233-
// to avoid allocating memory each frame
234-
private static List<Vector3> corners = new List<Vector3>();
235-
// Help to clear caches when new frame runs
236-
static private int lastCalculatedFrame = -1;
237-
// Map from grabbable => is the grabbable in FOV for this frame. Cleared every frame
238-
private static Dictionary<Collider, bool> colliderCache = new Dictionary<Collider, bool>();
239-
240231
/// <summary>
241232
/// How many colliders are near the point from the latest call to TryUpdateQueryBufferForLayerMask
242233
/// </summary>
@@ -290,6 +281,7 @@ public bool TryUpdateQueryBufferForLayerMask(LayerMask layerMask, Vector3 pointe
290281
Debug.LogWarning($"Maximum number of {numColliders} colliders found in SpherePointer overlap query. Consider increasing the query buffer size in the pointer profile.");
291282
}
292283

284+
Camera mainCam = CameraCache.Main;
293285
for (int i = 0; i < numColliders; i++)
294286
{
295287
Collider collider = queryBuffer[i];
@@ -298,7 +290,7 @@ public bool TryUpdateQueryBufferForLayerMask(LayerMask layerMask, Vector3 pointe
298290
{
299291
if (ignoreCollidersNotInFOV)
300292
{
301-
if (!isInFOVConeCached(collider))
293+
if (!mainCam.IsInFOVConeCached(collider))
302294
{
303295
// Additional check: is grabbable in the camera frustrum
304296
// We do this so that if grabbable is not visible it is not accidentally grabbed
@@ -316,60 +308,6 @@ public bool TryUpdateQueryBufferForLayerMask(LayerMask layerMask, Vector3 pointe
316308
return false;
317309
}
318310

319-
320-
/// <summary>
321-
/// Returns true if a collider's bounds is within the camera FOV.
322-
/// Utilizes a cache to test if this collider has been seen before and returns current frame's calculated result.
323-
/// </summary>
324-
/// <param name="myCollider">The collider to test</param>
325-
private bool isInFOVConeCached(Collider myCollider)
326-
{
327-
if (lastCalculatedFrame != Time.frameCount)
328-
{
329-
colliderCache.Clear();
330-
lastCalculatedFrame = Time.frameCount;
331-
}
332-
333-
if (colliderCache.TryGetValue(myCollider, out bool result))
334-
{
335-
return result;
336-
}
337-
338-
var cam = CameraCache.Main;
339-
corners.Clear();
340-
BoundsExtensions.GetColliderBoundsPoints(myCollider, corners, 0);
341-
342-
float xMin = float.MaxValue, yMin = float.MaxValue, zMin = float.MaxValue;
343-
float xMax = float.MinValue, yMax = float.MinValue, zMax = float.MinValue;
344-
for (int i = 0; i < corners.Count; i++)
345-
{
346-
var corner = corners[i];
347-
if (cam.IsInFOVCone(corner, 0))
348-
{
349-
colliderCache.Add(myCollider, true);
350-
return true;
351-
}
352-
353-
xMin = Mathf.Min(xMin, corner.x);
354-
yMin = Mathf.Min(yMin, corner.y);
355-
zMin = Mathf.Min(zMin, corner.z);
356-
xMax = Mathf.Max(xMax, corner.x);
357-
yMax = Mathf.Max(yMax, corner.y);
358-
zMax = Mathf.Max(zMax, corner.z);
359-
}
360-
361-
// edge case: check if camera is inside the entire bounds of the collider;
362-
// Consider simplifying to myCollider.bounds.Contains(CameraCache.main.transform.position)
363-
var cameraPos = cam.transform.position;
364-
result = xMin <= cameraPos.x && cameraPos.x <= xMax
365-
&& yMin <= cameraPos.y && cameraPos.y <= yMax
366-
&& zMin <= cameraPos.z && cameraPos.z <= zMax;
367-
368-
colliderCache.Add(myCollider, result);
369-
370-
return result;
371-
}
372-
373311
/// <summary>
374312
/// Returns true if any of the objects inside QueryBuffer contain a grabbable
375313
/// </summary>

Assets/MixedRealityToolkit.Tests/EditModeTests/Core/Extensions/CameraExtensionTests.cs

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ namespace Microsoft.MixedReality.Toolkit.Tests.Extensions
1010
public class CameraExtensionTests
1111
{
1212
private static Camera testCamera = null;
13+
private static Camera testCamera2 = null;
1314
private const float MarginTolerance = 0.005f;
1415

1516
private class TestPoint
@@ -23,7 +24,27 @@ public TestPoint(Vector3 point, bool isInFOV)
2324
}
2425
}
2526

27+
private class TestCollider
28+
{
29+
public bool ShouldBeInFOVCamera1, ShouldBeInFOVCamera2;
30+
public Vector3 Position { get; private set; }
31+
public Vector3 Bounds { get; private set; }
32+
public Collider Collider { get; private set; }
33+
public TestCollider(Vector3 point, Vector3 bounds, bool isInFOVCamera1, bool isInFOVCamera2)
34+
{
35+
ShouldBeInFOVCamera1 = isInFOVCamera1;
36+
ShouldBeInFOVCamera2 = isInFOVCamera2;
37+
Position = point;
38+
Bounds = bounds;
39+
GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
40+
obj.transform.position = Position;
41+
obj.transform.localScale = bounds;
42+
Collider = obj.GetComponent<BoxCollider>();
43+
}
44+
}
45+
2646
private static List<TestPoint> TestPoints = new List<TestPoint>();
47+
private List<TestCollider> TestColliders = new List<TestCollider>();
2748

2849
[SetUp]
2950
public void SetUp()
@@ -36,6 +57,14 @@ public void SetUp()
3657
testCamera.orthographic = false;
3758
testCamera.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity);
3859

60+
var obj2 = new GameObject("TestCamera2");
61+
testCamera2 = obj2.AddComponent<Camera>();
62+
testCamera2.nearClipPlane = 0.3f;
63+
testCamera2.farClipPlane = 1000.0f;
64+
testCamera2.fieldOfView = 60.0f;
65+
testCamera2.orthographic = false;
66+
testCamera2.transform.SetPositionAndRotation(Vector3.zero, Quaternion.Euler(0f, 180f, 0f));
67+
3968
// Create test data with pre-determined points.
4069
// This data expects the same results for both IsInFOVCone and IsInFOV
4170
TestPoints = new List<TestPoint>()
@@ -54,6 +83,26 @@ public void SetUp()
5483
new TestPoint(2.0f * Vector3.right, false),
5584
new TestPoint(2.0f * Vector3.up, false),
5685
};
86+
87+
Vector3 smallCubeSize = Vector3.one * 0.01f;
88+
Vector3 largeCubeSize = Vector3.one;
89+
Vector3 zeroCubeSize = Vector3.zero;
90+
TestColliders = new List<TestCollider>()
91+
{
92+
new TestCollider(-Vector3.forward, smallCubeSize, false, true),
93+
new TestCollider(-Vector3.forward - Vector3.right, largeCubeSize, false, true),
94+
new TestCollider(Vector3.forward, smallCubeSize, true, false),
95+
new TestCollider(Vector3.forward, smallCubeSize, true, false),
96+
new TestCollider(Vector3.zero, Vector3.zero, false, false),
97+
new TestCollider(Vector3.zero, largeCubeSize, true, true),
98+
new TestCollider(new Vector3(0.0f, 0.0f, testCamera.nearClipPlane), smallCubeSize, true, false),
99+
new TestCollider(new Vector3(0.0f, 0.0f, testCamera.farClipPlane), smallCubeSize, true, false),
100+
new TestCollider(new Vector3(0.0f, 0.0f, -testCamera.nearClipPlane), smallCubeSize, false, true),
101+
new TestCollider(new Vector3(0.0f, 0.0f, -testCamera.farClipPlane), smallCubeSize, false, true),
102+
new TestCollider(2.0f * Vector3.right, smallCubeSize, false, false),
103+
new TestCollider(2.0f * Vector3.up, smallCubeSize, false, false),
104+
};
105+
57106
}
58107

59108
[TearDown]
@@ -62,14 +111,43 @@ public void TearDown()
62111
GameObjectExtensions.DestroyGameObject(testCamera.gameObject);
63112
}
64113

114+
115+
/// <summary>
116+
/// Test that the Camera extension method IsInFOVConeCached returns valid results for colliders whose bounds are renderable to the camera
117+
/// </summary>
118+
[Test]
119+
public void TestIsInFOVConeCached()
120+
{
121+
for (int i = 0; i < TestColliders.Count; i++)
122+
{
123+
var test = TestColliders[i];
124+
Assert.AreEqual(test.ShouldBeInFOVCamera1, testCamera.IsInFOVConeCached(test.Collider), $"TestCollider[{i}] did not match");
125+
}
126+
}
127+
128+
/// <summary>
129+
/// Test that extension method IsInFOVConeCached gives expected results when called from multiple cameras
130+
/// facing different directions.
131+
/// </summary>
132+
[Test]
133+
public void TestIsInFOVConeCachedSecondCamera()
134+
{
135+
for (int i = 0; i < TestColliders.Count; i++)
136+
{
137+
var test = TestColliders[i];
138+
Assert.AreEqual(test.ShouldBeInFOVCamera1, testCamera.IsInFOVConeCached(test.Collider), $"TestCollider[{i}] did not match");
139+
Assert.AreEqual(test.ShouldBeInFOVCamera2, testCamera2.IsInFOVConeCached(test.Collider), $"TestColliderSecondCamera[{i}] did not match");
140+
}
141+
}
142+
65143
/// <summary>
66144
/// Test that the Camera extension method IsInFov returns valid points that would be renderable on the camera
67145
/// </summary>
68146
[Test]
69147
public void TestIsInFOV()
70148
{
71149
for (int i = 0; i < TestPoints.Count; i++)
72-
{
150+
{
73151
var test = TestPoints[i];
74152
Assert.AreEqual(test.ShouldBeInFOV, testCamera.IsInFOV(test.Point), $"TestPoint[{i}] at {test.Point} did not match");
75153
}

0 commit comments

Comments
 (0)