Skip to content

Instantly share code, notes, and snippets.

@prnthp
Last active May 29, 2022 23:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save prnthp/eb3f299407b7593c105ec6c986cbb360 to your computer and use it in GitHub Desktop.
Save prnthp/eb3f299407b7593c105ec6c986cbb360 to your computer and use it in GitHub Desktop.
Improved OneGrabRotateTransformer
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
ANY KIND, either express or implied. See the License for the specific language governing
permissions and limitations under the License.
************************************************************************************/
using System;
using UnityEngine;
using UnityEngine.Serialization;
namespace Oculus.Interaction
{
/// <summary>
/// A Transformer that rotates the target about an axis.
/// Updates apply relative rotational changes of a GrabPoint about an axis.
/// The axis is defined by a pivot transform: a world position and up vector.
/// </summary>
public class OneGrabRotateTransformer : MonoBehaviour, ITransformer
{
public enum Axis
{
Right = 0,
Up = 1,
Forward = 2
}
[SerializeField, Optional]
private Transform _pivotTransform = null;
[SerializeField]
private Axis _rotationAxis = Axis.Up;
[Serializable]
public class OneGrabRotateConstraints
{
public FloatConstraint MinAngle;
public FloatConstraint MaxAngle;
}
[SerializeField]
private OneGrabRotateConstraints _constraints;
public OneGrabRotateConstraints Constraints
{
get
{
return _constraints;
}
set
{
_constraints = value;
}
}
private float _relativeAngle = 0.0f;
private float _constrainedRelativeAngle = 0.0f;
private IGrabbable _grabbable;
private Pose _previousGrabPose;
public void Initialize(IGrabbable grabbable)
{
_grabbable = grabbable;
}
public void BeginTransform()
{
var grabPoint = _grabbable.GrabPoints[0];
_previousGrabPose = grabPoint;
}
public void UpdateTransform()
{
var grabPoint = _grabbable.GrabPoints[0];
var targetTransform = _grabbable.Transform;
Transform pivot = _pivotTransform != null ? _pivotTransform : targetTransform;
Vector3 localAxis = Vector3.zero;
localAxis[(int)_rotationAxis] = 1f;
Vector3 rotationAxis = pivot.TransformDirection(localAxis);
// Project our positional offsets onto a plane with normal equal to the rotation axis
Vector3 initialOffset = _previousGrabPose.position - pivot.position;
Vector3 initialVector = Vector3.ProjectOnPlane(initialOffset, rotationAxis);
Vector3 targetOffset = grabPoint.position - pivot.position;
Vector3 targetVector = Vector3.ProjectOnPlane(targetOffset, rotationAxis);
// Shortest angle between two planar vectors is the angle about the axis
// Because we know the vectors are planar, we derive the sign ourselves
float angleDelta = Vector3.Angle(initialVector, targetVector);
angleDelta *= Vector3.Dot(Vector3.Cross(initialVector, targetVector), rotationAxis) > 0.0f ? 1.0f : -1.0f;
float previousAngle = _constrainedRelativeAngle;
_relativeAngle += angleDelta;
_constrainedRelativeAngle = _relativeAngle;
if (_constraints.MinAngle.Constrain)
{
_constrainedRelativeAngle = Mathf.Max(_constrainedRelativeAngle, _constraints.MinAngle.Value);
}
if (_constraints.MaxAngle.Constrain)
{
_constrainedRelativeAngle = Mathf.Min(_constrainedRelativeAngle, _constraints.MaxAngle.Value);
}
angleDelta = _constrainedRelativeAngle - previousAngle;
// Apply this angle rotation about the axis to our transform
targetTransform.RotateAround(pivot.position, rotationAxis, angleDelta);
_previousGrabPose = grabPoint;
}
public void EndTransform()
{
// Clamps relative angle to constraints to remove windup
if (_constraints.MinAngle.Constrain)
{
_relativeAngle = Mathf.Max(_constrainedRelativeAngle, _constraints.MinAngle.Value);
}
if (_constraints.MaxAngle.Constrain)
{
_relativeAngle = Mathf.Min(_constrainedRelativeAngle, _constraints.MaxAngle.Value);
}
}
#region Inject
public void InjectOptionalPivotTransform(Transform pivotTransform)
{
_pivotTransform = pivotTransform;
}
public void InjectOptionalRotationAxis(Axis rotationAxis)
{
_rotationAxis = rotationAxis;
}
public void InjectOptionalConstraints(OneGrabRotateConstraints constraints)
{
_constraints = constraints;
}
#endregion
}
}

Changes

Local rotation axis

Previously the transformer rotated around world X, Y, and Z axes, which is not that useful. This changes the math so the Rotation Axis parameter is now the pivot's local axis.

            Vector3 localAxis = Vector3.zero;
            localAxis[(int)_rotationAxis] = 1f;
            Vector3 rotationAxis = pivot.TransformDirection(localAxis);

Wind-up correction for constrained rotation

Say you had the transformer on a "wheel" and constrained the wheel to (-180,180), if you rotated the wheel continuously for, say, 3 revolutions and let go, the wheel now seems locked. You have to imaginitively "unwind" the wheel by 3 revolutions for it to behave normally again.

This is fixed by clamping the relative values when letting go. So the next time you manipulate the wheel it starts at the constraint.

Note that for an end without a constraint, the behavior will persist at that end, but it is actually logical now in my opinion - the object will behave like a "reel".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment