Skip to content

Commit 8c0a007

Browse files
authored
Merge pull request #10074 from Zee2/surfaceMagnetismFix
Fixing SurfaceMagnetism quaternion math
2 parents b16dc23 + 1ecb165 commit 8c0a007

File tree

2 files changed

+86
-5
lines changed

2 files changed

+86
-5
lines changed

Assets/MRTK/SDK/Features/Utilities/Solvers/SurfaceMagnetism.cs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -388,15 +388,25 @@ private Vector3 RaycastDirection
388388
/// <returns>Quaternion, the orientation to use for the object</returns>
389389
private Quaternion CalculateMagnetismOrientation(Vector3 direction, Vector3 surfaceNormal)
390390
{
391+
// Compute the up vector of our current working rotation,
392+
// to avoid gimbal lock instability when normal is also pointing upwards.
393+
// This "current" up vector is used in the LookRotation, which causes
394+
// the derived rotation to fit "closest" to the current up vector.
395+
Vector3 currentUpVector = WorkingRotation * Vector3.up;
396+
397+
Quaternion trackedReferenceRotation = Quaternion.LookRotation(-direction, currentUpVector);
398+
Quaternion surfaceReferenceRotation = Quaternion.LookRotation(-surfaceNormal, currentUpVector);
399+
400+
// If requested, compute FromTo from the current computed Up to global Up,
401+
// and apply to the computed quat; this will ensure object stays globally vertical.
391402
if (KeepOrientationVertical)
392403
{
393-
direction.y = 0;
394-
surfaceNormal.y = 0;
404+
Vector3 trackedReferenceUp = trackedReferenceRotation * Vector3.up;
405+
trackedReferenceRotation = Quaternion.FromToRotation(trackedReferenceUp, Vector3.up) * trackedReferenceRotation;
406+
Vector3 surfaceReferenceUp = surfaceReferenceRotation * Vector3.up;
407+
surfaceReferenceRotation = Quaternion.FromToRotation(surfaceReferenceUp, Vector3.up) * surfaceReferenceRotation;
395408
}
396409

397-
var trackedReferenceRotation = Quaternion.LookRotation(-direction, Vector3.up);
398-
var surfaceReferenceRotation = Quaternion.LookRotation(-surfaceNormal, Vector3.up);
399-
400410
switch (CurrentOrientationMode)
401411
{
402412
case OrientationMode.None:

Assets/MRTK/Tests/PlayModeTests/SolverTests.cs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,77 @@ public IEnumerator TestSurfaceMagnetism()
219219
Assert.IsTrue(Mathf.Approximately(1.0f, Vector3.Dot(targetTransform.forward.normalized, Vector3.forward)));
220220
}
221221

222+
/// <summary>
223+
/// Test Surface Magnetism against vertical normal and ensure no temporal instability (gimbal lock)
224+
/// </summary>
225+
[UnityTest]
226+
public IEnumerator TestSurfaceMagnetismGimbalLock()
227+
{
228+
Vector3 floorCenter = Vector3.forward * 10.0f + Vector3.up * -2.0f;
229+
230+
// Reset view to origin
231+
MixedRealityPlayspace.PerformTransformation(p =>
232+
{
233+
p.position = Vector3.zero;
234+
p.LookAt(floorCenter);
235+
});
236+
237+
// Build floor to collide against
238+
var floor = GameObject.CreatePrimitive(PrimitiveType.Cube);
239+
floor.transform.localScale = new Vector3(25.0f, 0.2f, 25.0f);
240+
floor.transform.position = floorCenter;
241+
242+
// Give the floor a very small tilt
243+
floor.transform.rotation = Quaternion.Euler(10, 0, 0);
244+
245+
yield return WaitForFrames(2);
246+
247+
// Instantiate our test GameObject with solver.
248+
// Set layer to ignore raycast so solver doesn't raycast itself (i.e BoxCollider)
249+
var testObjects = InstantiateTestSolver<SurfaceMagnetism>();
250+
testObjects.target.layer = LayerMask.NameToLayer("Ignore Raycast");
251+
SurfaceMagnetism surfaceMag = testObjects.solver as SurfaceMagnetism;
252+
253+
var targetTransform = testObjects.target.transform;
254+
var cameraTransform = CameraCache.Main.transform;
255+
256+
yield return WaitForFrames(2);
257+
258+
// Change default orientation mode to surface normal
259+
surfaceMag.CurrentOrientationMode = SurfaceMagnetism.OrientationMode.SurfaceNormal;
260+
surfaceMag.KeepOrientationVertical = false;
261+
262+
yield return WaitForFrames(2);
263+
264+
// Test object should now be facing the floor
265+
Assert.IsTrue(Mathf.Approximately(1.0f, Vector3.Dot(-targetTransform.forward.normalized, floor.transform.up)));
266+
267+
// Cache test object's current right vector
268+
Vector3 rightVector = testObjects.target.transform.right;
269+
270+
floor.transform.Rotate(new Vector3(-20, 0, 0));
271+
yield return WaitForFrames(2);
272+
273+
// Make sure that the target's right vector has not flip-flopped
274+
Assert.IsTrue(Mathf.Sign(targetTransform.transform.right.x) == Mathf.Sign(rightVector.x));
275+
276+
// Do the same, but with KeepOrientationVertical = true
277+
surfaceMag.KeepOrientationVertical = true;
278+
279+
// Cache test object's current right vector
280+
rightVector = testObjects.target.transform.right;
281+
282+
yield return WaitForFrames(2);
283+
284+
// Test object should now have proper upright orientation
285+
Assert.IsTrue(Mathf.Approximately(1.0f, Vector3.Dot(targetTransform.up.normalized, Vector3.up)));
286+
floor.transform.Rotate(new Vector3(20, 0, 0));
287+
yield return WaitForFrames(2);
288+
289+
// Make sure that the target's right vector has not flip-flopped
290+
Assert.IsTrue(Mathf.Sign(targetTransform.transform.right.x) == Mathf.Sign(rightVector.x));
291+
}
292+
222293
/// <summary>
223294
/// Test solver system's ability to change target types at runtime
224295
/// </summary>

0 commit comments

Comments
 (0)