255 lines
11 KiB
C#
255 lines
11 KiB
C#
using System.Collections.Generic;
|
|
using UnityEditor;
|
|
using UnityEngine.ProBuilder.MeshOperations;
|
|
|
|
namespace UnityEngine.ProBuilder.Shapes
|
|
{
|
|
/// <summary>
|
|
/// Represents a basic [torus](../manual/Torus.html) shape.
|
|
/// </summary>
|
|
[Shape("Torus")]
|
|
public class Torus : Shape
|
|
{
|
|
/// <summary>
|
|
/// Sets the complexity of the mesh, together with the `Columns` value. The higher the value,
|
|
/// the smoother the shape, but at the cost of more polygons to calculate.
|
|
///
|
|
/// The default value is 16. Valid values are from 3 to 64.
|
|
/// </summary>
|
|
[Range(3, 64)]
|
|
[SerializeField]
|
|
int m_Rows = 16;
|
|
|
|
/// <summary>
|
|
/// Sets the complexity of the mesh, together with the `Rows` value. The higher the value,
|
|
/// the smoother the shape, but at the cost of more polygons (and therefore more computation).
|
|
///
|
|
/// The default value is 24. Valid values are from 3 to 64.
|
|
/// </summary>
|
|
[Range(3, 64)]
|
|
[SerializeField]
|
|
int m_Columns = 24;
|
|
|
|
/// <summary>
|
|
/// Sets the radius of the tube itself in meters.
|
|
/// The default value is 0.1. The minimum value is 0.01.
|
|
/// </summary>
|
|
[Min(0.01f)]
|
|
[SerializeField]
|
|
float m_TubeRadius = .1f;
|
|
|
|
/// <summary>
|
|
/// Sets the degree of the torus's circumference.
|
|
/// The default value is 360. Valid values are from 0 to 360.
|
|
/// </summary>
|
|
[Range(0, 360)]
|
|
[SerializeField]
|
|
float m_HorizontalCircumference = 360;
|
|
|
|
/// <summary>
|
|
/// Sets the degree of the tube's circumference.
|
|
/// The default value is 360. Valid values are from 0 to 360.
|
|
/// </summary>
|
|
[Range(0, 360)]
|
|
[SerializeField]
|
|
float m_VerticalCircumference = 360;
|
|
|
|
/// <summary>
|
|
/// Determines whether to smooth the edges of the polygons.
|
|
/// The default value is true.
|
|
/// </summary>
|
|
[SerializeField]
|
|
bool m_Smooth = true;
|
|
|
|
/// <inheritdoc/>
|
|
public override void CopyShape(Shape shape)
|
|
{
|
|
if(shape is Torus)
|
|
{
|
|
Torus torus = (Torus) shape;
|
|
m_Rows = torus.m_Rows;
|
|
m_Columns = torus.m_Columns;
|
|
m_TubeRadius = torus.m_TubeRadius;
|
|
m_HorizontalCircumference = torus.m_HorizontalCircumference;
|
|
m_VerticalCircumference = torus.m_VerticalCircumference;
|
|
m_Smooth = torus.m_Smooth;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override Bounds UpdateBounds(ProBuilderMesh mesh, Vector3 size, Quaternion rotation, Bounds bounds)
|
|
{
|
|
bounds.size = mesh.mesh.bounds.size;
|
|
return bounds;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override Bounds RebuildMesh(ProBuilderMesh mesh, Vector3 size, Quaternion rotation)
|
|
{
|
|
var meshSize = Math.Abs(rotation * size);
|
|
|
|
var xOuterRadius = Mathf.Clamp(meshSize.x /2f ,.01f, 2048f);
|
|
var yOuterRadius = Mathf.Clamp(meshSize.z /2f ,.01f, 2048f);
|
|
int clampedRows = Mathf.Clamp(m_Rows + 1, 4, 128);
|
|
int clampedColumns = Mathf.Clamp(m_Columns + 1, 4, 128);
|
|
float clampedTubeRadius = Mathf.Clamp(m_TubeRadius, .01f, Mathf.Min(xOuterRadius, yOuterRadius) - .001f);
|
|
|
|
xOuterRadius -= clampedTubeRadius;
|
|
yOuterRadius -= clampedTubeRadius;
|
|
float clampedHorizontalCircumference = Mathf.Clamp(m_HorizontalCircumference, .01f, 360f);
|
|
float clampedVerticalCircumference = Mathf.Clamp(m_VerticalCircumference, .01f, 360f);
|
|
|
|
List<Vector3> vertices = new List<Vector3>();
|
|
|
|
int col = clampedColumns - 1;
|
|
|
|
float clampedRadius = xOuterRadius;
|
|
Vector3[] cir = GetCirclePoints(clampedRows, clampedTubeRadius, clampedVerticalCircumference, Quaternion.Euler(0,0,0), clampedRadius);
|
|
|
|
Vector2 ellipseCoord;
|
|
for (int i = 1; i < clampedColumns; i++)
|
|
{
|
|
vertices.AddRange(cir);
|
|
float angle = (i / (float)col) * clampedHorizontalCircumference;
|
|
//Compute the coordinates of the current point
|
|
ellipseCoord = new Vector2( xOuterRadius * Mathf.Cos(Mathf.Deg2Rad * angle),
|
|
yOuterRadius * Mathf.Sin(Mathf.Deg2Rad * angle) );
|
|
|
|
//Compute the tangent direction to know how to orient the current slice
|
|
var tangent = new Vector2( -ellipseCoord.y / (yOuterRadius * yOuterRadius), ellipseCoord.x / (xOuterRadius * xOuterRadius));
|
|
Quaternion rot = Quaternion.Euler(Vector3.up * Vector2.SignedAngle(Vector2.up, tangent.normalized));
|
|
|
|
//Get the slice/circle that must be placed at this position
|
|
cir = GetCirclePoints(clampedRows, clampedTubeRadius, clampedVerticalCircumference, rot, new Vector3(ellipseCoord.x, 0, -ellipseCoord.y));
|
|
vertices.AddRange(cir);
|
|
}
|
|
|
|
List<Face> faces = new List<Face>();
|
|
int fc = 0;
|
|
|
|
// faces
|
|
for (int i = 0; i < (clampedColumns - 1) * 2; i += 2)
|
|
{
|
|
for (int n = 0; n < clampedRows - 1; n++)
|
|
{
|
|
int a = (i + 0) * ((clampedRows - 1) * 2) + (n * 2);
|
|
int b = (i + 1) * ((clampedRows - 1) * 2) + (n * 2);
|
|
|
|
int c = (i + 0) * ((clampedRows - 1) * 2) + (n * 2) + 1;
|
|
int d = (i + 1) * ((clampedRows - 1) * 2) + (n * 2) + 1;
|
|
|
|
faces.Add(new Face(new int[] { a, b, c, b, d, c }));
|
|
faces[fc].smoothingGroup = m_Smooth ? 1 : -1;
|
|
faces[fc].manualUV = true;
|
|
|
|
fc++;
|
|
}
|
|
}
|
|
|
|
for(int i = 0; i < vertices.Count; ++i)
|
|
vertices[i] = rotation * vertices[i];
|
|
|
|
mesh.RebuildWithPositionsAndFaces(vertices, faces);
|
|
|
|
mesh.TranslateVerticesInWorldSpace(mesh.mesh.triangles, mesh.transform.TransformDirection(-mesh.mesh.bounds.center));
|
|
mesh.Refresh();
|
|
|
|
UVEditing.ProjectFacesBox(mesh, mesh.facesInternal);
|
|
|
|
return UpdateBounds(mesh, size, rotation, new Bounds());
|
|
}
|
|
|
|
|
|
static Vector3[] GetCirclePoints(int segments, float radius, float circumference, Quaternion rotation, float offset)
|
|
{
|
|
float seg = (float)segments - 1;
|
|
|
|
Vector3[] v = new Vector3[(segments - 1) * 2];
|
|
v[0] = new Vector3(Mathf.Cos(((0f / seg) * circumference) * Mathf.Deg2Rad) * radius, Mathf.Sin(((0f / seg) * circumference) * Mathf.Deg2Rad) * radius, 0f);
|
|
v[1] = new Vector3(Mathf.Cos(((1f / seg) * circumference) * Mathf.Deg2Rad) * radius, Mathf.Sin(((1f / seg) * circumference) * Mathf.Deg2Rad) * radius, 0f);
|
|
|
|
v[0] = rotation * ((v[0] + Vector3.right * offset));
|
|
v[1] = rotation * ((v[1] + Vector3.right * offset));
|
|
|
|
int n = 2;
|
|
|
|
for (int i = 2; i < segments; i++)
|
|
{
|
|
float rad = ((i / seg) * circumference) * Mathf.Deg2Rad;
|
|
|
|
v[n + 0] = v[n - 1];
|
|
v[n + 1] = rotation * (new Vector3(Mathf.Cos(rad) * radius, Mathf.Sin(rad) * radius, 0f) + Vector3.right * offset);
|
|
|
|
n += 2;
|
|
}
|
|
|
|
return v;
|
|
}
|
|
|
|
static Vector3[] GetCirclePoints(int segments, float radius, float circumference, Quaternion rotation, Vector3 offset)
|
|
{
|
|
float seg = (float)segments - 1;
|
|
|
|
Vector3[] v = new Vector3[(segments - 1) * 2];
|
|
v[0] = new Vector3(Mathf.Cos(((0f / seg) * circumference) * Mathf.Deg2Rad) * radius, Mathf.Sin(((0f / seg) * circumference) * Mathf.Deg2Rad) * radius, 0f);
|
|
v[1] = new Vector3(Mathf.Cos(((1f / seg) * circumference) * Mathf.Deg2Rad) * radius, Mathf.Sin(((1f / seg) * circumference) * Mathf.Deg2Rad) * radius, 0f);
|
|
|
|
v[0] = rotation * v[0] + offset;
|
|
v[1] = rotation * v[1] + offset;
|
|
|
|
int n = 2;
|
|
|
|
for (int i = 2; i < segments; i++)
|
|
{
|
|
float rad = ((i / seg) * circumference) * Mathf.Deg2Rad;
|
|
|
|
v[n + 0] = v[n - 1];
|
|
v[n + 1] = rotation * new Vector3(Mathf.Cos(rad) * radius, Mathf.Sin(rad) * radius, 0f) + offset;
|
|
|
|
n += 2;
|
|
}
|
|
|
|
return v;
|
|
}
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
[CustomPropertyDrawer(typeof(Torus))]
|
|
public class TorusDrawer : PropertyDrawer
|
|
{
|
|
static bool s_foldoutEnabled = true;
|
|
|
|
const bool k_ToggleOnLabelClick = true;
|
|
|
|
static readonly GUIContent k_RowsContent = new GUIContent("Rows", L10n.Tr("Set the number of faces used to define the tube's circumference."));
|
|
static readonly GUIContent k_ColumnsContent = new GUIContent("Columns", L10n.Tr("Set the number of faces used to define the torus's circumference / tube's length."));
|
|
static readonly GUIContent k_RadiusContent = new GUIContent("Tube Radius", L10n.Tr("Set the tube's radius."));
|
|
static readonly GUIContent k_HorCircumferenceContent = new GUIContent("Hor. Circ.", L10n.Tr("Circumference of the torus in degrees."));
|
|
static readonly GUIContent k_VertCircumferenceContent = new GUIContent("Vert. Circ.", L10n.Tr("Circumference of the torus' inner pipe in degrees."));
|
|
static readonly GUIContent k_SmoothContent = new GUIContent("Smooth", L10n.Tr("Whether to smooth the edges of the torus."));
|
|
|
|
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
|
{
|
|
EditorGUI.BeginProperty(position, label, property);
|
|
|
|
s_foldoutEnabled = EditorGUI.Foldout(position, s_foldoutEnabled, "Torus Settings", k_ToggleOnLabelClick);
|
|
|
|
EditorGUI.indentLevel++;
|
|
|
|
if(s_foldoutEnabled)
|
|
{
|
|
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_TubeRadius"), k_RadiusContent);
|
|
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_Rows"), k_RowsContent);
|
|
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_Columns"), k_ColumnsContent);
|
|
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_HorizontalCircumference"), k_HorCircumferenceContent);
|
|
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_VerticalCircumference"), k_VertCircumferenceContent);
|
|
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_Smooth"), k_SmoothContent);
|
|
}
|
|
|
|
EditorGUI.indentLevel--;
|
|
EditorGUI.EndProperty();
|
|
}
|
|
}
|
|
#endif
|
|
}
|