use crate::ecs::EditorWorld;
use nightshade::ecs::material::material_registry_insert;
use nightshade::ecs::transform::commands::mark_local_transform_dirty;
use nightshade::ecs::world::{BOUNDING_VOLUME, GLOBAL_TRANSFORM, NAME, RENDER_MESH, VISIBILITY};
use nightshade::prelude::*;
const KHRONOS_SPONZA_TIED_CURTAINS: std::ops::RangeInclusive<u32> = 54..=61;
const KHRONOS_SPONZA_DRAPES: std::ops::RangeInclusive<u32> = 63..=72;
const RESOLUTION_PER_METER: f32 = 24.0;
const MIN_GRID: u32 = 16;
const MAX_GRID: u32 = 192;
struct CurtainCandidate {
entity: Entity,
minimum: Vec3,
maximum: Vec3,
material_name: Option<String>,
}
pub fn convert(editor_world: &mut EditorWorld, world: &mut World) {
let mut candidates: Vec<CurtainCandidate> = Vec::new();
world
.core
.query()
.with(RENDER_MESH | BOUNDING_VOLUME | GLOBAL_TRANSFORM | NAME)
.iter(|entity, table, index| {
if editor_world
.resources
.cloth_sponza
.converted
.contains(&entity)
{
return;
}
if !is_curtain_name(&table.name[index].0) {
return;
}
let (minimum, maximum) = world_bounds(
&table.bounding_volume[index].obb,
&table.global_transform[index].0,
);
if !is_sheet_shaped(minimum, maximum) {
return;
}
candidates.push(CurtainCandidate {
entity,
minimum,
maximum,
material_name: None,
});
});
for candidate in &mut candidates {
candidate.material_name = world
.core
.get_material_ref(candidate.entity)
.map(|material_ref| material_ref.name.clone());
}
for (curtain_index, candidate) in candidates.iter().enumerate() {
let extents = candidate.maximum - candidate.minimum;
let spans_z = extents.z > extents.x;
let width = extents.x.max(extents.z);
let height = extents.y;
let center = (candidate.minimum + candidate.maximum) / 2.0;
let top_center = Vec3::new(center.x, candidate.maximum.y, center.z);
let cloth_entity = spawn_cloth(
world,
Cloth {
columns: grid_resolution(width),
rows: grid_resolution(height),
width,
height,
pinning: ClothPinning::TopRow,
ground_height: None,
..Default::default()
},
top_center,
format!("Cloth Curtain {}", curtain_index),
);
if let Some(material_name) = &candidate.material_name {
apply_source_material(world, cloth_entity, material_name, curtain_index);
}
if spans_z && let Some(transform) = world.core.get_local_transform_mut(cloth_entity) {
transform.rotation = nalgebra_glm::quat_angle_axis(
std::f32::consts::FRAC_PI_2,
&Vec3::new(0.0, 1.0, 0.0),
);
}
mark_local_transform_dirty(world, cloth_entity);
if !world
.core
.entity_has_components(candidate.entity, VISIBILITY)
{
world.core.add_components(candidate.entity, VISIBILITY);
}
world
.core
.set_visibility(candidate.entity, Visibility { visible: false });
editor_world
.resources
.cloth_sponza
.converted
.push(candidate.entity);
}
}
fn apply_source_material(
world: &mut World,
cloth_entity: Entity,
source_material_name: &str,
curtain_index: usize,
) {
let Some(source_material) = registry_entry_by_name(
&world.resources.assets.material_registry.registry,
source_material_name,
)
.cloned() else {
return;
};
let cloth_material = Material {
double_sided: true,
..source_material
};
let cloth_material_name = format!("Cloth Sponza Curtain {}", curtain_index);
material_registry_insert(
&mut world.resources.assets.material_registry,
cloth_material_name.clone(),
cloth_material,
);
if let Some(&index) = world
.resources
.assets
.material_registry
.registry
.name_to_index
.get(&cloth_material_name)
{
registry_add_reference(
&mut world.resources.assets.material_registry.registry,
index,
);
}
world
.core
.set_material_ref(cloth_entity, MaterialRef::new(cloth_material_name));
}
fn is_curtain_name(name: &str) -> bool {
let lowered = name.to_lowercase();
if lowered.contains("curtain") || lowered.contains("fabric") || lowered.contains("drape") {
return true;
}
name.strip_prefix("Primitive_")
.and_then(|suffix| suffix.parse::<u32>().ok())
.is_some_and(|index| {
KHRONOS_SPONZA_TIED_CURTAINS.contains(&index) || KHRONOS_SPONZA_DRAPES.contains(&index)
})
}
fn world_bounds(
obb: &nightshade::ecs::bounding_volume::components::OrientedBoundingBox,
global_transform: &Mat4,
) -> (Vec3, Vec3) {
let mut minimum = Vec3::new(f32::MAX, f32::MAX, f32::MAX);
let mut maximum = Vec3::new(f32::MIN, f32::MIN, f32::MIN);
for corner_index in 0..8 {
let sign = Vec3::new(
if corner_index & 1 == 0 { -1.0 } else { 1.0 },
if corner_index & 2 == 0 { -1.0 } else { 1.0 },
if corner_index & 4 == 0 { -1.0 } else { 1.0 },
);
let offset = nalgebra_glm::quat_rotate_vec3(
&obb.orientation,
&obb.half_extents.component_mul(&sign),
);
let local = obb.center + offset;
let world = global_transform * Vec4::new(local.x, local.y, local.z, 1.0);
minimum = nalgebra_glm::min2(&minimum, &world.xyz());
maximum = nalgebra_glm::max2(&maximum, &world.xyz());
}
(minimum, maximum)
}
fn is_sheet_shaped(minimum: Vec3, maximum: Vec3) -> bool {
let extents = maximum - minimum;
let thin = extents.x.min(extents.z);
let span = extents.x.max(extents.z);
thin < span * 0.4 && extents.y > 0.3 && span > 0.3
}
fn grid_resolution(size: f32) -> u32 {
((size * RESOLUTION_PER_METER) as u32).clamp(MIN_GRID, MAX_GRID)
}