1use nightshade::prelude::*;
6use serde::{Deserialize, Serialize};
7
8#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
11pub struct Bounds {
12 pub center: Vec3,
13 pub radius: f32,
14}
15
16pub fn bounds(world: &World, entity: Entity) -> Option<Bounds> {
19 let bounding_volume =
20 world.get::<nightshade::render::bounding_volume::BoundingVolume>(entity)?;
21 let global = world.get::<nightshade::ecs::transform::components::GlobalTransform>(entity)?;
22 let transformed = bounding_volume.transform(&global.0);
23 Some(Bounds {
24 center: transformed.obb.center,
25 radius: transformed.sphere_radius,
26 })
27}
28
29pub fn bounds_of(world: &World, entities: &[Entity]) -> Option<Bounds> {
32 let mut combined: Option<Bounds> = None;
33 for &entity in entities {
34 if let Some(next) = bounds(world, entity) {
35 combined = Some(match combined {
36 Some(current) => merge(current, next),
37 None => next,
38 });
39 }
40 }
41 combined
42}
43
44pub fn frame_entities(world: &mut World, entities: &[Entity]) {
48 let Some(bounds) = bounds_of(world, entities) else {
49 return;
50 };
51 let Some(camera) = world
52 .res::<nightshade::ecs::camera::resources::ActiveCamera>()
53 .0
54 else {
55 return;
56 };
57 if let Some(orbit) =
58 world.get_mut::<nightshade::ecs::camera::components::PanOrbitCamera>(camera)
59 {
60 orbit.target_focus = bounds.center;
61 orbit.target_radius = (bounds.radius * 2.5).max(0.5);
62 }
63}
64
65fn merge(a: Bounds, b: Bounds) -> Bounds {
66 let offset = b.center - a.center;
67 let distance = offset.magnitude();
68 if distance + b.radius <= a.radius {
69 return a;
70 }
71 if distance + a.radius <= b.radius {
72 return b;
73 }
74 if distance < f32::EPSILON {
75 return Bounds {
76 center: a.center,
77 radius: a.radius.max(b.radius),
78 };
79 }
80 let radius = (a.radius + distance + b.radius) * 0.5;
81 let center = a.center + offset * ((radius - a.radius) / distance);
82 Bounds { center, radius }
83}