use glam::Vec3; use crate::world::World; const CAMERA_LAG: f32 = 4.0; const VERTICAL_BIAS: f32 = 0.4; const MIN_DISTANCE: f32 = 8.0; const MAX_DISTANCE: f32 = 24.0; pub fn dialog_camera_system(world: &mut World, delta: f32) { let Some((camera_entity, _)) = world.active_camera() else { return; }; let player_pos = world.player_position(); let character_positions: Vec = world .bubble_tags .all() .iter() .filter_map(|&bubble| { let char_entity = world.dialog_bubbles.with(bubble, |b| b.character_entity)?; world.transforms.with(char_entity, |t| t.position) }) .collect(); if character_positions.is_empty() { return; } let all_positions: Vec = std::iter::once(player_pos) .chain(character_positions.iter().copied()) .collect(); let centroid = all_positions.iter().copied().fold(Vec3::ZERO, |a, b| a + b) / all_positions.len() as f32; let max_spread = all_positions .iter() .map(|p| (*p - centroid).length()) .fold(0.0f32, f32::max); let camera_distance = (max_spread * 1.8 + MIN_DISTANCE).min(MAX_DISTANCE); let to_player = (player_pos - centroid).normalize_or(Vec3::Z); let camera_back_dir = Vec3::new(to_player.x, 0.0, to_player.z).normalize_or(Vec3::Z); let target_camera_pos = centroid + camera_back_dir * camera_distance + Vec3::Y * camera_distance * VERTICAL_BIAS; let current_camera_pos = world .transforms .with(camera_entity, |t| t.position) .unwrap_or(target_camera_pos); let smoothed = current_camera_pos.lerp(target_camera_pos, (CAMERA_LAG * delta).min(1.0)); world.transforms.with_mut(camera_entity, |t| { t.position = smoothed; }); let look_target = centroid + Vec3::Y * 1.0; if let Some(camera) = world.cameras.get_mut(camera_entity) { let look_dir = (look_target - smoothed).normalize_or(-Vec3::Z); camera.yaw = look_dir.z.atan2(look_dir.x); camera.pitch = look_dir.y.asin(); } }