// %BANNER_BEGIN%
// ---------------------------------------------------------------------
// %COPYRIGHT_BEGIN%
// Copyright (c) (2024) Magic Leap, Inc. All Rights Reserved.
// Use of this file is governed by the Software License Agreement, located here: https://www.magicleap.com/software-license-agreement-ml2
// Terms and conditions applicable to third-party materials accompanying this distribution may also be found in the top-level NOTICE file appearing herein.
// %COPYRIGHT_END%
// ---------------------------------------------------------------------
// %BANNER_END%
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.ARSubsystems;
using MagicLeap.OpenXR.Features.Meshing;
using UnityEngine.XR;
///
/// Adapts and prepares meshes from ARMeshManager to use the TexturedWireframe material and shader.
/// Somewhat based on the texture-based wireframe technique described in
/// http://sibgrapi.sid.inpe.br/col/sid.inpe.br/sibgrapi/2010/09.15.18.18/doc/texture-based_wireframe_rendering.pdf
/// See Figure 5c and related description
///
public class MeshTexturedWireframeAdapter : MonoBehaviour
{
[SerializeField, Tooltip("The ARMeshManager in the scene.")]
private ARMeshManager meshManager = null;
[SerializeField, Tooltip("The textured wireframe material.")]
private Material wireframeMaterial = null;
private Texture2D proceduralTexture = null;
private int lineTextureWidth = 2048; // Overall width of texture used for the line (will be 1px high)
private int linePixelWidth = 24; // Line fill pixel width (left side) representing line, over background
private int lineEdgeGradientWidth = 4; // Falloff gradient pixel size to smooth line edge
private float[] confidenceData = new float[0];
private List vertices = new List();
private List indices = new List();
private List uvs = new List();
private MagicLeapMeshingFeature meshingFeature;
private Mesh meshReference;
public MagicLeapMeshingFeature MeshingFeature
{
get
{
meshingFeature ??= OpenXRSettings.Instance.GetFeature();
return meshingFeature;
}
}
public bool ComputeConfidences { get; set; }
public bool ComputeNormals { get; set; }
private void Awake()
{
if (wireframeMaterial != null)
{
// Create procedural texture used to render the line (more control this way over mip-map levels)
proceduralTexture = new Texture2D(lineTextureWidth, 1, TextureFormat.ARGB32, 7, true);
int w = linePixelWidth - (lineEdgeGradientWidth / 2);
for (int i = 0; i < lineTextureWidth; i++)
{
var color = i <= w ? Color.white :
i > w + lineEdgeGradientWidth ? Color.clear :
Color.Lerp(Color.white, Color.clear, (i - w) / (float)lineEdgeGradientWidth);
proceduralTexture.SetPixel(i, 0, color);
}
proceduralTexture.wrapMode = TextureWrapMode.Clamp;
proceduralTexture.Apply();
wireframeMaterial.mainTexture = proceduralTexture;
}
}
private void OnEnable()
{
// Subscribe to meshing changes
if (meshManager != null)
{
meshManager.meshesChanged += OnMeshUpdatedOrAdded;
}
}
private void OnDisable()
{
if (meshManager != null)
{
meshManager.meshesChanged -= OnMeshUpdatedOrAdded;
}
}
private void OnDestroy()
{
if (proceduralTexture != null)
{
Destroy(proceduralTexture);
proceduralTexture = null;
}
}
private MeshId GetMeshIdForMesh(Mesh mesh)
{
var meshName = mesh.name;
var splitResult = meshName.Split(" ").LastOrDefault();
if (string.IsNullOrEmpty(splitResult))
{
return MeshId.InvalidId;
}
return MeshingFeature.CreateMeshId(splitResult);
}
private void OnMeshUpdatedOrAdded(ARMeshesChangedEventArgs args)
{
foreach (var meshFilter in args.added)
ConfigureTrianglesForMesh(meshFilter);
foreach (var meshFilter in args.updated)
ConfigureTrianglesForMesh(meshFilter);
}
private void ConfigureTrianglesForMesh(MeshFilter meshFilter)
{
// Adapt the mesh for the textured wireframe shader.
if (meshFilter != null)
{
meshReference = meshFilter.mesh;
var meshId = GetMeshIdForMesh(meshReference);
if (meshId == MeshId.InvalidId)
return;
meshReference.GetVertices(vertices);
uvs.Clear();
for (int i = 0; i < vertices.Count; i++)
{
uvs.Add(Vector3.forward);
}
meshReference.GetTriangles(indices, 0);
bool validConfidences = ComputeConfidences && MeshingFeature != null
&& MeshingFeature.GetMeshData(in meshId, out _, out _, out confidenceData)
&& uvs.Count == confidenceData.Length;
// Encode confidence in uv.z
for (int i = 0; i < uvs.Count; i++)
{
var uv = uvs[i];
uv.z = validConfidences ? confidenceData[i] : 1;
uvs[i] = uv;
}
int indicesOrigCount = indices.Count;
for (int i = 0; i < indicesOrigCount; i += 3)
{
var i1 = indices[i];
var i2 = indices[i + 1];
var i3 = indices[i + 2];
var v1 = vertices[i1];
var v2 = vertices[i2];
var v3 = vertices[i3];
var uv1 = uvs[i1];
var uv2 = uvs[i2];
var uv3 = uvs[i3];
// Create a new center vertex of each triangle, adjusting indices and add new triangles
// Will use Incenter of Triangle (center that is equidistant to edges).
// This allows the line width to be consistent regardless of triangle size.
// Also allows line width to be adjusted dynamically.
// Calculate position of incenter vertex
var a = Vector3.Distance(v2, v3);
var b = Vector3.Distance(v1, v3);
var c = Vector3.Distance(v1, v2);
var sum = a + b + c;
var vIntercenter = new Vector3((a * v1.x + b * v2.x + c * v3.x) / sum,
(a * v1.y + b * v2.y + c * v3.y) / sum,
(a * v1.z + b * v2.z + c * v3.z) / sum);
vertices.Add(vIntercenter);
int iC = vertices.Count - 1;
// Distance to edge, or radius of incircle
var s = sum / 2.0f;
var r = Mathf.Sqrt(((s - a) * (s - b) * (s - c)) / s);
// Calculate UV for the incenter vertex for a 1mm target line width
// Half of each line is rendered on the edges of each triangle, so .001/2 = .0005
// Can be adjusted in shader to vary line width dynamically.
float lineWidth = .0005f;
float segmentPixels = (r / lineWidth) * linePixelWidth;
float segmentUV = segmentPixels / lineTextureWidth;
Vector3 centerUV = Vector3.one * segmentUV;
centerUV.z = validConfidences ? (a * uv1.z + b * uv2.z + c * uv3.z) / sum : 1;
uvs.Add(centerUV);
// Modify triangle to emanate from new center vertex, along with 2 new triangles
indices[i + 2] = iC;
indices.Add(i1);
indices.Add(iC);
indices.Add(i3);
indices.Add(i2);
indices.Add(i3);
indices.Add(iC);
}
meshReference.SetVertices(vertices);
meshReference.SetUVs(0, uvs);
meshReference.SetTriangles(indices, 0);
if (ComputeNormals)
{
meshReference.RecalculateNormals();
}
}
}
}