AGENTS.md
This commit is contained in:
parent
087c8b32c3
commit
b701059c8c
7 changed files with 331 additions and 62 deletions
|
|
@ -5,6 +5,10 @@
|
|||
// applies them: the static Normalize on a 4-vector, the Hamilton product and
|
||||
// its composition order, conjugate, axis-angle, shortest-arc between two
|
||||
// directions (including the opposite-directions case), nlerp and slerp.
|
||||
// Also RotatePivot, on the scalar Vector and on VectorF32 and VectorF16 for
|
||||
// Packing 1 and for the widest packing a 3-vector and a quaternion share, and
|
||||
// ToRadian from :Basic, which an importer such as this unit has to be able to
|
||||
// name.
|
||||
|
||||
#include <cmath>
|
||||
import Crafter.Math;
|
||||
|
|
@ -145,11 +149,102 @@ std::string* TestSlerp() {
|
|||
return CheckUnit(mid, "Slerp of nearly identical quaternions");
|
||||
}
|
||||
|
||||
// ---------- ToRadian ----------
|
||||
|
||||
std::string* TestToRadian() {
|
||||
// Lived in :Basic without `export`, so no importer could name it.
|
||||
if (!Near(ToRadian(180.0f), kPi)) return new std::string(std::format("ToRadian(180) = {}, expected pi", ToRadian(180.0f)));
|
||||
if (!Near(static_cast<float>(ToRadian(90.0)), kPi / 2)) return new std::string("ToRadian(90.0) is not pi/2");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// ---------- RotatePivot ----------
|
||||
|
||||
std::string* TestRotatePivotScalar() {
|
||||
// +90° about Z around the pivot (1, 0, 0) swings (2, 0, 0) to (1, 1, 0).
|
||||
if (auto err = CheckV3(V3::RotatePivot(V3(2, 0, 0), AboutZ(kPi / 2), V3(1, 0, 0)), V3(1, 1, 0), "RotatePivot about (1,0,0)")) return err;
|
||||
// The pivot is a fixed point.
|
||||
const Q q = Q::QuanternionFromAxisAngle(V3::Normalize(V3(1, 2, 3)), 0.7f);
|
||||
const V3 pivot(0.5f, -1, 2);
|
||||
return CheckV3(V3::RotatePivot(pivot, q, pivot), pivot, "RotatePivot fixed point");
|
||||
}
|
||||
|
||||
// `Packing` 3-vectors at stride 3, or quaternions at stride 4, in one register
|
||||
// of T lanes, and back. The padding lanes stay zero.
|
||||
template <template <std::uint8_t, std::uint8_t> class VectorType, typename T, std::uint8_t Packing>
|
||||
VectorType<3, Packing> Pack3(const std::array<V3, Packing>& vs) {
|
||||
alignas(64) T buf[VectorType<3, Packing>::AlignmentElement] = {};
|
||||
for (std::uint8_t s = 0; s < Packing; ++s) {
|
||||
buf[s * 3 + 0] = static_cast<T>(vs[s].x);
|
||||
buf[s * 3 + 1] = static_cast<T>(vs[s].y);
|
||||
buf[s * 3 + 2] = static_cast<T>(vs[s].z);
|
||||
}
|
||||
return VectorType<3, Packing>(buf);
|
||||
}
|
||||
|
||||
template <template <std::uint8_t, std::uint8_t> class VectorType, typename T, std::uint8_t Packing>
|
||||
VectorType<4, Packing> Pack4(const std::array<Q, Packing>& qs) {
|
||||
alignas(64) T buf[VectorType<4, Packing>::AlignmentElement] = {};
|
||||
for (std::uint8_t s = 0; s < Packing; ++s) {
|
||||
buf[s * 4 + 0] = static_cast<T>(qs[s].x);
|
||||
buf[s * 4 + 1] = static_cast<T>(qs[s].y);
|
||||
buf[s * 4 + 2] = static_cast<T>(qs[s].z);
|
||||
buf[s * 4 + 3] = static_cast<T>(qs[s].w);
|
||||
}
|
||||
return VectorType<4, Packing>(buf);
|
||||
}
|
||||
|
||||
template <template <std::uint8_t, std::uint8_t> class VectorType, typename T, std::uint8_t Packing>
|
||||
std::array<V3, Packing> Unpack3(VectorType<3, Packing> v) {
|
||||
const auto lanes = v.template Store<T>();
|
||||
std::array<V3, Packing> out;
|
||||
for (std::uint8_t s = 0; s < Packing; ++s) {
|
||||
out[s] = V3(static_cast<float>(lanes[s * 3 + 0]), static_cast<float>(lanes[s * 3 + 1]), static_cast<float>(lanes[s * 3 + 2]));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Every slot gets its own vector, quaternion and pivot, so a backend that
|
||||
// reads the quaternion lanes at the wrong stride, or returns the wrong shape,
|
||||
// disagrees with the scalar Vector somewhere.
|
||||
template <template <std::uint8_t, std::uint8_t> class VectorType, typename T, std::uint8_t Packing>
|
||||
std::string* CheckRotatePivotPacked(float epsilon, std::string_view label) {
|
||||
std::array<V3, Packing> vs, pivots, expected;
|
||||
std::array<Q, Packing> qs;
|
||||
for (std::uint8_t s = 0; s < Packing; ++s) {
|
||||
vs[s] = V3(1.0f + s, 2.0f - 0.5f * s, -1.0f + 0.25f * s);
|
||||
pivots[s] = V3(0.5f * s, 1.0f, -0.5f);
|
||||
qs[s] = Q::QuanternionFromAxisAngle(V3::Normalize(V3(1.0f, 2.0f + s, 3.0f)), 0.4f + 0.3f * s);
|
||||
expected[s] = V3::Rotate(vs[s] - pivots[s], qs[s]) + pivots[s];
|
||||
}
|
||||
const std::array<V3, Packing> got = Unpack3<VectorType, T, Packing>(VectorType<3, Packing>::RotatePivot(
|
||||
Pack3<VectorType, T, Packing>(vs), Pack4<VectorType, T, Packing>(qs), Pack3<VectorType, T, Packing>(pivots)));
|
||||
for (std::uint8_t s = 0; s < Packing; ++s) {
|
||||
if (!Near(got[s].x, expected[s].x, epsilon) || !Near(got[s].y, expected[s].y, epsilon) || !Near(got[s].z, expected[s].z, epsilon)) {
|
||||
return new std::string(std::format("{} slot {}: expected ({},{},{}), got ({},{},{})",
|
||||
label, s, expected[s].x, expected[s].y, expected[s].z, got[s].x, got[s].y, got[s].z));
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string* TestRotatePivotSimd() {
|
||||
// The x86 VectorF32 and VectorF16 RotatePivot returned a <4, 2> whatever
|
||||
// the shape and took the quaternion register as a 3-vector register.
|
||||
constexpr float halfEps = 0.05f; // half precision over a handful of operations on values up to about 5
|
||||
if (auto err = CheckRotatePivotPacked<VectorF32, float, 1>(kEps, "VectorF32<3,1>::RotatePivot")) return err;
|
||||
constexpr std::uint8_t packing32 = std::min(VectorF32<3, 1>::OptimalPacking, VectorF32<4, 1>::OptimalPacking);
|
||||
if (auto err = CheckRotatePivotPacked<VectorF32, float, packing32>(kEps, "VectorF32<3,P>::RotatePivot")) return err;
|
||||
if (auto err = CheckRotatePivotPacked<VectorF16, _Float16, 1>(halfEps, "VectorF16<3,1>::RotatePivot")) return err;
|
||||
constexpr std::uint8_t packing16 = std::min(VectorF16<3, 1>::OptimalPacking, VectorF16<4, 1>::OptimalPacking);
|
||||
return CheckRotatePivotPacked<VectorF16, _Float16, packing16>(halfEps, "VectorF16<3,P>::RotatePivot");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
using Fn = std::string* (*)();
|
||||
constexpr std::array<std::pair<const char*, Fn>, 9> tests = {{
|
||||
constexpr std::array<std::pair<const char*, Fn>, 12> tests = {{
|
||||
{ "NormalizeLen4", TestNormalizeLen4 },
|
||||
{ "MultiplyIdentity", TestMultiplyIdentity },
|
||||
{ "ConjugateUndoes", TestConjugateUndoes },
|
||||
|
|
@ -159,6 +254,9 @@ int main() {
|
|||
{ "Nlerp", TestNlerp },
|
||||
{ "Slerp", TestSlerp },
|
||||
{ "SlerpIdentity", [] { return CheckSameRotation(Q::QuanternionSlerp(Q(0, 0, 0, 1), Q(0, 0, 0, 1), 0.5f), Q(0, 0, 0, 1), "Slerp of identities"); } },
|
||||
{ "ToRadian", TestToRadian },
|
||||
{ "RotatePivotScalar", TestRotatePivotScalar },
|
||||
{ "RotatePivotSimd", TestRotatePivotSimd },
|
||||
}};
|
||||
|
||||
for (auto const& [name, fn] : tests) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue