Initialer Upload neues Unity-Projekt
This commit is contained in:
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe16fe5c237c19b45bc8c5843cfc4934
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bbe6418317e852d4e8fd122a4149acba
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8951a656a7c00e74094166ef415cdce5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ab0e0bf5507fad4a9def20ce63299e4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cada41609b5284144aef14e522e6351a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b84868e1fec742746b4f97a01bf836af
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 18467ddce2adbac48aa374514e13d1ed
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user