using System;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.ProBuilder;
using UnityEditor.SettingsManagement;
using UnityEngine.ProBuilder.MeshOperations;
namespace UnityEditor.ProBuilder
{
///
/// Shape creation panel implementation.
///
sealed class ShapeEditor : ConfigurableWindow
{
abstract class ShapeBuilder
{
public virtual string name
{
get { return ObjectNames.NicifyVariableName(GetType().Name); }
}
public abstract void OnGUI();
public abstract ProBuilderMesh Build(bool isPreview = false);
}
public static void MenuOpenShapeCreator()
{
GetWindow("Shape Tool");
}
Vector2 m_Scroll = Vector2.zero;
static int s_CurrentIndex = 0;
GameObject m_PreviewObject;
[UserSetting("Toolbar", "Close Shape Window after Build", "When true the shape window will close after hitting the build button.")]
static Pref s_CloseWindowAfterCreateShape = new Pref("editor.closeWindowAfterShapeCreation", false);
ShapeBuilder[] m_ShapeBuilders = new ShapeBuilder[]
{
new Cube(),
new Sprite(),
new Prism(),
new Stair(),
new Cylinder(),
new Door(),
new Plane(),
new Pipe(),
new Cone(),
new Arch(),
new Sphere(),
new Torus(),
new Custom()
};
string[] m_ShapeTypes;
void OnEnable()
{
m_ShapeTypes = m_ShapeBuilders.Select(x => x.name).ToArray();
SetPreviewMesh(m_ShapeBuilders[s_CurrentIndex].Build());
}
void OnDestroy()
{
DestroyPreviewObject();
}
[MenuItem("GameObject/3D Object/" + PreferenceKeys.pluginTitle + " Cube _%k")]
public static void MenuCreateCube()
{
ProBuilderMesh mesh = ShapeGenerator.GenerateCube(EditorUtility.newShapePivotLocation, Vector3.one);
UndoUtility.RegisterCreatedObjectUndo(mesh.gameObject, "Create Shape");
EditorUtility.InitObject(mesh);
}
void OnGUI()
{
DoContextMenu();
if (Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Return)
CreateSelectedShape(true);
GUILayout.Label("Shape Selector", EditorStyles.boldLabel);
EditorGUI.BeginChangeCheck();
s_CurrentIndex = EditorGUILayout.Popup(s_CurrentIndex, m_ShapeTypes);
GUILayout.Label("Shape Settings", EditorStyles.boldLabel);
m_Scroll = EditorGUILayout.BeginScrollView(m_Scroll);
var shape = m_ShapeBuilders[s_CurrentIndex];
shape.OnGUI();
EditorGUILayout.EndScrollView();
if (EditorGUI.EndChangeCheck())
SetPreviewMesh(shape.Build());
GUILayout.FlexibleSpace();
if (GUILayout.Button("Build"))
CreateSelectedShape();
}
void CreateSelectedShape(bool forceCloseWindow = false)
{
var res = m_ShapeBuilders[s_CurrentIndex].Build();
EditorUtility.InitObject(res);
ApplyPreviewTransform(res);
DestroyPreviewObject();
Undo.RegisterCreatedObjectUndo(res.gameObject, "Create Shape");
if (forceCloseWindow || s_CloseWindowAfterCreateShape)
Close();
}
void DestroyPreviewObject()
{
if (m_PreviewObject != null)
{
if (m_PreviewObject.GetComponent().sharedMesh != null)
DestroyImmediate(m_PreviewObject.GetComponent().sharedMesh);
DestroyImmediate(m_PreviewObject);
}
}
void SetPreviewMesh(ProBuilderMesh mesh)
{
ApplyPreviewTransform(mesh);
DestroyPreviewObject();
mesh.selectable = false;
m_PreviewObject = mesh.gameObject;
mesh.preserveMeshAssetOnDestroy = true;
var umesh = mesh.mesh;
DestroyImmediate(mesh);
umesh.hideFlags = HideFlags.DontSave;
m_PreviewObject.hideFlags = HideFlags.DontSave;
m_PreviewObject.GetComponent().sharedMaterial = BuiltinMaterials.ShapePreviewMaterial;
Selection.activeTransform = m_PreviewObject.transform;
}
void ApplyPreviewTransform(ProBuilderMesh mesh)
{
var position = Vector3.zero;
var scale = Vector3.one;
var rotation = Quaternion.identity;
var previous = m_PreviewObject != null;
if (previous)
{
position = m_PreviewObject.transform.position;
rotation = m_PreviewObject.transform.localRotation;
scale = m_PreviewObject.transform.localScale;
}
if (previous)
{
mesh.transform.position = position;
mesh.transform.localRotation = rotation;
mesh.transform.localScale = scale;
}
else
{
EditorUtility.ScreenCenter(mesh.gameObject);
EditorUtility.SetPivotLocationAndSnap(mesh);
}
}
class Cube : ShapeBuilder
{
static Vector3 s_CubeSize = Vector3.one;
public override void OnGUI()
{
s_CubeSize = EditorGUILayout.Vector3Field("Size", s_CubeSize);
if (s_CubeSize.x <= 0)
s_CubeSize.x = .01f;
if (s_CubeSize.y <= 0)
s_CubeSize.y = .01f;
if (s_CubeSize.z <= 0)
s_CubeSize.z = .01f;
}
public override ProBuilderMesh Build(bool preview = false)
{
return ShapeGenerator.GenerateCube(EditorUtility.newShapePivotLocation, s_CubeSize);
}
}
class Sprite : ShapeBuilder
{
static Axis s_Axis = Axis.Forward;
public override void OnGUI()
{
s_Axis = (Axis)EditorGUILayout.EnumPopup("Axis", s_Axis);
}
public override ProBuilderMesh Build(bool preview = false)
{
return ShapeGenerator.GeneratePlane(EditorUtility.newShapePivotLocation, 1, 1, 0, 0, s_Axis);
}
}
class Prism : ShapeBuilder
{
static Vector3 s_PrismSize = Vector3.one;
public override void OnGUI()
{
s_PrismSize = EditorGUILayout.Vector3Field("Size", s_PrismSize);
if (s_PrismSize.x < 0) s_PrismSize.x = 0.01f;
if (s_PrismSize.y < 0) s_PrismSize.y = 0.01f;
if (s_PrismSize.z < 0) s_PrismSize.z = 0.01f;
}
public override ProBuilderMesh Build(bool preview = false)
{
return ShapeGenerator.GeneratePrism(EditorUtility.newShapePivotLocation, s_PrismSize);
}
}
class Stair : ShapeBuilder
{
static int s_Steps = 6;
static Vector3 s_Size = new Vector3(2f, 2.5f, 4f);
static float s_Circumference = 0f;
static bool s_Sides = true;
static bool s_Mirror = false;
public override void OnGUI()
{
s_Steps = (int)Mathf.Max(UI.EditorGUIUtility.FreeSlider("Steps", s_Steps, 2, 64), 2);
s_Sides = EditorGUILayout.Toggle("Build Sides", s_Sides);
s_Circumference = EditorGUILayout.Slider("Curvature", s_Circumference, 0f, 360f);
if (s_Circumference > 0f)
{
s_Mirror = EditorGUILayout.Toggle("Mirror", s_Mirror);
s_Size.x =
Mathf.Max(
UI.EditorGUIUtility.FreeSlider(
new GUIContent("Stair Width", "The width of an individual stair step."), s_Size.x,
.01f, 10f), .01f);
s_Size.y =
Mathf.Max(
UI.EditorGUIUtility.FreeSlider(
new GUIContent("Stair Height",
"The total height of this staircase. You may enter any value in the float field."),
s_Size.y, .01f, 10f), .01f);
s_Size.z =
Mathf.Max(
UI.EditorGUIUtility.FreeSlider(
new GUIContent("Inner Radius", "The distance from the center that stairs begin."),
s_Size.z, 0f, 10f), 0f);
}
else
{
s_Size.x = UI.EditorGUIUtility.FreeSlider("Width", s_Size.x, 0.01f, 10f);
s_Size.y = UI.EditorGUIUtility.FreeSlider("Height", s_Size.y, 0.01f, 10f);
s_Size.z = UI.EditorGUIUtility.FreeSlider("Depth", s_Size.z, 0.01f, 10f);
}
}
public override ProBuilderMesh Build(bool preview = false)
{
if (s_Circumference > 0f)
{
return ShapeGenerator.GenerateCurvedStair(
EditorUtility.newShapePivotLocation,
s_Size.x,
s_Size.y,
s_Size.z,
s_Mirror ? -s_Circumference : s_Circumference,
s_Steps,
s_Sides);
}
return ShapeGenerator.GenerateStair(
EditorUtility.newShapePivotLocation,
s_Size,
s_Steps,
s_Sides);
}
}
class Cylinder : ShapeBuilder
{
static int s_AxisSegments = 8;
static float s_Radius = .5f;
static float s_Height = 1f;
static int s_HeighSegments = 0;
static bool s_Smooth = true;
public override void OnGUI()
{
s_Radius = EditorGUILayout.FloatField("Radius", s_Radius);
s_Radius = Mathf.Clamp(s_Radius, .01f, Mathf.Infinity);
s_AxisSegments = EditorGUILayout.IntField("Number of Sides", s_AxisSegments);
s_AxisSegments = UnityEngine.ProBuilder.Math.Clamp(s_AxisSegments, 4, 48);
s_Height = EditorGUILayout.FloatField("Height", s_Height);
s_HeighSegments = EditorGUILayout.IntField("Height Segments", s_HeighSegments);
s_HeighSegments = UnityEngine.ProBuilder.Math.Clamp(s_HeighSegments, 0, 48);
s_Smooth = EditorGUILayout.Toggle("Smooth", s_Smooth);
if (s_AxisSegments % 2 != 0)
s_AxisSegments++;
if (s_HeighSegments < 0)
s_HeighSegments = 0;
}
public override ProBuilderMesh Build(bool preview = false)
{
return ShapeGenerator.GenerateCylinder(
EditorUtility.newShapePivotLocation,
s_AxisSegments,
s_Radius,
s_Height,
s_HeighSegments,
s_Smooth ? 1 : -1);
}
}
class Door : ShapeBuilder
{
static float s_Width = 4.0f;
static float s_Height = 4.0f;
static float s_LedgeHeight = 1.0f;
static float s_LegWidth = 1.0f;
static float s_Depth = 0.5f;
public override void OnGUI()
{
s_Width = EditorGUILayout.FloatField("Total Width", s_Width);
s_Width = Mathf.Clamp(s_Width, 1.0f, 500.0f);
s_Height = EditorGUILayout.FloatField("Total Height", s_Height);
s_Height = Mathf.Clamp(s_Height, 1.0f, 500.0f);
s_Depth = EditorGUILayout.FloatField("Total Depth", s_Depth);
s_Depth = Mathf.Clamp(s_Depth, 0.01f, 500.0f);
s_LedgeHeight = EditorGUILayout.FloatField("Door Height", s_LedgeHeight);
s_LedgeHeight = Mathf.Clamp(s_LedgeHeight, 0.01f, 500.0f);
s_LegWidth = EditorGUILayout.FloatField("Leg Width", s_LegWidth);
s_LegWidth = Mathf.Clamp(s_LegWidth, 0.01f, 2.0f);
}
public override ProBuilderMesh Build(bool preview = false)
{
return ShapeGenerator.GenerateDoor(
EditorUtility.newShapePivotLocation,
s_Width,
s_Height,
s_LedgeHeight,
s_LegWidth,
s_Depth);
}
}
class Plane : ShapeBuilder
{
static float s_Height = 10;
static float s_Width = 10;
static int s_HeightSegments = 3;
static int s_WidthSegments = 3;
static Axis s_Axis = Axis.Up;
public override void OnGUI()
{
s_Axis = (Axis)EditorGUILayout.EnumPopup("Axis", s_Axis);
s_Width = EditorGUILayout.FloatField("Width", s_Width);
s_Height = EditorGUILayout.FloatField("Length", s_Height);
if (s_Height < 1f)
s_Height = 1f;
if (s_Width < 1f)
s_Width = 1f;
s_WidthSegments = EditorGUILayout.IntField("Width Segments", s_WidthSegments);
s_HeightSegments = EditorGUILayout.IntField("Length Segments", s_HeightSegments);
if (s_WidthSegments < 0)
s_WidthSegments = 0;
if (s_HeightSegments < 0)
s_HeightSegments = 0;
}
public override ProBuilderMesh Build(bool preview = false)
{
return ShapeGenerator.GeneratePlane(
EditorUtility.newShapePivotLocation,
s_Height,
s_Width,
s_HeightSegments,
s_WidthSegments,
s_Axis);
}
}
class Pipe : ShapeBuilder
{
static float s_Radius = 1f;
static float s_Height = 2f;
static float s_Thickness = .2f;
static int s_AxisSegments = 6;
static int s_HeightSegments = 1;
public override void OnGUI()
{
s_Radius = EditorGUILayout.FloatField("Radius", s_Radius);
s_Height = EditorGUILayout.FloatField("Height", s_Height);
s_Thickness = EditorGUILayout.FloatField("Thickness", s_Thickness);
s_AxisSegments = EditorGUILayout.IntField("Number of Sides", s_AxisSegments);
s_HeightSegments = EditorGUILayout.IntField("Height Segments", s_HeightSegments);
if (s_Radius < .1f)
s_Radius = .1f;
if (s_Height < .1f)
s_Height = .1f;
s_HeightSegments = (int)Mathf.Clamp(s_HeightSegments, 0f, 32f);
s_Thickness = Mathf.Clamp(s_Thickness, .01f, s_Radius - .01f);
s_AxisSegments = (int)Mathf.Clamp(s_AxisSegments, 3f, 32f);
}
public override ProBuilderMesh Build(bool preview = false)
{
return ShapeGenerator.GeneratePipe(
EditorUtility.newShapePivotLocation,
s_Radius,
s_Height,
s_Thickness,
s_AxisSegments,
s_HeightSegments
);
}
}
class Cone : ShapeBuilder
{
static float s_Radius = 1f;
static float s_Height = 2f;
static int s_AxisSegments = 6;
public override void OnGUI()
{
s_Radius = EditorGUILayout.FloatField("Radius", s_Radius);
s_Height = EditorGUILayout.FloatField("Height", s_Height);
s_AxisSegments = EditorGUILayout.IntField("Number of Sides", s_AxisSegments);
if (s_Radius < .1f)
s_Radius = .1f;
if (s_Height < .1f)
s_Height = .1f;
s_AxisSegments = (int)Mathf.Clamp(s_AxisSegments, 3f, 32f);
}
public override ProBuilderMesh Build(bool preview = false)
{
return ShapeGenerator.GenerateCone(
EditorUtility.newShapePivotLocation,
s_Radius,
s_Height,
s_AxisSegments
);
}
}
class Arch : ShapeBuilder
{
static float s_Angle = 180.0f;
static float s_Radius = 3.0f;
static float s_Width = 0.50f;
static float s_Depth = 1f;
static int s_RadiusSegments = 6;
static bool s_EndCaps = true;
const bool k_InsideFaces = true;
const bool k_OutsideFaces = true;
const bool k_FrontFaces = true;
const bool k_BackFaces = true;
public override void OnGUI()
{
s_Radius = EditorGUILayout.FloatField("Radius", s_Radius);
s_Radius = s_Radius <= 0f ? .01f : s_Radius;
s_Width = EditorGUILayout.FloatField("Thickness", s_Width);
s_Width = Mathf.Clamp(s_Width, 0.01f, 100f);
s_Depth = EditorGUILayout.FloatField("Depth", s_Depth);
s_Depth = Mathf.Clamp(s_Depth, 0.1f, 500.0f);
s_RadiusSegments = EditorGUILayout.IntField("Number of Sides", s_RadiusSegments);
s_RadiusSegments = Mathf.Clamp(s_RadiusSegments, 2, 200);
s_Angle = EditorGUILayout.FloatField("Arch Degrees", s_Angle);
s_Angle = Mathf.Clamp(s_Angle, 0.0f, 360.0f);
if (s_Angle < 360f)
s_EndCaps = EditorGUILayout.Toggle("End Caps", s_EndCaps);
if (s_Angle > 180f)
s_RadiusSegments = System.Math.Max(3, s_RadiusSegments);
}
public override ProBuilderMesh Build(bool preview = false)
{
return ShapeGenerator.GenerateArch(
EditorUtility.newShapePivotLocation,
s_Angle,
s_Radius,
Mathf.Clamp(s_Width, 0.01f, s_Radius),
s_Depth,
s_RadiusSegments + 1,
k_InsideFaces,
k_OutsideFaces,
k_FrontFaces,
k_BackFaces,
s_EndCaps);
}
}
class Sphere : ShapeBuilder
{
static float s_Radius = 1f;
static int s_Subdivisions = 1;
public override void OnGUI()
{
s_Radius = EditorGUILayout.Slider("Radius", s_Radius, 0.01f, 10f);
s_Subdivisions = (int)EditorGUILayout.Slider("Subdivisions", s_Subdivisions, 0, 4);
}
public override ProBuilderMesh Build(bool preview = false)
{
// To keep the preview snappy, shared indexes aren't built in IcosahadreonGenerator
var mesh = ShapeGenerator.GenerateIcosahedron(
EditorUtility.newShapePivotLocation,
s_Radius,
s_Subdivisions,
!preview);
if (!preview)
UVEditing.ProjectFacesBox(mesh, mesh.facesInternal);
for (int i = 0; i < mesh.facesInternal.Length; i++)
mesh.facesInternal[i].manualUV = true;
return mesh;
}
}
class Torus : ShapeBuilder
{
static float s_Radius = 1f;
static float s_TubeRadius = .3f;
static int s_Rows = 16;
static int s_Columns = 24;
static bool s_Smooth = true;
static float s_HorizontalCirumference = 360f;
static float s_VerticalCircumference = 360f;
static Vector2 s_InnerOuter = new Vector2(1f, .7f);
static Pref s_UseInnerOuterMethod =
new Pref("shape.torusDefinesInnerOuter", false, SettingsScope.User);
public override void OnGUI()
{
s_Rows = (int)EditorGUILayout.IntSlider(
new GUIContent("Rows", "How many rows the torus will have. More equates to smoother geometry."),
s_Rows, 3, 32);
s_Columns = (int)EditorGUILayout.IntSlider(
new GUIContent("Columns",
"How many columns the torus will have. More equates to smoother geometry."), s_Columns, 3, 64);
s_UseInnerOuterMethod.value =
EditorGUILayout.Toggle("Define Inner / Out Radius", s_UseInnerOuterMethod);
if (!s_UseInnerOuterMethod)
{
s_Radius = EditorGUILayout.FloatField("Radius", s_Radius);
if (s_Radius < .001f)
s_Radius = .001f;
s_TubeRadius = UI.EditorGUIUtility.Slider(
new GUIContent("Tube Radius", "How thick the donut will be."), s_TubeRadius, .01f, s_Radius);
}
else
{
s_InnerOuter.x = s_Radius;
s_InnerOuter.y = s_Radius - (s_TubeRadius * 2f);
s_InnerOuter.x = EditorGUILayout.FloatField("Outer Radius", s_InnerOuter.x);
s_InnerOuter.y = UI.EditorGUIUtility.Slider(
new GUIContent("Inner Radius", "Distance from center to inside of donut ring."), s_InnerOuter.y,
.001f, s_InnerOuter.x);
s_Radius = s_InnerOuter.x;
s_TubeRadius = (s_InnerOuter.x - s_InnerOuter.y) * .5f;
}
s_HorizontalCirumference =
EditorGUILayout.Slider("Horizontal Circumference", s_HorizontalCirumference, .01f, 360f);
s_VerticalCircumference =
EditorGUILayout.Slider("Vertical Circumference", s_VerticalCircumference, .01f, 360f);
s_Smooth = EditorGUILayout.Toggle("Smooth", s_Smooth);
}
public override ProBuilderMesh Build(bool isPreview = false)
{
var mesh = ShapeGenerator.GenerateTorus(
EditorUtility.newShapePivotLocation,
s_Rows,
s_Columns,
s_Radius,
s_TubeRadius,
s_Smooth,
s_HorizontalCirumference,
s_VerticalCircumference,
true);
UVEditing.ProjectFacesBox(mesh, mesh.facesInternal);
return mesh;
}
}
class Custom : ShapeBuilder
{
static Vector2 scrollbar = new Vector2(0f, 0f);
static string verts = "//Vertical Plane\n0, 0, 0\n1, 0, 0\n0, 1, 0\n1, 1, 0\n";
public override void OnGUI()
{
GUILayout.Label("Custom Geometry", EditorStyles.boldLabel);
EditorGUILayout.HelpBox("Vertices must be wound in faces, and counter-clockwise.\n(Think horizontally reversed Z)", MessageType.Info);
scrollbar = GUILayout.BeginScrollView(scrollbar);
verts = EditorGUILayout.TextArea(verts, GUILayout.MinHeight(160));
GUILayout.EndScrollView();
}
public override ProBuilderMesh Build(bool isPreview = false)
{
var positions = InternalUtility.StringToVector3Array(verts);
if (positions.Length % 4 == 0)
return ProBuilderMesh.CreateInstanceWithPoints(
InternalUtility.StringToVector3Array(verts)
);
return ProBuilderMesh.Create();
}
}
}
}