fix(render): project static scene on xy plane
This commit is contained in:
@@ -18,20 +18,20 @@ pub enum VulkanAssetMeshError {
|
|||||||
IndexOutOfRange,
|
IndexOutOfRange,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shared XZ frame for a deliberately top-down diagnostic static scene.
|
/// Shared XY frame for a deliberately top-down diagnostic static scene.
|
||||||
///
|
///
|
||||||
/// It is a CPU-side viewer transform, not evidence of the original camera or
|
/// It is a CPU-side viewer transform, not evidence of the original camera or
|
||||||
/// object transform convention. Keeping it explicit prevents separately
|
/// object transform convention. Keeping it explicit prevents separately
|
||||||
/// normalized terrain and model components from being incorrectly overlaid.
|
/// normalized terrain and model components from being incorrectly overlaid.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct VulkanStaticXzFrame {
|
pub struct VulkanStaticXyFrame {
|
||||||
center_x: f32,
|
center_x: f32,
|
||||||
center_z: f32,
|
center_y: f32,
|
||||||
extent: f32,
|
extent: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VulkanStaticXzFrame {
|
impl VulkanStaticXyFrame {
|
||||||
/// Builds a frame that maps the supplied XZ bounds into the static viewer.
|
/// Builds a frame that maps the supplied XY bounds into the static viewer.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
@@ -40,16 +40,16 @@ impl VulkanStaticXzFrame {
|
|||||||
pub fn from_bounds(
|
pub fn from_bounds(
|
||||||
min_x: f32,
|
min_x: f32,
|
||||||
max_x: f32,
|
max_x: f32,
|
||||||
min_z: f32,
|
min_y: f32,
|
||||||
max_z: f32,
|
max_y: f32,
|
||||||
) -> Result<Self, VulkanAssetMeshError> {
|
) -> Result<Self, VulkanAssetMeshError> {
|
||||||
let extent = (max_x - min_x).max(max_z - min_z);
|
let extent = (max_x - min_x).max(max_y - min_y);
|
||||||
if !extent.is_finite() || extent <= f32::EPSILON {
|
if !extent.is_finite() || extent <= f32::EPSILON {
|
||||||
return Err(VulkanAssetMeshError::DegenerateViewExtent);
|
return Err(VulkanAssetMeshError::DegenerateViewExtent);
|
||||||
}
|
}
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
center_x: (min_x + max_x) * 0.5,
|
center_x: (min_x + max_x) * 0.5,
|
||||||
center_z: (min_z + max_z) * 0.5,
|
center_y: (min_y + max_y) * 0.5,
|
||||||
extent,
|
extent,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -58,14 +58,14 @@ impl VulkanStaticXzFrame {
|
|||||||
let scale = 1.6 / self.extent;
|
let scale = 1.6 / self.extent;
|
||||||
[
|
[
|
||||||
(position[0] - self.center_x) * scale,
|
(position[0] - self.center_x) * scale,
|
||||||
(position[2] - self.center_z) * scale,
|
(position[1] - self.center_y) * scale,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn planar_uv(self, position: [f32; 3]) -> [f32; 2] {
|
fn planar_uv(self, position: [f32; 3]) -> [f32; 2] {
|
||||||
[
|
[
|
||||||
(position[0] - self.center_x) / self.extent + 0.5,
|
(position[0] - self.center_x) / self.extent + 0.5,
|
||||||
(position[2] - self.center_z) / self.extent + 0.5,
|
(position[1] - self.center_y) / self.extent + 0.5,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,7 @@ impl std::fmt::Display for VulkanAssetMeshError {
|
|||||||
match self {
|
match self {
|
||||||
Self::EmptyGeometry => write!(f, "MSH contains no triangle geometry"),
|
Self::EmptyGeometry => write!(f, "MSH contains no triangle geometry"),
|
||||||
Self::NonFinitePosition => write!(f, "MSH contains a non-finite position"),
|
Self::NonFinitePosition => write!(f, "MSH contains a non-finite position"),
|
||||||
Self::DegenerateViewExtent => write!(f, "MSH has a degenerate XZ view extent"),
|
Self::DegenerateViewExtent => write!(f, "MSH has a degenerate XY view extent"),
|
||||||
Self::IndexOutOfRange => write!(f, "MSH index exceeds the static Vulkan u16 contract"),
|
Self::IndexOutOfRange => write!(f, "MSH index exceeds the static Vulkan u16 contract"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,7 +88,7 @@ impl std::error::Error for VulkanAssetMeshError {}
|
|||||||
/// The original engine's node transforms, camera and material pipeline are not
|
/// The original engine's node transforms, camera and material pipeline are not
|
||||||
/// substituted here. This bridge is intentionally a static asset-viewer step:
|
/// substituted here. This bridge is intentionally a static asset-viewer step:
|
||||||
/// it preserves every batch's `index_start`, `index_count` and `base_vertex`,
|
/// it preserves every batch's `index_start`, `index_count` and `base_vertex`,
|
||||||
/// projects the conventional `Iron3D` XZ ground plane into the current XY shader
|
/// projects the conventional `Iron3D` XY ground plane into the current XY shader
|
||||||
/// input, and scales the used bounds uniformly into the visible clip rectangle.
|
/// input, and scales the used bounds uniformly into the visible clip rectangle.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
@@ -99,12 +99,12 @@ pub fn project_msh_to_static_mesh(
|
|||||||
model: &ModelAsset,
|
model: &ModelAsset,
|
||||||
) -> Result<VulkanStaticMesh, VulkanAssetMeshError> {
|
) -> Result<VulkanStaticMesh, VulkanAssetMeshError> {
|
||||||
let (indices, _) = static_model_indices_and_ranges(model)?;
|
let (indices, _) = static_model_indices_and_ranges(model)?;
|
||||||
let (min_x, max_x, min_z, max_z) = static_mesh_xz_bounds(&model.positions, &indices)?;
|
let (min_x, max_x, min_y, max_y) = static_mesh_xy_bounds(&model.positions, &indices)?;
|
||||||
let frame = VulkanStaticXzFrame::from_bounds(min_x, max_x, min_z, max_z)?;
|
let frame = VulkanStaticXyFrame::from_bounds(min_x, max_x, min_y, max_y)?;
|
||||||
project_msh_to_static_mesh_in_xz_frame(model, frame, [0.0; 3], [1.0; 3])
|
project_msh_to_static_mesh_in_xy_frame(model, frame, [0.0; 3], [1.0; 3])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Projects MSH geometry with a known translation/scale into a shared XZ frame.
|
/// Projects MSH geometry with a known translation/scale into a shared XY frame.
|
||||||
///
|
///
|
||||||
/// The caller supplies only transforms already decoded from mission data. Raw
|
/// The caller supplies only transforms already decoded from mission data. Raw
|
||||||
/// orientation is intentionally not interpreted until its original convention
|
/// orientation is intentionally not interpreted until its original convention
|
||||||
@@ -114,9 +114,9 @@ pub fn project_msh_to_static_mesh(
|
|||||||
///
|
///
|
||||||
/// Returns [`VulkanAssetMeshError`] when geometry, transform values or the
|
/// Returns [`VulkanAssetMeshError`] when geometry, transform values or the
|
||||||
/// static Vulkan input contract cannot represent the source model.
|
/// static Vulkan input contract cannot represent the source model.
|
||||||
pub fn project_msh_to_static_mesh_in_xz_frame(
|
pub fn project_msh_to_static_mesh_in_xy_frame(
|
||||||
model: &ModelAsset,
|
model: &ModelAsset,
|
||||||
frame: VulkanStaticXzFrame,
|
frame: VulkanStaticXyFrame,
|
||||||
translation: [f32; 3],
|
translation: [f32; 3],
|
||||||
scale: [f32; 3],
|
scale: [f32; 3],
|
||||||
) -> Result<VulkanStaticMesh, VulkanAssetMeshError> {
|
) -> Result<VulkanStaticMesh, VulkanAssetMeshError> {
|
||||||
@@ -146,7 +146,7 @@ pub fn project_msh_to_static_mesh_in_xz_frame(
|
|||||||
color: [0.82, 0.72, 0.31],
|
color: [0.82, 0.72, 0.31],
|
||||||
// Iron3D stores Res5 UV0 as signed fixed point with 1/1024 units.
|
// Iron3D stores Res5 UV0 as signed fixed point with 1/1024 units.
|
||||||
// Models that omit this optional stream retain the static viewer's
|
// Models that omit this optional stream retain the static viewer's
|
||||||
// XZ planar fallback instead of receiving fabricated raw UV values.
|
// XY planar fallback instead of receiving fabricated raw UV values.
|
||||||
uv: model
|
uv: model
|
||||||
.uv0
|
.uv0
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -176,7 +176,7 @@ pub fn project_msh_to_static_mesh_in_xz_frame(
|
|||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns [`VulkanAssetMeshError`] if the terrain has no triangles, contains
|
/// Returns [`VulkanAssetMeshError`] if the terrain has no triangles, contains
|
||||||
/// non-finite positions, has no usable XZ extent, or references data outside
|
/// non-finite positions, has no usable XY extent, or references data outside
|
||||||
/// the current static Vulkan input contract.
|
/// the current static Vulkan input contract.
|
||||||
pub fn project_land_msh_to_static_mesh(
|
pub fn project_land_msh_to_static_mesh(
|
||||||
terrain: &LandMeshDocument,
|
terrain: &LandMeshDocument,
|
||||||
@@ -195,20 +195,20 @@ pub fn project_land_msh_to_static_mesh(
|
|||||||
return Err(VulkanAssetMeshError::EmptyGeometry);
|
return Err(VulkanAssetMeshError::EmptyGeometry);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (min_x, max_x, min_z, max_z) = static_mesh_xz_bounds(&terrain.positions, &indices)?;
|
let (min_x, max_x, min_y, max_y) = static_mesh_xy_bounds(&terrain.positions, &indices)?;
|
||||||
let frame = VulkanStaticXzFrame::from_bounds(min_x, max_x, min_z, max_z)?;
|
let frame = VulkanStaticXyFrame::from_bounds(min_x, max_x, min_y, max_y)?;
|
||||||
project_land_msh_to_static_mesh_in_xz_frame(terrain, frame)
|
project_land_msh_to_static_mesh_in_xy_frame(terrain, frame)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Projects `Land.msh` terrain geometry into a shared diagnostic XZ frame.
|
/// Projects `Land.msh` terrain geometry into a shared diagnostic XY frame.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns [`VulkanAssetMeshError`] when terrain geometry or the static Vulkan
|
/// Returns [`VulkanAssetMeshError`] when terrain geometry or the static Vulkan
|
||||||
/// input contract cannot represent the source mesh.
|
/// input contract cannot represent the source mesh.
|
||||||
pub fn project_land_msh_to_static_mesh_in_xz_frame(
|
pub fn project_land_msh_to_static_mesh_in_xy_frame(
|
||||||
terrain: &LandMeshDocument,
|
terrain: &LandMeshDocument,
|
||||||
frame: VulkanStaticXzFrame,
|
frame: VulkanStaticXyFrame,
|
||||||
) -> Result<VulkanStaticMesh, VulkanAssetMeshError> {
|
) -> Result<VulkanStaticMesh, VulkanAssetMeshError> {
|
||||||
let mut indices = Vec::with_capacity(
|
let mut indices = Vec::with_capacity(
|
||||||
terrain
|
terrain
|
||||||
@@ -298,14 +298,14 @@ fn static_model_indices_and_ranges(
|
|||||||
Ok((indices, draw_ranges))
|
Ok((indices, draw_ranges))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn static_mesh_xz_bounds(
|
fn static_mesh_xy_bounds(
|
||||||
positions: &[[f32; 3]],
|
positions: &[[f32; 3]],
|
||||||
indices: &[u16],
|
indices: &[u16],
|
||||||
) -> Result<(f32, f32, f32, f32), VulkanAssetMeshError> {
|
) -> Result<(f32, f32, f32, f32), VulkanAssetMeshError> {
|
||||||
let mut min_x = f32::INFINITY;
|
let mut min_x = f32::INFINITY;
|
||||||
let mut max_x = f32::NEG_INFINITY;
|
let mut max_x = f32::NEG_INFINITY;
|
||||||
let mut min_z = f32::INFINITY;
|
let mut min_y = f32::INFINITY;
|
||||||
let mut max_z = f32::NEG_INFINITY;
|
let mut max_y = f32::NEG_INFINITY;
|
||||||
for &index in indices {
|
for &index in indices {
|
||||||
let position = positions
|
let position = positions
|
||||||
.get(usize::from(index))
|
.get(usize::from(index))
|
||||||
@@ -315,10 +315,10 @@ fn static_mesh_xz_bounds(
|
|||||||
}
|
}
|
||||||
min_x = min_x.min(position[0]);
|
min_x = min_x.min(position[0]);
|
||||||
max_x = max_x.max(position[0]);
|
max_x = max_x.max(position[0]);
|
||||||
min_z = min_z.min(position[2]);
|
min_y = min_y.min(position[1]);
|
||||||
max_z = max_z.max(position[2]);
|
max_y = max_y.max(position[1]);
|
||||||
}
|
}
|
||||||
Ok((min_x, max_x, min_z, max_z))
|
Ok((min_x, max_x, min_y, max_y))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -356,13 +356,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn projects_xz_geometry_and_applies_base_vertex() {
|
fn projects_xy_geometry_and_applies_base_vertex() {
|
||||||
let mesh = project_msh_to_static_mesh(&model(
|
let mesh = project_msh_to_static_mesh(&model(
|
||||||
vec![
|
vec![
|
||||||
[99.0, 0.0, 99.0],
|
[99.0, 99.0, 0.0],
|
||||||
[-2.0, 4.0, -1.0],
|
[-2.0, -1.0, 4.0],
|
||||||
[2.0, 8.0, -1.0],
|
[2.0, -1.0, 8.0],
|
||||||
[-2.0, 1.0, 3.0],
|
[-2.0, 3.0, 1.0],
|
||||||
],
|
],
|
||||||
vec![0, 1, 2],
|
vec![0, 1, 2],
|
||||||
vec![batch(0, 3, 1)],
|
vec![batch(0, 3, 1)],
|
||||||
@@ -448,10 +448,10 @@ mod tests {
|
|||||||
slots_raw: Vec::new(),
|
slots_raw: Vec::new(),
|
||||||
},
|
},
|
||||||
positions: vec![
|
positions: vec![
|
||||||
[-2.0, 5.0, -1.0],
|
[-2.0, -1.0, 5.0],
|
||||||
[2.0, 3.0, -1.0],
|
[2.0, -1.0, 3.0],
|
||||||
[-2.0, 9.0, 3.0],
|
[-2.0, 3.0, 9.0],
|
||||||
[2.0, 1.0, 3.0],
|
[2.0, 3.0, 1.0],
|
||||||
],
|
],
|
||||||
normals: Vec::new(),
|
normals: Vec::new(),
|
||||||
uv0: vec![[1024, -512], [0, 2048], [-1024, 512], [512, 0]],
|
uv0: vec![[1024, -512], [0, 2048], [-1024, 512], [512, 0]],
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ mod swapchain_resources;
|
|||||||
mod validation;
|
mod validation;
|
||||||
|
|
||||||
pub use self::asset_mesh::{
|
pub use self::asset_mesh::{
|
||||||
project_land_msh_to_static_mesh, project_land_msh_to_static_mesh_in_xz_frame,
|
project_land_msh_to_static_mesh, project_land_msh_to_static_mesh_in_xy_frame,
|
||||||
project_msh_to_static_mesh, project_msh_to_static_mesh_in_xz_frame, VulkanAssetMeshError,
|
project_msh_to_static_mesh, project_msh_to_static_mesh_in_xy_frame, VulkanAssetMeshError,
|
||||||
VulkanStaticXzFrame,
|
VulkanStaticXyFrame,
|
||||||
};
|
};
|
||||||
pub use self::capabilities::{
|
pub use self::capabilities::{
|
||||||
probe_vulkan_runtime_capabilities, probe_vulkan_runtime_capabilities_for_request,
|
probe_vulkan_runtime_capabilities, probe_vulkan_runtime_capabilities_for_request,
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ use fparkan_assets::{
|
|||||||
decode_mission_payload, extend_graph_report_with_visual_dependencies, TmaProfile,
|
decode_mission_payload, extend_graph_report_with_visual_dependencies, TmaProfile,
|
||||||
};
|
};
|
||||||
use fparkan_corpus::{discover, render_report_json, report, DiscoverOptions};
|
use fparkan_corpus::{discover, render_report_json, report, DiscoverOptions};
|
||||||
use fparkan_inspection::inspect_archive_file;
|
use fparkan_inspection::{inspect_archive_file, inspect_land_msh_bounds_file, ArchiveInspection};
|
||||||
use fparkan_inspection::ArchiveInspection;
|
|
||||||
use fparkan_path::{normalize_relative, PathPolicy};
|
use fparkan_path::{normalize_relative, PathPolicy};
|
||||||
use fparkan_prototype::build_prototype_graph_report;
|
use fparkan_prototype::build_prototype_graph_report;
|
||||||
use fparkan_resource::{resource_name, CachedResourceRepository};
|
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 PROTOTYPE_INSPECT_SCHEMA: &str = "fparkan-prototype-inspect-v1";
|
||||||
const MISSION_GRAPH_SCHEMA: &str = "fparkan-mission-graph-v1";
|
const MISSION_GRAPH_SCHEMA: &str = "fparkan-mission-graph-v1";
|
||||||
const MISSION_INSPECT_SCHEMA: &str = "fparkan-mission-inspect-v1";
|
const MISSION_INSPECT_SCHEMA: &str = "fparkan-mission-inspect-v1";
|
||||||
|
const TERRAIN_INSPECT_SCHEMA: &str = "fparkan-terrain-inspect-v1";
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct ArchiveInspectOutput<'a> {
|
struct ArchiveInspectOutput<'a> {
|
||||||
@@ -114,6 +114,15 @@ struct MissionObjectInspectOutput {
|
|||||||
scale: [f32; 3],
|
scale: [f32; 3],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct TerrainInspectOutput {
|
||||||
|
schema_version: &'static str,
|
||||||
|
path: String,
|
||||||
|
positions: usize,
|
||||||
|
min: [f32; 3],
|
||||||
|
max: [f32; 3],
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct GraphFailureOutput {
|
struct GraphFailureOutput {
|
||||||
root_index: usize,
|
root_index: usize,
|
||||||
@@ -178,6 +187,10 @@ fn run(args: &[String]) -> Result<(), String> {
|
|||||||
let rest = strip_format_json(rest)?;
|
let rest = strip_format_json(rest)?;
|
||||||
inspect_mission(&rest)
|
inspect_mission(&rest)
|
||||||
}
|
}
|
||||||
|
[domain, command, rest @ ..] if domain == "terrain" && command == "inspect" => {
|
||||||
|
let rest = strip_format_json(rest)?;
|
||||||
|
inspect_terrain(&rest)
|
||||||
|
}
|
||||||
_ => Err(usage()),
|
_ => 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(
|
fn archive_inspect_json(
|
||||||
path: &str,
|
path: &str,
|
||||||
kind: &str,
|
kind: &str,
|
||||||
@@ -416,10 +445,14 @@ fn archive_inspect_json(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_archive_path(args: &[String]) -> Result<PathBuf, String> {
|
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 {
|
match args {
|
||||||
[path] => Ok(PathBuf::from(path)),
|
[path] => Ok(PathBuf::from(path)),
|
||||||
[flag, path] if flag == "--file" => 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 {
|
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)]
|
#[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]}]}"
|
"{\"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,
|
RenderSnapshotDraw,
|
||||||
};
|
};
|
||||||
use fparkan_render_vulkan::{
|
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,
|
VulkanPlanningBackend, VulkanSmokeFrameOutcome, VulkanSmokeRenderer,
|
||||||
VulkanSmokeRendererCreateInfo, VulkanStaticMaterial, VulkanStaticMesh, VulkanStaticTexture,
|
VulkanSmokeRendererCreateInfo, VulkanStaticMaterial, VulkanStaticMesh, VulkanStaticTexture,
|
||||||
VulkanStaticXzFrame,
|
VulkanStaticXyFrame,
|
||||||
};
|
};
|
||||||
use fparkan_runtime::{
|
use fparkan_runtime::{
|
||||||
create, frame, load_mission, load_mission_static_preview, load_mission_static_preview_roots,
|
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
|
/// 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
|
/// This intentionally takes only the first MAT0 texture request for each MSH
|
||||||
/// batch selector. The terrain uses an explicit white diagnostic texture;
|
/// batch selector. The terrain uses an explicit white diagnostic texture;
|
||||||
@@ -220,7 +220,7 @@ fn static_preview_mesh_and_materials(
|
|||||||
let terrain_mesh = terrain
|
let terrain_mesh = terrain
|
||||||
.source_mesh()
|
.source_mesh()
|
||||||
.ok_or_else(|| "runtime terrain does not retain its validated source mesh".to_string())?;
|
.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 {
|
let mut mesh = VulkanStaticMesh {
|
||||||
vertices: Vec::new(),
|
vertices: Vec::new(),
|
||||||
indices: Vec::new(),
|
indices: Vec::new(),
|
||||||
@@ -234,7 +234,7 @@ fn static_preview_mesh_and_materials(
|
|||||||
rgba8: vec![255, 255, 255, 255],
|
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}"))?;
|
.map_err(|err| format!("project mission terrain for Vulkan: {err}"))?;
|
||||||
append_static_preview_component(&mut mesh, terrain_component, &[(0, 0)])?;
|
append_static_preview_component(&mut mesh, terrain_component, &[(0, 0)])?;
|
||||||
let mut mesh_components = 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(|| {
|
let model = assets.model_by_id(model_id).ok_or_else(|| {
|
||||||
format!("static preview visual {visual_id:?} references unknown model {model_id:?}")
|
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,
|
&model.validated,
|
||||||
frame,
|
frame,
|
||||||
root.position,
|
root.position,
|
||||||
@@ -275,20 +275,20 @@ fn static_preview_mesh_and_materials(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn static_preview_xz_frame(
|
fn static_preview_xy_frame(
|
||||||
assets: &MissionAssets,
|
assets: &MissionAssets,
|
||||||
terrain: &TerrainWorld,
|
terrain: &TerrainWorld,
|
||||||
roots: &[MissionObjectDraft],
|
roots: &[MissionObjectDraft],
|
||||||
) -> Result<VulkanStaticXzFrame, String> {
|
) -> Result<VulkanStaticXyFrame, String> {
|
||||||
let mut min_x = f32::INFINITY;
|
let mut min_x = f32::INFINITY;
|
||||||
let mut max_x = f32::NEG_INFINITY;
|
let mut max_x = f32::NEG_INFINITY;
|
||||||
let mut min_z = f32::INFINITY;
|
let mut min_y = f32::INFINITY;
|
||||||
let mut max_z = f32::NEG_INFINITY;
|
let mut max_y = f32::NEG_INFINITY;
|
||||||
let terrain_positions = terrain
|
let terrain_positions = terrain
|
||||||
.source_positions()
|
.source_positions()
|
||||||
.ok_or_else(|| "runtime terrain does not retain source positions".to_string())?;
|
.ok_or_else(|| "runtime terrain does not retain source positions".to_string())?;
|
||||||
for position in terrain_positions {
|
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() {
|
for (object_index, root) in roots.iter().enumerate() {
|
||||||
if !root
|
if !root
|
||||||
@@ -314,7 +314,7 @@ fn static_preview_xz_frame(
|
|||||||
format!("static preview visual {visual_id:?} references unknown model {model_id:?}")
|
format!("static preview visual {visual_id:?} references unknown model {model_id:?}")
|
||||||
})?;
|
})?;
|
||||||
for position in &model.validated.positions {
|
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[0] * root.scale[0] + root.position[0],
|
||||||
position[1] * root.scale[1] + root.position[1],
|
position[1] * root.scale[1] + root.position[1],
|
||||||
@@ -322,30 +322,30 @@ fn static_preview_xz_frame(
|
|||||||
],
|
],
|
||||||
&mut min_x,
|
&mut min_x,
|
||||||
&mut max_x,
|
&mut max_x,
|
||||||
&mut min_z,
|
&mut min_y,
|
||||||
&mut max_z,
|
&mut max_y,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VulkanStaticXzFrame::from_bounds(min_x, max_x, min_z, max_z)
|
VulkanStaticXyFrame::from_bounds(min_x, max_x, min_y, max_y)
|
||||||
.map_err(|err| format!("build static preview XZ frame: {err}"))
|
.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],
|
position: [f32; 3],
|
||||||
min_x: &mut f32,
|
min_x: &mut f32,
|
||||||
max_x: &mut f32,
|
max_x: &mut f32,
|
||||||
min_z: &mut f32,
|
min_y: &mut f32,
|
||||||
max_z: &mut f32,
|
max_y: &mut f32,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
if !position.iter().all(|value| value.is_finite()) {
|
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]);
|
*min_x = min_x.min(position[0]);
|
||||||
*max_x = max_x.max(position[0]);
|
*max_x = max_x.max(position[0]);
|
||||||
*min_z = min_z.min(position[2]);
|
*min_y = min_y.min(position[1]);
|
||||||
*max_z = max_z.max(position[2]);
|
*max_y = max_y.max(position[1]);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -142,6 +142,17 @@ pub struct MapInspection {
|
|||||||
pub grid_height: u32,
|
pub grid_height: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Axis-aligned position bounds of a decoded `Land.msh`.
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct LandMeshBoundsInspection {
|
||||||
|
/// Number of source positions covered by the bounds.
|
||||||
|
pub positions: usize,
|
||||||
|
/// Per-axis inclusive minimum position.
|
||||||
|
pub min: [f32; 3],
|
||||||
|
/// Per-axis inclusive maximum position.
|
||||||
|
pub max: [f32; 3],
|
||||||
|
}
|
||||||
|
|
||||||
/// Supported land file kinds.
|
/// Supported land file kinds.
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub enum LandFileKind {
|
pub enum LandFileKind {
|
||||||
@@ -450,6 +461,46 @@ pub fn load_land_msh_from_path(path: &Path) -> Result<LandMeshDocument, String>
|
|||||||
decode_land_msh(&document).map_err(|err| err.to_string())
|
decode_land_msh(&document).map_err(|err| err.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Inspects the source-coordinate bounds of a standalone `Land.msh` file.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns a string error when the file cannot be read, decoded, or contains
|
||||||
|
/// no finite source positions.
|
||||||
|
pub fn inspect_land_msh_bounds_file(path: &Path) -> Result<LandMeshBoundsInspection, String> {
|
||||||
|
let mesh = load_land_msh_from_path(path)?;
|
||||||
|
inspect_land_msh_bounds(&mesh)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes source-coordinate bounds for an already validated `Land.msh`.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns a string error when the mesh has no finite source positions.
|
||||||
|
pub fn inspect_land_msh_bounds(
|
||||||
|
mesh: &LandMeshDocument,
|
||||||
|
) -> Result<LandMeshBoundsInspection, String> {
|
||||||
|
let mut min = [f32::INFINITY; 3];
|
||||||
|
let mut max = [f32::NEG_INFINITY; 3];
|
||||||
|
for position in &mesh.positions {
|
||||||
|
if !position.iter().all(|value| value.is_finite()) {
|
||||||
|
return Err("Land.msh contains a non-finite source position".to_string());
|
||||||
|
}
|
||||||
|
for axis in 0..3 {
|
||||||
|
min[axis] = min[axis].min(position[axis]);
|
||||||
|
max[axis] = max[axis].max(position[axis]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mesh.positions.is_empty() {
|
||||||
|
return Err("Land.msh contains no source positions".to_string());
|
||||||
|
}
|
||||||
|
Ok(LandMeshBoundsInspection {
|
||||||
|
positions: mesh.positions.len(),
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn inspect_land_msh(document: &NresDocument) -> Result<MapInspection, String> {
|
fn inspect_land_msh(document: &NresDocument) -> Result<MapInspection, String> {
|
||||||
let land_msh = decode_land_msh(document).map_err(|err| err.to_string())?;
|
let land_msh = decode_land_msh(document).map_err(|err| err.to_string())?;
|
||||||
Ok(MapInspection {
|
Ok(MapInspection {
|
||||||
@@ -599,6 +650,7 @@ fn resource_parse_diagnostic(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use fparkan_terrain_format::TerrainSlotTable;
|
||||||
use std::io::Write as _;
|
use std::io::Write as _;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
@@ -709,6 +761,31 @@ mod tests {
|
|||||||
assert_eq!(selected.image.rgba8, vec![0x11, 0x22, 0x33, 0x40]);
|
assert_eq!(selected.image.rgba8, vec![0x11, 0x22, 0x33, 0x40]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn land_mesh_bounds_preserve_each_source_axis() {
|
||||||
|
let mesh = LandMeshDocument {
|
||||||
|
streams: Vec::new(),
|
||||||
|
nodes_raw: Vec::new(),
|
||||||
|
slots: TerrainSlotTable {
|
||||||
|
header_raw: Vec::new(),
|
||||||
|
slots_raw: Vec::new(),
|
||||||
|
},
|
||||||
|
positions: vec![[4.0, -2.0, 8.0], [-3.0, 6.0, 1.0]],
|
||||||
|
normals: Vec::new(),
|
||||||
|
uv0: Vec::new(),
|
||||||
|
accelerator: Vec::new(),
|
||||||
|
aux14: Vec::new(),
|
||||||
|
aux18: Vec::new(),
|
||||||
|
faces: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let bounds = inspect_land_msh_bounds(&mesh).expect("bounds");
|
||||||
|
|
||||||
|
assert_eq!(bounds.positions, 2);
|
||||||
|
assert_eq!(bounds.min, [-3.0, -2.0, 1.0]);
|
||||||
|
assert_eq!(bounds.max, [4.0, 6.0, 8.0]);
|
||||||
|
}
|
||||||
|
|
||||||
fn temp_dir(name: &str) -> PathBuf {
|
fn temp_dir(name: &str) -> PathBuf {
|
||||||
let base = PathBuf::from("/tmp")
|
let base = PathBuf::from("/tmp")
|
||||||
.join("fparkan-inspection-tests")
|
.join("fparkan-inspection-tests")
|
||||||
|
|||||||
+21
-12
@@ -938,7 +938,7 @@ from a runtime slot or draw order.
|
|||||||
|
|
||||||
`fparkan-game --backend static-vulkan` is an explicit native-window experiment,
|
`fparkan-game --backend static-vulkan` is an explicit native-window experiment,
|
||||||
not the default planning path. It visits only the first mission root, selects
|
not the default planning path. It visits only the first mission root, selects
|
||||||
every prepared MSH component of that root, merges their static XZ clip-space
|
every prepared MSH component of that root, merges their static XY clip-space
|
||||||
geometry, and renders a requested number of frames through
|
geometry, and renders a requested number of frames through
|
||||||
`VulkanSmokeRenderer`; teardown rejects validation warnings/errors and reports
|
`VulkanSmokeRenderer`; teardown rejects validation warnings/errors and reports
|
||||||
swapchain/readback telemetry. For each used `Batch20.material_index`, it resolves
|
swapchain/readback telemetry. For each used `Batch20.material_index`, it resolves
|
||||||
@@ -984,7 +984,7 @@ The bounded native preview now retains the already validated `Land.msh` inside
|
|||||||
`TerrainWorld`; this lets the application consume source geometry through the
|
`TerrainWorld`; this lets the application consume source geometry through the
|
||||||
runtime boundary instead of decoding archive formats a second time. It merges
|
runtime boundary instead of decoding archive formats a second time. It merges
|
||||||
one terrain component with the first TMA root's 14 reachable MSH components in
|
one terrain component with the first TMA root's 14 reachable MSH components in
|
||||||
one explicit top-down XZ frame. The frame is computed from terrain positions
|
one explicit top-down XY frame. The frame is computed from terrain positions
|
||||||
and the root models after applying only the decoded TMA `position` and `scale`.
|
and the root models after applying only the decoded TMA `position` and `scale`.
|
||||||
Raw TMA orientation is deliberately excluded: its convention is not proven.
|
Raw TMA orientation is deliberately excluded: its convention is not proven.
|
||||||
|
|
||||||
@@ -1005,7 +1005,7 @@ geometry and descriptor set changed; it is not an original-frame comparison.
|
|||||||
|
|
||||||
The native bridge now combines every mesh-backed visual of each selected root,
|
The native bridge now combines every mesh-backed visual of each selected root,
|
||||||
using that root's preserved TMA `position` and `scale` in the shared diagnostic
|
using that root's preserved TMA `position` and `scale` in the shared diagnostic
|
||||||
XZ frame. It reports the actual selected root count as `preview_roots`, rather
|
XY frame. It reports the actual selected root count as `preview_roots`, rather
|
||||||
than implying that all mission objects are rendered. Raw orientation, original
|
than implying that all mission objects are rendered. Raw orientation, original
|
||||||
camera/frustum and visibility remain unresolved.
|
camera/frustum and visibility remain unresolved.
|
||||||
|
|
||||||
@@ -1014,8 +1014,8 @@ in 59.7 seconds: 25 submitted MSH components, one terrain component and 26
|
|||||||
descriptors on the native 1280x720 two-image swapchain, with validation
|
descriptors on the native 1280x720 two-image swapchain, with validation
|
||||||
warnings/errors `0/0`. The readback FNV-1a remained
|
warnings/errors `0/0`. The readback FNV-1a remained
|
||||||
`10739087367165646439`, equal to the one-root terrain/root run. That equality
|
`10739087367165646439`, equal to the one-root terrain/root run. That equality
|
||||||
does **not** prove the second root is visible or equivalent: it is recorded as
|
does **not** prove the second root is visible or equivalent: it was recorded as
|
||||||
a camera/frustum/overlap investigation target, not hidden as a parity result.
|
an investigation target, not hidden as a parity result.
|
||||||
|
|
||||||
The reproducible `fparkan-cli mission inspect` probe closes two tempting but
|
The reproducible `fparkan-cli mission inspect` probe closes two tempting but
|
||||||
incorrect explanations for that equality. GOG `Autodemo.00` root 0 is
|
incorrect explanations for that equality. GOG `Autodemo.00` root 0 is
|
||||||
@@ -1023,11 +1023,20 @@ incorrect explanations for that equality. GOG `Autodemo.00` root 0 is
|
|||||||
`w_s_wlk1.dat` at `(479.12396, 795.95337, 1.6228507)`; therefore the TMA data
|
`w_s_wlk1.dat` at `(479.12396, 795.95337, 1.6228507)`; therefore the TMA data
|
||||||
is neither duplicate nor co-located. Separately, the Vulkan static submit loop
|
is neither duplicate nor co-located. Separately, the Vulkan static submit loop
|
||||||
calls `cmd_draw_indexed` for every prepared draw range and its depth comparison
|
calls `cmd_draw_indexed` for every prepared draw range and its depth comparison
|
||||||
is `LESS_OR_EQUAL`. The unresolved equality is consequently evidence about the
|
is `LESS_OR_EQUAL`. A later direct `Land.msh` bounds probe identified the actual
|
||||||
diagnostic camera/projection or visibility framing, not grounds to patch a
|
static-viewer defect: GOG `AutoMAP/Land.msh` spans X/Y
|
||||||
missing draw or a `LESS` depth bug. The inspector intentionally reports raw
|
`0..1190.6976` but Z only `0..94.50981`; the same TMA objects use X/Y for their
|
||||||
orientation as well as position and scale so later camera/transform work can
|
world placement and a small Z height. The old XZ CPU frame discarded the second
|
||||||
be checked against the original mission bytes.
|
horizontal TMA component and treated height as the screen axis. The renderer now
|
||||||
|
uses a shared XY CPU frame and retains Z as geometry height. This proves only
|
||||||
|
the source-axis convention needed by the diagnostic bridge; it does not recover
|
||||||
|
the original camera, orientation order, culling or projection matrix.
|
||||||
|
|
||||||
|
The new `fparkan-cli terrain inspect <Land.msh>` exposes these three-axis source
|
||||||
|
bounds without a renderer. The first XY GPU rechecks were intentionally bounded
|
||||||
|
to 120 seconds but remained at the existing `Graph` loading checkpoint and were
|
||||||
|
terminated without a native frame/report; therefore no new readback hash or GPU
|
||||||
|
acceptance result is claimed for the corrected projection yet.
|
||||||
|
|
||||||
### Camera ownership boundary from the GOG renderer
|
### Camera ownership boundary from the GOG renderer
|
||||||
|
|
||||||
@@ -1051,7 +1060,7 @@ This establishes that original camera creation/selection is Terrain-owned and
|
|||||||
not a field that can safely be inferred from a TMA transform. It does **not**
|
not a field that can safely be inferred from a TMA transform. It does **not**
|
||||||
yet establish world-to-view matrix layout, FOV, near/far values, projection
|
yet establish world-to-view matrix layout, FOV, near/far values, projection
|
||||||
handedness, or initial mission camera selection. The Vulkan path must therefore
|
handedness, or initial mission camera selection. The Vulkan path must therefore
|
||||||
continue to label its XZ projection as diagnostic until a dynamic capture or
|
continue to label its XY projection as diagnostic until a dynamic capture or
|
||||||
further Terrain disassembly proves these contracts.
|
further Terrain disassembly proves these contracts.
|
||||||
|
|
||||||
The public `World3D.dll!stdSetCurrentCamera` is a one-pointer `__stdcall`
|
The public `World3D.dll!stdSetCurrentCamera` is a one-pointer `__stdcall`
|
||||||
@@ -1070,7 +1079,7 @@ matrix convention; they must not be treated as a usable renderer ABI yet.
|
|||||||
|
|
||||||
`Land.msh` использует отдельный geometry-only bridge: validated `TerrainFace28`
|
`Land.msh` использует отдельный geometry-only bridge: validated `TerrainFace28`
|
||||||
сохраняет source triangle order, а его positions и packed UV0 попадают в тот же
|
сохраняет source triangle order, а его positions и packed UV0 попадают в тот же
|
||||||
static vertex/index upload path. Для текущего диагностического viewer XZ bounds
|
static vertex/index upload path. Для текущего диагностического viewer XY bounds
|
||||||
нормализуются в clip-space, packed UV0 декодируется как signed `int16 / 1024`.
|
нормализуются в clip-space, packed UV0 декодируется как signed `int16 / 1024`.
|
||||||
Один static draw range намеренно не трактует `material_tag`, surface mask, slots
|
Один static draw range намеренно не трактует `material_tag`, surface mask, slots
|
||||||
или auxiliary streams как material/pipeline state: их связь с original terrain
|
или auxiliary streams как material/pipeline state: их связь с original terrain
|
||||||
|
|||||||
Reference in New Issue
Block a user