====== std.rect — Achsenparallele Rechtecke ======
→ [[lyx_-_programmiersprache:units|Zurück zur Unit-Übersicht]]
Rechtecke als Paar aus zwei Eckpunkten: ''min'' (links oben) und ''max'' (rechts unten), beide vom Typ ''Vec2'' aus [[lyx_-_programmiersprache:units:vector|std.vector]]. Alle Rechtecke sind **achsenparallel** — es gibt keine Drehung. Gerechnet wird ganzzahlig.
Einsatzbereiche: Layoutflächen, Sichtbereiche, Kollisionsprüfung, umschließende Rechtecke (Bounding Boxes).
**Autor:** Andreas Röne\\
**Copyright:** 2024–2025 Andreas Röne\\
**Quelle:** ''std/rect.lyx''
Drei Punkte weichen von dem ab, was die Namen versprechen:
* ''RectContains'' und ''RectContainsInclusive'' verhalten sich **genau umgekehrt** zu ihrer Benennung (#1479).
* ''RectCorners'' liefert **vier Nullvektoren** statt der Ecken — die Funktion füllt ihr Array nie (#1480).
* ''RectDistanceToPoint'' misst die **Manhattan-Distanz**, nicht den geometrischen Abstand (#1480).
Alles Übrige ist geprüft und in Ordnung. Einzelheiten unter [[#Was nicht stimmt]].
===== Import =====
import std.rect;
Zieht ''std.vector'' mit — ''Vec2'' wird für Eckpunkte und Größen gebraucht.
----
===== Der Typ Rect =====
^ Feld ^ Typ ^ Bedeutung ^
| ''min'' | ''Vec2'' | Ecke mit den kleineren Koordinaten |
| ''max'' | ''Vec2'' | Ecke mit den größeren Koordinaten |
Die Benennung „oben links" und „unten rechts" gilt für Bildschirmkoordinaten, bei denen y nach unten wächst. In einem mathematischen Koordinatensystem liegt ''min'' entsprechend unten links — die Rechnungen sind davon unberührt.
**Die Unit setzt ''min <= max'' voraus**, prüft es aber nur in ''RectIsValid''. Ein Rechteck mit vertauschten Ecken entsteht etwa als Ergebnis von ''RectIntersect'' bei disjunkten Eingaben; Breite und Fläche sind dann negativ. ''RectFromPoints'' sortiert die Ecken selbst und ist deshalb der sichere Konstruktor, wenn die Reihenfolge unbekannt ist.
----
===== Funktionen =====
==== Erzeugen ====
^ Signatur ^ Beschreibung ^
| ''RectNew(min, max: Vec2): Rect'' | Direkt aus zwei Ecken — **ohne** Sortierung |
| ''RectFromPoints(p1, p2: Vec2): Rect'' | Aus zwei beliebigen Punkten, sortiert die Ecken |
| ''RectFromXYWH(x, y, w, h: int64): Rect'' | Aus Position und Größe |
| ''RectFromCenterSize(center, size: Vec2): Rect'' | Um einen Mittelpunkt herum |
| ''RectEmpty(): Rect'' | Alles null — gilt als ungültig und leer |
''RectFromCenterSize'' halbiert die Größe ganzzahlig: bei ungerader Breite ist das Ergebnis um eins schmaler als angegeben.
==== Maße ====
^ Signatur ^ Beschreibung ^
| ''RectWidth(r: Rect): int64'' | ''max.x − min.x'' |
| ''RectHeight(r: Rect): int64'' | ''max.y − min.y'' |
| ''RectSize(r: Rect): Vec2'' | Breite und Höhe als Vektor |
| ''RectCenter(r: Rect): Vec2'' | Mittelpunkt, ganzzahlig gerundet |
| ''RectArea(r: Rect): int64'' | Breite mal Höhe |
| ''RectLeft'' · ''RectRight'' · ''RectTop'' · ''RectBottom'' | Einzelne Kantenkoordinate |
| ''RectTopLeft'' · ''RectTopRight'' · ''RectBottomLeft'' · ''RectBottomRight'' | Einzelne Ecke als ''Vec2'' |
==== Prüfen ====
^ Signatur ^ Beschreibung ^ Status ^
| ''RectIsValid(r: Rect): bool'' | ''max'' echt größer ''min'' in beiden Achsen | ok |
| ''RectIsEmpty(r: Rect): bool'' | Breite oder Höhe ist 0 oder negativ | ok |
| ''RectEqual(a, b: Rect): bool'' | Beide Ecken gleich | ok |
| ''RectContains(r: Rect, p: Vec2): bool'' | Punkt im Rechteck, **beide Ränder eingeschlossen** | Name irreführend (#1479) |
| ''RectContainsInclusive(r: Rect, p: Vec2): bool'' | Punkt im Rechteck, **''max''-Rand ausgeschlossen** | Name irreführend (#1479) |
| ''RectIntersects(a, b: Rect): bool'' | Ob sich zwei Rechtecke überlappen | ok |
Ein Rechteck der Breite 0 gilt als leer **und** ungültig — die beiden Prüfungen sind exakt gegenläufig.
==== Verändern ====
^ Signatur ^ Beschreibung ^
| ''RectInflate(r: Rect, amount: int64): Rect'' | Nach allen Seiten vergrößern |
| ''RectDeflate(r: Rect, amount: int64): Rect'' | Nach allen Seiten verkleinern |
| ''RectExpand(r: Rect, p: Vec2): Rect'' | So erweitern, dass ''p'' enthalten ist |
| ''RectUnion(a, b: Rect): Rect'' | Kleinstes Rechteck um beide |
| ''RectIntersect(a, b: Rect): Rect'' | Überlappungsfläche |
| ''RectClampPoint(r: Rect, p: Vec2): Vec2'' | Punkt in das Rechteck hineinziehen |
| ''RectClamp(r, bounds: Rect): Rect'' | Rechteck auf einen Rahmen beschneiden — identisch zu ''RectIntersect'' |
''RectDeflate'' kann ein Rechteck ungültig machen, wenn der Betrag größer als die halbe Kantenlänge ist. Das Ergebnis sollte danach mit ''RectIsValid'' geprüft werden. ''RectIntersect'' liefert bei disjunkten Rechtecken ein Ergebnis mit vertauschten Ecken; genau darauf stützt sich ''RectIntersects''.
==== Sonstiges ====
^ Signatur ^ Beschreibung ^ Status ^
| ''RectDistanceToPoint(r: Rect, p: Vec2): int64'' | Abstand eines Punkts zum Rechteck; 0 wenn innen | **Manhattan** (#1480) |
| ''RectCorners(r: Rect): [4]Vec2'' | Alle vier Ecken auf einmal | **liefert Nullvektoren** (#1480) |
----
===== Beispiel =====
import std.rect;
import std.vector;
fn zeigR(name: pchar, r: Rect): void {
PrintLn(name, " = min(", IntToStr(r.min.x), ",", IntToStr(r.min.y),
") max(", IntToStr(r.max.x), ",", IntToStr(r.max.y), ")");
}
fn main(): int64 {
// Ein Fenster: Position (10,20), 100 breit, 50 hoch
var fenster: Rect := RectFromXYWH(10, 20, 100, 50);
zeigR("Fenster ", fenster);
PrintLn("Breite ", IntToStr(RectWidth(fenster)), ", Hoehe ", IntToStr(RectHeight(fenster)),
", Flaeche ", IntToStr(RectArea(fenster)));
var m: Vec2 := RectCenter(fenster);
PrintLn("Mittelpunkt = (", IntToStr(m.x), ", ", IntToStr(m.y), ")");
// Rand abziehen und wieder aufschlagen
zeigR("Innenraum", RectDeflate(fenster, 5));
zeigR("mit Rahmen", RectInflate(fenster, 5));
// Ueberschneidung zweier Bereiche
var a: Rect := RectFromXYWH(0, 0, 100, 100);
var b: Rect := RectFromXYWH(50, 50, 100, 100);
PrintLn("-- zwei Bereiche:");
PrintLn("ueberschneiden sich? ", IntToStr(RectIntersects(a, b) as int64));
zeigR("Schnittflaeche", RectIntersect(a, b));
zeigR("Umschliessend ", RectUnion(a, b));
// Punkt einpassen
var p: Vec2 := RectClampPoint(a, Vec2New(150, -20));
PrintLn("ClampPoint((150,-20)) = (", IntToStr(p.x), ", ", IntToStr(p.y), ")");
// Bounding-Box ueber mehrere Punkte aufbauen
var box: Rect := RectFromPoints(Vec2New(40, 10), Vec2New(40, 10));
box := RectExpand(box, Vec2New(-5, 60));
box := RectExpand(box, Vec2New(120, 25));
zeigR("Bounding-Box ", box);
return 0;
}
Ausgabe:
Fenster = min(10,20) max(110,70)
Breite 100, Hoehe 50, Flaeche 5000
Mittelpunkt = (60, 45)
Innenraum = min(15,25) max(105,65)
mit Rahmen = min(5,15) max(115,75)
-- zwei Bereiche:
ueberschneiden sich? 1
Schnittflaeche = min(50,50) max(100,100)
Umschliessend = min(0,0) max(150,150)
ClampPoint((150,-20)) = (100, 0)
Bounding-Box = min(-5,10) max(120,60)
Das Muster am Ende — mit einem Punkt beginnen und per ''RectExpand'' erweitern — ist der übliche Weg zu einer Bounding Box über eine Punktmenge. Wichtig ist der Start mit ''RectFromPoints(p, p)'' statt mit ''RectEmpty()'': Letzteres läge bei (0,0) und würde den Ursprung fälschlich einschließen.
----
===== Was nicht stimmt =====
Geprüft mit ''lyxc 1.0.21A''.
==== Contains und ContainsInclusive sind vertauscht (#1479) ====
Für das Rechteck 10…110 × 20…70 und den Eckpunkt ''max'':
Contains(110,70) = 1 der max-Rand gilt als enthalten
ContainsInclusive(110,70) = 0 ausgerechnet die "inklusive" Variante schliesst ihn aus
''RectContains'' vergleicht mit ''<='' und schließt damit beide Ränder ein; ''RectContainsInclusive'' vergleicht mit ''<'' und lässt den ''max''-Rand außen vor.
Die **halboffene** Variante — also ''RectContainsInclusive'' — ist für Pixelraster und Kachelgitter die richtige: ein Punkt auf der gemeinsamen Kante zweier angrenzender Rechtecke gehört dann zu genau einem von beiden. Bei der Auswahl nach dem Namen bekommt man derzeit das Gegenteil und zählt Treffer doppelt.
==== RectCorners liefert keine Ecken (#1480) ====
var ecken: [4]Vec2 := RectCorners(RectFromXYWH(0, 0, 100, 100));
Ecke 0 = (0, 0)
Ecke 1 = (0, 0)
Ecke 2 = (0, 0)
Ecke 3 = (0, 0)
Die Funktion legt das Array an und gibt es zurück, ohne es zu füllen — der Parameter ''r'' wird nicht verwendet. Dass hier Nullen erscheinen, ist Zufall des Stackinhalts.
Ersatz über die vier Einzelfunktionen, die korrekt arbeiten:
var ecken: [4]Vec2;
ecken[0] := RectTopLeft(r);
ecken[1] := RectTopRight(r);
ecken[2] := RectBottomRight(r);
ecken[3] := RectBottomLeft(r);
==== RectDistanceToPoint misst Manhattan (#1480) ====
DistanceToPoint(Rect 0..100, Punkt (130,140)) = 70
Der Punkt liegt 30 rechts und 40 unterhalb der Ecke (100,100); der geometrische Abstand ist 50. Geliefert wird die Summe der beiden Abstände. Liegt der Punkt achsenparallel außerhalb, stimmt das Ergebnis zufällig — ''(130, 0)'' ergibt korrekt 30. Bei diagonaler Lage ist es zu groß, was eine Suche nach dem nächstgelegenen Rechteck verfälschen kann.
Ersatz:
import std.math;
// dx, dy wie in der Unit bestimmen, dann:
var abstand: int64 := IntSqrt(dx * dx + dy * dy);
----
===== Verwandte Units =====
* [[lyx_-_programmiersprache:units:vector|std.vector]] — ''Vec2'', Grundlage dieser Unit
* [[lyx_-_programmiersprache:units:circle|std.circle]] — Kreise und Kollisionen
* [[lyx_-_programmiersprache:units:math|std.math]] — ''IntSqrt'', ''Clamp64'', ''Min64''/''Max64''