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.
Related#
- std/geometry - the shapes built out of these vectors.
- std/number -
Numeric,SignedandReal, 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
extendthat decides which member exists for which scalar.