using System; using System.Numerics; using System.Text; using Robust.Client.GameObjects; using Robust.Shared.Collections; using Robust.Shared.GameObjects; using Robust.Shared.Graphics; using Robust.Shared.Maths; namespace Robust.Client.Graphics { public abstract class DrawingHandleScreen : DrawingHandleBase { protected DrawingHandleScreen(Texture white) : base(white) { } /// /// Simialr to DrawLine but has dashes interspersed. /// /// Offset from the start of the line. /// How long a dash is. /// How long the gap between dashes is. public void DrawDottedLine(Vector2 from, Vector2 to, Color color, float offset = 0f, float dashSize = 8f, float gapSize = 2f) { var lineVector = to - from; // No drawing for you. if (lineVector.LengthSquared() < 10f * float.Epsilon) return; var lineAndGap = gapSize + dashSize; var lines = new ValueList(); // Minimum distance. if (lineVector.Length() < lineAndGap) { lines.Add(from); lines.Add(to); } else { var maxLength = lineVector.Length(); var normalizedLine = lineVector.Normalized(); var dashVector = normalizedLine * dashSize; var gapVector = normalizedLine * gapSize; var position = from; offset %= (dashSize + gapSize); var length = offset; var dashLength = dashSize; // If offset is less than gap size then start with a gap // otherwise start with a partial line if (offset > 0f) { if (offset < gapSize) { position += normalizedLine * offset; length += offset; } else { dashLength = (offset - gapSize); } } while (length < maxLength) { lines.Add(position); position += normalizedLine * dashLength; var lengthFromStart = (position - from).Length(); // if over length then cap the thing. if (lengthFromStart > maxLength) { position = to; } lines.Add(position); dashLength = dashVector.Length(); position += gapVector; length = (position - from).Length(); } } DrawPrimitives(DrawPrimitiveTopology.LineList, lines.Span, color); } public abstract void DrawRect(UIBox2 rect, Color color, bool filled = true); public abstract void DrawTextureRectRegion(Texture texture, UIBox2 rect, UIBox2? subRegion = null, Color? modulate = null); public override void DrawTexture(Texture texture, Vector2 position, Color? modulate = null) { CheckDisposed(); DrawTextureRect(texture, UIBox2.FromDimensions(position, texture.Size), modulate); } public void DrawTextureRect(Texture texture, UIBox2 rect, Color? modulate = null) { CheckDisposed(); DrawTextureRectRegion(texture, rect, null, modulate); } public override void DrawCircle(Vector2 position, float radius, Color color, bool filled = true) { const int segments = 64; Span buffer = stackalloc Vector2[segments + 1]; for (var i = 0; i <= segments; i++) { var angle = i / (float) segments * MathHelper.TwoPi; var pos = new Vector2(MathF.Sin(angle), MathF.Cos(angle)); buffer[i] = position + pos * radius; } DrawPrimitiveTopology type = filled ? DrawPrimitiveTopology.TriangleFan : DrawPrimitiveTopology.LineStrip; DrawPrimitives(type, buffer, color); } /// /// Draw a simple string to the screen at the specified position. /// /// /// This method is primarily intended for debug purposes and does not handle things like UI scaling. /// /// /// The space taken up (horizontal and vertical) by the text. /// /// The font to render with. /// The top-left corner to start drawing text at. /// The text to draw. /// The color of text to draw. public Vector2 DrawString(Font font, Vector2 pos, string str, Color color) => DrawString(font, pos, str, 1, color); /// /// Draw a simple string to the screen at the specified position. /// /// /// This method is primarily intended for debug purposes and does not handle things like UI scaling. /// /// /// The space taken up (horizontal and vertical) by the text. /// /// The font to render with. /// The top-left corner to start drawing text at. /// The text to draw. public Vector2 DrawString(Font font, Vector2 pos, string str) => DrawString(font, pos, str, Color.White); public Vector2 DrawString(Font font, Vector2 pos, ReadOnlySpan str, float scale, Color color, TextOutline? outline = null) { var advanceTotal = Vector2.Zero; var baseLine = new Vector2(pos.X, font.GetAscent(scale) + pos.Y); var lineHeight = font.GetLineHeight(scale); if (outline is { } outlineSettings) { foreach (var rune in str.EnumerateRunes()) { if (rune == new Rune('\n')) { baseLine.X = pos.X; baseLine.Y += lineHeight; continue; } var advance = font.DrawCharOutline(this, rune, baseLine, scale, outlineSettings); baseLine.X += advance; } baseLine = new Vector2(pos.X, font.GetAscent(scale) + pos.Y); } foreach (var rune in str.EnumerateRunes()) { if (rune == new Rune('\n')) { baseLine.X = pos.X; baseLine.Y += lineHeight; advanceTotal.Y += lineHeight; continue; } var advance = font.DrawChar(this, rune, baseLine, scale, color); advanceTotal.X += advance; baseLine.X += advance; } return advanceTotal; } public Vector2 GetDimensions(Font font, ReadOnlySpan str, float scale) { var baseLine = new Vector2(0f, font.GetAscent(scale)); var lineHeight = font.GetLineHeight(scale); var advanceTotal = new Vector2(0f, lineHeight); foreach (var rune in str.EnumerateRunes()) { if (rune == new Rune('\n')) { baseLine.Y += lineHeight; advanceTotal.Y += lineHeight; baseLine.X = 0f; continue; } var metrics = font.GetCharMetrics(rune, scale); if (metrics == null) continue; var advance = metrics.Value.Advance; baseLine += new Vector2(advance, 0); advanceTotal.X = MathF.Max(baseLine.X, advanceTotal.X); } return advanceTotal; } /// /// Draws an entity. /// /// The entity to draw /// The local pixel position where the entity should be drawn. /// Scales the drawn entity /// The world rotation to use when drawing the entity. /// This impacts the sprites RSI direction. Null will retrieve the entity's actual rotation. /// /// The effective "eye" angle. /// This will cause the entity to be rotated, and may also affect the RSI directions. /// Draws the entity at some given angle. /// RSI direction override. /// The entity's sprite component /// The entity's transform component. /// Only required if is null. /// The transform system public abstract void DrawEntity(EntityUid entity, Vector2 position, Vector2 scale, Angle? worldRot, Angle eyeRotation = default, Shared.Maths.Direction? overrideDirection = null, SpriteComponent? sprite = null, TransformComponent? xform = null, SharedTransformSystem? xformSystem = null); } }