Tanoda
WorkstationBehaviourExample.cs
Go to the documentation of this file.
1/******************************************************************************
2 * Copyright (C) Ultraleap, Inc. 2011-2020. *
3 * *
4 * Use subject to the terms of the Apache License 2.0 available at *
5 * http://www.apache.org/licenses/LICENSE-2.0, or another agreement *
6 * between Ultraleap and you, your company or other organization. *
7 ******************************************************************************/
8
11using System.Collections;
12using System.Collections.Generic;
13using UnityEngine;
14
15namespace Leap.Unity.Examples {
16
34 [RequireComponent(typeof(InteractionBehaviour))]
35 [RequireComponent(typeof(AnchorableBehaviour))]
36 [AddComponentMenu("")]
37 public class WorkstationBehaviourExample : MonoBehaviour {
38
43 public const float MAX_SPEED_AS_WORKSTATION = 0.005F;
44
50 public const float MIN_SPEED_TO_ACTIVATE_TRAVELING = 0.5F;
51
53
54 private InteractionBehaviour _intObj;
55 private AnchorableBehaviour _anchObj;
56
57 private bool _wasKinematicBeforeActivation = false;
58
64 public bool isTraveling { get { return _travelTween.isValid && _travelTween.isRunning; } }
65
66 public enum WorkstationState { Closed, Traveling, Opening, Open, Closing }
68 get {
69 if (workstationModeTween == null) return WorkstationState.Closed;
70 else {
72 return WorkstationState.Closed;
73 }
74 else if (isTraveling) {
75 return WorkstationState.Traveling;
76 }
79 return WorkstationState.Opening;
80 }
81 else if (workstationModeTween.tween.progress == 1F) {
82 return WorkstationState.Open;
83 }
84 else {
85 return WorkstationState.Closing;
86 }
87 }
88 }
89 }
90
91 void OnValidate() {
92 refreshRequiredComponents();
93 }
94
95 void Awake() {
96 initWorkstationPoseFunctions();
97 }
98
99 void Start() {
100 refreshRequiredComponents();
101
102 if (!_anchObj.tryAnchorNearestOnGraspEnd) {
103 Debug.LogWarning("WorkstationBehaviour expects its AnchorableBehaviour's tryAnchorNearestOnGraspEnd property to be enabled.", this.gameObject);
104 }
105 }
106
107 void OnDestroy() {
108 _intObj.OnGraspedMovement -= onGraspedMovement;
109
110 _anchObj.OnPostTryAnchorOnGraspEnd -= onPostObjectGraspEnd;
111 }
112
113 public void ActivateWorkstation() {
115 _wasKinematicBeforeActivation = _intObj.rigidbody.isKinematic;
116 _intObj.rigidbody.isKinematic = true;
117 }
118
120 }
121
122 public void DeactivateWorkstation() {
123 _intObj.rigidbody.isKinematic = _wasKinematicBeforeActivation;
124
126 }
127
128 private void refreshRequiredComponents() {
129 _intObj = GetComponent<InteractionBehaviour>();
130 _anchObj = GetComponent<AnchorableBehaviour>();
131
132 _intObj.OnGraspedMovement += onGraspedMovement;
133
134 _anchObj.OnPostTryAnchorOnGraspEnd += onPostObjectGraspEnd;
135 }
136
137 private void onGraspedMovement(Vector3 preSolvePos, Quaternion preSolveRot,
138 Vector3 curPos, Quaternion curRot,
139 List<InteractionController> controllers) {
140 // If the workstation is not fully open when grasped, close it.
141 if (workstationState == WorkstationState.Opening) {
143 }
144
145 // If the velocity of the object while grasped is too large, exit workstation mode.
146 if (_intObj.rigidbody.velocity.magnitude > MAX_SPEED_AS_WORKSTATION
147 || (_intObj.rigidbody.isKinematic && ((preSolvePos - curPos).magnitude / Time.fixedDeltaTime) > MAX_SPEED_AS_WORKSTATION)) {
149 }
150
151 // Lock our upward axis while workstation mode is open.
153 _intObj.rigidbody.rotation = (Quaternion.FromToRotation(_intObj.rigidbody.rotation * Vector3.up, Vector3.up)) * _intObj.rigidbody.rotation;
154 _intObj.transform.rotation = _intObj.rigidbody.rotation;
155 }
156 }
157
158 private void onPostObjectGraspEnd() {
159 if (_anchObj.preferredAnchor == null) {
160 // Choose a good position and rotation for workstation mode and begin traveling there.
161 Vector3 targetPosition;
162
163 // Choose the current position if our velocity is small.
164 if (_intObj.rigidbody.velocity.magnitude < MIN_SPEED_TO_ACTIVATE_TRAVELING) {
165 targetPosition = _intObj.rigidbody.position;
166 }
167 else {
168 targetPosition = determineWorkstationPosition();
169 }
170
171 Quaternion targetRotation = determineWorkstationRotation(targetPosition);
172
173 beginTraveling(_intObj.rigidbody.position, _intObj.rigidbody.velocity,
174 _intObj.rigidbody.rotation, _intObj.rigidbody.angularVelocity,
175 targetPosition, targetRotation);
176 }
177 else {
178 // Ensure the workstation is not active or being deactivated if
179 // we are attaching to an anchor.
181 }
182 }
183
184 #region Traveling
185
186 private const float MAX_TRAVEL_SPEED = 4.00F;
187
188 private Tween _travelTween;
189
190 private float _initTravelTime = 0F;
191 private Vector3 _initTravelPosition = Vector3.zero;
192 private Vector3 _initTravelVelocity = Vector3.zero;
193 private Quaternion _initTravelRotation = Quaternion.identity;
194 private Vector3 _initTravelAngVelocity = Vector3.zero;
195 private Vector3 _effGravity = Vector3.zero;
196
197 private Vector3 _travelTargetPosition;
198 private Quaternion _travelTargetRotation;
199
200 private Vector2 _minMaxWorkstationTravelTime = new Vector2(0.05F, 1.00F);
201 private Vector2 _minMaxTravelTimeFromThrowSpeed = new Vector2(0.30F, 8.00F);
202
203 private void beginTraveling(Vector3 initPosition, Vector3 initVelocity,
204 Quaternion initRotation, Vector3 initAngVelocity,
205 Vector3 targetPosition, Quaternion targetRotation) {
206 _initTravelTime = Time.time;
207 _initTravelPosition = initPosition;
208 _initTravelVelocity = initVelocity;
209 _initTravelRotation = initRotation;
210 _initTravelAngVelocity = initAngVelocity;
211
212 float velMagnitude = _initTravelVelocity.magnitude;
213 if (velMagnitude > MAX_TRAVEL_SPEED) {
214 float capSpeedMultiplier = MAX_TRAVEL_SPEED / velMagnitude;
215 _initTravelVelocity *= capSpeedMultiplier;
216 }
217
218 _effGravity = Vector3.Lerp(Vector3.zero, Physics.gravity, initVelocity.magnitude.Map(0.80F, 3F, 0F, 0.70F));
219
220
221
222 _travelTargetPosition = targetPosition;
223 _travelTargetRotation = targetRotation;
224
225 // Construct a single-use Tween that will last a specific duration
226 // and specify a custom callback as it progresses to update the
227 // object's position and rotation.
228 _travelTween = Tween.Single()
229 .OverTime(_initTravelVelocity.magnitude.Map(_minMaxTravelTimeFromThrowSpeed.x, _minMaxTravelTimeFromThrowSpeed.y,
230 _minMaxWorkstationTravelTime.x, _minMaxWorkstationTravelTime.y))
231 .OnProgress(onTravelTweenProgress)
232 .OnReachEnd(ActivateWorkstation) // When the tween is finished, open workstation mode.
233 .Play();
234 }
235
236 private void onTravelTweenProgress(float progress) {
237 float curTime = Time.time;
238 Vector3 extrapolatedPosition = evaluatePosition(_initTravelPosition, _initTravelVelocity, _effGravity, _initTravelTime, curTime);
239 Quaternion extrapolatedRotation = evaluateRotation(_initTravelRotation, _initTravelAngVelocity, _initTravelTime, curTime);
240
241 // Interpolate from the position and rotation that the object would naturally have over time
242 // (by movement due to inertia and by acceleration due to gravity)
243 // to the target position and rotation over the lifetime of the tween.
244 _intObj.rigidbody.position = Vector3.Lerp(extrapolatedPosition, _travelTargetPosition, progress);
245 _intObj.rigidbody.rotation = Quaternion.Slerp(extrapolatedRotation, _travelTargetRotation, progress);
246 }
247
248 private void cancelTraveling() {
249 // In case traveling was halted mid-travel-tween, halt the tween.
250 if (_travelTween.isValid) { _travelTween.Stop(); }
251 }
252
256 private Vector3 evaluatePosition(Vector3 initialPosition, Vector3 initialVelocity, Vector3 gravity, float initialTime, float timeToEvaluate) {
257 float t = timeToEvaluate - initialTime;
258 return initialPosition + (initialVelocity * t) + (0.5f * gravity * t * t);
259 }
260
264 private Quaternion evaluateRotation(Quaternion initialRotation, Vector3 angularVelocity, float initialTime, float timeToEvaluate) {
265 float t = timeToEvaluate - initialTime;
266 return Quaternion.Euler(angularVelocity * t) * initialRotation;
267 }
268
269 #endregion
270
271 #region Workstation Pose
272
273 public delegate Vector3 WorkstationPositionFunc(Vector3 userEyePosition, Quaternion userEyeRotation,
274 Vector3 workstationObjInitPosition, Vector3 workstationObjInitVelocity, float workstationObjRadius,
275 List<Vector3> otherWorkstationPositions, List<float> otherWorkstationRadii);
276
277 public delegate Quaternion WorkstationRotationFunc(Vector3 userEyePosition, Vector3 targetWorkstationPosition);
278
286
292
293 private List<Vector3> _otherStationObjPositions = new List<Vector3>();
294 private List<float> _otherStationObjRadii = new List<float>();
295
296 // Called on Awake(); possible to override the default functions in a MonoBehaviour's Start().
297 private void initWorkstationPoseFunctions() {
300 }
301
302 private Vector3 determineWorkstationPosition() {
303 return workstationPositionFunc(Camera.main.transform.position, Camera.main.transform.rotation,
304 _intObj.rigidbody.position, _intObj.rigidbody.velocity, 0.30F,
305 _otherStationObjPositions, _otherStationObjRadii);
306 }
307
308 private Quaternion determineWorkstationRotation(Vector3 workstationPosition) {
309 return workstationRotationFunc(Camera.main.transform.position, workstationPosition);
310 }
311
312 public static Vector3 DefaultDetermineWorkstationPosition(Vector3 userEyePosition, Quaternion userEyeRotation,
313 Vector3 workstationObjInitPosition, Vector3 workstationObjInitVelocity, float workstationObjRadius,
314 List<Vector3> otherWorkstationPositions, List<float> otherWorkstationRadii) {
315 // Push velocity away from the camera if necessary.
316 Vector3 towardsCamera = (userEyePosition - workstationObjInitPosition).normalized;
317 float towardsCameraness = Mathf.Clamp01(Vector3.Dot(towardsCamera, workstationObjInitVelocity.normalized));
318 workstationObjInitVelocity = workstationObjInitVelocity + Vector3.Lerp(Vector3.zero, -towardsCamera * 2.00F, towardsCameraness);
319
320 // Calculate velocity direction on the XZ plane.
321 Vector3 groundPlaneVelocity = Vector3.ProjectOnPlane(workstationObjInitVelocity, Vector3.up);
322 float groundPlaneDirectedness = groundPlaneVelocity.magnitude.Map(0.003F, 0.40F, 0F, 1F);
323 Vector3 groundPlaneDirection = groundPlaneVelocity.normalized;
324
325 // Calculate camera "forward" direction on the XZ plane.
326 Vector3 cameraGroundPlaneForward = Vector3.ProjectOnPlane(userEyeRotation * Vector3.forward, Vector3.up);
327 float cameraGroundPlaneDirectedness = cameraGroundPlaneForward.magnitude.Map(0.001F, 0.01F, 0F, 1F);
328 Vector3 alternateCameraDirection = (userEyeRotation * Vector3.forward).y > 0F ? userEyeRotation * Vector3.down : userEyeRotation * Vector3.up;
329 cameraGroundPlaneForward = Vector3.Slerp(alternateCameraDirection, cameraGroundPlaneForward, cameraGroundPlaneDirectedness);
330 cameraGroundPlaneForward = cameraGroundPlaneForward.normalized;
331
332 // Calculate a placement direction based on the camera and throw directions on the XZ plane.
333 Vector3 placementDirection = Vector3.Slerp(cameraGroundPlaneForward, groundPlaneDirection, groundPlaneDirectedness);
334
335 // Calculate a placement position along the placement direction between min and max placement distances.
336 float minPlacementDistance = 0.25F;
337 float maxPlacementDistance = 0.51F;
338 Vector3 placementPosition = userEyePosition + placementDirection * Mathf.Lerp(minPlacementDistance, maxPlacementDistance,
339 (groundPlaneDirectedness * workstationObjInitVelocity.magnitude)
340 .Map(0F, 1.50F, 0F, 1F));
341
342 // Don't move far if the initial velocity is small.
343 float overallDirectedness = workstationObjInitVelocity.magnitude.Map(0.00F, 3.00F, 0F, 1F);
344 placementPosition = Vector3.Lerp(workstationObjInitPosition, placementPosition, overallDirectedness * overallDirectedness);
345
346 // Enforce placement height.
347 float placementHeightFromCamera = -0.30F;
348 placementPosition.y = userEyePosition.y + placementHeightFromCamera;
349
350 // Enforce minimum placement away from user.
351 Vector2 cameraXZ = new Vector2(userEyePosition.x, userEyePosition.z);
352 Vector2 stationXZ = new Vector2(placementPosition.x, placementPosition.z);
353 float placementDist = Vector2.Distance(cameraXZ, stationXZ);
354 if (placementDist < minPlacementDistance) {
355 float distanceLeft = (minPlacementDistance - placementDist) + workstationObjRadius;
356 Vector2 xzDisplacement = (stationXZ - cameraXZ).normalized * distanceLeft;
357 placementPosition += new Vector3(xzDisplacement[0], 0F, xzDisplacement[1]);
358 }
359
360 return placementPosition;
361 }
362
363 public static Quaternion DefaultDetermineWorkstationRotation(Vector3 userEyePos, Vector3 workstationPosition) {
364 Vector3 toCamera = userEyePos - workstationPosition;
365 toCamera.y = 0F;
366 Quaternion placementRotation = Quaternion.LookRotation(toCamera.normalized, Vector3.up);
367
368 return placementRotation;
369 }
370
371 #endregion
372
373 }
374
375}
UnityEngine.Debug Debug
Definition: TanodaServer.cs:19
This is a wrapper MonoBehaviour that demonstrates and exposes some of the basic functionality of the ...
Tween tween
Returns the Tween object the TransformTween behaviour produces on Start().
This example script constructs behavior for a very specific kind of UI object that can animate out a ...
static Vector3 DefaultDetermineWorkstationPosition(Vector3 userEyePosition, Quaternion userEyeRotation, Vector3 workstationObjInitPosition, Vector3 workstationObjInitVelocity, float workstationObjRadius, List< Vector3 > otherWorkstationPositions, List< float > otherWorkstationRadii)
const float MIN_SPEED_TO_ACTIVATE_TRAVELING
If the rigidbody of this object moves slower than this speed and the object wants to enter workstatio...
static Quaternion DefaultDetermineWorkstationRotation(Vector3 userEyePos, Vector3 workstationPosition)
delegate Quaternion WorkstationRotationFunc(Vector3 userEyePosition, Vector3 targetWorkstationPosition)
delegate Vector3 WorkstationPositionFunc(Vector3 userEyePosition, Quaternion userEyeRotation, Vector3 workstationObjInitPosition, Vector3 workstationObjInitVelocity, float workstationObjRadius, List< Vector3 > otherWorkstationPositions, List< float > otherWorkstationRadii)
WorkstationRotationFunc workstationRotationFunc
The function used to calculate this workstation's target rotation. By default, will make the workstat...
WorkstationPositionFunc workstationPositionFunc
The function used to calculate this workstation's target position. By default, will attempt to choose...
const float MAX_SPEED_AS_WORKSTATION
If the rigidbody of this object moves faster than this speed and the object is in workstation mode,...
bool isTraveling
Gets whether the workstation is currently traveling to a target position to open in workstation mode....
AnchorableBehaviours mix well with InteractionBehaviours you'd like to be able to pick up and place i...
Action OnPostTryAnchorOnGraspEnd
Called just after this anchorable behaviour's InteractionBehaviour OnObjectGraspEnd for this anchor....
Anchor preferredAnchor
Gets the anchor this AnchorableBehaviour would most prefer to attach to. This value is refreshed ever...
InteractionBehaviours are components that enable GameObjects to interact with interaction controllers...
Rigidbody rigidbody
The Rigidbody associated with this interaction object.
GraspedMovementEvent OnGraspedMovement
Called directly after this grasped object's Rigidbody has had its position and rotation set by its cu...
Tween Play()
Starts playing this Tween. It will continue from the same position it left off on,...
Definition: Tween.cs:272
Direction direction
Gets or sets whether or not this Tween is moving forwards or backwards.
Definition: Tween.cs:73
bool isRunning
Returns whether or not the Tween is currently running.
Definition: Tween.cs:63
Tween OverTime(float seconds)
Specifies that this Tween should travel from begining to end over a certain number of seconds.
Definition: Tween.cs:165
Tween OnProgress(Action< float > action)
Specifies an action to be called every step of the Tween. This callback happens after:
Definition: Tween.cs:222
Tween OnReachEnd(Action action)
Specifies an action to be called whenever this Tween reaches the end.
Definition: Tween.cs:262
static Tween Single()
Create a single-use Tween that will auto-release itself as soon as it is finished playing.
Definition: Tween.cs:28
bool isValid
Returns whether or not this Tween is considered valid. A Tween can become invalid under the following...
Definition: Tween.cs:54
void Stop()
Stops this Tween. If it is not a persistant Tween, it will be recycled right away.
Definition: Tween.cs:345
float progress
Gets or sets how far along completion this Tween is. This value is a percent that ranges from 0 to 1.
Definition: Tween.cs:99