@@ -38,25 +38,24 @@ namespace omath::iw_engine
3838 Mat4X4 calc_perspective_projection_matrix (const float field_of_view, const float aspect_ratio, const float near,
3939 const float far, const NDCDepthRange ndc_depth_range) noexcept
4040 {
41- // NOTE: Need magic number to fix fov calculation, since IW engine inherit Quake proj matrix calculation
42- constexpr auto k_multiply_factor = 0 .75f ;
43-
44- const float fov_half_tan = std::tan (angles::degrees_to_radians (field_of_view) / 2 .f ) * k_multiply_factor;
41+ // InfinityWard Engine (inherited from Quake) stores FOV as horizontal FOV at a 4:3
42+ // reference aspect. Convert to true vertical FOV, then delegate to the
43+ // standard vertical-FOV left-handed builder with the caller's actual
44+ // aspect ratio.
45+ // vfov = 2 · atan( tan(hfov_4:3 / 2) / (4/3) )
46+ constexpr float k_source_reference_aspect = 4 .f / 3 .f ;
47+ const float half_hfov_4_3 = angles::degrees_to_radians (field_of_view) / 2 .f ;
48+ const float vfov_deg = angles::radians_to_degrees (
49+ 2 .f * std::atan (std::tan (half_hfov_4_3) / k_source_reference_aspect));
4550
4651 if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
47- return {
48- {1 .f / (aspect_ratio * fov_half_tan), 0 , 0 , 0 },
49- {0 , 1 .f / (fov_half_tan), 0 , 0 },
50- {0 , 0 , far / (far - near), -(near * far) / (far - near)},
51- {0 , 0 , 1 , 0 },
52- };
52+ return mat_perspective_left_handed<
53+ float , MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
54+ vfov_deg, aspect_ratio, near, far);
5355 if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
54- return {
55- {1 .f / (aspect_ratio * fov_half_tan), 0 , 0 , 0 },
56- {0 , 1 .f / (fov_half_tan), 0 , 0 },
57- {0 , 0 , (far + near) / (far - near), -(2 .f * far * near) / (far - near)},
58- {0 , 0 , 1 , 0 },
59- };
56+ return mat_perspective_left_handed<
57+ float , MatStoreType::ROW_MAJOR, NDCDepthRange::NEGATIVE_ONE_TO_ONE>(
58+ vfov_deg, aspect_ratio, near, far);
6059 std::unreachable ();
6160 };
6261} // namespace omath::iw_engine
0 commit comments