This commit is contained in:
@@ -525,8 +525,9 @@ impl PreviewCameraFrame {
|
|||||||
self.up
|
self.up
|
||||||
}
|
}
|
||||||
|
|
||||||
fn yaw(self) -> f32 {
|
fn native_sky_heading(self) -> f32 {
|
||||||
self.forward[1].atan2(self.forward[0])
|
// Terrain+0x4F9C0 uses atan2(forward.x, forward.y); CSky+0x481C5 adds pi later.
|
||||||
|
self.forward[0].atan2(self.forward[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2374,7 +2375,7 @@ impl EnvironmentGpuScene {
|
|||||||
(optics.primary_color[index] - sky_frame.sun.color[index]).max(0.0)
|
(optics.primary_color[index] - sky_frame.sun.color[index]).max(0.0)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
let gradient = sky_frame.screen_gradient(camera.yaw(), glare_rgb_delta);
|
let gradient = sky_frame.screen_gradient(camera.native_sky_heading(), glare_rgb_delta);
|
||||||
if let Some(optics) = optics {
|
if let Some(optics) = optics {
|
||||||
let mut uniforms = renderer.frame_uniforms();
|
let mut uniforms = renderer.frame_uniforms();
|
||||||
if sky_frame.sun.active {
|
if sky_frame.sun.active {
|
||||||
@@ -6260,6 +6261,86 @@ mod tests {
|
|||||||
nearly_equal(far[2] / far[3], 1.0);
|
nearly_equal(far[2] / far[3], 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn native_sky_heading_selects_matching_gradient_sectors_for_both_camera_origins() {
|
||||||
|
use fparkan_fx::atmosphere::{PackedColor, SkySample, SKY_COLOR_COUNT};
|
||||||
|
|
||||||
|
let mut colors = [PackedColor(0xFF00_0000); SKY_COLOR_COUNT];
|
||||||
|
colors[..4].copy_from_slice(&[
|
||||||
|
PackedColor(0xFF11_0000),
|
||||||
|
PackedColor(0xFF00_2200),
|
||||||
|
PackedColor(0xFF00_0033),
|
||||||
|
PackedColor(0xFF44_4400),
|
||||||
|
]);
|
||||||
|
let sample = SkySample {
|
||||||
|
colors,
|
||||||
|
values: [0.0; 2],
|
||||||
|
packed: [PackedColor(0); 2],
|
||||||
|
};
|
||||||
|
let projection = LegacyD3d7Projection {
|
||||||
|
viewport: [0, 0, 1280, 720],
|
||||||
|
near_plane: 0.5,
|
||||||
|
far_plane: 700.0,
|
||||||
|
field_of_view_radians: 1.596_851_4,
|
||||||
|
};
|
||||||
|
|
||||||
|
let cardinal_forwards: [([f32; 3], usize); 4] = [
|
||||||
|
([0.0, 1.0, 0.0], 0),
|
||||||
|
([1.0, 0.0, 0.0], 1),
|
||||||
|
([0.0, -1.0, 0.0], 2),
|
||||||
|
([-1.0, 0.0, 0.0], 3),
|
||||||
|
];
|
||||||
|
for (forward, sector) in cardinal_forwards {
|
||||||
|
let free_flight = FreeFlightCamera {
|
||||||
|
position: [0.0; 3],
|
||||||
|
yaw: forward[1].atan2(forward[0]),
|
||||||
|
pitch: 0.0,
|
||||||
|
vertical_fov: std::f32::consts::FRAC_PI_3,
|
||||||
|
near_plane: projection.near_plane,
|
||||||
|
far_plane: projection.far_plane,
|
||||||
|
move_speed: 1.0,
|
||||||
|
}
|
||||||
|
.preview_frame(1280.0 / 720.0);
|
||||||
|
|
||||||
|
let right = normalize3(cross3(forward, [0.0, 0.0, 1.0])).expect("camera right");
|
||||||
|
let up = normalize3(cross3(right, forward)).expect("camera up");
|
||||||
|
let raw_transform = RawCameraTransform {
|
||||||
|
words: [
|
||||||
|
forward[0].to_bits(),
|
||||||
|
(-right[0]).to_bits(),
|
||||||
|
up[0].to_bits(),
|
||||||
|
0.0_f32.to_bits(),
|
||||||
|
forward[1].to_bits(),
|
||||||
|
(-right[1]).to_bits(),
|
||||||
|
up[1].to_bits(),
|
||||||
|
0.0_f32.to_bits(),
|
||||||
|
forward[2].to_bits(),
|
||||||
|
(-right[2]).to_bits(),
|
||||||
|
up[2].to_bits(),
|
||||||
|
0.0_f32.to_bits(),
|
||||||
|
0.0_f32.to_bits(),
|
||||||
|
0.0_f32.to_bits(),
|
||||||
|
0.0_f32.to_bits(),
|
||||||
|
1.0_f32.to_bits(),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
let captured = PreviewCameraFrame::from_legacy_d3d7(raw_transform, projection)
|
||||||
|
.expect("captured camera frame");
|
||||||
|
|
||||||
|
for (origin, camera) in [("free flight", free_flight), ("captured", captured)] {
|
||||||
|
let gradient = fparkan_fx::sky::screen_gradient_frame(
|
||||||
|
&sample,
|
||||||
|
camera.native_sky_heading(),
|
||||||
|
[0.0; 3],
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
gradient.color, sample.colors[sector],
|
||||||
|
"{origin} camera heading for forward {forward:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sky_normals_keep_native_signed_byte_scale_without_renormalizing() {
|
fn sky_normals_keep_native_signed_byte_scale_without_renormalizing() {
|
||||||
assert_eq!(sky_normal([-128, 127, -64]), [-1.0, 127.0 / 128.0, -0.5]);
|
assert_eq!(sky_normal([-128, 127, -64]), [-1.0, 127.0 / 128.0, -0.5]);
|
||||||
|
|||||||
@@ -100,6 +100,15 @@ receives real frame time. `simulation_time_seconds`, weather, RNG and complete
|
|||||||
mission state remain unknown, and a camera/phase match alone does not establish
|
mission state remain unknown, and a camera/phase match alone does not establish
|
||||||
visual parity with a native frame.
|
visual parity with a native frame.
|
||||||
|
|
||||||
|
The preview's sky heading is `atan2(forward.x, forward.y)`, matching the second
|
||||||
|
output calculated at `Terrain.dll` RVA `0x4F9C0`; `CSky` adds π at RVA `0x481C5`
|
||||||
|
when selecting screen-gradient sectors. This mapping is applied only at the sky
|
||||||
|
boundary and does not change `FreeFlightCamera` yaw or movement controls.
|
||||||
|
Matched same-camera, same-phase three-frame captures at phases 56.227 and
|
||||||
|
998.893 showed zero changed pixels before and after this correction; the
|
||||||
|
remaining sky and color mismatch is unresolved, so this source-level match does
|
||||||
|
not establish visual parity.
|
||||||
|
|
||||||
## Parity risks
|
## Parity risks
|
||||||
|
|
||||||
`VulkanStaticCamera::from_legacy_d3d7` keeps the D3D7 view and projection
|
`VulkanStaticCamera::from_legacy_d3d7` keeps the D3D7 view and projection
|
||||||
|
|||||||
Reference in New Issue
Block a user