TurnBasedStrategyCourse/Library/PackageCache/com.unity.probuilder@5.1.0/Runtime/Shapes/Stairs.cs

634 lines
25 KiB
C#

using UnityEditor;
namespace UnityEngine.ProBuilder.Shapes
{
/// <summary>
/// Describes how ProBuilder will construct the <see cref="Stairs" /> mesh.
/// </summary>
enum StepGenerationType
{
/// <summary>
/// Instructs ProBuilder to generate a predictable height for each step in the staircase.
/// This means that if you increase the height of the overall size of the staircase, the number of steps increases.
/// </summary>
Height,
/// <summary>
/// ProBuilder to generate a specific number of steps, regardless of any changes in the size of the staircase.
/// This means that if you increase the height of the overall size of the stairs, each step becomes higher.
/// </summary>
Count
}
/// <summary>
/// Represents a basic [stairs](../manual/Stairs.html) shape.
/// </summary>
[Shape("Stairs")]
public class Stairs : Shape
{
/// <summary>
/// Determines whether you want ProBuilder to build the same number of steps regardless of how the size of the stairs
/// changes (the default) or make each step the same height and automatically adapt the number of steps to match the stairs size.
///
/// The default value is to build the same number of steps.
/// </summary>
[SerializeField]
StepGenerationType m_StepGenerationType = StepGenerationType.Count;
/// <summary>
/// Sets the fixed height of each step on the stairs.
/// The default value is 0.2.
/// </summary>
/// <seealso cref="StepGenerationType.Count" />
[Min(0.01f)]
[SerializeField]
float m_StepsHeight = .2f;
/// <summary>
/// Sets the fixed number of steps that the stairs always has.
/// The default value is 10. Valid values range from 1 to 256.
/// </summary>
/// <seealso cref="StepGenerationType.Height" />
[Range(1, 256)]
[SerializeField]
int m_StepsCount = 10;
/// <summary>
/// Determines whether to force every step to be the exactly the same height. If disabled,
/// the height of the last step is smaller than the others depending on the remaining height.
/// This is enabled by default.
/// </summary>
/// <seealso cref="StepGenerationType.Height" />
[SerializeField]
bool m_HomogeneousSteps = true;
/// <summary>
/// Sets the degree of curvature on the stairs in degrees, where 0 makes straight stairs, 360 makes stairs
/// in a complete circle, and negative angles makes the stairs curve to the left while positive angles make
/// turns to the right. Remember that you might need to increase the number of stairs to compensate as you
/// increase this value.
///
/// The default value is 0. Valid values range from -360 to 360.
/// </summary>
[Range(-360, 360)]
[SerializeField]
float m_Circumference = 0f;
/// <summary>
/// Determines whether to draw polygons on the sides of the stairs.
/// This is enabled by default. You can disable this option if the sides of your stairs
/// are not visible to the camera (for example, if your stairs are built into a wall).
/// </summary>
[SerializeField]
bool m_Sides = true;
/// <summary>
/// Gets or sets whether to draw polygons on the sides of the stairs.
/// </summary>
public bool sides
{
get => m_Sides;
set => m_Sides = value;
}
[SerializeField, Min(0f)]
float m_InnerRadius;
/// <inheritdoc/>
public override void CopyShape(Shape shape)
{
if(shape is Stairs)
{
Stairs stairs = (Stairs) shape;
m_StepGenerationType = stairs.m_StepGenerationType;
m_StepsHeight = stairs.m_StepsHeight;
m_StepsCount = stairs.m_StepsCount;
m_HomogeneousSteps = stairs.m_HomogeneousSteps;
m_Circumference = stairs.m_Circumference;
m_Sides = stairs.m_Sides;
m_InnerRadius = stairs.m_InnerRadius;
}
}
/// <inheritdoc/>
public override Bounds RebuildMesh(ProBuilderMesh mesh, Vector3 size, Quaternion rotation)
{
if (Mathf.Abs(m_Circumference) > 0)
return BuildCurvedStairs(mesh, size, rotation);
else
return BuildStairs(mesh, size, rotation);
}
/// <inheritdoc/>
public override Bounds UpdateBounds(ProBuilderMesh mesh, Vector3 size, Quaternion rotation, Bounds bounds)
{
if (Mathf.Abs(m_Circumference) > 0)
{
bounds.center = mesh.mesh.bounds.center;
bounds.size = Vector3.Scale(Math.Sign(size),mesh.mesh.bounds.size);
}
else
{
bounds = mesh.mesh.bounds;
bounds.size = size;
}
return bounds;
}
Bounds BuildStairs(ProBuilderMesh mesh, Vector3 size, Quaternion rotation)
{
var upDir = Vector3.Scale(rotation * Vector3.up, size) ;
var rightDir = Vector3.Scale(rotation * Vector3.right, size) ;
var forwardDir = Vector3.Scale(rotation * Vector3.forward, size) ;
var meshSize = new Vector3(rightDir.magnitude, upDir.magnitude, forwardDir.magnitude);
var useStepHeight = m_StepGenerationType == StepGenerationType.Height;
var stairsHeight = meshSize.y;
var stepsHeight = Mathf.Min(m_StepsHeight, stairsHeight);
var steps = m_StepsCount;
if(useStepHeight)
{
if(stairsHeight > 0)
{
steps = (int) ( stairsHeight / stepsHeight );
if(m_HomogeneousSteps)
stepsHeight = stairsHeight / steps;
else
steps += ( ( stairsHeight / stepsHeight ) - steps ) > 0.001f ? 1 : 0;
}
else
steps = 1;
}
//Clamping max steps number
if(steps > 256)
{
steps = 256;
stepsHeight = stairsHeight / steps;
}
// 4 vertices per quad, 2 quads per step.
var vertices = new Vector3[4 * steps * 2];
var faces = new Face[steps * 2];
Vector3 extents = meshSize * .5f;
// vertex index, face index
int v = 0, t = 0;
float heightInc0, heightInc1, inc0, inc1;
float x0, x1, y0, y1, z0, z1;
for (int i = 0; i < steps; i++)
{
heightInc0 = i * stepsHeight;
heightInc1 = i != steps -1 ? (i + 1) * stepsHeight : meshSize.y;
inc0 = i / (float)steps;
inc1 = (i + 1) / (float)steps;
x0 = meshSize.x - extents.x;
x1 = 0 - extents.x;
y0 = (useStepHeight ? heightInc0 : meshSize.y * inc0) - extents.y;
y1 = (useStepHeight ? heightInc1 : meshSize.y * inc1) - extents.y;
z0 = meshSize.z * inc0 - extents.z;
z1 = meshSize.z * inc1 - extents.z;
vertices[v + 0] = new Vector3(x0, y0, z0);
vertices[v + 1] = new Vector3(x1, y0, z0);
vertices[v + 2] = new Vector3(x0, y1, z0);
vertices[v + 3] = new Vector3(x1, y1, z0);
vertices[v + 4] = new Vector3(x0, y1, z0);
vertices[v + 5] = new Vector3(x1, y1, z0);
vertices[v + 6] = new Vector3(x0, y1, z1);
vertices[v + 7] = new Vector3(x1, y1, z1);
faces[t + 0] = new Face(new int[] { v + 0,
v + 1,
v + 2,
v + 1,
v + 3,
v + 2 });
faces[t + 1] = new Face(new int[] { v + 4,
v + 5,
v + 6,
v + 5,
v + 7,
v + 6 });
v += 8;
t += 2;
}
// sides
if (sides)
{
// first step is special case - only needs a quad, but all other steps need
// a quad and tri.
float x = 0f;
for (int side = 0; side < 2; side++)
{
Vector3[] sides_v = new Vector3[steps * 4 + (steps - 1) * 3];
Face[] sides_f = new Face[steps + steps - 1];
int sv = 0, st = 0;
for (int i = 0; i < steps; i++)
{
heightInc0 = Mathf.Max(i, 1) * stepsHeight;
heightInc1 = i != steps-1 ? (i + 1) * stepsHeight : meshSize.y;
inc0 = Mathf.Max(i, 1) / (float)steps;
inc1 = (i + 1) / (float)steps;
y0 = useStepHeight ? heightInc0 : inc0 * meshSize.y;
y1 = useStepHeight ? heightInc1 : inc1 * meshSize.y;
inc0 = i / (float)steps;
z0 = inc0 * meshSize.z;
z1 = inc1 * meshSize.z;
sides_v[sv + 0] = new Vector3(x, 0f, z0) - extents;
sides_v[sv + 1] = new Vector3(x, 0f, z1) - extents;
sides_v[sv + 2] = new Vector3(x, y0, z0) - extents;
sides_v[sv + 3] = new Vector3(x, y1, z1) - extents;
sides_f[st++] = new Face(side % 2 == 0 ?
new int[] { v + 0, v + 1, v + 2, v + 1, v + 3, v + 2 } :
new int[] { v + 2, v + 1, v + 0, v + 2, v + 3, v + 1 });
sides_f[st - 1].textureGroup = side + 1;
v += 4;
sv += 4;
// that connecting triangle
if (i > 0)
{
sides_v[sv + 0] = new Vector3(x, y0, z0) - extents;
sides_v[sv + 1] = new Vector3(x, y1, z0) - extents;
sides_v[sv + 2] = new Vector3(x, y1, z1) - extents;
sides_f[st++] = new Face(side % 2 == 0 ?
new int[] { v + 2, v + 1, v + 0 } :
new int[] { v + 0, v + 1, v + 2 });
sides_f[st - 1].textureGroup = side + 1;
v += 3;
sv += 3;
}
}
vertices = vertices.Concat(sides_v);
faces = faces.Concat(sides_f);
x += meshSize.x;
}
// add that last back face
vertices = vertices.Concat(new Vector3[] {
new Vector3(0f, 0f, meshSize.z) - extents,
new Vector3(meshSize.x, 0f, meshSize.z) - extents,
new Vector3(0f, meshSize.y, meshSize.z) - extents,
new Vector3(meshSize.x, meshSize.y, meshSize.z) - extents
});
faces = faces.Add(new Face(new int[] { v + 0, v + 1, v + 2, v + 1, v + 3, v + 2 }));
}
var sizeSigns = Math.Sign(size);
for(int i = 0; i < vertices.Length; i++)
{
vertices[i] = rotation * vertices[i];
vertices[i].Scale(sizeSigns);
}
var sizeSign = sizeSigns.x * sizeSigns.y * sizeSigns.z;
if(sizeSign < 0)
{
foreach(var face in faces)
face.Reverse();
}
mesh.RebuildWithPositionsAndFaces(vertices, faces);
return UpdateBounds(mesh, size, rotation, new Bounds());
}
Bounds BuildCurvedStairs(ProBuilderMesh mesh, Vector3 size, Quaternion rotation)
{
var meshSize = Math.Abs(size);
var buildSides = m_Sides;
var maxWidth = Mathf.Min(meshSize.x, meshSize.z);
var innerRadius = Mathf.Clamp(m_InnerRadius, 0f, maxWidth - float.Epsilon);
var stairWidth = maxWidth - innerRadius;
var height = Mathf.Abs(meshSize.y);
var circumference = m_Circumference;
bool noInnerSide = innerRadius < Mathf.Epsilon;
bool useStepHeight = m_StepGenerationType == StepGenerationType.Height;
var stepsHeight = Mathf.Min(m_StepsHeight, height);
var steps = m_StepsCount;
if(useStepHeight && stepsHeight > 0.01f * m_StepsHeight)
{
if(height > 0)
{
steps = (int) ( height / m_StepsHeight );
if(m_HomogeneousSteps && steps > 0)
stepsHeight = height / steps;
else
steps += ( ( height / m_StepsHeight ) - steps ) > 0.001f ? 1 : 0;
}
else
steps = 1;
}
//Clamping max steps number
if(steps > 256)
{
steps = 256;
stepsHeight = height / steps;
}
// 4 vertices per quad, vertical step first, then floor step can be 3 or 4 verts depending on
// if the inner radius is 0 or not.
Vector3[] positions = new Vector3[(4 * steps) + ((noInnerSide ? 3 : 4) * steps)];
Face[] faces = new Face[steps * 2];
// vertex index, face index
int v = 0, t = 0;
float cir = Mathf.Abs(circumference) * Mathf.Deg2Rad;
float outerRadius = innerRadius + stairWidth;
for (int i = 0; i < steps; i++)
{
float inc0 = (i / (float)steps) * cir;
float inc1 = ((i + 1) / (float)steps) * cir;
float h0 = useStepHeight ? i * stepsHeight : ((i / (float)steps) * height);
float h1 = useStepHeight ? ((i != steps-1) ? ((i+1) * stepsHeight) : height) :( ((i + 1) / (float)steps) * height );
Vector3 v0 = new Vector3(-Mathf.Cos(inc0), 0f, Mathf.Sin(inc0));
Vector3 v1 = new Vector3(-Mathf.Cos(inc1), 0f, Mathf.Sin(inc1));
/*
*
* /6-----/7
* / /
* /5_____/4
* |3 |2
* | |
* |1_____|0
*
*/
positions[v + 0] = v0 * innerRadius;
positions[v + 1] = v0 * outerRadius;
positions[v + 2] = v0 * innerRadius;
positions[v + 3] = v0 * outerRadius;
positions[v + 0].y = h0;
positions[v + 1].y = h0;
positions[v + 2].y = h1;
positions[v + 3].y = h1;
positions[v + 4] = positions[v + 2];
positions[v + 5] = positions[v + 3];
positions[v + 6] = v1 * outerRadius;
positions[v + 6].y = h1;
if (!noInnerSide)
{
positions[v + 7] = v1 * innerRadius;
positions[v + 7].y = h1;
}
faces[t + 0] = new Face(new int[] {
v + 0,
v + 1,
v + 2,
v + 1,
v + 3,
v + 2
});
if (noInnerSide)
{
faces[t + 1] = new Face(new int[] {
v + 4,
v + 5,
v + 6
});
}
else
{
faces[t + 1] = new Face(new int[] {
v + 4,
v + 5,
v + 6,
v + 4,
v + 6,
v + 7
});
}
float uvRotation = ((inc1 + inc0) * -.5f) * Mathf.Rad2Deg;
uvRotation %= 360f;
if (uvRotation < 0f)
uvRotation = 360f + uvRotation;
var uv = faces[t + 1].uv;
uv.rotation = uvRotation;
faces[t + 1].uv = uv;
v += noInnerSide ? 7 : 8;
t += 2;
}
// sides
if (buildSides)
{
// first step is special case - only needs a quad, but all other steps need
// a quad and tri.
float x = noInnerSide ? innerRadius + stairWidth : innerRadius;
for (int side = (noInnerSide ? 1 : 0); side < 2; side++)
{
Vector3[] sides_v = new Vector3[steps * 4 + (steps - 1) * 3];
Face[] sides_f = new Face[steps + steps - 1];
int sv = 0, st = 0;
for (int i = 0; i < steps; i++)
{
float inc0 = (i / (float)steps) * cir;
float inc1 = ((i + 1) / (float)steps) * cir;
float h0 = useStepHeight ? Mathf.Max(i, 1) * stepsHeight : ((Mathf.Max(i, 1) / (float)steps) * height);
float h1 = useStepHeight ? (i != steps-1 ? (i + 1) * stepsHeight : meshSize.y) : (((i + 1) / (float)steps) * height);
Vector3 v0 = new Vector3(-Mathf.Cos(inc0), 0f, Mathf.Sin(inc0)) * x;
Vector3 v1 = new Vector3(-Mathf.Cos(inc1), 0f, Mathf.Sin(inc1)) * x;
sides_v[sv + 0] = v0;
sides_v[sv + 1] = v1;
sides_v[sv + 2] = v0;
sides_v[sv + 3] = v1;
sides_v[sv + 0].y = 0f;
sides_v[sv + 1].y = 0f;
sides_v[sv + 2].y = h0;
sides_v[sv + 3].y = h1;
sides_f[st++] = new Face(side % 2 == 0 ?
new int[] { v + 2, v + 1, v + 0, v + 2, v + 3, v + 1 } :
new int[] { v + 0, v + 1, v + 2, v + 1, v + 3, v + 2 });
sides_f[st - 1].smoothingGroup = side + 1;
v += 4;
sv += 4;
// that connecting triangle
if (i > 0)
{
sides_f[st - 1].textureGroup = (side * steps) + i;
sides_v[sv + 0] = v0;
sides_v[sv + 1] = v1;
sides_v[sv + 2] = v0;
sides_v[sv + 0].y = h0;
sides_v[sv + 1].y = h1;
sides_v[sv + 2].y = h1;
sides_f[st++] = new Face(side % 2 == 0 ?
new int[] { v + 2, v + 1, v + 0 } :
new int[] { v + 0, v + 1, v + 2 });
sides_f[st - 1].textureGroup = (side * steps) + i;
sides_f[st - 1].smoothingGroup = side + 1;
v += 3;
sv += 3;
}
}
positions = positions.Concat(sides_v);
faces = faces.Concat(sides_f);
x += stairWidth;
}
// // add that last back face
float cos = -Mathf.Cos(cir), sin = Mathf.Sin(cir);
positions = positions.Concat(new Vector3[]
{
new Vector3(cos, 0f, sin) * innerRadius,
new Vector3(cos, 0f, sin) * outerRadius,
new Vector3(cos * innerRadius, height, sin * innerRadius),
new Vector3(cos * outerRadius, height, sin * outerRadius)
});
faces = faces.Add(new Face(new int[] { v + 2, v + 1, v + 0, v + 2, v + 3, v + 1 }));
}
if (circumference < 0f)
{
Vector3 flip = new Vector3(-1f, 1f, 1f);
for (int i = 0; i < positions.Length; i++)
positions[i].Scale(flip);
foreach (Face f in faces)
f.Reverse();
}
var sizeSigns = Math.Sign(size);
for(int i = 0; i < positions.Length; i++)
{
positions[i] = rotation * positions[i];
positions[i].Scale(sizeSigns);
}
var sizeSign = sizeSigns.x * sizeSigns.y * sizeSigns.z;
if(sizeSign < 0)
{
foreach(var face in faces)
face.Reverse();
}
mesh.RebuildWithPositionsAndFaces(positions, faces);
mesh.TranslateVerticesInWorldSpace(mesh.mesh.triangles, mesh.transform.TransformDirection(-mesh.mesh.bounds.center));
mesh.Refresh();
return UpdateBounds(mesh, size, rotation, new Bounds());
}
}
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(Stairs))]
public class StairsDrawer : PropertyDrawer
{
static bool s_foldoutEnabled = true;
const bool k_ToggleOnLabelClick = true;
static readonly GUIContent k_StepGenerationContent = new GUIContent("Steps Generation", L10n.Tr("Whether to generate steps using the number of steps or by step height."));
static readonly GUIContent k_StepsCountContent = new GUIContent("Steps Count", L10n.Tr("Number of steps of the stair."));
static readonly GUIContent k_StepsHeightContent = new GUIContent("Steps Height", L10n.Tr("Height of each step of the generated stairs."));
static readonly GUIContent k_HomogeneousStepsContent = new GUIContent("Homogeneous Steps", L10n.Tr("Whether to round the step height to create homogenous steps."));
static readonly GUIContent k_CircumferenceContent = new GUIContent("Circumference", L10n.Tr("Circumference of the stairs. Use a negative number to rotate in the opposite direction."));
static readonly GUIContent k_SidesContent = new GUIContent("Sides", L10n.Tr("Whether to generate sides."));
static readonly GUIContent k_InnerRadius = new GUIContent("Inner Radius", L10n.Tr("In a curved stair-set, this defines the radius from center to the inner edge of the stair."));
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
s_foldoutEnabled = EditorGUI.Foldout(position, s_foldoutEnabled, "Stairs Settings", k_ToggleOnLabelClick);
EditorGUI.indentLevel++;
if(s_foldoutEnabled)
{
var typeProperty = property.FindPropertyRelative("m_StepGenerationType");
StepGenerationType typeEnum = (StepGenerationType)(typeProperty.intValue);
EditorGUI.BeginChangeCheck();
typeEnum = (StepGenerationType)EditorGUILayout.EnumPopup(k_StepGenerationContent, typeEnum);
if(EditorGUI.EndChangeCheck())
typeProperty.intValue = (int)typeEnum;
if(typeEnum == StepGenerationType.Count)
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_StepsCount"), k_StepsCountContent);
else
{
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_StepsHeight"), k_StepsHeightContent);
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_HomogeneousSteps"), k_HomogeneousStepsContent);
}
var circumference = property.FindPropertyRelative("m_Circumference");
var innerRadius = property.FindPropertyRelative("m_InnerRadius");
EditorGUILayout.PropertyField(circumference, k_CircumferenceContent);
EditorGUI.BeginDisabledGroup(Mathf.Abs(circumference.floatValue) < float.Epsilon);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(innerRadius, k_InnerRadius);
EditorGUI.indentLevel--;
EditorGUI.EndDisabledGroup();
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_Sides"), k_SidesContent);
}
EditorGUI.indentLevel--;
EditorGUI.EndProperty();
}
}
#endif
}