fix(render): project static scene on xy plane
This commit is contained in:
@@ -25,8 +25,7 @@ use fparkan_assets::{
|
||||
decode_mission_payload, extend_graph_report_with_visual_dependencies, TmaProfile,
|
||||
};
|
||||
use fparkan_corpus::{discover, render_report_json, report, DiscoverOptions};
|
||||
use fparkan_inspection::inspect_archive_file;
|
||||
use fparkan_inspection::ArchiveInspection;
|
||||
use fparkan_inspection::{inspect_archive_file, inspect_land_msh_bounds_file, ArchiveInspection};
|
||||
use fparkan_path::{normalize_relative, PathPolicy};
|
||||
use fparkan_prototype::build_prototype_graph_report;
|
||||
use fparkan_resource::{resource_name, CachedResourceRepository};
|
||||
@@ -42,6 +41,7 @@ const ARCHIVE_INSPECT_SCHEMA: &str = "fparkan-archive-inspect-v1";
|
||||
const PROTOTYPE_INSPECT_SCHEMA: &str = "fparkan-prototype-inspect-v1";
|
||||
const MISSION_GRAPH_SCHEMA: &str = "fparkan-mission-graph-v1";
|
||||
const MISSION_INSPECT_SCHEMA: &str = "fparkan-mission-inspect-v1";
|
||||
const TERRAIN_INSPECT_SCHEMA: &str = "fparkan-terrain-inspect-v1";
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ArchiveInspectOutput<'a> {
|
||||
@@ -114,6 +114,15 @@ struct MissionObjectInspectOutput {
|
||||
scale: [f32; 3],
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct TerrainInspectOutput {
|
||||
schema_version: &'static str,
|
||||
path: String,
|
||||
positions: usize,
|
||||
min: [f32; 3],
|
||||
max: [f32; 3],
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct GraphFailureOutput {
|
||||
root_index: usize,
|
||||
@@ -178,6 +187,10 @@ fn run(args: &[String]) -> Result<(), String> {
|
||||
let rest = strip_format_json(rest)?;
|
||||
inspect_mission(&rest)
|
||||
}
|
||||
[domain, command, rest @ ..] if domain == "terrain" && command == "inspect" => {
|
||||
let rest = strip_format_json(rest)?;
|
||||
inspect_terrain(&rest)
|
||||
}
|
||||
_ => Err(usage()),
|
||||
}
|
||||
}
|
||||
@@ -400,6 +413,22 @@ fn inspect_archive(args: &[String]) -> Result<(), String> {
|
||||
}
|
||||
}
|
||||
|
||||
fn inspect_terrain(args: &[String]) -> Result<(), String> {
|
||||
let path = parse_file_path(args, "terrain inspect")?;
|
||||
let bounds = inspect_land_msh_bounds_file(&path)?;
|
||||
println!(
|
||||
"{}",
|
||||
serialize_json(&TerrainInspectOutput {
|
||||
schema_version: TERRAIN_INSPECT_SCHEMA,
|
||||
path: path.display().to_string(),
|
||||
positions: bounds.positions,
|
||||
min: bounds.min,
|
||||
max: bounds.max,
|
||||
})?
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn archive_inspect_json(
|
||||
path: &str,
|
||||
kind: &str,
|
||||
@@ -416,10 +445,14 @@ fn archive_inspect_json(
|
||||
}
|
||||
|
||||
fn parse_archive_path(args: &[String]) -> Result<PathBuf, String> {
|
||||
parse_file_path(args, "archive inspect")
|
||||
}
|
||||
|
||||
fn parse_file_path(args: &[String], command: &str) -> Result<PathBuf, String> {
|
||||
match args {
|
||||
[path] => Ok(PathBuf::from(path)),
|
||||
[flag, path] if flag == "--file" => Ok(PathBuf::from(path)),
|
||||
_ => Err("archive inspect requires <file> or --file <file>".to_string()),
|
||||
_ => Err(format!("{command} requires <file> or --file <file>")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,7 +505,7 @@ fn prototype_graph_requiredness_label(
|
||||
}
|
||||
|
||||
fn usage() -> String {
|
||||
"usage: fparkan corpus discover|validate --root <path> [--format json] | archive inspect <file> [--format json] | prototype inspect --root <path> --key <key> [--format json] | mission graph|inspect --root <path> --mission <path> [--format json]".to_string()
|
||||
"usage: fparkan corpus discover|validate --root <path> [--format json] | archive inspect <file> [--format json] | terrain inspect <Land.msh> [--format json] | prototype inspect --root <path> --key <key> [--format json] | mission graph|inspect --root <path> --mission <path> [--format json]".to_string()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -584,4 +617,21 @@ mod tests {
|
||||
"{\"schema_version\":\"fparkan-mission-inspect-v1\",\"mission\":\"MISSIONS/test/data.tma\",\"objects\":[{\"index\":1,\"resource\":\"unit.dat\",\"position\":[1.0,2.0,3.0],\"orientation_raw\":[4.0,5.0,6.0],\"scale\":[7.0,8.0,9.0]}]}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn terrain_inspect_output_retains_axis_bounds() {
|
||||
let json = serialize_json(&TerrainInspectOutput {
|
||||
schema_version: TERRAIN_INSPECT_SCHEMA,
|
||||
path: "DATA/MAPS/AutoMAP/Land.msh".to_string(),
|
||||
positions: 3,
|
||||
min: [-1.0, -2.0, -3.0],
|
||||
max: [4.0, 5.0, 6.0],
|
||||
})
|
||||
.expect("serialize terrain inspection");
|
||||
|
||||
assert_eq!(
|
||||
json,
|
||||
"{\"schema_version\":\"fparkan-terrain-inspect-v1\",\"path\":\"DATA/MAPS/AutoMAP/Land.msh\",\"positions\":3,\"min\":[-1.0,-2.0,-3.0],\"max\":[4.0,5.0,6.0]}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +30,10 @@ use fparkan_render::{
|
||||
RenderSnapshotDraw,
|
||||
};
|
||||
use fparkan_render_vulkan::{
|
||||
project_land_msh_to_static_mesh_in_xz_frame, project_msh_to_static_mesh_in_xz_frame,
|
||||
project_land_msh_to_static_mesh_in_xy_frame, project_msh_to_static_mesh_in_xy_frame,
|
||||
VulkanPlanningBackend, VulkanSmokeFrameOutcome, VulkanSmokeRenderer,
|
||||
VulkanSmokeRendererCreateInfo, VulkanStaticMaterial, VulkanStaticMesh, VulkanStaticTexture,
|
||||
VulkanStaticXzFrame,
|
||||
VulkanStaticXyFrame,
|
||||
};
|
||||
use fparkan_runtime::{
|
||||
create, frame, load_mission, load_mission_static_preview, load_mission_static_preview_roots,
|
||||
@@ -206,7 +206,7 @@ struct StaticPreviewScene {
|
||||
}
|
||||
|
||||
/// Projects the mission terrain plus every MSH component of the explicitly
|
||||
/// bounded static-preview root into one diagnostic XZ frame.
|
||||
/// bounded static-preview root into one diagnostic XY frame.
|
||||
///
|
||||
/// This intentionally takes only the first MAT0 texture request for each MSH
|
||||
/// batch selector. The terrain uses an explicit white diagnostic texture;
|
||||
@@ -220,7 +220,7 @@ fn static_preview_mesh_and_materials(
|
||||
let terrain_mesh = terrain
|
||||
.source_mesh()
|
||||
.ok_or_else(|| "runtime terrain does not retain its validated source mesh".to_string())?;
|
||||
let frame = static_preview_xz_frame(assets, terrain, roots)?;
|
||||
let frame = static_preview_xy_frame(assets, terrain, roots)?;
|
||||
let mut mesh = VulkanStaticMesh {
|
||||
vertices: Vec::new(),
|
||||
indices: Vec::new(),
|
||||
@@ -234,7 +234,7 @@ fn static_preview_mesh_and_materials(
|
||||
rgba8: vec![255, 255, 255, 255],
|
||||
},
|
||||
}];
|
||||
let terrain_component = project_land_msh_to_static_mesh_in_xz_frame(terrain_mesh, frame)
|
||||
let terrain_component = project_land_msh_to_static_mesh_in_xy_frame(terrain_mesh, frame)
|
||||
.map_err(|err| format!("project mission terrain for Vulkan: {err}"))?;
|
||||
append_static_preview_component(&mut mesh, terrain_component, &[(0, 0)])?;
|
||||
let mut mesh_components = 0;
|
||||
@@ -251,7 +251,7 @@ fn static_preview_mesh_and_materials(
|
||||
let model = assets.model_by_id(model_id).ok_or_else(|| {
|
||||
format!("static preview visual {visual_id:?} references unknown model {model_id:?}")
|
||||
})?;
|
||||
let component = project_msh_to_static_mesh_in_xz_frame(
|
||||
let component = project_msh_to_static_mesh_in_xy_frame(
|
||||
&model.validated,
|
||||
frame,
|
||||
root.position,
|
||||
@@ -275,20 +275,20 @@ fn static_preview_mesh_and_materials(
|
||||
})
|
||||
}
|
||||
|
||||
fn static_preview_xz_frame(
|
||||
fn static_preview_xy_frame(
|
||||
assets: &MissionAssets,
|
||||
terrain: &TerrainWorld,
|
||||
roots: &[MissionObjectDraft],
|
||||
) -> Result<VulkanStaticXzFrame, String> {
|
||||
) -> Result<VulkanStaticXyFrame, String> {
|
||||
let mut min_x = f32::INFINITY;
|
||||
let mut max_x = f32::NEG_INFINITY;
|
||||
let mut min_z = f32::INFINITY;
|
||||
let mut max_z = f32::NEG_INFINITY;
|
||||
let mut min_y = f32::INFINITY;
|
||||
let mut max_y = f32::NEG_INFINITY;
|
||||
let terrain_positions = terrain
|
||||
.source_positions()
|
||||
.ok_or_else(|| "runtime terrain does not retain source positions".to_string())?;
|
||||
for position in terrain_positions {
|
||||
extend_static_preview_xz_bounds(*position, &mut min_x, &mut max_x, &mut min_z, &mut max_z)?;
|
||||
extend_static_preview_xy_bounds(*position, &mut min_x, &mut max_x, &mut min_y, &mut max_y)?;
|
||||
}
|
||||
for (object_index, root) in roots.iter().enumerate() {
|
||||
if !root
|
||||
@@ -314,7 +314,7 @@ fn static_preview_xz_frame(
|
||||
format!("static preview visual {visual_id:?} references unknown model {model_id:?}")
|
||||
})?;
|
||||
for position in &model.validated.positions {
|
||||
extend_static_preview_xz_bounds(
|
||||
extend_static_preview_xy_bounds(
|
||||
[
|
||||
position[0] * root.scale[0] + root.position[0],
|
||||
position[1] * root.scale[1] + root.position[1],
|
||||
@@ -322,30 +322,30 @@ fn static_preview_xz_frame(
|
||||
],
|
||||
&mut min_x,
|
||||
&mut max_x,
|
||||
&mut min_z,
|
||||
&mut max_z,
|
||||
&mut min_y,
|
||||
&mut max_y,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
VulkanStaticXzFrame::from_bounds(min_x, max_x, min_z, max_z)
|
||||
.map_err(|err| format!("build static preview XZ frame: {err}"))
|
||||
VulkanStaticXyFrame::from_bounds(min_x, max_x, min_y, max_y)
|
||||
.map_err(|err| format!("build static preview XY frame: {err}"))
|
||||
}
|
||||
|
||||
fn extend_static_preview_xz_bounds(
|
||||
fn extend_static_preview_xy_bounds(
|
||||
position: [f32; 3],
|
||||
min_x: &mut f32,
|
||||
max_x: &mut f32,
|
||||
min_z: &mut f32,
|
||||
max_z: &mut f32,
|
||||
min_y: &mut f32,
|
||||
max_y: &mut f32,
|
||||
) -> Result<(), String> {
|
||||
if !position.iter().all(|value| value.is_finite()) {
|
||||
return Err("static preview contains a non-finite XZ position".to_string());
|
||||
return Err("static preview contains a non-finite XY position".to_string());
|
||||
}
|
||||
*min_x = min_x.min(position[0]);
|
||||
*max_x = max_x.max(position[0]);
|
||||
*min_z = min_z.min(position[2]);
|
||||
*max_z = max_z.max(position[2]);
|
||||
*min_y = min_y.min(position[1]);
|
||||
*max_y = max_y.max(position[1]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user