Zwei getrennte Themen in einer Unit: Kreise in der Ebene (Mittelpunkt als Vec2 plus Radius) und Zahlenbereiche — Range für eine Achse, Range2D für zwei. Gerechnet wird ganzzahlig in denselben Einheiten wie die Koordinaten.
Einsatzbereiche: Umkreissuche, Kollisionsvorprüfung, Wertebereiche und Intervallprüfungen.
Autor: Andreas Röne
Copyright: 2024–2025 Andreas Röne
Quelle: std/circle.lyx
<WRAP alert> Zwei Funktionen sind fehlerhaft:
CircleArea liefert für jeden Radius unter 1000 den Wert 0 — Fläche und Umfang gehen von unterschiedlichen Einheiten aus (#1489).CircleUnion lässt den Mittelpunkt stehen, wo er war; der Ergebniskreis deckt den zweiten Kreis nicht ab (#1490).
Der gesamte übrige Teil — Kreisgeometrie wie Range/Range2D — ist geprüft und in Ordnung.
</WRAP>
import std.circle;
Zieht std.vector, std.rect und std.math mit.
Feld von Circle | Typ | Bedeutung |
|---|---|---|
center | Vec2 | Mittelpunkt |
radius | int64 | Radius in denselben Einheiten wie die Koordinaten |
| Signatur | Beschreibung |
|---|---|
CircleNew(center: Vec2, radius: int64): Circle | Aus Mittelpunkt und Radius |
CircleFromXYR(x, y, r: int64): Circle | Aus Einzelwerten |
CircleFromPoints(p1, p2: Vec2): Circle | Kleinster Kreis, dessen Durchmesser die Strecke p1–p2 ist |
CircleUnit(): Circle | Mittelpunkt (0,0), Radius 1 000 000 — siehe Hinweis unten |
CircleUnit geht als einzige Funktion von einem Festkomma-Radius aus (1 000 000 = 1,0). Alle übrigen Kreisfunktionen rechnen in rohen Einheiten; der „Einheitskreis„ passt daher nicht zu ihnen (#1489).
| Signatur | Beschreibung | Status |
|---|---|---|
CircleDiameter(c: Circle): int64 | Doppelter Radius | ok |
CircleCircumference(c: Circle): int64 | Umfang, ganzzahlig abgerundet | ok |
CircleArea(c: Circle): int64 | Flächeninhalt | immer 0 (#1489) |
CircleIsEmpty(c: Circle): bool | Radius kleiner oder gleich 0 | ok |
| Signatur | Beschreibung | Status |
|---|---|---|
CircleContainsPoint(c: Circle, p: Vec2): bool | Punkt im Kreis, Rand eingeschlossen | ok |
CircleContainsCircle(c, inner: Circle): bool | Kreis vollständig enthalten | ok |
CircleIntersectsCircle(a, b: Circle): bool | Überschneidung; Berührung zählt nicht | ok |
CircleIntersectsRect(c: Circle, r: Rect): bool | Überschneidung mit einem Rechteck | ok |
CircleDistanceToPoint(c: Circle, p: Vec2): int64 | Abstand zum Rand, 0 wenn innen | ok |
CircleDistanceToCircle(c, other: Circle): int64 | Abstand zwischen den Rändern, 0 bei Überschneidung | ok |
CircleEqual(a, b: Circle): bool | Mittelpunkt und Radius gleich | ok |
CircleContainsPoint vergleicht die quadrierten Abstände und zieht keine Wurzel — dadurch ist die Prüfung exakt, ohne Rundungsfehler. Das ist der Unterschied zu CircleContainsCircle und CircleIntersectsCircle, die über Vec2Distance gehen und deshalb den abgerundeten Abstand verwenden.
| Signatur | Beschreibung | Status |
|---|---|---|
CircleExpand(c: Circle, amount: int64): Circle | Radius ändern, Mittelpunkt bleibt | ok |
CircleBoundingRect(c: Circle): Rect | Umschließendes achsenparalleles Rechteck | ok |
CircleUnion(a, b: Circle): Circle | Kleinster Kreis um beide | Mittelpunkt falsch (#1490) |
CircleBoundingRect ist der übliche Grobfilter: erst die Rechtecke vergleichen, dann für die verbleibenden Kandidaten den genauen Kreistest.
Range beschreibt ein Intervall auf einer Achse. RangeNew sortiert die Grenzen selbst, ein verdrehtes Intervall kann also gar nicht entstehen.
| Signatur | Beschreibung |
|---|---|
RangeNew(min, max: int64): Range | Intervall, Grenzen werden sortiert |
RangeFromValue(value, delta: int64): Range | value ± delta |
RangeEmpty(): Range | [0, 0] |
RangeLength(r: Range): int64 | max − min |
RangeCenter(r: Range): int64 | Mitte, ganzzahlig |
RangeIsEmpty(r: Range): bool | min >= max |
RangeContains(r: Range, value: int64): bool | Wert im Intervall, Grenzen eingeschlossen |
RangeContainsExclusive(r: Range, value: int64): bool | ohne die Grenzen |
RangeContainsRange(r, inner: Range): bool | Intervall vollständig enthalten |
RangeIntersects(r, other: Range): bool | Überschneidung; reine Berührung zählt nicht |
RangeIntersection(r, other: Range): Range | Gemeinsamer Teil |
RangeUnion(r, other: Range): Range | Kleinstes Intervall um beide |
RangeExpand(r: Range, value: int64): Range | So erweitern, dass value enthalten ist |
RangeInflate(r: Range, amount: int64): Range | Nach beiden Seiten vergrößern |
<WRAP info>
RangeIntersection erkennt man den leeren Schnitt nicht an. Weil RangeNew die Grenzen sortiert, wird aus dem rechnerisch leeren Ergebnis ein scheinbar gültiges Intervall: RangeIntersection([10,50], [60,80]) liefert [50, 60]. Vor dem Schnitt daher RangeIntersects abfragen.
Bei std.rect ist das anders gelöst — dort bleibt RectIntersect verdreht, und RectIsValid erkennt den Fall.
</WRAP>
Range2D fasst zwei Range zusammen — für x und y.
| Signatur | Beschreibung |
|---|---|
Range2DNew(x_range, y_range: Range): Range2D | Aus zwei Intervallen |
Range2DFromPoints(p1, p2: Vec2): Range2D | Aus zwei Eckpunkten |
Range2DFromRect(r: Rect): Range2D | Aus einem Rechteck |
Range2DToRect(r2d: Range2D): Rect | Zurück in ein Rechteck |
Range2DContains(r2d: Range2D, p: Vec2): bool | Punkt in beiden Achsen enthalten |
Range2DIntersects(r2d, other: Range2D): bool | Überschneidung in beiden Achsen |
Auf die Felder wird verschachtelt zugegriffen — r2d.x.min funktioniert:
var r2: Range2D := Range2DFromPoints(Vec2New(0,0), Vec2New(100,50));
PrintLn(IntToStr(r2.x.min), " bis ", IntToStr(r2.x.max)); // 0 bis 100
Range2D und Rect beschreiben dasselbe; die Umwandlungsfunktionen erlauben den Wechsel je nachdem, ob man achsenweise oder eckenweise denken will.
import std.circle;
import std.vector;
import std.rect;
fn main(): int64 {
// Ein Suchradius um einen Punkt
var umkreis: Circle := CircleFromXYR(100, 100, 50);
PrintLn("Radius ", IntToStr(umkreis.radius), ", Durchmesser ", IntToStr(CircleDiameter(umkreis)),
", Umfang ", IntToStr(CircleCircumference(umkreis)));
// Welche Punkte liegen darin?
PrintLn("(100,100) mittig = ", IntToStr(CircleContainsPoint(umkreis, Vec2New(100,100)) as int64));
PrintLn("(150,100) am Rand = ", IntToStr(CircleContainsPoint(umkreis, Vec2New(150,100)) as int64));
PrintLn("(151,100) knapp draussen = ", IntToStr(CircleContainsPoint(umkreis, Vec2New(151,100)) as int64));
// Abstand bis zum Rand
PrintLn("Abstand von (250,100) = ", IntToStr(CircleDistanceToPoint(umkreis, Vec2New(250,100))));
// Grobfilter ueber das umschliessende Rechteck
var box: Rect := CircleBoundingRect(umkreis);
PrintLn("Bounding-Rechteck: min(", IntToStr(box.min.x), ",", IntToStr(box.min.y),
") max(", IntToStr(box.max.x), ",", IntToStr(box.max.y), ")");
// Ueberschneidung mit einem Rechteck
PrintLn("schneidet Rechteck 120..220? ", IntToStr(CircleIntersectsRect(umkreis, RectFromXYWH(120,120,100,100)) as int64));
// Zahlenbereiche
var spanne: Range := RangeNew(10, 50);
PrintLn("-- Range [10,50]: Laenge ", IntToStr(RangeLength(spanne)), ", Mitte ", IntToStr(RangeCenter(spanne)));
PrintLn("enthaelt 30? ", IntToStr(RangeContains(spanne, 30) as int64));
PrintLn("ueberschneidet [40,80]? ", IntToStr(RangeIntersects(spanne, RangeNew(40,80)) as int64));
var s: Range := RangeIntersection(spanne, RangeNew(40, 80));
PrintLn("Schnitt = [", IntToStr(s.min), ", ", IntToStr(s.max), "]");
return 0;
}
Ausgabe:
Radius 50, Durchmesser 100, Umfang 314
(100,100) mittig = 1
(150,100) am Rand = 1
(151,100) knapp draussen = 0
Abstand von (250,100) = 100
Bounding-Rechteck: min(50,50) max(150,150)
schneidet Rechteck 120..220? 1
-- Range [10,50]: Laenge 40, Mitte 30
enthaelt 30? 1
ueberschneidet [40,80]? 1
Schnitt = [40, 50]
Geprüft mit lyxc 1.0.21A.
| Radius | Fläche geliefert | Fläche korrekt | Umfang geliefert |
|---|---|---|---|
| 10 | 0 | 314 | 62 |
| 50 | 0 | 7 854 | 314 |
| 100 | 0 | 31 416 | 628 |
| 1000 | 3 | 3 141 593 | 6 283 |
CircleArea teilt das Radiusquadrat durch 1 000 000, geht also von einem Festkomma-Radius aus; CircleCircumference rechnet mit rohen Einheiten. Für jeden Radius unter 1000 ergibt die Division bereits 0.
Der Rückgabewert 0 sieht nach einem leeren Kreis aus, während CircleIsEmpty für denselben Kreis korrekt false meldet. Bis zum Fix die Fläche selbst rechnen:
var flaeche: int64 := 3141593 * c.radius * c.radius / 1000000;
Union von (0,0,r=100) und (300,0,r=100)
-> Mittelpunkt (0,0), Radius 250 erwartet Mittelpunkt (150,0)
Der Radius stimmt, der Mittelpunkt bleibt bei a. Ursache ist eine ganzzahlige Division, die den Skalierungsfaktor für den Verbindungsvektor auf 0 abrundet. Damit reicht der Ergebniskreis nur bis 250, der zweite Kreis aber bis 400 — er wird also nicht umschlossen, was die einzige Zusage einer Union-Funktion ist.
Die Sonderfälle davor arbeiten korrekt: enthält ein Kreis den anderen, kommt der größere zurück; bei gleichen Mittelpunkten greift ein eigener Zweig.
Vec2, Grundlage der MittelpunkteRange2D-Umwandlung
Codebeispiele geprüft: gegen lyxc 1.2.5C übersetzt (Prüflauf 2026-09-08 über die gesamte Doku: 574 Vollprogramme, 0 echte Fehler; zusätzlich 5159 Aufrufe gegen die pub fn-Signaturen in aurum/std gehalten, 0 Abweichungen).
Letzte Aktualisierung: 2026-08-30 (Dateistand)