When doing research for programmatically creating the circular sectors used for the cannon firing range, I encountered a few instances of other people having questions about this topic. So, I thought it might hopefully prove useful to others to share the code I wrote to solve this problem.

I mainly used an enum and two functions, one to calculate the points that define the circular sector and one that takes those points and generates a mesh from them.

Enum:

1 2 3 4 | /// <summary> /// Enum for indicating if an IEnumerable of points, describing a shape, do so in a clockwise or counterclockwise manner (starting from first element) /// </summary> public enum Orientation { Clockwise, CounterClockwise } |

Function to calculate points defining the defining points of a circular vector:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | /// <summary> /// Returns points defining a circular sector. /// </summary> /// <param name="center">Centr of circle.</param> /// <param name="forwardDirection">Forward direction of sector (direction vector pointing from center of circle to middle of sector arch).</param> /// <param name="radius">Circle radius.</param> /// <param name="centralAngleInDegrees">Central angle in degrees.</param> /// <param name="numPointsOnArc">Number of points on arc. Must at least be two.</param> /// <param name="orientation">Orientation the points describe the circular section in.</param> /// <param name="addFirstPointToEndAsWell">Add first point as last point as well? Useful e.g. for drawing whole shape by connecting all points in a matter of point_n --> point_n+1.</param> /// <returns>Points defining the circular sector in order defined by <see cref="orientation"/> in same space as input params <see cref="center"/> and <see cref="forwardDirection"/>. First point is at center of circle, /// all consecutive ones evenly spaced out on the arc.</returns> /// <exception cref="ArgumentException">If <see cref="numPointsOnArc"/> is smaller than 2.</exception> public static IEnumerable<Vector2> GetPointsDefiningCircularSector(Vector2 center, Vector2 forwardDirection, float radius, float centralAngleInDegrees, int numPointsOnArc, Orientation orientation, bool addFirstPointToEndAsWell = false) { //make sure at least two points on arc are requested because with fewer, ther couldn't be an arc. if (numPointsOnArc < 2) { var ex = new ArgumentException($"Param {nameof(numPointsOnArc)} must be 2 or greater. Actual value: {numPointsOnArc}"); Debug.LogException(ex); throw ex; } var allPoints = new List<Vector2>(); var pointsOnArc = new List<Vector2>(); var circlePlaneNormal = Vector3.Cross(Vector2.up, Vector2.right); // Could just use Vector3.down when working with circle entirely in x/y plane, like we do here, but this might be a little clearer. //since we're only taking in 2d cases/vectors with an x and y value and rotate around perpendicular axis, rotated vector should also alsway be in x/y space and we can safley convert to vector2 //and lose the z value which will be always 0 anyway. var leftEdgeDirection = (Vector2)(Quaternion.AngleAxis(centralAngleInDegrees / 2, circlePlaneNormal) * forwardDirection); var numArcSegments = numPointsOnArc - 1; var rotationBetweenArcSegmentsInDegrees = centralAngleInDegrees / numArcSegments; //finally add points allPoints.Add(center); pointsOnArc.Add(center + leftEdgeDirection.normalized * radius); for (var i = 1; i <= numArcSegments; i++) { var rotationInDegrees = -(i * rotationBetweenArcSegmentsInDegrees); //minus because we want clockwise rotation pointsOnArc.Add(center + (Vector2)(Quaternion.AngleAxis(rotationInDegrees, circlePlaneNormal) * (leftEdgeDirection.normalized * radius))); } if (orientation == Orientation.CounterClockwise) { pointsOnArc.Reverse(); } allPoints.AddRange(pointsOnArc); if (addFirstPointToEndAsWell) { //no need to generate a new vector since Vector 2 are structs and thus there's no reference link between first and last element, //even when passing in the same vector twice. So, no danger of two list items referencing the same instance (shouldn't matter //here anyway but just for matters of defensive programming ;) ) allPoints.Add(center); } return allPoints; } |

Function to generate a mesh from those points:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | /// <summary> /// Returns a mesh that forms a circular sector. /// </summary> /// <param name="vertices">Vertices of the mesh to be created. First vertex must be at center of circle, all consecutive ones evenly spaced out on the arc and in clockwise or counterclockwise manner. /// Correct sequence of vertices can be generated wit <see cref="CircularSectorHelper.GetPointsDefiningCircularSector"/></param> /// <returns></returns> public static UnityEngine.Mesh CreateCircularSectorMesh(IEnumerable<Vector2> vertices) { var verticesInVector3 = vertices.Select(vertex => (Vector3)vertex).ToArray(); var mesh = new UnityEngine.Mesh(); //To get segment number, we subtract 1 point because it's the center and another because there's one point more on the arc than there are arc segments. //e.g. one segment needs two points, two segments need three points, etc. var numberOfArcSegments = verticesInVector3.Length - 2; //using one triangle per segment on the arc. var triangles = new int[numberOfArcSegments * 3]; for (var i = 0; i < numberOfArcSegments; i++) { //start triangle at center of circular arc, then go clockwise(which is default for forward facing faces in unity). triangles[(i) * 3] = 0; triangles[(i) * 3 + 1] = i + 1; //+1 to skip first point, which is center, i*2 because we always go "two forward" for next arc section triangles[(i) * 3 + 2] = i + 2; //+2 to skip first point, which is center and second point, whci is first arc point, i*2 because we always go "two forward" for next arc section. } mesh.vertices = verticesInVector3; mesh.triangles = triangles; mesh.RecalculateBounds(); mesh.RecalculateNormals(); mesh.RecalculateTangents(); return mesh; } |

### Making things work in an arbitrary plane

As the code is right now, it only works with circular sectors that lie on the plane defined by the x- and y-axis (z -axis component of 0). There are mainly two reasons for this:

- I need the points defining the circular sector not only for mesh generation but also for generating a
*2DPolygonColider*, which expects the collider path to be defined in by a series of*Vector2*values. To avoid unnecessary data/space translations and data sanity checks, it was easiest to work with*Vector2*to begin with - A more generic approach would cause input computation/conversion to be a little more complex which in turn means more room for errors. And if we can avoid that without any drawbacks, why not?

However, it would be very easy to make this code more generic so it works with arbitrary planes. If you are in need of that and don’t know how to go about it, feel free to drop me a message in the comments.

### Words of caution

This code isn’t meant to be *the* solution to this problem. I’m certain there are more efficient ways to get the same results if you have a deeper knowledge of trigonometry, linear algebra, etc.

But for me, it’s good enough. It’s not the worst way you could solve it and more importantly readable to me. It’s good enough until it isn’t and should that moment ever come, I’ll change it then. I’m repeating myself but”Done is better than perfect” ðŸ˜‰

So, please, apply critical thinking when using this code and keep in mind that there are certainly other and probably more elegant solutions to this problem.