From 0d022ecd65bf4ed169c7f5c83d49720952c4b9e6 Mon Sep 17 00:00:00 2001 From: Henry de Jongh Date: Wed, 9 May 2018 17:23:39 +0200 Subject: [PATCH 1/2] Can now extrude any 2D Shape Editor project into a single concave NoCSG brush. --- .../Editor/ShapeEditorWindowPopup.cs | 33 +++-- .../CompoundBrushes/ShapeEditor/Project.cs | 6 + .../ShapeEditor/ShapeEditorBrush.cs | 113 ++++++++++++------ 3 files changed, 106 insertions(+), 46 deletions(-) diff --git a/Scripts/Brushes/CompoundBrushes/Editor/ShapeEditorWindowPopup.cs b/Scripts/Brushes/CompoundBrushes/Editor/ShapeEditorWindowPopup.cs index b602ea20..49db3715 100644 --- a/Scripts/Brushes/CompoundBrushes/Editor/ShapeEditorWindowPopup.cs +++ b/Scripts/Brushes/CompoundBrushes/Editor/ShapeEditorWindowPopup.cs @@ -36,6 +36,7 @@ public enum PopupMode public int revolveSteps = 4; public bool revolveSpiralSloped = false; public Vector2Int GlobalPivotPosition_Position; + public bool convexBrushes = true; private Action onApply; @@ -50,6 +51,7 @@ public ShapeEditorWindowPopup(PopupMode popupMode, ShapeEditor.Project project, revolve360 = project.revolve360; revolveSteps = project.revolveSteps; revolveSpiralSloped = project.revolveSpiralSloped; + convexBrushes = project.convexBrushes; GlobalPivotPosition_Position = project.globalPivot.position; this.onApply = (self) => @@ -59,10 +61,12 @@ public ShapeEditorWindowPopup(PopupMode popupMode, ShapeEditor.Project project, { case PopupMode.CreatePolygon: project.extrudeScale = extrudeScale; + project.convexBrushes = convexBrushes; break; case PopupMode.RevolveShape: project.extrudeScale = extrudeScale; + project.convexBrushes = convexBrushes; project.revolve360 = revolve360; project.revolveSteps = revolveSteps; project.revolveSpiralSloped = revolveSpiralSloped; @@ -70,16 +74,19 @@ public ShapeEditorWindowPopup(PopupMode popupMode, ShapeEditor.Project project, case PopupMode.ExtrudeShape: project.extrudeScale = extrudeScale; + project.convexBrushes = convexBrushes; project.extrudeDepth = extrudeDepth; break; case PopupMode.ExtrudePoint: project.extrudeScale = extrudeScale; + project.convexBrushes = convexBrushes; project.extrudeDepth = extrudeDepth; break; case PopupMode.ExtrudeBevel: project.extrudeScale = extrudeScale; + project.convexBrushes = convexBrushes; project.extrudeDepth = extrudeDepth; project.extrudeClipDepth = extrudeClipDepth; break; @@ -93,28 +100,29 @@ public ShapeEditorWindowPopup(PopupMode popupMode, ShapeEditor.Project project, public override Vector2 GetWindowSize() { + // + 18 for every element switch (popupMode) { case PopupMode.BezierDetailLevel: return new Vector2(205, 140); case PopupMode.GlobalPivotPosition: - return new Vector2(300, 50 + 18); + return new Vector2(300, 68); case PopupMode.CreatePolygon: - return new Vector2(300, 50 + 18); + return new Vector2(300, 50 + 36); case PopupMode.RevolveShape: - return new Vector2(300, 86 + 18 + 18); + return new Vector2(300, 104 + 36); case PopupMode.ExtrudeShape: - return new Vector2(300, 68 + 18); + return new Vector2(300, 68 + 36); case PopupMode.ExtrudePoint: - return new Vector2(300, 68 + 18); + return new Vector2(300, 68 + 36); case PopupMode.ExtrudeBevel: - return new Vector2(300, 86 + 18); + return new Vector2(300, 86 + 36); default: return new Vector2(300, 150); @@ -124,12 +132,14 @@ public override Vector2 GetWindowSize() public override void OnGUI(Rect rect) { bool hasScale = true; + bool hasConvexBrushes = true; string accept = ""; switch (popupMode) { case PopupMode.BezierDetailLevel: GUILayout.Label("Bezier Detail Level", EditorStyles.boldLabel); hasScale = false; + hasConvexBrushes = false; accept = "Apply"; GUILayout.BeginHorizontal(EditorStyles.toolbar); @@ -184,6 +194,7 @@ public override void OnGUI(Rect rect) case PopupMode.GlobalPivotPosition: GUILayout.Label("Global Pivot Position", EditorStyles.boldLabel); hasScale = false; + hasConvexBrushes = false; accept = "Set Position"; #if !UNITY_2017_2_OR_NEWER @@ -211,10 +222,7 @@ public override void OnGUI(Rect rect) revolveSteps = EditorGUILayout.IntField("Steps", revolveSteps); if (revolveSteps < 1) revolveSteps = 1; - EditorGUILayout.BeginHorizontal(); - GUILayout.Label("NoCSG Only"); - revolveSpiralSloped = GUILayout.Toggle(revolveSpiralSloped, "Sloped Spiral", EditorStyles.toolbarButton); - EditorGUILayout.EndHorizontal(); + revolveSpiralSloped = EditorGUILayout.Toggle("Sloped Spiral", revolveSpiralSloped); // steps can't be more than 360. if (revolveSteps > revolve360) revolveSteps = revolve360; @@ -248,6 +256,11 @@ public override void OnGUI(Rect rect) break; } + if (hasConvexBrushes) + { + convexBrushes = EditorGUILayout.Toggle("Convex Brushes", convexBrushes); + } + if (hasScale) { EditorGUIUtility.wideMode = true; diff --git a/Scripts/Brushes/CompoundBrushes/ShapeEditor/Project.cs b/Scripts/Brushes/CompoundBrushes/ShapeEditor/Project.cs index 408f6910..74cff746 100644 --- a/Scripts/Brushes/CompoundBrushes/ShapeEditor/Project.cs +++ b/Scripts/Brushes/CompoundBrushes/ShapeEditor/Project.cs @@ -101,6 +101,12 @@ public class Project [SerializeField] public bool revolveSpiralSloped = false; + /// + /// Whether the shape uses Convex Decomposition or Concave Shapes. + /// + [SerializeField] + public bool convexBrushes = true; + /// /// Clones this project and returns the copy. /// diff --git a/Scripts/Brushes/CompoundBrushes/ShapeEditor/ShapeEditorBrush.cs b/Scripts/Brushes/CompoundBrushes/ShapeEditor/ShapeEditorBrush.cs index 9bbe855c..439d9d4c 100644 --- a/Scripts/Brushes/CompoundBrushes/ShapeEditor/ShapeEditorBrush.cs +++ b/Scripts/Brushes/CompoundBrushes/ShapeEditor/ShapeEditorBrush.cs @@ -110,6 +110,10 @@ public override int BrushCount { get { + // if the user desires a single concave brush we return 1. + if (!project.convexBrushes) + return 1; + // we already know the amount of brushes we need. if (!isDirty) return desiredBrushCount; @@ -149,10 +153,14 @@ public override void Invalidate(bool polygonsChanged) if (extrudeMode == ExtrudeMode.RevolveShape && project.revolveSpiralSloped && project.globalPivot.position.y != 0) this.IsNoCSG = true; + // force nocsg when using concave brushes as sabrecsg doesn't support it. + if (!project.convexBrushes) + this.IsNoCSG = true; + // nothing to do except copy csg information to our child brushes. if (!isDirty) { - for (int i = 0; i < BrushCount; i++) + for (int i = 0; i < (project.convexBrushes ? desiredBrushCount : 1); i++) { generatedBrushes[i].Mode = this.Mode; generatedBrushes[i].IsNoCSG = this.IsNoCSG; @@ -175,16 +183,22 @@ public override void Invalidate(bool polygonsChanged) if (m_LastBuiltPolygons == null) m_LastBuiltPolygons = BuildConvexPolygons(); + // prepare a list of polygons for concave brushes. + List concavePolygons = null; + if (!project.convexBrushes) + concavePolygons = new List(); + // iterate through the brushes we received: - int brushCount = BrushCount; + int brushCount = desiredBrushCount; + // iterate through the brushes we received: for (int i = 0; i < brushCount; i++) { // copy our csg information to our child brushes. - generatedBrushes[i].Mode = this.Mode; - generatedBrushes[i].IsNoCSG = this.IsNoCSG; - generatedBrushes[i].IsVisible = this.IsVisible; - generatedBrushes[i].HasCollision = this.HasCollision; + generatedBrushes[project.convexBrushes ? i : 0].Mode = this.Mode; + generatedBrushes[project.convexBrushes ? i : 0].IsNoCSG = this.IsNoCSG; + generatedBrushes[project.convexBrushes ? i : 0].IsVisible = this.IsVisible; + generatedBrushes[project.convexBrushes ? i : 0].HasCollision = this.HasCollision; // local variables. Quaternion rot; @@ -198,7 +212,11 @@ public override void Invalidate(bool polygonsChanged) GenerateUvCoordinates(m_LastBuiltPolygons[i], false); Polygon poly1 = m_LastBuiltPolygons[i].DeepCopy(); poly1.Flip(); - generatedBrushes[i].SetPolygons(new Polygon[] { poly1 }); + + if (project.convexBrushes) + generatedBrushes[i].SetPolygons(new Polygon[] { poly1 }); + else + concavePolygons.Add(poly1); break; // generate 3d cube-ish shapes that revolve around the pivot and spirals up or down. @@ -254,7 +272,10 @@ public override void Invalidate(bool polygonsChanged) GenerateUvCoordinates(backPoly, false); polygons.Add(backPoly); - generatedBrushes[i].SetPolygons(polygons.ToArray()); + if (project.convexBrushes) + generatedBrushes[i].SetPolygons(polygons.ToArray()); + else + concavePolygons.AddRange(polygons); break; // generate a 3d cube-ish shape. @@ -263,7 +284,10 @@ public override void Invalidate(bool polygonsChanged) SurfaceUtility.ExtrudePolygon(m_LastBuiltPolygons[i], project.extrudeDepth, out outputPolygons, out rot); foreach (Polygon poly in outputPolygons) GenerateUvCoordinates(poly, false); - generatedBrushes[i].SetPolygons(outputPolygons); + if (project.convexBrushes) + generatedBrushes[i].SetPolygons(outputPolygons); + else + concavePolygons.AddRange(outputPolygons); break; // generate a 3d cone-ish shape. @@ -272,7 +296,10 @@ public override void Invalidate(bool polygonsChanged) ExtrudePolygonToPoint(m_LastBuiltPolygons[i], project.extrudeDepth, new Vector2((project.globalPivot.position.x * project.extrudeScale.x) / 8.0f, -(project.globalPivot.position.y * project.extrudeScale.y) / 8.0f), out outputPolygons, out rot); foreach (Polygon poly in outputPolygons) GenerateUvCoordinates(poly, false); - generatedBrushes[i].SetPolygons(outputPolygons); + if (project.convexBrushes) + generatedBrushes[i].SetPolygons(outputPolygons); + else + concavePolygons.AddRange(outputPolygons); break; // generate a 3d trapezoid-ish shape. @@ -281,54 +308,68 @@ public override void Invalidate(bool polygonsChanged) ExtrudePolygonBevel(m_LastBuiltPolygons[i], project.extrudeDepth, project.extrudeClipDepth / project.extrudeDepth, new Vector2((project.globalPivot.position.x * project.extrudeScale.x) / 8.0f, -(project.globalPivot.position.y * project.extrudeScale.y) / 8.0f), out outputPolygons, out rot); foreach (Polygon poly in outputPolygons) GenerateUvCoordinates(poly, false); - generatedBrushes[i].SetPolygons(outputPolygons); + if (project.convexBrushes) + generatedBrushes[i].SetPolygons(outputPolygons); + else + concavePolygons.AddRange(outputPolygons); break; } // we invalidate every brush after hidden surface removal. } + // assign the concave polygons to the brush. + if (!project.convexBrushes) + { + generatedBrushes[0].SetPolygons(concavePolygons.ToArray()); + csgBounds.Encapsulate(generatedBrushes[0].GetBounds()); + } + // we exclude hidden faces automatically. // this step will automatically optimize NoCSG output the same way additive brushes would have. // it also excludes a couple faces that CSG doesn't exclude due to floating point precision errors. // the latter is especially noticable with complex revolved shapes. - // compare each brush to another brush: - for (int i = 0; i < brushCount; i++) + // can only use hidden surface removal on convex brushes. + if (project.convexBrushes) { - for (int j = 0; j < brushCount; j++) + // compare each brush to another brush: + for (int i = 0; i < brushCount; i++) { - // can't check for hidden faces on the same brush. - if (i == j) continue; - - // compare each polygon on brush i to each polygon on brush j: - foreach (Polygon pa in generatedBrushes[i].GetPolygons()) + for (int j = 0; j < brushCount; j++) { - foreach (Polygon pb in generatedBrushes[j].GetPolygons()) + // can't check for hidden faces on the same brush. + if (i == j) continue; + + // compare each polygon on brush i to each polygon on brush j: + foreach (Polygon pa in generatedBrushes[i].GetPolygons()) { - // check they both have this polygon: - bool identical = true; - foreach (Vertex va in pa.Vertices) + foreach (Polygon pb in generatedBrushes[j].GetPolygons()) { - if (!pb.Vertices.Any(vb => vb.Position == va.Position)) + // check they both have this polygon: + bool identical = true; + foreach (Vertex va in pa.Vertices) { - identical = false; - break; + if (!pb.Vertices.Any(vb => vb.Position == va.Position)) + { + identical = false; + break; + } + } + // identical polygons on both brushes means it can be excluded: + if (identical) + { + pa.UserExcludeFromFinal = true; + pb.UserExcludeFromFinal = true; } - } - // identical polygons on both brushes means it can be excluded: - if (identical) - { - pa.UserExcludeFromFinal = true; - pb.UserExcludeFromFinal = true; } } } - } - // invalidate every brush. - generatedBrushes[i].Invalidate(true); - csgBounds.Encapsulate(generatedBrushes[i].GetBounds()); + // invalidate every brush. + generatedBrushes[i].Invalidate(true); + csgBounds.Encapsulate(generatedBrushes[i].GetBounds()); + } } // apply the generated csg bounds. From fc0fae46f9eded8d5f78abb356503388258a5e08 Mon Sep 17 00:00:00 2001 From: Henry de Jongh Date: Wed, 9 May 2018 19:24:26 +0200 Subject: [PATCH 2/2] Added a hidden surface removal algorithm for a single concave NoCSG brush. --- .../ShapeEditor/ShapeEditorBrush.cs | 46 +++++++++++++++---- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/Scripts/Brushes/CompoundBrushes/ShapeEditor/ShapeEditorBrush.cs b/Scripts/Brushes/CompoundBrushes/ShapeEditor/ShapeEditorBrush.cs index 439d9d4c..0283eb1d 100644 --- a/Scripts/Brushes/CompoundBrushes/ShapeEditor/ShapeEditorBrush.cs +++ b/Scripts/Brushes/CompoundBrushes/ShapeEditor/ShapeEditorBrush.cs @@ -318,19 +318,12 @@ public override void Invalidate(bool polygonsChanged) // we invalidate every brush after hidden surface removal. } - // assign the concave polygons to the brush. - if (!project.convexBrushes) - { - generatedBrushes[0].SetPolygons(concavePolygons.ToArray()); - csgBounds.Encapsulate(generatedBrushes[0].GetBounds()); - } - // we exclude hidden faces automatically. // this step will automatically optimize NoCSG output the same way additive brushes would have. // it also excludes a couple faces that CSG doesn't exclude due to floating point precision errors. // the latter is especially noticable with complex revolved shapes. - // can only use hidden surface removal on convex brushes. + // hidden surface removal for convex brushes. if (project.convexBrushes) { // compare each brush to another brush: @@ -372,6 +365,43 @@ public override void Invalidate(bool polygonsChanged) } } + // hidden surface removal for a concave brush. + else + { + List concavePolygonsCopy = concavePolygons.ToList(); + + // compare each polygon and find duplicates: + foreach (Polygon pa in concavePolygonsCopy) + { + foreach (Polygon pb in concavePolygonsCopy) + { + // can't be the same polygon. + if (pa == pb) continue; + + // check they both have this polygon: + bool identical = true; + foreach (Vertex va in pa.Vertices) + { + if (!pb.Vertices.Any(vb => vb.Position == va.Position)) + { + identical = false; + break; + } + } + // identical polygons on both brushes means it can be excluded: + if (identical) + { + concavePolygons.Remove(pa); + concavePolygons.Remove(pb); + } + } + } + + // invalidate the brush. + generatedBrushes[0].SetPolygons(concavePolygons.ToArray()); + csgBounds.Encapsulate(generatedBrushes[0].GetBounds()); + } + // apply the generated csg bounds. localBounds = csgBounds; m_LastKnownExtents = localBounds.extents;