1#![allow(clippy::needless_range_loop)]
23
24use crate::math::{Mat3I16, Vec3I16};
25use psx_math::SIN_TABLE;
26
27#[inline]
33pub const fn sin_1_3_12(angle: u16) -> i16 {
34 SIN_TABLE[(angle & 0xFF) as usize]
35}
36
37#[inline]
39pub const fn cos_1_3_12(angle: u16) -> i16 {
40 sin_1_3_12(angle.wrapping_add(64))
42}
43
44impl Mat3I16 {
45 pub fn mul(&self, other: &Self) -> Self {
50 let mut out = [[0i16; 3]; 3];
51 for i in 0..3 {
52 for j in 0..3 {
53 let column = [other.m[0][j], other.m[1][j], other.m[2][j]];
54 out[i][j] = clamp_i32_to_i16(dot_q12_i16(self.m[i], column));
55 }
56 }
57 Self { m: out }
58 }
59
60 #[inline]
66 pub fn rotate_xyz(x: u16, y: u16, z: u16) -> Self {
67 Self::rotate_x(x)
68 .mul(&Self::rotate_y(y))
69 .mul(&Self::rotate_z(z))
70 }
71
72 pub fn scale_rows_q12(&self, scale: [i32; 3]) -> Self {
78 let mut out = self.m;
79 for row in 0..3 {
80 for col in 0..3 {
81 let value = ((self.m[row][col] as i64 * scale[row] as i64) >> 12)
82 .clamp(i16::MIN as i64, i16::MAX as i64);
83 out[row][col] = value as i16;
84 }
85 }
86 Self { m: out }
87 }
88
89 pub fn scale_columns_q12(&self, scale: [i32; 3]) -> Self {
94 let mut out = self.m;
95 for row in 0..3 {
96 for col in 0..3 {
97 let value = ((self.m[row][col] as i64 * scale[col] as i64) >> 12)
98 .clamp(i16::MIN as i64, i16::MAX as i64);
99 out[row][col] = value as i16;
100 }
101 }
102 Self { m: out }
103 }
104
105 pub fn transform(&self, v: Vec3I16) -> [i32; 3] {
110 let mut out = [0i32; 3];
111 for i in 0..3 {
112 out[i] = dot_q12_i16(self.m[i], [v.x, v.y, v.z]);
113 }
114 out
115 }
116
117 pub fn transform_i32(&self, v: [i32; 3]) -> [i32; 3] {
121 self.m.map(|r| {
122 i32::from(r[0])
123 .wrapping_mul(v[0])
124 .wrapping_add(i32::from(r[1]).wrapping_mul(v[1]))
125 .wrapping_add(i32::from(r[2]).wrapping_mul(v[2]))
126 >> 12
127 })
128 }
129
130 pub fn rotate_x_q12(angle: u16) -> Self {
132 let c = psx_math::cos_q12(angle) as i16;
133 let s = psx_math::sin_q12(angle) as i16;
134 Self {
135 m: [[4096, 0, 0], [0, c, -s], [0, s, c]],
136 }
137 }
138
139 pub fn rotate_y_q12(angle: u16) -> Self {
141 let c = psx_math::cos_q12(angle) as i16;
142 let s = psx_math::sin_q12(angle) as i16;
143 Self {
144 m: [[c, 0, s], [0, 4096, 0], [-s, 0, c]],
145 }
146 }
147
148 pub const fn rotate_x(angle: u16) -> Self {
156 let c = cos_1_3_12(angle);
157 let s = sin_1_3_12(angle);
158 Self {
159 m: [[0x1000, 0, 0], [0, c, -s], [0, s, c]],
160 }
161 }
162
163 pub const fn rotate_y(angle: u16) -> Self {
171 let c = cos_1_3_12(angle);
172 let s = sin_1_3_12(angle);
173 Self {
174 m: [[c, 0, s], [0, 0x1000, 0], [-s, 0, c]],
175 }
176 }
177
178 pub const fn rotate_z(angle: u16) -> Self {
186 let c = cos_1_3_12(angle);
187 let s = sin_1_3_12(angle);
188 Self {
189 m: [[c, -s, 0], [s, c, 0], [0, 0, 0x1000]],
190 }
191 }
192}
193
194#[inline]
195fn dot_q12_i16(a: [i16; 3], b: [i16; 3]) -> i32 {
196 let mut sum = 0i32;
197 let mut i = 0;
198 while i < 3 {
199 sum = sum.saturating_add((a[i] as i32) * (b[i] as i32));
200 i += 1;
201 }
202 sum >> 12
203}
204
205#[inline]
206fn clamp_i32_to_i16(value: i32) -> i16 {
207 value.clamp(i16::MIN as i32, i16::MAX as i32) as i16
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn interpolated_rotations_preserve_full_angle_resolution() {
216 for a in 0..4096 {
217 let c = psx_math::cos_q12(a) as i16;
218 let sn = psx_math::sin_q12(a) as i16;
219 assert_eq!(
220 Mat3I16::rotate_x_q12(a).m,
221 [[4096, 0, 0], [0, c, -sn], [0, sn, c]]
222 );
223 assert_eq!(
224 Mat3I16::rotate_y_q12(a).m,
225 [[c, 0, sn], [0, 4096, 0], [-sn, 0, c]]
226 );
227 let m = Mat3I16::rotate_y_q12(a);
228 let v = [12345, -34567, 8901];
229 let expected = m.m.map(|r| {
230 (i32::from(r[0]) * v[0] + i32::from(r[1]) * v[1] + i32::from(r[2]) * v[2]) >> 12
231 });
232 assert_eq!(m.transform_i32(v), expected);
233 }
234 }
235
236 #[test]
237 fn sin_cos_identity_at_cardinal_angles() {
238 assert_eq!(sin_1_3_12(0), 0);
240 assert_eq!(cos_1_3_12(0), 0x1000);
241 assert_eq!(sin_1_3_12(64), 0x1000);
242 assert_eq!(cos_1_3_12(64), 0);
243 assert_eq!(sin_1_3_12(128), 0);
244 assert_eq!(cos_1_3_12(128), -0x1000);
245 assert_eq!(sin_1_3_12(192), -0x1000);
246 assert_eq!(cos_1_3_12(192), 0);
247 }
248
249 #[test]
250 fn scene_rtps_matches_captured_gameplay_outputs() {
251 fn rtps(vxy0: u32, vz0: u32) -> (u32, u32) {
259 let mut g = crate::Gte::new();
260 g.write_control(0, 0x0000_0f19); g.write_control(1, 0x016e_fab4); g.write_control(2, 0x0411_f098); g.write_control(3, 0xfbb1_fae7); g.write_control(4, 0xffff_f177); g.write_control(24, 0x00a0_0000); g.write_control(25, 0x0078_0000); g.write_control(26, 0x0000_0140); g.write_data(0, vxy0); g.write_data(1, vz0); g.execute(0x4a08_0001); (g.read_data(14), g.read_control(31)) }
273 assert_eq!(rtps(0x0c3e_0000, 0x0000_0a4d), (0xfc00_fc00, 0x8006_6000));
274 assert_eq!(rtps(0x0c3e_0529, 0x0000_08eb).0, 0xfc00_03ff);
275 assert_eq!(rtps(0x0c3e_0526, 0xffff_f714), (0xfc00_03b4, 0x8000_2000));
276 assert_eq!(rtps(0x099c_f335, 0x0000_0000), (0xfc00_fc00, 0x8000_6000));
277 }
278
279 #[test]
280 fn scene_ops_and_corners_match_psx_gte_core() {
281 fn tri(a: u32, b: u32, c: u32) -> u32 {
286 a ^ b.rotate_left(11) ^ c.rotate_left(22)
287 }
288 fn seed_xform(g: &mut crate::Gte) {
289 g.write_control(31, 0);
290 g.write_control(0, 0x0000_0f19);
291 g.write_control(1, 0x016e_fab4);
292 g.write_control(2, 0x0411_f098);
293 g.write_control(3, 0xfbb1_fae7);
294 g.write_control(4, 0xffff_f177);
295 g.write_control(5, 0xffff_eabc);
296 g.write_control(6, 0xffff_fdb9);
297 g.write_control(7, 0x0000_35be);
298 g.write_control(24, 0x00a0_0000);
299 g.write_control(25, 0x0078_0000);
300 g.write_control(26, 0x0000_0140);
301 g.write_control(27, 0);
302 g.write_control(28, 0);
303 }
304
305 let mvmva = |vxy0: u32, vz0: u32| {
307 let mut g = crate::Gte::new();
308 seed_xform(&mut g);
309 g.write_data(0, vxy0);
310 g.write_data(1, vz0);
311 g.execute(0x4a08_0012);
312 tri(g.read_data(25), g.read_data(26), g.read_data(27))
313 };
314 assert_eq!(mvmva(0x2040_0340, 0x0000_09c0), 0xca74_6d65);
315 assert_eq!(mvmva(0x2040_0340, 0x0000_16c0), 0xd65a_09bf);
316 assert_eq!(mvmva(0x0b00_0340, 0x0000_23c0), 0x511b_ee0c);
317 assert_eq!(mvmva(0x2040_09c0, 0x0000_09c0), 0x46ef_d743);
318
319 let rtpt = |v: [u32; 6]| {
321 let mut g = crate::Gte::new();
322 seed_xform(&mut g);
323 for (i, &w) in v.iter().enumerate() {
324 g.write_data(i as u8, w);
325 }
326 g.execute(0x4a08_0030);
327 (
328 tri(g.read_data(12), g.read_data(13), g.read_data(14)),
329 g.read_control(31),
330 g.read_data(19),
331 )
332 };
333 assert_eq!(
334 rtpt([
335 0x0480_2d80,
336 0x0000_2700,
337 0x0480_2080,
338 0x0000_2d80,
339 0x0480_1a00,
340 0x0000_2d80
341 ]),
342 (0xfc1f_e61f, 0x8000_6000, 0x0000_02e9)
343 );
344 assert_eq!(
345 rtpt([
346 0x0480_2700,
347 0x0000_2d80,
348 0x0480_2d80,
349 0x0000_2d80,
350 0x0480_2080,
351 0x0000_3400
352 ]),
353 (0xfbe0_066f, 0x8006_6000, 0x0000_0000)
354 );
355 assert_eq!(
356 rtpt([
357 0x10c0_0000,
358 0x0000_0d00,
359 0x10c0_0680,
360 0x0000_0d00,
361 0x1dc0_0680,
362 0x0000_0d00
363 ]),
364 (0xaf36_5a05, 0x0000_0000, 0x0000_1fd9)
365 );
366
367 let nclip = |s0: u32, s1: u32, s2: u32| {
369 let mut g = crate::Gte::new();
370 g.write_control(31, 0);
371 g.write_data(12, s0);
372 g.write_data(13, s1);
373 g.write_data(14, s2);
374 g.execute(0x4a00_0006);
375 g.read_data(24)
376 };
377 assert_eq!(nclip(0x006e_0095, 0xffe2_0094, 0xffde_00dc), 0x0000_2764);
378 assert_eq!(nclip(0x0073_00d5, 0xffde_00dc, 0xffd8_0130), 0x0000_30ba);
379 assert_eq!(nclip(0x0079_011f, 0xffd8_0130, 0xffd2_0194), 0x0000_3e7e);
380
381 let lzcr = |v: u32| {
383 let mut g = crate::Gte::new();
384 g.write_data(30, v);
385 g.read_data(31)
386 };
387 assert_eq!(lzcr(0x00ff_ffff), 8);
388 assert_eq!(lzcr(0xffff_0000), 16);
389 assert_eq!(lzcr(0x0000_0001), 31);
390 assert_eq!(lzcr(0x7fff_ffff), 1);
391 assert_eq!(lzcr(0x8000_0000), 1);
392
393 {
397 let mut g = crate::Gte::new();
398 seed_xform(&mut g);
399 g.write_control(21, 0x0000_1000); g.write_control(22, 0x0000_2000); g.write_control(23, 0x0000_3000); g.write_data(0, 0x2040_0340);
403 g.write_data(1, 0x0000_09c0);
404 g.execute(0x4a08_4012); assert_eq!(
406 (g.read_data(25), g.read_data(26), g.read_data(27)),
407 (0xffff_fcc5, 0xffff_e36c, 0xffff_ee75)
408 );
409 }
410 }
411
412 #[test]
413 fn sin_wraps_modulo_256() {
414 assert_eq!(sin_1_3_12(256), sin_1_3_12(0));
416 assert_eq!(sin_1_3_12(320), sin_1_3_12(64));
417 assert_eq!(sin_1_3_12(65535), sin_1_3_12(255));
418 }
419
420 #[test]
421 fn sin_cos_pythagorean_identity_holds_approximately() {
422 for angle in (0..256).step_by(7) {
425 let s = sin_1_3_12(angle as u16) as i32;
426 let c = cos_1_3_12(angle as u16) as i32;
427 let sum = s * s + c * c;
428 let expected = 0x0100_0000;
429 let tolerance = expected / 200;
431 assert!(
432 (sum - expected).abs() <= tolerance,
433 "angle {angle}: sin²+cos² = {sum:#x}, expected ≈ {expected:#x}"
434 );
435 }
436 }
437
438 #[test]
439 fn identity_mul_identity_is_identity() {
440 let id = Mat3I16::IDENTITY;
441 let prod = id.mul(&id);
442 assert_eq!(prod, id);
443 }
444
445 #[test]
446 fn rotate_y_zero_is_identity() {
447 assert_eq!(Mat3I16::rotate_y(0), Mat3I16::IDENTITY);
448 }
449
450 #[test]
451 fn rotate_xyz_uses_x_then_y_then_z_order() {
452 let expected = Mat3I16::rotate_x(17)
453 .mul(&Mat3I16::rotate_y(39))
454 .mul(&Mat3I16::rotate_z(73));
455 assert_eq!(Mat3I16::rotate_xyz(17, 39, 73), expected);
456 assert_ne!(
457 Mat3I16::rotate_xyz(17, 39, 73),
458 Mat3I16::rotate_z(73)
459 .mul(&Mat3I16::rotate_y(39))
460 .mul(&Mat3I16::rotate_x(17))
461 );
462 }
463
464 #[test]
465 fn row_and_column_scaling_are_explicit_and_saturating() {
466 let matrix = Mat3I16 {
467 m: [
468 [0x1000, 0x0800, 0],
469 [0, 0x1000, 0x0400],
470 [0x0200, 0, 0x1000],
471 ],
472 };
473 assert_eq!(
474 matrix.scale_rows_q12([0x0800, 0x1000, 0x2000]).m,
475 [
476 [0x0800, 0x0400, 0],
477 [0, 0x1000, 0x0400],
478 [0x0400, 0, 0x2000]
479 ]
480 );
481 assert_eq!(
482 matrix.scale_columns_q12([0x0800, 0x1000, 0x2000]).m,
483 [
484 [0x0800, 0x0800, 0],
485 [0, 0x1000, 0x0800],
486 [0x0100, 0, 0x2000]
487 ]
488 );
489 assert_eq!(
490 Mat3I16::IDENTITY
491 .scale_rows_q12([i32::MAX, 0x1000, 0x1000])
492 .m[0][0],
493 i16::MAX
494 );
495 }
496
497 #[test]
498 fn rotate_y_quarter_turn_swaps_axes() {
499 let m = Mat3I16::rotate_y(64);
501 assert_eq!(m.m[0][0], 0);
502 assert_eq!(m.m[0][2], 0x1000);
503 assert_eq!(m.m[2][0], -0x1000);
504 assert_eq!(m.m[2][2], 0);
505 assert_eq!(m.m[1][1], 0x1000);
507 }
508
509 #[test]
510 fn rotate_x_then_inverse_round_trips() {
511 let r = Mat3I16::rotate_x(30);
514 let r_inv = Mat3I16::rotate_x(226);
515 let prod = r.mul(&r_inv);
516 for i in 0..3 {
518 for j in 0..3 {
519 let expected = if i == j { 0x1000 } else { 0 };
520 let diff = (prod.m[i][j] as i32 - expected).abs();
521 assert!(
522 diff < 8,
523 "rotate_x ∘ rotate_x⁻¹ cell ({i},{j}) = {:#x}, expected ≈ {expected:#x}",
524 prod.m[i][j]
525 );
526 }
527 }
528 }
529
530 #[test]
531 fn transform_identity_leaves_vector_unchanged() {
532 let v = Vec3I16::new(100, -200, 300);
533 let r = Mat3I16::IDENTITY.transform(v);
534 assert_eq!(r, [100, -200, 300]);
535 }
536
537 #[test]
538 fn transform_rotate_y_quarter_turn() {
539 let v = Vec3I16::new(0x1000, 0, 0);
541 let r = Mat3I16::rotate_y(64).transform(v);
542 assert_eq!(r, [0, 0, -0x1000]);
543 }
544}