use concinnity_core::components::{GlassPanel, ReflectionProbe, WaterSurface};
use concinnity_core::ecs::PipelineContext;
use concinnity_core::gfx::mesh_payload::Vertex;
use concinnity_core::gfx::render_types;
use concinnity_core::render::reflection_probe::{self, ProbePlacement};
pub(super) fn draw_object_position(obj: &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]]
}
}
const AUTO_SEED_MAX_TRIANGLES: usize = 200_000;
pub(super) fn gather_auto_seed_triangles(
draw_objects: &[render_types::DrawObject],
all_vertices: &[Vertex],
all_indices: &[u32],
) -> Option<Vec<[[f32; 3]; 3]>> {
let eligible = |o: &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 declared_probe_placements(ctx: &PipelineContext) -> Vec<ProbePlacement> {
ctx.query::<ReflectionProbe>()
.map(|p| ProbePlacement::from_center_extents(p.position, p.half_extents))
.collect()
}
pub(super) fn auto_seed_probe_placements(
draw_objects: &[render_types::DrawObject],
vertices: &[Vertex],
indices: &[u32],
water_surfaces: &[WaterSurface],
glass_panels: &[GlassPanel],
) -> Option<Vec<ProbePlacement>> {
let occupancy: Vec<([f32; 3], [f32; 3])> = draw_objects
.iter()
.map(|o| (o.bb_min, o.bb_max))
.filter(|(mn, mx)| mn.iter().chain(mx).all(|c| c.is_finite()))
.collect();
let reflectors = water_surfaces
.iter()
.map(|w| reflection_probe::reflector_bounds(w.center, [w.extent[0], 0.0, w.extent[1]]))
.chain(glass_panels.iter().map(|g| {
let r = g.half_size[0].max(g.half_size[1]);
reflection_probe::reflector_bounds(g.center, [r, r, r])
}));
let tris = gather_auto_seed_triangles(draw_objects, vertices, indices).unwrap_or_default();
reflection_probe::fold_world_bounds(occupancy.iter().copied().chain(reflectors))
.map(|(mn, mx)| reflection_probe::auto_seed_probes_with_geometry(mn, mx, &occupancy, &tris))
}
#[cfg(test)]
mod tests {
use super::*;
use concinnity_core::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 draw_object_position_uses_aabb_center_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 auto_seed_probe_placements_none_without_geometry_or_reflectors() {
let unbounded = vec![draw(IDENTITY, [f32::NAN; 3], [f32::NAN; 3], 0, 0, 0)];
assert!(auto_seed_probe_placements(&[], &[], &[], &[], &[]).is_none());
assert!(auto_seed_probe_placements(&unbounded, &[], &[], &[], &[]).is_none());
}
#[test]
fn auto_seed_probe_placements_widen_the_grid_to_a_reflector() {
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])];
let idx = [0u32, 1, 2];
let reach = |placements: Vec<ProbePlacement>| {
placements
.iter()
.map(|p| p.box_max[0])
.fold(f32::NEG_INFINITY, f32::max)
};
let alone = auto_seed_probe_placements(&objs, &verts, &idx, &[], &[]).expect("geometry");
let pool = WaterSurface {
center: [50.0, 0.0, 0.0],
extent: [5.0, 5.0],
..Default::default()
};
let widened = auto_seed_probe_placements(&objs, &verts, &idx, &[pool], &[])
.expect("geometry and a reflector");
assert!(reach(alone) <= 1.0);
assert!(reach(widened) >= 55.0);
}
}