====== std.vector — 2D-Vektoren ====== → [[lyx_-_programmiersprache:units|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 [[lyx_-_programmiersprache:units:rect|std.rect]] und [[lyx_-_programmiersprache:units:circle|std.circle]], Spiellogik, Bildschirmkoordinaten. **Autor:** Andreas Röne\\ **Copyright:** 2024–2025 Andreas Röne\\ **Quelle:** ''std/vector.lyx'' **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 übergeben** — ''Vec2Rotate(v, 90000000)'' dreht um 90°. Das entspricht der Umstellung in [[lyx_-_programmiersprache:units:math|std.math]]; wer Grad einsetzt, dreht praktisch um null. ===== Import ===== import std.vector; Zieht [[lyx_-_programmiersprache:units:math|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 [[lyx_-_programmiersprache:units:math|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 übergeben** — ''Vec2Rotate(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 ===== * [[lyx_-_programmiersprache:units:math|std.math]] — ''IntSqrt'', ''Clamp64'' und die Winkelfunktionen * [[lyx_-_programmiersprache:units:rect|std.rect]] · [[lyx_-_programmiersprache:units:circle|std.circle]] — Geometrie auf Basis von ''Vec2'' * [[lyx_-_programmiersprache:units:math_batch|std.math_batch]] — SIMD-Operationen auf Zahlenfeldern