mixedreality/com.microsoft.mixedreality..../Providers/OpenVR/OpenVRRenderModel.cs

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));
}
}
}
}