@vib3code/sdk 2.0.1 → 2.0.3-canary.6f35b4c
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/DOCS/AGENT_HARNESS_ARCHITECTURE.md +243 -0
- package/DOCS/CLI_ONBOARDING.md +1 -1
- package/DOCS/CROSS_SITE_DESIGN_PATTERNS.md +117 -0
- package/DOCS/EPIC_SCROLL_EVENTS.md +773 -0
- package/DOCS/HANDOFF_LANDING_PAGE.md +154 -0
- package/DOCS/HANDOFF_SDK_DEVELOPMENT.md +493 -0
- package/DOCS/MULTIVIZ_CHOREOGRAPHY_PATTERNS.md +937 -0
- package/DOCS/PRODUCT_STRATEGY.md +63 -0
- package/DOCS/README.md +103 -0
- package/DOCS/REFERENCE_SCROLL_ANALYSIS.md +97 -0
- package/DOCS/ROADMAP.md +111 -0
- package/DOCS/SCROLL_TIMELINE_v3.md +269 -0
- package/DOCS/SITE_REFACTOR_PLAN.md +100 -0
- package/DOCS/STATUS.md +24 -0
- package/DOCS/SYSTEM_INVENTORY.md +33 -30
- package/DOCS/VISUAL_ANALYSIS_CLICKERSS.md +85 -0
- package/DOCS/VISUAL_ANALYSIS_FACETAD.md +133 -0
- package/DOCS/VISUAL_ANALYSIS_SIMONE.md +95 -0
- package/DOCS/VISUAL_ANALYSIS_TABLESIDE.md +86 -0
- package/DOCS/{BLUEPRINT_EXECUTION_PLAN_2026-01-07.md → archive/BLUEPRINT_EXECUTION_PLAN_2026-01-07.md} +1 -1
- package/DOCS/{DEV_TRACK_ANALYSIS.md → archive/DEV_TRACK_ANALYSIS.md} +3 -0
- package/DOCS/{SYSTEM_AUDIT_2026-01-30.md → archive/SYSTEM_AUDIT_2026-01-30.md} +3 -0
- package/DOCS/{DEV_TRACK_SESSION_2026-01-31.md → dev-tracks/DEV_TRACK_SESSION_2026-01-31.md} +1 -1
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-06.md +231 -0
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-13.md +127 -0
- package/DOCS/dev-tracks/README.md +10 -0
- package/README.md +26 -13
- package/cpp/CMakeLists.txt +236 -0
- package/cpp/bindings/embind.cpp +269 -0
- package/cpp/build.sh +129 -0
- package/cpp/geometry/Crystal.cpp +103 -0
- package/cpp/geometry/Fractal.cpp +136 -0
- package/cpp/geometry/GeometryGenerator.cpp +262 -0
- package/cpp/geometry/KleinBottle.cpp +71 -0
- package/cpp/geometry/Sphere.cpp +134 -0
- package/cpp/geometry/Tesseract.cpp +94 -0
- package/cpp/geometry/Tetrahedron.cpp +83 -0
- package/cpp/geometry/Torus.cpp +65 -0
- package/cpp/geometry/WarpFunctions.cpp +238 -0
- package/cpp/geometry/Wave.cpp +85 -0
- package/cpp/include/vib3_ffi.h +238 -0
- package/cpp/math/Mat4x4.cpp +409 -0
- package/cpp/math/Mat4x4.hpp +209 -0
- package/cpp/math/Projection.cpp +142 -0
- package/cpp/math/Projection.hpp +148 -0
- package/cpp/math/Rotor4D.cpp +322 -0
- package/cpp/math/Rotor4D.hpp +204 -0
- package/cpp/math/Vec4.cpp +303 -0
- package/cpp/math/Vec4.hpp +225 -0
- package/cpp/src/vib3_ffi.cpp +607 -0
- package/cpp/tests/Geometry_test.cpp +213 -0
- package/cpp/tests/Mat4x4_test.cpp +494 -0
- package/cpp/tests/Projection_test.cpp +298 -0
- package/cpp/tests/Rotor4D_test.cpp +423 -0
- package/cpp/tests/Vec4_test.cpp +489 -0
- package/package.json +31 -27
- package/src/agent/mcp/MCPServer.js +722 -0
- package/src/agent/mcp/stdio-server.js +264 -0
- package/src/agent/mcp/tools.js +367 -0
- package/src/cli/index.js +0 -0
- package/src/core/CanvasManager.js +97 -204
- package/src/core/ErrorReporter.js +1 -1
- package/src/core/Parameters.js +1 -1
- package/src/core/VIB3Engine.js +38 -1
- package/src/core/VitalitySystem.js +53 -0
- package/src/core/renderers/HolographicRendererAdapter.js +2 -2
- package/src/creative/AestheticMapper.js +628 -0
- package/src/creative/ChoreographyPlayer.js +481 -0
- package/src/export/TradingCardManager.js +3 -4
- package/src/faceted/FacetedSystem.js +237 -388
- package/src/holograms/HolographicVisualizer.js +29 -12
- package/src/holograms/RealHolographicSystem.js +68 -12
- package/src/polychora/PolychoraSystem.js +77 -0
- package/src/quantum/QuantumEngine.js +103 -66
- package/src/quantum/QuantumVisualizer.js +7 -2
- package/src/render/UnifiedRenderBridge.js +3 -0
- package/src/shaders/faceted/faceted.frag.glsl +220 -80
- package/src/shaders/faceted/faceted.frag.wgsl +138 -97
- package/src/shaders/holographic/holographic.frag.glsl +28 -9
- package/src/shaders/holographic/holographic.frag.wgsl +107 -38
- package/src/shaders/quantum/quantum.frag.glsl +1 -0
- package/src/shaders/quantum/quantum.frag.wgsl +1 -1
- package/src/viewer/index.js +1 -1
- package/tools/headless-renderer.js +258 -0
- package/tools/shader-sync-verify.js +8 -4
- package/tools/site-analysis/all-reports.json +32 -0
- package/tools/site-analysis/combined-analysis.md +50 -0
- package/tools/site-analyzer.mjs +779 -0
- package/tools/visual-catalog/capture.js +276 -0
- package/tools/visual-catalog/composite.js +138 -0
- /package/DOCS/{DEV_TRACK_PLAN_2026-01-07.md → archive/DEV_TRACK_PLAN_2026-01-07.md} +0 -0
- /package/DOCS/{SESSION_014_PLAN.md → archive/SESSION_014_PLAN.md} +0 -0
- /package/DOCS/{SESSION_LOG_2026-01-07.md → archive/SESSION_LOG_2026-01-07.md} +0 -0
- /package/DOCS/{STRATEGIC_BLUEPRINT_2026-01-07.md → archive/STRATEGIC_BLUEPRINT_2026-01-07.md} +0 -0
- /package/src/viewer/{ReactivityManager.js → ViewerInputHandler.js} +0 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Geometry_test.cpp - Unit tests for geometry generation
|
|
3
|
+
*
|
|
4
|
+
* Tests that the geometry generator produces valid vertex data for all 24
|
|
5
|
+
* geometry indices (3 core types x 8 base geometries).
|
|
6
|
+
*
|
|
7
|
+
* Since the geometry library may not have a standalone header, we include
|
|
8
|
+
* the relevant source and math headers directly.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
#include <gtest/gtest.h>
|
|
12
|
+
#include "math/Vec4.hpp"
|
|
13
|
+
#include "math/Mat4x4.hpp"
|
|
14
|
+
#include "math/Rotor4D.hpp"
|
|
15
|
+
#include "math/Projection.hpp"
|
|
16
|
+
|
|
17
|
+
// Forward-declare the public API from GeometryGenerator.cpp
|
|
18
|
+
// (linked via vib3_geometry library in CMake)
|
|
19
|
+
namespace vib3 {
|
|
20
|
+
std::vector<Vec4> generateGeometry(int geometryIndex, int resolution) noexcept;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
using namespace vib3;
|
|
24
|
+
|
|
25
|
+
// ============================================================================
|
|
26
|
+
// Geometry Name Constants (mirror the 24-geometry encoding)
|
|
27
|
+
// ============================================================================
|
|
28
|
+
|
|
29
|
+
// geometry_index = core_type * 8 + base_geometry
|
|
30
|
+
// core_type: 0=Base, 1=Hypersphere, 2=Hypertetrahedron
|
|
31
|
+
// base_geometry: 0=Tetrahedron, 1=Hypercube, 2=Sphere, 3=Torus,
|
|
32
|
+
// 4=KleinBottle, 5=Fractal, 6=Wave, 7=Crystal
|
|
33
|
+
|
|
34
|
+
static constexpr int kTetrahedronBase = 0;
|
|
35
|
+
static constexpr int kHypercubeBase = 1;
|
|
36
|
+
static constexpr int kSphereBase = 2;
|
|
37
|
+
static constexpr int kTorusBase = 3;
|
|
38
|
+
static constexpr int kKleinBottleBase = 4;
|
|
39
|
+
static constexpr int kFractalBase = 5;
|
|
40
|
+
static constexpr int kWaveBase = 6;
|
|
41
|
+
static constexpr int kCrystalBase = 7;
|
|
42
|
+
|
|
43
|
+
static constexpr int kHypersphereOffset = 8;
|
|
44
|
+
static constexpr int kHypertetraOffset = 16;
|
|
45
|
+
|
|
46
|
+
static constexpr int kTotalGeometries = 24;
|
|
47
|
+
|
|
48
|
+
/** Default subdivision resolution for tests. */
|
|
49
|
+
static constexpr int kDefaultRes = 16;
|
|
50
|
+
|
|
51
|
+
// ============================================================================
|
|
52
|
+
// Tesseract (Hypercube) Vertex Count
|
|
53
|
+
// ============================================================================
|
|
54
|
+
|
|
55
|
+
TEST(Geometry, TesseractHas16Vertices) {
|
|
56
|
+
// A 4D hypercube (tesseract) has 2^4 = 16 vertices
|
|
57
|
+
// The vertices are all combinations of (+/-1, +/-1, +/-1, +/-1)
|
|
58
|
+
auto vertices = generateGeometry(kHypercubeBase, kDefaultRes);
|
|
59
|
+
EXPECT_GE(vertices.size(), 16u)
|
|
60
|
+
<< "Tesseract should have at least 16 vertices";
|
|
61
|
+
|
|
62
|
+
// Verify that all vertices have valid (non-NaN, non-Inf) components
|
|
63
|
+
for (const auto& v : vertices) {
|
|
64
|
+
EXPECT_FALSE(std::isnan(v.x)) << "Vertex has NaN x component";
|
|
65
|
+
EXPECT_FALSE(std::isnan(v.y)) << "Vertex has NaN y component";
|
|
66
|
+
EXPECT_FALSE(std::isnan(v.z)) << "Vertex has NaN z component";
|
|
67
|
+
EXPECT_FALSE(std::isnan(v.w)) << "Vertex has NaN w component";
|
|
68
|
+
EXPECT_FALSE(std::isinf(v.x)) << "Vertex has Inf x component";
|
|
69
|
+
EXPECT_FALSE(std::isinf(v.y)) << "Vertex has Inf y component";
|
|
70
|
+
EXPECT_FALSE(std::isinf(v.z)) << "Vertex has Inf z component";
|
|
71
|
+
EXPECT_FALSE(std::isinf(v.w)) << "Vertex has Inf w component";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ============================================================================
|
|
76
|
+
// Tetrahedron Vertex Count
|
|
77
|
+
// ============================================================================
|
|
78
|
+
|
|
79
|
+
TEST(Geometry, TetrahedronHas4Vertices) {
|
|
80
|
+
// A tetrahedron has 4 vertices
|
|
81
|
+
auto vertices = generateGeometry(kTetrahedronBase, kDefaultRes);
|
|
82
|
+
EXPECT_GE(vertices.size(), 4u)
|
|
83
|
+
<< "Tetrahedron should have at least 4 vertices";
|
|
84
|
+
|
|
85
|
+
for (const auto& v : vertices) {
|
|
86
|
+
EXPECT_FALSE(std::isnan(v.x));
|
|
87
|
+
EXPECT_FALSE(std::isnan(v.y));
|
|
88
|
+
EXPECT_FALSE(std::isnan(v.z));
|
|
89
|
+
EXPECT_FALSE(std::isnan(v.w));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ============================================================================
|
|
94
|
+
// All 24 Geometries Produce Non-Empty Vertex Sets
|
|
95
|
+
// ============================================================================
|
|
96
|
+
|
|
97
|
+
TEST(Geometry, AllGeometriesProduceVertices) {
|
|
98
|
+
for (int i = 0; i < kTotalGeometries; ++i) {
|
|
99
|
+
auto vertices = generateGeometry(i, kDefaultRes);
|
|
100
|
+
EXPECT_GT(vertices.size(), 0u)
|
|
101
|
+
<< "Geometry index " << i << " should produce non-empty vertex set";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
TEST(Geometry, AllGeometriesHaveValidVertices) {
|
|
106
|
+
for (int i = 0; i < kTotalGeometries; ++i) {
|
|
107
|
+
auto vertices = generateGeometry(i, kDefaultRes);
|
|
108
|
+
for (size_t j = 0; j < vertices.size(); ++j) {
|
|
109
|
+
EXPECT_FALSE(std::isnan(vertices[j].x))
|
|
110
|
+
<< "Geometry " << i << " vertex " << j << " has NaN x";
|
|
111
|
+
EXPECT_FALSE(std::isnan(vertices[j].y))
|
|
112
|
+
<< "Geometry " << i << " vertex " << j << " has NaN y";
|
|
113
|
+
EXPECT_FALSE(std::isnan(vertices[j].z))
|
|
114
|
+
<< "Geometry " << i << " vertex " << j << " has NaN z";
|
|
115
|
+
EXPECT_FALSE(std::isnan(vertices[j].w))
|
|
116
|
+
<< "Geometry " << i << " vertex " << j << " has NaN w";
|
|
117
|
+
EXPECT_FALSE(std::isinf(vertices[j].x))
|
|
118
|
+
<< "Geometry " << i << " vertex " << j << " has Inf x";
|
|
119
|
+
EXPECT_FALSE(std::isinf(vertices[j].y))
|
|
120
|
+
<< "Geometry " << i << " vertex " << j << " has Inf y";
|
|
121
|
+
EXPECT_FALSE(std::isinf(vertices[j].z))
|
|
122
|
+
<< "Geometry " << i << " vertex " << j << " has Inf z";
|
|
123
|
+
EXPECT_FALSE(std::isinf(vertices[j].w))
|
|
124
|
+
<< "Geometry " << i << " vertex " << j << " has Inf w";
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ============================================================================
|
|
130
|
+
// Core Type Offset Encoding
|
|
131
|
+
// ============================================================================
|
|
132
|
+
|
|
133
|
+
TEST(Geometry, BaseGeometriesRange0to7) {
|
|
134
|
+
// Base geometries (core_type=0) are indices 0-7
|
|
135
|
+
for (int i = 0; i <= 7; ++i) {
|
|
136
|
+
auto vertices = generateGeometry(i, kDefaultRes);
|
|
137
|
+
EXPECT_GT(vertices.size(), 0u)
|
|
138
|
+
<< "Base geometry " << i << " should produce vertices";
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
TEST(Geometry, HypersphereGeometriesRange8to15) {
|
|
143
|
+
// Hypersphere warped geometries (core_type=1) are indices 8-15
|
|
144
|
+
for (int i = 8; i <= 15; ++i) {
|
|
145
|
+
auto vertices = generateGeometry(i, kDefaultRes);
|
|
146
|
+
EXPECT_GT(vertices.size(), 0u)
|
|
147
|
+
<< "Hypersphere geometry " << i << " should produce vertices";
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
TEST(Geometry, HypertetraGeometriesRange16to23) {
|
|
152
|
+
// Hypertetrahedron warped geometries (core_type=2) are indices 16-23
|
|
153
|
+
for (int i = 16; i <= 23; ++i) {
|
|
154
|
+
auto vertices = generateGeometry(i, kDefaultRes);
|
|
155
|
+
EXPECT_GT(vertices.size(), 0u)
|
|
156
|
+
<< "Hypertetra geometry " << i << " should produce vertices";
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ============================================================================
|
|
161
|
+
// Specific Geometry Properties
|
|
162
|
+
// ============================================================================
|
|
163
|
+
|
|
164
|
+
TEST(Geometry, SphereVerticesAreApproximatelyOnUnitSphere) {
|
|
165
|
+
auto vertices = generateGeometry(kSphereBase, kDefaultRes);
|
|
166
|
+
EXPECT_GT(vertices.size(), 0u);
|
|
167
|
+
|
|
168
|
+
// For a unit sphere, most vertices should have length close to 1.0
|
|
169
|
+
int onSphere = 0;
|
|
170
|
+
for (const auto& v : vertices) {
|
|
171
|
+
float len = v.length();
|
|
172
|
+
if (std::abs(len - 1.0f) < 0.2f) {
|
|
173
|
+
++onSphere;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// At least half should be near the unit sphere
|
|
177
|
+
EXPECT_GT(onSphere, static_cast<int>(vertices.size()) / 2)
|
|
178
|
+
<< "Most sphere vertices should be near unit length";
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
TEST(Geometry, HypercubeVerticesHaveSymmetry) {
|
|
182
|
+
// Tesseract vertices should be symmetric: if (x,y,z,w) is a vertex,
|
|
183
|
+
// then (-x,y,z,w) should also be (approximately) a vertex
|
|
184
|
+
auto vertices = generateGeometry(kHypercubeBase, kDefaultRes);
|
|
185
|
+
EXPECT_GE(vertices.size(), 16u);
|
|
186
|
+
|
|
187
|
+
// Count vertices with positive x and negative x
|
|
188
|
+
int positiveX = 0, negativeX = 0;
|
|
189
|
+
for (const auto& v : vertices) {
|
|
190
|
+
if (v.x > 0.01f) ++positiveX;
|
|
191
|
+
if (v.x < -0.01f) ++negativeX;
|
|
192
|
+
}
|
|
193
|
+
// Should be roughly balanced
|
|
194
|
+
EXPECT_GT(positiveX, 0) << "Should have vertices with positive x";
|
|
195
|
+
EXPECT_GT(negativeX, 0) << "Should have vertices with negative x";
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ============================================================================
|
|
199
|
+
// Same Base Geometry Across Core Types
|
|
200
|
+
// ============================================================================
|
|
201
|
+
|
|
202
|
+
TEST(Geometry, WarpedVersionsHaveSameOrMoreVertices) {
|
|
203
|
+
// Warped versions should have at least as many vertices as the base
|
|
204
|
+
for (int base = 0; base < 8; ++base) {
|
|
205
|
+
auto baseVertices = generateGeometry(base, kDefaultRes);
|
|
206
|
+
auto hypersphereVertices = generateGeometry(base + kHypersphereOffset, kDefaultRes);
|
|
207
|
+
auto hypertetraVertices = generateGeometry(base + kHypertetraOffset, kDefaultRes);
|
|
208
|
+
|
|
209
|
+
EXPECT_GT(baseVertices.size(), 0u);
|
|
210
|
+
EXPECT_GT(hypersphereVertices.size(), 0u);
|
|
211
|
+
EXPECT_GT(hypertetraVertices.size(), 0u);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mat4x4_test.cpp - Unit tests for Mat4x4 4x4 matrix class
|
|
3
|
+
*
|
|
4
|
+
* Tests identity construction, matrix-vector multiplication, matrix-matrix
|
|
5
|
+
* multiplication, determinant, transpose, rotation factories, and inversion.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#include <gtest/gtest.h>
|
|
9
|
+
#include "math/Mat4x4.hpp"
|
|
10
|
+
#include "math/Vec4.hpp"
|
|
11
|
+
#include <cmath>
|
|
12
|
+
#include <numbers>
|
|
13
|
+
|
|
14
|
+
using namespace vib3;
|
|
15
|
+
|
|
16
|
+
static constexpr float kEpsilon = 1e-4f;
|
|
17
|
+
static constexpr float kPi = std::numbers::pi_v<float>;
|
|
18
|
+
static constexpr float kHalfPi = kPi / 2.0f;
|
|
19
|
+
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// Construction
|
|
22
|
+
// ============================================================================
|
|
23
|
+
|
|
24
|
+
TEST(Mat4x4, DefaultConstructorIsIdentity) {
|
|
25
|
+
Mat4x4 m;
|
|
26
|
+
EXPECT_TRUE(m.isIdentity());
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
TEST(Mat4x4, IdentityFactory) {
|
|
30
|
+
Mat4x4 m = Mat4x4::identity();
|
|
31
|
+
EXPECT_TRUE(m.isIdentity());
|
|
32
|
+
|
|
33
|
+
// Verify diagonal is 1, off-diagonal is 0
|
|
34
|
+
for (size_t r = 0; r < 4; ++r) {
|
|
35
|
+
for (size_t c = 0; c < 4; ++c) {
|
|
36
|
+
if (r == c) {
|
|
37
|
+
EXPECT_FLOAT_EQ(m.at(r, c), 1.0f);
|
|
38
|
+
} else {
|
|
39
|
+
EXPECT_FLOAT_EQ(m.at(r, c), 0.0f);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
TEST(Mat4x4, ZeroFactory) {
|
|
46
|
+
Mat4x4 m = Mat4x4::zero();
|
|
47
|
+
for (size_t r = 0; r < 4; ++r) {
|
|
48
|
+
for (size_t c = 0; c < 4; ++c) {
|
|
49
|
+
EXPECT_FLOAT_EQ(m.at(r, c), 0.0f);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
TEST(Mat4x4, DiagonalConstructor) {
|
|
55
|
+
Mat4x4 m(3.0f);
|
|
56
|
+
EXPECT_FLOAT_EQ(m.at(0, 0), 3.0f);
|
|
57
|
+
EXPECT_FLOAT_EQ(m.at(1, 1), 3.0f);
|
|
58
|
+
EXPECT_FLOAT_EQ(m.at(2, 2), 3.0f);
|
|
59
|
+
EXPECT_FLOAT_EQ(m.at(3, 3), 3.0f);
|
|
60
|
+
EXPECT_FLOAT_EQ(m.at(0, 1), 0.0f);
|
|
61
|
+
EXPECT_FLOAT_EQ(m.at(1, 0), 0.0f);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
TEST(Mat4x4, ColumnConstructor) {
|
|
65
|
+
Vec4 c0(1.0f, 0.0f, 0.0f, 0.0f);
|
|
66
|
+
Vec4 c1(0.0f, 2.0f, 0.0f, 0.0f);
|
|
67
|
+
Vec4 c2(0.0f, 0.0f, 3.0f, 0.0f);
|
|
68
|
+
Vec4 c3(0.0f, 0.0f, 0.0f, 4.0f);
|
|
69
|
+
Mat4x4 m(c0, c1, c2, c3);
|
|
70
|
+
EXPECT_FLOAT_EQ(m.at(0, 0), 1.0f);
|
|
71
|
+
EXPECT_FLOAT_EQ(m.at(1, 1), 2.0f);
|
|
72
|
+
EXPECT_FLOAT_EQ(m.at(2, 2), 3.0f);
|
|
73
|
+
EXPECT_FLOAT_EQ(m.at(3, 3), 4.0f);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ============================================================================
|
|
77
|
+
// Element Access
|
|
78
|
+
// ============================================================================
|
|
79
|
+
|
|
80
|
+
TEST(Mat4x4, ColumnAccess) {
|
|
81
|
+
Mat4x4 m = Mat4x4::identity();
|
|
82
|
+
Vec4 col0 = m.column(0);
|
|
83
|
+
EXPECT_FLOAT_EQ(col0.x, 1.0f);
|
|
84
|
+
EXPECT_FLOAT_EQ(col0.y, 0.0f);
|
|
85
|
+
EXPECT_FLOAT_EQ(col0.z, 0.0f);
|
|
86
|
+
EXPECT_FLOAT_EQ(col0.w, 0.0f);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
TEST(Mat4x4, RowAccess) {
|
|
90
|
+
Mat4x4 m = Mat4x4::identity();
|
|
91
|
+
Vec4 row0 = m.row(0);
|
|
92
|
+
EXPECT_FLOAT_EQ(row0.x, 1.0f);
|
|
93
|
+
EXPECT_FLOAT_EQ(row0.y, 0.0f);
|
|
94
|
+
EXPECT_FLOAT_EQ(row0.z, 0.0f);
|
|
95
|
+
EXPECT_FLOAT_EQ(row0.w, 0.0f);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
TEST(Mat4x4, SetColumn) {
|
|
99
|
+
Mat4x4 m = Mat4x4::zero();
|
|
100
|
+
m.setColumn(1, Vec4(10.0f, 20.0f, 30.0f, 40.0f));
|
|
101
|
+
EXPECT_FLOAT_EQ(m.at(0, 1), 10.0f);
|
|
102
|
+
EXPECT_FLOAT_EQ(m.at(1, 1), 20.0f);
|
|
103
|
+
EXPECT_FLOAT_EQ(m.at(2, 1), 30.0f);
|
|
104
|
+
EXPECT_FLOAT_EQ(m.at(3, 1), 40.0f);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
TEST(Mat4x4, SetRow) {
|
|
108
|
+
Mat4x4 m = Mat4x4::zero();
|
|
109
|
+
m.setRow(2, Vec4(10.0f, 20.0f, 30.0f, 40.0f));
|
|
110
|
+
EXPECT_FLOAT_EQ(m.at(2, 0), 10.0f);
|
|
111
|
+
EXPECT_FLOAT_EQ(m.at(2, 1), 20.0f);
|
|
112
|
+
EXPECT_FLOAT_EQ(m.at(2, 2), 30.0f);
|
|
113
|
+
EXPECT_FLOAT_EQ(m.at(2, 3), 40.0f);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ============================================================================
|
|
117
|
+
// Matrix-Vector Multiplication
|
|
118
|
+
// ============================================================================
|
|
119
|
+
|
|
120
|
+
TEST(Mat4x4, IdentityTimesVectorIsVector) {
|
|
121
|
+
Mat4x4 m = Mat4x4::identity();
|
|
122
|
+
Vec4 v(1.0f, 2.0f, 3.0f, 4.0f);
|
|
123
|
+
Vec4 result = m * v;
|
|
124
|
+
EXPECT_FLOAT_EQ(result.x, 1.0f);
|
|
125
|
+
EXPECT_FLOAT_EQ(result.y, 2.0f);
|
|
126
|
+
EXPECT_FLOAT_EQ(result.z, 3.0f);
|
|
127
|
+
EXPECT_FLOAT_EQ(result.w, 4.0f);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
TEST(Mat4x4, ZeroMatrixTimesVectorIsZero) {
|
|
131
|
+
Mat4x4 m = Mat4x4::zero();
|
|
132
|
+
Vec4 v(1.0f, 2.0f, 3.0f, 4.0f);
|
|
133
|
+
Vec4 result = m * v;
|
|
134
|
+
EXPECT_FLOAT_EQ(result.x, 0.0f);
|
|
135
|
+
EXPECT_FLOAT_EQ(result.y, 0.0f);
|
|
136
|
+
EXPECT_FLOAT_EQ(result.z, 0.0f);
|
|
137
|
+
EXPECT_FLOAT_EQ(result.w, 0.0f);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
TEST(Mat4x4, MultiplyVec4MatchesOperator) {
|
|
141
|
+
Mat4x4 m = Mat4x4::identity();
|
|
142
|
+
Vec4 v(1.0f, 2.0f, 3.0f, 4.0f);
|
|
143
|
+
Vec4 r1 = m * v;
|
|
144
|
+
Vec4 r2 = m.multiplyVec4(v);
|
|
145
|
+
EXPECT_FLOAT_EQ(r1.x, r2.x);
|
|
146
|
+
EXPECT_FLOAT_EQ(r1.y, r2.y);
|
|
147
|
+
EXPECT_FLOAT_EQ(r1.z, r2.z);
|
|
148
|
+
EXPECT_FLOAT_EQ(r1.w, r2.w);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
TEST(Mat4x4, ScaleMatrixTimesVector) {
|
|
152
|
+
Mat4x4 m = Mat4x4::scale(2.0f, 3.0f, 4.0f, 5.0f);
|
|
153
|
+
Vec4 v(1.0f, 1.0f, 1.0f, 1.0f);
|
|
154
|
+
Vec4 result = m * v;
|
|
155
|
+
EXPECT_NEAR(result.x, 2.0f, kEpsilon);
|
|
156
|
+
EXPECT_NEAR(result.y, 3.0f, kEpsilon);
|
|
157
|
+
EXPECT_NEAR(result.z, 4.0f, kEpsilon);
|
|
158
|
+
EXPECT_NEAR(result.w, 5.0f, kEpsilon);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ============================================================================
|
|
162
|
+
// Matrix-Matrix Multiplication
|
|
163
|
+
// ============================================================================
|
|
164
|
+
|
|
165
|
+
TEST(Mat4x4, IdentityTimesIdentityIsIdentity) {
|
|
166
|
+
Mat4x4 a = Mat4x4::identity();
|
|
167
|
+
Mat4x4 b = Mat4x4::identity();
|
|
168
|
+
Mat4x4 result = a * b;
|
|
169
|
+
EXPECT_TRUE(result.isIdentity());
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
TEST(Mat4x4, IdentityTimesMatrixIsMatrix) {
|
|
173
|
+
Mat4x4 m = Mat4x4::scale(2.0f);
|
|
174
|
+
Mat4x4 result = Mat4x4::identity() * m;
|
|
175
|
+
for (size_t r = 0; r < 4; ++r) {
|
|
176
|
+
for (size_t c = 0; c < 4; ++c) {
|
|
177
|
+
EXPECT_NEAR(result.at(r, c), m.at(r, c), kEpsilon);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
TEST(Mat4x4, MatrixTimesIdentityIsMatrix) {
|
|
183
|
+
Mat4x4 m = Mat4x4::scale(3.0f);
|
|
184
|
+
Mat4x4 result = m * Mat4x4::identity();
|
|
185
|
+
for (size_t r = 0; r < 4; ++r) {
|
|
186
|
+
for (size_t c = 0; c < 4; ++c) {
|
|
187
|
+
EXPECT_NEAR(result.at(r, c), m.at(r, c), kEpsilon);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
TEST(Mat4x4, ScaleMatrixMultiplication) {
|
|
193
|
+
Mat4x4 s2 = Mat4x4::scale(2.0f);
|
|
194
|
+
Mat4x4 s3 = Mat4x4::scale(3.0f);
|
|
195
|
+
Mat4x4 result = s2 * s3;
|
|
196
|
+
// scale(2) * scale(3) = scale(6)
|
|
197
|
+
EXPECT_NEAR(result.at(0, 0), 6.0f, kEpsilon);
|
|
198
|
+
EXPECT_NEAR(result.at(1, 1), 6.0f, kEpsilon);
|
|
199
|
+
EXPECT_NEAR(result.at(2, 2), 6.0f, kEpsilon);
|
|
200
|
+
EXPECT_NEAR(result.at(3, 3), 6.0f, kEpsilon);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
TEST(Mat4x4, CompoundMultiplication) {
|
|
204
|
+
Mat4x4 m = Mat4x4::identity();
|
|
205
|
+
m *= Mat4x4::scale(2.0f);
|
|
206
|
+
EXPECT_NEAR(m.at(0, 0), 2.0f, kEpsilon);
|
|
207
|
+
EXPECT_NEAR(m.at(1, 1), 2.0f, kEpsilon);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ============================================================================
|
|
211
|
+
// Scalar Operations
|
|
212
|
+
// ============================================================================
|
|
213
|
+
|
|
214
|
+
TEST(Mat4x4, ScalarMultiplication) {
|
|
215
|
+
Mat4x4 m = Mat4x4::identity();
|
|
216
|
+
Mat4x4 result = m * 3.0f;
|
|
217
|
+
EXPECT_NEAR(result.at(0, 0), 3.0f, kEpsilon);
|
|
218
|
+
EXPECT_NEAR(result.at(1, 1), 3.0f, kEpsilon);
|
|
219
|
+
EXPECT_NEAR(result.at(0, 1), 0.0f, kEpsilon);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
TEST(Mat4x4, ScalarMultiplicationFreeFunction) {
|
|
223
|
+
Mat4x4 m = Mat4x4::identity();
|
|
224
|
+
Mat4x4 result = 5.0f * m;
|
|
225
|
+
EXPECT_NEAR(result.at(0, 0), 5.0f, kEpsilon);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ============================================================================
|
|
229
|
+
// Addition and Subtraction
|
|
230
|
+
// ============================================================================
|
|
231
|
+
|
|
232
|
+
TEST(Mat4x4, MatrixAddition) {
|
|
233
|
+
Mat4x4 a = Mat4x4::identity();
|
|
234
|
+
Mat4x4 b = Mat4x4::identity();
|
|
235
|
+
Mat4x4 result = a + b;
|
|
236
|
+
EXPECT_NEAR(result.at(0, 0), 2.0f, kEpsilon);
|
|
237
|
+
EXPECT_NEAR(result.at(1, 1), 2.0f, kEpsilon);
|
|
238
|
+
EXPECT_NEAR(result.at(0, 1), 0.0f, kEpsilon);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
TEST(Mat4x4, MatrixSubtraction) {
|
|
242
|
+
Mat4x4 a = Mat4x4::identity();
|
|
243
|
+
Mat4x4 b = Mat4x4::identity();
|
|
244
|
+
Mat4x4 result = a - b;
|
|
245
|
+
// Should be zero matrix
|
|
246
|
+
for (size_t r = 0; r < 4; ++r) {
|
|
247
|
+
for (size_t c = 0; c < 4; ++c) {
|
|
248
|
+
EXPECT_NEAR(result.at(r, c), 0.0f, kEpsilon);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ============================================================================
|
|
254
|
+
// Determinant
|
|
255
|
+
// ============================================================================
|
|
256
|
+
|
|
257
|
+
TEST(Mat4x4, DeterminantOfIdentityIsOne) {
|
|
258
|
+
Mat4x4 m = Mat4x4::identity();
|
|
259
|
+
EXPECT_NEAR(m.determinant(), 1.0f, kEpsilon);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
TEST(Mat4x4, DeterminantOfZeroIsZero) {
|
|
263
|
+
Mat4x4 m = Mat4x4::zero();
|
|
264
|
+
EXPECT_NEAR(m.determinant(), 0.0f, kEpsilon);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
TEST(Mat4x4, DeterminantOfScaleMatrix) {
|
|
268
|
+
Mat4x4 m = Mat4x4::scale(2.0f, 3.0f, 4.0f, 5.0f);
|
|
269
|
+
// det(diag(2,3,4,5)) = 2*3*4*5 = 120
|
|
270
|
+
EXPECT_NEAR(m.determinant(), 120.0f, kEpsilon);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
TEST(Mat4x4, DeterminantOfUniformScale) {
|
|
274
|
+
Mat4x4 m = Mat4x4::scale(2.0f);
|
|
275
|
+
// det(2*I) = 2^4 = 16
|
|
276
|
+
EXPECT_NEAR(m.determinant(), 16.0f, kEpsilon);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
TEST(Mat4x4, DeterminantOfRotationIsOne) {
|
|
280
|
+
// Rotation matrices have determinant 1
|
|
281
|
+
Mat4x4 m = Mat4x4::rotationXY(0.5f);
|
|
282
|
+
EXPECT_NEAR(m.determinant(), 1.0f, kEpsilon);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ============================================================================
|
|
286
|
+
// Transpose
|
|
287
|
+
// ============================================================================
|
|
288
|
+
|
|
289
|
+
TEST(Mat4x4, TransposeOfIdentityIsIdentity) {
|
|
290
|
+
Mat4x4 m = Mat4x4::identity();
|
|
291
|
+
Mat4x4 t = m.transposed();
|
|
292
|
+
EXPECT_TRUE(t.isIdentity());
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
TEST(Mat4x4, TransposeSwapsElements) {
|
|
296
|
+
Mat4x4 m = Mat4x4::zero();
|
|
297
|
+
m.at(0, 1) = 5.0f;
|
|
298
|
+
m.at(1, 0) = 10.0f;
|
|
299
|
+
Mat4x4 t = m.transposed();
|
|
300
|
+
EXPECT_FLOAT_EQ(t.at(0, 1), 10.0f);
|
|
301
|
+
EXPECT_FLOAT_EQ(t.at(1, 0), 5.0f);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
TEST(Mat4x4, DoubleTransposeIsOriginal) {
|
|
305
|
+
Mat4x4 m = Mat4x4::rotationXY(0.7f);
|
|
306
|
+
Mat4x4 tt = m.transposed().transposed();
|
|
307
|
+
for (size_t r = 0; r < 4; ++r) {
|
|
308
|
+
for (size_t c = 0; c < 4; ++c) {
|
|
309
|
+
EXPECT_NEAR(tt.at(r, c), m.at(r, c), kEpsilon);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
TEST(Mat4x4, TransposeInPlace) {
|
|
315
|
+
Mat4x4 m = Mat4x4::zero();
|
|
316
|
+
m.at(0, 2) = 7.0f;
|
|
317
|
+
m.transpose();
|
|
318
|
+
EXPECT_FLOAT_EQ(m.at(2, 0), 7.0f);
|
|
319
|
+
EXPECT_FLOAT_EQ(m.at(0, 2), 0.0f);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ============================================================================
|
|
323
|
+
// Inverse
|
|
324
|
+
// ============================================================================
|
|
325
|
+
|
|
326
|
+
TEST(Mat4x4, InverseOfIdentityIsIdentity) {
|
|
327
|
+
Mat4x4 m = Mat4x4::identity();
|
|
328
|
+
Mat4x4 inv = m.inverse();
|
|
329
|
+
EXPECT_TRUE(inv.isIdentity());
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
TEST(Mat4x4, InverseOfScaleMatrix) {
|
|
333
|
+
Mat4x4 m = Mat4x4::scale(2.0f);
|
|
334
|
+
Mat4x4 inv = m.inverse();
|
|
335
|
+
// inverse of scale(2) should be scale(0.5)
|
|
336
|
+
EXPECT_NEAR(inv.at(0, 0), 0.5f, kEpsilon);
|
|
337
|
+
EXPECT_NEAR(inv.at(1, 1), 0.5f, kEpsilon);
|
|
338
|
+
EXPECT_NEAR(inv.at(2, 2), 0.5f, kEpsilon);
|
|
339
|
+
EXPECT_NEAR(inv.at(3, 3), 0.5f, kEpsilon);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
TEST(Mat4x4, MatrixTimesInverseIsIdentity) {
|
|
343
|
+
Mat4x4 m = Mat4x4::rotationXY(0.8f);
|
|
344
|
+
Mat4x4 inv = m.inverse();
|
|
345
|
+
Mat4x4 product = m * inv;
|
|
346
|
+
EXPECT_TRUE(product.isIdentity(1e-4f));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// ============================================================================
|
|
350
|
+
// Rotation Matrices
|
|
351
|
+
// ============================================================================
|
|
352
|
+
|
|
353
|
+
TEST(Mat4x4, RotationXY_RotatesXtoY) {
|
|
354
|
+
Mat4x4 m = Mat4x4::rotationXY(kHalfPi);
|
|
355
|
+
Vec4 result = m * Vec4::unitX();
|
|
356
|
+
EXPECT_NEAR(result.x, 0.0f, kEpsilon);
|
|
357
|
+
EXPECT_NEAR(result.y, 1.0f, kEpsilon);
|
|
358
|
+
EXPECT_NEAR(result.z, 0.0f, kEpsilon);
|
|
359
|
+
EXPECT_NEAR(result.w, 0.0f, kEpsilon);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
TEST(Mat4x4, RotationXZ_RotatesXtoZ) {
|
|
363
|
+
Mat4x4 m = Mat4x4::rotationXZ(kHalfPi);
|
|
364
|
+
Vec4 result = m * Vec4::unitX();
|
|
365
|
+
EXPECT_NEAR(result.x, 0.0f, kEpsilon);
|
|
366
|
+
EXPECT_NEAR(result.y, 0.0f, kEpsilon);
|
|
367
|
+
EXPECT_NEAR(result.z, 1.0f, kEpsilon);
|
|
368
|
+
EXPECT_NEAR(result.w, 0.0f, kEpsilon);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
TEST(Mat4x4, RotationYZ_RotatesYtoZ) {
|
|
372
|
+
Mat4x4 m = Mat4x4::rotationYZ(kHalfPi);
|
|
373
|
+
Vec4 result = m * Vec4::unitY();
|
|
374
|
+
EXPECT_NEAR(result.x, 0.0f, kEpsilon);
|
|
375
|
+
EXPECT_NEAR(result.y, 0.0f, kEpsilon);
|
|
376
|
+
EXPECT_NEAR(result.z, 1.0f, kEpsilon);
|
|
377
|
+
EXPECT_NEAR(result.w, 0.0f, kEpsilon);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
TEST(Mat4x4, RotationXW_RotatesXtoW) {
|
|
381
|
+
Mat4x4 m = Mat4x4::rotationXW(kHalfPi);
|
|
382
|
+
Vec4 result = m * Vec4::unitX();
|
|
383
|
+
EXPECT_NEAR(result.x, 0.0f, kEpsilon);
|
|
384
|
+
EXPECT_NEAR(result.y, 0.0f, kEpsilon);
|
|
385
|
+
EXPECT_NEAR(result.z, 0.0f, kEpsilon);
|
|
386
|
+
EXPECT_NEAR(result.w, 1.0f, kEpsilon);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
TEST(Mat4x4, RotationYW_RotatesYtoW) {
|
|
390
|
+
Mat4x4 m = Mat4x4::rotationYW(kHalfPi);
|
|
391
|
+
Vec4 result = m * Vec4::unitY();
|
|
392
|
+
EXPECT_NEAR(result.x, 0.0f, kEpsilon);
|
|
393
|
+
EXPECT_NEAR(result.y, 0.0f, kEpsilon);
|
|
394
|
+
EXPECT_NEAR(result.z, 0.0f, kEpsilon);
|
|
395
|
+
EXPECT_NEAR(result.w, 1.0f, kEpsilon);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
TEST(Mat4x4, RotationZW_RotatesZtoW) {
|
|
399
|
+
Mat4x4 m = Mat4x4::rotationZW(kHalfPi);
|
|
400
|
+
Vec4 result = m * Vec4::unitZ();
|
|
401
|
+
EXPECT_NEAR(result.x, 0.0f, kEpsilon);
|
|
402
|
+
EXPECT_NEAR(result.y, 0.0f, kEpsilon);
|
|
403
|
+
EXPECT_NEAR(result.z, 0.0f, kEpsilon);
|
|
404
|
+
EXPECT_NEAR(result.w, 1.0f, kEpsilon);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
TEST(Mat4x4, RotationZeroAngleIsIdentity) {
|
|
408
|
+
Mat4x4 m = Mat4x4::rotationXY(0.0f);
|
|
409
|
+
EXPECT_TRUE(m.isIdentity());
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
TEST(Mat4x4, RotationFromAnglesAllZerosIsIdentity) {
|
|
413
|
+
Mat4x4 m = Mat4x4::rotationFromAngles(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
|
|
414
|
+
EXPECT_TRUE(m.isIdentity());
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
TEST(Mat4x4, RotationFromAnglesArrayOverload) {
|
|
418
|
+
std::array<float, 6> angles = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
|
419
|
+
Mat4x4 m = Mat4x4::rotationFromAngles(angles);
|
|
420
|
+
EXPECT_TRUE(m.isIdentity());
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
TEST(Mat4x4, RotationIsOrthogonal) {
|
|
424
|
+
Mat4x4 m = Mat4x4::rotationFromAngles(0.3f, 0.5f, 0.7f, 0.1f, 0.2f, 0.4f);
|
|
425
|
+
EXPECT_TRUE(m.isOrthogonal(1e-3f));
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
TEST(Mat4x4, RotationDeterminantIsOne) {
|
|
429
|
+
Mat4x4 m = Mat4x4::rotationFromAngles(0.3f, 0.5f, 0.7f, 0.1f, 0.2f, 0.4f);
|
|
430
|
+
EXPECT_NEAR(m.determinant(), 1.0f, kEpsilon);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
TEST(Mat4x4, RotationPreservesVectorLength) {
|
|
434
|
+
Mat4x4 m = Mat4x4::rotationFromAngles(0.5f, 0.3f, 0.7f, 0.2f, 0.4f, 0.1f);
|
|
435
|
+
Vec4 v(1.0f, 2.0f, 3.0f, 4.0f);
|
|
436
|
+
Vec4 rotated = m * v;
|
|
437
|
+
EXPECT_NEAR(rotated.length(), v.length(), kEpsilon);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// ============================================================================
|
|
441
|
+
// Scale
|
|
442
|
+
// ============================================================================
|
|
443
|
+
|
|
444
|
+
TEST(Mat4x4, UniformScaleMatrix) {
|
|
445
|
+
Mat4x4 m = Mat4x4::scale(3.0f);
|
|
446
|
+
EXPECT_FLOAT_EQ(m.at(0, 0), 3.0f);
|
|
447
|
+
EXPECT_FLOAT_EQ(m.at(1, 1), 3.0f);
|
|
448
|
+
EXPECT_FLOAT_EQ(m.at(2, 2), 3.0f);
|
|
449
|
+
EXPECT_FLOAT_EQ(m.at(3, 3), 3.0f);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
TEST(Mat4x4, ScaleFromVec4) {
|
|
453
|
+
Vec4 s(2.0f, 3.0f, 4.0f, 5.0f);
|
|
454
|
+
Mat4x4 m = Mat4x4::scale(s);
|
|
455
|
+
EXPECT_FLOAT_EQ(m.at(0, 0), 2.0f);
|
|
456
|
+
EXPECT_FLOAT_EQ(m.at(1, 1), 3.0f);
|
|
457
|
+
EXPECT_FLOAT_EQ(m.at(2, 2), 4.0f);
|
|
458
|
+
EXPECT_FLOAT_EQ(m.at(3, 3), 5.0f);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// ============================================================================
|
|
462
|
+
// Comparison
|
|
463
|
+
// ============================================================================
|
|
464
|
+
|
|
465
|
+
TEST(Mat4x4, EqualityOperator) {
|
|
466
|
+
Mat4x4 a = Mat4x4::identity();
|
|
467
|
+
Mat4x4 b = Mat4x4::identity();
|
|
468
|
+
EXPECT_TRUE(a == b);
|
|
469
|
+
EXPECT_FALSE(a != b);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
TEST(Mat4x4, InequalityOperator) {
|
|
473
|
+
Mat4x4 a = Mat4x4::identity();
|
|
474
|
+
Mat4x4 b = Mat4x4::scale(2.0f);
|
|
475
|
+
EXPECT_FALSE(a == b);
|
|
476
|
+
EXPECT_TRUE(a != b);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// ============================================================================
|
|
480
|
+
// Data Pointer
|
|
481
|
+
// ============================================================================
|
|
482
|
+
|
|
483
|
+
TEST(Mat4x4, DataPointerAccess) {
|
|
484
|
+
Mat4x4 m = Mat4x4::identity();
|
|
485
|
+
const float* p = m.ptr();
|
|
486
|
+
// Column-major: first column is [1,0,0,0]
|
|
487
|
+
EXPECT_FLOAT_EQ(p[0], 1.0f);
|
|
488
|
+
EXPECT_FLOAT_EQ(p[1], 0.0f);
|
|
489
|
+
EXPECT_FLOAT_EQ(p[2], 0.0f);
|
|
490
|
+
EXPECT_FLOAT_EQ(p[3], 0.0f);
|
|
491
|
+
// Second column starts at index 4: [0,1,0,0]
|
|
492
|
+
EXPECT_FLOAT_EQ(p[4], 0.0f);
|
|
493
|
+
EXPECT_FLOAT_EQ(p[5], 1.0f);
|
|
494
|
+
}
|