// Snap Drop Zone|Prefabs|0080 namespace VRTK { using UnityEngine; using System.Collections; using System.Collections.Generic; using Highlighters; /// /// Event Payload /// /// The interactable object that is dealing with the snap drop zone. public struct SnapDropZoneEventArgs { public GameObject snappedObject; } /// /// Event Payload /// /// this object /// public delegate void SnapDropZoneEventHandler(object sender, SnapDropZoneEventArgs e); /// /// Provides a predefined zone where a valid interactable object can be dropped and upon dropping it snaps to the set snap drop zone transform position, rotation and scale. /// /// /// **Prefab Usage:** /// * Place the `VRTK/Prefabs/SnapDropZone/SnapDropZone` prefab into the scene hierarchy. /// * Provide the SnapDropZone with an optional `Highlight Object Prefab` to generate an object outline in the scene that determines the final position, rotation and scale of the snapped object. /// * If no `VRTK_BaseHighlighter` derivative is applied to the SnapDropZone then the default MaterialColorSwap Highlighter will be used. /// * The collision zone that activates the SnapDropZone is a `Sphere Collider` by default but can be amended or replaced on the SnapDropZone GameObject. /// * If the `Use Joint` Snap Type is selected then a custom Joint component is required to be added to the `SnapDropZone` Game Object and upon release the interactable object's rigidbody will be linked to this joint as the `Connected Body`. /// /// /// `VRTK/Examples/041_Controller_ObjectSnappingToDropZones` uses the `VRTK_SnapDropZone` prefab to set up pre-determined snap zones for a range of objects and demonstrates how only objects of certain types can be snapped into certain areas. /// [ExecuteInEditMode] public class VRTK_SnapDropZone : MonoBehaviour { /// /// The types of snap on release available. /// public enum SnapTypes { /// /// Will set the interactable object rigidbody to `isKinematic = true`. /// UseKinematic, /// /// Will attach the interactable object's rigidbody to the provided joint as it's `Connected Body`. /// UseJoint, /// /// Will set the SnapDropZone as the interactable object's parent and set it's rigidbody to `isKinematic = true`. /// UseParenting } [Tooltip("A game object that is used to draw the highlighted destination for within the drop zone. This object will also be created in the Editor for easy placement.")] public GameObject highlightObjectPrefab; [Tooltip("The Snap Type to apply when a valid interactable object is dropped within the snap zone.")] public SnapTypes snapType = SnapTypes.UseKinematic; [Tooltip("The amount of time it takes for the object being snapped to move into the new snapped position, rotation and scale.")] public float snapDuration = 0f; [Tooltip("If this is checked then the scaled size of the snap drop zone will be applied to the object that is snapped to it.")] public bool applyScalingOnSnap = false; [Tooltip("If this is checked then when the snapped object is unsnapped from the drop zone, a clone of the unsnapped object will be snapped back into the drop zone.")] public bool cloneNewOnUnsnap = false; [Tooltip("The colour to use when showing the snap zone is active. This is used as the highlight colour when no object is hovering but `Highlight Always Active` is true.")] public Color highlightColor = Color.clear; [Tooltip("The colour to use when showing the snap zone is active and a valid object is hovering. If this is `Color.clear` then the `Highlight Color` will be used.")] public Color validHighlightColor = Color.clear; [Tooltip("The highlight object will always be displayed when the snap drop zone is available even if a valid item isn't being hovered over.")] public bool highlightAlwaysActive = false; [Tooltip("A specified VRTK_PolicyList to use to determine which interactable objects will be snapped to the snap drop zone on release.")] public VRTK_PolicyList validObjectListPolicy; [Tooltip("If this is checked then the drop zone highlight section will be displayed in the scene editor window.")] public bool displayDropZoneInEditor = true; [Tooltip("The Interactable Object to snap into the dropzone when the drop zone is enabled. The Interactable Object must be valid in any given policy list to snap.")] public VRTK_InteractableObject defaultSnappedInteractableObject; [Header("Obsolete Settings")] [System.Obsolete("`VRTK_SnapDropZone.defaultSnappedObject` has been replaced with the `VRTK_SnapDropZone.defaultSnappedInteractableObject`. This parameter will be removed in a future version of VRTK.")] [ObsoleteInspector] public GameObject defaultSnappedObject; /// /// Emitted when a valid interactable object enters the snap drop zone trigger collider. /// public event SnapDropZoneEventHandler ObjectEnteredSnapDropZone; /// /// Emitted when a valid interactable object exists the snap drop zone trigger collider. /// public event SnapDropZoneEventHandler ObjectExitedSnapDropZone; /// /// Emitted when an interactable object is successfully snapped into a drop zone. /// public event SnapDropZoneEventHandler ObjectSnappedToDropZone; /// /// Emitted when an interactable object is removed from a snapped drop zone. /// public event SnapDropZoneEventHandler ObjectUnsnappedFromDropZone; protected GameObject previousPrefab; protected GameObject highlightContainer; protected GameObject highlightObject; protected GameObject highlightEditorObject = null; protected List currentValidSnapInteractableObjects = new List(); protected VRTK_InteractableObject currentSnappedObject = null; protected GameObject objectToClone = null; protected bool[] clonedObjectColliderStates = new bool[0]; protected bool willSnap = false; protected bool isSnapped = false; protected bool wasSnapped = false; protected bool isHighlighted = false; protected VRTK_BaseHighlighter objectHighlighter; protected Coroutine transitionInPlaceRoutine; protected Coroutine attemptTransitionAtEndOfFrameRoutine; protected Coroutine checkCanSnapRoutine; protected bool originalJointCollisionState = false; protected Coroutine overridePreviousStateAtEndOfFrameRoutine; protected const string HIGHLIGHT_CONTAINER_NAME = "HighlightContainer"; protected const string HIGHLIGHT_OBJECT_NAME = "HighlightObject"; protected const string HIGHLIGHT_EDITOR_OBJECT_NAME = "EditorHighlightObject"; public virtual void OnObjectEnteredSnapDropZone(SnapDropZoneEventArgs e) { if (ObjectEnteredSnapDropZone != null) { ObjectEnteredSnapDropZone(this, e); } } public virtual void OnObjectExitedSnapDropZone(SnapDropZoneEventArgs e) { if (ObjectExitedSnapDropZone != null) { ObjectExitedSnapDropZone(this, e); } } public virtual void OnObjectSnappedToDropZone(SnapDropZoneEventArgs e) { if (ObjectSnappedToDropZone != null) { ObjectSnappedToDropZone(this, e); } } public virtual void OnObjectUnsnappedFromDropZone(SnapDropZoneEventArgs e) { UnsnapObject(); if (ObjectUnsnappedFromDropZone != null) { ObjectUnsnappedFromDropZone(this, e); } } public virtual SnapDropZoneEventArgs SetSnapDropZoneEvent(GameObject interactableObject) { SnapDropZoneEventArgs e; e.snappedObject = interactableObject; return e; } /// /// The InitaliseHighlightObject method sets up the highlight object based on the given Highlight Object Prefab. /// /// If this is set to true then it attempts to delete the old highlight object if it exists. Defaults to `false` public virtual void InitaliseHighlightObject(bool removeOldObject = false) { //force delete previous created highlight object if (removeOldObject) { DeleteHighlightObject(); } //Always remove editor highlight object at runtime ChooseDestroyType(transform.Find(ObjectPath(HIGHLIGHT_EDITOR_OBJECT_NAME))); highlightEditorObject = null; GenerateObjects(); } /// /// the ForceSnap method attempts to automatically attach a valid GameObject to the snap drop zone. /// /// The GameObject to attempt to snap. public virtual void ForceSnap(GameObject objectToSnap) { ForceSnap(objectToSnap.GetComponentInParent()); } /// /// the ForceSnap method attempts to automatically attach a valid Interactable Object to the snap drop zone. /// /// The Interactable Object to attempt to snap. protected virtual void ForceSnap(VRTK_InteractableObject interactableObjectToSnap) { if (interactableObjectToSnap != null) { if (attemptTransitionAtEndOfFrameRoutine != null) { StopCoroutine(attemptTransitionAtEndOfFrameRoutine); } if (checkCanSnapRoutine != null) { StopCoroutine(checkCanSnapRoutine); } if (interactableObjectToSnap.IsGrabbed()) { interactableObjectToSnap.ForceStopInteracting(); } if (gameObject.activeInHierarchy) { attemptTransitionAtEndOfFrameRoutine = StartCoroutine(AttemptForceSnapAtEndOfFrame(interactableObjectToSnap)); } } } /// /// The ForceUnsnap method attempts to automatically remove the current snapped game object from the snap drop zone. /// public virtual void ForceUnsnap() { if (isSnapped && ValidSnapObject(currentSnappedObject, false)) { currentSnappedObject.ToggleSnapDropZone(this, false); } } /// /// The ValidSnappableObjectIsHovering method determines if any valid objects are currently hovering in the snap drop zone area. /// /// Returns true if a valid object is currently in the snap drop zone area. public virtual bool ValidSnappableObjectIsHovering() { for (int i = 0; i < currentValidSnapInteractableObjects.Count; i++) { if (currentValidSnapInteractableObjects[i].IsGrabbed()) { return true; } } return false; } /// /// The IsObjectHovering method determines if the given GameObject is currently howvering (but not snapped) in the snap drop zone area. /// /// The GameObject to check to see if it's hovering in the snap drop zone area. /// Returns true if the given GameObject is hovering (but not snapped) in the snap drop zone area. public virtual bool IsObjectHovering(GameObject checkObject) { VRTK_InteractableObject interactableObjectToCheck = checkObject.GetComponentInParent(); return (interactableObjectToCheck != null ? currentValidSnapInteractableObjects.Contains(interactableObjectToCheck) : false); } /// /// The IsInteractableObjectHovering method determines if the given Interactable Object script is currently howvering (but not snapped) in the snap drop zone area. /// /// The Interactable Object script to check to see if it's hovering in the snap drop zone area. /// Returns true if the given Interactable Object script is hovering (but not snapped) in the snap drop zone area. public virtual bool IsInteractableObjectHovering(VRTK_InteractableObject checkObject) { return (checkObject != null ? currentValidSnapInteractableObjects.Contains(checkObject) : false); } /// /// The GetHoveringObjects method returns a List of valid GameObjects that are currently hovering (but not snapped) in the snap drop zone area. /// /// The List of valid GameObjects that are hovering (but not snapped) in the snap drop zone area. public virtual List GetHoveringObjects() { List returnList = new List(); for (int i = 0; i < currentValidSnapInteractableObjects.Count; i++) { VRTK_SharedMethods.AddListValue(returnList, currentValidSnapInteractableObjects[i].gameObject); } return returnList; } /// /// The GetHoveringInteractableObjects method returns a List of valid Interactable Object scripts that are currently hovering (but not snapped) in the snap drop zone area. /// /// The List of valid Interactable Object scripts that are hovering (but not snapped) in the snap drop zone area. public virtual List GetHoveringInteractableObjects() { return currentValidSnapInteractableObjects; } /// /// The GetCurrentSnappedObejct method returns the GameObject that is currently snapped in the snap drop zone area. /// /// The GameObject that is currently snapped in the snap drop zone area. public virtual GameObject GetCurrentSnappedObject() { return (currentSnappedObject != null ? currentSnappedObject.gameObject : null); } /// /// The GetCurrentSnappedInteractableObject method returns the Interactable Object script that is currently snapped in the snap drop zone area. /// /// The Interactable Object script that is currently snapped in the snap drop zone area. public virtual VRTK_InteractableObject GetCurrentSnappedInteractableObject() { return currentSnappedObject; } /// /// The Clone method returns the GameObject of the cloned snap drop zone /// /// Position of the cloned GameObject /// The GameObject of the clone public virtual GameObject Clone(Vector3 position) { VRTK_SnapDropZone cloneSDZ = Instantiate(gameObject, position, transform.rotation).GetComponent(); for (int childID = 0; childID < cloneSDZ.transform.childCount; childID++) { Transform child = cloneSDZ.transform.GetChild(childID); if (child.GetComponent() != null) { Destroy(child.gameObject); } } if (isSnapped) { VRTK_InteractableObject currObject = currentSnappedObject; //Get copy of Objects original state Transform previousParent; bool previousKinematic; bool previousGrabbable; currObject.GetPreviousState(out previousParent, out previousKinematic, out previousGrabbable); GameObject clonedObject = null; if (cloneNewOnUnsnap) { clonedObject = Instantiate(objectToClone); clonedObject.SetActive(true); } else { clonedObject = Instantiate(currObject.gameObject); } clonedObject.transform.position = cloneSDZ.transform.position; cloneSDZ.ForceSnap(clonedObject); overridePreviousStateAtEndOfFrameRoutine = StartCoroutine(OverridePreviousStateAtEndOfFrame(clonedObject.GetComponent(), previousParent, previousKinematic, previousGrabbable)); } return cloneSDZ.gameObject; } /// /// The Clone method returns the GameObject of the cloned snap drop zone /// /// The GameObject of the clone public virtual GameObject Clone() { return Clone(Vector3.zero); } protected virtual void Awake() { if (Application.isPlaying) { InitaliseHighlightObject(); } } protected virtual void OnApplicationQuit() { if (objectHighlighter != null) { objectHighlighter.Unhighlight(); } } protected virtual void OnEnable() { currentValidSnapInteractableObjects.Clear(); currentSnappedObject = null; objectToClone = null; clonedObjectColliderStates = new bool[0]; willSnap = false; isSnapped = false; wasSnapped = false; isHighlighted = false; #pragma warning disable 618 if (defaultSnappedObject != null && defaultSnappedInteractableObject == null) { defaultSnappedInteractableObject = defaultSnappedObject.GetComponentInParent(); } #pragma warning restore 618 DisableHighlightShadows(); if (!VRTK_SharedMethods.IsEditTime() && Application.isPlaying && defaultSnappedInteractableObject != null) { ForceSnap(defaultSnappedInteractableObject); } } protected virtual void OnDisable() { if (transitionInPlaceRoutine != null) { StopCoroutine(transitionInPlaceRoutine); } if (attemptTransitionAtEndOfFrameRoutine != null) { StopCoroutine(attemptTransitionAtEndOfFrameRoutine); } if (checkCanSnapRoutine != null) { StopCoroutine(checkCanSnapRoutine); } if(overridePreviousStateAtEndOfFrameRoutine != null) { StopCoroutine(overridePreviousStateAtEndOfFrameRoutine); } ForceUnsnap(); SetHighlightObjectActive(false); UnregisterAllUngrabEvents(); } protected virtual void Update() { CheckSnappedItemExists(); CheckPrefabUpdate(); CreateHighlightersInEditor(); CheckCurrentValidSnapObjectStillValid(); previousPrefab = highlightObjectPrefab; SetObjectHighlight(); } protected virtual void OnTriggerEnter(Collider collider) { CheckCanSnap(collider.GetComponentInParent()); } protected virtual void OnTriggerExit(Collider collider) { CheckCanUnsnap(collider.GetComponentInParent()); } protected virtual void CheckCanSnap(VRTK_InteractableObject interactableObjectCheck) { if (interactableObjectCheck != null && ValidSnapObject(interactableObjectCheck, true)) { AddCurrentValidSnapObject(interactableObjectCheck); if (!isSnapped) { ToggleHighlight(interactableObjectCheck, true); interactableObjectCheck.SetSnapDropZoneHover(this, true); if (!willSnap) { OnObjectEnteredSnapDropZone(SetSnapDropZoneEvent(interactableObjectCheck.gameObject)); } willSnap = true; ToggleHighlightColor(); } } } protected virtual void CheckCanUnsnap(VRTK_InteractableObject interactableObjectCheck) { if (interactableObjectCheck != null && currentValidSnapInteractableObjects.Contains(interactableObjectCheck) && ValidUnsnap(interactableObjectCheck)) { if (isSnapped && currentSnappedObject == interactableObjectCheck) { ForceUnsnap(); } RemoveCurrentValidSnapObject(interactableObjectCheck); if (!ValidSnappableObjectIsHovering()) { ToggleHighlight(interactableObjectCheck, false); willSnap = false; } interactableObjectCheck.SetSnapDropZoneHover(this, false); if (ValidSnapObject(interactableObjectCheck, true)) { ToggleHighlightColor(); OnObjectExitedSnapDropZone(SetSnapDropZoneEvent(interactableObjectCheck.gameObject)); } } } protected virtual bool ValidUnsnap(VRTK_InteractableObject interactableObjectCheck) { return (interactableObjectCheck.IsGrabbed() || ((snapType != SnapTypes.UseJoint || !float.IsInfinity(GetComponent().breakForce)) && interactableObjectCheck.validDrop == VRTK_InteractableObject.ValidDropTypes.DropAnywhere)); } protected virtual void SnapObjectToZone(VRTK_InteractableObject objectToSnap) { if (!isSnapped && ValidSnapObject(objectToSnap, false)) { SnapObject(objectToSnap); } } protected virtual void UnregisterAllUngrabEvents() { for (int i = 0; i < currentValidSnapInteractableObjects.Count; i++) { currentValidSnapInteractableObjects[i].InteractableObjectGrabbed -= InteractableObjectGrabbed; currentValidSnapInteractableObjects[i].InteractableObjectUngrabbed -= InteractableObjectUngrabbed; } } protected virtual bool ValidSnapObject(VRTK_InteractableObject interactableObjectCheck, bool grabState, bool checkGrabState = true) { return (interactableObjectCheck != null && (!checkGrabState || interactableObjectCheck.IsGrabbed() == grabState) && !VRTK_PolicyList.Check(interactableObjectCheck.gameObject, validObjectListPolicy)); } protected virtual string ObjectPath(string name) { return HIGHLIGHT_CONTAINER_NAME + "/" + name; } protected virtual void CheckSnappedItemExists() { if (isSnapped && (currentSnappedObject == null || !currentSnappedObject.gameObject.activeInHierarchy)) { ForceUnsnap(); OnObjectUnsnappedFromDropZone(SetSnapDropZoneEvent((currentSnappedObject != null ? currentSnappedObject.gameObject : null))); } } protected virtual void CheckPrefabUpdate() { //If the highlightObjectPrefab has changed then delete the highlight object in preparation to create a new one if (previousPrefab != null && previousPrefab != highlightObjectPrefab) { DeleteHighlightObject(); } } protected virtual void SetObjectHighlight() { if (highlightAlwaysActive && !isSnapped && !isHighlighted) { SetHighlightObjectActive(true); ToggleHighlightColor(); } if (!highlightAlwaysActive && isHighlighted && !ValidSnappableObjectIsHovering()) { SetHighlightObjectActive(false); } } protected virtual void ToggleHighlightColor() { if (Application.isPlaying && highlightAlwaysActive && !isSnapped && objectHighlighter != null) { objectHighlighter.Highlight((willSnap && validHighlightColor != Color.clear ? validHighlightColor : highlightColor)); } } protected virtual void CreateHighlightersInEditor() { if (VRTK_SharedMethods.IsEditTime()) { GenerateHighlightObject(); if (snapType == SnapTypes.UseJoint && GetComponent() == null) { VRTK_Logger.Warn(VRTK_Logger.GetCommonMessage(VRTK_Logger.CommonMessageKeys.REQUIRED_COMPONENT_MISSING_FROM_GAMEOBJECT, "SnapDropZone:" + name, "Joint", "the same", " because the `Snap Type` is set to `Use Joint`")); } GenerateEditorHighlightObject(); ForceSetObjects(); if (highlightEditorObject != null) { highlightEditorObject.SetActive(displayDropZoneInEditor); } } } protected virtual void CheckCurrentValidSnapObjectStillValid() { for (int i = 0; i < currentValidSnapInteractableObjects.Count; i++) { VRTK_InteractableObject interactableObjectCheck = currentValidSnapInteractableObjects[i]; //if the interactable object associated with it has been snapped to another zone, then unset the current valid snap object if (interactableObjectCheck != null && interactableObjectCheck.GetStoredSnapDropZone() != null && interactableObjectCheck.GetStoredSnapDropZone() != this) { RemoveCurrentValidSnapObject(interactableObjectCheck); if (isHighlighted && highlightObject != null && !highlightAlwaysActive) { SetHighlightObjectActive(false); } } } } protected virtual void ForceSetObjects() { if (highlightEditorObject == null) { Transform forceFindHighlightEditorObject = transform.Find(ObjectPath(HIGHLIGHT_EDITOR_OBJECT_NAME)); highlightEditorObject = (forceFindHighlightEditorObject ? forceFindHighlightEditorObject.gameObject : null); } if (highlightObject == null) { Transform forceFindHighlightObject = transform.Find(ObjectPath(HIGHLIGHT_OBJECT_NAME)); highlightObject = (forceFindHighlightObject ? forceFindHighlightObject.gameObject : null); } if (highlightContainer == null) { Transform forceFindHighlightContainer = transform.Find(HIGHLIGHT_CONTAINER_NAME); highlightContainer = (forceFindHighlightContainer ? forceFindHighlightContainer.gameObject : null); } } protected virtual void GenerateContainer() { if (highlightContainer == null || transform.Find(HIGHLIGHT_CONTAINER_NAME) == null) { highlightContainer = new GameObject(HIGHLIGHT_CONTAINER_NAME); highlightContainer.transform.SetParent(transform); highlightContainer.transform.localPosition = Vector3.zero; highlightContainer.transform.localRotation = Quaternion.identity; highlightContainer.transform.localScale = Vector3.one; } } protected virtual void DisableHighlightShadows() { if (highlightObject != null) { Renderer[] foundRenderers = highlightObject.GetComponentsInChildren(true); for (int i = 0; i < foundRenderers.Length; i++) { foundRenderers[i].receiveShadows = false; foundRenderers[i].shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; } } } protected virtual void SetContainer() { Transform findContainer = transform.Find(HIGHLIGHT_CONTAINER_NAME); if (findContainer != null) { highlightContainer = findContainer.gameObject; } } protected virtual void GenerateObjects() { GenerateHighlightObject(); if (highlightObject != null && objectHighlighter == null) { InitialiseHighlighter(); } } protected virtual void SnapObject(VRTK_InteractableObject interactableObjectCheck) { //If the item is in a snappable position and this drop zone isn't snapped and the collider is a valid interactable object if (willSnap && !isSnapped && ValidSnapObject(interactableObjectCheck, false)) { //Only snap it to the drop zone if it's not already in a drop zone if (!interactableObjectCheck.IsInSnapDropZone()) { if (highlightObject != null) { //Turn off the drop zone highlighter SetHighlightObjectActive(false); } Vector3 newLocalScale = GetNewLocalScale(interactableObjectCheck); if (transitionInPlaceRoutine != null) { StopCoroutine(transitionInPlaceRoutine); } isSnapped = true; currentSnappedObject = interactableObjectCheck; if (cloneNewOnUnsnap) { CreatePermanentClone(); } if (gameObject.activeInHierarchy) { transitionInPlaceRoutine = StartCoroutine(UpdateTransformDimensions(interactableObjectCheck, highlightContainer, newLocalScale, snapDuration)); } interactableObjectCheck.ToggleSnapDropZone(this, true); } } //Force reset isSnapped if the item is grabbed but isSnapped is still true isSnapped = (isSnapped && interactableObjectCheck != null && interactableObjectCheck.IsGrabbed() ? false : isSnapped); willSnap = !isSnapped; wasSnapped = false; } protected virtual void CreatePermanentClone() { VRTK_BaseHighlighter currentSnappedObjectHighlighter = currentSnappedObject.GetComponent(); if (currentSnappedObjectHighlighter != null) { currentSnappedObjectHighlighter.Unhighlight(); } objectToClone = Instantiate(currentSnappedObject.gameObject); objectToClone.transform.position = highlightContainer.transform.position; objectToClone.transform.rotation = highlightContainer.transform.rotation; Collider[] clonedObjectStates = currentSnappedObject.GetComponentsInChildren(); clonedObjectColliderStates = new bool[clonedObjectStates.Length]; for (int i = 0; i < clonedObjectStates.Length; i++) { Collider clonedObjectColliderState = clonedObjectStates[i]; clonedObjectColliderStates[i] = clonedObjectColliderState.isTrigger; clonedObjectColliderState.isTrigger = true; } objectToClone.SetActive(false); } protected virtual void ResetPermanentCloneColliders(GameObject objectToReset) { if (objectToReset != null && clonedObjectColliderStates.Length > 0) { Collider[] clonedObjectStates = objectToReset.GetComponentsInChildren(); for (int i = 0; i < clonedObjectStates.Length; i++) { Collider clonedObjectColliderState = clonedObjectStates[i]; if (clonedObjectColliderStates.Length > i) { clonedObjectColliderState.isTrigger = clonedObjectColliderStates[i]; } } } } protected virtual void ResnapPermanentClone() { if (objectToClone != null) { float savedSnapDuration = snapDuration; snapDuration = 0f; objectToClone.SetActive(true); ResetPermanentCloneColliders(objectToClone); ForceSnap(objectToClone); snapDuration = savedSnapDuration; } } protected virtual void UnsnapObject() { if (currentSnappedObject != null) { ResetPermanentCloneColliders(currentSnappedObject.gameObject); RemoveCurrentValidSnapObject(currentSnappedObject); } isSnapped = false; wasSnapped = true; VRTK_InteractableObject checkCanSnapObject = currentSnappedObject; currentSnappedObject = null; ResetSnapDropZoneJoint(); if (transitionInPlaceRoutine != null) { StopCoroutine(transitionInPlaceRoutine); } if (cloneNewOnUnsnap) { ResnapPermanentClone(); } if (checkCanSnapRoutine != null) { StopCoroutine(checkCanSnapRoutine); } if (gameObject.activeInHierarchy) { checkCanSnapRoutine = StartCoroutine(CheckCanSnapObjectAtEndOfFrame(checkCanSnapObject)); } checkCanSnapObject = null; } protected virtual Vector3 GetNewLocalScale(VRTK_InteractableObject checkObject) { // If apply scaling is checked then use the drop zone scale to resize the object Vector3 newLocalScale = checkObject.transform.localScale; if (applyScalingOnSnap) { checkObject.StoreLocalScale(); newLocalScale = Vector3.Scale(checkObject.transform.localScale, transform.localScale); } return newLocalScale; } protected virtual IEnumerator CheckCanSnapObjectAtEndOfFrame(VRTK_InteractableObject interactableObjectCheck) { yield return new WaitForEndOfFrame(); CheckCanSnap(interactableObjectCheck); } protected virtual IEnumerator UpdateTransformDimensions(VRTK_InteractableObject ioCheck, GameObject endSettings, Vector3 endScale, float duration) { float elapsedTime = 0f; Transform ioTransform = ioCheck.transform; Vector3 startPosition = ioTransform.position; Quaternion startRotation = ioTransform.rotation; Vector3 startScale = ioTransform.localScale; bool storedKinematicState = ioCheck.isKinematic; ioCheck.isKinematic = true; while (elapsedTime <= duration) { elapsedTime += Time.deltaTime; if (ioTransform != null && endSettings != null) { ioTransform.position = Vector3.Lerp(startPosition, endSettings.transform.position, (elapsedTime / duration)); ioTransform.rotation = Quaternion.Lerp(startRotation, endSettings.transform.rotation, (elapsedTime / duration)); ioTransform.localScale = Vector3.Lerp(startScale, endScale, (elapsedTime / duration)); } yield return null; } if (ioTransform != null && endSettings != null) { //Force all to the last setting in case anything has moved during the transition ioTransform.position = endSettings.transform.position; ioTransform.rotation = endSettings.transform.rotation; ioTransform.localScale = endScale; } ioCheck.isKinematic = storedKinematicState; SetDropSnapType(ioCheck); } protected virtual void SetDropSnapType(VRTK_InteractableObject ioCheck) { switch (snapType) { case SnapTypes.UseKinematic: ioCheck.SaveCurrentState(); ioCheck.isKinematic = true; break; case SnapTypes.UseParenting: ioCheck.SaveCurrentState(); ioCheck.isKinematic = true; ioCheck.transform.SetParent(transform); break; case SnapTypes.UseJoint: SetSnapDropZoneJoint(ioCheck.GetComponent()); break; } OnObjectSnappedToDropZone(SetSnapDropZoneEvent(ioCheck.gameObject)); } protected virtual void SetSnapDropZoneJoint(Rigidbody snapTo) { Joint snapDropZoneJoint = GetComponent(); if (snapDropZoneJoint == null) { VRTK_Logger.Error(VRTK_Logger.GetCommonMessage(VRTK_Logger.CommonMessageKeys.REQUIRED_COMPONENT_MISSING_FROM_GAMEOBJECT, "SnapDropZone:" + name, "Joint", "the same", " because the `Snap Type` is set to `Use Joint`")); return; } if (snapTo == null) { VRTK_Logger.Error(VRTK_Logger.GetCommonMessage(VRTK_Logger.CommonMessageKeys.REQUIRED_COMPONENT_MISSING_FROM_GAMEOBJECT, "VRTK_SnapDropZone", "Rigidbody", "the `VRTK_InteractableObject`")); return; } snapDropZoneJoint.connectedBody = snapTo; originalJointCollisionState = snapDropZoneJoint.enableCollision; //need to set this to true otherwise highlighting doesn't work again on grab snapDropZoneJoint.enableCollision = true; } protected virtual void ResetSnapDropZoneJoint() { Joint snapDropZoneJoint = GetComponent(); if (snapDropZoneJoint != null) { snapDropZoneJoint.enableCollision = originalJointCollisionState; } } protected virtual void AddCurrentValidSnapObject(VRTK_InteractableObject givenObject) { if (givenObject != null) { if (VRTK_SharedMethods.AddListValue(currentValidSnapInteractableObjects, givenObject, true)) { givenObject.InteractableObjectGrabbed += InteractableObjectGrabbed; givenObject.InteractableObjectUngrabbed += InteractableObjectUngrabbed; } } } protected virtual void RemoveCurrentValidSnapObject(VRTK_InteractableObject givenObject) { if (givenObject != null) { if (currentValidSnapInteractableObjects.Remove(givenObject)) { givenObject.InteractableObjectGrabbed -= InteractableObjectGrabbed; givenObject.InteractableObjectUngrabbed -= InteractableObjectUngrabbed; } } } protected virtual void InteractableObjectGrabbed(object sender, InteractableObjectEventArgs e) { VRTK_InteractableObject grabbedInteractableObject = sender as VRTK_InteractableObject; if (!grabbedInteractableObject.IsInSnapDropZone()) { CheckCanSnap(grabbedInteractableObject); } } protected virtual void InteractableObjectUngrabbed(object sender, InteractableObjectEventArgs e) { VRTK_InteractableObject releasedInteractableObject = sender as VRTK_InteractableObject; if (attemptTransitionAtEndOfFrameRoutine != null) { StopCoroutine(attemptTransitionAtEndOfFrameRoutine); } attemptTransitionAtEndOfFrameRoutine = StartCoroutine(AttemptForceSnapAtEndOfFrame(releasedInteractableObject)); } protected virtual void AttemptForceSnap(VRTK_InteractableObject objectToSnap) { //force snap settings on willSnap = true; //Force touch one of the object's colliders on this trigger collider SnapObjectToZone(objectToSnap); } protected virtual IEnumerator AttemptForceSnapAtEndOfFrame(VRTK_InteractableObject objectToSnap) { yield return new WaitForEndOfFrame(); objectToSnap.SaveCurrentState(); AttemptForceSnap(objectToSnap); } protected virtual void ToggleHighlight(VRTK_InteractableObject checkObject, bool state) { if (highlightObject != null && ValidSnapObject(checkObject, true, state)) { //Toggle the highlighter state SetHighlightObjectActive(state); } } protected virtual void CopyObject(GameObject objectBlueprint, ref GameObject clonedObject, string givenName) { GenerateContainer(); Vector3 saveScale = transform.localScale; transform.localScale = Vector3.one; clonedObject = Instantiate(objectBlueprint, highlightContainer.transform) as GameObject; clonedObject.name = givenName; //default position of new highlight object clonedObject.transform.localPosition = Vector3.zero; clonedObject.transform.localRotation = Quaternion.identity; transform.localScale = saveScale; CleanHighlightObject(clonedObject); } protected virtual void GenerateHighlightObject() { //If there is a given highlight prefab and no existing highlight object then create a new highlight object if (highlightObjectPrefab != null && highlightObject == null && transform.Find(ObjectPath(HIGHLIGHT_OBJECT_NAME)) == null) { CopyObject(highlightObjectPrefab, ref highlightObject, HIGHLIGHT_OBJECT_NAME); } //if highlight object exists but not in the variable then force grab it Transform checkForChild = transform.Find(ObjectPath(HIGHLIGHT_OBJECT_NAME)); if (checkForChild != null && highlightObject == null) { highlightObject = checkForChild.gameObject; } //if no highlight object prefab is set but a highlight object is found then destroy the highlight object if (highlightObjectPrefab == null && highlightObject != null) { DeleteHighlightObject(); } DisableHighlightShadows(); SetHighlightObjectActive(false); SetContainer(); } protected virtual void SetHighlightObjectActive(bool state) { if (highlightObject != null) { highlightObject.SetActive(state); isHighlighted = state; } } protected virtual void DeleteHighlightObject() { ChooseDestroyType(transform.Find(HIGHLIGHT_CONTAINER_NAME)); highlightContainer = null; highlightObject = null; objectHighlighter = null; } protected virtual void GenerateEditorHighlightObject() { if (highlightObject != null && highlightEditorObject == null && transform.Find(ObjectPath(HIGHLIGHT_EDITOR_OBJECT_NAME)) == null) { CopyObject(highlightObject, ref highlightEditorObject, HIGHLIGHT_EDITOR_OBJECT_NAME); Renderer[] renderers = highlightEditorObject.GetComponentsInChildren(); for (int i = 0; i < renderers.Length; i++) { renderers[i].material = Resources.Load("SnapDropZoneEditorObject") as Material; } highlightEditorObject.SetActive(true); } } protected virtual void CleanHighlightObject(GameObject objectToClean) { //If the highlight object has any child snap zones, then force delete these VRTK_SnapDropZone[] deleteSnapZones = objectToClean.GetComponentsInChildren(true); for (int i = 0; i < deleteSnapZones.Length; i++) { ChooseDestroyType(deleteSnapZones[i].gameObject); } //determine components that shouldn't be deleted from highlight object string[] validComponents = new string[] { "Transform", "MeshFilter", "MeshRenderer", "SkinnedMeshRenderer", "VRTK_GameObjectLinker" }; //First clean out the joints cause RigidBodys depends on them. Joint[] joints = objectToClean.GetComponentsInChildren(true); for (int i = 0; i < joints.Length; i++) { ChooseDestroyType(joints[i]); } //Go through all of the components on the highlighted object and delete any components that aren't in the valid component list Component[] components = objectToClean.GetComponentsInChildren(true); for (int i = 0; i < components.Length; i++) { Component component = components[i]; bool valid = false; //Loop through each valid component and check to see if this component is valid for (int j = 0; j < validComponents.Length; j++) { //if it's a valid component then break the check if (component.GetType().ToString().Contains("." + validComponents[j])) { valid = true; break; } } //if this is a valid component then just continue to the next component if (valid) { continue; } //If not a valid component then delete it ChooseDestroyType(component); } } protected virtual void InitialiseHighlighter() { VRTK_BaseHighlighter existingHighlighter = VRTK_BaseHighlighter.GetActiveHighlighter(gameObject); //If no highlighter is found on the GameObject then create the default one if (existingHighlighter == null) { highlightObject.AddComponent(); } else { VRTK_SharedMethods.CloneComponent(existingHighlighter, highlightObject); } //Initialise highlighter and set highlight colour objectHighlighter = highlightObject.GetComponent(); objectHighlighter.unhighlightOnDisable = false; objectHighlighter.Initialise(highlightColor); objectHighlighter.Highlight(highlightColor); //if the object highlighter is using a cloned object then disable the created highlight object's renderers if (objectHighlighter.UsesClonedObject()) { Renderer[] renderers = GetComponentsInChildren(true); for (int i = 0; i < renderers.Length; i++) { if (!VRTK_PlayerObject.IsPlayerObject(renderers[i].gameObject, VRTK_PlayerObject.ObjectTypes.Highlighter)) { renderers[i].enabled = false; } } } } protected virtual void ChooseDestroyType(Transform deleteTransform) { if (deleteTransform != null) { ChooseDestroyType(deleteTransform.gameObject); } } protected virtual void ChooseDestroyType(GameObject deleteObject) { if (VRTK_SharedMethods.IsEditTime()) { if (deleteObject != null) { DestroyImmediate(deleteObject); } } else { if (deleteObject != null) { Destroy(deleteObject); } } } protected virtual void ChooseDestroyType(Component deleteComponent) { if (VRTK_SharedMethods.IsEditTime()) { if (deleteComponent != null) { DestroyImmediate(deleteComponent); } } else { if (deleteComponent != null) { Destroy(deleteComponent); } } } protected virtual void OnDrawGizmosSelected() { if (highlightObject != null && !displayDropZoneInEditor) { Vector3 boxSize = VRTK_SharedMethods.GetBounds(highlightObject.transform).size * 1.05f; Gizmos.color = Color.red; Gizmos.DrawWireCube(highlightObject.transform.position, boxSize); } } protected virtual IEnumerator OverridePreviousStateAtEndOfFrame(VRTK_InteractableObject io, Transform parent, bool kinematic, bool grabbable) { yield return new WaitForEndOfFrame(); io.OverridePreviousState(parent, kinematic, grabbable); } } }