Added player character and step in/out of dialogue in scene 101

This commit is contained in:
2026-08-15 15:22:47 +02:00
parent a42f25d5fa
commit 38ba2b0eff
2019 changed files with 181227 additions and 265 deletions
@@ -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: