//========= Copyright 2016-2023, HTC Corporation. All rights reserved. =========== using HTC.UnityPlugin.Utility; using HTC.UnityPlugin.VRModuleManagement; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using UnityEngine; namespace HTC.UnityPlugin.Vive { // This script creates and handles SteamVR_RenderModel using viveRole property or device index [DisallowMultipleComponent] [AddComponentMenu("VIU/Hooks/Render Model Hook", 10)] public class RenderModelHook : MonoBehaviour, IViveRoleComponent { public interface ICustomModel { void OnAfterModelCreated(RenderModelHook hook); /// /// If from all components return true, hook will perform SetActive(true) on this model after this message /// bool OnBeforeModelActivated(RenderModelHook hook); /// /// If from all components return true, hook will perform SetActive(false) on this model after this message /// bool OnBeforeModelDeactivated(RenderModelHook hook); } [AttributeUsage(AttributeTargets.Class)] public class CreatorPriorityAttirbute : Attribute { public int priority { get; set; } public CreatorPriorityAttirbute(int priority = 0) { this.priority = priority; } } public abstract class RenderModelCreator { public abstract bool shouldActive { get; } protected RenderModelHook hook { get; private set; } public void Initialize(RenderModelHook hook) { this.hook = hook; } public abstract void UpdateRenderModel(); public abstract void CleanUpRenderModel(); } public class DefaultRenderModelCreator : RenderModelCreator { private struct DefaultModelData { public bool isLoaded; public GameObject model; } private static EnumArray s_defaultModels = new EnumArray(); private bool m_isModelActivated; private EnumArray m_modelObjs = new EnumArray(); private VRModuleDeviceModel m_activeModel; [Obsolete] protected GameObject m_model; public override bool shouldActive { get { return true; } } protected VRModuleDeviceModel activeDefaultModel { get { return m_activeModel; } } protected bool isDefaultModelActive { get { return m_isModelActivated; } } public static GameObject GetDefaultDeviceModelPrefab(VRModuleDeviceModel modelNum) { if (modelNum < s_defaultModels.Min || modelNum > s_defaultModels.Max) { return null; } var modelData = s_defaultModels[modelNum]; if (!modelData.isLoaded) { GameObject modelPrefab = null; int modelNameIndex; var info = EnumUtils.GetDisplayInfo(typeof(VRModuleDeviceModel)); if (info.value2displayedIndex.TryGetValue((int)modelNum, out modelNameIndex)) { modelPrefab = Resources.Load("Models/VIUModel" + info.displayedNames[modelNameIndex]); } s_defaultModels[modelNum] = modelData = new DefaultModelData() { isLoaded = true, model = modelPrefab, }; } return modelData.model; } public override void UpdateRenderModel() { UpdateDefaultRenderModel(true); } protected void UpdateDefaultRenderModel(bool shouldActive) { var deviceState = VRModule.GetDeviceState(hook.GetModelDeviceIndex()); var lastModelActivated = m_isModelActivated; var lastActivatedModel = m_activeModel; var shouldActiveModelNum = hook.overrideModel == OverrideModelEnum.DontOverride ? deviceState.deviceModel : (VRModuleDeviceModel)hook.overrideModel; var shouldActiveModelPrefab = shouldActive ? GetDefaultDeviceModelPrefab(shouldActiveModelNum) : null; var shouldActiveModel = shouldActive && deviceState.isConnected && shouldActiveModelPrefab != null; if (lastModelActivated) { if (!shouldActiveModel || lastActivatedModel != shouldActiveModelNum) { // deactivate custom override model var lastActiveModelObj = m_modelObjs[m_activeModel]; if (lastActiveModelObj != null && SendBeforeModelDeactivatedMessage(lastActiveModelObj, hook)) { lastActiveModelObj.gameObject.SetActive(false); } m_isModelActivated = false; } } if (shouldActiveModel) { if (!lastModelActivated || lastActivatedModel != shouldActiveModelNum) { var shouldActiveModelObj = m_modelObjs[shouldActiveModelNum]; if (shouldActiveModelObj == null) { // instantiate custom override model shouldActiveModelObj = Instantiate(shouldActiveModelPrefab); shouldActiveModelObj.transform.position = Vector3.zero; shouldActiveModelObj.transform.rotation = Quaternion.identity; if (hook.m_overrideMaterial != null) { var renderer = shouldActiveModelObj.GetComponentInChildren(); if (renderer != null) { renderer.material = hook.m_overrideMaterial; } } if (hook.m_overrideShader != null) { var renderer = shouldActiveModelObj.GetComponentInChildren(); if (renderer != null) { renderer.material.shader = hook.m_overrideShader; } } shouldActiveModelObj.transform.SetParent(hook.transform, false); m_modelObjs[shouldActiveModelNum] = shouldActiveModelObj; SendAfterModelCreatedMessage(shouldActiveModelObj, hook); } // active custom override model if (SendBeforeModelActivatedMessage(shouldActiveModelObj, hook)) { shouldActiveModelObj.gameObject.SetActive(true); } m_activeModel = shouldActiveModelNum; m_isModelActivated = true; } } } public override void CleanUpRenderModel() { if (m_isModelActivated) { // deactivate custom override model var lastActiveModelObj = m_modelObjs[m_activeModel]; if (lastActiveModelObj != null && SendBeforeModelDeactivatedMessage(lastActiveModelObj, hook)) { lastActiveModelObj.gameObject.SetActive(false); } m_isModelActivated = false; } } } public enum Mode { Disable, ViveRole, DeivceIndex, } public enum Index { None = -1, Hmd, Device1, Device2, Device3, Device4, Device5, Device6, Device7, Device8, Device9, Device10, Device11, Device12, Device13, Device14, Device15, } public enum OverrideModelEnum { DontOverride = VRModuleDeviceModel.Unknown, ViveController = VRModuleDeviceModel.ViveController, ViveTracker = VRModuleDeviceModel.ViveTracker, ViveBaseStation = VRModuleDeviceModel.ViveBaseStation, OculusTouchLeft = VRModuleDeviceModel.OculusTouchLeft, OculusTouchRight = VRModuleDeviceModel.OculusTouchRight, OculusSensor = VRModuleDeviceModel.OculusSensor, KnucklesLeft = VRModuleDeviceModel.KnucklesLeft, KnucklesRight = VRModuleDeviceModel.KnucklesRight, DaydreamController = VRModuleDeviceModel.DaydreamController, ViveFocusFinch = VRModuleDeviceModel.ViveFocusFinch, OculusGoController = VRModuleDeviceModel.OculusGoController, OculusGearVrController = VRModuleDeviceModel.OculusGearVrController, WMRControllerLeft = VRModuleDeviceModel.WMRControllerLeft, WMRControllerRight = VRModuleDeviceModel.WMRControllerRight, ViveCosmosControllerLeft = VRModuleDeviceModel.ViveCosmosControllerLeft, ViveCosmosControllerRight = VRModuleDeviceModel.ViveCosmosControllerRight, OculusQuestControllerLeft = VRModuleDeviceModel.OculusQuestControllerLeft, OculusQuestControllerRight = VRModuleDeviceModel.OculusQuestControllerRight, IndexHMD = VRModuleDeviceModel.IndexHMD, // no model IndexControllerLeft = VRModuleDeviceModel.IndexControllerLeft, IndexControllerRight = VRModuleDeviceModel.IndexControllerRight, MagicLeapHMD = VRModuleDeviceModel.MagicLeapHMD, // no model MagicLeapController = VRModuleDeviceModel.MagicLeapController, // no model ViveHandTrackingTrackedHandLeft = VRModuleDeviceModel.ViveHandTrackingTrackedHandLeft, ViveHandTrackingTrackedHandRight = VRModuleDeviceModel.ViveHandTrackingTrackedHandRight, WaveLegacyTrackedHandLeft = VRModuleDeviceModel.WaveLegacyTrackedHandLeft, WaveLegacyTrackedHandRight = VRModuleDeviceModel.WaveLegacyTrackedHandRight, WaveTrackedHandLeft = VRModuleDeviceModel.WaveTrackedHandLeft, WaveTrackedHandRight = VRModuleDeviceModel.WaveTrackedHandRight, OculusTrackedHandLeft = VRModuleDeviceModel.OculusTrackedHandLeft, OculusTrackedHandRight = VRModuleDeviceModel.OculusTrackedHandRight, ViveFocus3ControllerLeft = VRModuleDeviceModel.ViveFocus3ControllerLeft, ViveFocus3ControllerRight = VRModuleDeviceModel.ViveFocus3ControllerRight, ViveFocusChirp = VRModuleDeviceModel.ViveFocusChirp, ViveTracker3 = VRModuleDeviceModel.ViveTracker3, ViveFlowPhoneController = VRModuleDeviceModel.ViveFlowPhoneController, OculusQuest2ControllerLeft = VRModuleDeviceModel.OculusQuest2ControllerLeft, OculusQuest2ControllerRight = VRModuleDeviceModel.OculusQuest2ControllerRight, ViveWristTracker = VRModuleDeviceModel.ViveWristTracker, } [SerializeField] private Mode m_mode = Mode.ViveRole; [SerializeField] private ViveRoleProperty m_viveRole = ViveRoleProperty.New(HandRole.RightHand); [SerializeField] private Index m_deviceIndex = Index.Hmd; [SerializeField] private OverrideModelEnum m_overrideModel = OverrideModelEnum.DontOverride; [SerializeField] private Shader m_overrideShader = null; [SerializeField] private Material m_overrideMaterial = null; [SerializeField] private VIUSettings.DeviceModelArray m_customModels = new VIUSettings.DeviceModelArray(); private static Type[] s_creatorTypes; private RenderModelCreator[] m_creators; private int m_activeCreatorIndex = -1; private int m_defaultCreatorIndex = -1; private bool m_isQuiting; private bool m_updateModelLock; public ViveRoleProperty viveRole { get { return m_viveRole; } } [Obsolete] public Transform origin { get; set; } [Obsolete] public bool applyTracking { get; set; } public OverrideModelEnum overrideModel { get { return m_overrideModel; } set { m_overrideModel = value; } } public Shader overrideShader { get { return m_overrideShader; } set { m_overrideShader = value; } } public Material overrideMaterial { get { return m_overrideMaterial; } set { m_overrideMaterial = value; } } public VIUSettings.DeviceModelArray customModels { get { return m_customModels; } } private static void FindAllRenderModelCreatorTypes() { if (s_creatorTypes != null) { return; } var defaultCreatorType = typeof(DefaultRenderModelCreator); try { var creatorBaseType = typeof(RenderModelCreator); var creatorTypes = new List(); var currentAsm = creatorBaseType.Assembly; var currentAsmName = currentAsm.GetName().Name; foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) { var referencingCurrentAsm = false; if (asm == currentAsm) { referencingCurrentAsm = true; } else { foreach (var asmref in asm.GetReferencedAssemblies()) { if (asmref.Name == currentAsmName) { referencingCurrentAsm = true; break; } } } if (referencingCurrentAsm) { foreach (var type in asm.GetTypes().Where(t => t.IsSubclassOf(creatorBaseType) && !t.IsAbstract && t != defaultCreatorType)) { creatorTypes.Add(type); } } } creatorTypes.Sort((x, y) => GetCreatorPriority(x) - GetCreatorPriority(y)); creatorTypes.Add(defaultCreatorType); s_creatorTypes = creatorTypes.ToArray(); } catch (Exception e) { s_creatorTypes = new Type[] { defaultCreatorType }; Debug.LogError(e); } } private static int GetCreatorPriority(Type t, int defaultValue = 0) { foreach (var at in t.GetCustomAttributes(typeof(CreatorPriorityAttirbute), true)) { return ((CreatorPriorityAttirbute)at).priority; } return defaultValue; } private void Awake() { FindAllRenderModelCreatorTypes(); m_creators = new RenderModelCreator[s_creatorTypes.Length]; for (int i = s_creatorTypes.Length - 1; i >= 0; --i) { m_creators[i] = (RenderModelCreator)Activator.CreateInstance(s_creatorTypes[i]); m_creators[i].Initialize(this); if (s_creatorTypes[i] == typeof(DefaultRenderModelCreator)) { m_defaultCreatorIndex = i; } } } protected virtual void OnEnable() { UpdateModel(); VRModule.onActiveModuleChanged += OnActiveModuleChanged; VRModule.onControllerRoleChanged += UpdateModel; m_viveRole.onDeviceIndexChanged += OnDeviceIndexChanged; m_viveRole.onRoleChanged += UpdateModel; } protected virtual void OnDisable() { VRModule.onActiveModuleChanged -= OnActiveModuleChanged; VRModule.onControllerRoleChanged -= UpdateModel; m_viveRole.onDeviceIndexChanged -= OnDeviceIndexChanged; m_viveRole.onRoleChanged -= UpdateModel; UpdateModel(); } private void OnDeviceIndexChanged(uint deviceIndex) { UpdateModel(); } private void OnActiveModuleChanged(VRModuleActiveEnum module) { UpdateModel(); } private void OnApplicationQuit() { m_isQuiting = true; } public uint GetModelDeviceIndex() { if (!enabled) { return VRModule.INVALID_DEVICE_INDEX; } uint result; switch (m_mode) { case Mode.ViveRole: result = m_viveRole.GetDeviceIndex(); break; case Mode.DeivceIndex: result = (uint)m_deviceIndex; break; case Mode.Disable: default: return VRModule.INVALID_DEVICE_INDEX; } return result; } private bool m_isCustomModelActivated; private EnumArray m_customModelObjs = new EnumArray(); private VRModuleDeviceModel m_activeCustomModel; public EnumArray.IReadOnly loadedCuustomModels { get { return m_customModelObjs.ReadOnly; } } private static void SendAfterModelCreatedMessage(GameObject rootObj, RenderModelHook hook) { var iList = ListPool.Get(); try { rootObj.GetComponentsInChildren(true, iList); for (int i = 0, imax = iList.Count; i < imax; ++i) { iList[i].OnAfterModelCreated(hook); } } finally { ListPool.Release(iList); } } private static bool SendBeforeModelActivatedMessage(GameObject rootObj, RenderModelHook hook) { var result = true; var iList = ListPool.Get(); try { rootObj.GetComponentsInChildren(true, iList); for (int i = 0, imax = iList.Count; i < imax; ++i) { result &= iList[i].OnBeforeModelActivated(hook); } } finally { ListPool.Release(iList); } return result; } private static bool SendBeforeModelDeactivatedMessage(GameObject rootObj, RenderModelHook hook) { var result = true; var iList = ListPool.Get(); try { rootObj.GetComponentsInChildren(true, iList); for (int i = 0, imax = iList.Count; i < imax; ++i) { result &= iList[i].OnBeforeModelDeactivated(hook); } } finally { ListPool.Release(iList); } return result; } [ContextMenu("Update Model")] public void UpdateModel() { if (m_isQuiting) { return; } if (m_updateModelLock) { Debug.LogWarning("Recursive UpdateModel call is not supported"); return; } m_updateModelLock = true; var deviceState = VRModule.GetDeviceState(GetModelDeviceIndex()); var lastActiveCustomModelNum = m_activeCustomModel; var lastActiveCustomModelObj = m_customModelObjs[m_activeCustomModel]; var lastCustomModelActive = m_isCustomModelActivated; var shouldActiveCustomModelNum = deviceState.deviceModel; var shouldActiveCustomModelPrefab = m_customModels[shouldActiveCustomModelNum]; if (shouldActiveCustomModelPrefab == null) { shouldActiveCustomModelPrefab = VIUSettings.GetOverrideDeviceModel(shouldActiveCustomModelNum); } var shouldActiveCustomModel = enabled && deviceState.isConnected && shouldActiveCustomModelPrefab != null; var lastCreatorActive = m_activeCreatorIndex >= 0; var shouldActiveCreator = enabled && !shouldActiveCustomModel; var shouldActiveCreatorIndex = -1; if (shouldActiveCreator) { // determin which creator should be activated shouldActiveCreatorIndex = m_defaultCreatorIndex; if (m_overrideModel == OverrideModelEnum.DontOverride) { for (int i = 0, imax = m_creators.Length; i < imax; ++i) { if (m_creators[i].shouldActive) { shouldActiveCreatorIndex = i; break; } } } } if (lastCustomModelActive) { if (!shouldActiveCustomModel || lastActiveCustomModelNum != shouldActiveCustomModelNum) { // deactivate custom override model if (lastActiveCustomModelObj != null && SendBeforeModelDeactivatedMessage(lastActiveCustomModelObj, this)) { lastActiveCustomModelObj.gameObject.SetActive(false); } m_isCustomModelActivated = false; } } if (lastCreatorActive) { if (!shouldActiveCreator || m_activeCreatorIndex != shouldActiveCreatorIndex) { // clean up old creator m_creators[m_activeCreatorIndex].CleanUpRenderModel(); m_activeCreatorIndex = -1; } } if (shouldActiveCustomModel) { if (!lastCustomModelActive || lastActiveCustomModelNum != shouldActiveCustomModelNum) { var shouldActiveCustomModelObj = m_customModelObjs[shouldActiveCustomModelNum]; if (shouldActiveCustomModelObj == null) { // instantiate custom override model shouldActiveCustomModelObj = Instantiate(shouldActiveCustomModelPrefab); shouldActiveCustomModelObj.transform.position = Vector3.zero; shouldActiveCustomModelObj.transform.rotation = Quaternion.identity; shouldActiveCustomModelObj.transform.SetParent(transform, false); m_customModelObjs[shouldActiveCustomModelNum] = shouldActiveCustomModelObj; SendAfterModelCreatedMessage(shouldActiveCustomModelObj, this); } // active custom override model if (SendBeforeModelActivatedMessage(shouldActiveCustomModelObj, this)) { shouldActiveCustomModelObj.gameObject.SetActive(true); } m_activeCustomModel = shouldActiveCustomModelNum; m_isCustomModelActivated = true; } } if (shouldActiveCreator) { m_activeCreatorIndex = shouldActiveCreatorIndex; // update active creator m_creators[m_activeCreatorIndex].UpdateRenderModel(); } m_updateModelLock = false; } } }