Initialer Upload neues Unity-Projekt

This commit is contained in:
Daniel Ocks
2025-07-21 09:11:14 +02:00
commit eeca72985b
14558 changed files with 1508140 additions and 0 deletions

View File

@ -0,0 +1,372 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus 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 System.Collections.Generic;
using UnityEngine;
using UnityEngine.Serialization;
namespace Oculus.Interaction.Grab.GrabSurfaces
{
/// <summary>
/// Defines a series of points that specify paths along with the snapping pose is valid.
/// The points can be connected to the previous and/or next one, even looping, or completely isolated.
/// When two or more consecutive points are connected, they define a continuous path. The Tangent can be
/// used to specify the curve between two points. It is also possible to connect the last point with the
/// first one to create a closed loop. The rotation at a point in the path is specified by interpolating
/// the rotation of the starting and ending point of the current segment.
/// When a point is not connected to any other point, it is considered as a single point in space.
/// </summary>
[Serializable]
public class BezierGrabSurface : MonoBehaviour, IGrabSurface
{
[SerializeField]
private List<BezierControlPoint> _controlPoints = new List<BezierControlPoint>();
[SerializeField]
[Tooltip("Transform used as a reference to measure the local data of the grab surface")]
private Transform _relativeTo;
public List<BezierControlPoint> ControlPoints => _controlPoints;
private const float MAX_PLANE_DOT = 0.95f;
#region editor events
protected virtual void Reset()
{
_relativeTo = this.GetComponentInParent<IRelativeToRef>()?.RelativeTo;
}
#endregion
protected virtual void Start()
{
this.AssertCollectionField(ControlPoints, nameof(ControlPoints));
this.AssertField(_relativeTo, nameof(_relativeTo));
}
public GrabPoseScore CalculateBestPoseAtSurface(in Pose targetPose, out Pose bestPose,
in PoseMeasureParameters scoringModifier, Transform relativeTo)
{
Pose testPose = Pose.identity;
Pose smallestRotationPose = Pose.identity;
bestPose = targetPose;
GrabPoseScore bestScore = GrabPoseScore.Max;
for (int i = 0; i < _controlPoints.Count; i++)
{
BezierControlPoint currentControlPoint = _controlPoints[i];
BezierControlPoint nextControlPoint = _controlPoints[(i + 1) % _controlPoints.Count];
if (!currentControlPoint.Disconnected
&& nextControlPoint.Disconnected)
{
continue;
}
GrabPoseScore score;
if ((currentControlPoint.Disconnected && nextControlPoint.Disconnected)
|| _controlPoints.Count == 1)
{
Pose worldPose = currentControlPoint.GetPose(relativeTo);
testPose.CopyFrom(worldPose);
score = new GrabPoseScore(targetPose, testPose, scoringModifier.PositionRotationWeight);
}
else
{
Pose start = currentControlPoint.GetPose(relativeTo);
Pose end = nextControlPoint.GetPose(relativeTo);
Vector3 tangent = currentControlPoint.GetTangent(relativeTo);
NearestPointInTriangle(targetPose.position, start.position, tangent, end.position, out float positionT);
float rotationT = ProgressForRotation(targetPose.rotation, start.rotation, end.rotation);
score = GrabPoseHelper.CalculateBestPoseAtSurface(targetPose, out testPose, scoringModifier, relativeTo,
(in Pose target, Transform relativeTo) =>
{
Pose result;
result.position = EvaluateBezier(start.position, tangent, end.position, positionT);
result.rotation = Quaternion.Slerp(start.rotation, end.rotation, positionT);
return result;
},
(in Pose target, Transform relativeTo) =>
{
Pose result;
result.position = EvaluateBezier(start.position, tangent, end.position, rotationT);
result.rotation = Quaternion.Slerp(start.rotation, end.rotation, rotationT);
return result;
});
}
if (score.IsBetterThan(bestScore))
{
bestScore = score;
bestPose.CopyFrom(testPose);
}
}
return bestScore;
}
public bool CalculateBestPoseAtSurface(Ray targetRay, out Pose bestPose, Transform relativeTo)
{
Pose testPose = Pose.identity;
Pose targetPose = Pose.identity;
bestPose = Pose.identity;
bool poseFound = false;
GrabPoseScore bestScore = GrabPoseScore.Max;
for (int i = 0; i < _controlPoints.Count; i++)
{
BezierControlPoint currentControlPoint = _controlPoints[i];
BezierControlPoint nextControlPoint = _controlPoints[(i + 1) % _controlPoints.Count];
if (!currentControlPoint.Disconnected
&& nextControlPoint.Disconnected)
{
continue;
}
if ((currentControlPoint.Disconnected && nextControlPoint.Disconnected)
|| _controlPoints.Count == 1)
{
Pose worldPose = currentControlPoint.GetPose(relativeTo);
Plane plane = new Plane(-targetRay.direction, worldPose.position);
if (!plane.Raycast(targetRay, out float enter))
{
continue;
}
targetPose.position = targetRay.GetPoint(enter);
testPose.CopyFrom(worldPose);
}
else
{
Pose start = currentControlPoint.GetPose(relativeTo);
Pose end = nextControlPoint.GetPose(relativeTo);
Vector3 tangent = currentControlPoint.GetTangent(relativeTo);
Plane plane = GenerateRaycastPlane(start.position, tangent, end.position, -targetRay.direction);
if (!plane.Raycast(targetRay, out float enter))
{
continue;
}
targetPose.position = targetRay.GetPoint(enter);
NearestPointInTriangle(targetPose.position, start.position, tangent, end.position, out float t);
testPose.position = EvaluateBezier(start.position, tangent, end.position, t);
testPose.rotation = Quaternion.Slerp(start.rotation, end.rotation, t);
}
GrabPoseScore score = new GrabPoseScore(targetPose.position, testPose.position);
if (score.IsBetterThan(bestScore))
{
bestScore = score;
bestPose.CopyFrom(testPose);
poseFound = true;
}
}
return poseFound;
}
private Plane GenerateRaycastPlane(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 fallbackDir)
{
Vector3 line0 = (p1 - p0).normalized;
Vector3 line1 = (p2 - p0).normalized;
Plane plane;
if (Mathf.Abs(Vector3.Dot(line0, line1)) > MAX_PLANE_DOT)
{
plane = new Plane(fallbackDir, (p0 + p2 + p1) / 3f);
}
else
{
plane = new Plane(p0, p1, p2);
}
return plane;
}
private float ProgressForRotation(Quaternion targetRotation, Quaternion from, Quaternion to)
{
Vector3 targetForward = targetRotation * Vector3.forward;
Vector3 fromForward = from * Vector3.forward;
Vector3 toForward = to * Vector3.forward;
Vector3 axis = Vector3.Cross(fromForward, toForward).normalized;
float angleFrom = Vector3.SignedAngle(targetForward, fromForward, axis);
float angleTo = Vector3.SignedAngle(targetForward, toForward, axis);
if (angleFrom < 0 && angleTo < 0)
{
return 1f;
}
if (angleFrom > 0 && angleTo > 0)
{
return 0f;
}
return Mathf.Abs(angleFrom) / Vector3.Angle(fromForward, toForward);
}
private Vector3 NearestPointInTriangle(Vector3 point, Vector3 p0, Vector3 p1, Vector3 p2, out float t)
{
Vector3 centroid = (p0 + p1 + p2) / 3f;
Vector3 pointInMedian0 = NearestPointToSegment(point, p0, centroid, out float t0);
Vector3 pointInMedian1 = NearestPointToSegment(point, centroid, p2, out float t1);
float median0 = Vector3.Distance(p0, centroid);
float median2 = Vector3.Distance(p2, centroid);
float alpha = median2 / (median0 + median2);
float distance0 = (pointInMedian0 - point).sqrMagnitude;
float distance1 = (pointInMedian1 - point).sqrMagnitude;
if (distance0 < distance1)
{
t = t0 * alpha;
return pointInMedian0;
}
else
{
t = alpha + t1 * (1f - alpha);
return pointInMedian1;
}
}
private Vector3 NearestPointToSegment(Vector3 point, Vector3 start, Vector3 end, out float progress)
{
Vector3 segment = end - start;
Vector3 projection = Vector3.Project(point - start, segment.normalized);
Vector3 pointInSegment;
if (Vector3.Dot(segment, projection) <= 0)
{
pointInSegment = start;
progress = 0;
}
else if (projection.sqrMagnitude >= segment.sqrMagnitude)
{
pointInSegment = end;
progress = 1;
}
else
{
pointInSegment = start + projection;
progress = projection.magnitude / segment.magnitude;
}
return pointInSegment;
}
public IGrabSurface CreateDuplicatedSurface(GameObject gameObject)
{
BezierGrabSurface surface = gameObject.AddComponent<BezierGrabSurface>();
surface._controlPoints = new List<BezierControlPoint>(_controlPoints);
return surface;
}
public IGrabSurface CreateMirroredSurface(GameObject gameObject)
{
BezierGrabSurface mirroredSurface = gameObject.AddComponent<BezierGrabSurface>();
mirroredSurface._controlPoints = new List<BezierControlPoint>();
foreach (BezierControlPoint controlPoint in _controlPoints)
{
Pose pose = controlPoint.GetPose(_relativeTo);
pose.rotation = pose.rotation * Quaternion.Euler(180f, 180f, 0f);
controlPoint.SetPose(pose, _relativeTo);
mirroredSurface._controlPoints.Add(controlPoint);
}
return mirroredSurface;
}
public Pose MirrorPose(in Pose gripPose, Transform relativeTo)
{
return gripPose;
}
public static Vector3 EvaluateBezier(Vector3 start, Vector3 middle, Vector3 end, float t)
{
t = Mathf.Clamp01(t);
float oneMinusT = 1f - t;
return (oneMinusT * oneMinusT * start)
+ (2f * oneMinusT * t * middle)
+ (t * t * end);
}
#region Inject
public void InjectAllBezierSurface(List<BezierControlPoint> controlPoints,
Transform relativeTo)
{
InjectControlPoints(controlPoints);
InjectRelativeTo(relativeTo);
}
public void InjectControlPoints(List<BezierControlPoint> controlPoints)
{
_controlPoints = controlPoints;
}
public void InjectRelativeTo(Transform relativeTo)
{
_relativeTo = relativeTo;
}
#endregion
}
[Serializable]
public struct BezierControlPoint
{
[SerializeField]
[FormerlySerializedAs("pose")]
private Pose _pose;
[SerializeField]
[FormerlySerializedAs("tangentPoint")]
private Vector3 _tangentPoint;
[SerializeField]
[FormerlySerializedAs("disconnected")]
private bool _disconnected;
public bool Disconnected
{
get
{
return _disconnected;
}
set
{
_disconnected = value;
}
}
public Pose GetPose(Transform relativeTo)
{
return PoseUtils.GlobalPoseScaled(relativeTo, _pose);
}
public void SetPose(in Pose worldSpacePose, Transform relativeTo)
{
_pose = PoseUtils.DeltaScaled(relativeTo, worldSpacePose);
}
public Vector3 GetTangent(Transform relativeTo)
{
return relativeTo.TransformPoint(_tangentPoint);
}
public void SetTangent(in Vector3 tangent, Transform relativeTo)
{
_tangentPoint = relativeTo.InverseTransformPoint(tangent);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fe16fe5c237c19b45bc8c5843cfc4934
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,377 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus 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;
namespace Oculus.Interaction.Grab.GrabSurfaces
{
[Serializable]
public class BoxGrabSurfaceData : ICloneable
{
public object Clone()
{
BoxGrabSurfaceData clone = new BoxGrabSurfaceData();
clone.widthOffset = this.widthOffset;
clone.snapOffset = this.snapOffset;
clone.size = this.size;
clone.eulerAngles = this.eulerAngles;
return clone;
}
public BoxGrabSurfaceData Mirror()
{
BoxGrabSurfaceData mirror = Clone() as BoxGrabSurfaceData;
mirror.snapOffset = new Vector4(
-mirror.snapOffset.y, -mirror.snapOffset.x,
-mirror.snapOffset.w, -mirror.snapOffset.z);
return mirror;
}
[Range(0f, 1f)]
public float widthOffset = 0.5f;
public Vector4 snapOffset;
public Vector3 size = new Vector3(0.1f, 0f, 0.1f);
public Vector3 eulerAngles;
}
/// <summary>
/// This GrabSurface defines a Rectangle around which the grip point is valid.
/// Since the grip point might be offset from the fingers, a valid range for each opposite
/// side of the rectangle can be set so the grabbing fingers are constrained to the object bounds.
/// </summary>
[Serializable]
public class BoxGrabSurface : MonoBehaviour, IGrabSurface
{
[SerializeField]
protected BoxGrabSurfaceData _data = new BoxGrabSurfaceData();
[SerializeField]
[Tooltip("Transform used as a reference to measure the local data of the grab surface")]
private Transform _relativeTo;
private Pose RelativePose => PoseUtils.DeltaScaled(_relativeTo, this.transform);
/// <summary>
/// The origin pose of the surface. This is the point from which
/// the base of the box must start.
/// </summary>
/// <param name="relativeTo">The reference transform to apply the surface to</param>
/// <returns>Pose in world space</returns>
public Pose GetReferencePose(Transform relativeTo)
{
return PoseUtils.GlobalPoseScaled(relativeTo, RelativePose);
}
/// <summary>
/// The lateral displacement of the grip point in the main side.
/// </summary>
/// <param name="relativeTo">The reference transform to apply the surface to</param>
/// <returns>Lateral displacement in world space</returns>
public float GetWidthOffset(Transform relativeTo)
{
return _data.widthOffset * relativeTo.lossyScale.x;
}
public void SetWidthOffset(float widthOffset, Transform relativeTo)
{
_data.widthOffset = widthOffset / relativeTo.lossyScale.x;
}
/// <summary>
/// The range at which the sides are constrained.
/// X,Y for the back and forward sides range.
/// Z,W for the left and right sides range.
/// </summary>
/// <param name="relativeTo">The reference transform to apply the surface to</param>
/// <returns>Offsets in world space</returns>
public Vector4 GetSnapOffset(Transform relativeTo)
{
return _data.snapOffset * relativeTo.lossyScale.x;
}
public void SetSnapOffset(Vector4 snapOffset, Transform relativeTo)
{
_data.snapOffset = snapOffset / relativeTo.lossyScale.x;
}
/// <summary>
/// The size of the rectangle. Y is ignored.
/// </summary>
/// <param name="relativeTo">The reference transform to apply the surface to</param>
/// <returns>Size in world space</returns>
public Vector3 GetSize(Transform relativeTo)
{
return _data.size * relativeTo.lossyScale.x;
}
public void SetSize(Vector3 size, Transform relativeTo)
{
_data.size = size / relativeTo.lossyScale.x;
}
/// <summary>
/// The rotation of the rectangle from the Grip point
/// </summary>
/// <param name="relativeTo">The reference transform to apply the surface to</param>
/// <returns>Rotation in world space</returns>
public Quaternion GetRotation(Transform relativeTo)
{
return relativeTo.rotation * Quaternion.Euler(_data.eulerAngles);
}
public void SetRotation(Quaternion rotation, Transform relativeTo)
{
_data.eulerAngles = (Quaternion.Inverse(relativeTo.rotation) * rotation).eulerAngles;
}
/// <summary>
/// The forward direction of the rectangle (based on its rotation)
/// </summary>
/// <param name="relativeTo">The reference transform to apply the surface to</param>
/// <returns>Direction in world space</returns>
public Vector3 GetDirection(Transform relativeTo)
{
return GetRotation(relativeTo) * Vector3.forward;
}
#region editor events
protected virtual void Reset()
{
_relativeTo = this.GetComponentInParent<IRelativeToRef>()?.RelativeTo;
}
#endregion
protected virtual void Start()
{
this.AssertField(_data, nameof(_data));
this.AssertField(_relativeTo, nameof(_relativeTo));
}
public Pose MirrorPose(in Pose pose, Transform relativeTo)
{
Quaternion rotation = GetRotation(relativeTo);
Vector3 normal = Quaternion.Inverse(relativeTo.rotation) * rotation * Vector3.forward;
Vector3 tangent = Quaternion.Inverse(relativeTo.rotation) * (rotation * Vector3.up);
return pose.MirrorPoseRotation(normal, tangent);
}
public IGrabSurface CreateMirroredSurface(GameObject gameObject)
{
BoxGrabSurface surface = gameObject.AddComponent<BoxGrabSurface>();
surface._data = _data.Mirror();
return surface;
}
public IGrabSurface CreateDuplicatedSurface(GameObject gameObject)
{
BoxGrabSurface surface = gameObject.AddComponent<BoxGrabSurface>();
surface._data = _data;
return surface;
}
public GrabPoseScore CalculateBestPoseAtSurface(in Pose targetPose, out Pose bestPose,
in PoseMeasureParameters scoringModifier, Transform relativeTo)
{
return GrabPoseHelper.CalculateBestPoseAtSurface(targetPose, out bestPose,
scoringModifier, relativeTo,
MinimalTranslationPoseAtSurface, MinimalRotationPoseAtSurface);
}
private void CalculateCorners(out Vector3 bottomLeft, out Vector3 bottomRight, out Vector3 topLeft, out Vector3 topRight,
Transform relativeTo)
{
Pose referencePose = GetReferencePose(relativeTo);
Vector3 size = GetSize(relativeTo);
float widthOffset = GetWidthOffset(relativeTo);
Vector3 rightRot = GetRotation(relativeTo) * Vector3.right;
bottomLeft = referencePose.position - rightRot * size.x * (1f - widthOffset);
bottomRight = referencePose.position + rightRot * size.x * (widthOffset);
Vector3 forwardOffset = GetRotation(relativeTo) * Vector3.forward * size.z;
topLeft = bottomLeft + forwardOffset;
topRight = bottomRight + forwardOffset;
}
private Vector3 ProjectOnSegment(Vector3 point, (Vector3, Vector3) segment)
{
Vector3 line = segment.Item2 - segment.Item1;
Vector3 projection = Vector3.Project(point - segment.Item1, line);
if (Vector3.Dot(projection, line) < 0f)
{
projection = segment.Item1;
}
else if (projection.magnitude > line.magnitude)
{
projection = segment.Item2;
}
else
{
projection += segment.Item1;
}
return projection;
}
public bool CalculateBestPoseAtSurface(Ray targetRay, out Pose bestPose, Transform relativeTo)
{
Pose recordedPose = GetReferencePose(relativeTo);
Plane plane = new Plane(GetRotation(relativeTo) * Vector3.up, this.transform.position);
plane.Raycast(targetRay, out float rayDistance);
Vector3 proximalPoint = targetRay.origin + targetRay.direction * rayDistance;
Vector3 surfacePoint = NearestPointInSurface(proximalPoint, relativeTo);
Pose desiredPose = new Pose(surfacePoint, recordedPose.rotation);
bestPose = MinimalTranslationPoseAtSurface(desiredPose, relativeTo);
return true;
}
protected Vector3 NearestPointInSurface(Vector3 targetPosition, Transform relativeTo)
{
NearestPointAndAngleInSurface(targetPosition, out Vector3 surfacePoint, out float angle, relativeTo);
return surfacePoint;
}
private void NearestPointAndAngleInSurface(Vector3 targetPosition, out Vector3 surfacePoint, out float angle, Transform relativeTo)
{
Quaternion rotation = GetRotation(relativeTo);
Vector4 snappOffset = GetSnapOffset(relativeTo);
Vector3 rightDir = rotation * Vector3.right;
Vector3 forwardDir = rotation * Vector3.forward;
Vector3 bottomLeft, bottomRight, topLeft, topRight;
CalculateCorners(out bottomLeft, out bottomRight, out topLeft, out topRight, relativeTo);
Vector3 bottomP = ProjectOnSegment(targetPosition,
(bottomLeft + rightDir * snappOffset.y, bottomRight + rightDir * snappOffset.x));
Vector3 topP = ProjectOnSegment(targetPosition,
(topLeft - rightDir * snappOffset.x, topRight - rightDir * snappOffset.y));
Vector3 leftP = ProjectOnSegment(targetPosition,
(bottomLeft - forwardDir * snappOffset.z, topLeft - forwardDir * snappOffset.w));
Vector3 rightP = ProjectOnSegment(targetPosition,
(bottomRight + forwardDir * snappOffset.w, topRight + forwardDir * snappOffset.z));
float bottomDistance = (bottomP - targetPosition).sqrMagnitude;
float topDistance = (topP - targetPosition).sqrMagnitude;
float leftDistance = (leftP - targetPosition).sqrMagnitude;
float rightDistance = (rightP - targetPosition).sqrMagnitude;
float minDistance = Mathf.Min(bottomDistance,
Mathf.Min(topDistance,
Mathf.Min(leftDistance, rightDistance)));
if (bottomDistance == minDistance)
{
surfacePoint = bottomP;
angle = 0f;
return;
}
if (topDistance == minDistance)
{
surfacePoint = topP;
angle = 180f;
return;
}
if (leftDistance == minDistance)
{
surfacePoint = leftP;
angle = 90f;
return;
}
surfacePoint = rightP;
angle = -90f;
}
protected Pose MinimalRotationPoseAtSurface(in Pose userPose, Transform relativeTo)
{
Quaternion rotation = GetRotation(relativeTo);
Pose referencePose = GetReferencePose(relativeTo);
Vector4 snappOffset = GetSnapOffset(relativeTo);
Vector3 desiredPos = userPose.position;
Quaternion baseRot = referencePose.rotation;
Quaternion desiredRot = userPose.rotation;
Vector3 up = rotation * Vector3.up;
Quaternion bottomRot = baseRot;
Quaternion topRot = Quaternion.AngleAxis(180f, up) * baseRot;
Quaternion leftRot = Quaternion.AngleAxis(90f, up) * baseRot;
Quaternion rightRot = Quaternion.AngleAxis(-90f, up) * baseRot;
float bottomDot = RotationalScore(bottomRot, desiredRot);
float topDot = RotationalScore(topRot, desiredRot);
float leftDot = RotationalScore(leftRot, desiredRot);
float rightDot = RotationalScore(rightRot, desiredRot);
Vector3 rightDir = rotation * Vector3.right;
Vector3 forwardDir = rotation * Vector3.forward;
Vector3 bottomLeft, bottomRight, topLeft, topRight;
CalculateCorners(out bottomLeft, out bottomRight, out topLeft, out topRight, relativeTo);
float maxDot = Mathf.Max(bottomDot, Mathf.Max(topDot, Mathf.Max(leftDot, rightDot)));
if (bottomDot == maxDot)
{
Vector3 projBottom = ProjectOnSegment(desiredPos, (bottomLeft + rightDir * snappOffset.y, bottomRight + rightDir * snappOffset.x));
return new Pose(projBottom, bottomRot);
}
if (topDot == maxDot)
{
Vector3 projTop = ProjectOnSegment(desiredPos, (topLeft - rightDir * snappOffset.x, topRight - rightDir * snappOffset.y));
return new Pose(projTop, topRot);
}
if (leftDot == maxDot)
{
Vector3 projLeft = ProjectOnSegment(desiredPos, (bottomLeft - forwardDir * snappOffset.z, topLeft - forwardDir * snappOffset.w));
return new Pose(projLeft, leftRot);
}
Vector3 projRight = ProjectOnSegment(desiredPos, (bottomRight + forwardDir * snappOffset.w, topRight + forwardDir * snappOffset.z));
return new Pose(projRight, rightRot);
}
protected Pose MinimalTranslationPoseAtSurface(in Pose userPose, Transform relativeTo)
{
Pose referencePose = GetReferencePose(relativeTo);
Quaternion rotation = GetRotation(relativeTo);
Vector3 desiredPos = userPose.position;
Quaternion baseRot = referencePose.rotation;
NearestPointAndAngleInSurface(desiredPos, out Vector3 surfacePoint, out float surfaceAngle, relativeTo);
Quaternion surfaceRotation = Quaternion.AngleAxis(surfaceAngle, rotation * Vector3.up) * baseRot;
return new Pose(surfacePoint, surfaceRotation);
}
private static float RotationalScore(in Quaternion from, in Quaternion to)
{
float forwardDifference = Vector3.Dot(from * Vector3.forward, to * Vector3.forward) * 0.5f + 0.5f;
float upDifference = Vector3.Dot(from * Vector3.up, to * Vector3.up) * 0.5f + 0.5f;
return (forwardDifference * upDifference);
}
#region Inject
public void InjectAllBoxSurface(BoxGrabSurfaceData data, Transform relativeTo)
{
InjectData(data);
InjectRelativeTo(relativeTo);
}
public void InjectData(BoxGrabSurfaceData data)
{
_data = data;
}
public void InjectRelativeTo(Transform relativeTo)
{
_relativeTo = relativeTo;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bbe6418317e852d4e8fd122a4149acba
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,94 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus 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 UnityEngine;
namespace Oculus.Interaction.Grab.GrabSurfaces
{
public class ColliderGrabSurface : MonoBehaviour, IGrabSurface
{
[SerializeField]
private Collider _collider;
protected virtual void Start()
{
this.AssertField(_collider, nameof(_collider));
}
private Vector3 NearestPointInSurface(Vector3 targetPosition)
{
if (_collider.bounds.Contains(targetPosition))
{
targetPosition = _collider.ClosestPointOnBounds(targetPosition);
}
return _collider.ClosestPoint(targetPosition);
}
public GrabPoseScore CalculateBestPoseAtSurface(in Pose targetPose, out Pose bestPose,
in PoseMeasureParameters scoringModifier, Transform relativeTo)
{
Vector3 surfacePoint = NearestPointInSurface(targetPose.position);
bestPose = new Pose(surfacePoint, targetPose.rotation);
return new GrabPoseScore(surfacePoint, targetPose.position);
}
public bool CalculateBestPoseAtSurface(Ray targetRay, out Pose bestPose, Transform relativeTo)
{
if (_collider.Raycast(targetRay, out RaycastHit hit, Mathf.Infinity))
{
bestPose.position = hit.point;
bestPose.rotation = relativeTo.rotation;
return true;
}
bestPose = Pose.identity;
return false;
}
public Pose MirrorPose(in Pose gripPose, Transform relativeTo)
{
return gripPose;
}
public IGrabSurface CreateMirroredSurface(GameObject gameObject)
{
return CreateDuplicatedSurface(gameObject);
}
public IGrabSurface CreateDuplicatedSurface(GameObject gameObject)
{
ColliderGrabSurface colliderSurface = gameObject.AddComponent<ColliderGrabSurface>();
colliderSurface.InjectAllColliderGrabSurface(_collider);
return colliderSurface;
}
#region Inject
public void InjectAllColliderGrabSurface(Collider collider)
{
InjectCollider(collider);
}
public void InjectCollider(Collider collider)
{
_collider = collider;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8951a656a7c00e74094166ef415cdce5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,450 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus 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.Grab.GrabSurfaces
{
[Serializable]
public class CylinderSurfaceData : ICloneable
{
public object Clone()
{
CylinderSurfaceData clone = new CylinderSurfaceData();
clone.startPoint = this.startPoint;
clone.endPoint = this.endPoint;
clone.arcOffset = this.arcOffset;
clone.arcLength = this.arcLength;
return clone;
}
public CylinderSurfaceData Mirror()
{
CylinderSurfaceData mirror = Clone() as CylinderSurfaceData;
return mirror;
}
public Vector3 startPoint = new Vector3(0f, 0.1f, 0f);
public Vector3 endPoint = new Vector3(0f, -0.1f, 0f);
[Range(0f, 360f)]
public float arcOffset = 0f;
[Range(0f, 360f)]
[FormerlySerializedAs("angle")]
public float arcLength = 360f;
}
/// <summary>
/// This type of surface defines a cylinder in which the grip pose is valid around an object.
/// An angle and offset can be used to constrain the cylinder and not use a full circle.
/// The radius is automatically specified as the distance from the axis of the cylinder to the original grip position.
/// </summary>
[Serializable]
public class CylinderGrabSurface : MonoBehaviour, IGrabSurface
{
[SerializeField]
protected CylinderSurfaceData _data = new CylinderSurfaceData();
[SerializeField]
[Tooltip("Transform used as a reference to measure the local data of the grab surface")]
private Transform _relativeTo;
private Pose RelativePose => PoseUtils.DeltaScaled(_relativeTo, this.transform);
private const float Epsilon = 0.000001f;
/// <summary>
/// The reference pose of the surface. It defines the radius of the cylinder
/// as the point from the relative transform to the reference pose to ensure
/// that the cylinder covers this pose.
/// </summary>
/// <param name="relativeTo">The reference transform to apply the surface to</param>
/// <returns>Pose in world space</returns>
public Pose GetReferencePose(Transform relativeTo)
{
return PoseUtils.GlobalPoseScaled(relativeTo, RelativePose);
}
/// <summary>
/// Degrees from the starting radius from which the arc section starts
/// </summary>
public float ArcOffset
{
get
{
return _data.arcOffset;
}
set
{
if (value != 0 && value % 360f == 0)
{
_data.arcOffset = 360f;
}
else
{
_data.arcOffset = Mathf.Repeat(value, 360f);
}
}
}
/// <summary>
/// The maximum angle for the surface of the cylinder, starting from the ArcOffset.
/// To invert the direction of the angle, swap the caps order.
/// </summary>
public float ArcLength
{
get
{
return _data.arcLength;
}
set
{
if (value != 0 && value % 360f == 0)
{
_data.arcLength = 360f;
}
else
{
_data.arcLength = Mathf.Repeat(value, 360f);
}
}
}
/// <summary>
/// The direction of the main radius of the cylinder. This is the
/// radius from the center of the cylinder to the reference position.
/// </summary>
private Vector3 LocalPerpendicularDir
{
get
{
return Vector3.ProjectOnPlane(RelativePose.position - _data.startPoint, LocalDirection).normalized;
}
}
/// <summary>
/// The direction of the central axis of the cylinder in local space.
/// </summary>
private Vector3 LocalDirection
{
get
{
Vector3 dir = (_data.endPoint - _data.startPoint);
if (dir.sqrMagnitude <= Epsilon)
{
return Vector3.up;
}
return dir.normalized;
}
}
/// <summary>
/// Direction from the axis of the cylinder to the original grip position.
/// </summary>
/// <param name="relativeTo">The reference transform to apply the surface to</param>
/// <returns>Direction in world space</returns>
public Vector3 GetPerpendicularDir(Transform relativeTo)
{
return relativeTo.TransformDirection(LocalPerpendicularDir);
}
/// <summary>
/// Direction from the axis of the cylinder to the minimum angle allowance.
/// </summary>
/// <param name="relativeTo">The reference transform to apply the surface to</param>
/// <returns>Direction in world space</returns>
public Vector3 GetStartArcDir(Transform relativeTo)
{
Vector3 localStartArcDir = Quaternion.AngleAxis(ArcOffset, LocalDirection) * LocalPerpendicularDir;
return relativeTo.TransformDirection(localStartArcDir);
}
/// <summary>
/// Direction from the axis of the cylinder to the maximum angle allowance.
/// </summary>
/// <param name="relativeTo">The reference transform to apply the surface to</param>
/// <returns>Direction in world space</returns>
public Vector3 GetEndArcDir(Transform relativeTo)
{
Vector3 localEndArcDir = Quaternion.AngleAxis(ArcLength, LocalDirection) *
Quaternion.AngleAxis(ArcOffset, LocalDirection) * LocalPerpendicularDir;
return relativeTo.TransformDirection(localEndArcDir);
}
/// <summary>
/// Base cap of the cylinder, in world coordinates.
/// </summary>
/// <param name="relativeTo">The reference transform to apply the surface to</param>
/// <returns>Position in world space</returns>
public Vector3 GetStartPoint(Transform relativeTo)
{
return relativeTo.TransformPoint(_data.startPoint);
}
public void SetStartPoint(Vector3 point, Transform relativeTo)
{
_data.startPoint = relativeTo.InverseTransformPoint(point);
}
/// <summary>
/// End cap of the cylinder, in world coordinates.
/// </summary>
/// <param name="relativeTo">The reference transform to apply the surface to</param>
/// <returns>Position in world space</returns>
public Vector3 GetEndPoint(Transform relativeTo)
{
return relativeTo.TransformPoint(_data.endPoint);
}
public void SetEndPoint(Vector3 point, Transform relativeTo)
{
_data.endPoint = relativeTo.InverseTransformPoint(point);
}
/// <summary>
/// The generated radius of the cylinder.
/// Represents the distance from the axis of the cylinder to the original grip position.
/// </summary>
/// <param name="relativeTo">The reference transform to apply the surface to</param>
/// <returns>Distance in world space</returns>
public float GetRadius(Transform relativeTo)
{
Vector3 start = GetStartPoint(relativeTo);
Pose referencePose = GetReferencePose(relativeTo);
Vector3 direction = GetDirection(relativeTo);
Vector3 projectedPoint = start + Vector3.Project(referencePose.position - start, direction);
return Vector3.Distance(projectedPoint, referencePose.position);
}
/// <summary>
/// Direction of the cylinder, from the start cap to the end cap.
/// </summary>
/// <param name="relativeTo">The reference transform to apply the surface to</param>
/// <returns>Direction in world space</returns>
public Vector3 GetDirection(Transform relativeTo)
{
return relativeTo.TransformDirection(LocalDirection);
}
/// <summary>
/// Length of the cylinder, from the start cap to the end cap.
/// </summary>
/// <param name="relativeTo">The reference transform to apply the surface to</param>
/// <returns>Distance in world space</returns>
private float GetHeight(Transform relativeTo)
{
Vector3 start = GetStartPoint(relativeTo);
Vector3 end = GetEndPoint(relativeTo);
return Vector3.Distance(start, end);
}
/// <summary>
/// The rotation of the central axis of the cylinder.
/// </summary>
/// <param name="relativeTo">The reference transform to apply the surface to</param>
/// <returns>Rotation in world space</returns>
private Quaternion GetRotation(Transform relativeTo)
{
if (_data.startPoint == _data.endPoint)
{
return relativeTo.rotation;
}
return relativeTo.rotation * Quaternion.LookRotation(LocalPerpendicularDir, LocalDirection);
}
#region editor events
protected virtual void Reset()
{
_relativeTo = this.GetComponentInParent<IRelativeToRef>()?.RelativeTo;
}
#endregion
protected virtual void Start()
{
this.AssertField(_data, nameof(_data));
this.AssertField(_relativeTo, nameof(_relativeTo));
}
public Pose MirrorPose(in Pose pose, Transform relativeTo)
{
Vector3 normal = Quaternion.Inverse(relativeTo.rotation) * GetPerpendicularDir(relativeTo);
Vector3 tangent = Quaternion.Inverse(relativeTo.rotation) * GetDirection(relativeTo);
return pose.MirrorPoseRotation(normal, tangent);
}
private Vector3 PointAltitude(Vector3 point, Transform relativeTo)
{
Vector3 start = GetStartPoint(relativeTo);
Vector3 direction = GetDirection(relativeTo);
Vector3 projectedPoint = start + Vector3.Project(point - start, direction);
return projectedPoint;
}
public GrabPoseScore CalculateBestPoseAtSurface(in Pose targetPose, out Pose bestPose,
in PoseMeasureParameters scoringModifier, Transform relativeTo)
{
return GrabPoseHelper.CalculateBestPoseAtSurface(targetPose, out bestPose,
scoringModifier, relativeTo,
MinimalTranslationPoseAtSurface,
MinimalRotationPoseAtSurface);
}
public IGrabSurface CreateMirroredSurface(GameObject gameObject)
{
CylinderGrabSurface surface = gameObject.AddComponent<CylinderGrabSurface>();
surface._data = _data.Mirror();
return surface;
}
public IGrabSurface CreateDuplicatedSurface(GameObject gameObject)
{
CylinderGrabSurface surface = gameObject.AddComponent<CylinderGrabSurface>();
surface._data = _data.Clone() as CylinderSurfaceData;
return surface;
}
protected Vector3 NearestPointInSurface(Vector3 targetPosition, Transform relativeTo)
{
Vector3 start = GetStartPoint(relativeTo);
Vector3 dir = GetDirection(relativeTo);
Vector3 projectedVector = Vector3.Project(targetPosition - start, dir);
float height = GetHeight(relativeTo);
if (projectedVector.magnitude > height)
{
projectedVector = projectedVector.normalized * height;
}
if (Vector3.Dot(projectedVector, dir) < 0f)
{
projectedVector = Vector3.zero;
}
Vector3 projectedPoint = start + projectedVector;
Vector3 targetDirection = Vector3.ProjectOnPlane((targetPosition - projectedPoint), dir).normalized;
Vector3 startArcDir = GetStartArcDir(relativeTo);
float desiredAngle = Mathf.Repeat(Vector3.SignedAngle(startArcDir, targetDirection, dir), 360f);
if (desiredAngle > ArcLength)
{
if (Mathf.Abs(desiredAngle - ArcLength) >= Mathf.Abs(360f - desiredAngle))
{
targetDirection = startArcDir;
}
else
{
targetDirection = GetEndArcDir(relativeTo);
}
}
Vector3 surfacePoint = projectedPoint + targetDirection * GetRadius(relativeTo);
return surfacePoint;
}
public bool CalculateBestPoseAtSurface(Ray targetRay, out Pose bestPose,
Transform relativeTo)
{
Pose recordedPose = GetReferencePose(relativeTo);
Vector3 start = GetStartPoint(relativeTo);
Vector3 direction = GetDirection(relativeTo);
Vector3 lineToCylinder = start - targetRay.origin;
float perpendiculiarity = Vector3.Dot(targetRay.direction, direction);
float rayToLineDiff = Vector3.Dot(lineToCylinder, targetRay.direction);
float cylinderToLineDiff = Vector3.Dot(lineToCylinder, direction);
float determinant = 1f / (perpendiculiarity * perpendiculiarity - 1f);
float lineOffset = (perpendiculiarity * cylinderToLineDiff - rayToLineDiff) * determinant;
float cylinderOffset = (cylinderToLineDiff - perpendiculiarity * rayToLineDiff) * determinant;
float radius = GetRadius(relativeTo);
Vector3 pointInLine = targetRay.origin + targetRay.direction * lineOffset;
Vector3 pointInCylinder = start + direction * cylinderOffset;
float distanceToSurface = Mathf.Max(Vector3.Distance(pointInCylinder, pointInLine) - radius);
if (distanceToSurface < radius)
{
float adjustedDistance = Mathf.Sqrt(radius * radius - distanceToSurface * distanceToSurface);
pointInLine -= targetRay.direction * adjustedDistance;
}
Vector3 surfacePoint = NearestPointInSurface(pointInLine, relativeTo);
Pose desiredPose = new Pose(surfacePoint, recordedPose.rotation);
bestPose = MinimalTranslationPoseAtSurface(desiredPose, relativeTo);
return true;
}
protected Pose MinimalRotationPoseAtSurface(in Pose userPose, Transform relativeTo)
{
Pose referencePose = GetReferencePose(relativeTo);
Vector3 direction = GetDirection(relativeTo);
Quaternion rotation = GetRotation(relativeTo);
float radius = GetRadius(relativeTo);
Vector3 desiredPos = userPose.position;
Quaternion desiredRot = userPose.rotation;
Quaternion baseRot = referencePose.rotation;
Quaternion rotDif = (desiredRot) * Quaternion.Inverse(baseRot);
Vector3 desiredDirection = (rotDif * rotation) * Vector3.forward;
Vector3 projectedDirection = Vector3.ProjectOnPlane(desiredDirection, direction).normalized;
Vector3 altitudePoint = PointAltitude(desiredPos, relativeTo);
Vector3 surfacePoint = NearestPointInSurface(altitudePoint + projectedDirection * radius, relativeTo);
Quaternion surfaceRotation = CalculateRotationOffset(surfacePoint, relativeTo) * baseRot;
return new Pose(surfacePoint, surfaceRotation);
}
protected Pose MinimalTranslationPoseAtSurface(in Pose userPose, Transform relativeTo)
{
Pose referencePose = GetReferencePose(relativeTo);
Vector3 desiredPos = userPose.position;
Quaternion baseRot = referencePose.rotation;
Vector3 surfacePoint = NearestPointInSurface(desiredPos, relativeTo);
Quaternion surfaceRotation = CalculateRotationOffset(surfacePoint, relativeTo) * baseRot;
return new Pose(surfacePoint, surfaceRotation);
}
protected Quaternion CalculateRotationOffset(Vector3 surfacePoint, Transform relativeTo)
{
Vector3 start = GetStartPoint(relativeTo);
Vector3 direction = GetDirection(relativeTo);
Vector3 referenceDir = GetPerpendicularDir(relativeTo);
Vector3 recordedDirection = Vector3.ProjectOnPlane(referenceDir, direction);
Vector3 desiredDirection = Vector3.ProjectOnPlane(surfacePoint - start, direction);
return Quaternion.FromToRotation(recordedDirection, desiredDirection);
}
#region Inject
public void InjectAllCylinderSurface(CylinderSurfaceData data,
Transform relativeTo)
{
InjectData(data);
InjectRelativeTo(relativeTo);
}
public void InjectData(CylinderSurfaceData data)
{
_data = data;
}
public void InjectRelativeTo(Transform relativeTo)
{
_relativeTo = relativeTo;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0ab0e0bf5507fad4a9def20ce63299e4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,128 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus 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 UnityEngine;
namespace Oculus.Interaction.Grab
{
public static class GrabPoseHelper
{
public delegate Pose PoseCalculator(in Pose desiredPose, Transform relativeTo);
/// <summary>
/// Finds the best pose comparing the one that requires the minimum rotation
/// and minimum translation.
/// </summary>
/// <param name="desiredPose">Pose to measure from.</param>
/// <param name="bestPose">Nearest pose to the desired one at the hand grab pose.</param>
/// <param name="scoringModifier">Modifiers for the score based in rotation and distance.</param>
/// <param name="relativeTo">The reference transform to apply the calculators to</param>
/// <param name="minimalTranslationPoseCalculator">Delegate to calculate the nearest, by position, pose at a hand grab pose.</param>
/// <param name="minimalRotationPoseCalculator">Delegate to calculate the nearest, by rotation, pose at a hand grab pose.</param>
/// <returns>The score, normalized, of the best pose.</returns>
public static GrabPoseScore CalculateBestPoseAtSurface(in Pose desiredPose, out Pose bestPose,
in PoseMeasureParameters scoringModifier, Transform relativeTo,
PoseCalculator minimalTranslationPoseCalculator, PoseCalculator minimalRotationPoseCalculator)
{
if (scoringModifier.PositionRotationWeight == 1f)
{
bestPose = minimalRotationPoseCalculator(desiredPose, relativeTo);
return new GrabPoseScore(desiredPose, bestPose, 1f);
}
if (scoringModifier.PositionRotationWeight == 0f)
{
bestPose = minimalTranslationPoseCalculator(desiredPose, relativeTo);
return new GrabPoseScore(desiredPose, bestPose, 0f);
}
Pose minimalTranslationPose = minimalTranslationPoseCalculator(desiredPose, relativeTo);
Pose minimalRotationPose = minimalRotationPoseCalculator(desiredPose, relativeTo);
bestPose = SelectBestPose(minimalRotationPose, minimalTranslationPose,
desiredPose, scoringModifier, out GrabPoseScore bestScore);
return bestScore;
}
/// <summary>
/// Compares two poses to a reference and returns the most similar one
/// </summary>
/// <param name="poseA">First Pose to compare with the reference.</param>
/// <param name="poseB">Second Pose to compare with the reference.</param>
/// <param name="reference">Reference pose to measure from.</param>
/// <param name="scoringModifier">Modifiers for the score based in rotation and distance.</param>
/// <param name="bestScore">Out value with the score of the best pose.</param>
/// <returns>The most similar pose to reference out of the poses</returns>
public static Pose SelectBestPose(in Pose poseA, in Pose poseB, in Pose reference,
PoseMeasureParameters scoringModifier, out GrabPoseScore bestScore)
{
GrabPoseScore poseAScore = new GrabPoseScore(reference, poseA,
scoringModifier.PositionRotationWeight);
GrabPoseScore poseBScore = new GrabPoseScore(reference, poseB,
scoringModifier.PositionRotationWeight);
if (poseAScore.IsBetterThan(poseBScore))
{
bestScore = poseAScore;
return poseA;
}
else
{
bestScore = poseBScore;
return poseB;
}
}
/// <summary>
/// Calculates the score from a point to a set of colliders.
/// When the point is outside the colliders the further from their surface means the
/// lower the score.
/// When the point is inside any of the colliders the score is always higher.
/// </summary>
/// <param name="position">Position to measure against the colliders</param>
/// <param name="colliders">Group of colliders to measure the score</param>
/// <param name="hitPoint">Output point in the surface or inside the colliders that is near the position</param>
/// <returns>A GrabPoseScore value containing the score of the position in reference to the colliders</returns>
public static GrabPoseScore CollidersScore(Vector3 position, Collider[] colliders,
out Vector3 hitPoint)
{
GrabPoseScore bestScore = GrabPoseScore.Max;
GrabPoseScore score;
hitPoint = position;
foreach (Collider collider in colliders)
{
bool isPointInsideCollider = Collisions.IsPointWithinCollider(position, collider);
Vector3 measuringPoint = isPointInsideCollider ? collider.bounds.center
: collider.ClosestPoint(position);
score = new GrabPoseScore(position, measuringPoint,
isPointInsideCollider);
if (score.IsBetterThan(bestScore))
{
hitPoint = isPointInsideCollider ? position : measuringPoint;
bestScore = score;
}
}
return bestScore;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cada41609b5284144aef14e522e6351a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,78 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus 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 UnityEngine;
namespace Oculus.Interaction.Grab.GrabSurfaces
{
/// <summary>
/// This interface defines the method needed to use grab surfaces. They allow finding the
/// nearest poses at the surface to a given set of parameters as well as duplicating and
/// mirroring the surface.
/// </summary>
public interface IGrabSurface
{
/// <summary>
/// Finds the Pose at the surface that is the closest to the given pose.
/// </summary>
/// <param name="targetPose">The pose to find the nearest to.</param>
/// <param name="bestPose">The best found pose at the surface.<</param>
/// <param name="scoringModifier">Weight used to decide which target pose to select</param>
/// <param name="relativeTo">Reference transform to measure the poses against</param>
/// <returns>The score indicating how good the found pose was, -1 for invalid result.</returns>
GrabPoseScore CalculateBestPoseAtSurface(in Pose targetPose, out Pose bestPose,
in PoseMeasureParameters scoringModifier, Transform relativeTo);
/// <summary>
/// Finds the Pose at the surface that is the closest to the given ray.
/// </summary>
/// <param name="targetRay">Ray searching for the nearest snap pose</param>
/// <param name="bestPose">The best found pose at the surface.</param>
/// <param name="relativeTo">Reference transform to measure the poses against</param>
/// <returns>True if the pose was found</returns>
bool CalculateBestPoseAtSurface(Ray targetRay, out Pose bestPose,
Transform relativeTo);
/// <summary>
/// Method for mirroring a Pose around the surface.
/// Different surfaces will prefer mirroring along different axis.
/// </summary>
/// <param name="gripPose">The Pose to be mirrored.</param>
/// <param name="relativeTo">Reference transform to mirror the pose around</param>
/// <returns>A new pose mirrored at this surface.</returns>
Pose MirrorPose(in Pose gripPose, Transform relativeTo);
/// <summary>
/// Creates a new IGrabSurface under the selected gameobject
/// that is a mirror version of the current.
/// </summary>
/// <param name="gameObject">The gameobject in which to place the new IGrabSurface.</param>
/// <returns>A mirror of this IGrabSurface.</returns>
IGrabSurface CreateMirroredSurface(GameObject gameObject);
/// <summary>
/// Creates a new IGrabSurface under the selected gameobject
/// with the same data as this one.
/// </summary>
/// <param name="gameObject">The gameobject in which to place the new IGrabSurface.</param>
/// <returns>A clone of this IGrabSurface.</returns>
IGrabSurface CreateDuplicatedSurface(GameObject gameObject);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b84868e1fec742746b4f97a01bf836af
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,238 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus 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;
namespace Oculus.Interaction.Grab.GrabSurfaces
{
[Serializable]
public class SphereGrabSurfaceData : ICloneable
{
public object Clone()
{
SphereGrabSurfaceData clone = new SphereGrabSurfaceData();
clone.centre = this.centre;
return clone;
}
public SphereGrabSurfaceData Mirror()
{
SphereGrabSurfaceData mirror = Clone() as SphereGrabSurfaceData;
return mirror;
}
public Vector3 centre = Vector3.zero;
}
/// <summary>
/// Specifies an entire sphere around an object in which the grip point is valid.
/// One of the main advantages of spheres is that the rotation of the hand pose does
/// not really matters, as it will always fit the surface correctly.
/// </summary>
[Serializable]
public class SphereGrabSurface : MonoBehaviour, IGrabSurface
{
[SerializeField]
protected SphereGrabSurfaceData _data = new SphereGrabSurfaceData();
[SerializeField]
[Tooltip("Transform used as a reference to measure the local data of the grab surface")]
private Transform _relativeTo;
private Pose RelativePose => PoseUtils.DeltaScaled(_relativeTo, this.transform);
/// <summary>
/// The reference pose of the surface. It defines the radius of the sphere
/// as the point from the relative transform to the reference pose to ensure
/// that the sphere covers this pose.
/// </summary>
/// <param name="relativeTo">The reference transform to apply the surface to</param>
/// <returns>Pose in world space</returns>
public Pose GetReferencePose(Transform relativeTo)
{
return PoseUtils.GlobalPoseScaled(relativeTo, RelativePose);
}
/// <summary>
/// The center of the sphere in world coordinates.
/// </summary>
/// <param name="relativeTo">The reference transform to apply the surface to</param>
/// <returns>Position in world space</returns>
public Vector3 GetCentre(Transform relativeTo)
{
return relativeTo.TransformPoint(_data.centre);
}
public void SetCentre(Vector3 point, Transform relativeTo)
{
_data.centre = relativeTo.InverseTransformPoint(point);
}
/// <summary>
/// The radius of the sphere, this is automatically calculated as the distance between
/// the center and the original grip pose.
/// </summary>
/// <param name="relativeTo">The reference transform to apply the surface to</param>
/// <returns>Distance in world space</returns>
public float GetRadius(Transform relativeTo)
{
Vector3 centre = GetCentre(relativeTo);
Pose referencePose = GetReferencePose(relativeTo);
return Vector3.Distance(centre, referencePose.position);
}
/// <summary>
/// The direction of the sphere, measured from the center to the original grip position.
/// </summary>
/// <param name="relativeTo">The reference transform to apply the surface to</param>
/// <returns>Direction in world space</returns>
public Vector3 GetDirection(Transform relativeTo)
{
Vector3 centre = GetCentre(relativeTo);
Pose referencePose = GetReferencePose(relativeTo);
return (referencePose.position - centre).normalized;
}
#region editor events
protected virtual void Reset()
{
_relativeTo = this.GetComponentInParent<IRelativeToRef>()?.RelativeTo;
}
#endregion
protected virtual void Start()
{
this.AssertField(_data, nameof(_data));
this.AssertField(_relativeTo, nameof(_relativeTo));
}
public Pose MirrorPose(in Pose pose, Transform relativeTo)
{
Vector3 normal = Quaternion.Inverse(relativeTo.rotation) * GetDirection(relativeTo);
Vector3 tangent = Vector3.Cross(normal, Vector3.up);
return pose.MirrorPoseRotation(normal, tangent);
}
public bool CalculateBestPoseAtSurface(Ray targetRay, out Pose bestPose, Transform relativeTo)
{
Vector3 centre = GetCentre(relativeTo);
Vector3 projection = Vector3.Project(centre - targetRay.origin, targetRay.direction);
Vector3 nearestCentre = targetRay.origin + projection;
float radius = GetRadius(relativeTo);
float distanceToSurface = Mathf.Max(Vector3.Distance(centre, nearestCentre) - radius);
if (distanceToSurface < radius)
{
float adjustedDistance = Mathf.Sqrt(radius * radius - distanceToSurface * distanceToSurface);
nearestCentre -= targetRay.direction * adjustedDistance;
}
Pose recordedPose = GetReferencePose(relativeTo);
Vector3 surfacePoint = NearestPointInSurface(nearestCentre, relativeTo);
Pose desiredPose = new Pose(surfacePoint, recordedPose.rotation);
bestPose = MinimalTranslationPoseAtSurface(desiredPose, relativeTo);
return true;
}
public GrabPoseScore CalculateBestPoseAtSurface(in Pose targetPose, out Pose bestPose,
in PoseMeasureParameters scoringModifier, Transform relativeTo)
{
return GrabPoseHelper.CalculateBestPoseAtSurface(targetPose, out bestPose,
scoringModifier, relativeTo,
MinimalTranslationPoseAtSurface,
MinimalRotationPoseAtSurface);
}
public IGrabSurface CreateMirroredSurface(GameObject gameObject)
{
SphereGrabSurface surface = gameObject.AddComponent<SphereGrabSurface>();
surface._data = _data.Mirror();
return surface;
}
public IGrabSurface CreateDuplicatedSurface(GameObject gameObject)
{
SphereGrabSurface surface = gameObject.AddComponent<SphereGrabSurface>();
surface._data = _data;
return surface;
}
protected Vector3 NearestPointInSurface(Vector3 targetPosition, Transform relativeTo)
{
Vector3 centre = GetCentre(relativeTo);
Vector3 direction = (targetPosition - centre).normalized;
float radius = GetRadius(relativeTo);
return centre + direction * radius;
}
protected Pose MinimalRotationPoseAtSurface(in Pose userPose, Transform relativeTo)
{
Vector3 centre = GetCentre(relativeTo);
Pose referencePose = GetReferencePose(relativeTo);
float radius = GetRadius(relativeTo);
Quaternion rotCorrection = userPose.rotation * Quaternion.Inverse(referencePose.rotation);
Vector3 correctedDir = rotCorrection * GetDirection(relativeTo);
Vector3 surfacePoint = NearestPointInSurface(centre + correctedDir * radius, relativeTo);
Quaternion surfaceRotation = RotationAtPoint(surfacePoint, referencePose.rotation, userPose.rotation, relativeTo);
return new Pose(surfacePoint, surfaceRotation);
}
protected Pose MinimalTranslationPoseAtSurface(in Pose userPose, Transform relativeTo)
{
Pose referencePose = GetReferencePose(relativeTo);
Vector3 desiredPos = userPose.position;
Quaternion baseRot = referencePose.rotation;
Vector3 surfacePoint = NearestPointInSurface(desiredPos, relativeTo);
Quaternion surfaceRotation = RotationAtPoint(surfacePoint, baseRot, userPose.rotation, relativeTo);
return new Pose(surfacePoint, surfaceRotation);
}
protected Quaternion RotationAtPoint(Vector3 surfacePoint,
Quaternion baseRot, Quaternion desiredRotation,
Transform relativeTo)
{
Vector3 desiredDirection = (surfacePoint - GetCentre(relativeTo)).normalized;
Quaternion targetRotation = Quaternion.FromToRotation(GetDirection(relativeTo), desiredDirection) * baseRot;
Vector3 targetProjected = Vector3.ProjectOnPlane(targetRotation * Vector3.forward, desiredDirection).normalized;
Vector3 desiredProjected = Vector3.ProjectOnPlane(desiredRotation * Vector3.forward, desiredDirection).normalized;
Quaternion rotCorrection = Quaternion.FromToRotation(targetProjected, desiredProjected);
return rotCorrection * targetRotation;
}
#region Inject
public void InjectAllSphereSurface(SphereGrabSurfaceData data, Transform relativeTo)
{
InjectData(data);
InjectRelativeTo(relativeTo);
}
public void InjectData(SphereGrabSurfaceData data)
{
_data = data;
}
public void InjectRelativeTo(Transform relativeTo)
{
_relativeTo = relativeTo;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 18467ddce2adbac48aa374514e13d1ed
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: