feat(terrain): bind map base textures

This commit is contained in:
2026-07-18 16:41:53 +04:00
parent e0f0e4a18e
commit 4df4686509
6 changed files with 223 additions and 26 deletions
@@ -420,8 +420,10 @@ fn rotate_by_quaternion(position: [f32; 3], rotation: [f32; 4]) -> [f32; 3] {
/// Projects validated `Land.msh` terrain geometry into the static Vulkan path.
///
/// This bridge preserves the source triangle order from `TerrainFace28` and
/// consumes its validated position and UV0 streams. It is deliberately a
/// geometry-only terrain slice: source slot/material selection, camera
/// consumes its validated position and UV0 streams. Contiguous source faces are
/// retained as separate draw ranges keyed by their exact packed
/// `TerrainFace28.material_tag`, so a caller can resolve the map-local
/// material contract without changing topology. Slot selection, camera
/// transforms, fog and surface-mask shading need their own evidence before
/// they can be modeled as renderer state.
///
@@ -592,16 +594,32 @@ fn static_terrain_indices(terrain: &LandMeshDocument) -> Result<Vec<u32>, Vulkan
fn static_terrain_draw_ranges(
terrain: &LandMeshDocument,
) -> Result<Vec<VulkanStaticDrawRange>, VulkanAssetMeshError> {
Ok(vec![VulkanStaticDrawRange {
first_index: 0,
index_count: u32::try_from(terrain.faces.len())
.map_err(|_| VulkanAssetMeshError::IndexOutOfRange)?
.checked_mul(3)
.ok_or(VulkanAssetMeshError::IndexOutOfRange)?,
material_index: 0,
pipeline_state: LegacyPipelineState::default(),
alpha_test_reference: 0,
}])
let mut ranges = Vec::new();
let mut start_face = 0usize;
while start_face < terrain.faces.len() {
let material_index = terrain.faces[start_face].material_tag;
let mut end_face = start_face + 1;
while end_face < terrain.faces.len()
&& terrain.faces[end_face].material_tag == material_index
{
end_face += 1;
}
ranges.push(VulkanStaticDrawRange {
first_index: u32::try_from(start_face)
.map_err(|_| VulkanAssetMeshError::IndexOutOfRange)?
.checked_mul(3)
.ok_or(VulkanAssetMeshError::IndexOutOfRange)?,
index_count: u32::try_from(end_face - start_face)
.map_err(|_| VulkanAssetMeshError::IndexOutOfRange)?
.checked_mul(3)
.ok_or(VulkanAssetMeshError::IndexOutOfRange)?,
material_index,
pipeline_state: LegacyPipelineState::default(),
alpha_test_reference: 0,
});
start_face = end_face;
}
Ok(ranges)
}
fn static_model_indices_and_ranges(
@@ -1109,9 +1127,13 @@ mod tests {
}
fn terrain_face(vertices: [u16; 3]) -> TerrainFace28 {
terrain_face_with_tag(0, vertices)
}
fn terrain_face_with_tag(material_tag: u16, vertices: [u16; 3]) -> TerrainFace28 {
TerrainFace28 {
flags: FullSurfaceMask(0),
material_tag: 0,
material_tag,
aux_tag: 0,
vertices,
neighbors: [None; 3],
@@ -1119,4 +1141,41 @@ mod tests {
raw: [0; 28],
}
}
#[test]
fn terrain_ranges_preserve_face_order_and_packed_material_tags() {
let terrain = LandMeshDocument {
streams: Vec::new(),
nodes_raw: Vec::new(),
slots: TerrainSlotTable {
header_raw: Vec::new(),
slots_raw: Vec::new(),
},
positions: vec![[0.0, 0.0, 0.0]; 3],
normals: Vec::new(),
uv0: vec![[0, 0]; 3],
accelerator: Vec::new(),
aux14: Vec::new(),
aux18: Vec::new(),
faces: vec![
terrain_face_with_tag(0xff01, [0, 1, 2]),
terrain_face_with_tag(0xff01, [0, 1, 2]),
terrain_face_with_tag(0x0102, [0, 1, 2]),
terrain_face_with_tag(0xff01, [0, 1, 2]),
],
};
let ranges = static_terrain_draw_ranges(&terrain).expect("representable terrain");
assert_eq!(ranges.len(), 3);
assert_eq!(ranges[0].first_index, 0);
assert_eq!(ranges[0].index_count, 6);
assert_eq!(ranges[0].material_index, 0xff01);
assert_eq!(ranges[1].first_index, 6);
assert_eq!(ranges[1].index_count, 3);
assert_eq!(ranges[1].material_index, 0x0102);
assert_eq!(ranges[2].first_index, 9);
assert_eq!(ranges[2].index_count, 3);
assert_eq!(ranges[2].material_index, 0xff01);
}
}