123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265 |
- using System;
- using UnityEngine.Assertions;
- using System.Collections.Generic;
- using UnityEditor;
- using UnityEngine;
- namespace Rokid.UXR.Interaction
- {
- public class CanvasCylinder : CanvasMesh, ICurvedPlane
- {
- [Serializable]
- public struct MeshGenerationSettings
- {
- [Delayed]
- public float VerticesPerDegree;
- [Delayed]
- public int MaxHorizontalResolution;
- [Delayed]
- public int MaxVerticalResolution;
- }
- public const int MIN_RESOLUTION = 2;
- [SerializeField]
- [Tooltip("The cylinder used to dictate the position and radius of the mesh.")]
- private Cylinder _cylinder;
- [SerializeField]
- [Tooltip("Determines how the mesh is projected on the cylinder wall. " +
- "Vertical results in a left-to-right curvature, Horizontal results in a top-to-bottom curvature.")]
- private CylinderOrientation _orientation = CylinderOrientation.Vertical;
- [SerializeField]
- private MeshGenerationSettings _meshGeneration = new MeshGenerationSettings()
- {
- VerticesPerDegree = 1.4f,
- MaxHorizontalResolution = 128,
- MaxVerticalResolution = 32
- };
- public float Radius => _cylinder.Radius;
- public Cylinder Cylinder => _cylinder;
- public float ArcDegrees { get; private set; }
- public float Rotation { get; private set; }
- public float Bottom { get; private set; }
- public float Top { get; private set; }
- private float CylinderRelativeScale => _cylinder.transform.lossyScale.x / transform.lossyScale.x;
- protected override void Start()
- {
- Assert.IsNotNull(_cylinder);
- }
- #if UNITY_EDITOR
- protected virtual void OnValidate()
- {
- _meshGeneration.MaxHorizontalResolution = Mathf.Max(MIN_RESOLUTION,
- _meshGeneration.MaxHorizontalResolution);
- _meshGeneration.MaxVerticalResolution = Mathf.Max(MIN_RESOLUTION,
- _meshGeneration.MaxVerticalResolution);
- _meshGeneration.VerticesPerDegree = Mathf.Max(0, _meshGeneration.VerticesPerDegree);
- if (Application.isPlaying)
- {
- EditorApplication.delayCall += () =>
- {
- UpdateImposter();
- };
- }
- }
- #endif
- public override void UpdateImposter()
- {
- base.UpdateImposter();
- UpdateMeshPosition();
- UpdateCurvedPlane();
- }
- protected override Vector3 MeshInverseTransform(Vector3 localPosition)
- {
- float angle = Mathf.Atan2(localPosition.x / _cylinder.CylinderCanvasScaleWidth, localPosition.z + Radius);
- float x = angle * Radius;
- float y = localPosition.y / _cylinder.CylinderCanvasScaleHeight;
- return new Vector3(x, y);
- }
- protected override void GenerateMesh(out List<Vector3> verts,
- out List<int> tris,
- out List<Vector2> uvs)
- {
- verts = new List<Vector3>();
- tris = new List<int>();
- uvs = new List<Vector2>();
- Vector2 worldSize = GetWorldSize();
- float scaledRadius = Radius * CylinderRelativeScale;
- float xPos = worldSize.x * 0.5f;
- float xNeg = -xPos;
- float yPos = worldSize.y * 0.5f;
- float yNeg = -yPos;
- Vector2Int GetClampedResolution(float arcMax, float axisMax)
- {
- int horizontalResolution = Mathf.Max(2,
- Mathf.RoundToInt(_meshGeneration.VerticesPerDegree *
- Mathf.Rad2Deg * arcMax / scaledRadius));
- int verticalResolution =
- Mathf.Max(2, Mathf.RoundToInt(horizontalResolution * axisMax / arcMax));
- horizontalResolution = Mathf.Clamp(horizontalResolution, 2,
- _meshGeneration.MaxHorizontalResolution);
- verticalResolution = Mathf.Clamp(verticalResolution, 2,
- _meshGeneration.MaxVerticalResolution);
- return new Vector2Int(horizontalResolution, verticalResolution);
- }
- Vector3 GetCurvedPoint(float u, float v)
- {
- float x = Mathf.Lerp(xNeg, xPos, u);
- float y = Mathf.Lerp(yNeg, yPos, v);
- float angle;
- Vector3 point;
- switch (_orientation)
- {
- default:
- case CylinderOrientation.Vertical:
- angle = x / scaledRadius;
- point.x = Mathf.Sin(angle) * scaledRadius;
- point.y = y;
- point.z = Mathf.Cos(angle) * scaledRadius - scaledRadius;
- break;
- case CylinderOrientation.Horizontal:
- angle = y / scaledRadius;
- point.x = x;
- point.y = Mathf.Sin(angle) * scaledRadius;
- point.z = Mathf.Cos(angle) * scaledRadius - scaledRadius;
- break;
- }
- return point;
- }
- Vector2Int resolution;
- switch (_orientation)
- {
- default:
- case CylinderOrientation.Vertical:
- resolution = GetClampedResolution(xPos, yPos);
- break;
- case CylinderOrientation.Horizontal:
- resolution = GetClampedResolution(yPos, xPos);
- break;
- }
- for (int y = 0; y < resolution.y; y++)
- {
- for (int x = 0; x < resolution.x; x++)
- {
- float u = x / (resolution.x - 1.0f);
- float v = y / (resolution.y - 1.0f);
- verts.Add(GetCurvedPoint(u, v));
- uvs.Add(new Vector2(u, v));
- }
- }
- for (int y = 0; y < resolution.y - 1; y++)
- {
- for (int x = 0; x < resolution.x - 1; x++)
- {
- int v00 = x + y * resolution.x;
- int v10 = v00 + 1;
- int v01 = v00 + resolution.x;
- int v11 = v00 + 1 + resolution.x;
- tris.Add(v00);
- tris.Add(v11);
- tris.Add(v10);
- tris.Add(v00);
- tris.Add(v01);
- tris.Add(v11);
- }
- }
- }
- private void UpdateMeshPosition()
- {
- Vector3 posInCylinder = _cylinder.transform.InverseTransformPoint(transform.position);
- Vector3 localYOffset = new Vector3(0, posInCylinder.y, 0);
- Vector3 localCancelY = posInCylinder - localYOffset;
- // If canvas position is on cylinder center axis, project forward.
- // Otherwise, project canvas onto cylinder wall from center axis.
- Vector3 projection = Mathf.Approximately(localCancelY.sqrMagnitude, 0f) ?
- Vector3.forward : localCancelY.normalized;
- Vector3 localUp;
- switch (_orientation)
- {
- default:
- case CylinderOrientation.Vertical:
- localUp = Vector3.up;
- break;
- case CylinderOrientation.Horizontal:
- localUp = Vector3.right;
- break;
- }
- transform.position = _cylinder.transform.TransformPoint((projection * _cylinder.Radius) + localYOffset);
- transform.rotation = _cylinder.transform.rotation * Quaternion.LookRotation(projection, localUp);
- if (_meshCollider != null &&
- _meshCollider.transform != transform &&
- !transform.IsChildOf(_meshCollider.transform))
- {
- _meshCollider.transform.position = transform.position;
- _meshCollider.transform.rotation = transform.rotation;
- _meshCollider.transform.localScale *= transform.lossyScale.x / _meshCollider.transform.lossyScale.x;
- }
- }
- private Vector2 GetWorldSize()
- {
- Vector2Int resolution = _canvasRenderTexture.GetBaseResolutionToUse();
- float width = _canvasRenderTexture.PixelsToUnits(Mathf.RoundToInt(resolution.x)) * _cylinder.CylinderCanvasScaleWidth;
- float height = _canvasRenderTexture.PixelsToUnits(Mathf.RoundToInt(resolution.y)) * _cylinder.CylinderCanvasScaleHeight;
- return new Vector2(width, height) / transform.lossyScale;
- }
- private void UpdateCurvedPlane()
- {
- // Get world size in cylinder space
- Vector2 cylinderSize = GetWorldSize() / CylinderRelativeScale;
- float arcSize, axisSize;
- switch (_orientation)
- {
- default:
- case CylinderOrientation.Vertical:
- arcSize = cylinderSize.x;
- axisSize = cylinderSize.y;
- break;
- case CylinderOrientation.Horizontal:
- arcSize = cylinderSize.y;
- axisSize = cylinderSize.x;
- break;
- }
- Vector3 posInCylinder = Cylinder.transform.InverseTransformPoint(transform.position);
- Rotation = Mathf.Atan2(posInCylinder.x, posInCylinder.z) * Mathf.Rad2Deg;
- ArcDegrees = (arcSize * 0.5f / Radius) * 2f * Mathf.Rad2Deg;
- Top = posInCylinder.y + (axisSize * 0.5f);
- Bottom = posInCylinder.y - (axisSize * 0.5f);
- }
- }
- }
|