-
Notifications
You must be signed in to change notification settings - Fork 38
/
Copy pathOneGrabToggleRotateTransformer.cs
241 lines (192 loc) · 9.08 KB
/
OneGrabToggleRotateTransformer.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
// Copyright (c) Meta Platforms, Inc. and affiliates.
using System;
using Fusion;
using Oculus.Interaction;
using UnityEngine;
namespace CrypticCabinet.Interactions
{
/// <summary>
/// This is a blend of the <see cref="OneGrabRotateTransformer"/> and <see cref="OneGrabFreeTransformer"/>
/// There's added functionality to allow toggling between a free movement and rotation transformation.
/// Furthermore, there's a "break snap" feature to pull from rotation to free movement.
/// </summary>
public class OneGrabToggleRotateTransformer : NetworkBehaviour, ITransformer
{
public enum Axis
{
RIGHT = 0,
UP = 1,
FORWARD = 2
}
public enum RotationDirection
{
BOTH,
POSITIVE,
NEGATIVE
}
[Networked] public bool LockPosition { get; set; }
[Networked] public bool CanUnlockPosition { get; set; }
[Networked] public float ConstrainedRelativeAngle { get; set; }
[Networked] private float RelativeAngle { get; set; }
[SerializeField] private float m_unlockBreakDistance = 0.3f;
[SerializeField, Optional] private Transform m_pivotTransform;
[SerializeField] private Axis m_rotationAxis = Axis.UP;
[SerializeField]
private OneGrabRotateConstraints m_constraints = new()
{
MinAngle = new FloatConstraint(),
MaxAngle = new FloatConstraint()
};
public Axis RotationAxis => m_rotationAxis;
public RotationDirection RotationDirectionLimit;
public OneGrabRotateConstraints Constraints => m_constraints;
public Transform Pivot => m_pivotTransform != null ? m_pivotTransform : transform;
[Serializable]
public class OneGrabRotateConstraints
{
public FloatConstraint MinAngle;
public FloatConstraint MaxAngle;
}
private IGrabbable m_grabbable;
private Vector3 m_grabPositionInPivotSpace;
private Pose m_transformPoseInPivotSpace;
private Pose m_worldPivotPose;
private Vector3 m_previousVectorInPivotSpace;
private Quaternion m_localRotation;
private float m_startAngle;
private Pose m_grabDeltaInLocalSpace;
public void Initialize(IGrabbable grabbable)
{
m_grabbable = grabbable;
}
private Pose ComputeWorldPivotPose()
{
if (m_pivotTransform != null)
{
return m_pivotTransform.GetPose();
}
var targetTransform = m_grabbable.Transform;
var worldPosition = targetTransform.position;
var worldRotation = targetTransform.parent != null
? targetTransform.parent.rotation * m_localRotation
: m_localRotation;
return new Pose(worldPosition, worldRotation);
}
public void BeginTransform()
{
var grabPoint = m_grabbable.GrabPoints[0];
var targetTransform = m_grabbable.Transform;
//BeginTransform functionality from OneGrabFreeTransformer
m_grabDeltaInLocalSpace = new Pose(targetTransform.InverseTransformVector(grabPoint.position - targetTransform.position),
Quaternion.Inverse(grabPoint.rotation) * targetTransform.rotation);
if (m_pivotTransform == null)
{
m_localRotation = targetTransform.localRotation;
}
//BeginTransform functionality from OneGrabRotateTransformer
var localAxis = Vector3.zero;
localAxis[(int)m_rotationAxis] = 1f;
m_worldPivotPose = ComputeWorldPivotPose();
var rotationAxis = m_worldPivotPose.rotation * localAxis;
var inverseRotation = Quaternion.Inverse(m_worldPivotPose.rotation);
var grabDelta = grabPoint.position - m_worldPivotPose.position;
// The initial delta must be non-zero between the pivot and grab location for rotation
if (Mathf.Abs(grabDelta.magnitude) < 0.001f)
{
var localAxisNext = Vector3.zero;
localAxisNext[((int)m_rotationAxis + 1) % 3] = 0.001f;
grabDelta = m_worldPivotPose.rotation * localAxisNext;
}
m_grabPositionInPivotSpace =
inverseRotation * grabDelta;
var worldPositionDelta =
inverseRotation * (targetTransform.position - m_worldPivotPose.position);
var worldRotationDelta = inverseRotation * targetTransform.rotation;
m_transformPoseInPivotSpace = new Pose(worldPositionDelta, worldRotationDelta);
var initialOffset = m_worldPivotPose.rotation * m_grabPositionInPivotSpace;
var initialVector = Vector3.ProjectOnPlane(initialOffset, rotationAxis);
m_previousVectorInPivotSpace = Quaternion.Inverse(m_worldPivotPose.rotation) * initialVector;
m_startAngle = ConstrainedRelativeAngle;
RelativeAngle = m_startAngle;
var parentScale = targetTransform.parent != null ? targetTransform.parent.lossyScale.x : 1f;
m_transformPoseInPivotSpace.position /= parentScale;
}
public void UpdateTransform()
{
var grabPoint = m_grabbable.GrabPoints[0];
var targetTransform = m_grabbable.Transform;
// Check for if the transform should switch from locked to rotation to free movement again.
if (LockPosition &&
CanUnlockPosition &&
Vector3.Distance(grabPoint.position, targetTransform.position) > m_unlockBreakDistance)
{
LockPosition = false;
}
if (!LockPosition)
{
//UpdateTransform functionality from OneGrabFreeTransformer
targetTransform.rotation = grabPoint.rotation * m_grabDeltaInLocalSpace.rotation;
targetTransform.position = grabPoint.position - targetTransform.TransformVector(m_grabDeltaInLocalSpace.position);
return;
}
//UpdateTransform functionality from OneGrabRotateTransformer
var localAxis = Vector3.zero;
localAxis[(int)m_rotationAxis] = 1f;
m_worldPivotPose = ComputeWorldPivotPose();
var rotationAxis = m_worldPivotPose.rotation * localAxis;
// Project our positional offsets onto a plane with normal equal to the rotation axis
var targetOffset = grabPoint.position - m_worldPivotPose.position;
var targetVector = Vector3.ProjectOnPlane(targetOffset, rotationAxis);
var previousVectorInWorldSpace =
m_worldPivotPose.rotation * m_previousVectorInPivotSpace;
// update previous
m_previousVectorInPivotSpace = Quaternion.Inverse(m_worldPivotPose.rotation) * targetVector;
var signedAngle =
Vector3.SignedAngle(previousVectorInWorldSpace, targetVector, rotationAxis);
if (RotationDirectionLimit == RotationDirection.NEGATIVE)
{
signedAngle = Mathf.Min(signedAngle, 0);
}
else if (RotationDirectionLimit == RotationDirection.POSITIVE)
{
signedAngle = Mathf.Max(signedAngle, 0);
}
RelativeAngle += signedAngle;
ConstrainedRelativeAngle = RelativeAngle;
if (Constraints.MinAngle.Constrain)
{
ConstrainedRelativeAngle = Mathf.Max(ConstrainedRelativeAngle, Constraints.MinAngle.Value);
}
if (Constraints.MaxAngle.Constrain)
{
ConstrainedRelativeAngle = Mathf.Min(ConstrainedRelativeAngle, Constraints.MaxAngle.Value);
}
var deltaRotation = Quaternion.AngleAxis(ConstrainedRelativeAngle - m_startAngle, rotationAxis);
var parentScale = targetTransform.parent != null ? targetTransform.parent.lossyScale.x : 1f;
var transformDeltaInWorldSpace =
new Pose(
m_worldPivotPose.rotation * (parentScale * m_transformPoseInPivotSpace.position),
m_worldPivotPose.rotation * m_transformPoseInPivotSpace.rotation);
var transformDeltaRotated = new Pose(
deltaRotation * transformDeltaInWorldSpace.position,
deltaRotation * transformDeltaInWorldSpace.rotation);
targetTransform.position = m_worldPivotPose.position + transformDeltaRotated.position;
targetTransform.rotation = transformDeltaRotated.rotation;
}
public void EndTransform() { }
#region Inject
public void InjectOptionalPivotTransform(Transform pivotTransform)
{
m_pivotTransform = pivotTransform;
}
public void InjectOptionalRotationAxis(Axis rotationAxis)
{
m_rotationAxis = rotationAxis;
}
public void InjectOptionalConstraints(OneGrabRotateConstraints constraints)
{
m_constraints = constraints;
}
#endregion
}
}