Beginn des Ports des G3-HUD (Referenz luneraria/g3, 1b540f2). - Entscheidung UI Toolkit statt uGUI nach kurzer Bewertung und einer Probe: Panel rendert im Batchmodus in eine 1920x1080-Textur (Skalierung wie Godot canvas_items/expand, 1600x960), gefaste Verlaufsplatten, alle 50 Symbole, Schriften aus den variablen Fonts der Referenz. - Sichtmodell portiert (LuneHudModel, LuneLaneThreat, LuneStats) mit den Privatsphäre-Regeln der Referenz: Gold, Entwurf, Rückgängig und neue Buchungen nur für den eigenen Sitz; Lanes, Gebäude, Boss und Wellen aus öffentlichem Zustand. - Menschlicher Sitz in LuneMatchRunner/LuneMatchDriver: Bots planen die übrigen fünf Sitze, die Planung wartet auf Bereit; Tempo 1x/2x/4x und Pause nur im Kampf. Bot-Partien unverändert. - Theme-Grundlagen: Farben und Zahlenformate, Platten (forged_style.gd), Vektorsymbole (lune_glyphs.gd) mit Painter2D, statische SDF-Atlanten aus den benannten Instanzen von Cantarell und Cinzel (OFL). - Tests: EditMode 23/23 (12 neue: gleiches öffentliches Bild für alle Sitze, fremde Entwürfe privat, private Boss-Lane-Wahl, Lane-Bedrohung, Bosszustände, nächste Welle, Bereit-Sperre, Formate, menschlicher Sitz); PlayMode 3/3, Fingerabdrücke der Bot-Partien unverändert (DCE95C96556F3A1A, 7B8E43AEFC0D60C5). - Dokumentation: U3-UI (Bewertung, U3A), U5-VISUAL (Vorgaben des Projektinhabers: stilisierter Realismus für die ganze Welt, erst in U5). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
482 lines
25 KiB
C#
482 lines
25 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
|
|
namespace Luneraria.UI
|
|
{
|
|
/// <summary>
|
|
/// Vector symbols of the HUD (port of scripts/luneraria/ui/theme/lune_glyphs.gd), drawn from a
|
|
/// unit square so they stay sharp at every UI scale. Team emblems follow the G2 world: Blau =
|
|
/// Mondsichel (round shapes), Rot = Sonnenscheibe (rayed, angular), the grotto = hexagon.
|
|
/// </summary>
|
|
public static class LuneGlyphs
|
|
{
|
|
public static readonly string[] Names = { "coin", "emblem_0", "emblem_1", "emblem_2", "shape_0", "shape_1", "shape_2",
|
|
"tank", "melee", "ranged", "splash", "support", "siege", "crown", "champion", "hourglass", "tower",
|
|
"ancient", "warning", "check", "close", "bot", "offline", "clock", "heart", "tempo", "range", "armor",
|
|
"move", "star", "lock", "up", "lane_top", "lane_mid", "lane_bot", "grotto", "pause", "play", "menu",
|
|
"gear", "human", "sword", "basaltschild", "glutklinge", "rankenmantel", "funkenfokus", "quellamulett",
|
|
"kristallsplitter", "hero", "skull" };
|
|
|
|
const float Tau = Mathf.PI * 2f;
|
|
static readonly Dictionary<string, Vector2[]> Cache = new Dictionary<string, Vector2[]>();
|
|
|
|
/// <summary>Draws one symbol into rect (unit square centred, aspect kept). `accent` is an optional second colour for inner details.</summary>
|
|
public static void Draw(Painter2D painter, string glyph, Rect rect, Color color, Color accent = default)
|
|
{
|
|
if (string.IsNullOrEmpty(glyph))
|
|
return;
|
|
var f = new Frame(rect);
|
|
Color dark = accent.a > 0f ? accent : new Color(color.r * .45f, color.g * .45f, color.b * .45f, color.a);
|
|
switch (glyph)
|
|
{
|
|
case "coin":
|
|
{
|
|
Vector2 c = f.P(.5f, .5f);
|
|
float r = f.S * .42f;
|
|
Disc(painter, c, r, color);
|
|
ArcLine(painter, c, r, 0f, Tau, LuneUiTheme.Darkened(color, .35f), Mathf.Max(1f, r * .12f));
|
|
ArcLine(painter, c, r * .62f, 0f, Tau, LuneUiTheme.Darkened(color, .28f), Mathf.Max(1f, r * .1f));
|
|
ArcLine(painter, c, r * .8f, Mathf.PI * 1.1f, Mathf.PI * 1.45f, new Color(1f, 1f, 1f, .55f * color.a), Mathf.Max(1f, r * .1f));
|
|
break;
|
|
}
|
|
case "emblem_0":
|
|
Fill(painter, f, Crescent(), color);
|
|
break;
|
|
case "emblem_1":
|
|
Disc(painter, f.P(.5f, .5f), f.S * .2f, color);
|
|
for (int index = 0; index < 8; index++)
|
|
{
|
|
float angle = Tau * index / 8f - Mathf.PI * .5f;
|
|
Vector2 tip = new Vector2(.5f, .5f) + Dir(angle) * .47f;
|
|
Vector2 left = new Vector2(.5f, .5f) + Dir(angle - .22f) * .27f;
|
|
Vector2 right = new Vector2(.5f, .5f) + Dir(angle + .22f) * .27f;
|
|
Fill(painter, f, new[] { left, tip, right }, color);
|
|
}
|
|
break;
|
|
case "emblem_2":
|
|
{
|
|
List<Vector2> hexagon = Circle(6, new Vector2(.5f, .5f), .42f, -Mathf.PI * .5f);
|
|
hexagon.Add(hexagon[0]);
|
|
Line(painter, f, hexagon.ToArray(), color, .08f);
|
|
Fill(painter, f, new[] { V(.5f, .3f), V(.64f, .5f), V(.5f, .7f), V(.36f, .5f) }, color);
|
|
break;
|
|
}
|
|
case "shape_0":
|
|
Disc(painter, f.P(.5f, .5f), f.S * .4f, color);
|
|
break;
|
|
case "shape_1":
|
|
Fill(painter, f, new[] { V(.5f, .06f), V(.94f, .5f), V(.5f, .94f), V(.06f, .5f) }, color);
|
|
break;
|
|
case "shape_2":
|
|
Fill(painter, f, Circle(6, new Vector2(.5f, .5f), .44f, -Mathf.PI * .5f).ToArray(), color);
|
|
break;
|
|
case "tank":
|
|
case "armor":
|
|
{
|
|
var shield = new List<Vector2> { V(.2f, .16f), V(.8f, .16f), V(.8f, .48f), V(.72f, .66f), V(.5f, .87f), V(.28f, .66f), V(.2f, .48f) };
|
|
if (glyph == "tank")
|
|
{
|
|
Fill(painter, f, shield.ToArray(), color);
|
|
Line(painter, f, new[] { V(.5f, .24f), V(.5f, .74f) }, dark, .07f);
|
|
}
|
|
else
|
|
{
|
|
shield.Add(shield[0]);
|
|
Line(painter, f, shield.ToArray(), color, .09f);
|
|
}
|
|
break;
|
|
}
|
|
case "melee":
|
|
case "sword":
|
|
{
|
|
Vector2[] blade = { V(.5f, .06f), V(.56f, .14f), V(.56f, .62f), V(.44f, .62f), V(.44f, .14f) };
|
|
Fill(painter, f, Rot(blade, Mathf.PI * .25f), color);
|
|
Fill(painter, f, Rot(new[] { V(.3f, .62f), V(.7f, .62f), V(.7f, .69f), V(.3f, .69f) }, Mathf.PI * .25f), color);
|
|
Fill(painter, f, Rot(new[] { V(.46f, .69f), V(.54f, .69f), V(.54f, .88f), V(.46f, .88f) }, Mathf.PI * .25f), color);
|
|
break;
|
|
}
|
|
case "ranged":
|
|
{
|
|
var bow = new Vector2[13];
|
|
for (int index = 0; index < 13; index++)
|
|
{
|
|
float angle = Mathf.PI * .62f + Mathf.PI * .76f * index / 12f;
|
|
bow[index] = new Vector2(.78f, .5f) + Dir(angle) * .5f;
|
|
}
|
|
Line(painter, f, Rot(bow, -Mathf.PI * .25f), color, .08f);
|
|
Line(painter, f, Rot(new[] { bow[0], bow[12] }, -Mathf.PI * .25f), color, .03f);
|
|
Line(painter, f, Rot(new[] { V(.14f, .5f), V(.82f, .5f) }, -Mathf.PI * .25f), color, .06f);
|
|
Fill(painter, f, Rot(new[] { V(.9f, .5f), V(.76f, .41f), V(.76f, .59f) }, -Mathf.PI * .25f), color);
|
|
break;
|
|
}
|
|
case "splash":
|
|
Fill(painter, f, Star(8, .46f, .2f), color);
|
|
Disc(painter, f.P(.5f, .5f), f.S * .1f, dark);
|
|
break;
|
|
case "support":
|
|
Fill(painter, f, new[] { V(.4f, .14f), V(.6f, .14f), V(.6f, .4f), V(.86f, .4f), V(.86f, .6f), V(.6f, .6f),
|
|
V(.6f, .86f), V(.4f, .86f), V(.4f, .6f), V(.14f, .6f), V(.14f, .4f), V(.4f, .4f) }, color);
|
|
break;
|
|
case "siege":
|
|
Fill(painter, f, Rot(new[] { V(.18f, .18f), V(.74f, .18f), V(.74f, .42f), V(.18f, .42f) }, -Mathf.PI * .2f), color);
|
|
Fill(painter, f, Rot(new[] { V(.42f, .4f), V(.52f, .4f), V(.52f, .92f), V(.42f, .92f) }, -Mathf.PI * .2f), color);
|
|
break;
|
|
case "crown":
|
|
case "hero":
|
|
Fill(painter, f, new[] { V(.12f, .74f), V(.12f, .3f), V(.32f, .5f), V(.5f, .2f), V(.68f, .5f), V(.88f, .3f), V(.88f, .74f) }, color);
|
|
Fill(painter, f, new[] { V(.12f, .78f), V(.88f, .78f), V(.88f, .88f), V(.12f, .88f) }, color);
|
|
if (glyph == "crown")
|
|
Disc(painter, f.P(.5f, .6f), f.S * .06f, dark);
|
|
break;
|
|
case "skull":
|
|
Fill(painter, f, Circle(20, new Vector2(.5f, .44f), .34f).ToArray(), color);
|
|
Fill(painter, f, new[] { V(.32f, .6f), V(.68f, .6f), V(.64f, .86f), V(.36f, .86f) }, color);
|
|
Disc(painter, f.P(.37f, .45f), f.S * .09f, dark);
|
|
Disc(painter, f.P(.63f, .45f), f.S * .09f, dark);
|
|
break;
|
|
case "champion":
|
|
Fill(painter, f, new[] { V(.5f, .06f), V(.82f, .46f), V(.61f, .46f), V(.61f, .9f), V(.39f, .9f), V(.39f, .46f), V(.18f, .46f) }, color);
|
|
break;
|
|
case "hourglass":
|
|
Fill(painter, f, new[] { V(.24f, .12f), V(.76f, .12f), V(.56f, .5f), V(.44f, .5f) }, color);
|
|
Fill(painter, f, new[] { V(.44f, .5f), V(.56f, .5f), V(.76f, .88f), V(.24f, .88f) }, color);
|
|
Line(painter, f, new[] { V(.18f, .1f), V(.82f, .1f) }, color, .06f);
|
|
Line(painter, f, new[] { V(.18f, .9f), V(.82f, .9f) }, color, .06f);
|
|
break;
|
|
case "tower":
|
|
Fill(painter, f, new[] { V(.28f, .92f), V(.28f, .36f), V(.22f, .36f), V(.22f, .14f), V(.33f, .14f), V(.33f, .23f),
|
|
V(.44f, .23f), V(.44f, .14f), V(.56f, .14f), V(.56f, .23f), V(.67f, .23f), V(.67f, .14f),
|
|
V(.78f, .14f), V(.78f, .36f), V(.72f, .36f), V(.72f, .92f) }, color);
|
|
Fill(painter, f, new[] { V(.44f, .92f), V(.44f, .72f), V(.5f, .66f), V(.56f, .72f), V(.56f, .92f) }, dark);
|
|
break;
|
|
case "ancient":
|
|
Fill(painter, f, new[] { V(.5f, .05f), V(.76f, .4f), V(.6f, .95f), V(.4f, .95f), V(.24f, .4f) }, color);
|
|
Line(painter, f, new[] { V(.5f, .05f), V(.5f, .95f) }, dark, .04f);
|
|
Line(painter, f, new[] { V(.24f, .4f), V(.5f, .52f), V(.76f, .4f) }, dark, .04f);
|
|
break;
|
|
case "warning":
|
|
Fill(painter, f, new[] { V(.5f, .08f), V(.94f, .88f), V(.06f, .88f) }, color);
|
|
Fill(painter, f, new[] { V(.46f, .34f), V(.54f, .34f), V(.53f, .64f), V(.47f, .64f) }, dark);
|
|
Disc(painter, f.P(.5f, .75f), f.S * .05f, dark);
|
|
break;
|
|
case "check":
|
|
Line(painter, f, new[] { V(.16f, .52f), V(.4f, .76f), V(.86f, .24f) }, color, .13f);
|
|
break;
|
|
case "close":
|
|
Line(painter, f, new[] { V(.2f, .2f), V(.8f, .8f) }, color, .12f);
|
|
Line(painter, f, new[] { V(.8f, .2f), V(.2f, .8f) }, color, .12f);
|
|
break;
|
|
case "bot":
|
|
case "gear":
|
|
{
|
|
Vector2 c = f.P(.5f, .5f);
|
|
for (int index = 0; index < 8; index++)
|
|
Fill(painter, f, Rot(new[] { V(.43f, .06f), V(.57f, .06f), V(.57f, .24f), V(.43f, .24f) }, Tau * index / 8f), color);
|
|
Disc(painter, c, f.S * .3f, color);
|
|
Disc(painter, c, f.S * .12f, dark);
|
|
break;
|
|
}
|
|
case "human":
|
|
Disc(painter, f.P(.5f, .3f), f.S * .17f, color);
|
|
Fill(painter, f, new[] { V(.2f, .9f), V(.24f, .62f), V(.38f, .52f), V(.62f, .52f), V(.76f, .62f), V(.8f, .9f) }, color);
|
|
break;
|
|
case "offline":
|
|
ArcLine(painter, f.P(.5f, .5f), f.S * .38f, 0f, Tau, color, Mathf.Max(1f, f.S * .1f));
|
|
Line(painter, f, new[] { V(.22f, .78f), V(.78f, .22f) }, color, .1f);
|
|
break;
|
|
case "clock":
|
|
ArcLine(painter, f.P(.5f, .5f), f.S * .4f, 0f, Tau, color, Mathf.Max(1f, f.S * .09f));
|
|
Line(painter, f, new[] { V(.5f, .24f), V(.5f, .5f), V(.68f, .62f) }, color, .08f);
|
|
break;
|
|
case "heart":
|
|
Fill(painter, f, Heart(), color);
|
|
break;
|
|
case "tempo":
|
|
Fill(painter, f, new[] { V(.6f, .04f), V(.22f, .56f), V(.46f, .56f), V(.38f, .96f), V(.78f, .42f), V(.54f, .42f), V(.66f, .04f) }, color);
|
|
break;
|
|
case "range":
|
|
{
|
|
Vector2 c = f.P(.5f, .5f);
|
|
ArcLine(painter, c, f.S * .38f, 0f, Tau, color, Mathf.Max(1f, f.S * .08f));
|
|
ArcLine(painter, c, f.S * .18f, 0f, Tau, color, Mathf.Max(1f, f.S * .08f));
|
|
Disc(painter, c, f.S * .06f, color);
|
|
Line(painter, f, new[] { V(.5f, .02f), V(.5f, .16f) }, color, .07f);
|
|
Line(painter, f, new[] { V(.5f, .84f), V(.5f, .98f) }, color, .07f);
|
|
Line(painter, f, new[] { V(.02f, .5f), V(.16f, .5f) }, color, .07f);
|
|
Line(painter, f, new[] { V(.84f, .5f), V(.98f, .5f) }, color, .07f);
|
|
break;
|
|
}
|
|
case "move":
|
|
Line(painter, f, new[] { V(.18f, .2f), V(.46f, .5f), V(.18f, .8f) }, color, .11f);
|
|
Line(painter, f, new[] { V(.52f, .2f), V(.8f, .5f), V(.52f, .8f) }, color, .11f);
|
|
break;
|
|
case "star":
|
|
Fill(painter, f, Star(5, .47f, .2f), color);
|
|
break;
|
|
case "lock":
|
|
Fill(painter, f, new[] { V(.22f, .46f), V(.78f, .46f), V(.78f, .9f), V(.22f, .9f) }, color);
|
|
ArcLine(painter, f.P(.5f, .46f), f.S * .19f, Mathf.PI, Tau, color, Mathf.Max(1f, f.S * .09f));
|
|
Disc(painter, f.P(.5f, .64f), f.S * .06f, dark);
|
|
break;
|
|
case "up":
|
|
Fill(painter, f, new[] { V(.5f, .08f), V(.86f, .48f), V(.62f, .48f), V(.62f, .9f), V(.38f, .9f), V(.38f, .48f), V(.14f, .48f) }, color);
|
|
break;
|
|
case "lane_top":
|
|
case "lane_mid":
|
|
case "lane_bot":
|
|
{
|
|
Line(painter, f, new[] { V(.12f, .12f), V(.88f, .12f), V(.88f, .88f), V(.12f, .88f), V(.12f, .12f) }, LuneUiTheme.WithAlpha(color, color.a * .3f), .05f);
|
|
Vector2[] path = glyph == "lane_top" ? new[] { V(.2f, .8f), V(.2f, .2f), V(.8f, .2f) }
|
|
: glyph == "lane_mid" ? new[] { V(.2f, .8f), V(.8f, .2f) } : new[] { V(.2f, .8f), V(.8f, .8f), V(.8f, .2f) };
|
|
Line(painter, f, path, color, .12f);
|
|
break;
|
|
}
|
|
case "grotto":
|
|
{
|
|
var arch = new List<Vector2> { V(.08f, .9f) };
|
|
for (int index = 0; index < 13; index++)
|
|
{
|
|
float angle = Mathf.PI + Mathf.PI * index / 12f;
|
|
arch.Add(new Vector2(.5f, .62f) + new Vector2(Mathf.Cos(angle) * .42f, Mathf.Sin(angle) * .5f));
|
|
}
|
|
arch.Add(V(.92f, .9f));
|
|
Fill(painter, f, arch.ToArray(), color);
|
|
var mouth = new List<Vector2> { V(.3f, .9f) };
|
|
for (int index = 0; index < 9; index++)
|
|
{
|
|
float angle = Mathf.PI + Mathf.PI * index / 8f;
|
|
mouth.Add(new Vector2(.5f, .72f) + new Vector2(Mathf.Cos(angle) * .2f, Mathf.Sin(angle) * .26f));
|
|
}
|
|
mouth.Add(V(.7f, .9f));
|
|
Fill(painter, f, mouth.ToArray(), dark);
|
|
break;
|
|
}
|
|
case "pause":
|
|
Fill(painter, f, new[] { V(.24f, .16f), V(.42f, .16f), V(.42f, .84f), V(.24f, .84f) }, color);
|
|
Fill(painter, f, new[] { V(.58f, .16f), V(.76f, .16f), V(.76f, .84f), V(.58f, .84f) }, color);
|
|
break;
|
|
case "play":
|
|
Fill(painter, f, new[] { V(.26f, .14f), V(.84f, .5f), V(.26f, .86f) }, color);
|
|
break;
|
|
case "menu":
|
|
foreach (float y in new[] { .24f, .5f, .76f })
|
|
Line(painter, f, new[] { V(.16f, y), V(.84f, y) }, color, .1f);
|
|
break;
|
|
case "basaltschild":
|
|
{
|
|
Fill(painter, f, Circle(6, new Vector2(.5f, .5f), .44f, -Mathf.PI * .5f).ToArray(), color);
|
|
List<Vector2> inner = Circle(6, new Vector2(.5f, .5f), .27f, -Mathf.PI * .5f);
|
|
inner.Add(inner[0]);
|
|
Line(painter, f, inner.ToArray(), dark, .07f);
|
|
break;
|
|
}
|
|
case "glutklinge":
|
|
{
|
|
Vector2[] blade = { V(.5f, .06f), V(.57f, .15f), V(.57f, .66f), V(.43f, .66f), V(.43f, .15f) };
|
|
Fill(painter, f, Rot(blade, Mathf.PI * .25f), color);
|
|
Fill(painter, f, Rot(new[] { V(.3f, .66f), V(.7f, .66f), V(.7f, .73f), V(.3f, .73f) }, Mathf.PI * .25f), color);
|
|
Fill(painter, f, new[] { V(.2f, .5f), V(.3f, .3f), V(.36f, .42f), V(.42f, .22f), V(.5f, .44f), V(.4f, .6f) }, accent.a > 0f ? dark : LuneUiTheme.Hex("ffb070"));
|
|
break;
|
|
}
|
|
case "rankenmantel":
|
|
Fill(painter, f, new[] { V(.5f, .1f), V(.66f, .18f), V(.8f, .9f), V(.5f, .8f), V(.2f, .9f), V(.34f, .18f) }, color);
|
|
Line(painter, f, new[] { V(.5f, .22f), V(.42f, .4f), V(.56f, .56f), V(.46f, .74f) }, dark, .05f);
|
|
break;
|
|
case "funkenfokus":
|
|
ArcLine(painter, f.P(.5f, .5f), f.S * .42f, 0f, Tau, color, Mathf.Max(1f, f.S * .08f));
|
|
Fill(painter, f, new[] { V(.56f, .16f), V(.3f, .54f), V(.47f, .54f), V(.42f, .84f), V(.7f, .44f), V(.53f, .44f), V(.62f, .16f) }, color);
|
|
break;
|
|
case "quellamulett":
|
|
ArcLine(painter, f.P(.5f, .2f), f.S * .14f, Mathf.PI * .9f, Mathf.PI * 2.1f, color, Mathf.Max(1f, f.S * .06f));
|
|
Fill(painter, f, Drop(), color);
|
|
Disc(painter, f.P(.44f, .66f), f.S * .06f, new Color(1f, 1f, 1f, .45f * color.a));
|
|
break;
|
|
case "kristallsplitter":
|
|
Shard(painter, f, V(.5f, .5f), 0f, .44f, color);
|
|
Shard(painter, f, V(.3f, .62f), -.55f, .3f, color);
|
|
Shard(painter, f, V(.7f, .62f), .55f, .3f, color);
|
|
break;
|
|
default:
|
|
Disc(painter, f.P(.5f, .5f), f.S * .3f, color);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void Shard(Painter2D painter, Frame f, Vector2 center, float angle, float length, Color color)
|
|
{
|
|
Vector2[] shard = { center + new Vector2(0f, -length), center + new Vector2(.08f, 0f), center + new Vector2(0f, length * .45f), center + new Vector2(-.08f, 0f) };
|
|
Fill(painter, f, Rot(shard, angle, center), color);
|
|
}
|
|
|
|
/// <summary>The unit square fitted into a rect (centred, aspect kept).</summary>
|
|
struct Frame
|
|
{
|
|
public readonly float S;
|
|
public readonly Vector2 Origin;
|
|
|
|
public Frame(Rect rect)
|
|
{
|
|
S = Mathf.Min(rect.width, rect.height);
|
|
Origin = rect.position + (rect.size - new Vector2(S, S)) * .5f;
|
|
}
|
|
|
|
public Vector2 P(float x, float y) { return Origin + new Vector2(x, y) * S; }
|
|
public Vector2 P(Vector2 p) { return Origin + p * S; }
|
|
}
|
|
|
|
static Vector2 V(float x, float y) { return new Vector2(x, y); }
|
|
|
|
static Vector2 Dir(float angle) { return new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)); }
|
|
|
|
static Vector2[] Rot(Vector2[] points, float angle) { return Rot(points, angle, new Vector2(.5f, .5f)); }
|
|
|
|
static Vector2[] Rot(Vector2[] points, float angle, Vector2 pivot)
|
|
{
|
|
var result = new Vector2[points.Length];
|
|
float c = Mathf.Cos(angle);
|
|
float s = Mathf.Sin(angle);
|
|
for (int index = 0; index < points.Length; index++)
|
|
{
|
|
Vector2 d = points[index] - pivot;
|
|
result[index] = pivot + new Vector2(d.x * c - d.y * s, d.x * s + d.y * c);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void Fill(Painter2D painter, Frame f, Vector2[] points, Color color)
|
|
{
|
|
if (points.Length < 3)
|
|
return;
|
|
painter.fillColor = color;
|
|
painter.BeginPath();
|
|
painter.MoveTo(f.P(points[0]));
|
|
for (int index = 1; index < points.Length; index++)
|
|
painter.LineTo(f.P(points[index]));
|
|
painter.ClosePath();
|
|
painter.Fill(FillRule.NonZero);
|
|
}
|
|
|
|
static void Line(Painter2D painter, Frame f, Vector2[] points, Color color, float width)
|
|
{
|
|
painter.strokeColor = color;
|
|
painter.lineWidth = Mathf.Max(1f, width * f.S);
|
|
painter.lineJoin = LineJoin.Miter;
|
|
painter.lineCap = LineCap.Butt;
|
|
painter.BeginPath();
|
|
painter.MoveTo(f.P(points[0]));
|
|
for (int index = 1; index < points.Length; index++)
|
|
painter.LineTo(f.P(points[index]));
|
|
painter.Stroke();
|
|
}
|
|
|
|
static void Disc(Painter2D painter, Vector2 center, float radius, Color color)
|
|
{
|
|
painter.fillColor = color;
|
|
painter.BeginPath();
|
|
painter.Arc(center, radius, Angle.Degrees(0f), Angle.Degrees(360f));
|
|
painter.ClosePath();
|
|
painter.Fill();
|
|
}
|
|
|
|
/// <summary>Godot draw_arc: start to end angle (radians, clockwise on screen).</summary>
|
|
static void ArcLine(Painter2D painter, Vector2 center, float radius, float start, float end, Color color, float width)
|
|
{
|
|
painter.strokeColor = color;
|
|
painter.lineWidth = width;
|
|
painter.lineCap = LineCap.Butt;
|
|
painter.BeginPath();
|
|
painter.Arc(center, radius, Angle.Radians(start), Angle.Radians(end));
|
|
if (end - start >= Tau - .001f)
|
|
painter.ClosePath();
|
|
painter.Stroke();
|
|
}
|
|
|
|
static List<Vector2> Circle(int points, Vector2 center, float radius, float start = 0f)
|
|
{
|
|
var result = new List<Vector2>(points);
|
|
for (int index = 0; index < points; index++)
|
|
result.Add(center + Dir(start + Tau * index / points) * radius);
|
|
return result;
|
|
}
|
|
|
|
static Vector2[] Star(int points, float outer, float inner, float start = -Mathf.PI * .5f)
|
|
{
|
|
var result = new Vector2[points * 2];
|
|
for (int index = 0; index < points * 2; index++)
|
|
result[index] = new Vector2(.5f, .5f) + Dir(start + Mathf.PI * index / points) * (index % 2 == 0 ? outer : inner);
|
|
return result;
|
|
}
|
|
|
|
/// <summary>Crescent (Blau): the outer disc minus an offset disc, as Geometry2D.clip_polygons gave in Godot.</summary>
|
|
static Vector2[] Crescent()
|
|
{
|
|
if (Cache.TryGetValue("crescent", out Vector2[] cached))
|
|
return cached;
|
|
var outerCenter = new Vector2(.5f, .5f);
|
|
var innerCenter = new Vector2(.66f, .4f);
|
|
const float outerRadius = .42f;
|
|
const float innerRadius = .33f;
|
|
List<Vector2> outer = Circle(48, outerCenter, outerRadius);
|
|
List<Vector2> inner = Circle(48, innerCenter, innerRadius);
|
|
// The outer arc outside the inner disc, then the inner arc inside the outer disc, backwards.
|
|
int startOuter = 0;
|
|
while (!(Inside(outer[startOuter], innerCenter, innerRadius) && !Inside(outer[(startOuter + 1) % 48], innerCenter, innerRadius)))
|
|
startOuter++;
|
|
var result = new List<Vector2>();
|
|
for (int step = 1; step <= 48; step++)
|
|
{
|
|
Vector2 point = outer[(startOuter + step) % 48];
|
|
if (Inside(point, innerCenter, innerRadius))
|
|
break;
|
|
result.Add(point);
|
|
}
|
|
int startInner = 0;
|
|
while (!(Inside(inner[startInner], outerCenter, outerRadius) && !Inside(inner[(startInner + 1) % 48], outerCenter, outerRadius)))
|
|
startInner++;
|
|
for (int step = 0; step < 48; step++)
|
|
{
|
|
Vector2 point = inner[((startInner - step) % 48 + 48) % 48];
|
|
if (!Inside(point, outerCenter, outerRadius))
|
|
break;
|
|
result.Add(point);
|
|
}
|
|
cached = result.ToArray();
|
|
Cache["crescent"] = cached;
|
|
return cached;
|
|
}
|
|
|
|
static bool Inside(Vector2 point, Vector2 center, float radius)
|
|
{
|
|
return (point - center).sqrMagnitude < radius * radius;
|
|
}
|
|
|
|
static Vector2[] Heart()
|
|
{
|
|
if (Cache.TryGetValue("heart", out Vector2[] cached))
|
|
return cached;
|
|
cached = new Vector2[40];
|
|
for (int index = 0; index < 40; index++)
|
|
{
|
|
float t = Tau * index / 40f;
|
|
float x = 16f * Mathf.Pow(Mathf.Sin(t), 3);
|
|
float y = 13f * Mathf.Cos(t) - 5f * Mathf.Cos(2f * t) - 2f * Mathf.Cos(3f * t) - Mathf.Cos(4f * t);
|
|
cached[index] = new Vector2(.5f + x / 38f, .47f - y / 38f);
|
|
}
|
|
Cache["heart"] = cached;
|
|
return cached;
|
|
}
|
|
|
|
static Vector2[] Drop()
|
|
{
|
|
var result = new Vector2[18];
|
|
result[0] = new Vector2(.5f, .16f);
|
|
for (int index = 0; index < 17; index++)
|
|
{
|
|
float angle = -Mathf.PI * .15f + Mathf.PI * 1.3f * index / 16f;
|
|
result[1 + index] = new Vector2(.5f, .64f) + Dir(angle) * .22f;
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
}
|