std.circle — Kreise und Zahlenbereiche

→ Zurück zur Unit-Übersicht

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

import std.circle;

Zieht std.vector, std.rect und std.math mit.


Kreise

Feld von Circle Typ Bedeutung
center Vec2 Mittelpunkt
radius int64 Radius in denselben Einheiten wie die Koordinaten

Erzeugen

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).

Maße

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

Lagebeziehungen

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.

Verändern

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.


Zahlenbereiche

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

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.


Beispiel

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]


Was nicht stimmt

Geprüft mit lyxc 1.0.21A.

CircleArea liefert 0 (#1489)

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;

CircleUnion verschiebt den Mittelpunkt nicht (#1490)

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.


Verwandte Units

  • std.vector — Vec2, Grundlage der Mittelpunkte
  • std.rect — Rechtecke, für Grobfilter und Range2D-Umwandlung
  • std.math — IntSqrt, Min64/Max64

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)