// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #if GT_USE_UGUI using System.Collections.Generic; using UnityEngine; using UnityEngine.Serialization; using UnityEngine.UI; namespace Microsoft.MixedReality.GraphicsTools { /// /// Allows a 3D mesh to be rendered within a UnityUI canvas. /// [RequireComponent(typeof(CanvasRenderer))] public class CanvasElementMesh : MaskableGraphic { [Tooltip("The source mesh to use for populating the Graphic with vertex information.")] [SerializeField] [FormerlySerializedAs("Mesh")] private UnityEngine.Mesh mesh = null; /// /// The source mesh to use for populating the Graphic with vertex information. /// public UnityEngine.Mesh Mesh { get => mesh; set { mesh = value; SetVerticesDirty(); } } [Tooltip("The main texture to use on the material.")] [SerializeField] private Texture texture = null; /// /// The main texture to use on the material. /// public Texture Texture { get { if (texture == null) { return s_WhiteTexture; } return texture; } set { texture = value; } } [Tooltip("The normalized z-pivot that this mesh rotates around. (compare to RectTransform.pivot)")] [SerializeField] private float zPivot = 0f; /// /// The normalized z-pivot that this mesh rotates around. (compare to RectTransform.pivot) /// public float ZPivot { get => zPivot; set => zPivot = value; } [Tooltip("Whether this element should preserve its source mesh aspect ratio (scale).")] [SerializeField] private bool preserveAspect = true; /// /// Whether this element should preserve its source mesh aspect ratio (scale). /// public bool PreserveAspect { get => preserveAspect; set { preserveAspect = value; SetVerticesDirty(); } } private UnityEngine.Mesh previousMesh = null; private Color previousColor = Color.white; private List uiVerticies = new List(); private List uiIndices = new List(); #region UIBehaviour Implementation #if UNITY_EDITOR /// /// Enforces the parent canvas uses normal and tangent attributes. /// protected override void OnValidate() { base.OnValidate(); EnableVertexAttributes(); if (texture != null && material != null) { material.mainTexture = texture; } } #endif // UNITY_EDITOR /// /// Enforces the parent canvas uses normal and tangent attributes. /// protected override void Start() { base.Start(); EnableVertexAttributes(); } #endregion UIBehaviour Implementation #region Graphic Implementation /// public override Texture mainTexture { get { return Texture; } } /// /// Callback function when a UI element needs to generate vertices. /// protected override void OnPopulateMesh(VertexHelper vh) { vh.Clear(); RefreshMesh(); if (Mesh == null || uiVerticies.Count == 0) { return; } Vector3 meshSize = Mesh.bounds.size; Vector3 rectSize = rectTransform.rect.size; rectSize.z = meshSize.z; if (preserveAspect) { float meshRatio = meshSize.x / meshSize.y; float rectRatio = rectSize.x / rectSize.y; float scaler; // Wide if (meshSize.x > meshSize.y) { scaler = rectRatio > meshRatio ? rectSize.y * meshRatio : rectSize.x; } else // Tall { scaler = rectRatio > meshRatio ? rectSize.y : rectSize.x * (1.0f / meshRatio); } rectSize = new Vector3(scaler, scaler, scaler); } Vector3 rectPivot = rectTransform.pivot; rectPivot.z = ZPivot; List uiVerticiesTRS = new List(uiVerticies); // Scale, translate and rotate vertices. for (int i = 0; i < uiVerticiesTRS.Count; i++) { UIVertex vertex = uiVerticiesTRS[i]; // Scale the vector from the normalized position to the pivot by the rect size. vertex.position = Vector3.Scale(vertex.position - rectPivot, rectSize); uiVerticiesTRS[i] = vertex; } vh.AddUIVertexStream(uiVerticiesTRS, uiIndices); } #endregion Graphic Implementation /// /// Determines if vertex attributes within the Mesh need to be re-cached. /// [ContextMenu("Refresh Mesh")] private void RefreshMesh() { if (previousMesh != Mesh || previousColor != color || uiVerticies.Count == 0) { uiVerticies.Clear(); uiIndices.Clear(); if (Mesh != null) { List vertices = new List(); Mesh.GetVertices(vertices); List colors = new List(); Mesh.GetColors(colors); List uv0s = new List(); Mesh.GetUVs(0, uv0s); List uv1s = new List(); Mesh.GetUVs(1, uv1s); List uv2s = new List(); Mesh.GetUVs(2, uv2s); List uv3s = new List(); Mesh.GetUVs(3, uv3s); List normals = new List(); Mesh.GetNormals(normals); List tangents = new List(); Mesh.GetTangents(tangents); Vector3 rectPivot = new Vector3(0.5f, 0.5f, 0); Vector3 meshCenter = Mesh.bounds.center; Vector3 meshSize = Mesh.bounds.extents; float scaler = 0.5f / Mathf.Max(meshSize.x, meshSize.y); for (int i = 0; i < vertices.Count; ++i) { UIVertex vertex = new UIVertex(); vertex.position = vertices[i]; // Center the mesh at the origin. vertex.position -= meshCenter; // Normalize the mesh in a 1x1x1 cube. vertex.position *= scaler; // Center the mesh at the pivot. vertex.position += rectPivot; // Set the other attributes. vertex.normal = normals[i]; vertex.tangent = tangents[i]; if (i < colors.Count) { vertex.color = colors[i] * color; } else { vertex.color = color; } if (i < uv0s.Count) { vertex.uv0 = uv0s[i]; } if (i < uv1s.Count) { vertex.uv1 = uv1s[i]; } if (i < uv2s.Count) { vertex.uv2 = uv2s[i]; } if (i < uv3s.Count) { vertex.uv3 = uv3s[i]; } uiVerticies.Add(vertex); } Mesh.GetTriangles(uiIndices, 0); } previousMesh = Mesh; previousColor = color; } } /// /// Ensures the parent canvas has normal and tangent attributes enabled. /// private void EnableVertexAttributes() { var canvas = GetComponentInParent(); if (canvas != null) { canvas.additionalShaderChannels |= AdditionalCanvasShaderChannels.Normal; canvas.additionalShaderChannels |= AdditionalCanvasShaderChannels.Tangent; } } } } #endif // GT_USE_UGUI