packed intersection and matrix
This commit is contained in:
parent
027947cae6
commit
f0becd1582
7 changed files with 948 additions and 557 deletions
|
|
@ -40,134 +40,390 @@ VectorF32<4, 1> Vec4(float x, float y, float z, float w) {
|
|||
return VectorF32<4, 1>(buf);
|
||||
}
|
||||
|
||||
VectorF32<1, 4> Vec1x4(float a, float b, float c, float d) {
|
||||
alignas(16) float buf[4] = { a, b, c, d };
|
||||
return VectorF32<1, 4>(buf);
|
||||
// Pack Total = Packing * N vec3 records into N packed VectorF32<3, Packing>s.
|
||||
// `data[i]` is the i-th sub-primitive's three components in [x, y, z] order;
|
||||
// the helper places `data[batch*Packing + sub]` into the `sub`-th slot of
|
||||
// `result[batch]`. Records beyond `data.size()` are left as zeros.
|
||||
template <std::uint8_t Packing>
|
||||
std::array<VectorF32<3, Packing>, VectorF32<3, Packing>::BatchSize>
|
||||
PackVec3(std::span<const std::array<float, 3>> data) {
|
||||
constexpr std::uint8_t N = VectorF32<3, Packing>::BatchSize;
|
||||
std::array<VectorF32<3, Packing>, N> result;
|
||||
for (std::uint8_t b = 0; b < N; ++b) {
|
||||
alignas(64) float buf[VectorF32<3, Packing>::AlignmentElement] = {};
|
||||
for (std::uint8_t s = 0; s < Packing; ++s) {
|
||||
std::size_t idx = static_cast<std::size_t>(b) * Packing + s;
|
||||
if (idx < data.size()) {
|
||||
buf[s * 3 + 0] = data[idx][0];
|
||||
buf[s * 3 + 1] = data[idx][1];
|
||||
buf[s * 3 + 2] = data[idx][2];
|
||||
}
|
||||
}
|
||||
result[b] = VectorF32<3, Packing>(buf);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Möller-Trumbore in this codebase rejects det <= eps, so triangles must be
|
||||
// wound so their geometric normal opposes the ray direction. For rays going +Z
|
||||
// that means clockwise from a +Z viewer.
|
||||
std::string* TestRayTriangle() {
|
||||
// Same idea for vec4 records (quaternions).
|
||||
template <std::uint8_t Packing>
|
||||
std::array<VectorF32<4, Packing>, VectorF32<3, Packing>::BatchSize>
|
||||
PackVec4MatchingVec3Batch(std::span<const std::array<float, 4>> data) {
|
||||
constexpr std::uint8_t N = VectorF32<3, Packing>::BatchSize;
|
||||
std::array<VectorF32<4, Packing>, N> result;
|
||||
for (std::uint8_t b = 0; b < N; ++b) {
|
||||
alignas(64) float buf[VectorF32<4, Packing>::AlignmentElement] = {};
|
||||
for (std::uint8_t s = 0; s < Packing; ++s) {
|
||||
std::size_t idx = static_cast<std::size_t>(b) * Packing + s;
|
||||
if (idx < data.size()) {
|
||||
buf[s * 4 + 0] = data[idx][0];
|
||||
buf[s * 4 + 1] = data[idx][1];
|
||||
buf[s * 4 + 2] = data[idx][2];
|
||||
buf[s * 4 + 3] = data[idx][3];
|
||||
}
|
||||
}
|
||||
result[b] = VectorF32<4, Packing>(buf);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Pack `Total` scalars into a VectorF32<1, Total>.
|
||||
template <std::uint8_t Total>
|
||||
VectorF32<1, Total> PackScalars(std::span<const float> data) {
|
||||
alignas(64) float buf[VectorF32<1, Total>::AlignmentElement] = {};
|
||||
for (std::size_t i = 0; i < data.size() && i < Total; ++i) buf[i] = data[i];
|
||||
return VectorF32<1, Total>(buf);
|
||||
}
|
||||
|
||||
template <std::uint8_t Packing>
|
||||
std::string* TestRayTriangleN() {
|
||||
constexpr std::uint8_t N = VectorF32<3, Packing>::BatchSize;
|
||||
constexpr std::uint8_t Total = Packing * N;
|
||||
|
||||
VectorF32<3, 1> rayOrigin = Vec3(0, 0, -5);
|
||||
VectorF32<3, 1> rayDir = Vec3(0, 0, 1);
|
||||
|
||||
// A: hits at z=0, t=5 (front-facing).
|
||||
VectorF32<3, 1> a0 = Vec3(-1, -1, 0), a1 = Vec3(0, 1, 0), a2 = Vec3(1, -1, 0);
|
||||
// B: hits at z=10, t=15.
|
||||
VectorF32<3, 1> b0 = Vec3(-1, -1, 10), b1 = Vec3(0, 1, 10), b2 = Vec3(1, -1, 10);
|
||||
// C: front-facing triangle far off to the side - u or v out of [0,1].
|
||||
VectorF32<3, 1> c0 = Vec3(99, -1, 0), c1 = Vec3(100, 1, 0), c2 = Vec3(101, -1, 0);
|
||||
// D: triangle parallel to the ray (all vertices share y=2; ray lives in y=0).
|
||||
VectorF32<3, 1> d0 = Vec3(-1, 2, -1), d1 = Vec3(1, 2, 1), d2 = Vec3(0, 2, 2);
|
||||
// Cycle of four triangle patterns, repeated to fill Total slots:
|
||||
// 0: hits at z=0 (t=5)
|
||||
// 1: hits at z=10 (t=15)
|
||||
// 2: front-facing but off to the side - u/v rejected (miss)
|
||||
// 3: parallel to the ray (miss)
|
||||
constexpr std::array<std::array<float, 3>, 4> v0_pat = {{
|
||||
{-1, -1, 0}, {-1, -1, 10}, { 99, -1, 0}, {-1, 2, -1}
|
||||
}};
|
||||
constexpr std::array<std::array<float, 3>, 4> v1_pat = {{
|
||||
{ 0, 1, 0}, { 0, 1, 10}, {100, 1, 0}, { 1, 2, 1}
|
||||
}};
|
||||
constexpr std::array<std::array<float, 3>, 4> v2_pat = {{
|
||||
{ 1, -1, 0}, { 1, -1, 10}, {101, -1, 0}, { 0, 2, 2}
|
||||
}};
|
||||
constexpr std::array<float, 4> expected_pat = { 5.0f, 15.0f, kMaxF, kMaxF };
|
||||
|
||||
VectorF32<1, 4> t = IntersectionTestRayTriangle(rayOrigin, rayDir,
|
||||
a0, a1, a2,
|
||||
b0, b1, b2,
|
||||
c0, c1, c2,
|
||||
d0, d1, d2);
|
||||
std::array<float, 4> s = t.template Store<float>();
|
||||
std::array<std::array<float, 3>, Total> v0Data, v1Data, v2Data;
|
||||
for (std::uint8_t i = 0; i < Total; ++i) {
|
||||
v0Data[i] = v0_pat[i % 4];
|
||||
v1Data[i] = v1_pat[i % 4];
|
||||
v2Data[i] = v2_pat[i % 4];
|
||||
}
|
||||
auto v0 = PackVec3<Packing>(v0Data);
|
||||
auto v1 = PackVec3<Packing>(v1Data);
|
||||
auto v2 = PackVec3<Packing>(v2Data);
|
||||
|
||||
if (!FloatEquals(s[0], 5.0f))
|
||||
return new std::string(std::format("RayTriangle A: expected 5, got {}", s[0]));
|
||||
if (!FloatEquals(s[1], 15.0f))
|
||||
return new std::string(std::format("RayTriangle B: expected 15, got {}", s[1]));
|
||||
if (s[2] != kMaxF)
|
||||
return new std::string(std::format("RayTriangle C: expected max (miss), got {}", s[2]));
|
||||
if (s[3] != kMaxF)
|
||||
return new std::string(std::format("RayTriangle D: expected max (parallel miss), got {}", s[3]));
|
||||
return nullptr;
|
||||
}
|
||||
auto t = IntersectionTestRayTriangle<Packing>(rayOrigin, rayDir, v0, v1, v2);
|
||||
auto stored = t.template Store<float>();
|
||||
|
||||
std::string* TestRayTriangleBackFacing() {
|
||||
// Same A vertices but CCW from +Z viewer -> back-facing for +Z ray -> miss.
|
||||
VectorF32<3, 1> rayOrigin = Vec3(0, 0, -5);
|
||||
VectorF32<3, 1> rayDir = Vec3(0, 0, 1);
|
||||
VectorF32<3, 1> v0 = Vec3(-1, -1, 0), v1 = Vec3(1, -1, 0), v2 = Vec3(0, 1, 0);
|
||||
|
||||
VectorF32<1, 4> t = IntersectionTestRayTriangle(rayOrigin, rayDir,
|
||||
v0, v1, v2,
|
||||
v0, v1, v2,
|
||||
v0, v1, v2,
|
||||
v0, v1, v2);
|
||||
std::array<float, 4> s = t.template Store<float>();
|
||||
for (std::uint8_t i = 0; i < 4; ++i) {
|
||||
if (s[i] != kMaxF)
|
||||
return new std::string(std::format("RayTriangle back-facing lane {}: expected max, got {}", i, s[i]));
|
||||
for (std::uint8_t i = 0; i < Total; ++i) {
|
||||
float expected = expected_pat[i % 4];
|
||||
float got = stored[i];
|
||||
if (expected == kMaxF) {
|
||||
if (got != kMaxF)
|
||||
return new std::string(std::format(
|
||||
"RayTriangle<{}> tri {}: expected miss, got {}", Packing, i, got));
|
||||
} else if (!FloatEquals(got, expected)) {
|
||||
return new std::string(std::format(
|
||||
"RayTriangle<{}> tri {}: expected {}, got {}", Packing, i, expected, got));
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string* TestRaySphere() {
|
||||
VectorF32<3, 1> rayOrigin = Vec3(0, 0, -10);
|
||||
template <std::uint8_t Packing>
|
||||
std::string* TestRayTriangleBackFacingN() {
|
||||
constexpr std::uint8_t N = VectorF32<3, Packing>::BatchSize;
|
||||
constexpr std::uint8_t Total = Packing * N;
|
||||
|
||||
// Same vertices as the front-facing case but wound CCW from +Z (back-facing
|
||||
// for a +Z ray) - all sub-primitives should miss.
|
||||
std::array<std::array<float, 3>, Total> v0Data, v1Data, v2Data;
|
||||
for (std::uint8_t i = 0; i < Total; ++i) {
|
||||
v0Data[i] = {-1, -1, 0};
|
||||
v1Data[i] = { 1, -1, 0};
|
||||
v2Data[i] = { 0, 1, 0};
|
||||
}
|
||||
auto v0 = PackVec3<Packing>(v0Data);
|
||||
auto v1 = PackVec3<Packing>(v1Data);
|
||||
auto v2 = PackVec3<Packing>(v2Data);
|
||||
|
||||
VectorF32<3, 1> rayOrigin = Vec3(0, 0, -5);
|
||||
VectorF32<3, 1> rayDir = Vec3(0, 0, 1);
|
||||
|
||||
// A: sphere at origin radius 2 - first hit at z=-2, t=8.
|
||||
VectorF32<3, 1> posA = Vec3(0, 0, 0);
|
||||
// B: sphere at (0,0,20) radius 1 - first hit at z=19, t=29.
|
||||
VectorF32<3, 1> posB = Vec3(0, 0, 20);
|
||||
// C: sphere off to the side, ray misses.
|
||||
VectorF32<3, 1> posC = Vec3(10, 10, 0);
|
||||
// D: sphere behind the ray origin.
|
||||
VectorF32<3, 1> posD = Vec3(0, 0, -50);
|
||||
|
||||
VectorF32<1, 4> radii = Vec1x4(2.0f, 1.0f, 0.5f, 1.0f);
|
||||
VectorF32<1, 4> t = IntersectionTestRaySphere(rayOrigin, rayDir,
|
||||
posA, posB, posC, posD, radii);
|
||||
std::array<float, 4> s = t.template Store<float>();
|
||||
|
||||
if (!FloatEquals(s[0], 8.0f))
|
||||
return new std::string(std::format("RaySphere A: expected 8, got {}", s[0]));
|
||||
if (!FloatEquals(s[1], 29.0f))
|
||||
return new std::string(std::format("RaySphere B: expected 29, got {}", s[1]));
|
||||
if (s[2] != kMaxF)
|
||||
return new std::string(std::format("RaySphere C: expected max (miss), got {}", s[2]));
|
||||
if (s[3] != kMaxF)
|
||||
return new std::string(std::format("RaySphere D: expected max (behind), got {}", s[3]));
|
||||
auto t = IntersectionTestRayTriangle<Packing>(rayOrigin, rayDir, v0, v1, v2);
|
||||
auto stored = t.template Store<float>();
|
||||
for (std::uint8_t i = 0; i < Total; ++i) {
|
||||
if (stored[i] != kMaxF)
|
||||
return new std::string(std::format(
|
||||
"RayTriangle back-facing<{}> tri {}: expected max, got {}",
|
||||
Packing, i, stored[i]));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string* TestRayOrientedBox() {
|
||||
template <std::uint8_t Packing>
|
||||
std::string* TestRaySphereN() {
|
||||
constexpr std::uint8_t N = VectorF32<3, Packing>::BatchSize;
|
||||
constexpr std::uint8_t Total = Packing * N;
|
||||
|
||||
VectorF32<3, 1> rayOrigin = Vec3(0, 0, -10);
|
||||
VectorF32<3, 1> rayDir = Vec3(0, 0, 1);
|
||||
|
||||
// Cycle of four sphere patterns:
|
||||
// 0: at origin, r=2, first hit z=-2, t=8
|
||||
// 1: at (0,0,20), r=1, first hit z=19, t=29
|
||||
// 2: off-axis at (10,10,0), r=0.5, miss
|
||||
// 3: behind ray at (0,0,-50), r=1, miss
|
||||
constexpr std::array<std::array<float, 3>, 4> pos_pat = {{
|
||||
{ 0, 0, 0}, { 0, 0, 20}, {10, 10, 0}, { 0, 0, -50}
|
||||
}};
|
||||
constexpr std::array<float, 4> radii_pat = { 2.0f, 1.0f, 0.5f, 1.0f };
|
||||
constexpr std::array<float, 4> expected_pat = { 8.0f, 29.0f, kMaxF, kMaxF };
|
||||
|
||||
std::array<std::array<float, 3>, Total> posData;
|
||||
std::array<float, Total> radiiData;
|
||||
for (std::uint8_t i = 0; i < Total; ++i) {
|
||||
posData[i] = pos_pat[i % 4];
|
||||
radiiData[i] = radii_pat[i % 4];
|
||||
}
|
||||
auto pos = PackVec3<Packing>(posData);
|
||||
auto radii = PackScalars<Total>(radiiData);
|
||||
|
||||
auto t = IntersectionTestRaySphere<Packing>(rayOrigin, rayDir, pos, radii);
|
||||
auto stored = t.template Store<float>();
|
||||
|
||||
for (std::uint8_t i = 0; i < Total; ++i) {
|
||||
float expected = expected_pat[i % 4];
|
||||
float got = stored[i];
|
||||
if (expected == kMaxF) {
|
||||
if (got != kMaxF)
|
||||
return new std::string(std::format(
|
||||
"RaySphere<{}> sph {}: expected miss, got {}", Packing, i, got));
|
||||
} else if (!FloatEquals(got, expected)) {
|
||||
return new std::string(std::format(
|
||||
"RaySphere<{}> sph {}: expected {}, got {}", Packing, i, expected, got));
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <std::uint8_t Packing>
|
||||
std::string* TestRayOrientedBoxN() {
|
||||
constexpr std::uint8_t N = VectorF32<3, Packing>::BatchSize;
|
||||
constexpr std::uint8_t Total = Packing * N;
|
||||
|
||||
VectorF32<3, 1> rayOrigin = Vec3(0, 0, -5);
|
||||
VectorF32<3, 1> rayDir = Vec3(0, 0, 1);
|
||||
// Identity quaternion (axis-aligned).
|
||||
VectorF32<4, 1> idQ = Vec4(0, 0, 0, 1);
|
||||
|
||||
// Note: RayOrientedBox treats `size` as the *full* extent (it computes
|
||||
// halfExtents = size * 0.5 internally). So size=2 means the box spans
|
||||
// [-1, 1] in each axis. (SphereOrientedBox uses the opposite convention.)
|
||||
//
|
||||
// A: box at origin size 2 (half 1) -> ray enters at z=-1, t=4.
|
||||
VectorF32<3, 1> posA = Vec3(0, 0, 0), sizeA = Vec3(2, 2, 2);
|
||||
// B: box at (0,0,10) size 2 (half 1) -> ray enters at z=9, t=14.
|
||||
VectorF32<3, 1> posB = Vec3(0, 0, 10), sizeB = Vec3(2, 2, 2);
|
||||
// C: box off to the side - miss.
|
||||
VectorF32<3, 1> posC = Vec3(50, 0, 0), sizeC = Vec3(2, 2, 2);
|
||||
// D: box behind ray - miss.
|
||||
VectorF32<3, 1> posD = Vec3(0, 0, -50), sizeD = Vec3(2, 2, 2);
|
||||
// Cycle of four AABB-as-OBB patterns (identity rotation):
|
||||
// 0: at origin, full size 2, enters z=-1 -> t=4
|
||||
// 1: at (0,0,10), full size 2, enters z=9 -> t=14
|
||||
// 2: off-axis at (50,0,0) -> miss
|
||||
// 3: behind ray at (0,0,-50) -> miss
|
||||
constexpr std::array<std::array<float, 3>, 4> pos_pat = {{
|
||||
{ 0, 0, 0}, { 0, 0, 10}, {50, 0, 0}, { 0, 0, -50}
|
||||
}};
|
||||
constexpr std::array<float, 3> size_one = { 2, 2, 2 };
|
||||
constexpr std::array<float, 4> idQ = { 0, 0, 0, 1 };
|
||||
constexpr std::array<float, 4> expected_pat = { 4.0f, 14.0f, kMaxF, kMaxF };
|
||||
|
||||
VectorF32<1, 4> t = IntersectionTestRayOrientedBox(rayOrigin, rayDir,
|
||||
posA, sizeA, idQ,
|
||||
posB, sizeB, idQ,
|
||||
posC, sizeC, idQ,
|
||||
posD, sizeD, idQ);
|
||||
std::array<float, 4> s = t.template Store<float>();
|
||||
std::array<std::array<float, 3>, Total> posData;
|
||||
std::array<std::array<float, 3>, Total> sizeData;
|
||||
std::array<std::array<float, 4>, Total> rotData;
|
||||
for (std::uint8_t i = 0; i < Total; ++i) {
|
||||
posData[i] = pos_pat[i % 4];
|
||||
sizeData[i] = size_one;
|
||||
rotData[i] = idQ;
|
||||
}
|
||||
auto pos = PackVec3<Packing>(posData);
|
||||
auto size = PackVec3<Packing>(sizeData);
|
||||
auto rot = PackVec4MatchingVec3Batch<Packing>(rotData);
|
||||
|
||||
if (!FloatEquals(s[0], 4.0f))
|
||||
return new std::string(std::format("RayOrientedBox A: expected 4, got {}", s[0]));
|
||||
if (!FloatEquals(s[1], 14.0f))
|
||||
return new std::string(std::format("RayOrientedBox B: expected 14, got {}", s[1]));
|
||||
if (s[2] != kMaxF)
|
||||
return new std::string(std::format("RayOrientedBox C: expected max (miss), got {}", s[2]));
|
||||
if (s[3] != kMaxF)
|
||||
return new std::string(std::format("RayOrientedBox D: expected max (behind), got {}", s[3]));
|
||||
auto t = IntersectionTestRayOrientedBox<Packing>(rayOrigin, rayDir, pos, size, rot);
|
||||
auto stored = t.template Store<float>();
|
||||
|
||||
for (std::uint8_t i = 0; i < Total; ++i) {
|
||||
float expected = expected_pat[i % 4];
|
||||
float got = stored[i];
|
||||
if (expected == kMaxF) {
|
||||
if (got != kMaxF)
|
||||
return new std::string(std::format(
|
||||
"RayOrientedBox<{}> box {}: expected miss, got {}", Packing, i, got));
|
||||
} else if (!FloatEquals(got, expected)) {
|
||||
return new std::string(std::format(
|
||||
"RayOrientedBox<{}> box {}: expected {}, got {}", Packing, i, expected, got));
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Helper: pack a homogeneous array of OBB descriptors into a PackedOBBs<Packing>.
|
||||
template <std::uint8_t Packing>
|
||||
PackedOBBs<Packing> PackOBBs(
|
||||
std::span<const std::array<float, 3>> halfSizes,
|
||||
std::span<const std::array<float, 3>> xAxes,
|
||||
std::span<const std::array<float, 3>> yAxes,
|
||||
std::span<const std::array<float, 3>> zAxes,
|
||||
std::span<const std::array<float, 3>> origins
|
||||
) {
|
||||
PackedOBBs<Packing> out;
|
||||
out.halfSize = PackVec3<Packing>(halfSizes);
|
||||
out.xAxis = PackVec3<Packing>(xAxes);
|
||||
out.yAxis = PackVec3<Packing>(yAxes);
|
||||
out.zAxis = PackVec3<Packing>(zAxes);
|
||||
out.origin = PackVec3<Packing>(origins);
|
||||
return out;
|
||||
}
|
||||
|
||||
// SphereOrientedBox takes a PackedOBBs (half-extents, three rotation axes,
|
||||
// origin per sub-box). For axis-aligned boxes the axes are world x/y/z.
|
||||
template <std::uint8_t Packing>
|
||||
std::string* TestSphereOrientedBoxN() {
|
||||
constexpr std::uint8_t N = VectorF32<3, Packing>::BatchSize;
|
||||
constexpr std::uint8_t Total = Packing * N;
|
||||
|
||||
VectorF32<3, 1> sphereCenter = Vec3(0, 0, 0);
|
||||
|
||||
// Cycle of four box patterns (half-extent semantics, world-axis aligned):
|
||||
// 0: at origin half=2, r=1 -> sphere inside -> hit
|
||||
// 1: at (5,0,0) half=1, r=0.5 -> miss
|
||||
// 2: at (3,0,0) half=1, r=0.5 -> miss
|
||||
// 3: at origin half=0.5, r=1 -> sphere encloses box center -> hit
|
||||
constexpr std::array<std::array<float, 3>, 4> size_pat = {{
|
||||
{ 2, 2, 2}, { 1, 1, 1}, { 1, 1, 1}, {0.5f, 0.5f, 0.5f}
|
||||
}};
|
||||
constexpr std::array<std::array<float, 3>, 4> origin_pat = {{
|
||||
{ 0, 0, 0}, { 5, 0, 0}, { 3, 0, 0}, { 0, 0, 0}
|
||||
}};
|
||||
constexpr std::array<float, 4> radii_pat = { 1.0f, 0.5f, 0.5f, 1.0f };
|
||||
constexpr std::array<float, 4> expected_pat = { 0.0f, kMaxF, kMaxF, 0.0f };
|
||||
|
||||
constexpr std::array<float, 3> ax_x = { 1, 0, 0 };
|
||||
constexpr std::array<float, 3> ax_y = { 0, 1, 0 };
|
||||
constexpr std::array<float, 3> ax_z = { 0, 0, 1 };
|
||||
|
||||
std::array<std::array<float, 3>, Total> sizeData, originData;
|
||||
std::array<std::array<float, 3>, Total> xAxesData, yAxesData, zAxesData;
|
||||
std::array<float, Total> radiiData;
|
||||
for (std::uint8_t i = 0; i < Total; ++i) {
|
||||
sizeData[i] = size_pat[i % 4];
|
||||
originData[i] = origin_pat[i % 4];
|
||||
xAxesData[i] = ax_x;
|
||||
yAxesData[i] = ax_y;
|
||||
zAxesData[i] = ax_z;
|
||||
radiiData[i] = radii_pat[i % 4];
|
||||
}
|
||||
auto boxes = PackOBBs<Packing>(sizeData, xAxesData, yAxesData, zAxesData, originData);
|
||||
auto radii = PackScalars<Total>(radiiData);
|
||||
|
||||
auto t = IntersectionTestSphereOrientedBox<Packing>(sphereCenter, radii, boxes);
|
||||
auto stored = t.template Store<float>();
|
||||
|
||||
for (std::uint8_t i = 0; i < Total; ++i) {
|
||||
float expected = expected_pat[i % 4];
|
||||
float got = stored[i];
|
||||
if (expected == kMaxF) {
|
||||
if (got != kMaxF)
|
||||
return new std::string(std::format(
|
||||
"SphereOrientedBox<{}> box {}: expected miss, got {}", Packing, i, got));
|
||||
} else if (!FloatEquals(got, expected)) {
|
||||
return new std::string(std::format(
|
||||
"SphereOrientedBox<{}> box {}: expected {}, got {}", Packing, i, expected, got));
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// OBB-vs-OBB test against the new templated SAT routine. Cycles through:
|
||||
// 0: identical unit boxes at (0,0,0) and (1,0,0) -> overlap on x
|
||||
// 1: identical unit boxes at (0,0,0) and (10,0,0) -> far apart, miss
|
||||
// 2: rotated-45° box at origin vs identity at (1,0,0). Both have half=1.
|
||||
// The rotated box's projection along world-x is half=sqrt(2)≈1.414, so
|
||||
// the boxes still overlap on the world-x axis.
|
||||
// 3: identical unit boxes at (0,0,0) and (3,0,0) -> miss
|
||||
template <std::uint8_t Packing>
|
||||
std::string* TestOBBOBBN() {
|
||||
constexpr std::uint8_t N = VectorF32<3, Packing>::BatchSize;
|
||||
constexpr std::uint8_t Total = Packing * N;
|
||||
|
||||
constexpr float kRot45 = 0.70710678f; // cos(45°) = sin(45°)
|
||||
constexpr std::array<std::array<float, 3>, 4> halfA_pat = {{
|
||||
{ 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 }
|
||||
}};
|
||||
constexpr std::array<std::array<float, 3>, 4> halfB_pat = halfA_pat;
|
||||
constexpr std::array<std::array<float, 3>, 4> originA_pat = {{
|
||||
{ 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }
|
||||
}};
|
||||
constexpr std::array<std::array<float, 3>, 4> originB_pat = {{
|
||||
{ 1, 0, 0 }, { 10, 0, 0 }, { 1, 0, 0 }, { 3, 0, 0 }
|
||||
}};
|
||||
// Box A axes: identity for patterns 0/1/3, rotated 45° around z for pattern 2.
|
||||
constexpr std::array<std::array<float, 3>, 4> xAxisA_pat = {{
|
||||
{ 1, 0, 0 }, { 1, 0, 0 }, { kRot45, kRot45, 0 }, { 1, 0, 0 }
|
||||
}};
|
||||
constexpr std::array<std::array<float, 3>, 4> yAxisA_pat = {{
|
||||
{ 0, 1, 0 }, { 0, 1, 0 }, { -kRot45, kRot45, 0 }, { 0, 1, 0 }
|
||||
}};
|
||||
constexpr std::array<std::array<float, 3>, 4> zAxisA_pat = {{
|
||||
{ 0, 0, 1 }, { 0, 0, 1 }, { 0, 0, 1 }, { 0, 0, 1 }
|
||||
}};
|
||||
constexpr std::array<std::array<float, 3>, 4> xAxisB_pat = {{
|
||||
{ 1, 0, 0 }, { 1, 0, 0 }, { 1, 0, 0 }, { 1, 0, 0 }
|
||||
}};
|
||||
constexpr std::array<std::array<float, 3>, 4> yAxisB_pat = {{
|
||||
{ 0, 1, 0 }, { 0, 1, 0 }, { 0, 1, 0 }, { 0, 1, 0 }
|
||||
}};
|
||||
constexpr std::array<std::array<float, 3>, 4> zAxisB_pat = zAxisA_pat;
|
||||
constexpr std::array<float, 4> expected_pat = { 0.0f, kMaxF, 0.0f, kMaxF };
|
||||
|
||||
std::array<std::array<float, 3>, Total>
|
||||
halfA, halfB, originA, originB,
|
||||
xA, yA, zA, xB, yB, zB;
|
||||
for (std::uint8_t i = 0; i < Total; ++i) {
|
||||
halfA[i] = halfA_pat[i % 4];
|
||||
halfB[i] = halfB_pat[i % 4];
|
||||
originA[i] = originA_pat[i % 4];
|
||||
originB[i] = originB_pat[i % 4];
|
||||
xA[i] = xAxisA_pat[i % 4]; yA[i] = yAxisA_pat[i % 4]; zA[i] = zAxisA_pat[i % 4];
|
||||
xB[i] = xAxisB_pat[i % 4]; yB[i] = yAxisB_pat[i % 4]; zB[i] = zAxisB_pat[i % 4];
|
||||
}
|
||||
|
||||
auto a = PackOBBs<Packing>(halfA, xA, yA, zA, originA);
|
||||
auto b = PackOBBs<Packing>(halfB, xB, yB, zB, originB);
|
||||
auto r = IntersectionTestOrientedBoxOrientedBox<Packing>(a, b);
|
||||
auto stored = r.template Store<float>();
|
||||
|
||||
for (std::uint8_t i = 0; i < Total; ++i) {
|
||||
float expected = expected_pat[i % 4];
|
||||
float got = stored[i];
|
||||
if (expected == kMaxF) {
|
||||
if (got != kMaxF)
|
||||
return new std::string(std::format(
|
||||
"OBBOBB<{}> pair {}: expected miss, got {}", Packing, i, got));
|
||||
} else if (got != expected) {
|
||||
return new std::string(std::format(
|
||||
"OBBOBB<{}> pair {}: expected {}, got {}", Packing, i, expected, got));
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MatrixRowMajor<float, 4, 3, 1> MakeBoxMatrix(float tx, float ty, float tz) {
|
||||
// Box matrix the OBB intersection code expects: rows[i][0..2] is the i-th
|
||||
// axis (the existing semantics treat matrix rows as the OBB axes), and
|
||||
// rows[i][3] is the translation component along that axis.
|
||||
return MatrixRowMajor<float, 4, 3, 1>(
|
||||
1, 0, 0, tx,
|
||||
0, 1, 0, ty,
|
||||
|
|
@ -175,42 +431,6 @@ MatrixRowMajor<float, 4, 3, 1> MakeBoxMatrix(float tx, float ty, float tz) {
|
|||
);
|
||||
}
|
||||
|
||||
std::string* TestSphereOrientedBox() {
|
||||
// `size` is half-extents (the intersection code clamps to ±size).
|
||||
VectorF32<3, 1> sphereCenter = Vec3(0, 0, 0);
|
||||
VectorF32<1, 4> radii = Vec1x4(1.0f, 0.5f, 0.5f, 1.0f);
|
||||
|
||||
// A: box at origin half-extent 2 -> sphere center inside -> hit.
|
||||
VectorF32<3, 1> sizeA = Vec3(2, 2, 2);
|
||||
auto boxA = MakeBoxMatrix(0, 0, 0);
|
||||
// B: box at (5,0,0) half-extent 1 -> box spans x in [4,6], sphere in [-0.5,0.5] -> miss.
|
||||
VectorF32<3, 1> sizeB = Vec3(1, 1, 1);
|
||||
auto boxB = MakeBoxMatrix(5, 0, 0);
|
||||
// C: box at (3,0,0) half-extent 1 -> box spans [2,4], sphere [-0.5,0.5] -> miss.
|
||||
VectorF32<3, 1> sizeC = Vec3(1, 1, 1);
|
||||
auto boxC = MakeBoxMatrix(3, 0, 0);
|
||||
// D: box at origin half-extent 0.5 -> sphere center inside the box -> hit.
|
||||
VectorF32<3, 1> sizeD = Vec3(0.5f, 0.5f, 0.5f);
|
||||
auto boxD = MakeBoxMatrix(0, 0, 0);
|
||||
|
||||
VectorF32<1, 4> r = IntersectionTestSphereOrientedBox(sphereCenter, radii,
|
||||
sizeA, boxA,
|
||||
sizeB, boxB,
|
||||
sizeC, boxC,
|
||||
sizeD, boxD);
|
||||
std::array<float, 4> s = r.template Store<float>();
|
||||
|
||||
if (s[0] != 0.0f)
|
||||
return new std::string(std::format("SphereOrientedBox A: expected hit (0), got {}", s[0]));
|
||||
if (s[1] != kMaxF)
|
||||
return new std::string(std::format("SphereOrientedBox B: expected max (miss), got {}", s[1]));
|
||||
if (s[2] != kMaxF)
|
||||
return new std::string(std::format("SphereOrientedBox C: expected max (miss), got {}", s[2]));
|
||||
if (s[3] != 0.0f)
|
||||
return new std::string(std::format("SphereOrientedBox D: expected hit (0), got {}", s[3]));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string* TestGetOBBCorners() {
|
||||
// Identity matrix - the 8 corners are exactly ±size on each axis.
|
||||
VectorF32<3, 1> size = Vec3(2, 3, 4);
|
||||
|
|
@ -231,7 +451,6 @@ std::string* TestGetOBBCorners() {
|
|||
}
|
||||
}
|
||||
|
||||
// Translated matrix - corners shift by the translation column.
|
||||
auto m2 = MakeBoxMatrix(10, 20, 30);
|
||||
std::array<VectorF32<3, 1>, 8> corners2 = GetOBBCorners(size, m2);
|
||||
for (std::uint8_t i = 0; i < 8; ++i) {
|
||||
|
|
@ -251,31 +470,43 @@ std::string* TestGetOBBCorners() {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
std::string* TestOBBOBBOverlapping() {
|
||||
VectorF32<3, 1> size = Vec3(1, 1, 1);
|
||||
auto boxA = MakeBoxMatrix(0, 0, 0);
|
||||
auto boxB = MakeBoxMatrix(1, 0, 0); // overlap on x in [-1, 1] (B) and [-1, 1] (A) -> overlap
|
||||
if (!IntersectionTestOrientedBoxOrientedBox(size, boxA, size, boxB))
|
||||
return new std::string("OBB-OBB overlapping: expected true");
|
||||
|
||||
auto boxFar = MakeBoxMatrix(10, 0, 0);
|
||||
if (IntersectionTestOrientedBoxOrientedBox(size, boxA, size, boxFar))
|
||||
return new std::string("OBB-OBB far apart: expected false");
|
||||
return nullptr;
|
||||
// Top-level wrappers: exercise each refactored function at Packing=1 (always
|
||||
// supported) and at its default Packing (OptimalPacking for the build target).
|
||||
std::string* TestRayTriangle() { return TestRayTriangleN<1>(); }
|
||||
std::string* TestRayTriangleOpt() { return TestRayTriangleN<VectorF32<3, 1>::OptimalPacking>(); }
|
||||
std::string* TestRayTriangleBackFacing(){ return TestRayTriangleBackFacingN<1>(); }
|
||||
std::string* TestRayTriangleBackFacingOpt() { return TestRayTriangleBackFacingN<VectorF32<3, 1>::OptimalPacking>(); }
|
||||
std::string* TestRaySphere() { return TestRaySphereN<1>(); }
|
||||
std::string* TestRaySphereOpt() { return TestRaySphereN<VectorF32<3, 1>::OptimalPacking>(); }
|
||||
std::string* TestRayOrientedBox() { return TestRayOrientedBoxN<1>(); }
|
||||
std::string* TestRayOrientedBoxOpt() {
|
||||
constexpr std::uint8_t P = std::min(
|
||||
VectorF32<3, 1>::OptimalPacking, VectorF32<4, 1>::OptimalPacking);
|
||||
return TestRayOrientedBoxN<P>();
|
||||
}
|
||||
std::string* TestSphereOrientedBox() { return TestSphereOrientedBoxN<1>(); }
|
||||
std::string* TestSphereOrientedBoxOpt() { return TestSphereOrientedBoxN<VectorF32<3, 1>::OptimalPacking>(); }
|
||||
std::string* TestOBBOBB() { return TestOBBOBBN<1>(); }
|
||||
std::string* TestOBBOBBOpt() { return TestOBBOBBN<VectorF32<3, 1>::OptimalPacking>(); }
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
using Fn = std::string* (*)();
|
||||
constexpr std::array<std::pair<const char*, Fn>, 7> tests = {{
|
||||
{ "RayTriangle", TestRayTriangle },
|
||||
{ "RayTriangleBackFacing", TestRayTriangleBackFacing },
|
||||
{ "RaySphere", TestRaySphere },
|
||||
{ "RayOrientedBox", TestRayOrientedBox },
|
||||
{ "SphereOrientedBox", TestSphereOrientedBox },
|
||||
{ "GetOBBCorners", TestGetOBBCorners },
|
||||
{ "OBBOBB", TestOBBOBBOverlapping },
|
||||
constexpr std::array<std::pair<const char*, Fn>, 13> tests = {{
|
||||
{ "RayTriangle<1>", TestRayTriangle },
|
||||
{ "RayTriangle<Opt>", TestRayTriangleOpt },
|
||||
{ "RayTriangleBackFacing<1>", TestRayTriangleBackFacing },
|
||||
{ "RayTriangleBackFacing<Opt>", TestRayTriangleBackFacingOpt },
|
||||
{ "RaySphere<1>", TestRaySphere },
|
||||
{ "RaySphere<Opt>", TestRaySphereOpt },
|
||||
{ "RayOrientedBox<1>", TestRayOrientedBox },
|
||||
{ "RayOrientedBox<Opt>", TestRayOrientedBoxOpt },
|
||||
{ "SphereOrientedBox<1>", TestSphereOrientedBox },
|
||||
{ "SphereOrientedBox<Opt>", TestSphereOrientedBoxOpt },
|
||||
{ "GetOBBCorners", TestGetOBBCorners },
|
||||
{ "OBBOBB<1>", TestOBBOBB },
|
||||
{ "OBBOBB<Opt>", TestOBBOBBOpt },
|
||||
}};
|
||||
|
||||
for (auto const& [name, fn] : tests) {
|
||||
|
|
|
|||
|
|
@ -429,7 +429,7 @@ std::string* TestAllCombinations() {
|
|||
VectorType<Len, Packing> vecC = vecA * 3;
|
||||
VectorType<Len, Packing> vecD = vecA * 4;
|
||||
auto result = VectorType<Len, Packing>::Normalize(vecA, vecB, vecC, vecD);
|
||||
VectorType<1, 4> result2 = VectorType<Len, Packing>::Length(std::get<0>(result), std::get<1>(result), std::get<2>(result), std::get<3>(result));
|
||||
VectorType<1, 4> result2 = VectorType<Len, Packing>::Length(result[0], result[1], result[2], result[3]);
|
||||
std::array<T, VectorType<Len, Packing>::AlignmentElement> stored = result2.template Store<T>();
|
||||
|
||||
for(std::uint8_t i = 0; i < Len*Packing; i++) {
|
||||
|
|
@ -472,7 +472,7 @@ std::string* TestAllCombinations() {
|
|||
VectorType<Len, Packing> vecC = vecA * 3;
|
||||
VectorType<Len, Packing> vecD = vecA * 4;
|
||||
auto result = VectorType<Len, Packing>::Normalize(vecA, vecB, vecC, vecD);
|
||||
VectorType<1, 8> result2 = VectorType<Len, Packing>::Length(std::get<0>(result), std::get<1>(result), std::get<2>(result), std::get<3>(result));
|
||||
VectorType<1, 8> result2 = VectorType<Len, Packing>::Length(result[0], result[1], result[2], result[3]);
|
||||
std::array<T, VectorType<Len, Packing>::AlignmentElement> stored = result2.template Store<T>();
|
||||
|
||||
for(std::uint8_t i = 0; i < Len*Packing; i++) {
|
||||
|
|
@ -509,7 +509,7 @@ std::string* TestAllCombinations() {
|
|||
VectorType<Len, Packing> vecB = vecA * 2;
|
||||
VectorType<Len, Packing> vecC = vecA * 3;
|
||||
auto result = VectorType<Len, Packing>::Normalize(vecA, vecB, vecC);
|
||||
VectorType<1, 15> result2 = VectorType<Len, Packing>::Length(std::get<0>(result), std::get<1>(result), std::get<2>(result));
|
||||
VectorType<1, 15> result2 = VectorType<Len, Packing>::Length(result[0], result[1], result[2]);
|
||||
std::array<T, VectorType<Len, Packing>::AlignmentElement> stored = result2.template Store<T>();
|
||||
|
||||
for(std::uint8_t i = 0; i < Len*Packing; i++) {
|
||||
|
|
@ -540,7 +540,7 @@ std::string* TestAllCombinations() {
|
|||
VectorType<Len, Packing> vecA(floats);
|
||||
VectorType<Len, Packing> vecE = vecA * 2;
|
||||
auto result = VectorType<Len, Packing>::Normalize(vecA, vecE);
|
||||
VectorType<1, Packing*2> result2 = VectorType<Len, Packing>::Length(std::get<0>(result), std::get<1>(result));
|
||||
VectorType<1, Packing*2> result2 = VectorType<Len, Packing>::Length(result[0], result[1]);
|
||||
std::array<T, VectorType<Len, Packing>::AlignmentElement> stored = result2.template Store<T>();
|
||||
|
||||
for(std::uint8_t i = 0; i < Len*Packing; i++) {
|
||||
|
|
@ -583,7 +583,7 @@ std::string* TestAllCombinations() {
|
|||
VectorType<Len, Packing> vecE = vecA * 3;
|
||||
VectorType<Len, Packing> vecG = vecA * 4;
|
||||
auto result = VectorType<Len, Packing>::Normalize(vecA, vecC, vecE, vecG);
|
||||
VectorType<1, Packing*4> result2 = VectorType<Len, Packing>::Length(std::get<0>(result), std::get<1>(result), std::get<2>(result), std::get<3>(result));
|
||||
VectorType<1, Packing*4> result2 = VectorType<Len, Packing>::Length(result[0], result[1], result[2], result[3]);
|
||||
std::array<T, VectorType<Len, Packing>::AlignmentElement> stored = result2.template Store<T>();
|
||||
|
||||
for(std::uint8_t i = 0; i < Len*Packing; i++) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue