11-01-2023 08:42 AM
Hi!
I'm having a little problem using the OneGrabTransformers from the Oculus SDK.
I want to be able to move and rotate an object, but constrain it to y axis rotation only. As i understand, the OneGrabRotate Transformer component constrain the rotation on one axis (and stop the movement), and the OneGrabTranslate Transformer component constrain the movement on any axis you want (and stop rotation).
The Grabbable component only accept one OneGrabTransformer on its "Optionals", i can't seem to make multiple OneGrabTransformer components work together.
So, as I would like is my grabbed object to be able to move freely in any direction and rotate only on the y axis, how could i achieve that, do you have any examples ?
Thanks a lot!
02-09-2024 03:18 PM
Hi @Rednef, here's some code for a quick and dirty solution. What I did was make a new script and combine code from OneGrabFreeTransformer (for the position logic) and OneGrabRotateTransformer (for the rotation logic). The script also contains a duplicate of the TransformerUtils class since I couldn't access that class in my project for some reason, but I needed the logic. You can see the final result in this video--there's some stuttering, but overall it works.
Here's the script code:
using System;
using UnityEngine;
using Oculus.Interaction;
using UnityEditor;
using UnityEngine.Animations;
public class CustomOneGrabRotateTransformer : MonoBehaviour, ITransformer
{
private TransformerUtils.PositionConstraints _positionConstraints =
new TransformerUtils.PositionConstraints()
{
XAxis = new TransformerUtils.ConstrainedAxis(),
YAxis = new TransformerUtils.ConstrainedAxis(),
ZAxis = new TransformerUtils.ConstrainedAxis()
};
[SerializeField]
private TransformerUtils.RotationConstraints _rotationConstraints =
new TransformerUtils.RotationConstraints()
{
XAxis = new TransformerUtils.ConstrainedAxis(),
YAxis = new TransformerUtils.ConstrainedAxis(),
ZAxis = new TransformerUtils.ConstrainedAxis()
};
public enum Axis
{
Right = 0,
Up = 1,
Forward = 2
}
[SerializeField, Optional]
private Transform _pivotTransform = null;
public Transform Pivot => _pivotTransform != null ? _pivotTransform : transform;
[SerializeField]
private Axis _rotationAxis = Axis.Up;
public Axis RotationAxis => _rotationAxis;
[Serializable]
public class OneGrabRotateConstraints
{
public FloatConstraint MinAngle;
public FloatConstraint MaxAngle;
}
[SerializeField]
private OneGrabRotateConstraints _constraints =
new OneGrabRotateConstraints()
{
MinAngle = new FloatConstraint(),
MaxAngle = new FloatConstraint()
};
public OneGrabRotateConstraints Constraints
{
get
{
return _constraints;
}
set
{
_constraints = value;
}
}
private float _relativeAngle = 0.0f;
private float _constrainedRelativeAngle = 0.0f;
private IGrabbable _grabbable;
private Vector3 _grabPositionInPivotSpace;
private Pose _transformPoseInPivotSpace;
private Pose _worldPivotPose;
private Vector3 _previousVectorInPivotSpace;
private Quaternion _localRotation;
private float _startAngle = 0;
// New code
private Pose _grabDeltaInLocalSpace;
private TransformerUtils.PositionConstraints _parentConstraints;
public void Initialize(IGrabbable grabbable)
{
_grabbable = grabbable;
// New code
Vector3 initialPosition = _grabbable.Transform.localPosition;
_parentConstraints = TransformerUtils.GenerateParentConstraints(_positionConstraints, initialPosition);
}
public Pose ComputeWorldPivotPose()
{
if (_pivotTransform != null)
{
return _pivotTransform.GetPose();
}
var targetTransform = _grabbable.Transform;
Vector3 worldPosition = targetTransform.position;
Quaternion worldRotation = targetTransform.parent != null
? targetTransform.parent.rotation * _localRotation
: _localRotation;
return new Pose(worldPosition, worldRotation);
}
public void BeginTransform()
{
var grabPoint = _grabbable.GrabPoints[0];
var targetTransform = _grabbable.Transform;
if (_pivotTransform == null)
{
_localRotation = targetTransform.localRotation;
}
Vector3 localAxis = Vector3.zero;
localAxis[(int)_rotationAxis] = 1f;
_worldPivotPose = ComputeWorldPivotPose();
Vector3 rotationAxis = _worldPivotPose.rotation * localAxis;
Quaternion inverseRotation = Quaternion.Inverse(_worldPivotPose.rotation);
Vector3 grabDelta = grabPoint.position - _worldPivotPose.position;
// The initial delta must be non-zero between the pivot and grab location for rotation
if (Mathf.Abs(grabDelta.magnitude) < 0.001f)
{
Vector3 localAxisNext = Vector3.zero;
localAxisNext[((int)_rotationAxis + 1) % 3] = 0.001f;
grabDelta = _worldPivotPose.rotation * localAxisNext;
}
_grabPositionInPivotSpace =
inverseRotation * grabDelta;
Vector3 worldPositionDelta =
inverseRotation * (targetTransform.position - _worldPivotPose.position);
Quaternion worldRotationDelta = inverseRotation * targetTransform.rotation;
_transformPoseInPivotSpace = new Pose(worldPositionDelta, worldRotationDelta);
Vector3 initialOffset = _worldPivotPose.rotation * _grabPositionInPivotSpace;
Vector3 initialVector = Vector3.ProjectOnPlane(initialOffset, rotationAxis);
_previousVectorInPivotSpace = Quaternion.Inverse(_worldPivotPose.rotation) * initialVector;
_startAngle = _constrainedRelativeAngle;
_relativeAngle = _startAngle;
float parentScale = targetTransform.parent != null ? targetTransform.parent.lossyScale.x : 1f;
_transformPoseInPivotSpace.position /= parentScale;
// New code
_grabDeltaInLocalSpace = new Pose(targetTransform.InverseTransformVector(grabPoint.position - targetTransform.position),
Quaternion.Inverse(grabPoint.rotation) * targetTransform.rotation);
}
public void UpdateTransform()
{
var grabPoint = _grabbable.GrabPoints[0];
var targetTransform = _grabbable.Transform;
Vector3 localAxis = Vector3.zero;
localAxis[(int)_rotationAxis] = 1f;
_worldPivotPose = ComputeWorldPivotPose();
Vector3 rotationAxis = _worldPivotPose.rotation * localAxis;
// Project our positional offsets onto a plane with normal equal to the rotation axis
Vector3 targetOffset = grabPoint.position - _worldPivotPose.position;
Vector3 targetVector = Vector3.ProjectOnPlane(targetOffset, rotationAxis);
Vector3 previousVectorInWorldSpace =
_worldPivotPose.rotation * _previousVectorInPivotSpace;
// update previous
_previousVectorInPivotSpace = Quaternion.Inverse(_worldPivotPose.rotation) * targetVector;
float signedAngle =
Vector3.SignedAngle(previousVectorInWorldSpace, targetVector, rotationAxis);
_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);
}
Quaternion deltaRotation = Quaternion.AngleAxis(_constrainedRelativeAngle - _startAngle, rotationAxis);
float parentScale = targetTransform.parent != null ? targetTransform.parent.lossyScale.x : 1f;
Pose transformDeltaInWorldSpace =
new Pose(
_worldPivotPose.rotation * (parentScale * _transformPoseInPivotSpace.position),
_worldPivotPose.rotation * _transformPoseInPivotSpace.rotation);
Pose transformDeltaRotated = new Pose(
deltaRotation * transformDeltaInWorldSpace.position,
deltaRotation * transformDeltaInWorldSpace.rotation);
// New code
//targetTransform.position = _worldPivotPose.position + transformDeltaRotated.position;
Vector3 updatedPosition = grabPoint.position - targetTransform.TransformVector(_grabDeltaInLocalSpace.position);
targetTransform.position = TransformerUtils.GetConstrainedTransformPosition(updatedPosition, _parentConstraints, targetTransform.parent);
targetTransform.rotation = transformDeltaRotated.rotation;
}
public void EndTransform() { }
#region Inject
public void InjectOptionalPivotTransform(Transform pivotTransform)
{
_pivotTransform = pivotTransform;
}
public void InjectOptionalRotationAxis(Axis rotationAxis)
{
_rotationAxis = rotationAxis;
}
public void InjectOptionalConstraints(OneGrabRotateConstraints constraints)
{
_constraints = constraints;
}
#endregion
#region Subclass
public class TransformerUtils
{
[Serializable]
public struct FloatRange
{
public float Min;
public float Max;
}
[Serializable]
public struct ConstrainedAxis
{
public bool ConstrainAxis;
public FloatRange AxisRange;
}
[Serializable]
public class PositionConstraints
{
public bool ConstraintsAreRelative;
public ConstrainedAxis XAxis;
public ConstrainedAxis YAxis;
public ConstrainedAxis ZAxis;
}
[Serializable]
public class RotationConstraints
{
public ConstrainedAxis XAxis;
public ConstrainedAxis YAxis;
public ConstrainedAxis ZAxis;
}
[Serializable]
public class ScaleConstraints
{
public bool ConstraintsAreRelative;
public ConstrainedAxis XAxis;
public ConstrainedAxis YAxis;
public ConstrainedAxis ZAxis;
}
public static PositionConstraints GenerateParentConstraints(PositionConstraints constraints, Vector3 initialPosition)
{
PositionConstraints parentConstraints;
if (!constraints.ConstraintsAreRelative)
{
parentConstraints = constraints;
}
else
{
parentConstraints = new PositionConstraints();
parentConstraints.XAxis = new ConstrainedAxis();
parentConstraints.YAxis = new ConstrainedAxis();
parentConstraints.ZAxis = new ConstrainedAxis();
if (constraints.XAxis.ConstrainAxis)
{
parentConstraints.XAxis.ConstrainAxis = true;
parentConstraints.XAxis.AxisRange.Min = constraints.XAxis.AxisRange.Min + initialPosition.x;
parentConstraints.XAxis.AxisRange.Max = constraints.XAxis.AxisRange.Max + initialPosition.x;
}
if (constraints.YAxis.ConstrainAxis)
{
parentConstraints.YAxis.ConstrainAxis = true;
parentConstraints.YAxis.AxisRange.Min = constraints.YAxis.AxisRange.Min + initialPosition.y;
parentConstraints.YAxis.AxisRange.Max = constraints.YAxis.AxisRange.Max + initialPosition.y;
}
if (constraints.ZAxis.ConstrainAxis)
{
parentConstraints.ZAxis.ConstrainAxis = true;
parentConstraints.ZAxis.AxisRange.Min = constraints.ZAxis.AxisRange.Min + initialPosition.z;
parentConstraints.ZAxis.AxisRange.Max = constraints.ZAxis.AxisRange.Max + initialPosition.z;
}
}
return parentConstraints;
}
public static ScaleConstraints GenerateParentConstraints(ScaleConstraints constraints, Vector3 initialScale)
{
ScaleConstraints parentConstraints;
if (!constraints.ConstraintsAreRelative)
{
parentConstraints = constraints;
}
else
{
parentConstraints = new ScaleConstraints();
parentConstraints.XAxis = new ConstrainedAxis();
parentConstraints.YAxis = new ConstrainedAxis();
parentConstraints.ZAxis = new ConstrainedAxis();
if (constraints.XAxis.ConstrainAxis)
{
parentConstraints.XAxis.ConstrainAxis = true;
parentConstraints.XAxis.AxisRange.Min = constraints.XAxis.AxisRange.Min * initialScale.x;
parentConstraints.XAxis.AxisRange.Max = constraints.XAxis.AxisRange.Max * initialScale.x;
}
if (constraints.YAxis.ConstrainAxis)
{
parentConstraints.YAxis.ConstrainAxis = true;
parentConstraints.YAxis.AxisRange.Min = constraints.YAxis.AxisRange.Min * initialScale.y;
parentConstraints.YAxis.AxisRange.Max = constraints.YAxis.AxisRange.Max * initialScale.y;
}
if (constraints.ZAxis.ConstrainAxis)
{
parentConstraints.ZAxis.ConstrainAxis = true;
parentConstraints.ZAxis.AxisRange.Min = constraints.ZAxis.AxisRange.Min * initialScale.z;
parentConstraints.ZAxis.AxisRange.Max = constraints.ZAxis.AxisRange.Max * initialScale.z;
}
}
return parentConstraints;
}
public static Vector3 GetConstrainedTransformPosition(Vector3 unconstrainedPosition, PositionConstraints positionConstraints, Transform relativeTransform = null)
{
Vector3 constrainedPosition = unconstrainedPosition;
// the translation constraints occur in parent space
if (relativeTransform != null)
{
constrainedPosition = relativeTransform.InverseTransformPoint(constrainedPosition);
}
if (positionConstraints.XAxis.ConstrainAxis)
{
constrainedPosition.x = Mathf.Clamp(constrainedPosition.x, positionConstraints.XAxis.AxisRange.Min, positionConstraints.XAxis.AxisRange.Max);
}
if (positionConstraints.YAxis.ConstrainAxis)
{
constrainedPosition.y = Mathf.Clamp(constrainedPosition.y, positionConstraints.YAxis.AxisRange.Min, positionConstraints.YAxis.AxisRange.Max);
}
if (positionConstraints.ZAxis.ConstrainAxis)
{
constrainedPosition.z = Mathf.Clamp(constrainedPosition.z, positionConstraints.ZAxis.AxisRange.Min, positionConstraints.ZAxis.AxisRange.Max);
}
// Convert the constrained position back to world space
if (relativeTransform != null)
{
constrainedPosition = relativeTransform.TransformPoint(constrainedPosition);
}
return constrainedPosition;
}
public static Quaternion GetConstrainedTransformRotation(Quaternion unconstrainedRotation, RotationConstraints rotationConstraints)
{
var newX = unconstrainedRotation.eulerAngles.x;
var newY = unconstrainedRotation.eulerAngles.y;
var newZ = unconstrainedRotation.eulerAngles.z;
if (rotationConstraints.XAxis.ConstrainAxis)
{
newX = Mathf.Clamp(unconstrainedRotation.eulerAngles.x, rotationConstraints.XAxis.AxisRange.Min, rotationConstraints.XAxis.AxisRange.Max);
}
if (rotationConstraints.YAxis.ConstrainAxis)
{
newY = Mathf.Clamp(unconstrainedRotation.eulerAngles.y, rotationConstraints.YAxis.AxisRange.Min, rotationConstraints.YAxis.AxisRange.Max);
}
if (rotationConstraints.ZAxis.ConstrainAxis)
{
newZ = Mathf.Clamp(unconstrainedRotation.eulerAngles.z, rotationConstraints.ZAxis.AxisRange.Min, rotationConstraints.ZAxis.AxisRange.Max);
}
return Quaternion.Euler(newX, newY, newZ);
}
public static Vector3 GetConstrainedTransformScale(Vector3 unconstrainedScale, ScaleConstraints scaleConstraints)
{
Vector3 constrainedScale = unconstrainedScale;
if (scaleConstraints.XAxis.ConstrainAxis)
{
constrainedScale.x = Mathf.Clamp(constrainedScale.x, scaleConstraints.XAxis.AxisRange.Min, scaleConstraints.XAxis.AxisRange.Max);
}
if (scaleConstraints.YAxis.ConstrainAxis)
{
constrainedScale.y = Mathf.Clamp(constrainedScale.y, scaleConstraints.YAxis.AxisRange.Min, scaleConstraints.YAxis.AxisRange.Max);
}
if (scaleConstraints.ZAxis.ConstrainAxis)
{
constrainedScale.z = Mathf.Clamp(constrainedScale.z, scaleConstraints.ZAxis.AxisRange.Min, scaleConstraints.ZAxis.AxisRange.Max);
}
return constrainedScale;
}
}
#endregion
}