- A 4-vector doubles as a unit quaternion in (x, y, z, w) order: QuanternionMultiply, QuanternionConjugate, QuanternionFromAxisAngle, QuanternionFromUnitVectors (shortest arc), QuanternionNlerp and QuanternionSlerp, next to the existing QuanternionFromBasis. - MatrixRowMajor gains Rotation (from a quaternion), TRS, InverseAffine, Transpose and Decompose. The 4x4 transforms row vectors and the 4x3 column vectors (the VkTransformMatrixKHR layout); each of these means the same transform in both. The 4x3 Rotation copied the 4x4's rows, which rotated by the inverse. - The static Normalize no longer assumes three components. - Tests: Quaternion, and Matrix extended. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
444 lines
22 KiB
C++
Executable file
444 lines
22 KiB
C++
Executable file
//SPDX-License-Identifier: LGPL-3.0-only
|
||
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||
|
||
export module Crafter.Math:MatrixRowMajor;
|
||
|
||
import :Basic;
|
||
import :Vector;
|
||
import :VectorF32;
|
||
import std;
|
||
|
||
namespace Crafter {
|
||
// Row-major matrix whose rows live in optimized SIMD vectors. All
|
||
// multiplications are expressed as broadcast + fused multiply-add against
|
||
// these row vectors so the heavy work stays in __m128/__m256/__m512 land.
|
||
//
|
||
// CollumSize is the column count; only CollumSize == 4 is implemented (the
|
||
// matrix gets one SIMD row per row). Repeats is reserved for future
|
||
// SoA-style batching.
|
||
export template <typename T, std::uint32_t CollumSize, std::uint32_t RowSize, std::uint32_t Repeats>
|
||
class MatrixRowMajor {
|
||
public:
|
||
// Rows are exposed publicly so users can compose with VectorF32 ops
|
||
// directly without going through an accessor. Each row is a single
|
||
// SIMD vector covering all columns.
|
||
VectorF32<static_cast<std::uint8_t>(CollumSize), 1> rows[RowSize];
|
||
|
||
MatrixRowMajor() = default;
|
||
|
||
MatrixRowMajor(
|
||
float x0, float y0, float z0, float w0,
|
||
float x1, float y1, float z1, float w1,
|
||
float x2, float y2, float z2, float w2,
|
||
float x3, float y3, float z3, float w3
|
||
) requires(CollumSize == 4 && RowSize == 4 && Repeats == 1 && std::same_as<T, float>) {
|
||
alignas(16) float r0[4] = { x0, y0, z0, w0 };
|
||
alignas(16) float r1[4] = { x1, y1, z1, w1 };
|
||
alignas(16) float r2[4] = { x2, y2, z2, w2 };
|
||
alignas(16) float r3[4] = { x3, y3, z3, w3 };
|
||
rows[0] = VectorF32<4, 1>(r0);
|
||
rows[1] = VectorF32<4, 1>(r1);
|
||
rows[2] = VectorF32<4, 1>(r2);
|
||
rows[3] = VectorF32<4, 1>(r3);
|
||
}
|
||
|
||
MatrixRowMajor(
|
||
float x0, float y0, float z0, float w0,
|
||
float x1, float y1, float z1, float w1,
|
||
float x2, float y2, float z2, float w2
|
||
) requires(CollumSize == 4 && RowSize == 3 && Repeats == 1 && std::same_as<T, float>) {
|
||
alignas(16) float r0[4] = { x0, y0, z0, w0 };
|
||
alignas(16) float r1[4] = { x1, y1, z1, w1 };
|
||
alignas(16) float r2[4] = { x2, y2, z2, w2 };
|
||
rows[0] = VectorF32<4, 1>(r0);
|
||
rows[1] = VectorF32<4, 1>(r1);
|
||
rows[2] = VectorF32<4, 1>(r2);
|
||
}
|
||
|
||
// Flatten to RowSize*CollumSize contiguous floats (row-major). Replaces
|
||
// the old `m[i][j]` raw array access for callers that need a packed
|
||
// float buffer (e.g. GPU upload via memcpy).
|
||
constexpr void Store(float* dst) const requires(CollumSize == 4 && Repeats == 1 && std::same_as<T, float>) {
|
||
for (std::uint32_t i = 0; i < RowSize; ++i) {
|
||
rows[i].Store(dst + i * 4);
|
||
}
|
||
}
|
||
|
||
constexpr std::array<float, RowSize * 4> Store() const requires(CollumSize == 4 && Repeats == 1 && std::same_as<T, float>) {
|
||
std::array<float, RowSize * 4> out{};
|
||
Store(out.data());
|
||
return out;
|
||
}
|
||
|
||
// Affine transform: extend `b` with implicit w=1 (translation) and dot
|
||
// each row against it. Three row-dots packed into one batched 4-pair
|
||
// Dot call (lane 3 of the result is the duplicated row 0 and gets
|
||
// discarded).
|
||
VectorF32<3, 1> operator*(VectorF32<3, 1> b) const requires(CollumSize == 4 && RowSize == 3 && Repeats == 1 && std::same_as<T, float>) {
|
||
std::array<float, 4> bArr = b.template Store<float>();
|
||
alignas(16) float bhBuf[4] = { bArr[0], bArr[1], bArr[2], 1.0f };
|
||
VectorF32<4, 1> bh(bhBuf);
|
||
|
||
VectorF32<1, 4> dots = VectorF32<4, 1>::Dot(
|
||
rows[0], bh, rows[1], bh, rows[2], bh, rows[0], bh);
|
||
|
||
std::array<float, 4> dotsArr = dots.template Store<float>();
|
||
alignas(16) float outBuf[4] = { dotsArr[0], dotsArr[1], dotsArr[2], 0.0f };
|
||
return VectorF32<3, 1>(outBuf);
|
||
}
|
||
|
||
// Linear transform (no translation): same as the affine version but
|
||
// with bh.w = 0 so the translation column does not contribute. Useful
|
||
// for direction vectors and normals.
|
||
VectorF32<3, 1> TransformNormal(VectorF32<3, 1> b) const requires(CollumSize == 4 && RowSize == 3 && Repeats == 1 && std::same_as<T, float>) {
|
||
std::array<float, 4> bArr = b.template Store<float>();
|
||
alignas(16) float bhBuf[4] = { bArr[0], bArr[1], bArr[2], 0.0f };
|
||
VectorF32<4, 1> bh(bhBuf);
|
||
|
||
VectorF32<1, 4> dots = VectorF32<4, 1>::Dot(
|
||
rows[0], bh, rows[1], bh, rows[2], bh, rows[0], bh);
|
||
|
||
std::array<float, 4> dotsArr = dots.template Store<float>();
|
||
alignas(16) float outBuf[4] = { dotsArr[0], dotsArr[1], dotsArr[2], 0.0f };
|
||
return VectorF32<3, 1>(outBuf);
|
||
}
|
||
|
||
// 4×4 matrix product via broadcast + FMA. Each result row is
|
||
// b[i][0]·rows[0] + b[i][1]·rows[1] + b[i][2]·rows[2] + b[i][3]·rows[3]
|
||
// produced with four shuffle-broadcasts and three fused multiply-adds.
|
||
MatrixRowMajor operator*(MatrixRowMajor b) const requires(CollumSize == 4 && RowSize == 4 && Repeats == 1 && std::same_as<T, float>) {
|
||
MatrixRowMajor result;
|
||
for (std::uint32_t i = 0; i < 4; ++i) {
|
||
VectorF32<4, 1> bi = b.rows[i];
|
||
VectorF32<4, 1> bx = bi.template Shuffle<{{0, 0, 0, 0}}>();
|
||
VectorF32<4, 1> by = bi.template Shuffle<{{1, 1, 1, 1}}>();
|
||
VectorF32<4, 1> bz = bi.template Shuffle<{{2, 2, 2, 2}}>();
|
||
VectorF32<4, 1> bw = bi.template Shuffle<{{3, 3, 3, 3}}>();
|
||
|
||
VectorF32<4, 1> row = bx * rows[0];
|
||
row = VectorF32<4, 1>::MulitplyAdd(by, rows[1], row);
|
||
row = VectorF32<4, 1>::MulitplyAdd(bz, rows[2], row);
|
||
row = VectorF32<4, 1>::MulitplyAdd(bw, rows[3], row);
|
||
result.rows[i] = row;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
// 4×3 affine product. Same broadcast + FMA pattern, but the implicit
|
||
// 4th row of both matrices is [0, 0, 0, 1] so the b.w · row3 term
|
||
// contributes only to the translation slot.
|
||
MatrixRowMajor operator*(MatrixRowMajor b) const requires(CollumSize == 4 && RowSize == 3 && Repeats == 1 && std::same_as<T, float>) {
|
||
alignas(16) float wRowBuf[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
|
||
VectorF32<4, 1> wRow(wRowBuf);
|
||
|
||
MatrixRowMajor result;
|
||
for (std::uint32_t i = 0; i < 3; ++i) {
|
||
VectorF32<4, 1> bi = b.rows[i];
|
||
VectorF32<4, 1> bx = bi.template Shuffle<{{0, 0, 0, 0}}>();
|
||
VectorF32<4, 1> by = bi.template Shuffle<{{1, 1, 1, 1}}>();
|
||
VectorF32<4, 1> bz = bi.template Shuffle<{{2, 2, 2, 2}}>();
|
||
VectorF32<4, 1> bw = bi.template Shuffle<{{3, 3, 3, 3}}>();
|
||
|
||
VectorF32<4, 1> row = bx * rows[0];
|
||
row = VectorF32<4, 1>::MulitplyAdd(by, rows[1], row);
|
||
row = VectorF32<4, 1>::MulitplyAdd(bz, rows[2], row);
|
||
row = VectorF32<4, 1>::MulitplyAdd(bw, wRow, row);
|
||
result.rows[i] = row;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
static MatrixRowMajor Identity() requires(CollumSize == 4 && RowSize == 3 && Repeats == 1 && std::same_as<T, float>) {
|
||
return MatrixRowMajor(
|
||
1, 0, 0, 0,
|
||
0, 1, 0, 0,
|
||
0, 0, 1, 0
|
||
);
|
||
}
|
||
|
||
static MatrixRowMajor Identity() requires(CollumSize == 4 && RowSize == 4 && Repeats == 1 && std::same_as<T, float>) {
|
||
return MatrixRowMajor(
|
||
1, 0, 0, 0,
|
||
0, 1, 0, 0,
|
||
0, 0, 1, 0,
|
||
0, 0, 0, 1
|
||
);
|
||
}
|
||
|
||
static MatrixRowMajor Scaling(float x, float y, float z) requires(CollumSize == 4 && RowSize == 4 && Repeats == 1 && std::same_as<T, float>) {
|
||
return MatrixRowMajor(
|
||
x, 0, 0, 0,
|
||
0, y, 0, 0,
|
||
0, 0, z, 0,
|
||
0, 0, 0, 1
|
||
);
|
||
}
|
||
static MatrixRowMajor Scaling(float x, float y, float z) requires(CollumSize == 4 && RowSize == 3 && Repeats == 1 && std::same_as<T, float>) {
|
||
return MatrixRowMajor(
|
||
x, 0, 0, 0,
|
||
0, y, 0, 0,
|
||
0, 0, z, 0
|
||
);
|
||
}
|
||
static MatrixRowMajor Scaling(VectorF32<3, 1> vector) requires(CollumSize == 4 && Repeats == 1 && std::same_as<T, float>) {
|
||
std::array<float, 4> a = vector.template Store<float>();
|
||
return Scaling(a[0], a[1], a[2]);
|
||
}
|
||
|
||
static MatrixRowMajor Translation(float x, float y, float z) requires(CollumSize == 4 && RowSize == 4 && Repeats == 1 && std::same_as<T, float>) {
|
||
return MatrixRowMajor(
|
||
1, 0, 0, 0,
|
||
0, 1, 0, 0,
|
||
0, 0, 1, 0,
|
||
x, y, z, 1
|
||
);
|
||
}
|
||
static MatrixRowMajor Translation(float x, float y, float z) requires(CollumSize == 4 && RowSize == 3 && Repeats == 1 && std::same_as<T, float>) {
|
||
return MatrixRowMajor(
|
||
1, 0, 0, x,
|
||
0, 1, 0, y,
|
||
0, 0, 1, z
|
||
);
|
||
}
|
||
static MatrixRowMajor Translation(VectorF32<3, 1> vector) requires(CollumSize == 4 && Repeats == 1 && std::same_as<T, float>) {
|
||
std::array<float, 4> a = vector.template Store<float>();
|
||
return Translation(a[0], a[1], a[2]);
|
||
}
|
||
|
||
// Pitch/yaw/roll Euler rotation. Computes all three sin/cos pairs as a
|
||
// single batched SinCos on a VectorF32<3, 1>, then assembles the rows.
|
||
static MatrixRowMajor Rotation(float Pitch, float Yaw, float Roll) requires(CollumSize == 4 && RowSize == 4 && Repeats == 1 && std::same_as<T, float>) {
|
||
alignas(16) float angles[4] = { Pitch, Yaw, Roll, 0.0f };
|
||
VectorF32<3, 1> v(angles);
|
||
std::tuple<VectorF32<3, 1>, VectorF32<3, 1>> sc = v.SinCos();
|
||
std::array<float, 4> s = std::get<0>(sc).template Store<float>();
|
||
std::array<float, 4> c = std::get<1>(sc).template Store<float>();
|
||
const float sp = s[0], cp = c[0];
|
||
const float sy = s[1], cy = c[1];
|
||
const float sr = s[2], cr = c[2];
|
||
|
||
return MatrixRowMajor(
|
||
cr * cy + sr * sp * sy, sr * cp, sr * sp * cy - cr * sy, 0.0f,
|
||
cr * sp * sy - sr * cy, cr * cp, sr * sy + cr * sp * cy, 0.0f,
|
||
cp * sy, -sp, cp * cy, 0.0f,
|
||
0.0f, 0.0f, 0.0f, 1.0f
|
||
);
|
||
}
|
||
|
||
static MatrixRowMajor Rotation(float Pitch, float Yaw, float Roll) requires(CollumSize == 4 && RowSize == 3 && Repeats == 1 && std::same_as<T, float>) {
|
||
alignas(16) float angles[4] = { Pitch, Yaw, Roll, 0.0f };
|
||
VectorF32<3, 1> v(angles);
|
||
std::tuple<VectorF32<3, 1>, VectorF32<3, 1>> sc = v.SinCos();
|
||
std::array<float, 4> s = std::get<0>(sc).template Store<float>();
|
||
std::array<float, 4> c = std::get<1>(sc).template Store<float>();
|
||
const float sp = s[0], cp = c[0];
|
||
const float sy = s[1], cy = c[1];
|
||
const float sr = s[2], cr = c[2];
|
||
|
||
// The 4x4's rows transposed: the 4x4 multiplies row vectors and the
|
||
// 4x3 column vectors, so the same rows here would rotate the other
|
||
// way.
|
||
return MatrixRowMajor(
|
||
cr * cy + sr * sp * sy, cr * sp * sy - sr * cy, cp * sy, 0.0f,
|
||
sr * cp, cr * cp, -sp, 0.0f,
|
||
sr * sp * cy - cr * sy, sr * sy + cr * sp * cy, cp * cy, 0.0f
|
||
);
|
||
}
|
||
|
||
static MatrixRowMajor Rotation(VectorF32<3, 1> v) requires(CollumSize == 4 && Repeats == 1 && std::same_as<T, float>) {
|
||
std::array<float, 4> a = v.template Store<float>();
|
||
return Rotation(a[0], a[1], a[2]);
|
||
}
|
||
|
||
// View matrix: builds the basis from a forward (negated) direction and
|
||
// an up reference, then dots each basis vector with -eye for the
|
||
// translation column. The four dots needed are produced by a single
|
||
// batched 4-pair Dot.
|
||
static MatrixRowMajor LookTo(VectorF32<3, 1> eyePosition, VectorF32<3, 1> eyeDirection, VectorF32<3, 1> upDirection) requires(CollumSize == 4 && RowSize == 4 && Repeats == 1 && std::same_as<T, float>) {
|
||
// R0 = up × R2 is linear in R2, so its normalized direction does
|
||
// not depend on whether we hand R2 in before or after its own
|
||
// normalize. Computing R0_raw from the un-normalized R2 lets us
|
||
// satisfy the VectorF32<3,1>::BatchSize Normalize requirement with
|
||
// one batched call (duplicating R2 and R0 in the padding slots).
|
||
VectorF32<3, 1> R0Raw = VectorF32<3, 1>::Cross(upDirection, eyeDirection);
|
||
auto normalized = VectorF32<3, 1>::Normalize(eyeDirection, R0Raw, eyeDirection, R0Raw);
|
||
VectorF32<3, 1> R2 = normalized[0];
|
||
VectorF32<3, 1> R0 = normalized[1];
|
||
VectorF32<3, 1> R1 = VectorF32<3, 1>::Cross(R2, R0);
|
||
VectorF32<3, 1> negEye = -eyePosition;
|
||
|
||
VectorF32<1, 4> dots = VectorF32<3, 1>::Dot(
|
||
R0, negEye, R1, negEye, R2, negEye, R0, negEye);
|
||
std::array<float, 4> d = dots.template Store<float>();
|
||
std::array<float, 4> r0a = R0.template Store<float>();
|
||
std::array<float, 4> r1a = R1.template Store<float>();
|
||
std::array<float, 4> r2a = R2.template Store<float>();
|
||
|
||
return MatrixRowMajor(
|
||
r0a[0], r1a[0], r2a[0], 0.0f,
|
||
r0a[1], r1a[1], r2a[1], 0.0f,
|
||
r0a[2], r1a[2], r2a[2], 0.0f,
|
||
d[0], d[1], d[2], 1.0f
|
||
);
|
||
}
|
||
|
||
static MatrixRowMajor LookAt(VectorF32<3, 1> eyePosition, VectorF32<3, 1> focusPosition, VectorF32<3, 1> upDirection) requires(CollumSize == 4 && RowSize == 4 && Repeats == 1 && std::same_as<T, float>) {
|
||
return LookTo(eyePosition, eyePosition - focusPosition, upDirection);
|
||
}
|
||
|
||
// ─── Quaternions, TRS and the affine inverse ───────────────────
|
||
// Each call means the same transform in both shapes: the 4x4 takes
|
||
// row vectors (v * M, translation in row 3) and the 4x3 column
|
||
// vectors (M * v, translation in column 3, the VkTransformMatrixKHR
|
||
// layout), so the 4x4's upper 3x3 is the 4x3's transposed. These are
|
||
// scalar code: they build matrices, they don't sit in inner loops.
|
||
|
||
// The rotation by unit quaternion q (x, y, z, w) that Vector::Rotate
|
||
// applies.
|
||
template <std::uint32_t QAlignment>
|
||
static MatrixRowMajor Rotation(Vector<float, 4, QAlignment> q) requires(CollumSize == 4 && (RowSize == 3 || RowSize == 4) && Repeats == 1 && std::same_as<T, float>) {
|
||
return TRS(Vector<float, 3, 0>(0, 0, 0), q, Vector<float, 3, 0>(1, 1, 1));
|
||
}
|
||
|
||
// Scale by s, then rotate by unit quaternion q, then translate by t:
|
||
// a glTF node's transform.
|
||
template <std::uint32_t TAlignment, std::uint32_t QAlignment, std::uint32_t SAlignment>
|
||
static MatrixRowMajor TRS(Vector<float, 3, TAlignment> t, Vector<float, 4, QAlignment> q, Vector<float, 3, SAlignment> s) requires(CollumSize == 4 && (RowSize == 3 || RowSize == 4) && Repeats == 1 && std::same_as<T, float>) {
|
||
const float xx = q.x * q.x, yy = q.y * q.y, zz = q.z * q.z;
|
||
const float xy = q.x * q.y, xz = q.x * q.z, yz = q.y * q.z;
|
||
const float wx = q.w * q.x, wy = q.w * q.y, wz = q.w * q.z;
|
||
// r[i][j]: q's rotation matrix in column-vector form.
|
||
const float r[3][3] = {
|
||
{ 1 - 2 * (yy + zz), 2 * (xy - wz), 2 * (xz + wy) },
|
||
{ 2 * (xy + wz), 1 - 2 * (xx + zz), 2 * (yz - wx) },
|
||
{ 2 * (xz - wy), 2 * (yz + wx), 1 - 2 * (xx + yy) },
|
||
};
|
||
const float sc[3] = { s.x, s.y, s.z };
|
||
if constexpr (RowSize == 3) {
|
||
// Column j is basis vector j, scaled and rotated.
|
||
return MatrixRowMajor(
|
||
r[0][0] * sc[0], r[0][1] * sc[1], r[0][2] * sc[2], t.x,
|
||
r[1][0] * sc[0], r[1][1] * sc[1], r[1][2] * sc[2], t.y,
|
||
r[2][0] * sc[0], r[2][1] * sc[1], r[2][2] * sc[2], t.z
|
||
);
|
||
} else {
|
||
// Row i is basis vector i, scaled and rotated.
|
||
return MatrixRowMajor(
|
||
r[0][0] * sc[0], r[1][0] * sc[0], r[2][0] * sc[0], 0,
|
||
r[0][1] * sc[1], r[1][1] * sc[1], r[2][1] * sc[1], 0,
|
||
r[0][2] * sc[2], r[1][2] * sc[2], r[2][2] * sc[2], 0,
|
||
t.x, t.y, t.z, 1
|
||
);
|
||
}
|
||
}
|
||
|
||
// The inverse of an affine transform: any rotation, scale or shear
|
||
// plus a translation, which is everything TRS builds. Not valid for a
|
||
// projection.
|
||
MatrixRowMajor InverseAffine() const requires(CollumSize == 4 && (RowSize == 3 || RowSize == 4) && Repeats == 1 && std::same_as<T, float>) {
|
||
const std::array<float, RowSize * 4> a = Store();
|
||
// l[i][j] and t: the linear part and the translation, column-vector form.
|
||
float l[3][3];
|
||
float t[3];
|
||
for (std::uint32_t i = 0; i < 3; ++i) {
|
||
for (std::uint32_t j = 0; j < 3; ++j) {
|
||
l[i][j] = RowSize == 3 ? a[i * 4 + j] : a[j * 4 + i];
|
||
}
|
||
t[i] = RowSize == 3 ? a[i * 4 + 3] : a[12 + i];
|
||
}
|
||
// n = l^-1 through the adjugate.
|
||
const float c00 = l[1][1] * l[2][2] - l[1][2] * l[2][1];
|
||
const float c01 = l[1][2] * l[2][0] - l[1][0] * l[2][2];
|
||
const float c02 = l[1][0] * l[2][1] - l[1][1] * l[2][0];
|
||
const float invDet = 1.0f / (l[0][0] * c00 + l[0][1] * c01 + l[0][2] * c02);
|
||
const float n[3][3] = {
|
||
{ c00 * invDet, (l[0][2] * l[2][1] - l[0][1] * l[2][2]) * invDet, (l[0][1] * l[1][2] - l[0][2] * l[1][1]) * invDet },
|
||
{ c01 * invDet, (l[0][0] * l[2][2] - l[0][2] * l[2][0]) * invDet, (l[0][2] * l[1][0] - l[0][0] * l[1][2]) * invDet },
|
||
{ c02 * invDet, (l[0][1] * l[2][0] - l[0][0] * l[2][1]) * invDet, (l[0][0] * l[1][1] - l[0][1] * l[1][0]) * invDet },
|
||
};
|
||
// The inverse translates by -n * t.
|
||
float nt[3];
|
||
for (std::uint32_t i = 0; i < 3; ++i) {
|
||
nt[i] = -(n[i][0] * t[0] + n[i][1] * t[1] + n[i][2] * t[2]);
|
||
}
|
||
if constexpr (RowSize == 3) {
|
||
return MatrixRowMajor(
|
||
n[0][0], n[0][1], n[0][2], nt[0],
|
||
n[1][0], n[1][1], n[1][2], nt[1],
|
||
n[2][0], n[2][1], n[2][2], nt[2]
|
||
);
|
||
} else {
|
||
return MatrixRowMajor(
|
||
n[0][0], n[1][0], n[2][0], 0,
|
||
n[0][1], n[1][1], n[2][1], 0,
|
||
n[0][2], n[1][2], n[2][2], 0,
|
||
nt[0], nt[1], nt[2], 1
|
||
);
|
||
}
|
||
}
|
||
|
||
MatrixRowMajor Transpose() const requires(CollumSize == 4 && RowSize == 4 && Repeats == 1 && std::same_as<T, float>) {
|
||
const std::array<float, 16> a = Store();
|
||
return MatrixRowMajor(
|
||
a[0], a[4], a[8], a[12],
|
||
a[1], a[5], a[9], a[13],
|
||
a[2], a[6], a[10], a[14],
|
||
a[3], a[7], a[11], a[15]
|
||
);
|
||
}
|
||
|
||
// Splits a non-degenerate affine transform into the translation,
|
||
// rotation (unit quaternion) and scale that TRS builds it from. Shear
|
||
// is not represented. A mirroring transform (negative determinant) has
|
||
// no unique split; it comes back as a negative X scale, which TRS
|
||
// turns into the same transform.
|
||
std::tuple<Vector<float, 3, 0>, Vector<float, 4, 0>, Vector<float, 3, 0>> Decompose() const requires(CollumSize == 4 && (RowSize == 3 || RowSize == 4) && Repeats == 1 && std::same_as<T, float>) {
|
||
const std::array<float, RowSize * 4> a = Store();
|
||
// basis[j]: where the transform takes unit vector j.
|
||
Vector<float, 3, 0> basis[3];
|
||
for (std::uint32_t j = 0; j < 3; ++j) {
|
||
basis[j] = RowSize == 3
|
||
? Vector<float, 3, 0>(a[j], a[4 + j], a[8 + j])
|
||
: Vector<float, 3, 0>(a[j * 4], a[j * 4 + 1], a[j * 4 + 2]);
|
||
}
|
||
const Vector<float, 3, 0> t = RowSize == 3
|
||
? Vector<float, 3, 0>(a[3], a[7], a[11])
|
||
: Vector<float, 3, 0>(a[12], a[13], a[14]);
|
||
Vector<float, 3, 0> s(basis[0].Length(), basis[1].Length(), basis[2].Length());
|
||
if (Vector<float, 3, 0>::Dot(Vector<float, 3, 0>::Cross(basis[0], basis[1]), basis[2]) < 0) {
|
||
s.x = -s.x;
|
||
}
|
||
const Vector<float, 4, 0> q = Vector<float, 4, 0>::QuanternionFromBasis(basis[0] / s.x, basis[1] / s.y, basis[2] / s.z);
|
||
return { t, q, s };
|
||
}
|
||
};
|
||
}
|
||
|
||
// Pretty printer using Store() so it does not depend on the legacy m[i][j]
|
||
// access pattern.
|
||
template <>
|
||
struct std::formatter<Crafter::MatrixRowMajor<float, 4, 4, 1>> : std::formatter<std::string> {
|
||
auto format(const Crafter::MatrixRowMajor<float, 4, 4, 1>& obj, format_context& ctx) const {
|
||
std::array<float, 16> v = obj.Store();
|
||
return std::formatter<std::string>::format(std::format(
|
||
"{{{}, {}, {}, {}\n{}, {}, {}, {}\n{}, {}, {}, {}\n{}, {}, {}, {}}}",
|
||
v[0], v[1], v[2], v[3],
|
||
v[4], v[5], v[6], v[7],
|
||
v[8], v[9], v[10], v[11],
|
||
v[12], v[13], v[14], v[15]
|
||
), ctx);
|
||
}
|
||
};
|
||
|
||
template <>
|
||
struct std::formatter<Crafter::MatrixRowMajor<float, 4, 3, 1>> : std::formatter<std::string> {
|
||
auto format(const Crafter::MatrixRowMajor<float, 4, 3, 1>& obj, format_context& ctx) const {
|
||
std::array<float, 12> v = obj.Store();
|
||
return std::formatter<std::string>::format(std::format(
|
||
"{{{}, {}, {}, {}\n{}, {}, {}, {}\n{}, {}, {}, {}}}",
|
||
v[0], v[1], v[2], v[3],
|
||
v[4], v[5], v[6], v[7],
|
||
v[8], v[9], v[10], v[11]
|
||
), ctx);
|
||
}
|
||
};
|