std.vector — 2D-Vektoren

Zurück zur Unit-Übersicht

Vektorrechnung mit dem Typ Vec2 — zwei int64-Felder x und y. Es wird ganzzahlig gerechnet: keine Fließkommazahlen, keine FPU. Wo Nachkommastellen unvermeidlich sind (Normalisieren, Interpolieren), arbeitet die Unit mit Festkomma zum Faktor 1 000 000.

Einsatzbereiche: Basistyp für std.rect und std.circle, Spiellogik, Bildschirmkoordinaten.

Autor: Andreas Röne
Copyright: 2024–2025 Andreas Röne
Quelle: std/vector.lyx

<WRAP info> Seit lyxc 1.0.20F rechnen alle fünf früher fehlerhaften Funktionen richtig (#1476, #1477 samt der Winkelursache #1444, #1447):

Vec2Project((3,4), (1,0))    = (3000, 0)
Vec2Rotate((1,0), 90°)       = (0, 1000)
Vec2Heading((1,0))           = 0
Vec2Reflect((1,0), (0,1))    = (1000, 0)

Die Winkel werden in Mikrograd übergebenVec2Rotate(v, 90000000) dreht um 90°. Das entspricht der Umstellung in std.math; wer Grad einsetzt, dreht praktisch um null. </WRAP>

Import

import std.vector;

Zieht std.math als Abhängigkeit mit.


Der Typ Vec2

Feld Typ
x int64
y int64

Ein Vec2 ist ein einfacher Verbund und wird als Wert übergeben — Zuweisungen kopieren, es gibt nichts freizugeben. Die Bedeutung der Einheiten legt der Aufrufer fest: Pixel, Millimeter, Mikro-Pixel.

Festkomma zum Faktor 1 000 000. Drei Stellen arbeiten mit diesem Maßstab:

Stelle Bedeutung
Ergebnis von Vec2Normalize 1 000 000 entspricht der Länge 1
Parameter t von Vec2Lerp 0 = Startpunkt, 1 000 000 = Zielpunkt
Winkel bei Vec2Rotate, Vec2AngleTo, Vec2Heading Mikrograd: 90 000 000 = 90°

Funktionen

Erzeugen

Signatur Beschreibung
Vec2New(x, y: int64): Vec2 Vektor aus zwei Werten
Vec2Zero(): Vec2 Nullvektor (0, 0)
Vec2FromScalar(v: int64): Vec2 (v, v)

Rechnen

Signatur Beschreibung Status
Vec2Add(a, b: Vec2): Vec2 Summe ok
Vec2Sub(a, b: Vec2): Vec2 Differenz ok
Vec2Mul(v: Vec2, scalar: int64): Vec2 Skalierung ok
Vec2Div(v: Vec2, scalar: int64): Vec2 Division je Komponente. scalar == 0 beendet den Prozess mit SIGFPE (#1476) ok, aber ungeschützt
Vec2Negate(v: Vec2): Vec2 Vorzeichenwechsel ok
Vec2Dot(a, b: Vec2): int64 Skalarprodukt x·x + y·y ok
Vec2Cross(a, b: Vec2): int64 z-Komponente des Kreuzprodukts x·y′ − y·x′ ok

Längen und Abstände

Signatur Beschreibung Status
Vec2LengthSquared(v: Vec2): int64 Quadrat der Länge — ohne Wurzel, exakt ok
Vec2Length(v: Vec2): int64 Länge, ganzzahlig abgerundet ok
Vec2DistanceSquared(a, b: Vec2): int64 Quadrat des Abstands ok
Vec2Distance(a, b: Vec2): int64 Abstand, ganzzahlig abgerundet ok
Vec2Normalize(v: Vec2): Vec2 Einheitsvektor im Maßstab 1e6; Nullvektor ergibt (0,0) ok
Vec2NormalizeSafe(v, fallback: Vec2): Vec2 wie oben, aber fallback beim Nullvektor ok

Für Vergleiche „welcher Punkt ist näher„ immer die quadrierten Varianten nehmen: sie sind exakt und sparen die Wurzel. Vec2Length rundet ab — die Länge von (1,1) ist 1, nicht 1,41.

Verändern und Begrenzen

Signatur Beschreibung Status
Vec2Lerp(a, b: Vec2, t: int64): Vec2 Lineare Interpolation, t in Millionstel. Werte außerhalb 0…1 000 000 werden nicht geklemmt ok
Vec2Clamp(v, min, max: Vec2): Vec2 Begrenzung je Komponente ok
Vec2Min(a, b: Vec2): Vec2 · Vec2Max Komponentenweises Minimum/Maximum ok
Vec2Abs(v: Vec2): Vec2 · Vec2Sign Betrag bzw. Vorzeichen je Komponente ok

Drehen

Signatur Beschreibung Status
Vec2Rotate90(v: Vec2): Vec2 Vierteldrehung gegen den Uhrzeigersinn ok
Vec2Rotate180(v: Vec2): Vec2 Halbe Drehung ok
Vec2Rotate270(v: Vec2): Vec2 Dreivierteldrehung ok
Vec2Perpendicular(v: Vec2): Vec2 Senkrechter Vektor — identisch zu Vec2Rotate90 ok
Vec2Rotate(v: Vec2, mikrograd: int64): Vec2 Drehung um einen beliebigen Winkel ok

Die festen Drehungen kommen ohne Trigonometrie aus — sie tauschen und negieren nur Komponenten und sind deshalb exakt. Wo ein rechter Winkel genügt, sind sie der richtige Weg.

Prüfen

Signatur Beschreibung Status
Vec2Equal(a, b: Vec2): bool · Vec2NotEqual Komponentenvergleich ok
Vec2IsZero(v: Vec2): bool Ob beide Komponenten 0 sind ok
Vec2IsNormalized(v: Vec2): bool Ob die Länge dem Maßstab 1e6 entspricht ok

Geometrie

Signatur Beschreibung Status
Vec2Project(v, onto: Vec2): Vec2 Projektion auf einen anderen Vektor liefert (0,0) (#1476)
Vec2Reflect(v, normal: Vec2): Vec2 Spiegelung an einer Normalen gibt v zurück (#1476)
Vec2AngleTo(v, target: Vec2): int64 Winkel zwischen zwei Vektoren, in Mikrograd ok
Vec2Heading(v: Vec2): int64 Richtungswinkel gegenüber der x-Achse, in Mikrograd ok

Beispiel

import std.vector;

fn zeig(name: pchar, v: Vec2): void {
    PrintLn(name, " = (", IntToStr(v.x), ", ", IntToStr(v.y), ")");
}

fn main(): int64 {
    var a: Vec2 := Vec2New(3, 4);
    var b: Vec2 := Vec2New(1, 2);

    zeig("a           ", a);
    zeig("a + b       ", Vec2Add(a, b));
    zeig("a - b       ", Vec2Sub(a, b));
    zeig("a * 3       ", Vec2Mul(a, 3));
    PrintLn("Dot(a,b)     = ", IntToStr(Vec2Dot(a, b)), "   (3*1 + 4*2)");
    PrintLn("Cross(a,b)   = ", IntToStr(Vec2Cross(a, b)), "   (3*2 - 4*1)");
    PrintLn("LengthSquared = ", IntToStr(Vec2LengthSquared(a)));
    PrintLn("Length        = ", IntToStr(Vec2Length(a)), "   (ganzzahlig abgerundet)");

    // Normalisieren liefert Festkomma mit Faktor 1 000 000
    zeig("Normalize(3,4)", Vec2Normalize(a));
    PrintLn("   600000/1000000 = 0,6 und 800000/1000000 = 0,8");

    // Feste Drehungen brauchen keine Trigonometrie
    zeig("Rotate90      ", Vec2Rotate90(a));
    zeig("Rotate180     ", Vec2Rotate180(a));
    zeig("Perpendicular ", Vec2Perpendicular(a));

    // Interpolation: t in Millionstel
    zeig("Lerp 0 %      ", Vec2Lerp(Vec2Zero(), Vec2New(100, 200), 0));
    zeig("Lerp 50 %     ", Vec2Lerp(Vec2Zero(), Vec2New(100, 200), 500000));
    zeig("Lerp 100 %    ", Vec2Lerp(Vec2Zero(), Vec2New(100, 200), 1000000));
    return 0;
}

Ausgabe:

a            = (3, 4)
a + b        = (4, 6)
a - b        = (2, 2)
a * 3        = (9, 12)
Dot(a,b)     = 11   (3*1 + 4*2)
Cross(a,b)   = 2   (3*2 - 4*1)
LengthSquared = 25
Length        = 5   (ganzzahlig abgerundet)
Normalize(3,4) = (600000, 800000)
   600000/1000000 = 0,6 und 800000/1000000 = 0,8
Rotate90       = (-4, 3)
Rotate180      = (-3, -4)
Perpendicular  = (-4, 3)
Lerp 0 %       = (0, 0)
Lerp 50 %      = (50, 100)
Lerp 100 %     = (100, 200)


Was nicht funktioniert

Geprüft mit lyxc 1.0.21A.

Vec2Project und Vec2Reflect (#1476)

Project((3,4) auf (1,0)) = (0, 0)      erwartet (3, 0)
Reflect((1,-1) an (0,1)) = (1, -1)     erwartet (1, 1)

Beide teilen das Skalarprodukt durch 1 000 000. Bei ganzzahligen Vektoren ist dieses Produkt klein — für (3,4)·(1,0) = 3 ergibt die Division 0, und die anschließende Multiplikation liefert den Nullvektor. Vec2Reflect zieht dadurch nichts ab und gibt den Eingabevektor zurück; eine Spiegelung, die nichts spiegelt, fällt am Aufrufort nicht auf.

Bei Vec2Project kommt hinzu, dass die Quadratlänge des Zielvektors zwar berechnet, aber nicht in die Rechnung übernommen wird — der Nenner der Projektionsformel fehlt.

Ersatz bis zum Fix:

fn ProjektionAuf(v: Vec2, onto: Vec2): Vec2 {
    var lenSq: int64 := Vec2LengthSquared(onto);
    if (lenSq == 0) { return Vec2Zero(); }
    var dot: int64 := Vec2Dot(v, onto);
    var r: Vec2;
    r.x := onto.x * dot / lenSq;
    r.y := onto.y * dot / lenSq;
    return r;
}

Vec2Rotate, Vec2AngleTo, Vec2Heading (#1477)

(100,0) um 0 Grad   = (-64436, 0)      erwartet (100, 0)
(100,0) um 90 Grad  = (0, -64436)      erwartet (0, 100)
(100,0) um 180 Grad = (64436, 0)       erwartet (-100, 0)
Heading(1,1)         = 89999550        erwartet 45000000
AngleTo((1,0),(0,1)) = 179999999       erwartet 90000000

Seit 1.0.20F stimmen diese Werte — die Ursache lag nicht in dieser Unit, sondern in Sin64/Cos64 (#1444) und Atan2Microdegrees (#1447) aus std.math. Nachgemessen:

Vec2Rotate((1,0), 90000000)   = (0, 1000)
Vec2Heading((1,0))            = 0
Vec2Reflect((1,0), (0,1))     = (1000, 0)

Der Winkel wird in Mikrograd übergebenVec2Rotate(v, 90000000) dreht um 90°. Für rechte Winkel bleiben Vec2Rotate90/180/270 der kürzere Weg.

Division durch null (#1476)

Vec2Div(v, 0) läuft ungeprüft in die Hardware-Division und beendet den Prozess mit SIGFPE. Der Divisor ist vorher selbst zu prüfen.


Verwandte Units