use crate::components::BlockType;
use crate::gfx::mesh_payload::Vertex;
pub(super) fn block_type_to_chunk(bt: &BlockType) -> crate::geometry::ChunkBlockType {
let default_rect = [bt.uv_min[0], bt.uv_min[1], bt.uv_max[0], bt.uv_max[1]];
crate::geometry::ChunkBlockType {
solid: bt.solid,
uv_top: bt.uv_top.unwrap_or(default_rect),
uv_bottom: bt.uv_bottom.unwrap_or(default_rect),
uv_side: bt.uv_side.unwrap_or(default_rect),
}
}
pub(super) fn draw_object_position(obj: &crate::gfx::render_types::DrawObject) -> [f32; 3] {
let finite = obj
.bb_min
.iter()
.chain(obj.bb_max.iter())
.all(|v| v.is_finite());
if finite {
[
0.5 * (obj.bb_min[0] + obj.bb_max[0]),
0.5 * (obj.bb_min[1] + obj.bb_max[1]),
0.5 * (obj.bb_min[2] + obj.bb_max[2]),
]
} else {
[obj.model[3][0], obj.model[3][1], obj.model[3][2]]
}
}
pub(super) const AUTO_SEED_MAX_TRIANGLES: usize = 200_000;
pub(super) fn gather_auto_seed_triangles(
draw_objects: &[crate::gfx::render_types::DrawObject],
all_vertices: &[Vertex],
all_indices: &[u32],
) -> Option<Vec<[[f32; 3]; 3]>> {
let eligible = |o: &crate::gfx::render_types::DrawObject| o.cullable() && o.index_count >= 3;
let total_tris: usize = draw_objects
.iter()
.filter(|o| eligible(o))
.map(|o| o.index_count / 3)
.sum();
if total_tris == 0 || total_tris > AUTO_SEED_MAX_TRIANGLES {
return None;
}
let xf = |m: &[[f32; 4]; 4], p: [f32; 3]| {
[
m[0][0] * p[0] + m[1][0] * p[1] + m[2][0] * p[2] + m[3][0],
m[0][1] * p[0] + m[1][1] * p[1] + m[2][1] * p[2] + m[3][1],
m[0][2] * p[0] + m[1][2] * p[1] + m[2][2] * p[2] + m[3][2],
]
};
let mut tris = Vec::with_capacity(total_tris);
for o in draw_objects.iter().filter(|o| eligible(o)) {
let iend = o.index_offset + o.index_count;
if iend > all_indices.len() {
continue;
}
for t in all_indices[o.index_offset..iend].chunks_exact(3) {
let vi = |k: usize| (t[k] as i64 + o.base_vertex as i64) as usize;
let (a, b, c) = (vi(0), vi(1), vi(2));
if a >= all_vertices.len() || b >= all_vertices.len() || c >= all_vertices.len() {
continue;
}
tris.push([
xf(&o.model, all_vertices[a].pos),
xf(&o.model, all_vertices[b].pos),
xf(&o.model, all_vertices[c].pos),
]);
}
}
(!tris.is_empty()).then_some(tris)
}
pub(super) fn build_texture_payload_source(
payloads: Vec<Vec<u8>>,
locators: &[crate::ecs::PayloadLocator],
disk_backed: bool,
) -> Result<std::sync::Arc<dyn crate::gfx::streaming::texture::PayloadSource>, String> {
if !disk_backed {
return Ok(std::sync::Arc::new(
crate::gfx::streaming::texture::MemPayloadSource::new(payloads),
));
}
let mut section_starts: std::collections::HashMap<u32, u64> = std::collections::HashMap::new();
let mut disk_locators = Vec::with_capacity(locators.len());
for loc in locators {
let path = crate::blob::blob_path(loc.blob_index)
.ok_or_else(|| format!("blob {}: no blob layout installed", loc.blob_index))?;
let start = match section_starts.get(&loc.blob_index) {
Some(&s) => s,
None => {
let s = crate::blob::payload_section_start(&path)
.map_err(|e| format!("blob {}: {:?}", loc.blob_index, e))?;
section_starts.insert(loc.blob_index, s);
s
}
};
disk_locators.push(crate::gfx::streaming::texture::DiskTextureLocator {
path,
file_offset: start + loc.offset,
len: loc.len,
});
}
Ok(std::sync::Arc::new(
crate::gfx::streaming::texture::DiskPayloadSource::new(disk_locators),
))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::gfx::render_types::{DrawObject, MaterialUniforms, NO_NORMAL_MAP_SLOT};
fn draw(
model: [[f32; 4]; 4],
bb_min: [f32; 3],
bb_max: [f32; 3],
index_offset: usize,
index_count: usize,
base_vertex: i32,
) -> DrawObject {
DrawObject {
vertex_offset: 0,
vertex_count: 0,
index_offset,
index_count,
base_vertex,
geometry_generation: 0,
shader_bucket: 0,
model,
texture_slot: 0,
normal_map_slot: NO_NORMAL_MAP_SLOT,
material: MaterialUniforms::DEFAULT,
visible: true,
resident: true,
bb_min,
bb_max,
cull_distance: 0.0,
lod_alternates: Vec::new(),
}
}
fn vert(pos: [f32; 3]) -> Vertex {
Vertex {
pos,
normal: [0.0, 1.0, 0.0],
tangent: [1.0, 0.0, 0.0],
color: [1.0, 1.0, 1.0],
uv: [0.0, 0.0],
}
}
const IDENTITY: [[f32; 4]; 4] = [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
];
#[test]
fn block_type_to_chunk_overrides_then_falls_back() {
let bt = BlockType {
solid: true,
uv_min: [0.1, 0.2],
uv_max: [0.6, 0.7],
uv_top: Some([0.0, 0.0, 0.25, 0.25]),
uv_bottom: None,
uv_side: None,
..Default::default()
};
let chunk = block_type_to_chunk(&bt);
assert!(chunk.solid);
assert_eq!(chunk.uv_top, [0.0, 0.0, 0.25, 0.25], "override kept");
let fallback = [0.1, 0.2, 0.6, 0.7];
assert_eq!(
chunk.uv_bottom, fallback,
"unset face -> uv_min/uv_max rect"
);
assert_eq!(chunk.uv_side, fallback);
}
#[test]
fn block_type_to_chunk_air_is_not_solid() {
let bt = BlockType {
solid: false,
..Default::default()
};
assert!(!block_type_to_chunk(&bt).solid);
}
#[test]
fn draw_object_position_uses_aabb_centre_when_finite() {
let obj = draw(IDENTITY, [-2.0, 0.0, 4.0], [4.0, 6.0, 8.0], 0, 0, 0);
assert_eq!(draw_object_position(&obj), [1.0, 3.0, 6.0]);
}
#[test]
fn draw_object_position_uses_model_translation_when_unbounded() {
let model = [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[7.0, 8.0, 9.0, 1.0],
];
let obj = draw(model, [f32::NAN; 3], [f32::NAN; 3], 0, 0, 0);
assert_eq!(draw_object_position(&obj), [7.0, 8.0, 9.0]);
}
#[test]
fn gather_auto_seed_triangles_transforms_by_model_and_base_vertex() {
let model = [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[10.0, 0.0, 0.0, 1.0],
];
let verts = vec![
vert([0.0, 0.0, 0.0]),
vert([0.0, 0.0, 0.0]),
vert([1.0, 0.0, 0.0]),
vert([0.0, 0.0, 1.0]),
];
let idx = vec![0u32, 1, 2];
let objs = vec![draw(model, [-1.0; 3], [1.0; 3], 0, 3, 1)];
let tris = gather_auto_seed_triangles(&objs, &verts, &idx).expect("one triangle");
assert_eq!(tris.len(), 1);
assert_eq!(tris[0][0], [10.0, 0.0, 0.0]);
assert_eq!(tris[0][1], [11.0, 0.0, 0.0]);
assert_eq!(tris[0][2], [10.0, 0.0, 1.0]);
}
#[test]
fn gather_auto_seed_triangles_none_without_cullable_geometry() {
let objs = vec![draw(IDENTITY, [f32::NAN; 3], [f32::NAN; 3], 0, 3, 0)];
let verts = vec![vert([0.0; 3]), vert([1.0, 0.0, 0.0]), vert([0.0, 0.0, 1.0])];
assert!(gather_auto_seed_triangles(&objs, &verts, &[0, 1, 2]).is_none());
}
#[test]
fn gather_auto_seed_triangles_skips_out_of_range_index_span() {
let objs = vec![draw(IDENTITY, [-1.0; 3], [1.0; 3], 0, 6, 0)];
let verts = vec![vert([0.0; 3]), vert([1.0, 0.0, 0.0]), vert([0.0, 0.0, 1.0])];
assert!(gather_auto_seed_triangles(&objs, &verts, &[0, 1, 2]).is_none());
}
#[test]
fn gather_auto_seed_triangles_skips_out_of_range_vertex_index() {
let objs = vec![draw(IDENTITY, [-1.0; 3], [1.0; 3], 0, 3, 0)];
let verts = vec![vert([0.0; 3]), vert([1.0, 0.0, 0.0]), vert([0.0, 0.0, 1.0])];
assert!(gather_auto_seed_triangles(&objs, &verts, &[0, 1, 9]).is_none());
}
#[test]
fn build_texture_payload_source_mem_backed_decodes_payload() {
let payload = crate::build::texture::serialise(
&crate::build::texture::TextureImage::rgba8(1, 1, vec![0x11, 0x22, 0x33, 0xFF]),
);
let src = build_texture_payload_source(vec![payload], &[], false).expect("mem source");
let decoded = src.fetch(0).expect("decodes item 0");
assert_eq!((decoded.image.width(), decoded.image.height()), (1, 1));
assert_eq!(decoded.image.mips[0].data, vec![0x11, 0x22, 0x33, 0xFF]);
assert!(src.fetch(1).is_err());
}
}