OvrAvatarSDKManager.cs 14 KB
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<ulong, HashSet<specificationCallback>> specificationCallbacks;
    private Dictionary<ulong, HashSet<assetLoadedCallback>> assetLoadedCallbacks;
    private Dictionary<IntPtr, combinedMeshLoadedCallback> combinedMeshLoadedCallbacks;
    private Dictionary<UInt64, OvrAvatarAsset> assetCache;
    private OvrAvatarTextureCopyManager textureCopyManager;

    public ovrAvatarLogLevel LoggingLevel = ovrAvatarLogLevel.Info;
    private Queue<AvatarSpecRequestParams> avatarSpecificationQueue;
    private List<int> 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<OvrAvatarSDKManager>();
                if (_instance == null)
                {
                    GameObject manager = new GameObject("OvrAvatarSDKManager");
                    _instance = manager.AddComponent<OvrAvatarSDKManager>();
                    _instance.textureCopyManager = manager.AddComponent<OvrAvatarTextureCopyManager>();
                    _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<UInt64, HashSet<specificationCallback>>();
        assetLoadedCallbacks = new Dictionary<UInt64, HashSet<assetLoadedCallback>>();
        combinedMeshLoadedCallbacks = new Dictionary<IntPtr, combinedMeshLoadedCallback>();
        assetCache = new Dictionary<ulong, OvrAvatarAsset>();
        avatarSpecificationQueue = new Queue<AvatarSpecRequestParams>();
        loadingAvatars = new List<int>();

        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<assetLoadedCallback> 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<specificationCallback> 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<specificationCallback> callbackSet;
        if (!specificationCallbacks.TryGetValue(avatarSpecRequest._userId, out callbackSet))
        {
            callbackSet = new HashSet<specificationCallback>();
            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<assetLoadedCallback> callbackSet;
        if (!assetLoadedCallbacks.TryGetValue(assetId, out callbackSet))
        {
            callbackSet = new HashSet<assetLoadedCallback>();
            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;
        }
    }
}