// Copyright (C) 2014-2024 Gleechi Technology AB. All rights reserved. using System.Linq; using System.Collections.Generic; using UnityEngine; namespace VirtualGrasp.Scripts { /** * VG_HintVisualizer provides a tool to visualize some hints such as a selection sphere to debug object selection or a push sphere to guide pushing interactions. * The MonoBehavior provides a tutorial on the VG API functions for accessing the push state (GetPushCircle). */ [LIBVIRTUALGRASP_UNITY_SCRIPT] [HelpURL("https://docs.virtualgrasp.com/unity_component_vghintvisualizer." + VG_Version.__VG_VERSION__ + ".html")] public class VG_HintVisualizer : MonoBehaviour { [Tooltip("Enable push hint visualization. For each hand, a circular hint will appear when the hand is in PUSHING mode.")] public bool m_enablePushHints = true; [Tooltip("Enable grasp hint visualization. For each hand, a sphere will appear as a hint of the current selection state.")] public bool m_enableGraspHints = true; [Tooltip("A push hint visualization (e.g. put a sphere here) when push mode is PUSHING.")] public List pushHints = new List { }; [Tooltip("A selection hint visualization (e.g. put a sphere here) where the grasp selector is placed.")] public List graspHints = new List { }; /// Add a collider-less sphere as a marker private void AddHintObject(List hintList, string name) { GameObject go = GameObject.CreatePrimitive(PrimitiveType.Sphere); DestroyImmediate(go.GetComponent()); go.name = name; go.transform.localScale = Vector3.zero; go.transform.SetParent(transform); hintList.Add(go.transform); } private void RemoveHintObjects() { foreach (var hint in pushHints) DestroyImmediate(hint.gameObject); foreach (var hint in graspHints) DestroyImmediate(hint.gameObject); pushHints.Clear(); graspHints.Clear(); } private void OnEnable() { VG_Controller.OnPostUpdate.AddListener(HintUpdate); if (pushHints.Count == 0) AddHintObject(pushHints, "PushHint"); if (graspHints.Count == 0) AddHintObject(graspHints, "SelectionHint"); } private void OnDisable() { VG_Controller.OnPostUpdate.RemoveListener(HintUpdate); RemoveHintObjects(); } void HintUpdate() { if (m_enablePushHints) { // Fill up the push hints assuming each avatar has 2 hands while (pushHints.Count < VG_Controller.GetHands().Count()) { pushHints.Add(GameObject.Instantiate(pushHints.Last())); pushHints.Last().SetParent(transform); } int num = 0; foreach (VG_HandStatus hand in VG_Controller.GetHands()) { Transform t = VG_Controller.GetPushCircle(hand.m_avatarID, hand.m_side, out Vector3 p, out Quaternion q, out float radius, out bool inContact); Transform hint = pushHints[num]; hint.gameObject.SetActive(t != null); if (t != null) { hint.SetPositionAndRotation(p + q * new Vector3(0, 0, -0.001f), q); hint.localScale = new Vector3(2 * radius, 2 * radius, 0.001f); if (inContact) VG_Controller.OnObjectCollided.Invoke(hand); } num++; } } if (m_enableGraspHints) { // Fill up the push hints assuming each avatar has 2 hands while (graspHints.Count < VG_Controller.GetHands().Count()) { graspHints.Add(GameObject.Instantiate(graspHints.Last())); graspHints.Last().SetParent(transform); } int num = 0; foreach (VG_HandStatus hand in VG_Controller.GetHands()) { Transform t = VG_Controller.GetBone(hand.m_avatarID, hand.m_side, VG_BoneType.APPROACH, out _, out Matrix4x4 m); Transform hint = graspHints[num]; hint.gameObject.SetActive(t != null); if (t != null) { hint.SetPositionAndRotation(m.GetColumn(3), Quaternion.LookRotation(m.GetColumn(2), m.GetColumn(1))); if (m.GetRow(3) != new Vector4(0, 0, 0, 1)) hint.localScale = m.GetRow(3); hint.GetComponent().material.SetColor("_Color", VG_Controller.GetHand(hand.m_avatarID, hand.m_side).m_selectedObject == null ? Color.red : Color.green); } num++; } } } } }