use concinnity_core::components::Transform;
use concinnity_core::ecs::PickIndex;
use concinnity_core::ecs::World;
use concinnity_core::ecs::asset_id::AssetId;
use concinnity_core::math::pick::{PickRay, ray_aabb};
use super::EditorHook;
use super::drag;
use crate::editor::viewport::gizmo::GizmoMode;
const REST_EPSILON: f32 = 1e-3;
impl EditorHook {
pub(super) fn drop_selection_to_floor(&mut self, world: &mut World) -> usize {
let members = self.selected();
let selected_ids: std::collections::BTreeSet<AssetId> = members
.iter()
.filter_map(|h| self.handle_asset_id(h))
.collect();
let mut changed = Vec::new();
for handle in &members {
let Some(target) = self.member_target(world, GizmoMode::Translate, handle) else {
continue;
};
let Some(position) = world.get::<Transform>(target.entity).map(|t| t.position) else {
continue;
};
let bounds = self.handle_asset_id(handle).and_then(|id| {
world
.resource::<PickIndex>()?
.entries
.iter()
.find(|e| e.asset_id == id)
.map(|e| (e.bb_min, e.bb_max))
});
let foot = match bounds {
Some((mn, mx)) => [(mn[0] + mx[0]) * 0.5, mn[1], (mn[2] + mx[2]) * 0.5],
None => position,
};
let floor_y = nearest_floor(world, foot, &selected_ids).unwrap_or(0.0);
let delta = floor_y - foot[1];
if delta.abs() <= REST_EPSILON {
continue;
}
let landed = [position[0], position[1] + delta, position[2]];
if let Some(tr) = world.get_mut::<Transform>(target.entity) {
tr.position = landed;
}
Self::write_arg(
&mut self.entries,
target.key,
"position",
landed.map(drag::gizmo::round3),
);
changed.push(target.key);
}
if changed.is_empty() {
return 0;
}
self.mark_changed();
changed.len()
}
}
fn nearest_floor(
world: &World,
from: [f32; 3],
exclude: &std::collections::BTreeSet<AssetId>,
) -> Option<f32> {
let index = world.resource::<PickIndex>()?;
let ray = PickRay {
origin: from,
dir: [0.0, -1.0, 0.0],
};
index
.entries
.iter()
.filter(|e| !exclude.contains(&e.asset_id))
.filter(|e| e.bb_max[1] <= from[1])
.filter_map(|e| ray_aabb(&ray, e.bb_min, e.bb_max))
.map(|t| from[1] - t)
.max_by(f32::total_cmp)
}