diff options
| author | Valentin Popov <valentin@popov.link> | 2026-10-18 13:29:24 +0300 |
|---|---|---|
| committer | Valentin Popov <valentin@popov.link> | 2026-10-18 13:29:24 +0300 |
| commit | 76e5737b1dd047aede7712a6143fef61654df669 (patch) | |
| tree | 77a897f25316b6cf434f0aa5bc46dd609a60e7b4 /apps | |
| parent | 13f563bb79228fdb075698c4ed79deea3cc65301 (diff) | |
| download | fparkan-76e5737b1dd047aede7712a6143fef61654df669.tar.xz fparkan-76e5737b1dd047aede7712a6143fef61654df669.zip | |
Support parented camera capture matrices
Diffstat (limited to 'apps')
| -rw-r--r-- | apps/fparkan-game/src/main.rs | 98 | ||||
| -rw-r--r-- | apps/fparkan-game/testdata/legacy-camera-parented.json | 14 |
2 files changed, 111 insertions, 1 deletions
diff --git a/apps/fparkan-game/src/main.rs b/apps/fparkan-game/src/main.rs index 0c4c7eb..e25ee38 100644 --- a/apps/fparkan-game/src/main.rs +++ b/apps/fparkan-game/src/main.rs @@ -6599,6 +6599,9 @@ struct LegacyCameraCapture { schema: String, render_input_usable: Option<bool>, selector0_words: [u32; 16], + parented_camera: Option<bool>, + effective_world_matrix_space: Option<String>, + effective_world_matrix_words: Option<[u32; 16]>, viewport: [i32; 4], near_plane: f32, far_plane: f32, @@ -6614,6 +6617,26 @@ fn selected_atmosphere_seconds( } impl LegacyCameraCapture { + fn preview_matrix_words(&self) -> Result<[u32; 16], String> { + match ( + self.effective_world_matrix_space.as_deref(), + self.effective_world_matrix_words, + ) { + (Some("world"), Some(words)) => Ok(words), + (Some(_), _) => Err( + "legacy camera effective_world_matrix_space must be 'world'".to_string(), + ), + (None, Some(_)) => Err( + "legacy camera effective_world_matrix_words requires an explicit world-space marker" + .to_string(), + ), + (None, None) if self.parented_camera == Some(true) => Err( + "parented legacy camera requires an effective world-space matrix".to_string(), + ), + (None, None) => Ok(self.selector0_words), + } + } + fn captured_viewport(&self) -> Result<CapturedViewport, String> { let width = self.viewport[2] .checked_sub(self.viewport[0]) @@ -6637,7 +6660,7 @@ impl LegacyCameraCapture { fn preview_camera_frame(&self) -> Result<PreviewCameraFrame, String> { let frame = PreviewCameraFrame::from_legacy_d3d7( RawCameraTransform { - words: self.selector0_words, + words: self.preview_matrix_words()?, }, LegacyD3d7Projection { viewport: self.viewport, @@ -6668,6 +6691,14 @@ fn parse_legacy_camera_capture(bytes: &[u8]) -> Result<LegacyCameraCapture, Stri if capture.schema != "fparkan-legacy-camera-v1" { return Err("unsupported legacy camera capture schema".to_string()); } + if capture.effective_world_matrix_space.is_some() + != capture.effective_world_matrix_words.is_some() + { + return Err( + "effective_world_matrix_space and effective_world_matrix_words must be supplied together" + .to_string(), + ); + } if capture.render_input_usable == Some(false) { return Err("legacy camera capture is marked render_input_usable=false".to_string()); } @@ -8269,11 +8300,76 @@ mod tests { } #[test] + fn parented_camera_preview_uses_effective_world_matrix_fixture() { + let fixture = include_bytes!("../testdata/legacy-camera-parented.json"); + let capture = parse_legacy_camera_capture(fixture).expect("valid parented camera fixture"); + let parse_value = |value: serde_json::Value| { + let bytes = serde_json::to_vec(&value).expect("serialize camera fixture"); + parse_legacy_camera_capture(&bytes) + }; + assert_eq!(capture.parented_camera, Some(true)); + assert_eq!( + capture.preview_camera_frame().unwrap().position, + [10.0, 20.0, 30.0] + ); + assert_eq!( + capture.preview_matrix_words().unwrap(), + capture.effective_world_matrix_words.unwrap() + ); + + let mut missing_world: serde_json::Value = + serde_json::from_slice(fixture).expect("valid fixture JSON"); + missing_world + .as_object_mut() + .unwrap() + .remove("effective_world_matrix_words"); + let missing_world = serde_json::to_vec(&missing_world).unwrap(); + assert!(parse_legacy_camera_capture(&missing_world) + .expect_err("incomplete world matrix pair") + .contains("effective_world_matrix_space and effective_world_matrix_words")); + + let mut parented_without_world: serde_json::Value = + serde_json::from_slice(fixture).expect("valid fixture JSON"); + let parented_without_world = parented_without_world.as_object_mut().unwrap(); + parented_without_world.remove("effective_world_matrix_space"); + parented_without_world.remove("effective_world_matrix_words"); + assert!( + parse_value(serde_json::Value::Object(parented_without_world.clone())) + .expect_err("parented camera without its world matrix") + .contains("parented legacy camera requires an effective world-space matrix") + ); + + let mut invalid_marker: serde_json::Value = + serde_json::from_slice(fixture).expect("valid fixture JSON"); + invalid_marker["effective_world_matrix_space"] = + serde_json::Value::String("local".to_string()); + assert!(parse_value(invalid_marker) + .expect_err("unknown matrix space") + .contains("effective_world_matrix_space must be 'world'")); + + let mut legacy: serde_json::Value = + serde_json::from_slice(fixture).expect("valid fixture JSON"); + let legacy = legacy.as_object_mut().unwrap(); + legacy.remove("parented_camera"); + legacy.remove("effective_world_matrix_space"); + legacy.remove("effective_world_matrix_words"); + let legacy = parse_value(serde_json::Value::Object(legacy.clone())) + .expect("older local-only camera capture"); + assert_eq!( + legacy.preview_camera_frame().unwrap().position, + [1.0, 2.0, 3.0] + ); + } + + #[test] fn captured_atmosphere_is_default_and_cli_time_takes_precedence() { let capture = LegacyCameraCapture { schema: "fparkan-legacy-camera-v1".to_string(), render_input_usable: None, selector0_words: [0; 16], + parented_camera: None, + effective_world_matrix_space: None, + effective_world_matrix_words: None, viewport: [0, 0, 1, 1], near_plane: 0.1, far_plane: 1.0, diff --git a/apps/fparkan-game/testdata/legacy-camera-parented.json b/apps/fparkan-game/testdata/legacy-camera-parented.json new file mode 100644 index 0000000..c852de6 --- /dev/null +++ b/apps/fparkan-game/testdata/legacy-camera-parented.json @@ -0,0 +1,14 @@ +{ + "schema": "fparkan-legacy-camera-v1", + "capture_status": "native-frame-captured", + "render_input_usable": true, + "parented_camera": true, + "selector0_words": [1065353216, 0, 0, 1065353216, 0, 1065353216, 0, 1073741824, 0, 0, 1065353216, 1077936128, 0, 0, 0, 1065353216], + "effective_world_matrix_space": "world", + "effective_world_matrix_words": [1065353216, 0, 0, 1092616192, 0, 1065353216, 0, 1101004800, 0, 0, 1065353216, 1106247680, 0, 0, 0, 1065353216], + "viewport": [0, 0, 1280, 1024], + "near_plane": 0.5, + "far_plane": 700.0, + "field_of_view_radians": 1.04, + "projection_mode_byte": 1 +} |
