Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaClient/ClientSource/GUI/ShapeExtensions.cs
2021-11-18 21:34:30 +09:00

359 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Linq;
namespace Barotrauma
{
/// <summary>
/// Sprite batch extensions for drawing primitive shapes
/// Modified from: https://github.com/craftworkgames/MonoGame.Extended/blob/develop/Source/MonoGame.Extended/ShapeExtensions.cs
/// </summary>
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;
}
/// <summary>
/// Draws a closed polygon from a <see cref="Polygon" /> shape
/// </summary>
public static void DrawPolygon(this SpriteBatch spriteBatch, Vector2 position, Polygon polygon, Color color,
float thickness = 1f)
{
DrawPolygon(spriteBatch, position, polygon.Vertices, color, thickness);
}
/// <summary>
/// Draws a closed polygon from an array of points
/// </summary>
public static void DrawPolygon(this SpriteBatch spriteBatch, Vector2 offset, IReadOnlyList<Vector2> 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);
}
/// <summary>
/// Draws a closed polygon from an array of points
/// </summary>
public static void DrawPolygonInner(this SpriteBatch spriteBatch, Vector2 offset, IReadOnlyList<Vector2> 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 = GetTexture(spriteBatch);
spriteBatch.Draw(GetTexture(spriteBatch), 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);
}
/// <summary>
/// Draws a line from point1 to point2 with an offset
/// </summary>
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);
}
/// <summary>
/// Draws a line from point1 to point2 with an offset
/// </summary>
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, point1, distance, angle, color, thickness);
}
/// <summary>
/// Draws a line from point1 to point2 with an offset
/// </summary>
public static void DrawLine(this SpriteBatch spriteBatch, Vector2 point, float length, float angle, Color color,
float thickness = 1f)
{
var origin = new Vector2(0f, 0.5f);
var scale = new Vector2(length, thickness);
spriteBatch.Draw(GetTexture(spriteBatch), point, null, color, angle, origin, scale, SpriteEffects.None, 0);
}
/// <summary>
/// Draws a point at the specified x, y position. The center of the point will be at the position.
/// </summary>
public static void DrawPoint(this SpriteBatch spriteBatch, float x, float y, Color color, float size = 1f)
{
DrawPoint(spriteBatch, new Vector2(x, y), color, size);
}
/// <summary>
/// Draws a point at the specified position. The center of the point will be at the position.
/// </summary>
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);
}
}
/// <summary>
/// Original source: https://github.com/craftworkgames/MonoGame.Extended/blob/develop/Source/MonoGame.Extended/Shapes/Polygon.cs
/// </summary>
public class Polygon : IEquatable<Polygon>
{
public Polygon(IEnumerable<Vector2> 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());
}
}
}
}