using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System.Linq; namespace Barotrauma { /// /// Sprite batch extensions for drawing primitive shapes /// Modified from: https://github.com/craftworkgames/MonoGame.Extended/blob/develop/Source/MonoGame.Extended/ShapeExtensions.cs /// public static class ShapeExtensions { private static Texture2D _whitePixelTexture; private static Texture2D GetTexture(SpriteBatch spriteBatch) { if (_whitePixelTexture == null) { CrossThread.RequestExecutionOnMainThread(() => { _whitePixelTexture = new Texture2D(spriteBatch.GraphicsDevice, 1, 1, false, SurfaceFormat.Color); _whitePixelTexture.SetData(new[] { Color.White }); }); } return _whitePixelTexture; } /// /// Draws a closed polygon from a shape /// public static void DrawPolygon(this SpriteBatch spriteBatch, Vector2 position, Polygon polygon, Color color, float thickness = 1f) { DrawPolygon(spriteBatch, position, polygon.Vertices, color, thickness); } /// /// Draws a closed polygon from an array of points /// public static void DrawPolygon(this SpriteBatch spriteBatch, Vector2 offset, IReadOnlyList points, Color color, float thickness = 1f) { if (points.Count == 0) return; if (points.Count == 1) { DrawPoint(spriteBatch, points[0], color, (int)thickness); return; } var texture = GetTexture(spriteBatch); for (var i = 0; i < points.Count - 1; i++) DrawPolygonEdge(spriteBatch, points[i] + offset, points[i + 1] + offset, color, thickness); DrawPolygonEdge(spriteBatch, points[points.Count - 1] + offset, points[0] + offset, color, thickness); } /// /// Draws a closed polygon from an array of points /// public static void DrawPolygonInner(this SpriteBatch spriteBatch, Vector2 offset, IReadOnlyList points, Color color, float thickness = 1f) { if (points.Count == 0) { return; } if (points.Count == 1) { DrawPoint(spriteBatch, points[0], color, (int)thickness); return; } for (var i = 0; i < points.Count - 1; i++) { Vector2 point1 = points[i] + offset, point2 = points[i + 1] + offset; DrawPolygonEdgeInner(spriteBatch, point1, point2, color, thickness); } DrawPolygonEdgeInner(spriteBatch, points[^1] + offset, points[0] + offset, color, thickness); } private static void DrawPolygonEdgeInner(SpriteBatch spriteBatch, Vector2 point1, Vector2 point2, Color color, float thickness) { var length = Vector2.Distance(point1, point2) + thickness; var angle = (float)Math.Atan2(point2.Y - point1.Y, point2.X - point1.X); var scale = new Vector2(length, thickness); Vector2 middle = new Vector2((point1.X + point2.X) / 2f, (point1.Y + point2.Y) / 2f); Texture2D tex = GUI.WhiteTexture; spriteBatch.Draw(tex, middle, null, color, angle, new Vector2(tex.Width / 2f, tex.Height / 2f), scale, SpriteEffects.None, 0); } private static void DrawPolygonEdge(SpriteBatch spriteBatch, Vector2 point1, Vector2 point2, Color color, float thickness) { var length = Vector2.Distance(point1, point2); var angle = (float)Math.Atan2(point2.Y - point1.Y, point2.X - point1.X); var scale = new Vector2(length, thickness); spriteBatch.Draw(GetTexture(spriteBatch), point1, null, color, angle, Vector2.Zero, scale, SpriteEffects.None, 0); } /// /// Draws a line from point1 to point2 with an offset /// public static void DrawLine(this SpriteBatch spriteBatch, float x1, float y1, float x2, float y2, Color color, float thickness = 1f) { DrawLine(spriteBatch, new Vector2(x1, y1), new Vector2(x2, y2), color, thickness); } public static void DrawLineWithTexture(this SpriteBatch spriteBatch, Texture2D tex, Vector2 point1, Vector2 point2, Color color, float thickness = 1f) { // calculate the distance between the two vectors var distance = Vector2.Distance(point1, point2); // calculate the angle between the two vectors var angle = (float)Math.Atan2(point2.Y - point1.Y, point2.X - point1.X); DrawLine(spriteBatch, tex, point1, distance, angle, color, thickness); } /// /// Draws a line from point1 to point2 with an offset /// public static void DrawLine(this SpriteBatch spriteBatch, Vector2 point1, Vector2 point2, Color color, float thickness = 1f) { // calculate the distance between the two vectors var distance = Vector2.Distance(point1, point2); // calculate the angle between the two vectors var angle = (float)Math.Atan2(point2.Y - point1.Y, point2.X - point1.X); DrawLine(spriteBatch, GetTexture(spriteBatch), point1, distance, angle, color, thickness); } /// /// Draws a line from point1 to point2 with an offset /// public static void DrawLine(this SpriteBatch spriteBatch, Texture2D tex, Vector2 point, float length, float angle, Color color, float thickness = 1f) { var origin = new Vector2(0f, tex.Height / 2f); var scale = new Vector2(length / tex.Width, thickness / tex.Height); spriteBatch.Draw(tex, point, null, color, angle, origin, scale, SpriteEffects.None, 0); } /// /// Draws a point at the specified x, y position. The center of the point will be at the position. /// public static void DrawPoint(this SpriteBatch spriteBatch, float x, float y, Color color, float size = 1f) { DrawPoint(spriteBatch, new Vector2(x, y), color, size); } /// /// Draws a point at the specified position. The center of the point will be at the position. /// public static void DrawPoint(this SpriteBatch spriteBatch, Vector2 position, Color color, float size = 1f) { var offset = new Vector2(0.5f) - new Vector2(size * 0.5f); spriteBatch.Draw(GetTexture(spriteBatch), position + offset, null, color, 0.0f, Vector2.Zero, new Vector2(size), SpriteEffects.None, 0); } public static void DrawCircle(this SpriteBatch spriteBatch, Vector2 center, float radius, int sides, Color color, float thickness = 1f) { DrawPolygon(spriteBatch, center, CreateCircle(radius, sides), color, thickness); } public static void DrawCircle(this SpriteBatch spriteBatch, float x, float y, float radius, int sides, Color color, float thickness = 1f) { DrawPolygon(spriteBatch, new Vector2(x, y), CreateCircle(radius, sides), color, thickness); } public static void DrawSector(this SpriteBatch spriteBatch, Vector2 center, float radius, float radians, int sides, Color color, float offset = 0, float thickness = 1) { DrawPolygon(spriteBatch, center, CreateSector(radius, sides, radians, offset), color, thickness); } private static Vector2[] CreateSector(double radius, int sides, float radians, float offset = 0) { //circle sectors need one extra point at the center var points = new Vector2[radians < MathHelper.TwoPi ? sides + 1 : sides]; var step = radians / sides; double theta = offset; for (var i = 0; i < sides; i++) { points[i] = new Vector2((float)Math.Cos(theta), (float)Math.Sin(theta)) * (float)radius; theta += step; } return points; } private static Vector2[] CreateCircle(double radius, int sides) { return CreateSector(radius, sides, MathHelper.TwoPi); } } /// /// Original source: https://github.com/craftworkgames/MonoGame.Extended/blob/develop/Source/MonoGame.Extended/Shapes/Polygon.cs /// public class Polygon : IEquatable { public Polygon(IEnumerable vertices) { _localVertices = vertices.ToArray(); _transformedVertices = _localVertices; _offset = Vector2.Zero; _rotation = 0; _scale = Vector2.One; _isDirty = false; } private readonly Vector2[] _localVertices; private Vector2[] _transformedVertices; private Vector2 _offset; private float _rotation; private Vector2 _scale; private bool _isDirty; public Vector2[] Vertices { get { if (_isDirty) { _transformedVertices = GetTransformedVertices(); _isDirty = false; } return _transformedVertices; } } public float Left { get { return Vertices.Min(v => v.X); } } public float Right { get { return Vertices.Max(v => v.X); } } public float Top { get { return Vertices.Min(v => v.Y); } } public float Bottom { get { return Vertices.Max(v => v.Y); } } public void Offset(Vector2 amount) { _offset += amount; _isDirty = true; } public void Rotate(float amount) { _rotation += amount; _isDirty = true; } public void Scale(Vector2 amount) { _scale += amount; _isDirty = true; } private Vector2[] GetTransformedVertices() { var newVertices = new Vector2[_localVertices.Length]; var isScaled = _scale != Vector2.One; for (var i = 0; i < _localVertices.Length; i++) { var p = _localVertices[i]; if (isScaled) p *= _scale; // ReSharper disable once CompareOfFloatsByEqualityOperator if (_rotation != 0) { var cos = (float)Math.Cos(_rotation); var sin = (float)Math.Sin(_rotation); p = new Vector2(cos * p.X - sin * p.Y, sin * p.X + cos * p.Y); } newVertices[i] = p + _offset; } return newVertices; } public Polygon TransformedCopy(Vector2 offset, float rotation, Vector2 scale) { var polygon = new Polygon(_localVertices); polygon.Offset(offset); polygon.Rotate(rotation); polygon.Scale(scale - Vector2.One); return new Polygon(polygon.Vertices); } public bool Contains(Vector2 point) { return Contains(point.X, point.Y); } public bool Contains(float x, float y) { var intersects = 0; var vertices = Vertices; for (var i = 0; i < vertices.Length; i++) { var x1 = vertices[i].X; var y1 = vertices[i].Y; var x2 = vertices[(i + 1) % vertices.Length].X; var y2 = vertices[(i + 1) % vertices.Length].Y; if ((((y1 <= y) && (y < y2)) || ((y2 <= y) && (y < y1))) && (x < (x2 - x1) / (y2 - y1) * (y - y1) + x1)) intersects++; } return (intersects & 1) == 1; } public static bool operator ==(Polygon a, Polygon b) { return a.Equals(b); } public static bool operator !=(Polygon a, Polygon b) { return !(a == b); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; return obj is Polygon && Equals((Polygon)obj); } public bool Equals(Polygon other) { return Vertices.SequenceEqual(other.Vertices); } public override int GetHashCode() { unchecked { return Vertices.Aggregate(27, (current, v) => current + 13 * current + v.GetHashCode()); } } } }