318 lines
12 KiB
C#
318 lines
12 KiB
C#
/*
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
* All rights reserved.
|
|
*
|
|
* Licensed under the Oculus SDK License Agreement (the "License");
|
|
* you may not use the Oculus SDK except in compliance with the License,
|
|
* which is provided at the time of installation or download, or which
|
|
* otherwise accompanies this software in either electronic or hard copy form.
|
|
*
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* https://developer.oculus.com/licenses/oculussdk/
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, the Oculus SDK
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
using System;
|
|
using Oculus.Interaction.Surfaces;
|
|
using System.Collections.Generic;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
|
|
namespace Oculus.Interaction.UnityCanvas
|
|
{
|
|
public class CanvasCylinder : CanvasMesh, ICurvedPlane, ICylinderClipper
|
|
{
|
|
[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;
|
|
|
|
public bool GetCylinderSegment(out CylinderSegment segment)
|
|
{
|
|
segment = new CylinderSegment(Rotation, ArcDegrees, Bottom, Top);
|
|
return _started && isActiveAndEnabled;
|
|
}
|
|
|
|
protected override void Start()
|
|
{
|
|
this.BeginStart(ref _started, () => base.Start());
|
|
this.AssertField(_cylinder, nameof(_cylinder));
|
|
this.EndStart(ref _started);
|
|
}
|
|
|
|
#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 && _started)
|
|
{
|
|
EditorApplication.delayCall += () =>
|
|
{
|
|
UpdateImposter();
|
|
};
|
|
}
|
|
}
|
|
#endif
|
|
|
|
protected override void UpdateImposter()
|
|
{
|
|
base.UpdateImposter();
|
|
UpdateMeshPosition();
|
|
UpdateCurvedPlane();
|
|
}
|
|
|
|
protected override Vector3 MeshInverseTransform(Vector3 localPosition)
|
|
{
|
|
float angle = Mathf.Atan2(localPosition.x, localPosition.z + Radius);
|
|
float x = angle * Radius;
|
|
float y = localPosition.y;
|
|
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));
|
|
float height = _canvasRenderTexture.PixelsToUnits(Mathf.RoundToInt(resolution.y));
|
|
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);
|
|
}
|
|
|
|
#region Inject
|
|
|
|
public void InjectAllCanvasCylinder(CanvasRenderTexture canvasRenderTexture,
|
|
MeshFilter meshFilter,
|
|
Cylinder cylinder,
|
|
CylinderOrientation orientation)
|
|
{
|
|
InjectAllCanvasMesh(canvasRenderTexture, meshFilter);
|
|
InjectCylinder(cylinder);
|
|
InjectOrientation(orientation);
|
|
}
|
|
|
|
public void InjectCylinder(Cylinder cylinder)
|
|
{
|
|
_cylinder = cylinder;
|
|
}
|
|
|
|
public void InjectOrientation(CylinderOrientation orientation)
|
|
{
|
|
_orientation = orientation;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|