/* * 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 Oculus.Interaction.Input; 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; } /// /// 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. /// [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); /// /// 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. /// /// The reference transform to apply the surface to /// Pose in world space public Pose GetReferencePose(Transform relativeTo) { return PoseUtils.GlobalPoseScaled(relativeTo, RelativePose); } /// /// The center of the sphere in world coordinates. /// /// The reference transform to apply the surface to /// Position in world space public Vector3 GetCentre(Transform relativeTo) { return relativeTo.TransformPoint(_data.centre); } public void SetCentre(Vector3 point, Transform relativeTo) { _data.centre = relativeTo.InverseTransformPoint(point); } /// /// The radius of the sphere, this is automatically calculated as the distance between /// the center and the original grip pose. /// /// The reference transform to apply the surface to /// Distance in world space public float GetRadius(Transform relativeTo) { Vector3 centre = GetCentre(relativeTo); Pose referencePose = GetReferencePose(relativeTo); return Vector3.Distance(centre, referencePose.position); } /// /// The direction of the sphere, measured from the center to the original grip position. /// /// The reference transform to apply the surface to /// Direction in world space 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()?.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 mirrorPlane = Vector3.Cross(pose.position, Vector3.up).normalized; Quaternion reflectedRot = HandMirroring.Reflect(pose.rotation, mirrorPlane); return new Pose(pose.position, reflectedRot); } 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 CalculateBestPoseAtSurface(targetPose, Pose.identity, out bestPose, scoringModifier, relativeTo); } public GrabPoseScore CalculateBestPoseAtSurface(in Pose targetPose, in Pose offset, out Pose bestPose, in PoseMeasureParameters scoringModifier, Transform relativeTo) { return GrabPoseHelper.CalculateBestPoseAtSurface(targetPose, offset, out bestPose, scoringModifier, relativeTo, MinimalTranslationPoseAtSurface, MinimalRotationPoseAtSurface); } public IGrabSurface CreateMirroredSurface(GameObject gameObject) { SphereGrabSurface surface = gameObject.AddComponent(); surface._data = _data.Mirror(); return surface; } public IGrabSurface CreateDuplicatedSurface(GameObject gameObject) { SphereGrabSurface surface = gameObject.AddComponent(); 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 } }