Skip to content

Commit ce987fe

Browse files
jloehrJulian LöhrRogPodge
authored
PokePointer and TouchableVolume fix for packed scenes and nested set-ups (#10531)
* Fix PokePointer hitting arbitrary colliders when touching volumes For packed scenes the huge ray range used for Touchable volumes, regularly hits arbitrary colliders in the scene, instead of the actual touched object. Touchable volumes will now properly calculate the penetration distance into the object. Further PokePointer ray has been adjusted to only cast through the very touchable volume. * Improve touch events for nested touchable volumes * Add unit tests for the touchable volume raycasting and nesting Co-authored-by: Julian Löhr <[email protected]> Co-authored-by: RogPodge <[email protected]>
1 parent 48aa466 commit ce987fe

File tree

3 files changed

+125
-20
lines changed

3 files changed

+125
-20
lines changed

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

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Microsoft Corporation.
1+
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

44
using Microsoft.MixedReality.Toolkit.Physics;
@@ -24,12 +24,6 @@ namespace Microsoft.MixedReality.Toolkit.Input
2424
[AddComponentMenu("Scripts/MRTK/SDK/PokePointer")]
2525
public class PokePointer : BaseControllerPointer, IMixedRealityNearPointer
2626
{
27-
/// <summary>
28-
/// If touchable volumes are larger than this size (meters), pointer will raise
29-
/// touch up even when pointer is inside the volume
30-
/// </summary>
31-
private const int maximumTouchableVolumeSize = 1000;
32-
3327
[SerializeField]
3428
[Tooltip("Maximum distance a which a touchable surface can be interacted with.")]
3529
protected float touchableDistance = 0.2f;
@@ -177,13 +171,20 @@ public override void OnPreSceneQuery()
177171
if (newClosestTouchable != null)
178172
{
179173
// Build ray (poke from in front to the back of the pointer position)
180-
// We make a very long ray if we are touching a touchable volume to ensure that we actually
181-
// hit the volume when we are inside of the volume, which could be very large.
182-
var lengthOfPointerRay = newClosestTouchable is NearInteractionTouchableVolume ?
183-
maximumTouchableVolumeSize : touchableDistance;
184-
Vector3 start = Position + lengthOfPointerRay * closestNormal;
185-
Vector3 end = Position - lengthOfPointerRay * closestNormal;
186-
Rays[0].UpdateRayStep(ref start, ref end);
174+
NearInteractionTouchableVolume touchableVolume = newClosestTouchable as NearInteractionTouchableVolume;
175+
if (touchableVolume != null && (closestDistance < 0.0f))
176+
{
177+
// When we are inside of a volume, ensure that we actually hit it by placing the origin closely outside the volume.
178+
Vector3 start = Position + (-closestDistance * 1.01f) * closestNormal;
179+
Vector3 end = Position - touchableVolume.TouchableCollider.bounds.size.magnitude * closestNormal;
180+
Rays[0].UpdateRayStep(ref start, ref end);
181+
}
182+
else
183+
{
184+
Vector3 start = Position + touchableDistance * closestNormal;
185+
Vector3 end = Position - touchableDistance * closestNormal;
186+
Rays[0].UpdateRayStep(ref start, ref end);
187+
}
187188
}
188189
else
189190
{
@@ -232,8 +233,12 @@ private bool FindClosestTouchableForLayerMask(LayerMask layerMask, out BaseNearI
232233
{
233234
continue;
234235
}
235-
float distance = touchable.DistanceToTouchable(Position, out Vector3 normal);
236-
if (distance < closestDistance)
236+
float distance = touchable.DistanceToTouchable(Position, out Vector3 normal);
237+
238+
// Favor touched volumes, but when there are multiple touched volumes, favor the one with the closest surface.
239+
bool bothInside = (distance <= 0f) && (closestDistance <= 0f);
240+
bool betterFit = bothInside ? Mathf.Abs(distance) < Mathf.Abs(closestDistance) : distance < closestDistance;
241+
if (betterFit)
237242
{
238243
closest = touchable;
239244
closestDistance = distance;

Assets/MRTK/Services/InputSystem/NearInteractionTouchableVolume.cs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Microsoft Corporation.
1+
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

44
using UnityEngine;
@@ -52,9 +52,21 @@ public override float DistanceToTouchable(Vector3 samplePoint, out Vector3 norma
5252
// inside object, use vector to centre as normal
5353
normal = samplePoint - TouchableCollider.bounds.center;
5454
normal.Normalize();
55-
// Return value less than zero so that when poke pointer is inside
56-
// object, it will not raise a touch up event.
57-
return -1;
55+
56+
// Try to calculate the proper penetration distance, to allow more accurate processing of touchable volumes.
57+
// Return value less than zero so that when poke pointer is inside object, it will not raise a touch up event.
58+
float rayScale = 1.1f;
59+
Vector3 outsidePoint = TouchableCollider.bounds.center + normal * (TouchableCollider.bounds.extents.magnitude * rayScale);
60+
if (TouchableCollider.Raycast(new Ray(outsidePoint, -normal), out RaycastHit raycastHit, TouchableCollider.bounds.size.magnitude * rayScale))
61+
{
62+
return -Vector3.Distance(raycastHit.point, samplePoint);
63+
}
64+
else
65+
{
66+
// Somehow we didn't hit the object, although we're touching it.
67+
// Fallback to the max possible value, so other volumes may get favored over this.
68+
return -TouchableCollider.bounds.extents.magnitude;
69+
}
5870
}
5971
else
6072
{

Assets/MRTK/Tests/PlayModeTests/NearInteractionTouchableTests.cs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,91 @@ public IEnumerator NearInteractionTouchableVolumeVariant()
318318
Object.Destroy(touchable.gameObject);
319319
}
320320

321+
/// <summary>
322+
/// Test NearInteractionTouchableVolume raycasting, by placing a signifcant larger touchable volume next to a smaller one.
323+
/// Move from one to the other and check if the touch events are only raised once, without loosing "touch".
324+
/// </summary>
325+
[UnityTest]
326+
public IEnumerator NearInteractionTouchableVolumeRaycast()
327+
{
328+
var touchable = CreateTouchable<NearInteractionTouchableVolume>(Vector3.one);
329+
var blocking = CreateTouchable<NearInteractionTouchableVolume>(new Vector3(10f, 10f, 1f));
330+
blocking.transform.localPosition = objectPosition - new Vector3(touchable.TouchableCollider.bounds.extents.z + blocking.TouchableCollider.bounds.extents.z, 0f, 0f);
331+
332+
yield return new WaitForFixedUpdate();
333+
yield return null;
334+
335+
TestHand testHand = new TestHand(Handedness.Left);
336+
yield return testHand.Show(blocking.transform.position);
337+
using (var catcher = CreateTouchEventCatcher(touchable))
338+
{
339+
// Move from center to center, checking if raycasting is hitting the correct touchable volume
340+
yield return testHand.MoveTo(objectPosition);
341+
Assert.AreEqual(1, catcher.EventsStarted);
342+
Assert.AreEqual(0, catcher.EventsCompleted);
343+
344+
// Move back
345+
yield return testHand.MoveTo(blocking.transform.position);
346+
Assert.AreEqual(1, catcher.EventsStarted);
347+
Assert.AreEqual(1, catcher.EventsCompleted);
348+
}
349+
350+
Object.Destroy(touchable.gameObject);
351+
Object.Destroy(blocking.gameObject);
352+
}
353+
/// <summary>
354+
/// Test nested NearInteractionTouchableVolume instances.
355+
/// </summary>
356+
[UnityTest]
357+
public IEnumerator NearInteractionTouchableVolumeNested()
358+
{
359+
var outer = CreateTouchable<NearInteractionTouchableVolume>(Vector3.one);
360+
var inner = CreateTouchable<NearInteractionTouchableVolume>(Vector3.one * 0.1f);
361+
362+
Vector3 innerPosition = objectPosition + (inner.transform.forward * 0.2f);
363+
inner.transform.localPosition = innerPosition;
364+
365+
var outerCatcher = CreateTouchEventCatcher(outer);
366+
var innerCatcher = CreateTouchEventCatcher(inner);
367+
368+
yield return new WaitForFixedUpdate();
369+
yield return null;
370+
371+
TestHand testHand = new TestHand(Handedness.Left);
372+
yield return testHand.Show(Vector3.zero);
373+
374+
// Move into outer cube
375+
yield return testHand.MoveTo(objectPosition);
376+
Assert.AreEqual(1, outerCatcher.EventsStarted);
377+
Assert.AreEqual(0, outerCatcher.EventsCompleted);
378+
Assert.AreEqual(0, innerCatcher.EventsStarted);
379+
Assert.AreEqual(0, innerCatcher.EventsCompleted);
380+
381+
// Move from outer into inner cube
382+
yield return testHand.MoveTo(innerPosition);
383+
Assert.AreEqual(1, outerCatcher.EventsStarted);
384+
Assert.AreEqual(1, outerCatcher.EventsCompleted);
385+
Assert.AreEqual(1, innerCatcher.EventsStarted);
386+
Assert.AreEqual(0, innerCatcher.EventsCompleted);
387+
388+
// Move fromm inner to outer cube back
389+
yield return testHand.MoveTo(objectPosition);
390+
Assert.AreEqual(2, outerCatcher.EventsStarted);
391+
Assert.AreEqual(1, outerCatcher.EventsCompleted);
392+
Assert.AreEqual(1, innerCatcher.EventsStarted);
393+
Assert.AreEqual(1, innerCatcher.EventsCompleted);
394+
395+
// Move from outer back outside
396+
yield return testHand.MoveTo(Vector3.zero);
397+
Assert.AreEqual(2, outerCatcher.EventsStarted);
398+
Assert.AreEqual(2, outerCatcher.EventsCompleted);
399+
Assert.AreEqual(1, innerCatcher.EventsStarted);
400+
Assert.AreEqual(1, innerCatcher.EventsCompleted);
401+
402+
Object.Destroy(outer.gameObject);
403+
Object.Destroy(inner.gameObject);
404+
}
405+
321406
private static void TestEvents(TouchEventCatcher[] catchers, int[] eventsStarted, int[] eventsCompleted)
322407
{
323408
Assert.AreEqual(catchers.Length, eventsCompleted.Length);
@@ -741,6 +826,9 @@ public IEnumerator NearInteractionTouchableSetTouchableCollider()
741826
yield return PlayModeTestUtilities.WaitForInputSystemUpdate();
742827
Assert.AreEqual(2, catcher.EventsCompleted);
743828
}
829+
830+
Object.Destroy(cube);
831+
Object.Destroy(cube2);
744832
}
745833
}
746834
}

0 commit comments

Comments
 (0)