Added player character and step in/out of dialogue in scene 101
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEngine.ProBuilder.Shapes
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a basic [arch](../manual/Arch.html) shape.
|
||||
/// </summary>
|
||||
[Shape("Arch")]
|
||||
[System.Serializable]
|
||||
public class Arch : Shape
|
||||
{
|
||||
/// <summary>
|
||||
/// The thickness of the arch in meters. The larger the thickness, the smaller the opening becomes.
|
||||
/// The default value is 0.1. The minimum value is 0.01.
|
||||
/// </summary>
|
||||
[Min(0.01f)]
|
||||
[SerializeField]
|
||||
float m_Thickness = .1f;
|
||||
|
||||
/// <summary>
|
||||
/// The number of sides for the arch. The more sides you use (relative to the size of the Radius), the smoother the arch becomes.
|
||||
/// The default value is 5. Valid values range from 2 to 200.
|
||||
/// </summary>
|
||||
[Range(2, 200)]
|
||||
[SerializeField]
|
||||
int m_NumberOfSides = 5;
|
||||
|
||||
/// <summary>
|
||||
/// The circumference of the arch in degrees.
|
||||
/// The default value is 180. Valid values range from 1 to 360.
|
||||
/// </summary>
|
||||
[Range(1, 360)]
|
||||
[SerializeField]
|
||||
float m_ArchDegrees = 180;
|
||||
|
||||
/// <summary>
|
||||
/// True to create faces for the ends of the arch (default).
|
||||
/// You can set this value to false as an optimization strategy.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
bool m_EndCaps = true;
|
||||
|
||||
/// <summary>
|
||||
/// True to smooth the edges of the polygons (default).
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
bool m_Smooth = true;
|
||||
|
||||
internal override void SetParametersToBuiltInShape()
|
||||
{
|
||||
m_Thickness = 1f;
|
||||
m_NumberOfSides = 8;
|
||||
m_ArchDegrees = 180f;
|
||||
m_EndCaps = true;
|
||||
m_Smooth = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void CopyShape(Shape shape)
|
||||
{
|
||||
if(shape is Arch)
|
||||
{
|
||||
Arch arch = ( (Arch) shape );
|
||||
m_Thickness = arch.m_Thickness;
|
||||
m_NumberOfSides = arch.m_NumberOfSides;
|
||||
m_ArchDegrees = arch.m_ArchDegrees;
|
||||
m_EndCaps = arch.m_EndCaps;
|
||||
m_Smooth = arch.m_Smooth;
|
||||
}
|
||||
}
|
||||
|
||||
Vector3[] GetFace(Vector2 vertex1, Vector2 vertex2, float depth)
|
||||
{
|
||||
return new Vector3[4]
|
||||
{
|
||||
new Vector3(vertex1.x, vertex1.y, depth),
|
||||
new Vector3(vertex2.x, vertex2.y, depth),
|
||||
new Vector3(vertex1.x, vertex1.y, -depth),
|
||||
new Vector3(vertex2.x, vertex2.y, -depth)
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Bounds RebuildMesh(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 xRadius = rightDir.magnitude / 2f;
|
||||
var yRadius = upDir.magnitude;
|
||||
var depth = forwardDir.magnitude / 2f;
|
||||
|
||||
var radialCuts = m_NumberOfSides + 1;
|
||||
var angle = m_ArchDegrees;
|
||||
var templateOut = new Vector2[radialCuts];
|
||||
var templateIn = new Vector2[radialCuts];
|
||||
|
||||
if(angle < 90f)
|
||||
xRadius *= 2f;
|
||||
else if(angle < 180f)
|
||||
xRadius *= 1f+ Mathf.Lerp(1f, 0f, Mathf.Abs(Mathf.Cos(angle * Mathf.Deg2Rad)));
|
||||
else if(angle > 180f)
|
||||
yRadius /= 1f+ Mathf.Lerp(0f, 1f, (angle - 180f)/90f);
|
||||
|
||||
for (int i = 0; i < radialCuts; i++)
|
||||
{
|
||||
var currentAngle = i * ( angle / ( radialCuts - 1 ) );
|
||||
Vector2 tangent;
|
||||
templateOut[i] = Math.PointInEllipseCircumference(xRadius, yRadius, currentAngle, Vector2.zero, out tangent);
|
||||
templateIn[i] = Math.PointInEllipseCircumference(xRadius - m_Thickness, yRadius - m_Thickness, currentAngle, Vector2.zero, out tangent);
|
||||
}
|
||||
|
||||
List<Vector3> v = new List<Vector3>();
|
||||
|
||||
Vector2 tmp, tmp2, tmp3, tmp4;
|
||||
|
||||
float y = -depth;
|
||||
int smoothedFaceCount = 0;
|
||||
for (int n = 0; n < radialCuts - 1; n++)
|
||||
{
|
||||
// outside faces
|
||||
tmp = templateOut[n];
|
||||
tmp2 = n < (radialCuts - 1) ? templateOut[n + 1] : templateOut[n];
|
||||
|
||||
Vector3[] qvo = GetFace(tmp, tmp2, -depth);
|
||||
|
||||
// inside faces
|
||||
tmp = templateIn[n];
|
||||
tmp2 = n < (radialCuts - 1) ? templateIn[n + 1] : templateIn[n];
|
||||
|
||||
Vector3[] qvi = GetFace(tmp2, tmp, -depth);
|
||||
|
||||
// left side bottom face
|
||||
if(angle < 360f && m_EndCaps)
|
||||
{
|
||||
if(n == 0)
|
||||
v.AddRange(GetFace(templateOut[n], templateIn[n], depth));
|
||||
}
|
||||
|
||||
v.AddRange(qvo);
|
||||
v.AddRange(qvi);
|
||||
smoothedFaceCount += 2;
|
||||
|
||||
if(angle < 360f && m_EndCaps)
|
||||
{
|
||||
// right side bottom face
|
||||
if (n == radialCuts - 2)
|
||||
v.AddRange(GetFace(templateIn[n+1], templateOut[n+1], depth));
|
||||
}
|
||||
}
|
||||
|
||||
// build front and back faces
|
||||
for (int i = 0; i < radialCuts - 1; i++)
|
||||
{
|
||||
tmp = templateOut[i];
|
||||
tmp2 = (i < radialCuts - 1) ? templateOut[i + 1] : templateOut[i];
|
||||
tmp3 = templateIn[i];
|
||||
tmp4 = (i < radialCuts - 1) ? templateIn[i + 1] : templateIn[i];
|
||||
|
||||
// front
|
||||
Vector3[] tpb = new Vector3[4]
|
||||
{
|
||||
new Vector3(tmp.x, tmp.y, depth),
|
||||
new Vector3(tmp2.x, tmp2.y, depth),
|
||||
new Vector3(tmp3.x, tmp3.y, depth),
|
||||
new Vector3(tmp4.x, tmp4.y, depth),
|
||||
};
|
||||
|
||||
// back
|
||||
Vector3[] tpt = new Vector3[4]
|
||||
{
|
||||
new Vector3(tmp2.x, tmp2.y, y),
|
||||
new Vector3(tmp.x, tmp.y, y),
|
||||
new Vector3(tmp4.x, tmp4.y, y),
|
||||
new Vector3(tmp3.x, tmp3.y, y)
|
||||
};
|
||||
|
||||
v.AddRange(tpb);
|
||||
v.AddRange(tpt);
|
||||
}
|
||||
|
||||
var sizeSigns = Math.Sign(size);
|
||||
for(int i = 0; i < v.Count; i++)
|
||||
v[i] = Vector3.Scale(rotation * v[i], sizeSigns);
|
||||
|
||||
mesh.GeometryWithPoints(v.ToArray());
|
||||
|
||||
if(m_Smooth)
|
||||
{
|
||||
for(int i = ( angle < 360f && m_EndCaps ) ? 1 : 0; i < smoothedFaceCount; i++)
|
||||
mesh.facesInternal[i].smoothingGroup = 1;
|
||||
}
|
||||
|
||||
var sizeSign = sizeSigns.x * sizeSigns.y * sizeSigns.z;
|
||||
if(sizeSign < 0)
|
||||
{
|
||||
var faces = mesh.facesInternal;
|
||||
foreach(var face in faces)
|
||||
face.Reverse();
|
||||
}
|
||||
|
||||
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(Arch))]
|
||||
public class ArchDrawer : PropertyDrawer
|
||||
{
|
||||
static bool s_foldoutEnabled = true;
|
||||
|
||||
const bool k_ToggleOnLabelClick = true;
|
||||
|
||||
static readonly GUIContent k_ThicknessContent = new GUIContent("Thickness", L10n.Tr("Thickness of the arch borders. Larger value creates a smaller opening."));
|
||||
static readonly GUIContent k_SidesContent = new GUIContent("Sides Count", L10n.Tr("Number of sides of the arch."));
|
||||
static readonly GUIContent k_CircumferenceContent = new GUIContent("Arch Circ.", L10n.Tr("Circumference of the arch in degrees."));
|
||||
static readonly GUIContent k_EndCapsContent = new GUIContent("End Caps", L10n.Tr("Whether to generate faces for the ends of the arch."));
|
||||
static readonly GUIContent k_SmoothContent = new GUIContent("Smooth", L10n.Tr("Whether to smooth the edges of the arch."));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
EditorGUI.BeginProperty(position, label, property);
|
||||
|
||||
s_foldoutEnabled = EditorGUI.Foldout(position, s_foldoutEnabled, "Arch Settings", k_ToggleOnLabelClick);
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
if(s_foldoutEnabled)
|
||||
{
|
||||
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_Thickness"), k_ThicknessContent);
|
||||
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_NumberOfSides"), k_SidesContent);
|
||||
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_ArchDegrees"), k_CircumferenceContent);
|
||||
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_EndCaps"), k_EndCapsContent);
|
||||
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_Smooth"), k_SmoothContent);
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cd9c9830c34380541ba162a9751bfb4e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,179 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEngine.ProBuilder.Shapes
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a basic [cone](../manual/Cone.html) shape.
|
||||
/// </summary>
|
||||
[Shape("Cone")]
|
||||
[System.Serializable]
|
||||
public class Cone : Shape
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the number of sides for the cone. The more sides you use, the smoother the sides of the cone become.
|
||||
/// The default value is 6. Valid values range from 3 to 64.
|
||||
/// </summary>
|
||||
[Range(3,64)]
|
||||
[SerializeField]
|
||||
internal int m_NumberOfSides = 6;
|
||||
|
||||
float m_Radius = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether to smooth the edges of the polygons.
|
||||
/// This is enabled by default.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
bool m_Smooth = true;
|
||||
|
||||
internal override void SetParametersToBuiltInShape()
|
||||
{
|
||||
m_NumberOfSides = 8;
|
||||
m_Radius = 0.5f;
|
||||
m_Smooth = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void CopyShape(Shape shape)
|
||||
{
|
||||
if(shape is Cone)
|
||||
{
|
||||
Cone cone = (Cone) shape;
|
||||
m_NumberOfSides = cone.m_NumberOfSides;
|
||||
m_Radius = cone.m_Radius;
|
||||
m_Smooth = cone.m_Smooth;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Bounds UpdateBounds(ProBuilderMesh mesh, Vector3 size, Quaternion rotation, Bounds bounds)
|
||||
{
|
||||
var upLocalAxis = rotation * Vector3.up;
|
||||
upLocalAxis = Math.Abs(upLocalAxis);
|
||||
|
||||
Vector3 boxSize = mesh.mesh.bounds.size;
|
||||
boxSize.x = Mathf.Lerp(m_Radius * 2f, boxSize.x, upLocalAxis.x);
|
||||
boxSize.y = Mathf.Lerp(m_Radius * 2f, boxSize.y, upLocalAxis.y);
|
||||
boxSize.z = Mathf.Lerp(m_Radius * 2f, boxSize.z, upLocalAxis.z);
|
||||
bounds.size = boxSize;
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Bounds RebuildMesh(ProBuilderMesh mesh, Vector3 size, Quaternion rotation)
|
||||
{
|
||||
var meshSize = Math.Abs(size);
|
||||
|
||||
m_Radius = System.Math.Min(meshSize.x, meshSize.z);
|
||||
var height = meshSize.y;
|
||||
|
||||
var subdivAxis = m_NumberOfSides;
|
||||
// template is outer ring - radius refers to outer ring always
|
||||
Vector3[] template = new Vector3[subdivAxis];
|
||||
|
||||
for (int i = 0; i < subdivAxis; i++)
|
||||
{
|
||||
Vector2 ct = Math.PointInCircumference(m_Radius, i * (360f / subdivAxis), Vector2.zero);
|
||||
template[i] = new Vector3(ct.x, -height / 2f, ct.y);
|
||||
}
|
||||
|
||||
List<Vector3> v = new List<Vector3>();
|
||||
List<Face> f = new List<Face>();
|
||||
|
||||
// build sides
|
||||
for (int i = 0; i < subdivAxis; i++)
|
||||
{
|
||||
// side face
|
||||
v.Add(template[i]);
|
||||
v.Add((i < subdivAxis - 1) ? template[i + 1] : template[0]);
|
||||
v.Add(Vector3.up * height / 2f);
|
||||
|
||||
// bottom face
|
||||
v.Add(template[i]);
|
||||
v.Add((i < subdivAxis - 1) ? template[i + 1] : template[0]);
|
||||
v.Add(Vector3.down * height / 2f);
|
||||
}
|
||||
|
||||
List<Face> sideFaces = new List<Face>();
|
||||
for (int i = 0; i < subdivAxis * 6; i += 6)
|
||||
{
|
||||
Face face = new Face(new int[3] { i + 2, i + 1, i + 0 });
|
||||
face.smoothingGroup = m_Smooth ? 1 : 0;
|
||||
f.Add(face);
|
||||
sideFaces.Add(face);
|
||||
f.Add(new Face(new int[3] { i + 3, i + 4, i + 5 }));
|
||||
}
|
||||
|
||||
var sizeSigns = Math.Sign(size);
|
||||
for(int i = 0; i < v.Count; i++)
|
||||
v[i] = Vector3.Scale(rotation * v[i], sizeSigns);
|
||||
|
||||
var sizeSign = Mathf.Sign(size.x) * Mathf.Sign(size.y) * Mathf.Sign(size.z);
|
||||
if(sizeSign < 0)
|
||||
{
|
||||
foreach(var face in f)
|
||||
face.Reverse();
|
||||
}
|
||||
|
||||
mesh.RebuildWithPositionsAndFaces(v, f);
|
||||
|
||||
mesh.unwrapParameters = new UnwrapParameters()
|
||||
{
|
||||
packMargin = 30f
|
||||
};
|
||||
|
||||
// Set the UVs manually for the side faces, so that they are uniform.
|
||||
// Calculate the UVs for the first face, then set the others to the same.
|
||||
var firstFace = sideFaces[0];
|
||||
var uv = firstFace.uv;
|
||||
uv.anchor = AutoUnwrapSettings.Anchor.LowerLeft;
|
||||
firstFace.uv = uv;
|
||||
firstFace.manualUV = true;
|
||||
// Always use up vector for projection of side faces.
|
||||
// Otherwise the lines in the PB texture end up crooked.
|
||||
UvUnwrapping.Unwrap(mesh, firstFace, projection: Vector3.up);
|
||||
for (int i = 1; i < sideFaces.Count; i++)
|
||||
{
|
||||
var sideFace = sideFaces[i];
|
||||
sideFace.manualUV = true;
|
||||
UvUnwrapping.CopyUVs(mesh, firstFace, sideFace);
|
||||
}
|
||||
mesh.RefreshUV(sideFaces);
|
||||
|
||||
return UpdateBounds(mesh, size, rotation, new Bounds());
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomPropertyDrawer(typeof(Cone))]
|
||||
public class ConeDrawer : PropertyDrawer
|
||||
{
|
||||
static bool s_foldoutEnabled = true;
|
||||
|
||||
const bool k_ToggleOnLabelClick = true;
|
||||
|
||||
static readonly GUIContent k_SidesContent = new GUIContent("Sides Count", L10n.Tr("Number of sides of the cone."));
|
||||
static readonly GUIContent k_SmoothContent = new GUIContent("Smooth", L10n.Tr("Whether to smooth the edges of the arch."));
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
EditorGUI.BeginProperty(position, label, property);
|
||||
|
||||
s_foldoutEnabled = EditorGUI.Foldout(position, s_foldoutEnabled, "Cone Settings", k_ToggleOnLabelClick);
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
if(s_foldoutEnabled)
|
||||
{
|
||||
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_NumberOfSides"), k_SidesContent);
|
||||
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_Smooth"), k_SmoothContent);
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5ef681a72ba0a044aa8da69030116d54
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,72 @@
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEngine.ProBuilder.Shapes
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a basic [cube](../manual/Cube.html) shape.
|
||||
/// </summary>
|
||||
[Shape("Cube")]
|
||||
[System.Serializable]
|
||||
public class Cube : Shape
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a set of 8 vertices that forms the template for a cube mesh.
|
||||
/// </summary>
|
||||
static readonly Vector3[] k_CubeVertices = new Vector3[]
|
||||
{
|
||||
// bottom 4 verts
|
||||
new Vector3(-.5f, -.5f, .5f), // 0
|
||||
new Vector3(.5f, -.5f, .5f), // 1
|
||||
new Vector3(.5f, -.5f, -.5f), // 2
|
||||
new Vector3(-.5f, -.5f, -.5f), // 3
|
||||
|
||||
// top 4 verts
|
||||
new Vector3(-.5f, .5f, .5f), // 4
|
||||
new Vector3(.5f, .5f, .5f), // 5
|
||||
new Vector3(.5f, .5f, -.5f), // 6
|
||||
new Vector3(-.5f, .5f, -.5f) // 7
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Defines a set of triangles forming a cube with reference to the k_CubeVertices array.
|
||||
/// </summary>
|
||||
static readonly int[] k_CubeTriangles = new int[] {
|
||||
0, 1, 4, 5, 1, 2, 5, 6, 2, 3, 6, 7, 3, 0, 7, 4, 4, 5, 7, 6, 3, 2, 0, 1
|
||||
};
|
||||
|
||||
internal override void SetParametersToBuiltInShape() { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void CopyShape(Shape shape) {}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Bounds RebuildMesh(ProBuilderMesh mesh, Vector3 size, Quaternion rotation)
|
||||
{
|
||||
mesh.Clear();
|
||||
|
||||
Vector3[] points = new Vector3[k_CubeTriangles.Length];
|
||||
|
||||
for (int i = 0; i < k_CubeTriangles.Length; i++)
|
||||
points[i] = rotation * Vector3.Scale(k_CubeVertices[k_CubeTriangles[i]], Math.Abs(size));
|
||||
|
||||
mesh.GeometryWithPoints(points);
|
||||
|
||||
UvUnwrapping.SetAutoUV(mesh, mesh.facesInternal, true);
|
||||
foreach (var face in mesh.facesInternal)
|
||||
face.uv = new AutoUnwrapSettings(face.uv) { anchor = AutoUnwrapSettings.Anchor.UpperLeft};
|
||||
mesh.RefreshUV(mesh.faces);
|
||||
|
||||
return mesh.mesh.bounds;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomPropertyDrawer(typeof(Cube))]
|
||||
public class CubeDrawer : PropertyDrawer
|
||||
{
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1356ec15a14344c2f92417f250ccceae
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,213 @@
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEngine.ProBuilder.Shapes
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a basic [cylinder](../manual/Cylinder.html) shape.
|
||||
/// </summary>
|
||||
[Shape("Cylinder")]
|
||||
[System.Serializable]
|
||||
public class Cylinder : Shape
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the number of sides for the cylinder. The more sides you use, the smoother the sides of the cylinder become.
|
||||
/// The default value is 6. Valid values range from 4 to 64.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
[Range(3, 64)]
|
||||
int m_AxisDivisions = 6;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the number of divisions to use for the height of the cylinder.
|
||||
/// The default value is 0.
|
||||
/// </summary>
|
||||
[Min(0)]
|
||||
[SerializeField]
|
||||
int m_HeightCuts = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether to smooth the edges of the polygons.
|
||||
/// This property is enabled by default.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
bool m_Smooth = true;
|
||||
|
||||
internal override void SetParametersToBuiltInShape()
|
||||
{
|
||||
m_AxisDivisions = 8;
|
||||
m_HeightCuts = 2;
|
||||
m_Smooth = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void CopyShape(Shape shape)
|
||||
{
|
||||
if(shape is Cylinder)
|
||||
{
|
||||
m_AxisDivisions = ((Cylinder)shape).m_AxisDivisions;
|
||||
m_HeightCuts = ((Cylinder)shape).m_HeightCuts;
|
||||
m_Smooth = ((Cylinder)shape).m_Smooth;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Bounds UpdateBounds(ProBuilderMesh mesh, Vector3 size, Quaternion rotation, Bounds bounds)
|
||||
{
|
||||
bounds.size = size;
|
||||
return bounds;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Bounds RebuildMesh(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 height = upDir.magnitude;
|
||||
var xRadius = rightDir.magnitude / 2f;
|
||||
var zRadius = forwardDir.magnitude / 2f;
|
||||
|
||||
float heightStep = height / (m_HeightCuts + 1);
|
||||
Vector2[] circle = new Vector2[m_AxisDivisions];
|
||||
|
||||
// get a circle
|
||||
for (int i = 0; i < m_AxisDivisions; i++)
|
||||
{
|
||||
float angle = i * 360f / m_AxisDivisions;
|
||||
circle[i] = Math.PointInEllipseCircumference(xRadius, zRadius, angle, Vector2.zero, out _);
|
||||
}
|
||||
|
||||
// add two because end caps
|
||||
Vector3[] vertices = new Vector3[(m_AxisDivisions * (m_HeightCuts + 1) * 4) + (m_AxisDivisions * 6)];
|
||||
Face[] faces = new Face[m_AxisDivisions * (m_HeightCuts + 1) + (m_AxisDivisions * 2)];
|
||||
|
||||
// build vertex array
|
||||
int it = 0;
|
||||
|
||||
// +1 to account for 0 height cuts
|
||||
for (int i = 0; i < m_HeightCuts + 1; i++)
|
||||
{
|
||||
float Y = i * heightStep - height * .5f;
|
||||
float Y2 = (i + 1) * heightStep - height * .5f;
|
||||
|
||||
for (int n = 0; n < m_AxisDivisions; n++)
|
||||
{
|
||||
vertices[it + 0] = new Vector3(circle[n + 0].x, Y, circle[n + 0].y);
|
||||
vertices[it + 1] = new Vector3(circle[n + 0].x, Y2, circle[n + 0].y);
|
||||
|
||||
if (n != m_AxisDivisions - 1)
|
||||
{
|
||||
vertices[it + 2] = new Vector3(circle[n + 1].x, Y, circle[n + 1].y);
|
||||
vertices[it + 3] = new Vector3(circle[n + 1].x, Y2, circle[n + 1].y);
|
||||
}
|
||||
else
|
||||
{
|
||||
vertices[it + 2] = new Vector3(circle[0].x, Y, circle[0].y);
|
||||
vertices[it + 3] = new Vector3(circle[0].x, Y2, circle[0].y);
|
||||
}
|
||||
|
||||
it += 4;
|
||||
}
|
||||
}
|
||||
|
||||
// wind side faces
|
||||
int f = 0;
|
||||
for (int i = 0; i < m_HeightCuts + 1; i++)
|
||||
{
|
||||
for (int n = 0; n < m_AxisDivisions * 4; n += 4)
|
||||
{
|
||||
int index = (i * (m_AxisDivisions * 4)) + n;
|
||||
int zero = index;
|
||||
int one = index + 1;
|
||||
int two = index + 2;
|
||||
int three = index + 3;
|
||||
|
||||
faces[f++] = new Face(
|
||||
new int[6] { zero, one, two, one, three, two },
|
||||
0,
|
||||
AutoUnwrapSettings.tile,
|
||||
m_Smooth ? 1 : -1,
|
||||
-1,
|
||||
-1,
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
// construct caps separately, cause they aren't wound the same way
|
||||
int ind = (m_AxisDivisions * (m_HeightCuts + 1) * 4);
|
||||
int f_ind = m_AxisDivisions * (m_HeightCuts + 1);
|
||||
|
||||
for (int n = 0; n < m_AxisDivisions; n++)
|
||||
{
|
||||
// bottom faces
|
||||
var bottomCapHeight = -height * .5f;
|
||||
vertices[ind + 0] = new Vector3(circle[n].x, bottomCapHeight, circle[n].y);
|
||||
|
||||
vertices[ind + 1] = new Vector3(0f, bottomCapHeight, 0f);
|
||||
|
||||
if (n != m_AxisDivisions - 1)
|
||||
vertices[ind + 2] = new Vector3(circle[n + 1].x, bottomCapHeight, circle[n + 1].y);
|
||||
else
|
||||
vertices[ind + 2] = new Vector3(circle[000].x, bottomCapHeight, circle[000].y);
|
||||
|
||||
faces[f_ind + n] = new Face(new int[3] { ind + 2, ind + 1, ind + 0 });
|
||||
|
||||
ind += 3;
|
||||
|
||||
// top faces
|
||||
var topCapHeight = height * .5f;
|
||||
vertices[ind + 0] = new Vector3(circle[n].x, topCapHeight, circle[n].y);
|
||||
vertices[ind + 1] = new Vector3(0f, topCapHeight, 0f);
|
||||
if (n != m_AxisDivisions - 1)
|
||||
vertices[ind + 2] = new Vector3(circle[n + 1].x, topCapHeight, circle[n + 1].y);
|
||||
else
|
||||
vertices[ind + 2] = new Vector3(circle[000].x, topCapHeight, circle[000].y);
|
||||
|
||||
faces[f_ind + (n + m_AxisDivisions)] = new Face(new int[3] { ind + 0, ind + 1, ind + 2 });
|
||||
|
||||
ind += 3;
|
||||
}
|
||||
|
||||
for(int i = 0; i < vertices.Length; i++)
|
||||
vertices[i] = rotation * vertices[i];
|
||||
|
||||
mesh.RebuildWithPositionsAndFaces(vertices, faces);
|
||||
|
||||
return UpdateBounds(mesh, size, rotation, new Bounds());
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomPropertyDrawer(typeof(Cylinder))]
|
||||
public class CylinderDrawer : PropertyDrawer
|
||||
{
|
||||
static bool s_foldoutEnabled = true;
|
||||
|
||||
const bool k_ToggleOnLabelClick = true;
|
||||
|
||||
static readonly GUIContent k_SidesContent = new GUIContent("Sides Count", L10n.Tr("Number of sides of the cylinder."));
|
||||
static readonly GUIContent k_HeightCutsContent = new GUIContent("Height Cuts", L10n.Tr("Number of divisions in the cylinder height."));
|
||||
static readonly GUIContent k_SmoothContent = new GUIContent("Smooth", L10n.Tr("Whether to smooth the edges of the cylinder."));
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
EditorGUI.BeginProperty(position, label, property);
|
||||
|
||||
s_foldoutEnabled = EditorGUI.Foldout(position, s_foldoutEnabled, "Cylinder Settings", k_ToggleOnLabelClick);
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
if(s_foldoutEnabled)
|
||||
{
|
||||
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_AxisDivisions"), k_SidesContent);
|
||||
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_HeightCuts"), k_HeightCutsContent);
|
||||
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_Smooth"), k_SmoothContent);
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3ee68c576d0d746729c62f6561e44b27
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,185 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEngine.ProBuilder.Shapes
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a basic [door](../manual/Door.html) shape.
|
||||
/// </summary>
|
||||
[Shape("Door")]
|
||||
[System.Serializable]
|
||||
public class Door : Shape
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the height of the top of the door frame in meters.
|
||||
/// The default value is 0.5. The minimum value is 0.01.
|
||||
/// </summary>
|
||||
[Min(0.01f)]
|
||||
[SerializeField]
|
||||
float m_DoorHeight = .5f;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the width of the door frame on the sides in meters.
|
||||
/// The default value is 0.75. The minimum value is 0.01.
|
||||
/// </summary>
|
||||
[Min(0.01f)]
|
||||
[SerializeField]
|
||||
float m_LegWidth = .75f;
|
||||
|
||||
internal override void SetParametersToBuiltInShape()
|
||||
{
|
||||
m_DoorHeight = 0.5f;
|
||||
m_LegWidth = 0.75f;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void CopyShape(Shape shape)
|
||||
{
|
||||
if(shape is Door)
|
||||
{
|
||||
m_DoorHeight = ( (Door) shape ).m_DoorHeight;
|
||||
m_LegWidth = ( (Door) shape ).m_LegWidth;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Bounds RebuildMesh(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) ;
|
||||
|
||||
float totalWidth = rightDir.magnitude;
|
||||
float totalHeight = upDir.magnitude;
|
||||
float depth = forwardDir.magnitude;
|
||||
|
||||
float xLegCoord = totalWidth / 2f;
|
||||
var legWidth = xLegCoord - m_LegWidth > 0 ? xLegCoord - m_LegWidth : 0.001f;
|
||||
var ledgeHeight = (totalHeight - m_DoorHeight * 2f) > 0 ? totalHeight - m_DoorHeight * 2f : 0.001f;
|
||||
|
||||
var baseY = -totalHeight;
|
||||
var front = depth / 2f;
|
||||
// 8---9---10--11
|
||||
// | |
|
||||
// 4 5---6 7
|
||||
// | | | |
|
||||
// 0 1 2 3
|
||||
Vector3[] template = new Vector3[12]
|
||||
{
|
||||
new Vector3(-xLegCoord, baseY, front), // 0
|
||||
new Vector3(-legWidth, baseY, front), // 1
|
||||
new Vector3(legWidth, baseY, front), // 2
|
||||
new Vector3(xLegCoord, baseY, front), // 3
|
||||
new Vector3(-xLegCoord, ledgeHeight, front), // 4
|
||||
new Vector3(-legWidth, ledgeHeight, front), // 5
|
||||
new Vector3(legWidth, ledgeHeight, front), // 6
|
||||
new Vector3(xLegCoord, ledgeHeight, front), // 7
|
||||
new Vector3(-xLegCoord, totalHeight, front), // 8
|
||||
new Vector3(-legWidth, totalHeight, front), // 9
|
||||
new Vector3(legWidth, totalHeight, front), // 10
|
||||
new Vector3(xLegCoord, totalHeight, front) // 11
|
||||
};
|
||||
|
||||
List<Vector3> points = new List<Vector3>();
|
||||
|
||||
points.Add(template[4]);
|
||||
points.Add(template[0]);
|
||||
points.Add(template[5]);
|
||||
points.Add(template[1]);
|
||||
|
||||
points.Add(template[2]);
|
||||
points.Add(template[3]);
|
||||
points.Add(template[6]);
|
||||
points.Add(template[7]);
|
||||
|
||||
points.Add(template[4]);
|
||||
points.Add(template[5]);
|
||||
points.Add(template[8]);
|
||||
points.Add(template[9]);
|
||||
|
||||
points.Add(template[10]);
|
||||
points.Add(template[6]);
|
||||
points.Add(template[11]);
|
||||
points.Add(template[7]);
|
||||
|
||||
points.Add(template[5]);
|
||||
points.Add(template[6]);
|
||||
points.Add(template[9]);
|
||||
points.Add(template[10]);
|
||||
|
||||
List<Vector3> reverse = new List<Vector3>();
|
||||
|
||||
for (int i = 0; i < points.Count; i += 4)
|
||||
{
|
||||
reverse.Add(points[i + 0] - Vector3.forward * depth);
|
||||
reverse.Add(points[i + 2] - Vector3.forward * depth);
|
||||
reverse.Add(points[i + 1] - Vector3.forward * depth);
|
||||
reverse.Add(points[i + 3] - Vector3.forward * depth);
|
||||
}
|
||||
|
||||
points.AddRange(reverse);
|
||||
|
||||
points.Add(template[6]);
|
||||
points.Add(template[5]);
|
||||
points.Add(template[6] - Vector3.forward * depth);
|
||||
points.Add(template[5] - Vector3.forward * depth);
|
||||
|
||||
points.Add(template[2] - Vector3.forward * depth);
|
||||
points.Add(template[2]);
|
||||
points.Add(template[6] - Vector3.forward * depth);
|
||||
points.Add(template[6]);
|
||||
|
||||
points.Add(template[1]);
|
||||
points.Add(template[1] - Vector3.forward * depth);
|
||||
points.Add(template[5]);
|
||||
points.Add(template[5] - Vector3.forward * depth);
|
||||
|
||||
var sizeSigns = Math.Sign(size);
|
||||
for(int i = 0; i < points.Count; i++)
|
||||
points[i] = Vector3.Scale(rotation * points[i], sizeSigns);
|
||||
|
||||
mesh.GeometryWithPoints(points.ToArray());
|
||||
|
||||
var sizeSign = sizeSigns.x * sizeSigns.y * sizeSigns.z;
|
||||
if(sizeSign < 0)
|
||||
{
|
||||
var faces = mesh.facesInternal;
|
||||
foreach(var face in faces)
|
||||
face.Reverse();
|
||||
}
|
||||
|
||||
return mesh.mesh.bounds;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomPropertyDrawer(typeof(Door))]
|
||||
public class DoorDrawer : PropertyDrawer
|
||||
{
|
||||
static bool s_foldoutEnabled = true;
|
||||
|
||||
const bool k_ToggleOnLabelClick = true;
|
||||
|
||||
static readonly GUIContent k_HeightContent = new GUIContent("Pediment Height", L10n.Tr("Set the height of the door's top."));
|
||||
static readonly GUIContent k_SideContent = new GUIContent("Side Width", L10n.Tr("Set the width of the door's sides."));
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
EditorGUI.BeginProperty(position, label, property);
|
||||
|
||||
s_foldoutEnabled = EditorGUI.Foldout(position, s_foldoutEnabled, "Door Settings", k_ToggleOnLabelClick);
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
if(s_foldoutEnabled)
|
||||
{
|
||||
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_DoorHeight"), k_HeightContent);
|
||||
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_LegWidth"), k_SideContent);
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 945d633938915ec42a2e6da38708d8b3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,219 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEngine.ProBuilder.Shapes
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a basic [pipe](../manual/Pipe.html) shape.
|
||||
/// </summary>
|
||||
[Shape("Pipe")]
|
||||
[System.Serializable]
|
||||
public class Pipe : Shape
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the thickness of the walls of the pipe in meters. The thicker the value, the smaller the hole becomes.
|
||||
/// The default value is 0.25. The minimum value is 0.01.
|
||||
/// </summary>
|
||||
[Min(0.01f)]
|
||||
[SerializeField]
|
||||
float m_Thickness = .25f;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the number of sides for the pipe. The more sides you use, the smoother the sides of the pipe become.
|
||||
/// The default value is 6. Valid values range from 3 to 64.
|
||||
/// </summary>
|
||||
[Range(3, 64)]
|
||||
[SerializeField]
|
||||
int m_NumberOfSides = 6;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the number of divisions to use for the height of the pipe.
|
||||
/// The default value is 0. Valid values range from 0 to 31.
|
||||
/// </summary>
|
||||
[Range(0, 31)]
|
||||
[SerializeField]
|
||||
int m_HeightCuts = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether to smooth the edges of the polygons.
|
||||
/// This property is enabled by default.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
bool m_Smooth = true;
|
||||
|
||||
internal override void SetParametersToBuiltInShape()
|
||||
{
|
||||
m_Thickness = 0.25f;
|
||||
m_NumberOfSides = 8;
|
||||
m_HeightCuts = 2;
|
||||
m_Smooth = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void CopyShape(Shape shape)
|
||||
{
|
||||
if(shape is Pipe)
|
||||
{
|
||||
Pipe pipe = (Pipe) shape;
|
||||
m_Thickness = pipe.m_Thickness;
|
||||
m_NumberOfSides = pipe.m_NumberOfSides;
|
||||
m_HeightCuts = pipe.m_HeightCuts;
|
||||
m_Smooth = pipe.m_Smooth;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Bounds UpdateBounds(ProBuilderMesh mesh, Vector3 size, Quaternion rotation, Bounds bounds)
|
||||
{
|
||||
bounds.size = size;
|
||||
return bounds;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Bounds RebuildMesh(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 height = upDir.magnitude;
|
||||
var xRadius = rightDir.magnitude / 2f;
|
||||
var zRadius = forwardDir.magnitude / 2f;
|
||||
|
||||
// template is outer ring - radius refers to outer ring always
|
||||
Vector2[] templateOut = new Vector2[m_NumberOfSides];
|
||||
Vector2[] templateIn = new Vector2[m_NumberOfSides];
|
||||
|
||||
Vector2 tangent;
|
||||
for (int i = 0; i < m_NumberOfSides; i++)
|
||||
{
|
||||
float angle = i * ( 360f / m_NumberOfSides );
|
||||
templateOut[i] = Math.PointInEllipseCircumference(xRadius, zRadius, angle, Vector2.zero, out tangent);
|
||||
|
||||
Vector2 tangentOrtho = new Vector2(-tangent.y, tangent.x);
|
||||
templateIn[i] = templateOut[i] + (m_Thickness * tangentOrtho);
|
||||
}
|
||||
|
||||
List<Vector3> v = new List<Vector3>();
|
||||
var baseY = height / 2f;
|
||||
// build out sides
|
||||
Vector2 tmp, tmp2, tmp3, tmp4;
|
||||
var heightSegments = m_HeightCuts + 1;
|
||||
for (int i = 0; i < heightSegments; i++)
|
||||
{
|
||||
// height subdivisions
|
||||
float y = i * (height / heightSegments) - baseY;
|
||||
float y2 = (i + 1) * (height / heightSegments) - baseY;
|
||||
|
||||
for (int n = 0; n < m_NumberOfSides; n++)
|
||||
{
|
||||
tmp = templateOut[n];
|
||||
tmp2 = n < (m_NumberOfSides - 1) ? templateOut[n + 1] : templateOut[0];
|
||||
|
||||
// outside quads
|
||||
Vector3[] qvo = new Vector3[4]
|
||||
{
|
||||
new Vector3(tmp2.x, y, tmp2.y),
|
||||
new Vector3(tmp.x, y, tmp.y),
|
||||
new Vector3(tmp2.x, y2, tmp2.y),
|
||||
new Vector3(tmp.x, y2, tmp.y)
|
||||
};
|
||||
|
||||
// inside quad
|
||||
tmp = templateIn[n];
|
||||
tmp2 = n < (m_NumberOfSides - 1) ? templateIn[n + 1] : templateIn[0];
|
||||
Vector3[] qvi = new Vector3[4]
|
||||
{
|
||||
new Vector3(tmp.x, y, tmp.y),
|
||||
new Vector3(tmp2.x, y, tmp2.y),
|
||||
new Vector3(tmp.x, y2, tmp.y),
|
||||
new Vector3(tmp2.x, y2, tmp2.y)
|
||||
};
|
||||
|
||||
v.AddRange(qvo);
|
||||
v.AddRange(qvi);
|
||||
}
|
||||
}
|
||||
|
||||
// build top and bottom
|
||||
for (int i = 0; i < m_NumberOfSides; i++)
|
||||
{
|
||||
tmp = templateOut[i];
|
||||
tmp2 = (i < m_NumberOfSides - 1) ? templateOut[i + 1] : templateOut[0];
|
||||
tmp3 = templateIn[i];
|
||||
tmp4 = (i < m_NumberOfSides - 1) ? templateIn[i + 1] : templateIn[0];
|
||||
|
||||
// top
|
||||
Vector3[] tpt = new Vector3[4]
|
||||
{
|
||||
new Vector3(tmp2.x, height-baseY, tmp2.y),
|
||||
new Vector3(tmp.x, height-baseY, tmp.y),
|
||||
new Vector3(tmp4.x, height-baseY, tmp4.y),
|
||||
new Vector3(tmp3.x, height-baseY, tmp3.y)
|
||||
};
|
||||
|
||||
// bottom
|
||||
Vector3[] tpb = new Vector3[4]
|
||||
{
|
||||
new Vector3(tmp.x, -baseY, tmp.y),
|
||||
new Vector3(tmp2.x, -baseY, tmp2.y),
|
||||
new Vector3(tmp3.x, -baseY, tmp3.y),
|
||||
new Vector3(tmp4.x, -baseY, tmp4.y),
|
||||
};
|
||||
|
||||
v.AddRange(tpb);
|
||||
v.AddRange(tpt);
|
||||
}
|
||||
|
||||
for(int i = 0; i < v.Count; i++)
|
||||
v[i] = rotation * v[i];
|
||||
|
||||
mesh.GeometryWithPoints(v.ToArray());
|
||||
|
||||
//Smooth internal and external faces
|
||||
if(m_Smooth)
|
||||
{
|
||||
int smoothCount = 2 * heightSegments * m_NumberOfSides;
|
||||
for(int i = 0; i < smoothCount; i++)
|
||||
mesh.facesInternal[i].smoothingGroup = 1;
|
||||
}
|
||||
|
||||
return UpdateBounds(mesh, size, rotation, new Bounds());
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomPropertyDrawer(typeof(Pipe))]
|
||||
public class PipeDrawer : PropertyDrawer
|
||||
{
|
||||
static bool s_foldoutEnabled = true;
|
||||
|
||||
const bool k_ToggleOnLabelClick = true;
|
||||
|
||||
static readonly GUIContent k_ThicknessContent = new GUIContent("Thickness", L10n.Tr("Thickness of the pipe borders. Larger value creates a smaller hole."));
|
||||
static readonly GUIContent k_SidesContent = new GUIContent("Sides Count", L10n.Tr("Number of sides of the pipe."));
|
||||
static readonly GUIContent k_HeightCutsContent = new GUIContent("Height Cuts", L10n.Tr("Number of divisions in the pipe height."));
|
||||
static readonly GUIContent k_SmoothContent = new GUIContent("Smooth", L10n.Tr("Whether to smooth the edges of the pipe."));
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
EditorGUI.BeginProperty(position, label, property);
|
||||
|
||||
s_foldoutEnabled = EditorGUI.Foldout(position, s_foldoutEnabled, "Pipe Settings", k_ToggleOnLabelClick);
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
if(s_foldoutEnabled)
|
||||
{
|
||||
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_Thickness"), k_ThicknessContent);
|
||||
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_NumberOfSides"), k_SidesContent);
|
||||
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_HeightCuts"), k_HeightCutsContent);
|
||||
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_Smooth"), k_SmoothContent);
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3834e980d6387ed4eb6bb9461aba647f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,115 @@
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEngine.ProBuilder.Shapes
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a basic [plane](../manual/Plane.html) shape.
|
||||
/// </summary>
|
||||
[Shape("Plane")]
|
||||
[System.Serializable]
|
||||
public class Plane : Shape
|
||||
{
|
||||
/// <summary>
|
||||
/// Set the number of divisions to use for the length of the plane.
|
||||
/// The default value is 1. The minimum value is 0.
|
||||
/// </summary>
|
||||
[Min(0)]
|
||||
[SerializeField]
|
||||
int m_HeightSegments = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the number of divisions to use for the width of the plane.
|
||||
/// The default value is 1. The minimum value is 0.
|
||||
/// </summary>
|
||||
[Min(0)]
|
||||
[SerializeField]
|
||||
int m_WidthSegments = 1;
|
||||
|
||||
internal override void SetParametersToBuiltInShape()
|
||||
{
|
||||
m_HeightSegments = 5;
|
||||
m_WidthSegments = 5;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void CopyShape(Shape shape)
|
||||
{
|
||||
if(shape is Plane)
|
||||
{
|
||||
m_HeightSegments = ((Plane)shape).m_HeightSegments;
|
||||
m_WidthSegments = ((Plane)shape).m_WidthSegments;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Bounds RebuildMesh(ProBuilderMesh mesh, Vector3 size, Quaternion rotation)
|
||||
{
|
||||
int w = m_WidthSegments + 1;
|
||||
int h = m_HeightSegments + 1;
|
||||
|
||||
Vector2[] p = new Vector2[(w * h) * 4];
|
||||
Vector3[] v = new Vector3[(w * h) * 4];
|
||||
|
||||
float width = 1f, height = 1f;
|
||||
int i = 0;
|
||||
{
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
float x0 = x * (width / w) - (width / 2f);
|
||||
float x1 = (x + 1) * (width / w) - (width / 2f);
|
||||
|
||||
float y0 = y * (height / h) - (height / 2f);
|
||||
float y1 = (y + 1) * (height / h) - (height / 2f);
|
||||
|
||||
p[i + 0] = new Vector2(x0, y0);
|
||||
p[i + 1] = new Vector2(x1, y0);
|
||||
p[i + 2] = new Vector2(x0, y1);
|
||||
p[i + 3] = new Vector2(x1, y1);
|
||||
|
||||
i += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(i = 0; i < v.Length; i++)
|
||||
v[i] = new Vector3(p[i].y, 0f, p[i].x);
|
||||
|
||||
mesh.GeometryWithPoints(v);
|
||||
|
||||
return mesh.mesh.bounds;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomPropertyDrawer(typeof(Plane))]
|
||||
public class PlaneDrawer : PropertyDrawer
|
||||
{
|
||||
static bool s_foldoutEnabled = true;
|
||||
|
||||
const bool k_ToggleOnLabelClick = true;
|
||||
|
||||
static readonly GUIContent k_HeightCutsContent = new GUIContent("Height Cuts", L10n.Tr("Number of divisions in the plane height."));
|
||||
static readonly GUIContent k_WidthCutsContent = new GUIContent("Width Cuts", L10n.Tr("Number of divisions in the plane width."));
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
EditorGUI.BeginProperty(position, label, property);
|
||||
|
||||
s_foldoutEnabled = EditorGUI.Foldout(position, s_foldoutEnabled, "Plane Settings", k_ToggleOnLabelClick);
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
if(s_foldoutEnabled)
|
||||
{
|
||||
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_HeightSegments"), k_HeightCutsContent);
|
||||
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_WidthSegments"), k_WidthCutsContent);
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c15f177c3bca7664dba95c24a470e354
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,96 @@
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEngine.ProBuilder.Shapes
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a basic [prism](../manual/Prism.html) shape.
|
||||
/// </summary>
|
||||
[Shape("Prism")]
|
||||
[System.Serializable]
|
||||
public class Prism : Shape
|
||||
{
|
||||
internal override void SetParametersToBuiltInShape() { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void CopyShape(Shape shape) {}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Bounds RebuildMesh(ProBuilderMesh mesh, Vector3 size, Quaternion rotation)
|
||||
{
|
||||
var meshSize = Math.Abs(size);
|
||||
meshSize.y = meshSize.y == 0 ? 2f * Mathf.Epsilon : meshSize.y;
|
||||
var baseY = new Vector3(0, meshSize.y / 2f, 0);
|
||||
meshSize.y *= 2f;
|
||||
|
||||
Vector3[] template = new Vector3[6]
|
||||
{
|
||||
Vector3.Scale(new Vector3(-.5f, 0f, -.5f), meshSize) - baseY,
|
||||
Vector3.Scale(new Vector3(.5f, 0f, -.5f), meshSize) - baseY,
|
||||
Vector3.Scale(new Vector3(0f, .5f, -.5f), meshSize) - baseY,
|
||||
Vector3.Scale(new Vector3(-.5f, 0f, .5f), meshSize) - baseY,
|
||||
Vector3.Scale(new Vector3(0.5f, 0f, .5f), meshSize) - baseY,
|
||||
Vector3.Scale(new Vector3(0f, .5f, .5f), meshSize) - baseY
|
||||
};
|
||||
|
||||
Vector3[] v = new Vector3[18]
|
||||
{
|
||||
template[0], // 0 front
|
||||
template[1], // 1
|
||||
template[2], // 2
|
||||
|
||||
template[1], // 3 right side
|
||||
template[4], // 4
|
||||
template[2], // 5
|
||||
template[5], // 6
|
||||
|
||||
template[4], // 7 back side
|
||||
template[3], // 8
|
||||
template[5], // 9
|
||||
|
||||
template[3], // 10 left side
|
||||
template[0], // 11
|
||||
template[5], // 12
|
||||
template[2], // 13
|
||||
|
||||
template[0], // 14 // bottom
|
||||
template[1], // 15
|
||||
template[3], // 16
|
||||
template[4] // 17
|
||||
};
|
||||
|
||||
Face[] f = new Face[5]
|
||||
{
|
||||
new Face(new int[3] {2, 1, 0}), // x
|
||||
new Face(new int[6] {5, 4, 3, 5, 6, 4}), // x
|
||||
new Face(new int[3] {9, 8, 7}),
|
||||
new Face(new int[6] {12, 11, 10, 12, 13, 11}),
|
||||
new Face(new int[6] {14, 15, 16, 15, 17, 16})
|
||||
};
|
||||
|
||||
var sizeSigns = Math.Sign(size);
|
||||
for(int i = 0; i < v.Length; i++)
|
||||
v[i] = Vector3.Scale(rotation * v[i], sizeSigns);
|
||||
|
||||
var sizeSign = Mathf.Sign(size.x) * Mathf.Sign(size.y) * Mathf.Sign(size.z);
|
||||
if(sizeSign < 0)
|
||||
{
|
||||
foreach(var face in f)
|
||||
face.Reverse();
|
||||
}
|
||||
|
||||
mesh.RebuildWithPositionsAndFaces(v, f);
|
||||
|
||||
return mesh.mesh.bounds;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomPropertyDrawer(typeof(Prism))]
|
||||
public class PrismDrawer : PropertyDrawer
|
||||
{
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 90d02d4062a613d43bc5e9309ec8ae8f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,189 @@
|
||||
using UnityEngine.ProBuilder.MeshOperations;
|
||||
|
||||
namespace UnityEngine.ProBuilder.Shapes
|
||||
{
|
||||
[Icon(k_IconPath)]
|
||||
[AddComponentMenu(""), DisallowMultipleComponent]
|
||||
[HelpURL(k_HelpUrl)]
|
||||
sealed class ProBuilderShape : MonoBehaviour
|
||||
{
|
||||
const string k_HelpUrl = "https://docs.unity3d.com/Packages/com.unity.probuilder@latest";
|
||||
const string k_IconPath = "Packages/com.unity.probuilder/Editor Default Resources/Icons/EditableMesh/EditableMesh.png";
|
||||
|
||||
const float k_MinHeight = 0.0001f;
|
||||
|
||||
[SerializeReference]
|
||||
Shape m_Shape = new Cube();
|
||||
|
||||
[SerializeField]
|
||||
Quaternion m_ShapeRotation = Quaternion.identity;
|
||||
|
||||
ProBuilderMesh m_Mesh;
|
||||
|
||||
[SerializeField]
|
||||
internal ushort m_UnmodifiedMeshVersion;
|
||||
|
||||
public Shape shape => m_Shape;
|
||||
|
||||
|
||||
[SerializeField]
|
||||
Vector3 m_Size = Vector3.one;
|
||||
public Vector3 size
|
||||
{
|
||||
get => m_Size;
|
||||
set
|
||||
{
|
||||
m_Size.x = System.Math.Abs(value.x) == 0 ? Mathf.Sign(m_Size.x) * 0.001f: value.x;
|
||||
m_Size.y = value.y;
|
||||
m_Size.z = System.Math.Abs(value.z) == 0 ? Mathf.Sign(m_Size.z) * 0.001f: value.z;
|
||||
}
|
||||
}
|
||||
|
||||
public Quaternion shapeRotation
|
||||
{
|
||||
get => m_ShapeRotation;
|
||||
set => m_ShapeRotation = value;
|
||||
}
|
||||
|
||||
public Vector3 shapeWorldCenter
|
||||
{
|
||||
get
|
||||
{
|
||||
return transform.TransformPoint(m_LocalCenter);
|
||||
}
|
||||
}
|
||||
|
||||
Bounds m_EditionBounds;
|
||||
public Bounds editionBounds
|
||||
{
|
||||
get
|
||||
{
|
||||
m_EditionBounds.center = m_LocalCenter;
|
||||
m_EditionBounds.size = m_Size;
|
||||
if(Mathf.Abs(m_Size.y) < k_MinHeight)
|
||||
m_EditionBounds.size = new Vector3(m_Size.x, 0f, m_Size.z);
|
||||
|
||||
return m_EditionBounds;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
Vector3 m_LocalCenter;
|
||||
public Bounds shapeLocalBounds => new Bounds(m_LocalCenter, size);
|
||||
public Bounds shapeWorldBounds => new Bounds(shapeWorldCenter, size);
|
||||
|
||||
public bool isEditable => m_UnmodifiedMeshVersion == mesh.versionIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the <see cref="ProBuilderMesh"/> that this component is creating.
|
||||
/// </summary>
|
||||
public ProBuilderMesh mesh
|
||||
{
|
||||
get
|
||||
{
|
||||
if(m_Mesh == null)
|
||||
m_Mesh = GetComponent<ProBuilderMesh>();
|
||||
|
||||
if(m_Mesh == null)
|
||||
m_Mesh = gameObject.AddComponent<ProBuilderMesh>();
|
||||
|
||||
return m_Mesh;
|
||||
}
|
||||
}
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
//Ensure the size in X and Z is not set to 0 otherwise PhysX
|
||||
//is throwing errors as it cannot create a collider
|
||||
m_Size.x = System.Math.Abs(m_Size.x) == 0 ? 0.001f: m_Size.x;
|
||||
m_Size.z = System.Math.Abs(m_Size.z) == 0 ? 0.001f: m_Size.z;
|
||||
}
|
||||
|
||||
internal void UpdateShape()
|
||||
{
|
||||
if(gameObject == null || gameObject.hideFlags == HideFlags.HideAndDontSave)
|
||||
return;
|
||||
|
||||
Rebuild(mesh.transform.position, mesh.transform.rotation, new Bounds(shapeWorldCenter, size));
|
||||
}
|
||||
|
||||
internal void UpdateBounds(Bounds bounds)
|
||||
{
|
||||
Rebuild(mesh.transform.position, mesh.transform.rotation, bounds);
|
||||
}
|
||||
|
||||
internal void Rebuild(Vector3 pivotPosition, Quaternion rotation, Bounds bounds)
|
||||
{
|
||||
var trs = transform;
|
||||
trs.position = bounds.center;
|
||||
trs.rotation = rotation;
|
||||
size = bounds.size;
|
||||
Rebuild();
|
||||
mesh.SetPivot(pivotPosition);
|
||||
m_LocalCenter = mesh.transform.InverseTransformPoint(bounds.center);
|
||||
|
||||
m_UnmodifiedMeshVersion = mesh.versionIndex;
|
||||
}
|
||||
|
||||
internal void Rebuild(Bounds bounds, Quaternion rotation)
|
||||
{
|
||||
var trs = transform;
|
||||
trs.position = bounds.center;
|
||||
trs.rotation = rotation;
|
||||
size = bounds.size;
|
||||
Rebuild();
|
||||
|
||||
m_UnmodifiedMeshVersion = mesh.versionIndex;
|
||||
}
|
||||
|
||||
void Rebuild()
|
||||
{
|
||||
if(gameObject == null || gameObject.hideFlags == HideFlags.HideAndDontSave)
|
||||
return;
|
||||
|
||||
var bbox = m_Shape.RebuildMesh(mesh, size, shapeRotation);
|
||||
bbox.size = Math.Abs(bbox.size);
|
||||
MeshUtility.FitToSize(mesh, bbox, size);
|
||||
}
|
||||
|
||||
internal void SetShape(Shape shape)
|
||||
{
|
||||
bool wasFlat = m_Shape is Plane || m_Shape is Sprite;
|
||||
bool isFlat = shape is Plane || shape is Sprite;
|
||||
|
||||
m_Shape = shape;
|
||||
|
||||
if(isFlat)
|
||||
{
|
||||
Bounds bounds = new Bounds(m_LocalCenter, size);
|
||||
var newCenter = bounds.center;
|
||||
var newSize = bounds.size;
|
||||
newCenter.y = 0;
|
||||
newSize.y = 0;
|
||||
m_LocalCenter = newCenter;
|
||||
size = newSize;
|
||||
m_Size.y = 0;
|
||||
}
|
||||
else if(wasFlat && !isFlat)
|
||||
{
|
||||
// Transitioning FROM a 2D shape TO a 3D shape - restore Y dimension
|
||||
if(Mathf.Abs(m_Size.y) < k_MinHeight)
|
||||
m_Size.y = 1f;
|
||||
}
|
||||
|
||||
UpdateShape();
|
||||
|
||||
m_UnmodifiedMeshVersion = mesh.versionIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the Shape by a given quaternion while respecting the bounds
|
||||
/// </summary>
|
||||
internal void RotateInsideBounds(Quaternion deltaRotation)
|
||||
{
|
||||
shapeRotation = deltaRotation * shapeRotation;
|
||||
var bounds = new Bounds(mesh.transform.TransformPoint(m_LocalCenter), size);
|
||||
Rebuild(mesh.transform.position, mesh.transform.rotation , bounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1ca002da428252441b92f28d83c8a65f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,60 @@
|
||||
namespace UnityEngine.ProBuilder.Shapes
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all Shape types that represent a primitive [shape](../manual/shape-tool.html).
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public abstract class Shape
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows the user to redefine the default bounding box for this shape.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The mesh to find the bounds for.</param>
|
||||
/// <param name="size">The desired size for the shape defined when using the [Shape Tool](../manual/shape-tool.html).</param>
|
||||
/// <param name="rotation">The rotation (orientation) to use for this mesh.</param>
|
||||
/// <param name="bounds">The default bounds computed for the shape.</param>
|
||||
/// <returns>The bounds from this shape's <see cref="Mesh.bounds" /> property.</returns>
|
||||
public virtual Bounds UpdateBounds(ProBuilderMesh mesh, Vector3 size, Quaternion rotation, Bounds bounds)
|
||||
{
|
||||
return mesh.mesh.bounds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds the specified mesh using the existing property values for this shape. This includes
|
||||
/// building a list of vertices and normals for each face, applying smoothing to the faces if
|
||||
/// required, and calculating the bounds of the mesh.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The mesh to rebuild.</param>
|
||||
/// <param name="size">The position of the opposite corner of the bounding box for this shape.</param>
|
||||
/// <param name="rotation">The rotation (orientation) to use for this mesh.</param>
|
||||
/// <returns>The bounds calculated for this shape after rebuilding it.</returns>
|
||||
public abstract Bounds RebuildMesh(ProBuilderMesh mesh, Vector3 size, Quaternion rotation);
|
||||
|
||||
/// <summary>
|
||||
/// Overwrites this shape's property values by copying them from the specified Shape object.
|
||||
/// </summary>
|
||||
/// <param name="shape">The <see cref="Shape" /> to copy property values from.</param>
|
||||
public abstract void CopyShape(Shape shape);
|
||||
|
||||
internal abstract void SetParametersToBuiltInShape();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an attribute for a Shape type.
|
||||
/// </summary>
|
||||
[System.AttributeUsage(System.AttributeTargets.Class)]
|
||||
public class ShapeAttribute : System.Attribute
|
||||
{
|
||||
/// <summary>Name of the attribute</summary>
|
||||
public string name;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a ShapeAttribute with the specified name.
|
||||
/// </summary>
|
||||
/// <param name="n">The name of the new ShapeAttribute.</param>
|
||||
public ShapeAttribute(string n)
|
||||
{
|
||||
name = n;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b6f090373ec944f59a5423f485b2a776
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,254 @@
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEngine.ProBuilder.Shapes
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a basic [sphere](../manual/Sphere.html) shape.
|
||||
/// </summary>
|
||||
[Shape("Sphere")]
|
||||
[System.Serializable]
|
||||
public class Sphere : Shape
|
||||
{
|
||||
static readonly Vector3[] k_IcosphereVertices = new Vector3[12]
|
||||
{
|
||||
new Vector3(-1f, Math.phi, 0f),
|
||||
new Vector3(1f, Math.phi, 0f),
|
||||
new Vector3(-1f, -Math.phi, 0f),
|
||||
new Vector3(1f, -Math.phi, 0f),
|
||||
|
||||
new Vector3(0f, -1f, Math.phi),
|
||||
new Vector3(0f, 1f, Math.phi),
|
||||
new Vector3(0f, -1f, -Math.phi),
|
||||
new Vector3(0f, 1f, -Math.phi),
|
||||
|
||||
new Vector3(Math.phi, 0f, -1f),
|
||||
new Vector3(Math.phi, 0f, 1f),
|
||||
new Vector3(-Math.phi, 0f, -1f),
|
||||
new Vector3(-Math.phi, 0f, 1f)
|
||||
};
|
||||
|
||||
static readonly int[] k_IcosphereTriangles = new int[60]
|
||||
{
|
||||
0, 11, 5,
|
||||
0, 5, 1,
|
||||
0, 1, 7,
|
||||
0, 7, 10,
|
||||
0, 10, 11,
|
||||
|
||||
1, 5, 9,
|
||||
5, 11, 4,
|
||||
11, 10, 2,
|
||||
10, 7, 6,
|
||||
7, 1, 8,
|
||||
|
||||
3, 9, 4,
|
||||
3, 4, 2,
|
||||
3, 2, 6,
|
||||
3, 6, 8,
|
||||
3, 8, 9,
|
||||
|
||||
4, 9, 5,
|
||||
2, 4, 11,
|
||||
6, 2, 10,
|
||||
8, 6, 7,
|
||||
9, 8, 1
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Sets the number of times to subdivide each triangle. The more subdivisions you create, the smoother the sphere appears.
|
||||
/// However, remember that each subdivision increases the number of triangles exponentially, which means that it uses a lot
|
||||
/// more resources to render.
|
||||
///
|
||||
/// The default value is 3. Valid values range from 1 to 5.
|
||||
/// </summary>
|
||||
[Range(1, 5)]
|
||||
[SerializeField]
|
||||
int m_Subdivisions = 3;
|
||||
|
||||
int m_BottomMostVertexIndex = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether to smooth the edges of the polygons.
|
||||
/// This property is enabled by default.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
bool m_Smooth = true;
|
||||
|
||||
internal override void SetParametersToBuiltInShape()
|
||||
{
|
||||
m_Subdivisions = 2;
|
||||
m_Smooth = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void CopyShape(Shape shape)
|
||||
{
|
||||
if(shape is Sphere)
|
||||
{
|
||||
Sphere sphere = ( (Sphere) shape );
|
||||
m_Subdivisions = sphere.m_Subdivisions;
|
||||
m_BottomMostVertexIndex = sphere.m_BottomMostVertexIndex;
|
||||
m_Smooth = sphere.m_Smooth;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Bounds UpdateBounds(ProBuilderMesh mesh, Vector3 size, Quaternion rotation, Bounds bounds)
|
||||
{
|
||||
bounds = mesh.mesh.bounds;
|
||||
return bounds;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Bounds RebuildMesh(ProBuilderMesh mesh, Vector3 size, Quaternion rotation)
|
||||
{
|
||||
var radius = .5f;
|
||||
// http://blog.andreaskahler.com/2009/06/creating-icosphere-mesh-in-code.html
|
||||
|
||||
Vector3[] v = new Vector3[k_IcosphereTriangles.Length];
|
||||
|
||||
// Regular Icosahedron - 12 vertices, 20 faces.
|
||||
for (int i = 0; i < k_IcosphereTriangles.Length; i += 3)
|
||||
{
|
||||
v[i + 0] = k_IcosphereVertices[k_IcosphereTriangles[i + 0]].normalized * radius;
|
||||
v[i + 1] = k_IcosphereVertices[k_IcosphereTriangles[i + 1]].normalized * radius;
|
||||
v[i + 2] = k_IcosphereVertices[k_IcosphereTriangles[i + 2]].normalized * radius;
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_Subdivisions; i++)
|
||||
{
|
||||
v = SubdivideIcosahedron(v, radius);
|
||||
}
|
||||
|
||||
Face[] f = new Face[v.Length / 3];
|
||||
|
||||
Vector3 bottomMostVertexPosition = Vector3.positiveInfinity;
|
||||
|
||||
for (int i = 0; i < v.Length; i += 3)
|
||||
{
|
||||
f[i / 3] = new Face(new int[3] { i, i + 1, i + 2 });
|
||||
f[i / 3].smoothingGroup = m_Smooth ? 1 : 0;
|
||||
f[i / 3].manualUV = false;
|
||||
|
||||
// Get the bottom most vertex of the whole shape. We'll use it as a pivot point.
|
||||
for (int j = 0; j < f[i / 3].indexes.Count; ++j)
|
||||
{
|
||||
int index = f[i / 3].indexes[j];
|
||||
|
||||
if (v[index].y < bottomMostVertexPosition.y)
|
||||
{
|
||||
bottomMostVertexPosition = v[index];
|
||||
m_BottomMostVertexIndex = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < f.Length; i++)
|
||||
{
|
||||
var nrm = Math.Normal(v[f[i].indexesInternal[0]], v[f[i].indexesInternal[1]], v[f[i].indexesInternal[2]]);
|
||||
var axis = Projection.VectorToProjectionAxis(nrm);
|
||||
|
||||
if (axis == ProjectionAxis.X)
|
||||
f[i].textureGroup = 2;
|
||||
else if (axis == ProjectionAxis.Y)
|
||||
f[i].textureGroup = 3;
|
||||
else if (axis == ProjectionAxis.Z)
|
||||
f[i].textureGroup = 4;
|
||||
else if (axis == ProjectionAxis.XNegative)
|
||||
f[i].textureGroup = 5;
|
||||
else if (axis == ProjectionAxis.YNegative)
|
||||
f[i].textureGroup = 6;
|
||||
else if (axis == ProjectionAxis.ZNegative)
|
||||
f[i].textureGroup = 7;
|
||||
}
|
||||
|
||||
mesh.unwrapParameters = new UnwrapParameters()
|
||||
{
|
||||
packMargin = 30f
|
||||
};
|
||||
|
||||
mesh.RebuildWithPositionsAndFaces(v, f);
|
||||
|
||||
return UpdateBounds(mesh, size, rotation, new Bounds());
|
||||
}
|
||||
|
||||
// Subdivides a set of vertices (wound as individual triangles) on an icosphere.
|
||||
//
|
||||
// /\ /\
|
||||
// / \ -> /--\
|
||||
// /____\ /_\/_\
|
||||
//
|
||||
static Vector3[] SubdivideIcosahedron(Vector3[] vertices, float radius)
|
||||
{
|
||||
Vector3[] v = new Vector3[vertices.Length * 4];
|
||||
|
||||
int index = 0;
|
||||
|
||||
Vector3 p0 = Vector3.zero, // 5
|
||||
p1 = Vector3.zero, // 3 4
|
||||
p2 = Vector3.zero, // 0, 1, 2
|
||||
p3 = Vector3.zero,
|
||||
p4 = Vector3.zero,
|
||||
p5 = Vector3.zero;
|
||||
|
||||
for (int i = 0; i < vertices.Length; i += 3)
|
||||
{
|
||||
p0 = vertices[i + 0];
|
||||
p2 = vertices[i + 1];
|
||||
p5 = vertices[i + 2];
|
||||
p1 = ((p0 + p2) * .5f).normalized * radius;
|
||||
p3 = ((p0 + p5) * .5f).normalized * radius;
|
||||
p4 = ((p2 + p5) * .5f).normalized * radius;
|
||||
|
||||
v[index++] = p0;
|
||||
v[index++] = p1;
|
||||
v[index++] = p3;
|
||||
|
||||
v[index++] = p1;
|
||||
v[index++] = p2;
|
||||
v[index++] = p4;
|
||||
|
||||
v[index++] = p1;
|
||||
v[index++] = p4;
|
||||
v[index++] = p3;
|
||||
|
||||
v[index++] = p3;
|
||||
v[index++] = p4;
|
||||
v[index++] = p5;
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomPropertyDrawer(typeof(Sphere))]
|
||||
public class SphereDrawer : PropertyDrawer
|
||||
{
|
||||
static bool s_foldoutEnabled = true;
|
||||
|
||||
const bool k_ToggleOnLabelClick = true;
|
||||
|
||||
static readonly GUIContent k_SubdivisionsContent = new GUIContent("Subdivisions", L10n.Tr("Number of time each triangle of the basic sphere is divided."));
|
||||
static readonly GUIContent k_SmoothContent = new GUIContent("Smooth", L10n.Tr("Whether to smooth the edges of the sphere."));
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
EditorGUI.BeginProperty(position, label, property);
|
||||
|
||||
s_foldoutEnabled = EditorGUI.Foldout(position, s_foldoutEnabled, "Sphere Settings", k_ToggleOnLabelClick);
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
if(s_foldoutEnabled)
|
||||
{
|
||||
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_Subdivisions"), k_SubdivisionsContent);
|
||||
EditorGUILayout.PropertyField(property.FindPropertyRelative("m_Smooth"), k_SmoothContent);
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 054265503d1c00b4ebd680d24b2fae37
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,76 @@
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEngine.ProBuilder.Shapes
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a basic [sprite](../manual/Sprite.html) (a single-unit plane shape).
|
||||
/// </summary>
|
||||
[Shape("Sprite")]
|
||||
[System.Serializable]
|
||||
public class Sprite : Shape
|
||||
{
|
||||
internal override void SetParametersToBuiltInShape() { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void CopyShape(Shape shape) {}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Bounds RebuildMesh(ProBuilderMesh mesh, Vector3 size, Quaternion rotation)
|
||||
{
|
||||
var meshSize = Math.Abs(size);
|
||||
|
||||
if(meshSize.x < float.Epsilon || meshSize.z < float.Epsilon)
|
||||
{
|
||||
mesh.Clear();
|
||||
if(mesh.mesh != null)
|
||||
mesh.mesh.Clear();
|
||||
return new Bounds();
|
||||
}
|
||||
|
||||
var width = meshSize.x;
|
||||
var height = meshSize.z;
|
||||
|
||||
Vector2[] p = new Vector2[4];
|
||||
Vector3[] v = new Vector3[4];
|
||||
Face[] f = new Face[1];
|
||||
|
||||
float x0 = -(width / 2f);
|
||||
float x1 = (width / 2f);
|
||||
|
||||
float y0 = -(height / 2f);
|
||||
float y1 = (height / 2f);
|
||||
|
||||
p[0] = new Vector2(x0, y0);
|
||||
p[1] = new Vector2(x1, y0);
|
||||
p[2] = new Vector2(x0, y1);
|
||||
p[3] = new Vector2(x1, y1);
|
||||
|
||||
f[0] = new Face(new int[6]
|
||||
{
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
3,
|
||||
2
|
||||
});
|
||||
|
||||
for (int i = 0; i < v.Length; i++)
|
||||
v[i] = new Vector3(p[i].y, 0, p[i].x);
|
||||
|
||||
mesh.RebuildWithPositionsAndFaces(v, f);
|
||||
|
||||
return mesh.mesh.bounds;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomPropertyDrawer(typeof(Sprite))]
|
||||
public class SpriteDrawer : PropertyDrawer
|
||||
{
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 487d22f1515eb1740a43a577b39ab977
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,659 @@
|
||||
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")]
|
||||
[System.Serializable]
|
||||
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;
|
||||
internal int stepsCount
|
||||
{
|
||||
get => m_StepsCount;
|
||||
set => m_StepsCount = value;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
|
||||
internal float circumference
|
||||
{
|
||||
get => m_Circumference;
|
||||
set => m_Circumference = value;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
internal float innerRadius
|
||||
{
|
||||
get => m_InnerRadius;
|
||||
set => m_InnerRadius = value;
|
||||
}
|
||||
|
||||
internal override void SetParametersToBuiltInShape()
|
||||
{
|
||||
m_StepsHeight = 0.4f;
|
||||
m_StepsCount = 6;
|
||||
m_HomogeneousSteps = true;
|
||||
m_Circumference = 0f;
|
||||
m_Sides = true;
|
||||
}
|
||||
|
||||
/// <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
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f685c26016f847298dc0af81e9322c3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,264 @@
|
||||
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")]
|
||||
[System.Serializable]
|
||||
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;
|
||||
|
||||
internal override void SetParametersToBuiltInShape()
|
||||
{
|
||||
m_Rows = 12;
|
||||
m_Columns = 16;
|
||||
m_TubeRadius = 0.3f;
|
||||
m_HorizontalCircumference = m_VerticalCircumference = 360;
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 131b1d3bbc440834c88a294085679174
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user