// Chest|Controls3D|100030
|
|
namespace VRTK
|
|
{
|
|
using UnityEngine;
|
|
|
|
/// <summary>
|
|
/// Transforms a game object into a chest with a lid. The direction can be auto-detected with very high reliability or set manually.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The script will instantiate the required Rigidbody, Interactable and HingeJoint components automatically in case they do not exist yet. It will expect three distinct game objects: a body, a lid and a handle. These should be independent and not children of each other.
|
|
/// </remarks>
|
|
/// <example>
|
|
/// `VRTK/Examples/025_Controls_Overview` shows a chest that can be open and closed, it also displays the current opening angle of the chest.
|
|
/// </example>
|
|
[AddComponentMenu("VRTK/Scripts/Controls/3D/VRTK_Chest")]
|
|
[System.Obsolete("`VRTK.VRTK_Chest` has been deprecated and can be recreated with `VRTK.Controllables.PhysicsBased.VRTK_PhysicsRotator`. This script will be removed in a future version of VRTK.")]
|
|
public class VRTK_Chest : VRTK_Control
|
|
{
|
|
[Tooltip("The axis on which the chest should open. All other axis will be frozen.")]
|
|
public Direction direction = Direction.autodetect;
|
|
[Tooltip("The game object for the lid.")]
|
|
public GameObject lid;
|
|
[Tooltip("The game object for the body.")]
|
|
public GameObject body;
|
|
[Tooltip("The game object for the handle.")]
|
|
public GameObject handle;
|
|
[Tooltip("The parent game object for the chest content elements.")]
|
|
public GameObject content;
|
|
[Tooltip("Makes the content invisible while the chest is closed.")]
|
|
public bool hideContent = true;
|
|
[Tooltip("The maximum opening angle of the chest.")]
|
|
public float maxAngle = 160f;
|
|
|
|
protected float minAngle = 0f;
|
|
protected float stepSize = 1f;
|
|
protected Rigidbody bodyRigidbody;
|
|
protected Rigidbody handleRigidbody;
|
|
protected FixedJoint handleJoint;
|
|
protected Rigidbody lidRigidbody;
|
|
protected HingeJoint lidJoint;
|
|
protected bool lidJointCreated;
|
|
protected Direction finalDirection;
|
|
protected float subDirection = 1; // positive or negative can be determined automatically since handle dictates that
|
|
|
|
protected override void OnDrawGizmos()
|
|
{
|
|
base.OnDrawGizmos();
|
|
if (!enabled || !setupSuccessful)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// show opening direction
|
|
Bounds bounds;
|
|
if (handle)
|
|
{
|
|
bounds = VRTK_SharedMethods.GetBounds(handle.transform, handle.transform);
|
|
}
|
|
else
|
|
{
|
|
bounds = VRTK_SharedMethods.GetBounds(lid.transform, lid.transform);
|
|
}
|
|
float length = bounds.extents.y * 5f;
|
|
Vector3 point = bounds.center + new Vector3(0, length, 0);
|
|
switch (finalDirection)
|
|
{
|
|
case Direction.x:
|
|
point += transform.right.normalized * (length / 2f) * subDirection;
|
|
break;
|
|
case Direction.y:
|
|
point += transform.up.normalized * (length / 2f) * subDirection;
|
|
break;
|
|
case Direction.z:
|
|
point += transform.forward.normalized * (length / 2f) * subDirection;
|
|
break;
|
|
}
|
|
|
|
Gizmos.DrawLine(bounds.center + new Vector3(0, bounds.extents.y, 0), point);
|
|
Gizmos.DrawSphere(point, length / 8f);
|
|
}
|
|
|
|
protected override void InitRequiredComponents()
|
|
{
|
|
InitBody();
|
|
InitLid();
|
|
InitHandle();
|
|
|
|
SetContent(content, hideContent);
|
|
}
|
|
|
|
protected override bool DetectSetup()
|
|
{
|
|
if (lid == null || body == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
finalDirection = (direction == Direction.autodetect) ? DetectDirection() : direction;
|
|
if (finalDirection == Direction.autodetect)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Bounds lidBounds = VRTK_SharedMethods.GetBounds(lid.transform, transform);
|
|
|
|
// determin sub-direction depending on handle
|
|
if (handle)
|
|
{
|
|
Bounds handleBounds = VRTK_SharedMethods.GetBounds(handle.transform, transform);
|
|
switch (finalDirection)
|
|
{
|
|
case Direction.x:
|
|
subDirection = (handleBounds.center.x > lidBounds.center.x) ? -1 : 1;
|
|
break;
|
|
case Direction.y:
|
|
subDirection = (handleBounds.center.y > lidBounds.center.y) ? -1 : 1;
|
|
break;
|
|
case Direction.z:
|
|
subDirection = (handleBounds.center.z > lidBounds.center.z) ? -1 : 1;
|
|
break;
|
|
}
|
|
|
|
// handle should be outside lid hierarchy, otherwise anchor-by-bounds calculation is off
|
|
if (handle.transform.IsChildOf(lid.transform))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
subDirection = -1;
|
|
}
|
|
if (lidJointCreated)
|
|
{
|
|
lidJoint.useLimits = true;
|
|
lidJoint.enableCollision = true;
|
|
|
|
JointLimits limits = lidJoint.limits;
|
|
switch (finalDirection)
|
|
{
|
|
case Direction.x:
|
|
lidJoint.anchor = new Vector3(subDirection * lidBounds.extents.x, 0, 0);
|
|
lidJoint.axis = new Vector3(0, 0, 1);
|
|
if (subDirection > 0)
|
|
{
|
|
limits.min = -maxAngle;
|
|
limits.max = minAngle;
|
|
}
|
|
else
|
|
{
|
|
limits.min = minAngle;
|
|
limits.max = maxAngle;
|
|
}
|
|
break;
|
|
case Direction.y:
|
|
lidJoint.anchor = new Vector3(0, subDirection * lidBounds.extents.y, 0);
|
|
lidJoint.axis = new Vector3(0, 1, 0);
|
|
if (subDirection > 0)
|
|
{
|
|
limits.min = -maxAngle;
|
|
limits.max = minAngle;
|
|
}
|
|
else
|
|
{
|
|
limits.min = minAngle;
|
|
limits.max = maxAngle;
|
|
}
|
|
break;
|
|
case Direction.z:
|
|
lidJoint.anchor = new Vector3(0, 0, subDirection * lidBounds.extents.z);
|
|
lidJoint.axis = new Vector3(1, 0, 0);
|
|
if (subDirection < 0)
|
|
{
|
|
limits.min = -maxAngle;
|
|
limits.max = minAngle;
|
|
}
|
|
else
|
|
{
|
|
limits.min = minAngle;
|
|
limits.max = maxAngle;
|
|
}
|
|
break;
|
|
}
|
|
lidJoint.limits = limits;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
protected override ControlValueRange RegisterValueRange()
|
|
{
|
|
return new ControlValueRange()
|
|
{
|
|
controlMin = lidJoint.limits.min,
|
|
controlMax = lidJoint.limits.max
|
|
};
|
|
}
|
|
|
|
protected override void HandleUpdate()
|
|
{
|
|
value = CalculateValue();
|
|
}
|
|
|
|
protected virtual Direction DetectDirection()
|
|
{
|
|
Direction returnDirection = Direction.autodetect;
|
|
|
|
if (!handle)
|
|
{
|
|
return returnDirection;
|
|
}
|
|
|
|
Bounds handleBounds = VRTK_SharedMethods.GetBounds(handle.transform, transform);
|
|
Bounds lidBounds = VRTK_SharedMethods.GetBounds(lid.transform, transform);
|
|
|
|
float lengthX = Mathf.Abs(handleBounds.center.x - (lidBounds.center.x + lidBounds.extents.x));
|
|
float lengthZ = Mathf.Abs(handleBounds.center.z - (lidBounds.center.z + lidBounds.extents.z));
|
|
float lengthNegX = Mathf.Abs(handleBounds.center.x - (lidBounds.center.x - lidBounds.extents.x));
|
|
float lengthNegZ = Mathf.Abs(handleBounds.center.z - (lidBounds.center.z - lidBounds.extents.z));
|
|
|
|
if (VRTK_SharedMethods.IsLowest(lengthX, new float[] { lengthZ, lengthNegX, lengthNegZ }))
|
|
{
|
|
returnDirection = Direction.x;
|
|
}
|
|
else if (VRTK_SharedMethods.IsLowest(lengthNegX, new float[] { lengthX, lengthZ, lengthNegZ }))
|
|
{
|
|
returnDirection = Direction.x;
|
|
}
|
|
else if (VRTK_SharedMethods.IsLowest(lengthZ, new float[] { lengthX, lengthNegX, lengthNegZ }))
|
|
{
|
|
returnDirection = Direction.z;
|
|
}
|
|
else if (VRTK_SharedMethods.IsLowest(lengthNegZ, new float[] { lengthX, lengthZ, lengthNegX }))
|
|
{
|
|
returnDirection = Direction.z;
|
|
}
|
|
|
|
return returnDirection;
|
|
}
|
|
|
|
protected virtual void InitBody()
|
|
{
|
|
bodyRigidbody = body.GetComponent<Rigidbody>();
|
|
if (bodyRigidbody == null)
|
|
{
|
|
bodyRigidbody = body.AddComponent<Rigidbody>();
|
|
bodyRigidbody.isKinematic = true; // otherwise body moves/falls over when lid is moved or fully open
|
|
}
|
|
}
|
|
|
|
protected virtual void InitLid()
|
|
{
|
|
lidRigidbody = lid.GetComponent<Rigidbody>();
|
|
if (lidRigidbody == null)
|
|
{
|
|
lidRigidbody = lid.AddComponent<Rigidbody>();
|
|
}
|
|
|
|
lidJoint = lid.GetComponent<HingeJoint>();
|
|
if (lidJoint == null)
|
|
{
|
|
lidJoint = lid.AddComponent<HingeJoint>();
|
|
lidJointCreated = true;
|
|
}
|
|
lidJoint.connectedBody = bodyRigidbody;
|
|
|
|
if (!handle)
|
|
{
|
|
CreateInteractableObject(lid);
|
|
}
|
|
}
|
|
|
|
protected virtual void InitHandle()
|
|
{
|
|
if (!handle)
|
|
{
|
|
return;
|
|
}
|
|
handleRigidbody = handle.GetComponent<Rigidbody>();
|
|
if (handleRigidbody == null)
|
|
{
|
|
handleRigidbody = handle.AddComponent<Rigidbody>();
|
|
}
|
|
handleRigidbody.isKinematic = false;
|
|
handleRigidbody.useGravity = false;
|
|
|
|
handleJoint = handle.GetComponent<FixedJoint>();
|
|
if (handleJoint == null)
|
|
{
|
|
handleJoint = handle.AddComponent<FixedJoint>();
|
|
handleJoint.connectedBody = lidRigidbody;
|
|
}
|
|
|
|
CreateInteractableObject(handle);
|
|
}
|
|
|
|
protected virtual void CreateInteractableObject(GameObject targetGameObject)
|
|
{
|
|
VRTK_InteractableObject targetInteractableObject = targetGameObject.GetComponent<VRTK_InteractableObject>();
|
|
if (targetInteractableObject == null)
|
|
{
|
|
targetInteractableObject = targetGameObject.AddComponent<VRTK_InteractableObject>();
|
|
}
|
|
targetInteractableObject.isGrabbable = true;
|
|
targetInteractableObject.grabAttachMechanicScript = gameObject.AddComponent<GrabAttachMechanics.VRTK_TrackObjectGrabAttach>();
|
|
targetInteractableObject.secondaryGrabActionScript = gameObject.AddComponent<SecondaryControllerGrabActions.VRTK_SwapControllerGrabAction>();
|
|
targetInteractableObject.grabAttachMechanicScript.precisionGrab = true;
|
|
targetInteractableObject.stayGrabbedOnTeleport = false;
|
|
}
|
|
|
|
protected virtual float CalculateValue()
|
|
{
|
|
return (Mathf.Round((minAngle + Mathf.Clamp01(Mathf.Abs(lidJoint.angle / (lidJoint.limits.max - lidJoint.limits.min))) * (maxAngle - minAngle)) / stepSize) * stepSize);
|
|
}
|
|
}
|
|
}
|