447 lines
17 KiB
C#
447 lines
17 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
//======= Copyright (c) Valve Corporation, All rights reserved. ===============
|
|
//
|
|
// Purpose: Render model of associated tracked object
|
|
//
|
|
//=============================================================================
|
|
|
|
using Microsoft.MixedReality.Toolkit.OpenVR.Headers;
|
|
using Microsoft.MixedReality.Toolkit.Utilities;
|
|
using System.Collections;
|
|
using System.Runtime.InteropServices;
|
|
using UnityEngine;
|
|
|
|
namespace Microsoft.MixedReality.Toolkit.OpenVR.Input
|
|
{
|
|
/// <summary>
|
|
/// Represents and loads models from the OpenVR APIs. This class is based on the SteamVR_RenderModel class.
|
|
/// </summary>
|
|
[AddComponentMenu("Scripts/MRTK/Providers/OpenVRRenderModel")]
|
|
public class OpenVRRenderModel : MonoBehaviour
|
|
{
|
|
private class RenderModel
|
|
{
|
|
public RenderModel(Mesh mesh, Material material)
|
|
{
|
|
Mesh = mesh;
|
|
Material = material;
|
|
}
|
|
public Mesh Mesh { get; private set; }
|
|
public Material Material { get; private set; }
|
|
}
|
|
|
|
private static readonly Hashtable Models = new Hashtable();
|
|
private static readonly Hashtable Materials = new Hashtable();
|
|
|
|
private string renderModelName = string.Empty;
|
|
|
|
internal Shader shader = null;
|
|
|
|
/// <summary>
|
|
/// Attempts to load or reload a controller model based on the passed in handedness.
|
|
/// </summary>
|
|
/// <param name="handedness">The handedness of the controller model to load.</param>
|
|
/// <returns>True if the controller model was found and loaded. False otherwise.</returns>
|
|
public bool LoadModel(Handedness handedness)
|
|
{
|
|
var system = Headers.OpenVR.System;
|
|
if (system == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var error = ETrackedPropertyError.TrackedProp_Success;
|
|
uint index = system.GetTrackedDeviceIndexForControllerRole(handedness == Handedness.Left ? ETrackedControllerRole.LeftHand : ETrackedControllerRole.RightHand);
|
|
var capacity = system.GetStringTrackedDeviceProperty(index, ETrackedDeviceProperty.Prop_RenderModelName_String, null, 0, ref error);
|
|
if (capacity <= 1)
|
|
{
|
|
Debug.LogError("Failed to get render model name for tracked object " + index);
|
|
return false;
|
|
}
|
|
|
|
var buffer = new System.Text.StringBuilder((int)capacity);
|
|
system.GetStringTrackedDeviceProperty(index, ETrackedDeviceProperty.Prop_RenderModelName_String, buffer, capacity, ref error);
|
|
|
|
var s = buffer.ToString();
|
|
if (renderModelName != s)
|
|
{
|
|
StartCoroutine(SetModelAsync(s));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private IEnumerator SetModelAsync(string newRenderModelName)
|
|
{
|
|
if (string.IsNullOrEmpty(newRenderModelName))
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
// Pre-load all render models before asking for the data to create meshes.
|
|
CVRRenderModels renderModels = Headers.OpenVR.RenderModels;
|
|
if (renderModels == null)
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
// Gather names of render models to pre-load.
|
|
string[] renderModelNames;
|
|
|
|
uint count = renderModels.GetComponentCount(newRenderModelName);
|
|
if (count > 0)
|
|
{
|
|
renderModelNames = new string[count];
|
|
|
|
for (int componentIndex = 0; componentIndex < count; componentIndex++)
|
|
{
|
|
uint capacity = renderModels.GetComponentName(newRenderModelName, (uint)componentIndex, null, 0);
|
|
if (capacity == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var componentNameStringBuilder = new System.Text.StringBuilder((int)capacity);
|
|
if (renderModels.GetComponentName(newRenderModelName, (uint)componentIndex, componentNameStringBuilder, capacity) == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
string componentName = componentNameStringBuilder.ToString();
|
|
|
|
capacity = renderModels.GetComponentRenderModelName(newRenderModelName, componentName, null, 0);
|
|
if (capacity == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var nameStringBuilder = new System.Text.StringBuilder((int)capacity);
|
|
if (renderModels.GetComponentRenderModelName(newRenderModelName, componentName, nameStringBuilder, capacity) == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var s = nameStringBuilder.ToString();
|
|
|
|
// Only need to pre-load if not already cached.
|
|
if (!(Models[s] is RenderModel model) || model.Mesh == null)
|
|
{
|
|
renderModelNames[componentIndex] = s;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Only need to pre-load if not already cached.
|
|
if (!(Models[newRenderModelName] is RenderModel model) || model.Mesh == null)
|
|
{
|
|
renderModelNames = new string[] { newRenderModelName };
|
|
}
|
|
else
|
|
{
|
|
renderModelNames = System.Array.Empty<string>();
|
|
}
|
|
}
|
|
|
|
// Keep trying every 100ms until all components finish loading.
|
|
while (true)
|
|
{
|
|
var loading = false;
|
|
for (int renderModelNameIndex = 0; renderModelNameIndex < renderModelNames.Length; renderModelNameIndex++)
|
|
{
|
|
if (string.IsNullOrEmpty(renderModelNames[renderModelNameIndex]))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var pRenderModel = System.IntPtr.Zero;
|
|
|
|
var error = renderModels.LoadRenderModel_Async(renderModelNames[renderModelNameIndex], ref pRenderModel);
|
|
|
|
if (error == EVRRenderModelError.Loading)
|
|
{
|
|
loading = true;
|
|
}
|
|
else if (error == EVRRenderModelError.None)
|
|
{
|
|
// Pre-load textures as well.
|
|
var renderModel = MarshalRenderModel(pRenderModel);
|
|
|
|
// Check the cache first.
|
|
var material = Materials[renderModel.diffuseTextureId] as Material;
|
|
if (material == null || material.mainTexture == null)
|
|
{
|
|
var pDiffuseTexture = System.IntPtr.Zero;
|
|
|
|
error = renderModels.LoadTexture_Async(renderModel.diffuseTextureId, ref pDiffuseTexture);
|
|
|
|
if (error == EVRRenderModelError.Loading)
|
|
{
|
|
loading = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (loading)
|
|
{
|
|
yield return new WaitForSecondsRealtime(0.1f);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
SetModel(newRenderModelName);
|
|
renderModelName = newRenderModelName;
|
|
}
|
|
|
|
private bool SetModel(string renderModelName)
|
|
{
|
|
StripMesh(gameObject);
|
|
|
|
if (!string.IsNullOrEmpty(renderModelName))
|
|
{
|
|
var model = Models[renderModelName] as RenderModel;
|
|
if (model == null || model.Mesh == null)
|
|
{
|
|
var renderModels = Headers.OpenVR.RenderModels;
|
|
if (renderModels == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
model = LoadRenderModel(renderModels, renderModelName, renderModelName);
|
|
if (model == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Models[renderModelName] = model;
|
|
}
|
|
|
|
gameObject.AddComponent<MeshFilter>().mesh = model.Mesh;
|
|
MeshRenderer newRenderer = gameObject.AddComponent<MeshRenderer>();
|
|
newRenderer.sharedMaterial = model.Material;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private RenderModel LoadRenderModel(CVRRenderModels renderModels, string renderModelName, string baseName)
|
|
{
|
|
var pRenderModel = System.IntPtr.Zero;
|
|
|
|
EVRRenderModelError error;
|
|
while (true)
|
|
{
|
|
error = renderModels.LoadRenderModel_Async(renderModelName, ref pRenderModel);
|
|
if (error != EVRRenderModelError.Loading)
|
|
break;
|
|
|
|
Sleep();
|
|
}
|
|
|
|
if (error != EVRRenderModelError.None)
|
|
{
|
|
Debug.LogError(string.Format("Failed to load render model {0} - {1}", renderModelName, error.ToString()));
|
|
return null;
|
|
}
|
|
|
|
var renderModel = MarshalRenderModel(pRenderModel);
|
|
|
|
var vertices = new Vector3[renderModel.unVertexCount];
|
|
var normals = new Vector3[renderModel.unVertexCount];
|
|
var uv = new Vector2[renderModel.unVertexCount];
|
|
|
|
var type = typeof(RenderModel_Vertex_t);
|
|
for (int iVert = 0; iVert < renderModel.unVertexCount; iVert++)
|
|
{
|
|
var ptr = new System.IntPtr(renderModel.rVertexData.ToInt64() + iVert * Marshal.SizeOf(type));
|
|
var vert = (RenderModel_Vertex_t)Marshal.PtrToStructure(ptr, type);
|
|
|
|
vertices[iVert] = new Vector3(vert.vPosition.v0, vert.vPosition.v1, -vert.vPosition.v2);
|
|
normals[iVert] = new Vector3(vert.vNormal.v0, vert.vNormal.v1, -vert.vNormal.v2);
|
|
uv[iVert] = new Vector2(vert.rfTextureCoord0, vert.rfTextureCoord1);
|
|
}
|
|
|
|
int indexCount = (int)renderModel.unTriangleCount * 3;
|
|
var indices = new short[indexCount];
|
|
Marshal.Copy(renderModel.rIndexData, indices, 0, indices.Length);
|
|
|
|
var triangles = new int[indexCount];
|
|
for (int iTri = 0; iTri < renderModel.unTriangleCount; iTri++)
|
|
{
|
|
triangles[iTri * 3 + 0] = (int)indices[iTri * 3 + 2];
|
|
triangles[iTri * 3 + 1] = (int)indices[iTri * 3 + 1];
|
|
triangles[iTri * 3 + 2] = (int)indices[iTri * 3 + 0];
|
|
}
|
|
|
|
var mesh = new Mesh
|
|
{
|
|
vertices = vertices,
|
|
normals = normals,
|
|
uv = uv,
|
|
triangles = triangles
|
|
};
|
|
|
|
// Check cache before loading texture.
|
|
var material = Materials[renderModel.diffuseTextureId] as Material;
|
|
if (material == null || material.mainTexture == null)
|
|
{
|
|
var pDiffuseTexture = System.IntPtr.Zero;
|
|
|
|
while (true)
|
|
{
|
|
error = renderModels.LoadTexture_Async(renderModel.diffuseTextureId, ref pDiffuseTexture);
|
|
if (error != EVRRenderModelError.Loading)
|
|
{
|
|
break;
|
|
}
|
|
|
|
Sleep();
|
|
}
|
|
|
|
if (error == EVRRenderModelError.None)
|
|
{
|
|
var diffuseTexture = MarshalRenderModel_TextureMap(pDiffuseTexture);
|
|
var texture = new Texture2D(diffuseTexture.unWidth, diffuseTexture.unHeight, TextureFormat.RGBA32, false);
|
|
if (SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Direct3D11)
|
|
{
|
|
texture.Apply();
|
|
System.IntPtr texturePointer = texture.GetNativeTexturePtr();
|
|
while (true)
|
|
{
|
|
error = renderModels.LoadIntoTextureD3D11_Async(renderModel.diffuseTextureId, texturePointer);
|
|
if (error != EVRRenderModelError.Loading)
|
|
{
|
|
break;
|
|
}
|
|
|
|
Sleep();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var textureMapData = new byte[diffuseTexture.unWidth * diffuseTexture.unHeight * 4]; // RGBA
|
|
Marshal.Copy(diffuseTexture.rubTextureMapData, textureMapData, 0, textureMapData.Length);
|
|
|
|
var colors = new Color32[diffuseTexture.unWidth * diffuseTexture.unHeight];
|
|
int iColor = 0;
|
|
for (int iHeight = 0; iHeight < diffuseTexture.unHeight; iHeight++)
|
|
{
|
|
for (int iWidth = 0; iWidth < diffuseTexture.unWidth; iWidth++)
|
|
{
|
|
var r = textureMapData[iColor++];
|
|
var g = textureMapData[iColor++];
|
|
var b = textureMapData[iColor++];
|
|
var a = textureMapData[iColor++];
|
|
colors[iHeight * diffuseTexture.unWidth + iWidth] = new Color32(r, g, b, a);
|
|
}
|
|
}
|
|
|
|
texture.SetPixels32(colors);
|
|
texture.Apply();
|
|
}
|
|
|
|
material = new Material(shader != null ? shader : Shader.Find("Mixed Reality Toolkit/Standard"))
|
|
{
|
|
mainTexture = texture
|
|
};
|
|
|
|
Materials[renderModel.diffuseTextureId] = material;
|
|
|
|
renderModels.FreeTexture(pDiffuseTexture);
|
|
}
|
|
else
|
|
{
|
|
Debug.Log("Failed to load render model texture for render model " + renderModelName + ". Error: " + error.ToString());
|
|
}
|
|
}
|
|
|
|
// Delay freeing when we can since we'll often get multiple requests for the same model right
|
|
// after another (e.g. two controllers or two base stations).
|
|
#if UNITY_EDITOR
|
|
if (!Application.isPlaying)
|
|
{
|
|
renderModels.FreeRenderModel(pRenderModel);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
StartCoroutine(FreeRenderModel(pRenderModel));
|
|
}
|
|
|
|
return new RenderModel(mesh, material);
|
|
}
|
|
|
|
private IEnumerator FreeRenderModel(System.IntPtr pRenderModel)
|
|
{
|
|
yield return new WaitForSeconds(1.0f);
|
|
Headers.OpenVR.RenderModels.FreeRenderModel(pRenderModel);
|
|
}
|
|
|
|
private void StripMesh(GameObject go)
|
|
{
|
|
var meshRenderer = go.GetComponent<MeshRenderer>();
|
|
if (meshRenderer != null)
|
|
{
|
|
DestroyImmediate(meshRenderer);
|
|
}
|
|
|
|
var meshFilter = go.GetComponent<MeshFilter>();
|
|
if (meshFilter != null)
|
|
{
|
|
DestroyImmediate(meshFilter);
|
|
}
|
|
}
|
|
|
|
private static void Sleep()
|
|
{
|
|
#if !UNITY_WSA
|
|
System.Threading.Thread.Sleep(1);
|
|
#endif
|
|
}
|
|
|
|
private RenderModel_t MarshalRenderModel(System.IntPtr pRenderModel)
|
|
{
|
|
#if !ENABLE_DOTNET
|
|
if ((System.Environment.OSVersion.Platform == System.PlatformID.MacOSX) ||
|
|
(System.Environment.OSVersion.Platform == System.PlatformID.Unix))
|
|
{
|
|
var packedModel = (RenderModel_t_Packed)Marshal.PtrToStructure(pRenderModel, typeof(RenderModel_t_Packed));
|
|
RenderModel_t model = new RenderModel_t();
|
|
packedModel.Unpack(ref model);
|
|
return model;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
return (RenderModel_t)Marshal.PtrToStructure(pRenderModel, typeof(RenderModel_t));
|
|
}
|
|
}
|
|
|
|
private RenderModel_TextureMap_t MarshalRenderModel_TextureMap(System.IntPtr pRenderModel)
|
|
{
|
|
#if !ENABLE_DOTNET
|
|
if ((System.Environment.OSVersion.Platform == System.PlatformID.MacOSX) ||
|
|
(System.Environment.OSVersion.Platform == System.PlatformID.Unix))
|
|
{
|
|
var packedModel = (RenderModel_TextureMap_t_Packed)Marshal.PtrToStructure(pRenderModel, typeof(RenderModel_TextureMap_t_Packed));
|
|
RenderModel_TextureMap_t model = new RenderModel_TextureMap_t();
|
|
packedModel.Unpack(ref model);
|
|
return model;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
return (RenderModel_TextureMap_t)Marshal.PtrToStructure(pRenderModel, typeof(RenderModel_TextureMap_t));
|
|
}
|
|
}
|
|
}
|
|
}
|