Skip to content

AngularTransformDrive gets stuck when its current rotation is outside DriveLimit range #267

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Scraphead opened this issue Oct 6, 2023 · 2 comments
Labels
bug Something isn't working

Comments

@Scraphead
Copy link

Environment

Unity 2021.3
"io.extendreality.tilia.interactions.controllables.unity": "2.10.21",
"io.extendreality.tilia.interactions.interactables.unity": "2.16.6",

Steps to reproduce

1: Create standard VRTK environment with interactor
2: Create an AngularTransformDrive and set its DriveLimit max to 800 (Above 720)
3: Create script that will set AngularDriveFacade SetDriveLimitMaximum to 300 (Below 360)

4: Play Game
5: Grab and rotate cube so it reaches maximum 800.
6: Invoke script so DriveLimit max is set to 300.
7: Try to grab and rotate cube again.

Expected behavior

Cube should still be able to be rotated within min/max DriveLimit range.

Current behavior

Cube is stuck, unable to be rotated,

Other:

My crude workaround to set DriveLimit without it getting stuck.
  // Workaround for Controllables that can be stuck when rotationMultiplier is not within current DriveLimit restrictions
  public void SetMinMaxLimitWithCrudeFix(Vector2 minMax)
  {
      var angularFacade = GetComponent<AngularDriveFacade>();
      angularFacade.DriveLimit = new FloatRange(minMax);
      
      MethodInfo _mGetSimpleEulerAngels = typeof(AngularDrive).GetMethod("GetSimpleEulerAngles",
      BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);

      FieldInfo _fRotationMultiplier = typeof(AngularDrive).GetField("rotationMultiplier",
      BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);

      var simpleEulerAngels = (Vector3) _mGetSimpleEulerAngels.Invoke(angularFacade.Drive, null);
      var rotationMultiplier = (float) _fRotationMultiplier.GetValue(angularFacade.Drive);
      var currentPseudoRotation = simpleEulerAngels[(int) angularFacade.DriveAxis] + (rotationMultiplier * 360f);
      var currentLimit = angularFacade.DriveLimit;

      if (currentLimit.Contains(currentPseudoRotation))
          return; // All good, within limits.

      var limitExceeded = currentLimit.maximum;
      if (currentPseudoRotation < currentLimit.minimum)
          limitExceeded = currentLimit.minimum;

      // Set rotationMultiplier to be closest step within range of DriveLimit
      var diff = currentPseudoRotation - limitExceeded;
      var subtractMultiplier = (int) (diff / 360f);
      rotationMultiplier = rotationMultiplier - subtractMultiplier;

      // Very bad additional workaround:
      // Need to overshoot RotationMultiplier by one since I have to fight 
      // AngularDrive.CalculateRotationMultiplier() that re-applies a rotation step next Process()
      rotationMultiplier += subtractMultiplier < 0 ? 1 : -1;
      
      // This workaround does not update current angle to be at the new limit, only that the rotationMultipiler is in valid range.
      _fRotationMultiplier.SetValue(angularFacade.Drive, rotationMultiplier);
  }
@thestonefox thestonefox added the bug Something isn't working label Oct 12, 2023
@thestonefox
Copy link
Member

so yeah it looks like changing the min/max limit to something underneath your existing limit causes an issue. Partially because of the rotation multiplier as your work around is addressing

But also because the pseudo rotation is way higher than the new limit. So looks like your workaround is basically setting the rotation multiplier to be within the limit again, but will this also set the pseudorotation to the correct value?

@Scraphead
Copy link
Author

Scraphead commented Oct 12, 2023

No, the workaround above does not set the psudorotation to the correct value. It was a temp fix to at least get the rotation multiplier within acceptable values and getting the drive unstuck.

I made a slightly improved version of my reflection workaround that now updates the psudorotation to the limit when it detects new limits is exceeded.

New workaround that fix drive limit getting stuck and updates its psudorotation
  // Reflection to get inner values of angular drive
  private static FieldInfo _fRotationMultiplier = AngularFieldInfo("rotationMultiplier");
  private static FieldInfo _fCurrentPseudoRotation = AngularFieldInfo("currentPseudoRotation");
  private static FieldInfo _fPreviousPseudoRotation = AngularFieldInfo("previousPseudoRotation");
  private static FieldInfo _fCurrentActualRotation = AngularFieldInfo("currentActualRotation");
  private static FieldInfo _fPreviousActualRotation = AngularFieldInfo("previousActualRotation");
  
  private static MethodInfo _mGetSimpleEulerAngles = AngularMethodInfo("GetSimpleEulerAngles");
  private static MethodInfo _mGetDriveTransform = AngularMethodInfo("GetDriveTransform");

  private const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;
  private static FieldInfo AngularFieldInfo(string fieldName) => typeof(AngularDrive).GetField(fieldName, flags);
  private static MethodInfo AngularMethodInfo(string methodName) => typeof(AngularDrive).GetMethod(methodName, flags);

  public void SetMinMaxLimitWithCrudeFix(Vector2 minMax)
  {
      angularFacade.DriveLimit = new FloatRange(minMax);

      var simpleEulerAngles = (Vector3) _mGetSimpleEulerAngles.Invoke(angularFacade.Drive, null);
      var rotationMultiplier = (float) _fRotationMultiplier.GetValue(angularFacade.Drive);
      var currentPseudoRotation = simpleEulerAngles[(int) angularFacade.DriveAxis] + (rotationMultiplier * 360f);
      var currentLimit = angularFacade.DriveLimit;

      if (currentLimit.Contains(currentPseudoRotation))
          return;

      // Limits has been exceeded, update angle to current min or max limit
      var limitExceeded = currentLimit.maximum;
      if (currentPseudoRotation < currentLimit.minimum)
          limitExceeded = currentLimit.minimum;

      SetAngleNow(limitExceeded);
  }
  
  public void SetAngleNow(float targetAngle)
  {
      // Reset drive first, (Mostly to stop velocity)
      angularFacade.Drive.ResetDrive();

      // Ensure new angle is withing current limits
      var limit = angularFacade.DriveLimit;
      targetAngle = targetAngle.Clamp(limit.minimum, limit.maximum);

      var driveAxis = angularFacade.DriveAxis;
      var axis = Vector3.zero;
      if (driveAxis == DriveAxis.Axis.XAxis)
          axis = Vector3.right;
      else if (driveAxis == DriveAxis.Axis.YAxis)
          axis = Vector3.up;
      else
          axis = Vector3.forward;

      // Update drive transform so it is now at correct local rotation for axis
      var driveTransform = (Transform) _mGetDriveTransform.Invoke(angularFacade.Drive, null);
      driveTransform.localRotation= Quaternion.AngleAxis(targetAngle, axis);

      // Updates psudo rotation values
      _fCurrentPseudoRotation.SetValue(angularFacade.Drive, targetAngle);
      _fPreviousPseudoRotation.SetValue(angularFacade.Drive, targetAngle);

      // Calculate and set new rotation multiplier
      _fRotationMultiplier.SetValue(angularFacade.Drive, Mathf.Floor(targetAngle / 360f));

      // Recalculate values for ActualRotation
      var simpleEulerAngles = (Vector3) _mGetSimpleEulerAngles.Invoke(angularFacade.Drive, null);
      _fCurrentActualRotation.SetValue(angularFacade.Drive, simpleEulerAngles);
      _fPreviousActualRotation.SetValue(angularFacade.Drive, simpleEulerAngles);
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants