Skip to content

Commit 3549fa0

Browse files
author
Mike Thomas
committed
Adding support for hand based object manipulation
Holograms 211 contains a relatively simple implementation of hand based object manipulation. That implementation uses the world space deltas to move holograms exactly as the hand moves. This works fine in some cases, but becomes limiting in more sophisticated manipulation scenarios. The implementation in this change is taken directly from 3D Viewer and ActionGrams and supports more sophisticated movement scenarios which combine both gesture and gaze. For example, with this new implementation if you manipulate a hologram and then turn your head the hologram will move with your head, while still allowing you to make adjustments with your hands. If you manipulate a hologram and then walk somewhere else the hologram will move with you as long as you maintain the gesture. This allows for users to make large adjustments using their gaze and position, while making smaller adjustments with their hands, resulting in more intuitive and effective manipulation.
1 parent a47f3b1 commit 3549fa0

File tree

6 files changed

+805
-20
lines changed

6 files changed

+805
-20
lines changed

Assets/HoloToolkit/Input/README.md

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,24 @@ Stabilize the user's gaze to account for head jitter.
7272
**StabilityVarianceWeight** Stability variance weight multiplier factor.
7373

7474
#### GestureManager.cs
75-
GestureManager creates a gesture recognizer and signs up for a tap gesture. When a tap gesture is detected, GestureManager uses GazeManager to find the game object.
76-
GestureManager then sends a message to that game object.
75+
GestureManager provides access to several different input gestures, including Tap and Manipulation.
7776

78-
It also has an **OverrideFocusedObject** which lets you send gesture input to a specific object by overriding the gaze.
77+
When a tap gesture is detected, GestureManager uses GazeManager to find the game object.
78+
GestureManager then sends a message to that game object. It also has an **OverrideFocusedObject** which lets you send gesture input to a specific object by overriding the gaze.
79+
80+
Using Manipulation requires subscribing to the ManipulationStarted events and then querying information about the manipulation gesture via ManipulationOffset and ManipulationHandPosition. See GestureManipulator for an example.
81+
82+
#### GestureManipulator.cs
83+
A component for moving an object via the GestureManager manipulation gesture.
84+
85+
When an active GestureManipulator component is attached to a GameObject it will subscribe
86+
to GestureManager's manipulation gestures, and move the GameObject when a ManipulationGesture occurs.
87+
If the GestureManipulator is disabled it will not respond to any manipulation gestures.
88+
89+
This means that if multiple GestureManipulators are active in a given scene when a manipulation
90+
gesture is performed, all the relevant GameObjects will be moved. If the desired behavior is that only
91+
a single object be moved at a time, it is recommended that objects which should not be moved disable
92+
their GestureManipulators, then re-enable them when necessary (e.g. the object is focused).
7993

8094
#### HandGuidance.cs
8195
Show a GameObject when a gesturing hand is about to lose tracking.

Assets/HoloToolkit/Input/Scripts/GestureManager.cs

Lines changed: 165 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,40 @@
33

44
using UnityEngine;
55
using UnityEngine.VR.WSA.Input;
6+
using System.Collections.Generic;
67

78
namespace HoloToolkit.Unity
89
{
910
/// <summary>
10-
/// GestureManager creates a gesture recognizer and signs up for a tap gesture.
11+
/// GestureManager provides access to several different input gestures, including
12+
/// Tap and Manipulation.
13+
/// </summary>
14+
/// <remarks>
1115
/// When a tap gesture is detected, GestureManager uses GazeManager to find the game object.
1216
/// GestureManager then sends a message to that game object.
13-
/// </summary>
17+
///
18+
/// Using Manipulation requires subscribing the the ManipulationStarted events and then querying
19+
/// information about the manipulation gesture via ManipulationOffset and ManipulationHandPosition
20+
/// </remarks>
1421
[RequireComponent(typeof(GazeManager))]
1522
public partial class GestureManager : Singleton<GestureManager>
1623
{
24+
/// <summary>
25+
/// Occurs when a manipulation gesture has started
26+
/// </summary>
27+
public System.Action ManipulationStarted;
28+
29+
/// <summary>
30+
/// Occurs when a manipulation gesture ended as a result of user input
31+
/// </summary>
32+
public System.Action ManipulationCompleted;
33+
34+
/// <summary>
35+
/// Occurs when a manipulated gesture ended as a result of some other condition.
36+
/// (e.g. the hand being used for the gesture is no longer visible).
37+
/// </summary>
38+
public System.Action ManipulationCanceled;
39+
1740
/// <summary>
1841
/// Key to press in the editor to select the currently gazed hologram
1942
/// </summary>
@@ -24,66 +47,180 @@ public partial class GestureManager : Singleton<GestureManager>
2447
/// set the override focused object.
2548
/// If its null, then the gazed at object will be selected.
2649
/// </summary>
27-
public GameObject OverrideFocusedObject
28-
{
29-
get; set;
30-
}
31-
50+
public GameObject OverrideFocusedObject { get; set; }
51+
3252
/// <summary>
3353
/// Gets the currently focused object, or null if none.
3454
/// </summary>
35-
public GameObject FocusedObject
55+
public GameObject FocusedObject { get; private set; }
56+
57+
/// <summary>
58+
/// Whether or not a manipulation gesture is currently in progress
59+
/// </summary>
60+
public bool ManipulationInProgress { get; private set; }
61+
62+
/// <summary>
63+
/// The offset of the hand from its position at the beginning of
64+
/// the currently active manipulation gesture, in world space. Not valid if
65+
/// a manipulation gesture is not in progress
66+
/// </summary>
67+
public Vector3 ManipulationOffset { get; private set; }
68+
69+
/// <summary>
70+
/// The world space position of the hand being used for the current manipulation gesture. Not valid
71+
/// if a manipulation gesture is not in progress.
72+
/// </summary>
73+
public Vector3 ManipulationHandPosition
3674
{
37-
get { return focusedObject; }
75+
get
76+
{
77+
Vector3 handPosition = Vector3.zero;
78+
currentHandState.properties.location.TryGetPosition(out handPosition);
79+
return handPosition;
80+
}
3881
}
3982

4083
private GestureRecognizer gestureRecognizer;
41-
private GameObject focusedObject;
84+
// We use a separate manipulation recognizer here because the tap gesture recognizer cancels
85+
// capturing gestures whenever the GazeManager focus changes, which is not the behavior
86+
// we want for manipulation
87+
private GestureRecognizer manipulationRecognizer;
88+
89+
private bool HandPressed { get { return pressedHands.Count > 0; } }
90+
private HashSet<uint> pressedHands = new HashSet<uint>();
91+
92+
private InteractionSourceState currentHandState;
4293

4394
void Start()
4495
{
96+
InteractionManager.SourcePressed += InteractionManager_SourcePressed;
97+
InteractionManager.SourceReleased += InteractionManager_SourceReleased;
98+
InteractionManager.SourceUpdated += InteractionManager_SourceUpdated;
99+
InteractionManager.SourceLost += InteractionManager_SourceLost;
100+
45101
// Create a new GestureRecognizer. Sign up for tapped events.
46102
gestureRecognizer = new GestureRecognizer();
47103
gestureRecognizer.SetRecognizableGestures(GestureSettings.Tap);
48104

105+
manipulationRecognizer = new GestureRecognizer();
106+
manipulationRecognizer.SetRecognizableGestures(GestureSettings.ManipulationTranslate);
107+
49108
gestureRecognizer.TappedEvent += GestureRecognizer_TappedEvent;
50109

110+
manipulationRecognizer.ManipulationStartedEvent += ManipulationRecognizer_ManipulationStartedEvent;
111+
manipulationRecognizer.ManipulationUpdatedEvent += ManipulationRecognizer_ManipulationUpdatedEvent;
112+
manipulationRecognizer.ManipulationCompletedEvent += ManipulationRecognizer_ManipulationCompletedEvent;
113+
manipulationRecognizer.ManipulationCanceledEvent += ManipulationRecognizer_ManipulationCanceledEvent;
114+
51115
// Start looking for gestures.
52116
gestureRecognizer.StartCapturingGestures();
117+
manipulationRecognizer.StartCapturingGestures();
53118
}
54119

55-
private void OnTap()
120+
121+
122+
private void InteractionManager_SourcePressed(InteractionSourceState state)
123+
{
124+
if (!HandPressed)
125+
{
126+
currentHandState = state;
127+
}
128+
129+
pressedHands.Add(state.source.id);
130+
}
131+
132+
private void InteractionManager_SourceUpdated(InteractionSourceState state)
56133
{
57-
if (focusedObject != null)
134+
if (HandPressed && state.source.id == currentHandState.source.id)
58135
{
59-
focusedObject.SendMessage("OnSelect");
136+
currentHandState = state;
60137
}
61138
}
62139

140+
private void InteractionManager_SourceReleased(InteractionSourceState state)
141+
{
142+
pressedHands.Remove(state.source.id);
143+
}
144+
145+
private void InteractionManager_SourceLost(InteractionSourceState state)
146+
{
147+
pressedHands.Remove(state.source.id);
148+
}
149+
63150
private void GestureRecognizer_TappedEvent(InteractionSourceKind source, int tapCount, Ray headRay)
64151
{
65152
OnTap();
66153
}
67154

155+
private void OnTap()
156+
{
157+
if (FocusedObject != null)
158+
{
159+
FocusedObject.SendMessage("OnSelect");
160+
}
161+
}
162+
163+
private void ManipulationRecognizer_ManipulationStartedEvent(InteractionSourceKind source, Vector3 cumulativeDelta, Ray headRay)
164+
{
165+
// Don't start another manipulation gesture if one is already underway
166+
if (!ManipulationInProgress)
167+
{
168+
OnManipulation(true, cumulativeDelta);
169+
if (ManipulationStarted != null)
170+
{
171+
ManipulationStarted();
172+
}
173+
}
174+
}
175+
176+
private void ManipulationRecognizer_ManipulationUpdatedEvent(InteractionSourceKind source, Vector3 cumulativeDelta, Ray headRay)
177+
{
178+
OnManipulation(true, cumulativeDelta);
179+
}
180+
181+
private void ManipulationRecognizer_ManipulationCompletedEvent(InteractionSourceKind source, Vector3 cumulativeDelta, Ray headRay)
182+
{
183+
OnManipulation(false, cumulativeDelta);
184+
if (ManipulationCompleted != null)
185+
{
186+
ManipulationCompleted();
187+
}
188+
}
189+
190+
private void ManipulationRecognizer_ManipulationCanceledEvent(InteractionSourceKind source, Vector3 cumulativeDelta, Ray headRay)
191+
{
192+
OnManipulation(false, cumulativeDelta);
193+
if (ManipulationCanceled != null)
194+
{
195+
ManipulationCanceled();
196+
}
197+
}
198+
199+
private void OnManipulation(bool inProgress, Vector3 offset)
200+
{
201+
ManipulationInProgress = inProgress;
202+
ManipulationOffset = offset;
203+
}
204+
68205
void LateUpdate()
69206
{
70-
GameObject oldFocusedObject = focusedObject;
207+
GameObject oldFocusedObject = FocusedObject;
71208

72209
if (GazeManager.Instance.Hit &&
73210
OverrideFocusedObject == null &&
74211
GazeManager.Instance.HitInfo.collider != null)
75212
{
76213
// If gaze hits a hologram, set the focused object to that game object.
77214
// Also if the caller has not decided to override the focused object.
78-
focusedObject = GazeManager.Instance.HitInfo.collider.gameObject;
215+
FocusedObject = GazeManager.Instance.HitInfo.collider.gameObject;
79216
}
80217
else
81218
{
82219
// If our gaze doesn't hit a hologram, set the focused object to null or override focused object.
83-
focusedObject = OverrideFocusedObject;
220+
FocusedObject = OverrideFocusedObject;
84221
}
85222

86-
if (focusedObject != oldFocusedObject)
223+
if (FocusedObject != oldFocusedObject)
87224
{
88225
// If the currently focused object doesn't match the old focused object, cancel the current gesture.
89226
// Start looking for new gestures. This is to prevent applying gestures from one hologram to another.
@@ -103,6 +240,17 @@ void OnDestroy()
103240
{
104241
gestureRecognizer.StopCapturingGestures();
105242
gestureRecognizer.TappedEvent -= GestureRecognizer_TappedEvent;
243+
244+
manipulationRecognizer.StopCapturingGestures();
245+
manipulationRecognizer.ManipulationStartedEvent -= ManipulationRecognizer_ManipulationStartedEvent;
246+
manipulationRecognizer.ManipulationUpdatedEvent -= ManipulationRecognizer_ManipulationUpdatedEvent;
247+
manipulationRecognizer.ManipulationCompletedEvent -= ManipulationRecognizer_ManipulationCompletedEvent;
248+
manipulationRecognizer.ManipulationCanceledEvent -= ManipulationRecognizer_ManipulationCanceledEvent;
249+
250+
InteractionManager.SourcePressed -= InteractionManager_SourcePressed;
251+
InteractionManager.SourceReleased -= InteractionManager_SourceReleased;
252+
InteractionManager.SourceUpdated -= InteractionManager_SourceUpdated;
253+
InteractionManager.SourceLost -= InteractionManager_SourceLost;
106254
}
107255
}
108256
}

0 commit comments

Comments
 (0)