std/linear

Vectors, matrices, quaternions and angles over one generic scalar, plus Fixed, the fixed-point scalar whose answers are the same bits everywhere.

Diese Seite gibt es noch nicht auf Deutsch, deshalb steht sie hier auf Englisch. Zur englischen Seite

Die generierte Referenz von std/linear zeigt jede öffentliche Deklaration mit Signatur, Doc-Kommentar und Beispielen.

std/linear is the vector arithmetic every geometric package builds on: Vector2, Vector3, Vector4, Matrix2, Matrix3, Matrix4, Quaternion, Angle, and Fixed, the deterministic scalar. There is one vector type per width and not two - Vector2 is the float vector, Vector2<Int> the pixel or tile vector, Vector2<Fixed> the deterministic one - and what each of them can do follows from the scalar's bound: arithmetic under Numeric, a sign under Signed, and everything that needs a root or an angle under Real (std/number). It is a pure value library and knows nothing about entities, rendering or files.

Import#

use Vector2, Vector3, Vector4, Angle from "std/linear"
use Matrix2, Matrix3, Matrix4, Quaternion, Fixed from "std/linear"
use Vector2 from "std/linear"

const step = Vector2 3.0, 4.0
const tile = Vector2 3, 4
print "{step.length()} {step.normalized()} {tile.manhattanLength()}"

Declarations#

Vector2, Vector3, Vector4#

type Vector2<Scalar: Numeric = Float> with Add, Subtract, Multiply<Scalar>, Divide<Scalar> {
  x: Scalar
  y: Scalar
  static fn filled(value: Scalar): Self
  static zero: Self = Vector2 Scalar.zero, Scalar.zero
  static one: Self = Vector2 Scalar.one, Scalar.one
  static unitX: Self = Vector2 Scalar.one, Scalar.zero
  static unitY: Self = Vector2 Scalar.zero, Scalar.one
  fn add(other: Self): Self
  fn subtract(other: Self): Self
  fn multiply(other: Scalar): Self
  fn divide(other: Scalar): Self
  fn dot(other: Self): Scalar
  fn lengthSquared(): Scalar
  fn cross(other: Self): Scalar
  fn scaled(by: Self): Self
  fn divided(by: Self): Self
  fn min(other: Self): Self
  fn max(other: Self): Self
  fn clamped(low: Self, high: Self): Self
  fn withX(value: Scalar): Self
  fn withY(value: Scalar): Self
  fn isZero(): Bool
  fn largestComponent(): Scalar
  fn smallestComponent(): Scalar
  fn sum(): Scalar
}
extend<Scalar: Signed> Vector2<Scalar> with Negate
extend<Scalar: Signed> Vector2<Scalar> {
  fn absolute(): Self
  fn manhattanLength(): Scalar
  fn manhattanDistanceTo(other: Self): Scalar
  fn perpendicular(): Self
}
extend<Scalar: Real> Vector2<Scalar> {
  fn length(): Scalar
  fn distanceTo(other: Self): Scalar
  fn distanceSquaredTo(other: Self): Scalar
  fn normalized(): Self
  fn withLength(value: Scalar): Self
  fn angle(): Angle<Scalar>
  fn angleTo(other: Self): Angle<Scalar>
  fn rotated(by: Angle<Scalar>): Self
  fn rotatedAround(center: Self, by: Angle<Scalar>): Self
  fn interpolated(toward: Self, by: Scalar): Self
  fn projectedOnto(other: Self): Self
  fn reflected(normal: Self): Self
  fn isCloseTo(other: Self, tolerance: Scalar): Bool
}
extend Vector2<Float> {
  fn rounded(): Vector2<Int>
  fn floored(): Vector2<Int>
  fn ceiling(): Vector2<Int>
}
extend Vector2<Int> {
  fn toFloat(): Vector2<Float>
  fn toFixed(): Vector2<Fixed>
}
type Vector3<Scalar: Numeric = Float> with Add, Subtract, Multiply<Scalar>, Divide<Scalar> {
  x: Scalar
  y: Scalar
  z: Scalar
  static fn filled(value: Scalar): Self
  static zero: Self = Vector3 Scalar.zero, Scalar.zero, Scalar.zero
  static one: Self = Vector3 Scalar.one, Scalar.one, Scalar.one
  static unitX: Self = Vector3 Scalar.one, Scalar.zero, Scalar.zero
  static unitY: Self = Vector3 Scalar.zero, Scalar.one, Scalar.zero
  static unitZ: Self = Vector3 Scalar.zero, Scalar.zero, Scalar.one
  fn add(other: Self): Self
  fn subtract(other: Self): Self
  fn multiply(other: Scalar): Self
  fn divide(other: Scalar): Self
  fn dot(other: Self): Scalar
  fn lengthSquared(): Scalar
  fn cross(other: Self): Self
  fn scaled(by: Self): Self
  fn divided(by: Self): Self
  fn min(other: Self): Self
  fn max(other: Self): Self
  fn clamped(low: Self, high: Self): Self
  fn toVector2(): Vector2<Scalar>
  fn withX(value: Scalar): Self
  fn withY(value: Scalar): Self
  fn withZ(value: Scalar): Self
  fn isZero(): Bool
  fn largestComponent(): Scalar
  fn smallestComponent(): Scalar
  fn sum(): Scalar
}
extend<Scalar: Signed> Vector3<Scalar> with Negate
extend<Scalar: Signed> Vector3<Scalar> {
  fn absolute(): Self
  fn manhattanLength(): Scalar
  fn manhattanDistanceTo(other: Self): Scalar
}
extend<Scalar: Real> Vector3<Scalar> {
  fn length(): Scalar
  fn distanceTo(other: Self): Scalar
  fn distanceSquaredTo(other: Self): Scalar
  fn normalized(): Self
  fn withLength(value: Scalar): Self
  fn angleTo(other: Self): Angle<Scalar>
  fn interpolated(toward: Self, by: Scalar): Self
  fn projectedOnto(other: Self): Self
  fn reflected(normal: Self): Self
  fn isCloseTo(other: Self, tolerance: Scalar): Bool
}
extend Vector3<Float> {
  fn rounded(): Vector3<Int>
  fn floored(): Vector3<Int>
  fn ceiling(): Vector3<Int>
}
extend Vector3<Int> {
  fn toFloat(): Vector3<Float>
  fn toFixed(): Vector3<Fixed>
}
type Vector4<Scalar: Numeric = Float> with Add, Subtract, Multiply<Scalar>, Divide<Scalar> {
  x: Scalar
  y: Scalar
  z: Scalar
  w: Scalar
  static fn filled(value: Scalar): Self
  static zero: Self = Vector4 Scalar.zero, Scalar.zero, Scalar.zero, Scalar.zero
  static one: Self = Vector4 Scalar.one, Scalar.one, Scalar.one, Scalar.one
  static unitX: Self = Vector4 Scalar.one, Scalar.zero, Scalar.zero, Scalar.zero
  static unitY: Self = Vector4 Scalar.zero, Scalar.one, Scalar.zero, Scalar.zero
  static unitZ: Self = Vector4 Scalar.zero, Scalar.zero, Scalar.one, Scalar.zero
  static unitW: Self = Vector4 Scalar.zero, Scalar.zero, Scalar.zero, Scalar.one
  static fn of(point: Vector3<Scalar>, w: Scalar): Self
  fn add(other: Self): Self
  fn subtract(other: Self): Self
  fn multiply(other: Scalar): Self
  fn divide(other: Scalar): Self
  fn dot(other: Self): Scalar
  fn lengthSquared(): Scalar
  fn scaled(by: Self): Self
  fn min(other: Self): Self
  fn max(other: Self): Self
  fn toVector3(): Vector3<Scalar>
  fn isZero(): Bool
  fn sum(): Scalar
}
extend<Scalar: Signed> Vector4<Scalar> with Negate
extend<Scalar: Signed> Vector4<Scalar> {
  fn absolute(): Self
}
extend<Scalar: Real> Vector4<Scalar> {
  fn length(): Scalar
  fn normalized(): Self
  fn projected(): Vector3<Scalar>
  fn interpolated(toward: Self, by: Scalar): Self
  fn isCloseTo(other: Self, tolerance: Scalar): Bool
}
extend Vector4<Int> {
  fn toFloat(): Vector4<Float>
  fn toFixed(): Vector4<Fixed>
}

A point, a direction or a size, over whatever scalar the program counts in. Vector2(1, 2) is a Vector2<Int> and Vector2(1.0, 2.0) is a Vector2<Float>: the literals decide, and the two do not mix - toFloat(), toFixed(), rounded(), floored() and ceiling() cross the boundary on purpose.

Under Numeric: add, subtract, multiply and divide by a scalar, dot, lengthSquared, cross (a number in the plane, a vector in space), scaled(by:) and divided(by:) component by component, min, max, clamped, filled, isZero, withX/withY/withZ, sum, largestComponent, smallestComponent. Under Signed: negate, absolute, manhattanLength, manhattanDistanceTo, and perpendicular in the plane - a quarter turn that is exact for integers and needs no trigonometry. Under Real: length, distanceTo, normalized, withLength, angle, angleTo, rotated, rotatedAround, interpolated, projectedOnto, reflected, isCloseTo.

Nothing here knows which way is up. y is the second component and that is all it is.

Angle#

type Angle<Scalar: Real = Float> with Add, Subtract, Negate, Multiply<Scalar>, Compare {
  radians: Scalar
  static zero: Self = Angle Scalar.zero
  static fn degrees(value: Scalar): Self
  fn compare(other: Self): Ordering
  fn add(other: Self): Self
  fn subtract(other: Self): Self
  fn negate(): Self
  fn multiply(other: Scalar): Self
  fn toDegrees(): Scalar
  fn sine(): Scalar
  fn cosine(): Scalar
  fn tangent(): Scalar
  fn halved(): Self
  fn normalized(): Self
}
extend<Scalar: Real> Angle<Scalar> {
  static quarterTurn: Self = Angle Scalar.pi.halved()
  static halfTurn: Self = Angle Scalar.pi
  static fullTurn: Self = Angle Scalar.tau
}

A rotation, held in radians and constructed by the unit it is written in: Angle(1.5) is radians - the field says so - and Angle.degrees 90.0 is the other unit, spelled out. That wrapper is the whole point: a quarter turn written as 90 can never be read as 90 radians. sine, cosine, tangent, toDegrees, halved and normalized (the same rotation in (-pi, pi]) round it out; Angle.zero, .quarterTurn, .halfTurn and .fullTurn are the four constants, for every scalar, made of its own pi and tau.

Angles grow from the first axis towards the second: a quarter turn takes unitX onto unitY. Whether a viewer calls that clockwise depends on which way the program draws its second axis, and the library does not decide it.

Matrix2, Matrix3, Matrix4#

type Matrix2<Scalar: Numeric = Float> with Add, Subtract, Multiply {
  xAxis: Vector2<Scalar>
  yAxis: Vector2<Scalar>
  static identity: Self = Matrix2 Vector2(Scalar.one, Scalar.zero), Vector2(Scalar.zero, Scalar.one)
  static fn scaling(by: Vector2<Scalar>): Self
  fn transposed(): Self
  fn determinant(): Scalar
  fn at(row: Int, column: Int): Scalar
  fn add(other: Self): Self
  fn subtract(other: Self): Self
  fn multiply(other: Self): Self
  fn applied(to: Vector2<Scalar>): Vector2<Scalar>
}
extend<Scalar: Signed> Matrix2<Scalar> with Negate
extend<Scalar: Real> Matrix2<Scalar> {
  static fn rotation(by: Angle<Scalar>): Self
  fn inverse(): Self?
  fn isCloseTo(other: Self, tolerance: Scalar): Bool
}
extend<Scalar: Numeric> Matrix2<Scalar> with Power<Int64>
type Matrix3<Scalar: Numeric = Float> with Add, Subtract, Multiply {
  xAxis: Vector3<Scalar>
  yAxis: Vector3<Scalar>
  zAxis: Vector3<Scalar>
  static identity: Self
  static fn scaling(by: Vector3<Scalar>): Self
  fn linearPart(): Matrix2<Scalar>
  fn translationPart(): Vector2<Scalar>
  fn transposed(): Self
  fn determinant(): Scalar
  fn at(row: Int, index: Int): Scalar
  fn column(index: Int): Vector3<Scalar>
  fn row(index: Int): Vector3<Scalar>
  fn add(other: Self): Self
  fn subtract(other: Self): Self
  fn multiply(other: Self): Self
  fn applied(to: Vector3<Scalar>): Vector3<Scalar>
  fn transformedDirection(direction: Vector2<Scalar>): Vector2<Scalar>
  static fn affine(linear: Matrix2<Scalar>, by: Vector2<Scalar>): Self
  fn transformedPoint(point: Vector2<Scalar>): Vector2<Scalar>
}
extend<Scalar: Signed> Matrix3<Scalar> with Negate
extend<Scalar: Real> Matrix3<Scalar> {
  static fn rotationAroundX(by: Angle<Scalar>): Self
  static fn rotationAroundY(by: Angle<Scalar>): Self
  static fn rotationAroundZ(by: Angle<Scalar>): Self
  fn inverse(): Self?
  fn isCloseTo(other: Self, tolerance: Scalar): Bool
}
extend<Scalar: Numeric> Matrix3<Scalar> with Power<Int64>
type Matrix4<Scalar: Numeric = Float> with Add, Subtract, Multiply {
  xAxis: Vector4<Scalar>
  yAxis: Vector4<Scalar>
  zAxis: Vector4<Scalar>
  wAxis: Vector4<Scalar>
  static identity: Self
  fn linearPart(): Matrix3<Scalar>
  fn translationPart(): Vector3<Scalar>
  fn transposed(): Self
  fn column(index: Int): Vector4<Scalar>
  fn at(row: Int, index: Int): Scalar
  fn row(index: Int): Vector4<Scalar>
  fn add(other: Self): Self
  fn subtract(other: Self): Self
  fn multiply(other: Self): Self
  fn applied(to: Vector4<Scalar>): Vector4<Scalar>
  fn transformedDirection(direction: Vector3<Scalar>): Vector3<Scalar>
  fn determinant(): Scalar
  static fn scaling(by: Vector3<Scalar>): Self
  static fn translation(of: Vector3<Scalar>): Self
  static fn affine(linear: Matrix3<Scalar>, by: Vector3<Scalar>): Self
  fn transformedPoint(point: Vector3<Scalar>): Vector3<Scalar>
}
extend<Scalar: Signed> Matrix4<Scalar> with Negate
extend<Scalar: Real> Matrix4<Scalar> {
  fn inverseAffine(): Self?
  fn inverse(): Self?
  fn isCloseTo(other: Self, tolerance: Scalar): Bool
}
extend<Scalar: Numeric> Matrix4<Scalar> with Power<Int64>

A transformation, held as the vectors its basis lands on: the fields are the columns (xAxis, yAxis, zAxis, wAxis), so reading a matrix is reading two, three or four vectors and matrix.xAxis is matrix.applied(to: unitX). Vectors are columns and a transformation is applied on the left, so a * b is "a after b" and a chain reads right to left as it does in mathematics.

Matrix2 is the linear part in the plane, Matrix3 is both the linear transformation of space and the affine transformation of the plane (affine, linearPart, translationPart, transformedPoint, transformedDirection), and Matrix4 is the affine transformation of space. transposed, determinant, at, column, row, scaling, applied(to:) and the affine forms (affine, Matrix4.translation, transformedPoint) are everywhere, so a tile grid has them; rotation (rotationAroundX/Y/Z for Matrix3), inverse and isCloseTo need a Real scalar. Matrix4 answers both a general inverse - the adjugate over the determinant, for a matrix of any shape - and inverseAffine, the short way for the matrices a scene graph is made of.

The matrix-vector product is applied(to:) and not *: a type has one namespace of members, and multiply is already the composition of two matrices.

There is no projection matrix here. A projection depends on the clip space of the API that consumes it, and this package knows nothing about an API.

Quaternion#

type Quaternion<Scalar: Real = Float> with Multiply, Negate {
  x: Scalar
  y: Scalar
  z: Scalar
  w: Scalar
  static identity: Self = Quaternion Scalar.zero, Scalar.zero, Scalar.zero, Scalar.one
  static fn rotation(around: Vector3<Scalar>, by: Angle<Scalar>): Self
  fn axisPart(): Vector3<Scalar>
  fn conjugate(): Self
  fn negate(): Self
  fn lengthSquared(): Scalar
  fn length(): Scalar
  fn normalized(): Self
  fn multiply(other: Self): Self
  fn applied(to: Vector3<Scalar>): Vector3<Scalar>
  fn toMatrix3(): Matrix3<Scalar>
  fn interpolated(toward: Self, by: Scalar): Self
  fn sphericallyInterpolated(toward: Self, by: Scalar): Self
  fn dot(other: Self): Scalar
  fn isCloseTo(other: Self, tolerance: Scalar): Bool
}

A rotation of space that composes and interpolates without shearing and without the gimbal lock three angles in a row have. Quaternion.rotation(around:by:) builds one from an axis and an Angle, applied(to:) turns a point with it, multiply composes, conjugate undoes, toMatrix3 hands it to a shader, and interpolated walks between two along the straight line (cheap, fastest in the middle) where sphericallyInterpolated walks at a constant speed.

Fixed#

type Fixed with Signed, Real, Power, Hash, Show, TryFrom<String, NumberParseError> {
  parts: Int64
  static partsPerWhole: Int64 = 65536
  static largest: Self = Self 9223372036854775807
  static smallest: Self = Self(-9223372036854775807 - 1)
  static step: Self = Self 1
  static zero: Self = Self 0
  static one: Self = Self 65536
  static pi: Self = Self 205887
  static tau: Self = Self 411775
  static epsilon: Self = Self 1
  static e: Self = Self 178145
  static fn approximating(value: Float64): Self
  fn toFloat(): Float64
  fn toInt(): Int64
  fn add(other: Self): Self
  fn subtract(other: Self): Self
  fn multiply(other: Self): Self
  fn divide(other: Self): Self
  fn remainder(other: Self): Self
  fn negate(): Self
  fn absolute(): Self
  fn equals(other: Self): Bool
  fn compare(other: Self): Ordering
  fn hash(): Int
  fn show(): String
  static fn tryFrom(text: String): Result<Self, NumberParseError>
  fn squareRoot(): Self
  fn sine(): Self
  fn cosine(): Self
  fn tangent(): Self
  fn arcSine(): Self
  fn arcCosine(): Self
  fn arcTangent(): Self
  fn arcTangentDivided(by: Self): Self
  fn exponential(): Self
  fn naturalLogarithm(): Self
  fn power(exponent: Self): Self
  fn floor(): Self
  fn ceiling(): Self
  fn round(): Self
  fn halved(): Self
  fn radiansOfDegrees(): Self
  fn degreesOfRadians(): Self
  fn doubled(): Self
  fn isCloseTo(other: Self, tolerance: Self = Self(66)): Bool
}
extend Fixed with Power<Int64>
extend Fixed with From<Int64>

A number held as a whole number of 1/65536 parts - the Q16.16 scalar - and the only Real whose answers are the same bits on every platform and out of every back end. Every operation on it, the square root and the trigonometry included, is integer arithmetic: the square root is Newton's method on integers, and sine, cosine and the arc functions are CORDIC, sixteen rotations of an addition and a division by a power of two each.

That is what a lockstep simulation, a replay and a checksum over a world state need, and it is why Vector2<Fixed>, Rectangle<Fixed> and every intersection test of std/geometry accept it in place of Float with no other change to the code.

Fixed.from(anInt) is exact, Fixed.tryFrom(text) rounds to the nearest part, Fixed.approximating(aFloat) is the bridge out of floating point, and show writes the exact decimal the value is - so Fixed.tryFrom("0.1") shows as 0.100006103515625, because that is the number.

  • std/geometry - the shapes built out of these vectors.
  • std/number - Numeric, Signed and Real, the three bounds this package layers along.
  • std/number - Real, the bound every angle and root of this package is written under.
  • Bounds - the conditional extend that decides which member exists for which scalar.
Esc