jolt-ruby 1.0.0

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.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/.gitmodules +3 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +228 -0
  5. data/Rakefile +180 -0
  6. data/examples/character_virtual.rb +35 -0
  7. data/examples/falling_sphere.rb +25 -0
  8. data/examples/stagecraft_binding.rb +29 -0
  9. data/ext/jolt_ruby/CMakeLists.txt +42 -0
  10. data/ext/jolt_ruby/character_helper.cpp +44 -0
  11. data/ext/jolt_ruby/constraint_helper.cpp +154 -0
  12. data/ext/jolt_ruby/extconf.rb +56 -0
  13. data/ext/jolt_ruby/helper.cpp +225 -0
  14. data/ext/jolt_ruby/helper.h +103 -0
  15. data/ext/joltc/.github/FUNDING.yml +1 -0
  16. data/ext/joltc/.github/workflows/build.yml +298 -0
  17. data/ext/joltc/.gitignore +272 -0
  18. data/ext/joltc/CMakeLists.txt +431 -0
  19. data/ext/joltc/LICENSE +21 -0
  20. data/ext/joltc/README.md +10 -0
  21. data/ext/joltc/build/cmake_vs2026_arm64.bat +3 -0
  22. data/ext/joltc/build/cmake_vs2026_clang.bat +10 -0
  23. data/ext/joltc/build/cmake_vs2026_x64.bat +3 -0
  24. data/ext/joltc/build/cmake_vs2026_x64_double.bat +3 -0
  25. data/ext/joltc/include/joltc.h +3166 -0
  26. data/ext/joltc/samples/01_HelloWorld/main.cpp +170 -0
  27. data/ext/joltc/samples/CMakeLists.txt +35 -0
  28. data/ext/joltc/src/joltc.c +4 -0
  29. data/ext/joltc/src/joltc.cpp +11812 -0
  30. data/ext/joltc/src/joltc_assert.cpp +271 -0
  31. data/ext/joltc/tests/CMakeLists.txt +77 -0
  32. data/ext/joltc/tests/test_character.cpp +149 -0
  33. data/ext/joltc/tests/test_collision.cpp +146 -0
  34. data/ext/joltc/tests/test_constraints.cpp +353 -0
  35. data/ext/joltc/tests/test_core.cpp +94 -0
  36. data/ext/joltc/tests/test_math.cpp +585 -0
  37. data/ext/joltc/tests/test_physics_system.cpp +465 -0
  38. data/ext/joltc/tests/test_shapes.cpp +789 -0
  39. data/ext/joltc/tests/test_skeleton.cpp +370 -0
  40. data/ext/joltc/tests/test_vehicle.cpp +319 -0
  41. data/generator/generate.rb +237 -0
  42. data/generator/layout_probe.cpp +489 -0
  43. data/generator/verify_layout.rb +32 -0
  44. data/lib/jolt/body.rb +147 -0
  45. data/lib/jolt/body_collection.rb +115 -0
  46. data/lib/jolt/body_dynamics.rb +93 -0
  47. data/lib/jolt/character_virtual.rb +162 -0
  48. data/lib/jolt/constraint.rb +136 -0
  49. data/lib/jolt/constraint_collection.rb +123 -0
  50. data/lib/jolt/contact_events.rb +19 -0
  51. data/lib/jolt/conversions.rb +76 -0
  52. data/lib/jolt/errors.rb +12 -0
  53. data/lib/jolt/fixed_stepper.rb +37 -0
  54. data/lib/jolt/hit.rb +5 -0
  55. data/lib/jolt/layers.rb +182 -0
  56. data/lib/jolt/native/character_functions.rb +16 -0
  57. data/lib/jolt/native/constraint_functions.rb +27 -0
  58. data/lib/jolt/native/core_functions.rb +17 -0
  59. data/lib/jolt/native/generated.rb +1995 -0
  60. data/lib/jolt/native/platform.rb +51 -0
  61. data/lib/jolt/native/query_functions.rb +43 -0
  62. data/lib/jolt/native/types.rb +28 -0
  63. data/lib/jolt/native.rb +94 -0
  64. data/lib/jolt/shape.rb +90 -0
  65. data/lib/jolt/shape_builders.rb +155 -0
  66. data/lib/jolt/system.rb +238 -0
  67. data/lib/jolt/system_characters.rb +25 -0
  68. data/lib/jolt/system_constraints.rb +36 -0
  69. data/lib/jolt/system_contacts.rb +57 -0
  70. data/lib/jolt/system_queries.rb +111 -0
  71. data/lib/jolt/transform.rb +5 -0
  72. data/lib/jolt/version.rb +5 -0
  73. data/lib/jolt.rb +83 -0
  74. data/script/smoke_gem.rb +29 -0
  75. data/script/verify_release.rb +36 -0
  76. metadata +147 -0
@@ -0,0 +1,585 @@
1
+ // Copyright (c) Amer Koleci and Contributors.
2
+ // Licensed under the MIT License (MIT). See LICENSE in the repository root for more information.
3
+
4
+ #include <gtest/gtest.h>
5
+ #include "joltc.h"
6
+ #include <cmath>
7
+
8
+ // Helper for float comparison
9
+ constexpr float EPSILON = 1e-5f;
10
+
11
+ class MathTest : public ::testing::Test {
12
+ protected:
13
+ void SetUp() override {}
14
+ void TearDown() override {}
15
+ };
16
+
17
+ // ============================================================================
18
+ // Vec3 Tests
19
+ // ============================================================================
20
+
21
+ TEST_F(MathTest, Vec3_AxisX) {
22
+ JPH_Vec3 result;
23
+ JPH_Vec3_AxisX(&result);
24
+ EXPECT_FLOAT_EQ(result.x, 1.0f);
25
+ EXPECT_FLOAT_EQ(result.y, 0.0f);
26
+ EXPECT_FLOAT_EQ(result.z, 0.0f);
27
+ }
28
+
29
+ TEST_F(MathTest, Vec3_AxisY) {
30
+ JPH_Vec3 result;
31
+ JPH_Vec3_AxisY(&result);
32
+ EXPECT_FLOAT_EQ(result.x, 0.0f);
33
+ EXPECT_FLOAT_EQ(result.y, 1.0f);
34
+ EXPECT_FLOAT_EQ(result.z, 0.0f);
35
+ }
36
+
37
+ TEST_F(MathTest, Vec3_AxisZ) {
38
+ JPH_Vec3 result;
39
+ JPH_Vec3_AxisZ(&result);
40
+ EXPECT_FLOAT_EQ(result.x, 0.0f);
41
+ EXPECT_FLOAT_EQ(result.y, 0.0f);
42
+ EXPECT_FLOAT_EQ(result.z, 1.0f);
43
+ }
44
+
45
+ TEST_F(MathTest, Vec3_Add) {
46
+ JPH_Vec3 v1 = {1.0f, 2.0f, 3.0f};
47
+ JPH_Vec3 v2 = {4.0f, 5.0f, 6.0f};
48
+ JPH_Vec3 result;
49
+ JPH_Vec3_Add(&v1, &v2, &result);
50
+ EXPECT_FLOAT_EQ(result.x, 5.0f);
51
+ EXPECT_FLOAT_EQ(result.y, 7.0f);
52
+ EXPECT_FLOAT_EQ(result.z, 9.0f);
53
+ }
54
+
55
+ TEST_F(MathTest, Vec3_Subtract) {
56
+ JPH_Vec3 v1 = {4.0f, 5.0f, 6.0f};
57
+ JPH_Vec3 v2 = {1.0f, 2.0f, 3.0f};
58
+ JPH_Vec3 result;
59
+ JPH_Vec3_Subtract(&v1, &v2, &result);
60
+ EXPECT_FLOAT_EQ(result.x, 3.0f);
61
+ EXPECT_FLOAT_EQ(result.y, 3.0f);
62
+ EXPECT_FLOAT_EQ(result.z, 3.0f);
63
+ }
64
+
65
+ TEST_F(MathTest, Vec3_Multiply) {
66
+ JPH_Vec3 v1 = {1.0f, 2.0f, 3.0f};
67
+ JPH_Vec3 v2 = {2.0f, 3.0f, 4.0f};
68
+ JPH_Vec3 result;
69
+ JPH_Vec3_Multiply(&v1, &v2, &result);
70
+ EXPECT_FLOAT_EQ(result.x, 2.0f);
71
+ EXPECT_FLOAT_EQ(result.y, 6.0f);
72
+ EXPECT_FLOAT_EQ(result.z, 12.0f);
73
+ }
74
+
75
+ TEST_F(MathTest, Vec3_MultiplyScalar) {
76
+ JPH_Vec3 v = {1.0f, 2.0f, 3.0f};
77
+ JPH_Vec3 result;
78
+ JPH_Vec3_MultiplyScalar(&v, 2.0f, &result);
79
+ EXPECT_FLOAT_EQ(result.x, 2.0f);
80
+ EXPECT_FLOAT_EQ(result.y, 4.0f);
81
+ EXPECT_FLOAT_EQ(result.z, 6.0f);
82
+ }
83
+
84
+ TEST_F(MathTest, Vec3_Divide) {
85
+ JPH_Vec3 v1 = {4.0f, 6.0f, 8.0f};
86
+ JPH_Vec3 v2 = {2.0f, 3.0f, 4.0f};
87
+ JPH_Vec3 result;
88
+ JPH_Vec3_Divide(&v1, &v2, &result);
89
+ EXPECT_FLOAT_EQ(result.x, 2.0f);
90
+ EXPECT_FLOAT_EQ(result.y, 2.0f);
91
+ EXPECT_FLOAT_EQ(result.z, 2.0f);
92
+ }
93
+
94
+ TEST_F(MathTest, Vec3_DivideScalar) {
95
+ JPH_Vec3 v = {2.0f, 4.0f, 6.0f};
96
+ JPH_Vec3 result;
97
+ JPH_Vec3_DivideScalar(&v, 2.0f, &result);
98
+ EXPECT_FLOAT_EQ(result.x, 1.0f);
99
+ EXPECT_FLOAT_EQ(result.y, 2.0f);
100
+ EXPECT_FLOAT_EQ(result.z, 3.0f);
101
+ }
102
+
103
+ TEST_F(MathTest, Vec3_Length) {
104
+ JPH_Vec3 v = {3.0f, 4.0f, 0.0f};
105
+ float length = JPH_Vec3_Length(&v);
106
+ EXPECT_FLOAT_EQ(length, 5.0f);
107
+ }
108
+
109
+ TEST_F(MathTest, Vec3_LengthSquared) {
110
+ JPH_Vec3 v = {3.0f, 4.0f, 0.0f};
111
+ float lengthSq = JPH_Vec3_LengthSquared(&v);
112
+ EXPECT_FLOAT_EQ(lengthSq, 25.0f);
113
+ }
114
+
115
+ TEST_F(MathTest, Vec3_Normalized) {
116
+ JPH_Vec3 v = {3.0f, 0.0f, 0.0f};
117
+ JPH_Vec3 result;
118
+ JPH_Vec3_Normalized(&v, &result);
119
+ EXPECT_FLOAT_EQ(result.x, 1.0f);
120
+ EXPECT_FLOAT_EQ(result.y, 0.0f);
121
+ EXPECT_FLOAT_EQ(result.z, 0.0f);
122
+ }
123
+
124
+ TEST_F(MathTest, Vec3_DotProduct) {
125
+ JPH_Vec3 v1 = {1.0f, 2.0f, 3.0f};
126
+ JPH_Vec3 v2 = {4.0f, 5.0f, 6.0f};
127
+ float result;
128
+ JPH_Vec3_DotProduct(&v1, &v2, &result);
129
+ EXPECT_FLOAT_EQ(result, 32.0f); // 1*4 + 2*5 + 3*6 = 32
130
+ }
131
+
132
+ TEST_F(MathTest, Vec3_Cross) {
133
+ JPH_Vec3 v1 = {1.0f, 0.0f, 0.0f};
134
+ JPH_Vec3 v2 = {0.0f, 1.0f, 0.0f};
135
+ JPH_Vec3 result;
136
+ JPH_Vec3_Cross(&v1, &v2, &result);
137
+ EXPECT_FLOAT_EQ(result.x, 0.0f);
138
+ EXPECT_FLOAT_EQ(result.y, 0.0f);
139
+ EXPECT_FLOAT_EQ(result.z, 1.0f);
140
+ }
141
+
142
+ TEST_F(MathTest, Vec3_Negate) {
143
+ JPH_Vec3 v = {1.0f, -2.0f, 3.0f};
144
+ JPH_Vec3 result;
145
+ JPH_Vec3_Negate(&v, &result);
146
+ EXPECT_FLOAT_EQ(result.x, -1.0f);
147
+ EXPECT_FLOAT_EQ(result.y, 2.0f);
148
+ EXPECT_FLOAT_EQ(result.z, -3.0f);
149
+ }
150
+
151
+ TEST_F(MathTest, Vec3_Abs) {
152
+ JPH_Vec3 v = {-1.0f, -2.0f, 3.0f};
153
+ JPH_Vec3 result;
154
+ JPH_Vec3_Abs(&v, &result);
155
+ EXPECT_FLOAT_EQ(result.x, 1.0f);
156
+ EXPECT_FLOAT_EQ(result.y, 2.0f);
157
+ EXPECT_FLOAT_EQ(result.z, 3.0f);
158
+ }
159
+
160
+ TEST_F(MathTest, Vec3_IsClose) {
161
+ JPH_Vec3 v1 = {1.0f, 2.0f, 3.0f};
162
+ JPH_Vec3 v2 = {1.0f, 2.0f, 3.0f};
163
+ EXPECT_TRUE(JPH_Vec3_IsClose(&v1, &v2, 0.001f));
164
+
165
+ JPH_Vec3 v3 = {1.1f, 2.1f, 3.1f};
166
+ EXPECT_FALSE(JPH_Vec3_IsClose(&v1, &v3, 0.001f));
167
+ }
168
+
169
+ TEST_F(MathTest, Vec3_IsNearZero) {
170
+ JPH_Vec3 v1 = {0.0f, 0.0f, 0.0f};
171
+ EXPECT_TRUE(JPH_Vec3_IsNearZero(&v1, 0.001f));
172
+
173
+ JPH_Vec3 v2 = {0.0001f, 0.0001f, 0.0001f};
174
+ EXPECT_TRUE(JPH_Vec3_IsNearZero(&v2, 0.001f));
175
+
176
+ JPH_Vec3 v3 = {1.0f, 0.0f, 0.0f};
177
+ EXPECT_FALSE(JPH_Vec3_IsNearZero(&v3, 0.001f));
178
+ }
179
+
180
+ TEST_F(MathTest, Vec3_IsNormalized) {
181
+ JPH_Vec3 v1 = {1.0f, 0.0f, 0.0f};
182
+ EXPECT_TRUE(JPH_Vec3_IsNormalized(&v1, 0.001f));
183
+
184
+ JPH_Vec3 v2 = {2.0f, 0.0f, 0.0f};
185
+ EXPECT_FALSE(JPH_Vec3_IsNormalized(&v2, 0.001f));
186
+ }
187
+
188
+ TEST_F(MathTest, Vec3_IsNaN) {
189
+ JPH_Vec3 v1 = {1.0f, 2.0f, 3.0f};
190
+ EXPECT_FALSE(JPH_Vec3_IsNaN(&v1));
191
+
192
+ JPH_Vec3 v2 = {std::nanf(""), 2.0f, 3.0f};
193
+ EXPECT_TRUE(JPH_Vec3_IsNaN(&v2));
194
+ }
195
+
196
+ // ============================================================================
197
+ // Quaternion Tests
198
+ // ============================================================================
199
+
200
+ TEST_F(MathTest, Quat_FromEulerAngles) {
201
+ JPH_Vec3 angles = {0.0f, 0.0f, 0.0f}; // No rotation
202
+ JPH_Quat result;
203
+ JPH_Quat_FromEulerAngles(&angles, &result);
204
+
205
+ // Identity quaternion
206
+ EXPECT_NEAR(result.x, 0.0f, EPSILON);
207
+ EXPECT_NEAR(result.y, 0.0f, EPSILON);
208
+ EXPECT_NEAR(result.z, 0.0f, EPSILON);
209
+ EXPECT_NEAR(result.w, 1.0f, EPSILON);
210
+ }
211
+
212
+ TEST_F(MathTest, Quat_GetEulerAngles) {
213
+ // Identity quaternion
214
+ JPH_Quat q = {0.0f, 0.0f, 0.0f, 1.0f};
215
+ JPH_Vec3 result;
216
+ JPH_Quat_GetEulerAngles(&q, &result);
217
+
218
+ EXPECT_NEAR(result.x, 0.0f, EPSILON);
219
+ EXPECT_NEAR(result.y, 0.0f, EPSILON);
220
+ EXPECT_NEAR(result.z, 0.0f, EPSILON);
221
+ }
222
+
223
+ TEST_F(MathTest, Quat_Multiply) {
224
+ // Identity * Identity = Identity
225
+ JPH_Quat q1 = {0.0f, 0.0f, 0.0f, 1.0f};
226
+ JPH_Quat q2 = {0.0f, 0.0f, 0.0f, 1.0f};
227
+ JPH_Quat result;
228
+ JPH_Quat_Multiply(&q1, &q2, &result);
229
+
230
+ EXPECT_NEAR(result.x, 0.0f, EPSILON);
231
+ EXPECT_NEAR(result.y, 0.0f, EPSILON);
232
+ EXPECT_NEAR(result.z, 0.0f, EPSILON);
233
+ EXPECT_NEAR(result.w, 1.0f, EPSILON);
234
+ }
235
+
236
+ TEST_F(MathTest, Quat_Conjugated) {
237
+ JPH_Quat q = {1.0f, 2.0f, 3.0f, 4.0f};
238
+ JPH_Quat result;
239
+ JPH_Quat_Conjugated(&q, &result);
240
+
241
+ EXPECT_FLOAT_EQ(result.x, -1.0f);
242
+ EXPECT_FLOAT_EQ(result.y, -2.0f);
243
+ EXPECT_FLOAT_EQ(result.z, -3.0f);
244
+ EXPECT_FLOAT_EQ(result.w, 4.0f);
245
+ }
246
+
247
+ TEST_F(MathTest, Quat_Inversed) {
248
+ // For a unit quaternion, inverse equals conjugate
249
+ JPH_Quat q = {0.0f, 0.0f, 0.0f, 1.0f};
250
+ JPH_Quat result;
251
+ JPH_Quat_Inversed(&q, &result);
252
+
253
+ EXPECT_NEAR(result.x, 0.0f, EPSILON);
254
+ EXPECT_NEAR(result.y, 0.0f, EPSILON);
255
+ EXPECT_NEAR(result.z, 0.0f, EPSILON);
256
+ EXPECT_NEAR(result.w, 1.0f, EPSILON);
257
+ }
258
+
259
+ TEST_F(MathTest, Quat_Dot) {
260
+ JPH_Quat q1 = {1.0f, 0.0f, 0.0f, 0.0f};
261
+ JPH_Quat q2 = {1.0f, 0.0f, 0.0f, 0.0f};
262
+ float result;
263
+ JPH_Quat_Dot(&q1, &q2, &result);
264
+ EXPECT_FLOAT_EQ(result, 1.0f);
265
+ }
266
+
267
+ TEST_F(MathTest, Quat_Add) {
268
+ JPH_Quat q1 = {1.0f, 2.0f, 3.0f, 4.0f};
269
+ JPH_Quat q2 = {5.0f, 6.0f, 7.0f, 8.0f};
270
+ JPH_Quat result;
271
+ JPH_Quat_Add(&q1, &q2, &result);
272
+
273
+ EXPECT_FLOAT_EQ(result.x, 6.0f);
274
+ EXPECT_FLOAT_EQ(result.y, 8.0f);
275
+ EXPECT_FLOAT_EQ(result.z, 10.0f);
276
+ EXPECT_FLOAT_EQ(result.w, 12.0f);
277
+ }
278
+
279
+ TEST_F(MathTest, Quat_Subtract) {
280
+ JPH_Quat q1 = {5.0f, 6.0f, 7.0f, 8.0f};
281
+ JPH_Quat q2 = {1.0f, 2.0f, 3.0f, 4.0f};
282
+ JPH_Quat result;
283
+ JPH_Quat_Subtract(&q1, &q2, &result);
284
+
285
+ EXPECT_FLOAT_EQ(result.x, 4.0f);
286
+ EXPECT_FLOAT_EQ(result.y, 4.0f);
287
+ EXPECT_FLOAT_EQ(result.z, 4.0f);
288
+ EXPECT_FLOAT_EQ(result.w, 4.0f);
289
+ }
290
+
291
+ TEST_F(MathTest, Quat_MultiplyScalar) {
292
+ JPH_Quat q = {1.0f, 2.0f, 3.0f, 4.0f};
293
+ JPH_Quat result;
294
+ JPH_Quat_MultiplyScalar(&q, 2.0f, &result);
295
+
296
+ EXPECT_FLOAT_EQ(result.x, 2.0f);
297
+ EXPECT_FLOAT_EQ(result.y, 4.0f);
298
+ EXPECT_FLOAT_EQ(result.z, 6.0f);
299
+ EXPECT_FLOAT_EQ(result.w, 8.0f);
300
+ }
301
+
302
+ TEST_F(MathTest, Quat_Rotate) {
303
+ // 90 degree rotation around Y axis
304
+ float angle = JPH_M_PI / 2.0f;
305
+ JPH_Quat q = {0.0f, sinf(angle / 2.0f), 0.0f, cosf(angle / 2.0f)};
306
+ JPH_Vec3 v = {1.0f, 0.0f, 0.0f}; // X axis
307
+ JPH_Vec3 result;
308
+ JPH_Quat_Rotate(&q, &v, &result);
309
+
310
+ // X axis rotated 90 degrees around Y should give -Z axis
311
+ EXPECT_NEAR(result.x, 0.0f, EPSILON);
312
+ EXPECT_NEAR(result.y, 0.0f, EPSILON);
313
+ EXPECT_NEAR(result.z, -1.0f, EPSILON);
314
+ }
315
+
316
+ TEST_F(MathTest, Quat_Slerp) {
317
+ JPH_Quat q1 = {0.0f, 0.0f, 0.0f, 1.0f}; // Identity
318
+ JPH_Quat q2 = {0.0f, 0.0f, 0.0f, 1.0f}; // Identity
319
+ JPH_Quat result;
320
+ JPH_Quat_Slerp(&q1, &q2, 0.5f, &result);
321
+
322
+ // Slerp between two identity quaternions should give identity
323
+ EXPECT_NEAR(result.x, 0.0f, EPSILON);
324
+ EXPECT_NEAR(result.y, 0.0f, EPSILON);
325
+ EXPECT_NEAR(result.z, 0.0f, EPSILON);
326
+ EXPECT_NEAR(result.w, 1.0f, EPSILON);
327
+ }
328
+
329
+ TEST_F(MathTest, Quat_Lerp) {
330
+ JPH_Quat q1 = {0.0f, 0.0f, 0.0f, 1.0f}; // Identity
331
+ JPH_Quat q2 = {0.0f, 0.0f, 0.0f, 1.0f}; // Identity
332
+ JPH_Quat result;
333
+ JPH_Quat_Lerp(&q1, &q2, 0.5f, &result);
334
+
335
+ EXPECT_NEAR(result.x, 0.0f, EPSILON);
336
+ EXPECT_NEAR(result.y, 0.0f, EPSILON);
337
+ EXPECT_NEAR(result.z, 0.0f, EPSILON);
338
+ EXPECT_NEAR(result.w, 1.0f, EPSILON);
339
+ }
340
+
341
+ // ============================================================================
342
+ // Mat4 Tests
343
+ // ============================================================================
344
+
345
+ TEST_F(MathTest, Mat4_Identity) {
346
+ JPH_Mat4 result;
347
+ JPH_Mat4_Identity(&result);
348
+
349
+ // Check diagonal is 1
350
+ EXPECT_FLOAT_EQ(result.column[0].x, 1.0f);
351
+ EXPECT_FLOAT_EQ(result.column[1].y, 1.0f);
352
+ EXPECT_FLOAT_EQ(result.column[2].z, 1.0f);
353
+ EXPECT_FLOAT_EQ(result.column[3].w, 1.0f);
354
+
355
+ // Check off-diagonal is 0
356
+ EXPECT_FLOAT_EQ(result.column[0].y, 0.0f);
357
+ EXPECT_FLOAT_EQ(result.column[0].z, 0.0f);
358
+ EXPECT_FLOAT_EQ(result.column[0].w, 0.0f);
359
+ }
360
+
361
+ TEST_F(MathTest, Mat4_Zero) {
362
+ JPH_Mat4 result;
363
+ JPH_Mat4_Zero(&result);
364
+
365
+ for (int i = 0; i < 4; i++) {
366
+ EXPECT_FLOAT_EQ(result.column[i].x, 0.0f);
367
+ EXPECT_FLOAT_EQ(result.column[i].y, 0.0f);
368
+ EXPECT_FLOAT_EQ(result.column[i].z, 0.0f);
369
+ EXPECT_FLOAT_EQ(result.column[i].w, 0.0f);
370
+ }
371
+ }
372
+
373
+ TEST_F(MathTest, Mat4_Translation) {
374
+ JPH_Vec3 translation = {1.0f, 2.0f, 3.0f};
375
+ JPH_Mat4 result;
376
+ JPH_Mat4_Translation(&result, &translation);
377
+
378
+ // Check translation column
379
+ EXPECT_FLOAT_EQ(result.column[3].x, 1.0f);
380
+ EXPECT_FLOAT_EQ(result.column[3].y, 2.0f);
381
+ EXPECT_FLOAT_EQ(result.column[3].z, 3.0f);
382
+ EXPECT_FLOAT_EQ(result.column[3].w, 1.0f);
383
+
384
+ // Check identity part
385
+ EXPECT_FLOAT_EQ(result.column[0].x, 1.0f);
386
+ EXPECT_FLOAT_EQ(result.column[1].y, 1.0f);
387
+ EXPECT_FLOAT_EQ(result.column[2].z, 1.0f);
388
+ }
389
+
390
+ TEST_F(MathTest, Mat4_Scale) {
391
+ JPH_Vec3 scale = {2.0f, 3.0f, 4.0f};
392
+ JPH_Mat4 result;
393
+ JPH_Mat4_Scale(&result, &scale);
394
+
395
+ EXPECT_FLOAT_EQ(result.column[0].x, 2.0f);
396
+ EXPECT_FLOAT_EQ(result.column[1].y, 3.0f);
397
+ EXPECT_FLOAT_EQ(result.column[2].z, 4.0f);
398
+ EXPECT_FLOAT_EQ(result.column[3].w, 1.0f);
399
+ }
400
+
401
+ TEST_F(MathTest, Mat4_Rotation) {
402
+ JPH_Quat q = {0.0f, 0.0f, 0.0f, 1.0f}; // Identity rotation
403
+ JPH_Mat4 result;
404
+ JPH_Mat4_Rotation(&result, &q);
405
+
406
+ // Should be identity matrix
407
+ EXPECT_NEAR(result.column[0].x, 1.0f, EPSILON);
408
+ EXPECT_NEAR(result.column[1].y, 1.0f, EPSILON);
409
+ EXPECT_NEAR(result.column[2].z, 1.0f, EPSILON);
410
+ EXPECT_NEAR(result.column[3].w, 1.0f, EPSILON);
411
+ }
412
+
413
+ TEST_F(MathTest, Mat4_Multiply) {
414
+ JPH_Mat4 m1, m2, result;
415
+ JPH_Mat4_Identity(&m1);
416
+ JPH_Mat4_Identity(&m2);
417
+ JPH_Mat4_Multiply(&m1, &m2, &result);
418
+
419
+ // Identity * Identity = Identity
420
+ EXPECT_NEAR(result.column[0].x, 1.0f, EPSILON);
421
+ EXPECT_NEAR(result.column[1].y, 1.0f, EPSILON);
422
+ EXPECT_NEAR(result.column[2].z, 1.0f, EPSILON);
423
+ EXPECT_NEAR(result.column[3].w, 1.0f, EPSILON);
424
+ }
425
+
426
+ TEST_F(MathTest, Mat4_Add) {
427
+ JPH_Mat4 m1, m2, result;
428
+ JPH_Mat4_Identity(&m1);
429
+ JPH_Mat4_Identity(&m2);
430
+ JPH_Mat4_Add(&m1, &m2, &result);
431
+
432
+ // Identity + Identity should have 2 on diagonal
433
+ EXPECT_FLOAT_EQ(result.column[0].x, 2.0f);
434
+ EXPECT_FLOAT_EQ(result.column[1].y, 2.0f);
435
+ EXPECT_FLOAT_EQ(result.column[2].z, 2.0f);
436
+ EXPECT_FLOAT_EQ(result.column[3].w, 2.0f);
437
+ }
438
+
439
+ TEST_F(MathTest, Mat4_Subtract) {
440
+ JPH_Mat4 m1, m2, result;
441
+ JPH_Mat4_Identity(&m1);
442
+ JPH_Mat4_Identity(&m2);
443
+ JPH_Mat4_Subtract(&m1, &m2, &result);
444
+
445
+ // Identity - Identity = Zero
446
+ for (int i = 0; i < 4; i++) {
447
+ EXPECT_FLOAT_EQ(result.column[i].x, 0.0f);
448
+ EXPECT_FLOAT_EQ(result.column[i].y, 0.0f);
449
+ EXPECT_FLOAT_EQ(result.column[i].z, 0.0f);
450
+ EXPECT_FLOAT_EQ(result.column[i].w, 0.0f);
451
+ }
452
+ }
453
+
454
+ TEST_F(MathTest, Mat4_MultiplyScalar) {
455
+ JPH_Mat4 m, result;
456
+ JPH_Mat4_Identity(&m);
457
+ JPH_Mat4_MultiplyScalar(&m, 2.0f, &result);
458
+
459
+ EXPECT_FLOAT_EQ(result.column[0].x, 2.0f);
460
+ EXPECT_FLOAT_EQ(result.column[1].y, 2.0f);
461
+ EXPECT_FLOAT_EQ(result.column[2].z, 2.0f);
462
+ EXPECT_FLOAT_EQ(result.column[3].w, 2.0f);
463
+ }
464
+
465
+ TEST_F(MathTest, Mat4_Transposed) {
466
+ JPH_Mat4 m, result;
467
+ JPH_Mat4_Identity(&m);
468
+ m.column[0].y = 1.0f; // Set [1,0] to 1
469
+
470
+ JPH_Mat4_Transposed(&m, &result);
471
+
472
+ // After transpose, [0,1] should be 1
473
+ EXPECT_FLOAT_EQ(result.column[1].x, 1.0f);
474
+ }
475
+
476
+ TEST_F(MathTest, Mat4_GetAxisX) {
477
+ JPH_Mat4 m;
478
+ JPH_Mat4_Identity(&m);
479
+ JPH_Vec3 result;
480
+ JPH_Mat4_GetAxisX(&m, &result);
481
+
482
+ EXPECT_FLOAT_EQ(result.x, 1.0f);
483
+ EXPECT_FLOAT_EQ(result.y, 0.0f);
484
+ EXPECT_FLOAT_EQ(result.z, 0.0f);
485
+ }
486
+
487
+ TEST_F(MathTest, Mat4_GetAxisY) {
488
+ JPH_Mat4 m;
489
+ JPH_Mat4_Identity(&m);
490
+ JPH_Vec3 result;
491
+ JPH_Mat4_GetAxisY(&m, &result);
492
+
493
+ EXPECT_FLOAT_EQ(result.x, 0.0f);
494
+ EXPECT_FLOAT_EQ(result.y, 1.0f);
495
+ EXPECT_FLOAT_EQ(result.z, 0.0f);
496
+ }
497
+
498
+ TEST_F(MathTest, Mat4_GetAxisZ) {
499
+ JPH_Mat4 m;
500
+ JPH_Mat4_Identity(&m);
501
+ JPH_Vec3 result;
502
+ JPH_Mat4_GetAxisZ(&m, &result);
503
+
504
+ EXPECT_FLOAT_EQ(result.x, 0.0f);
505
+ EXPECT_FLOAT_EQ(result.y, 0.0f);
506
+ EXPECT_FLOAT_EQ(result.z, 1.0f);
507
+ }
508
+
509
+ TEST_F(MathTest, Mat4_GetTranslation) {
510
+ JPH_Vec3 translation = {1.0f, 2.0f, 3.0f};
511
+ JPH_Mat4 m;
512
+ JPH_Mat4_Translation(&m, &translation);
513
+
514
+ JPH_Vec3 result;
515
+ JPH_Mat4_GetTranslation(&m, &result);
516
+
517
+ EXPECT_FLOAT_EQ(result.x, 1.0f);
518
+ EXPECT_FLOAT_EQ(result.y, 2.0f);
519
+ EXPECT_FLOAT_EQ(result.z, 3.0f);
520
+ }
521
+
522
+ TEST_F(MathTest, Mat4_GetQuaternion) {
523
+ JPH_Quat q = {0.0f, 0.0f, 0.0f, 1.0f}; // Identity
524
+ JPH_Mat4 m;
525
+ JPH_Mat4_Rotation(&m, &q);
526
+
527
+ JPH_Quat result;
528
+ JPH_Mat4_GetQuaternion(&m, &result);
529
+
530
+ // Should get back identity quaternion (or its negative)
531
+ EXPECT_NEAR(std::abs(result.w), 1.0f, EPSILON);
532
+ }
533
+
534
+ TEST_F(MathTest, Mat4_RotationTranslation) {
535
+ JPH_Quat q = {0.0f, 0.0f, 0.0f, 1.0f}; // Identity rotation
536
+ JPH_Vec3 translation = {1.0f, 2.0f, 3.0f};
537
+ JPH_Mat4 result;
538
+ JPH_Mat4_RotationTranslation(&result, &q, &translation);
539
+
540
+ // Check translation
541
+ EXPECT_FLOAT_EQ(result.column[3].x, 1.0f);
542
+ EXPECT_FLOAT_EQ(result.column[3].y, 2.0f);
543
+ EXPECT_FLOAT_EQ(result.column[3].z, 3.0f);
544
+
545
+ // Check rotation part is identity
546
+ EXPECT_NEAR(result.column[0].x, 1.0f, EPSILON);
547
+ EXPECT_NEAR(result.column[1].y, 1.0f, EPSILON);
548
+ EXPECT_NEAR(result.column[2].z, 1.0f, EPSILON);
549
+ }
550
+
551
+ // ============================================================================
552
+ // Math Utility Tests
553
+ // ============================================================================
554
+
555
+ TEST_F(MathTest, Math_Sin) {
556
+ float result = JPH_Math_Sin(0.0f);
557
+ EXPECT_NEAR(result, 0.0f, EPSILON);
558
+
559
+ result = JPH_Math_Sin(JPH_M_PI / 2.0f);
560
+ EXPECT_NEAR(result, 1.0f, EPSILON);
561
+ }
562
+
563
+ TEST_F(MathTest, Math_Cos) {
564
+ float result = JPH_Math_Cos(0.0f);
565
+ EXPECT_NEAR(result, 1.0f, EPSILON);
566
+
567
+ result = JPH_Math_Cos(JPH_M_PI);
568
+ EXPECT_NEAR(result, -1.0f, EPSILON);
569
+ }
570
+
571
+ // ============================================================================
572
+ // RayCast Helper Tests
573
+ // ============================================================================
574
+
575
+ TEST_F(MathTest, RayCast_GetPointOnRay) {
576
+ JPH_Vec3 origin = {0.0f, 0.0f, 0.0f};
577
+ JPH_Vec3 direction = {1.0f, 0.0f, 0.0f};
578
+ JPH_Vec3 result;
579
+
580
+ JPH_RayCast_GetPointOnRay(&origin, &direction, 0.5f, &result);
581
+
582
+ EXPECT_FLOAT_EQ(result.x, 0.5f);
583
+ EXPECT_FLOAT_EQ(result.y, 0.0f);
584
+ EXPECT_FLOAT_EQ(result.z, 0.0f);
585
+ }