Skip to content
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

Added a way to modify scaling, translation, and rotation logic using manipulationlogic class #722

Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
using Unity.Profiling;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
using MixedReality.Toolkit.SpatialManipulation;
using MixedReality.Toolkit;

using System.Collections.Generic;
using static MixedReality.Toolkit.SpatialManipulation.ObjectManipulator;
using System;

#if UNITY_EDITOR
using UnityEditor;
Expand Down Expand Up @@ -406,6 +412,34 @@ public ConstraintManager ConstraintsManager
set => constraintsManager = value;
}

[SerializeField]
[Tooltip("The concrete types of ManipulationLogic<T> to use for manipulations.")]
private LogicType manipulationLogicTypes = new LogicType
{
moveLogicType = typeof(BoundsControlMoveLogic),
rotateLogicType = typeof(BoundsControlRotateLogic),
scaleLogicType = typeof(BoundsControlScaleLogic)
};

/// <summary>
/// The concrete types of <see cref="ManipulationLogicImplementations<T>"/> to use for manipulations.
/// </summary>
/// <remarks>
/// Setting this field at runtime can be expensive (reflection) and interrupt/break
/// currently occurring manipulations. Use with caution. Best used at startup or when
/// instantiating ObjectManipulators from code.
/// </remarks>
public LogicType ManipulationLogicTypes
{
get => manipulationLogicTypes;
set
{
// Re-instantiating manip logics is expensive and can interrupt ongoing interactions.
manipulationLogicTypes = value;
InstantiateManipulationLogic();
}
}

[Header("Events")]

[SerializeField]
Expand Down Expand Up @@ -445,6 +479,7 @@ public SelectExitEvent ManipulationEnded
public bool IsFlat { get; protected set; }

private Bounds currentBounds = new Bounds();
public Bounds CurrentBounds => currentBounds;

// The box visuals GameObject instantiated at Awake.
private GameObject boxInstance;
Expand All @@ -455,9 +490,6 @@ public SelectExitEvent ManipulationEnded
// threshold, we don't toggle the handles (as it was probably an intentional ObjectManipulation!)
private Vector3 startMovePosition;

// The interactor selecting the currentHandle's attachTransform position at time of selection.
private Vector3 initialGrabPoint;

// The handle that is currently being manipulated.
private BoundsHandleInteractable currentHandle;

Expand All @@ -469,19 +501,14 @@ public SelectExitEvent ManipulationEnded

// The corner opposite from the current scale handle (if a scale handle is being selected)
private Vector3 oppositeCorner;

// The vector representing the diagonal (from the current scale handle to the opposite corner)
private Vector3 diagonalDir;
public Vector3 OppositeCorner => oppositeCorner;

// Position of the anchor when manipulation started.
private Vector3 initialAnchorOnGrabStart;

// Delta from the anchor to the object's center when manipulation started.
private Vector3 initialAnchorDeltaOnGrabStart;

// Rotate axis during a rotation, translation axis during a translation, etc
private Vector3 currentManipulationAxis;

// If we calculate the bounds at Awake and discover a UGUI autolayout group,
// we need to queue up a second bounds computation pass to take the newly computed
// autolayout into account.
Expand All @@ -502,6 +529,29 @@ public SelectExitEvent ManipulationEnded
// Has the bounds control moved past the toggle threshold throughout the time it was selected?
private bool hasPassedToggleThreshold = false;

protected struct LogicImplementation
hossambasiony1 marked this conversation as resolved.
Show resolved Hide resolved
{
public ManipulationLogic<Vector3> moveLogic;
hossambasiony1 marked this conversation as resolved.
Show resolved Hide resolved
public ManipulationLogic<Quaternion> rotateLogic;
public ManipulationLogic<Vector3> scaleLogic;
}

/// <summary>
/// The instantiated manipulation logic objects, as specified by the types in <see cref="ManipulationLogicTypes"/>.
/// </summary>
protected LogicImplementation ManipulationLogicImplementations { get; private set; }

private void InstantiateManipulationLogic()
{
// Re-instantiate the manipulation logic objects.
ManipulationLogicImplementations = new LogicImplementation()
{
moveLogic = Activator.CreateInstance(ManipulationLogicTypes.moveLogicType) as ManipulationLogic<Vector3>,
rotateLogic = Activator.CreateInstance(ManipulationLogicTypes.rotateLogicType) as ManipulationLogic<Quaternion>,
scaleLogic = Activator.CreateInstance(ManipulationLogicTypes.scaleLogicType) as ManipulationLogic<Vector3>,
};
}

/// <summary>
/// A Unity event function that is called when an enabled script instance is being loaded.
/// </summary>
Expand Down Expand Up @@ -533,6 +583,8 @@ private void Awake()
{
constraintsManager.Setup(new MixedRealityTransform(Target.transform));
}

ManipulationLogicTypes = manipulationLogicTypes;
}

/// <summary>
Expand Down Expand Up @@ -711,28 +763,32 @@ protected virtual internal void OnHandleSelectEntered(BoundsHandleInteractable h
manipulationStarted?.Invoke(args);

currentHandle = handle;
initialGrabPoint = args.interactorObject.GetAttachTransform(handle).position;
initialTransformOnGrabStart = new MixedRealityTransform(Target.transform);
Vector3 anchorPoint = RotateAnchor == RotateAnchorType.BoundsCenter ? Target.transform.TransformPoint(currentBounds.center) : Target.transform.position;
initialAnchorOnGrabStart = anchorPoint;
initialAnchorDeltaOnGrabStart = Target.transform.position - anchorPoint;

// todo: move this out?
if (currentHandle.HandleType == HandleType.Scale)
{
// Will use this to scale the target relative to the opposite corner
oppositeCorner = boxInstance.transform.TransformPoint(-currentHandle.transform.localPosition);
diagonalDir = (currentHandle.transform.position - oppositeCorner).normalized;
// ScaleStarted?.Invoke();

ManipulationLogicImplementations.scaleLogic.Setup(currentHandle.interactorsSelecting, args.interactableObject, initialTransformOnGrabStart);
}
else if (currentHandle.HandleType == HandleType.Rotation)
{
currentManipulationAxis = handle.transform.forward;
// RotateStarted?.Invoke();

ManipulationLogicImplementations.rotateLogic.Setup(currentHandle.interactorsSelecting, args.interactableObject, initialTransformOnGrabStart);
}
else if (currentHandle.HandleType == HandleType.Translation)
{
// currentTranslationAxis = GetTranslationAxis(handle);
// TranslateStarted?.Invoke();

ManipulationLogicImplementations.moveLogic.Setup(currentHandle.interactorsSelecting, args.interactableObject, initialTransformOnGrabStart);
}

if (EnableConstraints && constraintsManager != null)
Expand All @@ -751,21 +807,12 @@ protected virtual void TransformTarget()
{
if (currentHandle != null)
{
Vector3 currentGrabPoint = currentHandle.interactorsSelecting[0].GetAttachTransform(currentHandle).position;
// bool isNear = currentInteractor is IGrabInteractor;

TransformFlags transformUpdated = 0;
MixedRealityTransform targetTransform = new MixedRealityTransform(target.transform);

if (currentHandle.HandleType == HandleType.Rotation)
{
// Compute the anchor around which we will be rotating the object, based
// on the desired RotateAnchorType.
Vector3 anchorPoint = RotateAnchor == RotateAnchorType.BoundsCenter ? Target.transform.TransformPoint(currentBounds.center) : Target.transform.position;

Vector3 initDir = Vector3.ProjectOnPlane(initialGrabPoint - anchorPoint, currentManipulationAxis).normalized;
Vector3 currentDir = Vector3.ProjectOnPlane(currentGrabPoint - anchorPoint, currentManipulationAxis).normalized;
Quaternion initQuat = Quaternion.LookRotation(initDir, currentManipulationAxis);
Quaternion currentQuat = Quaternion.LookRotation(currentDir, currentManipulationAxis);
Quaternion goalRotation = (currentQuat * Quaternion.Inverse(initQuat)) * initialTransformOnGrabStart.Rotation;
var goalRotation = ManipulationLogicImplementations.rotateLogic.Update(currentHandle.interactorsSelecting, interactable, targetTransform, RotateAnchor == RotateAnchorType.BoundsCenter);

Quaternion rotationDelta = goalRotation * Quaternion.Inverse(initialTransformOnGrabStart.Rotation);
Vector3 goalPosition = initialAnchorOnGrabStart + (rotationDelta * initialAnchorDeltaOnGrabStart);
Expand Down Expand Up @@ -803,26 +850,10 @@ protected virtual void TransformTarget()
else if (currentHandle.HandleType == HandleType.Scale)
{
Vector3 anchorPoint = ScaleAnchor == ScaleAnchorType.BoundsCenter ? Target.transform.TransformPoint(currentBounds.center) : oppositeCorner;
Vector3 scaleFactor = Target.transform.localScale;
if (ScaleBehavior == HandleScaleMode.Uniform)
{
float initialDist = Vector3.Dot(initialGrabPoint - anchorPoint, diagonalDir);
float currentDist = Vector3.Dot(currentGrabPoint - anchorPoint, diagonalDir);
float scaleFactorUniform = 1 + (currentDist - initialDist) / initialDist;
scaleFactor = new Vector3(scaleFactorUniform, scaleFactorUniform, scaleFactorUniform);
}
else // non-uniform scaling
{
// get diff from center point of box
Vector3 initialDist = Target.transform.InverseTransformVector(initialGrabPoint - anchorPoint);
Vector3 currentDist = Target.transform.InverseTransformVector(currentGrabPoint - anchorPoint);
Vector3 grabDiff = (currentDist - initialDist);

scaleFactor = Vector3.one + grabDiff.Div(initialDist);
}
var newScale = ManipulationLogicImplementations.scaleLogic.Update(currentHandle.interactorsSelecting, interactable, targetTransform, ScaleAnchor == ScaleAnchorType.BoundsCenter);

Vector3 newScale = initialTransformOnGrabStart.Scale.Mul(scaleFactor);
MixedRealityTransform clampedTransform = MixedRealityTransform.NewScale(newScale);

if (EnableConstraints && constraintsManager != null)
{
constraintsManager.ApplyScaleConstraints(ref clampedTransform, true, currentHandle.IsGrabSelected);
Expand Down Expand Up @@ -852,9 +883,8 @@ protected virtual void TransformTarget()
}
else if (currentHandle.HandleType == HandleType.Translation)
{
Vector3 translateVectorAlongAxis = Vector3.Project(currentGrabPoint - initialGrabPoint, currentHandle.transform.forward);
var goal = ManipulationLogicImplementations.moveLogic.Update(currentHandle.interactorsSelecting, interactable, targetTransform, true);

var goal = initialTransformOnGrabStart.Position + translateVectorAlongAxis;
MixedRealityTransform constraintTranslate = MixedRealityTransform.NewTranslate(goal);
if (EnableConstraints && constraintsManager != null)
{
Expand Down Expand Up @@ -888,3 +918,123 @@ private void CheckToggleThreshold()
}
}
}
/*
* These are the three classes that define the default manipulation behavior for Moving, Scaling and Rotation respectively
*/
public class BoundsControlMoveLogic : ManipulationLogic<Vector3>
hossambasiony1 marked this conversation as resolved.
Show resolved Hide resolved
hossambasiony1 marked this conversation as resolved.
Show resolved Hide resolved
{
private BoundsControl boundsCont;
private BoundsHandleInteractable currentHandle;
private Vector3 initialGrabPoint;
private MixedRealityTransform initialTransformOnGrabStart;

public override void Setup(List<IXRSelectInteractor> interactors, IXRSelectInteractable interactable, MixedRealityTransform currentTarget)
hossambasiony1 marked this conversation as resolved.
Show resolved Hide resolved
{
base.Setup(interactors, interactable, currentTarget);
currentHandle = interactable.transform.GetComponent<BoundsHandleInteractable>();
boundsCont = currentHandle.BoundsControlRoot;
initialGrabPoint = currentHandle.interactorsSelecting[0].GetAttachTransform(currentHandle).position;
initialTransformOnGrabStart = new MixedRealityTransform(boundsCont.Target.transform);
}

/// <inheritdoc />
public override Vector3 Update(List<IXRSelectInteractor> interactors, IXRSelectInteractable interactable, MixedRealityTransform currentTarget, bool centeredAnchor)
{
base.Update(interactors, interactable, currentTarget, centeredAnchor);

Vector3 currentGrabPoint = currentHandle.interactorsSelecting[0].GetAttachTransform(currentHandle).position;
Vector3 translateVectorAlongAxis = Vector3.Project(currentGrabPoint - initialGrabPoint, currentHandle.transform.forward);

return initialTransformOnGrabStart.Position + translateVectorAlongAxis;
}
}

public class BoundsControlScaleLogic : ManipulationLogic<Vector3>
hossambasiony1 marked this conversation as resolved.
Show resolved Hide resolved
{
private BoundsControl boundsCont;
private BoundsHandleInteractable currentHandle;
private Vector3 initialGrabPoint;
private MixedRealityTransform initialTransformOnGrabStart;
private Vector3 diagonalDir;

/// <inheritdoc />
public override void Setup(List<IXRSelectInteractor> interactors, IXRSelectInteractable interactable, MixedRealityTransform currentTarget)
{
base.Setup(interactors, interactable, currentTarget);
currentHandle = interactable.transform.GetComponent<BoundsHandleInteractable>();
boundsCont = currentHandle.BoundsControlRoot;
initialGrabPoint = currentHandle.interactorsSelecting[0].GetAttachTransform(currentHandle).position;
initialTransformOnGrabStart = new MixedRealityTransform(boundsCont.Target.transform);
diagonalDir = (currentHandle.transform.position - boundsCont.OppositeCorner).normalized;
}

/// <inheritdoc />
public override Vector3 Update(List<IXRSelectInteractor> interactors, IXRSelectInteractable interactable, MixedRealityTransform currentTarget, bool centeredAnchor)
{
base.Update(interactors, interactable, currentTarget, centeredAnchor);

Vector3 anchorPoint = centeredAnchor ? boundsCont.Target.transform.TransformPoint(boundsCont.CurrentBounds.center) : boundsCont.OppositeCorner;
Vector3 scaleFactor = boundsCont.Target.transform.localScale;
Vector3 currentGrabPoint = currentHandle.interactorsSelecting[0].GetAttachTransform(currentHandle).position;

if (boundsCont.ScaleBehavior == HandleScaleMode.Uniform)
{
float initialDist = Vector3.Dot(initialGrabPoint - anchorPoint, diagonalDir);
float currentDist = Vector3.Dot(currentGrabPoint - anchorPoint, diagonalDir);
float scaleFactorUniform = 1 + (currentDist - initialDist) / initialDist;
scaleFactor = new Vector3(scaleFactorUniform, scaleFactorUniform, scaleFactorUniform);
}
else // non-uniform scaling
{
// get diff from center point of box
Vector3 initialDist = boundsCont.Target.transform.InverseTransformVector(initialGrabPoint - anchorPoint);
Vector3 currentDist = boundsCont.Target.transform.InverseTransformVector(currentGrabPoint - anchorPoint);
Vector3 grabDiff = (currentDist - initialDist);

scaleFactor = Vector3.one + grabDiff.Div(initialDist);
}

Vector3 newScale = initialTransformOnGrabStart.Scale.Mul(scaleFactor);
return newScale;
}
}

public class BoundsControlRotateLogic : ManipulationLogic<Quaternion>
hossambasiony1 marked this conversation as resolved.
Show resolved Hide resolved
{
private BoundsControl boundsCont;
private BoundsHandleInteractable currentHandle;
private Vector3 initialGrabPoint;
private Vector3 currentManipulationAxis;
private MixedRealityTransform initialTransformOnGrabStart;

/// <inheritdoc />
public override void Setup(List<IXRSelectInteractor> interactors, IXRSelectInteractable interactable, MixedRealityTransform currentTarget)
{
base.Setup(interactors, interactable, currentTarget);

currentHandle = interactable.transform.GetComponent<BoundsHandleInteractable>();
boundsCont = currentHandle.BoundsControlRoot;
initialGrabPoint = currentHandle.interactorsSelecting[0].GetAttachTransform(currentHandle).position;
currentManipulationAxis = currentHandle.transform.forward;
initialTransformOnGrabStart = new MixedRealityTransform(boundsCont.Target.transform);
}

/// <inheritdoc />
public override Quaternion Update(List<IXRSelectInteractor> interactors, IXRSelectInteractable interactable, MixedRealityTransform currentTarget, bool centeredAnchor)
{
base.Update(interactors, interactable, currentTarget, centeredAnchor);

// Compute the anchor around which we will be rotating the object, based
// on the desired RotateAnchorType.
Vector3 anchorPoint = centeredAnchor ? boundsCont.Target.transform.TransformPoint(boundsCont.CurrentBounds.center) : boundsCont.Target.transform.position;
Vector3 currentGrabPoint = currentHandle.interactorsSelecting[0].GetAttachTransform(currentHandle).position;
Vector3 initDir = Vector3.ProjectOnPlane(initialGrabPoint - anchorPoint, currentManipulationAxis).normalized;
Vector3 currentDir = Vector3.ProjectOnPlane(currentGrabPoint - anchorPoint, currentManipulationAxis).normalized;

Quaternion initQuat = Quaternion.LookRotation(initDir, currentManipulationAxis);
Quaternion currentQuat = Quaternion.LookRotation(currentDir, currentManipulationAxis);
Quaternion goalRotation = (currentQuat * Quaternion.Inverse(initQuat)) * initialTransformOnGrabStart.Rotation;

return goalRotation;
}
}
6 changes: 6 additions & 0 deletions org.mixedrealitytoolkit.spatialmanipulation/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [3.4.0-development] - 2024-04-17

### Added

* Made bounds control overridable for custom translation, scaling and rotation logic using manipulation logic classes. PR #722

## [3.3.0-development] - 2024-04-5

### Added
Expand Down
2 changes: 1 addition & 1 deletion org.mixedrealitytoolkit.spatialmanipulation/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "org.mixedrealitytoolkit.spatialmanipulation",
"version": "3.3.0-development",
"version": "3.4.0-development",
"description": "Spatial manipulation features, including ObjectManipulator, BoundsControl, and the Solvers/Constraints systems.",
"displayName": "MRTK Spatial Manipulation",
"msftFeatureCategory": "MRTK3",
Expand Down