Skip to content

Instantly share code, notes, and snippets.

@mwfarb
Created October 16, 2023 02:51
Show Gist options
  • Save mwfarb/2a1df6642a89a32f249d4d61b6369884 to your computer and use it in GitHub Desktop.
Save mwfarb/2a1df6642a89a32f249d4d61b6369884 to your computer and use it in GitHub Desktop.
Read Quest 3 mesh data and render random color in Unity.
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEngine;
/// <summary>
/// Attach this script to an empty GameObject in an empty scene, add quest3JsonFilePath in the inspector and press Play.
/// </summary>
public class Quest3Mesh : MonoBehaviour
{
// public fields for inspector
public string quest3JsonFilePath = null;
/// <summary>
/// On Play, load the file from quest3JsonFilePath and parse
/// </summary>
void Start()
{
// load json
try
{
using (StreamReader r = new StreamReader(quest3JsonFilePath))
{
string json = r.ReadToEnd();
JObject jObj = JObject.Parse(json);
LoadMeshObject(jObj, "global mesh");
}
}
catch (JsonReaderException e)
{
using (StreamReader r = new StreamReader(quest3JsonFilePath))
{
string json = r.ReadToEnd();
JArray jArr = JArray.Parse(json);
for (int i = 0; i < jArr.Count; i++)
{
JObject jObj = (JObject)jArr[i];
LoadMeshObject(jObj, (string)jObj.SelectToken($"semanticLabel"));
}
}
}
}
static void LoadMeshObject(JObject jObj, string name)
{
// create entity
var gobj = new GameObject();
gobj.name = name;
// parse vertices and indices, translating to unity coordinate frame from quest coordinate frame
var vertices = new List<Vector3>();
var indices = new List<int>();
var v = 0;
while (jObj.SelectToken($"vertices.{v}") != null)
{
vertices.Add(new Vector3(
(float)jObj.SelectToken($"vertices.{v + 0}"),
(float)jObj.SelectToken($"vertices.{v + 1}"),
-(float)jObj.SelectToken($"vertices.{v + 2}")
));
v += 3;
}
var i = 0;
while (jObj.SelectToken($"indices.{i}") != null)
{
indices.Add((int)jObj.SelectToken($"indices.{i}"));
i++;
}
var x = 0;
while (jObj.SelectToken($"polygon.[{x}]") != null)
{
vertices.Add(new Vector4(
(float)jObj.SelectToken($"polygon.[{x}].x"),
(float)jObj.SelectToken($"polygon.[{x}].y"),
-(float)jObj.SelectToken($"polygon.[{x}].z"),
(float)jObj.SelectToken($"polygon.[{x}].w")
));
indices.Add(x);
x++;
}
if (jObj.SelectToken($"polygon.[2]") != null)
{
indices.Add(2);
}
// add mesh geometry
var mesh = new Mesh();
if (vertices.Count > 0)
{
mesh.SetVertices(vertices);
}
if (indices.Count > 0)
{
indices.Reverse();
mesh.SetTriangles(indices, 0);
}
mesh.RecalculateNormals();
mesh.RecalculateBounds();
var filter = gobj.AddComponent<MeshFilter>();
filter.sharedMesh = mesh;
// add material/color
var mr = gobj.AddComponent<MeshRenderer>();
Color c = Random.ColorHSV();
mr.material.color = c;
// compute pose matrix
var matrices = new List<Vector4>();
int p = 0;
while (jObj.SelectToken($"meshPose.{p}") != null)
{
matrices.Add(new Vector4(
(float)jObj.SelectToken($"meshPose.{p + 0}"),
(float)jObj.SelectToken($"meshPose.{p + 1}"),
(float)jObj.SelectToken($"meshPose.{p + 2}"),
(float)jObj.SelectToken($"meshPose.{p + 3}")
));
p += 4;
}
while (jObj.SelectToken($"planePose.{p}") != null)
{
matrices.Add(new Vector4(
(float)jObj.SelectToken($"planePose.{p + 0}"),
(float)jObj.SelectToken($"planePose.{p + 1}"),
(float)jObj.SelectToken($"planePose.{p + 2}"),
(float)jObj.SelectToken($"planePose.{p + 3}")
));
p += 4;
}
// set pose
if (matrices.Count > 0)
{
FromMatrix(gobj.transform, new Matrix4x4(matrices[0], matrices[1], matrices[2], matrices[3]));
}
static void FromMatrix(Transform transform, Matrix4x4 m)
{
if (m.ValidTRS())
{
transform.localPosition = new Vector3(
m.m03,
m.m13,
-m.m23
);
transform.localRotation = new Quaternion(
-m.rotation.x,
-m.rotation.y,
m.rotation.z,
m.rotation.w
);
transform.localScale = m.lossyScale;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment