// Chest|Controls3D|100030
namespace VRTK
{
using UnityEngine;
///
/// Transforms a game object into a chest with a lid. The direction can be auto-detected with very high reliability or set manually.
///
///
/// 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.
///
///
/// `VRTK/Examples/025_Controls_Overview` shows a chest that can be open and closed, it also displays the current opening angle of the chest.
///
[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();
if (bodyRigidbody == null)
{
bodyRigidbody = body.AddComponent();
bodyRigidbody.isKinematic = true; // otherwise body moves/falls over when lid is moved or fully open
}
}
protected virtual void InitLid()
{
lidRigidbody = lid.GetComponent();
if (lidRigidbody == null)
{
lidRigidbody = lid.AddComponent();
}
lidJoint = lid.GetComponent();
if (lidJoint == null)
{
lidJoint = lid.AddComponent();
lidJointCreated = true;
}
lidJoint.connectedBody = bodyRigidbody;
if (!handle)
{
CreateInteractableObject(lid);
}
}
protected virtual void InitHandle()
{
if (!handle)
{
return;
}
handleRigidbody = handle.GetComponent();
if (handleRigidbody == null)
{
handleRigidbody = handle.AddComponent();
}
handleRigidbody.isKinematic = false;
handleRigidbody.useGravity = false;
handleJoint = handle.GetComponent();
if (handleJoint == null)
{
handleJoint = handle.AddComponent();
handleJoint.connectedBody = lidRigidbody;
}
CreateInteractableObject(handle);
}
protected virtual void CreateInteractableObject(GameObject targetGameObject)
{
VRTK_InteractableObject targetInteractableObject = targetGameObject.GetComponent();
if (targetInteractableObject == null)
{
targetInteractableObject = targetGameObject.AddComponent();
}
targetInteractableObject.isGrabbable = true;
targetInteractableObject.grabAttachMechanicScript = gameObject.AddComponent();
targetInteractableObject.secondaryGrabActionScript = gameObject.AddComponent();
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);
}
}
}