std.rect — Achsenparallele Rechtecke
Rechtecke als Paar aus zwei Eckpunkten: min (links oben) und max (rechts unten), beide vom Typ Vec2 aus 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
<WRAP alert> Drei Punkte weichen von dem ab, was die Namen versprechen:
RectContainsundRectContainsInclusiveverhalten sich genau umgekehrt zu ihrer Benennung (#1479).RectCornersliefert vier Nullvektoren statt der Ecken — die Funktion füllt ihr Array nie (#1480).RectDistanceToPointmisst die Manhattan-Distanz, nicht den geometrischen Abstand (#1480).
Alles Übrige ist geprüft und in Ordnung. Einzelheiten unter Was nicht stimmt. </WRAP>
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
- std.vector —
Vec2, Grundlage dieser Unit - std.circle — Kreise und Kollisionen
