Skip to content

Commit 4ff2477

Browse files
committed
Added global symmetry axes to shapes (horizontal and/or vertical) with a shape inspector window.
1 parent c6c97a1 commit 4ff2477

18 files changed

Lines changed: 283 additions & 49 deletions

Scripts/Editor/ShapeEditorWindow.Grid.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,14 @@ internal IEnumerable<Segment> ForEachSelectedEdge()
501501
if (segment.selected && segment.next.selected)
502502
yield return segment;
503503
}
504+
505+
/// <summary>Iterates over all fully selected shapes.</summary>
506+
internal IEnumerable<Shape> ForEachSelectedShape()
507+
{
508+
foreach (var shape in project.shapes)
509+
if (shape.IsSelected())
510+
yield return shape;
511+
}
504512
}
505513
}
506514

Scripts/Editor/ShapeEditorWindow.User.cs

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,12 @@ internal void UserShowSineInspectorWindow()
397397
OpenWindow(new SineInspectorWindow());
398398
}
399399

400+
/// <summary>Displays the shape inspector window.</summary>
401+
internal void UserShowShapeInspectorWindow()
402+
{
403+
OpenWindow(new ShapeInspectorWindow());
404+
}
405+
400406
/// <summary>Displays the background settings window.</summary>
401407
internal void UserShowBackgroundSettingsWindow()
402408
{
@@ -451,26 +457,23 @@ internal void UserToggleGridSnapping()
451457
/// <summary>Sets the selected shapes as additive.</summary>
452458
internal void UserSetSelectedShapesAdditive()
453459
{
454-
foreach (var shape in project.shapes)
455-
if (shape.IsSelected())
456-
shape.booleanOperator = PolygonBooleanOperator.Union;
460+
foreach (var shape in ForEachSelectedShape())
461+
shape.booleanOperator = PolygonBooleanOperator.Union;
457462
}
458463

459464
/// <summary>Sets the selected shapes as subtractive.</summary>
460465
internal void UserSetSelectedShapesSubtractive()
461466
{
462-
foreach (var shape in project.shapes)
463-
if (shape.IsSelected())
464-
shape.booleanOperator = PolygonBooleanOperator.Difference;
467+
foreach (var shape in ForEachSelectedShape())
468+
shape.booleanOperator = PolygonBooleanOperator.Difference;
465469
}
466470

467471
/// <summary>Pushes the selected shapes to the front (for boolean operations).</summary>
468472
internal void UserPushSelectedShapesToFront()
469473
{
470474
var shapesToMove = new List<Shape>();
471-
foreach (var shape in project.shapes)
472-
if (shape.IsSelected())
473-
shapesToMove.Add(shape);
475+
foreach (var shape in ForEachSelectedShape())
476+
shapesToMove.Add(shape);
474477

475478
foreach (var shape in shapesToMove)
476479
{
@@ -483,9 +486,8 @@ internal void UserPushSelectedShapesToFront()
483486
internal void UserPushSelectedShapesToBack()
484487
{
485488
var shapesToMove = new List<Shape>();
486-
foreach (var shape in project.shapes)
487-
if (shape.IsSelected())
488-
shapesToMove.Add(shape);
489+
foreach (var shape in ForEachSelectedShape())
490+
shapesToMove.Add(shape);
489491

490492
foreach (var shape in shapesToMove)
491493
{
@@ -502,9 +504,8 @@ internal void UserPushSelectedShapesToBack()
502504
internal void UserDuplicateSelectedShapes()
503505
{
504506
var shapesToDuplicate = new List<Shape>();
505-
foreach (var shape in project.shapes)
506-
if (shape.IsSelected())
507-
shapesToDuplicate.Add(shape);
507+
foreach (var shape in ForEachSelectedShape())
508+
shapesToDuplicate.Add(shape);
508509

509510
if (shapesToDuplicate.Count > 0)
510511
{

Scripts/ShapeEditor/Generators/SegmentGenerator.Bezier.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@ private void Bezier_DrawSegments()
5858
var p3 = editor.GridPointToScreen(bezierPivot2.position);
5959
var p4 = editor.GridPointToScreen(segment.next.position);
6060

61-
// without snapping draw manually in screen space because we have a function for it.
61+
// without snapping and symmetry draw manually in screen space because we have a function for it.
6262
GL.Color((segment.selected && segment.next.selected) ? ShapeEditorWindow.segmentPivotOutlineColor : segment.shape.segmentColor);
63-
if (bezierGridSnapSize == 0f)
63+
if (bezierGridSnapSize == 0f && segment.shape.symmetryAxes == SimpleGlobalAxis.None)
6464
GLUtilities.DrawBezierLine(1.0f, p1, p2, p3, p4, bezierDetail);
6565
else
6666
DrawSegments(Bezier_ForEachSegmentPoint());

Scripts/ShapeEditor/Generators/SegmentGenerator.Linear.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ public partial class SegmentGenerator
99
private void Linear_DrawSegments()
1010
{
1111
// we just draw manually here.
12-
var p1 = editor.GridPointToScreen(segment.position);
13-
var p2 = editor.GridPointToScreen(segment.next.position);
14-
GLUtilities.DrawLine(1.0f, p1.x, p1.y, p2.x, p2.y, segment.selected ? ShapeEditorWindow.segmentPivotOutlineColor : segment.shape.segmentColor, segment.next.selected ? ShapeEditorWindow.segmentPivotOutlineColor : segment.shape.segmentColor);
12+
DrawGridLine(1.0f, segment.position, segment.next.position, segment.selected ? ShapeEditorWindow.segmentPivotOutlineColor : segment.shape.segmentColor, segment.next.selected ? ShapeEditorWindow.segmentPivotOutlineColor : segment.shape.segmentColor);
1513
}
1614
}
1715
}

Scripts/ShapeEditor/Generators/SegmentGenerator.cs

Lines changed: 92 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -155,18 +155,103 @@ public IEnumerable<float2> ForEachAdditionalSegmentPoint()
155155
/// <param name="iterator">The generated segment points in grid coordinates.</param>
156156
private void DrawSegments(IEnumerable<float2> iterator)
157157
{
158-
float2 last = editor.GridPointToScreen(segment.position);
159-
GL.Color((segment.selected && segment.next.selected) ? ShapeEditorWindow.segmentPivotOutlineColor : segment.shape.segmentColor);
158+
float2 last = segment.position;
159+
var color = (segment.selected && segment.next.selected) ? ShapeEditorWindow.segmentPivotOutlineColor : segment.shape.segmentColor;
160160

161161
foreach (var point in iterator)
162162
{
163-
float2 next = editor.GridPointToScreen(point);
164-
GLUtilities.DrawLine(1.0f, last.x, last.y, next.x, next.y);
165-
last = next;
163+
DrawGridLine(1.0f, last, point, color);
164+
last = point;
166165
}
167166

168-
float2 final = editor.GridPointToScreen(segment.next.position);
169-
GLUtilities.DrawLine(1.0f, last.x, last.y, final.x, final.y);
167+
DrawGridLine(1.0f, last, segment.next.position, color);
168+
}
169+
170+
private static readonly float2 mirrorXY = new float2(-1f, -1f);
171+
private static readonly float2 mirrorX = new float2(-1f, 1f);
172+
private static readonly float2 mirrorY = new float2(1f, -1f);
173+
174+
/// <summary>
175+
/// <see cref="GLUtilities.DrawLine"/> in grid coordinates that takes shape symmetry into account.
176+
/// </summary>
177+
private void DrawGridLine(float thickness, float2 from, float2 to, Color beginColor, Color endColor)
178+
{
179+
var p1 = editor.GridPointToScreen(from);
180+
var p2 = editor.GridPointToScreen(to);
181+
GLUtilities.DrawLine(thickness, p1.x, p1.y, p2.x, p2.y, beginColor, endColor);
182+
183+
var symmetry = segment.shape.symmetryAxes;
184+
if (symmetry != SimpleGlobalAxis.None)
185+
{
186+
bool flipX = symmetry.HasFlag(SimpleGlobalAxis.Horizontal);
187+
bool flipY = symmetry.HasFlag(SimpleGlobalAxis.Vertical);
188+
189+
beginColor.a = 0.75f;
190+
endColor.a = 0.75f;
191+
192+
if (flipX && flipY)
193+
{
194+
p1 = editor.GridPointToScreen(from * mirrorXY);
195+
p2 = editor.GridPointToScreen(to * mirrorXY);
196+
GLUtilities.DrawLine(thickness, p1.x, p1.y, p2.x, p2.y, beginColor, endColor);
197+
}
198+
199+
if (flipX)
200+
{
201+
p1 = editor.GridPointToScreen(from * mirrorX);
202+
p2 = editor.GridPointToScreen(to * mirrorX);
203+
GLUtilities.DrawLine(thickness, p1.x, p1.y, p2.x, p2.y, beginColor, endColor);
204+
}
205+
206+
if (flipY)
207+
{
208+
p1 = editor.GridPointToScreen(from * mirrorY);
209+
p2 = editor.GridPointToScreen(to * mirrorY);
210+
GLUtilities.DrawLine(thickness, p1.x, p1.y, p2.x, p2.y, beginColor, endColor);
211+
}
212+
}
213+
}
214+
215+
/// <summary>
216+
/// <see cref="GLUtilities.DrawLine"/> in grid coordinates that takes shape symmetry into account.
217+
/// </summary>
218+
private void DrawGridLine(float thickness, float2 from, float2 to, Color color)
219+
{
220+
var p1 = editor.GridPointToScreen(from);
221+
var p2 = editor.GridPointToScreen(to);
222+
GL.Color(color);
223+
GLUtilities.DrawLine(thickness, p1.x, p1.y, p2.x, p2.y);
224+
225+
var symmetry = segment.shape.symmetryAxes;
226+
if (symmetry != SimpleGlobalAxis.None)
227+
{
228+
bool flipX = symmetry.HasFlag(SimpleGlobalAxis.Horizontal);
229+
bool flipY = symmetry.HasFlag(SimpleGlobalAxis.Vertical);
230+
231+
color.a = 0.75f;
232+
GL.Color(color);
233+
234+
if (flipX && flipY)
235+
{
236+
p1 = editor.GridPointToScreen(from * mirrorXY);
237+
p2 = editor.GridPointToScreen(to * mirrorXY);
238+
GLUtilities.DrawLine(thickness, p1.x, p1.y, p2.x, p2.y);
239+
}
240+
241+
if (flipX)
242+
{
243+
p1 = editor.GridPointToScreen(from * mirrorX);
244+
p2 = editor.GridPointToScreen(to * mirrorX);
245+
GLUtilities.DrawLine(thickness, p1.x, p1.y, p2.x, p2.y);
246+
}
247+
248+
if (flipY)
249+
{
250+
p1 = editor.GridPointToScreen(from * mirrorY);
251+
p2 = editor.GridPointToScreen(to * mirrorY);
252+
GLUtilities.DrawLine(thickness, p1.x, p1.y, p2.x, p2.y);
253+
}
254+
}
170255
}
171256

172257
/// <summary>Applies a generator by inserting the generated points as new segments.</summary>

Scripts/ShapeEditor/Project.cs

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -101,20 +101,24 @@ public PolygonMesh GenerateConvexPolygons(bool useHoles = true)
101101
for (int i = 0; i < shapesCount; i++)
102102
{
103103
var shape = shapes[i];
104-
var shapePolygon = shape.GenerateConcavePolygon(true);
105-
var shapePolyboolPolygon = shapePolygon.ToPolybool();
106-
107-
if (shape.booleanOperator == PolygonBooleanOperator.Union)
108-
{
109-
var seg2 = polyBool.segments(shapePolyboolPolygon);
110-
var comb = polyBool.combine(finalSegmentList, seg2);
111-
finalSegmentList = polyBool.selectUnion(comb);
112-
}
113-
else
104+
var shapePolygons = shape.GenerateConcavePolygons(true);
105+
for (int j = 0; j < shapePolygons.Length; j++)
114106
{
115-
var seg2 = polyBool.segments(shapePolyboolPolygon);
116-
var comb = polyBool.combine(finalSegmentList, seg2);
117-
finalSegmentList = polyBool.selectDifference(comb);
107+
var shapePolygon = shapePolygons[j];
108+
var shapePolyboolPolygon = shapePolygon.ToPolybool();
109+
110+
if (shape.booleanOperator == PolygonBooleanOperator.Union)
111+
{
112+
var seg2 = polyBool.segments(shapePolyboolPolygon);
113+
var comb = polyBool.combine(finalSegmentList, seg2);
114+
finalSegmentList = polyBool.selectUnion(comb);
115+
}
116+
else
117+
{
118+
var seg2 = polyBool.segments(shapePolyboolPolygon);
119+
var comb = polyBool.combine(finalSegmentList, seg2);
120+
finalSegmentList = polyBool.selectDifference(comb);
121+
}
118122
}
119123
}
120124

Scripts/ShapeEditor/Shape.cs

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ public class Shape
1919
[SerializeField]
2020
public PolygonBooleanOperator booleanOperator = PolygonBooleanOperator.Union;
2121

22+
/// <summary>The global symmetry axes of the shape (horizontal and/or vertical).</summary>
23+
[SerializeField]
24+
public SimpleGlobalAxis symmetryAxes = SimpleGlobalAxis.None;
25+
2226
/// <summary>Creates a new shape.</summary>
2327
public Shape()
2428
{
@@ -203,24 +207,26 @@ public void Validate()
203207
}
204208

205209
/// <summary>[2D] Generates the concave polygon representing this shape.</summary>
206-
/// <param name="flipY">Whether to flip the shape on the Y-axis.</param>
210+
/// <param name="mirror">Whether to mirror the shape horizontally and/or vertically.</param>
207211
/// <returns>The collection of vertices.</returns>
208-
public Polygon GenerateConcavePolygon(bool flipY)
212+
public Polygon GenerateConcavePolygon(SimpleGlobalAxis mirror = SimpleGlobalAxis.None)
209213
{
210214
Polygon vertices = new Polygon();
211-
float flip = flipY ? -1.0f : 1.0f;
215+
216+
float flipX = mirror.HasFlag(SimpleGlobalAxis.Horizontal) ? -1.0f : 1.0f;
217+
float flipY = mirror.HasFlag(SimpleGlobalAxis.Vertical) ? -1.0f : 1.0f;
212218

213219
// for every segment in the shape:
214220
var segmentsCount = segments.Count;
215221
for (int j = 0; j < segmentsCount; j++)
216222
{
217223
// add the segment point.
218224
var segment = segments[j];
219-
vertices.Add(new Vertex(segment.position.x, flip * segment.position.y));
225+
vertices.Add(new Vertex(flipX * segment.position.x, flipY * segment.position.y));
220226

221227
// have the segment generator add additional points.
222228
foreach (var point in segments[j].generator.ForEachAdditionalSegmentPoint())
223-
vertices.Add(new Vertex(point.x, flip * point.y));
229+
vertices.Add(new Vertex(flipX * point.x, flipY * point.y));
224230
}
225231

226232
// ensure the vertices are counter clockwise.
@@ -229,6 +235,28 @@ public Polygon GenerateConcavePolygon(bool flipY)
229235
return vertices;
230236
}
231237

238+
/// <summary>
239+
/// [2D] For symmetry generates multiple concave polygons representing this shape.
240+
/// </summary>
241+
/// <param name="flipY">Whether to mirror the shapes vertically.</param>
242+
/// <returns>The collections of vertices.</returns>
243+
public Polygon[] GenerateConcavePolygons(bool flipY)
244+
{
245+
Polygon original = GenerateConcavePolygon(flipY ? SimpleGlobalAxis.Vertical : SimpleGlobalAxis.None);
246+
Polygon mirrorX = symmetryAxes.HasFlag(SimpleGlobalAxis.Horizontal) ? GenerateConcavePolygon(SimpleGlobalAxis.Horizontal | (flipY ? SimpleGlobalAxis.Vertical : SimpleGlobalAxis.None)) : null;
247+
Polygon mirrorY = symmetryAxes.HasFlag(SimpleGlobalAxis.Vertical) ? GenerateConcavePolygon(flipY ? SimpleGlobalAxis.None : SimpleGlobalAxis.Vertical) : null;
248+
Polygon mirrorXY = symmetryAxes.HasFlag(SimpleGlobalAxis.Horizontal | SimpleGlobalAxis.Vertical) ? GenerateConcavePolygon(SimpleGlobalAxis.Horizontal | (flipY ? SimpleGlobalAxis.None : SimpleGlobalAxis.Vertical)) : null;
249+
250+
if (mirrorX != null && mirrorY != null)
251+
return new Polygon[] { original, mirrorX, mirrorY, mirrorXY };
252+
if (mirrorX != null)
253+
return new Polygon[] { original, mirrorX };
254+
if (mirrorY != null)
255+
return new Polygon[] { original, mirrorY };
256+
257+
return new Polygon[] { original };
258+
}
259+
232260
// original source code from https://github.com/Genbox/VelcroPhysics/ (see Licenses/VelcroPhysics.txt).
233261
/// <summary>[2D] Winding number test for a point in a polygon.</summary>
234262
/// See more info about the algorithm here: http://softsurfer.com/Archive/algorithm_0103/algorithm_0103.htm
@@ -239,7 +267,7 @@ public Polygon GenerateConcavePolygon(bool flipY)
239267
/// </returns>
240268
public int ContainsPoint(Vector3 point)
241269
{
242-
var polygon = GenerateConcavePolygon(false);
270+
var polygon = GenerateConcavePolygon();
243271
return polygon.ContainsPoint2D(ref point);
244272
}
245273

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#if UNITY_EDITOR
2+
3+
namespace AeternumGames.ShapeEditor
4+
{
5+
/// <summary>The global axes horizontal and vertical as flags.</summary>
6+
[System.Flags]
7+
public enum SimpleGlobalAxis
8+
{
9+
None,
10+
Horizontal,
11+
Vertical
12+
}
13+
}
14+
15+
#endif

Scripts/ShapeEditor/Shared/SimpleGlobalAxis.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Scripts/ShapeEditor/Windows/Inspectors.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)