using UnityEngine; using Oculus.Avatar; using System; using System.Collections.Generic; public delegate void specificationCallback(IntPtr specification); public delegate void assetLoadedCallback(OvrAvatarAsset asset); public delegate void combinedMeshLoadedCallback(IntPtr asset); public class OvrAvatarSDKManager : MonoBehaviour { private static OvrAvatarSDKManager _instance; private bool initialized = false; private Dictionary> specificationCallbacks; private Dictionary> assetLoadedCallbacks; private Dictionary combinedMeshLoadedCallbacks; private Dictionary assetCache; private OvrAvatarTextureCopyManager textureCopyManager; public ovrAvatarLogLevel LoggingLevel = ovrAvatarLogLevel.Info; private Queue avatarSpecificationQueue; private List loadingAvatars; private bool avatarSpecRequestAvailable = true; private float lastDispatchedAvatarSpecRequestTime = 0f; private const float AVATAR_SPEC_REQUEST_TIMEOUT = 5f; #if AVATAR_DEBUG private ovrAvatarDebugContext debugContext = ovrAvatarDebugContext.None; #endif public struct AvatarSpecRequestParams { public UInt64 _userId; public specificationCallback _callback; public bool _useCombinedMesh; public ovrAvatarAssetLevelOfDetail _lod; public bool _forceMobileTextureFormat; public ovrAvatarLookAndFeelVersion _lookVersion; public ovrAvatarLookAndFeelVersion _fallbackVersion; public bool _enableExpressive; public AvatarSpecRequestParams( UInt64 userId, specificationCallback callback, bool useCombinedMesh, ovrAvatarAssetLevelOfDetail lod, bool forceMobileTextureFormat, ovrAvatarLookAndFeelVersion lookVersion, ovrAvatarLookAndFeelVersion fallbackVersion, bool enableExpressive) { _userId = userId; _callback = callback; _useCombinedMesh = useCombinedMesh; _lod = lod; _forceMobileTextureFormat = forceMobileTextureFormat; _lookVersion = lookVersion; _fallbackVersion = fallbackVersion; _enableExpressive = enableExpressive; } } public static OvrAvatarSDKManager Instance { get { if (_instance == null) { _instance = GameObject.FindObjectOfType(); if (_instance == null) { GameObject manager = new GameObject("OvrAvatarSDKManager"); _instance = manager.AddComponent(); _instance.textureCopyManager = manager.AddComponent(); _instance.initialized = _instance.Initialize(); } } return _instance.initialized ? _instance : null; } } private bool Initialize() { CAPI.Initialize(); string appId = GetAppId(); if (appId == "") { AvatarLogger.LogError("No Oculus App ID has been provided for target platform. " + "Go to Oculus Avatar > Edit Configuration to supply one", OvrAvatarSettings.Instance); appId = "0"; } #if UNITY_ANDROID && !UNITY_EDITOR #if AVATAR_XPLAT CAPI.ovrAvatar_Initialize(appId); #else CAPI.ovrAvatar_InitializeAndroidUnity(appId); #endif #else CAPI.ovrAvatar_Initialize(appId); CAPI.SendEvent("initialize", appId); #endif specificationCallbacks = new Dictionary>(); assetLoadedCallbacks = new Dictionary>(); combinedMeshLoadedCallbacks = new Dictionary(); assetCache = new Dictionary(); avatarSpecificationQueue = new Queue(); loadingAvatars = new List(); CAPI.ovrAvatar_SetLoggingLevel(LoggingLevel); CAPI.ovrAvatar_RegisterLoggingCallback(CAPI.LoggingCallback); #if AVATAR_DEBUG CAPI.ovrAvatar_SetDebugDrawContext((uint)debugContext); #endif return true; } void OnDestroy() { CAPI.Shutdown(); CAPI.ovrAvatar_RegisterLoggingCallback(null); CAPI.ovrAvatar_Shutdown(); } void Update() { if (Instance == null) { return; } #if AVATAR_DEBUG // Call before ovrAvatarMessage_Pop which flushes the state CAPI.ovrAvatar_DrawDebugLines(); #endif // Dispatch waiting avatar spec request if (avatarSpecificationQueue.Count > 0 && (avatarSpecRequestAvailable || Time.time - lastDispatchedAvatarSpecRequestTime >= AVATAR_SPEC_REQUEST_TIMEOUT)) { avatarSpecRequestAvailable = false; AvatarSpecRequestParams avatarSpec = avatarSpecificationQueue.Dequeue(); DispatchAvatarSpecificationRequest(avatarSpec); lastDispatchedAvatarSpecRequestTime = Time.time; AvatarLogger.Log("Avatar spec request dispatched: " + avatarSpec._userId); } IntPtr message = CAPI.ovrAvatarMessage_Pop(); if (message == IntPtr.Zero) { return; } ovrAvatarMessageType messageType = CAPI.ovrAvatarMessage_GetType(message); switch (messageType) { case ovrAvatarMessageType.AssetLoaded: { ovrAvatarMessage_AssetLoaded assetMessage = CAPI.ovrAvatarMessage_GetAssetLoaded(message); IntPtr asset = assetMessage.asset; UInt64 assetID = assetMessage.assetID; ovrAvatarAssetType assetType = CAPI.ovrAvatarAsset_GetType(asset); OvrAvatarAsset assetData = null; IntPtr avatarOwner = IntPtr.Zero; switch (assetType) { case ovrAvatarAssetType.Mesh: assetData = new OvrAvatarAssetMesh(assetID, asset, ovrAvatarAssetType.Mesh); break; case ovrAvatarAssetType.Texture: assetData = new OvrAvatarAssetTexture(assetID, asset); break; case ovrAvatarAssetType.Material: assetData = new OvrAvatarAssetMaterial(assetID, asset); break; case ovrAvatarAssetType.CombinedMesh: avatarOwner = CAPI.ovrAvatarAsset_GetAvatar(asset); assetData = new OvrAvatarAssetMesh(assetID, asset, ovrAvatarAssetType.CombinedMesh); break; case ovrAvatarAssetType.FailedLoad: AvatarLogger.LogWarning("Asset failed to load from SDK " + assetID); break; default: throw new NotImplementedException(string.Format("Unsupported asset type format {0}", assetType.ToString())); } HashSet callbackSet; if (assetType == ovrAvatarAssetType.CombinedMesh) { if (!assetCache.ContainsKey(assetID)) { assetCache.Add(assetID, assetData); } combinedMeshLoadedCallback callback; if (combinedMeshLoadedCallbacks.TryGetValue(avatarOwner, out callback)) { callback(asset); combinedMeshLoadedCallbacks.Remove(avatarOwner); } else { AvatarLogger.LogWarning("Loaded a combined mesh with no owner: " + assetMessage.assetID); } } else { if (assetData != null && assetLoadedCallbacks.TryGetValue(assetMessage.assetID, out callbackSet)) { assetCache.Add(assetID, assetData); foreach (var callback in callbackSet) { callback(assetData); } assetLoadedCallbacks.Remove(assetMessage.assetID); } } break; } case ovrAvatarMessageType.AvatarSpecification: { avatarSpecRequestAvailable = true; ovrAvatarMessage_AvatarSpecification spec = CAPI.ovrAvatarMessage_GetAvatarSpecification(message); HashSet callbackSet; if (specificationCallbacks.TryGetValue(spec.oculusUserID, out callbackSet)) { foreach (var callback in callbackSet) { callback(spec.avatarSpec); } specificationCallbacks.Remove(spec.oculusUserID); } else { AvatarLogger.LogWarning("Error, got an avatar specification callback from a user id we don't have a record for: " + spec.oculusUserID); } break; } default: throw new NotImplementedException("Unhandled ovrAvatarMessageType: " + messageType); } CAPI.ovrAvatarMessage_Free(message); } public bool IsAvatarSpecWaiting() { return avatarSpecificationQueue.Count > 0; } public bool IsAvatarLoading() { return loadingAvatars.Count > 0; } // Add avatar gameobject ID to loading list to keep track of loading avatars public void AddLoadingAvatar(int gameobjectID) { loadingAvatars.Add(gameobjectID); } // Remove avatar gameobject ID from loading list public void RemoveLoadingAvatar(int gameobjectID) { loadingAvatars.Remove(gameobjectID); } // Request an avatar specification to be loaded by adding to the queue. // Requests are dispatched in Update(). public void RequestAvatarSpecification(AvatarSpecRequestParams avatarSpecRequest) { avatarSpecificationQueue.Enqueue(avatarSpecRequest); AvatarLogger.Log("Avatar spec request queued: " + avatarSpecRequest._userId.ToString()); } private void DispatchAvatarSpecificationRequest(AvatarSpecRequestParams avatarSpecRequest) { textureCopyManager.CheckFallbackTextureSet(avatarSpecRequest._lod); CAPI.ovrAvatar_SetForceASTCTextures(avatarSpecRequest._forceMobileTextureFormat); HashSet callbackSet; if (!specificationCallbacks.TryGetValue(avatarSpecRequest._userId, out callbackSet)) { callbackSet = new HashSet(); specificationCallbacks.Add(avatarSpecRequest._userId, callbackSet); IntPtr specRequest = CAPI.ovrAvatarSpecificationRequest_Create(avatarSpecRequest._userId); CAPI.ovrAvatarSpecificationRequest_SetLookAndFeelVersion(specRequest, avatarSpecRequest._lookVersion); CAPI.ovrAvatarSpecificationRequest_SetFallbackLookAndFeelVersion(specRequest, avatarSpecRequest._fallbackVersion); CAPI.ovrAvatarSpecificationRequest_SetLevelOfDetail(specRequest, avatarSpecRequest._lod); CAPI.ovrAvatarSpecificationRequest_SetCombineMeshes(specRequest, avatarSpecRequest._useCombinedMesh); CAPI.ovrAvatarSpecificationRequest_SetExpressiveFlag(specRequest, avatarSpecRequest._enableExpressive); CAPI.ovrAvatar_RequestAvatarSpecificationFromSpecRequest(specRequest); CAPI.ovrAvatarSpecificationRequest_Destroy(specRequest); } callbackSet.Add(avatarSpecRequest._callback); } public void BeginLoadingAsset( UInt64 assetId, ovrAvatarAssetLevelOfDetail lod, assetLoadedCallback callback) { HashSet callbackSet; if (!assetLoadedCallbacks.TryGetValue(assetId, out callbackSet)) { callbackSet = new HashSet(); assetLoadedCallbacks.Add(assetId, callbackSet); } AvatarLogger.Log("Loading Asset ID: " + assetId); CAPI.ovrAvatarAsset_BeginLoadingLOD(assetId, lod); callbackSet.Add(callback); } public void RegisterCombinedMeshCallback( IntPtr sdkAvatar, combinedMeshLoadedCallback callback) { combinedMeshLoadedCallback currentCallback; if (!combinedMeshLoadedCallbacks.TryGetValue(sdkAvatar, out currentCallback)) { combinedMeshLoadedCallbacks.Add(sdkAvatar, callback); } else { throw new Exception("Adding second combind mesh callback for same avatar"); } } public OvrAvatarAsset GetAsset(UInt64 assetId) { OvrAvatarAsset asset; if (assetCache.TryGetValue(assetId, out asset)) { return asset; } else { return null; } } public void DeleteAssetFromCache(UInt64 assetId) { if (assetCache.ContainsKey(assetId)) { assetCache.Remove(assetId); } } public string GetAppId() { return UnityEngine.Application.platform == RuntimePlatform.Android ? OvrAvatarSettings.MobileAppID : OvrAvatarSettings.AppID; } public OvrAvatarTextureCopyManager GetTextureCopyManager() { if (textureCopyManager != null) { return textureCopyManager; } else { return null; } } }