use bevy_app::prelude::*;
use bevy_asset::Handle;
use bevy_core_pipeline::{prelude::*, Skybox};
use bevy_ecs::prelude::*;
use bevy_reflect::prelude::*;
use bevy_render::{prelude::*, view::RenderLayers};
use bevy_transform::prelude::*;
pub struct IndependentSkyboxPlugin;
impl Plugin for IndependentSkyboxPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
Update,
(
IndependentSkyboxCamera::spawn,
IndependentSkyboxCamera::despawn,
apply_deferred,
IndependentSkyboxCamera::update,
)
.chain(),
)
.register_type::<IndependentSkybox>();
}
}
#[derive(Debug, Clone, Reflect, Component)]
pub struct IndependentSkybox {
pub skybox: Handle<Image>,
pub brightness: f32,
pub skybox_cam_order_offset: isize,
pub fov: SkyboxFov,
skybox_cam: Option<Entity>,
}
impl IndependentSkybox {
pub fn new(skybox: Handle<Image>, brightness: f32) -> Self {
Self {
skybox,
brightness,
..Default::default()
}
}
}
impl Default for IndependentSkybox {
fn default() -> Self {
Self {
skybox: Default::default(),
brightness: 500.0,
skybox_cam_order_offset: -1_000,
fov: Default::default(),
skybox_cam: Default::default(),
}
}
}
#[derive(Debug, Clone, Reflect)]
pub enum SkyboxFov {
Auto,
Fixed(f32),
}
impl Default for SkyboxFov {
fn default() -> Self {
Self::Fixed(PerspectiveProjection::default().fov)
}
}
#[derive(Component)]
pub struct IndependentSkyboxCamera {
driven_by: Entity,
}
impl IndependentSkyboxCamera {
pub fn spawn(
mut commands: Commands,
mut editor_cams: Query<(Entity, &mut IndependentSkybox, &mut Camera)>,
skybox_cams: Query<&IndependentSkyboxCamera>,
) {
for (editor_cam_entity, mut editor_without_skybox, mut camera) in
editor_cams.iter_mut().filter(|(_, config, ..)| {
config
.skybox_cam
.and_then(|e| skybox_cams.get(e).ok())
.is_none()
})
{
camera.clear_color = ClearColorConfig::None;
camera.hdr = true;
let entity = commands
.spawn((
Camera3dBundle {
camera: Camera {
order: camera.order + editor_without_skybox.skybox_cam_order_offset,
hdr: true,
clear_color: ClearColorConfig::None,
..Default::default()
},
projection: Projection::Perspective(PerspectiveProjection {
fov: match editor_without_skybox.fov {
SkyboxFov::Auto => PerspectiveProjection::default().fov,
SkyboxFov::Fixed(fov) => fov,
},
..Default::default()
}),
..Default::default()
},
RenderLayers::none(),
Skybox {
image: editor_without_skybox.skybox.clone(),
brightness: editor_without_skybox.brightness,
},
IndependentSkyboxCamera {
driven_by: editor_cam_entity,
},
))
.id();
editor_without_skybox.skybox_cam = Some(entity);
}
}
pub fn despawn(
mut commands: Commands,
skybox_cams: Query<(Entity, &IndependentSkyboxCamera)>,
editor_cams: Query<&IndependentSkybox>,
) {
for (skybox_entity, skybox) in &skybox_cams {
if editor_cams.get(skybox.driven_by).is_err() {
commands.entity(skybox_entity).despawn();
}
}
}
#[allow(clippy::type_complexity)]
pub fn update(
mut editor_cams: Query<
(&IndependentSkybox, &Transform, &Projection, &Camera),
(
Or<(Changed<IndependentSkybox>, Changed<Transform>)>,
Without<Self>,
),
>,
mut skybox_cams: Query<(&mut Transform, &mut Projection, &mut Camera), With<Self>>,
) {
for (editor_cam, editor_transform, editor_projection, camera) in &mut editor_cams {
let Some(skybox_entity) = editor_cam.skybox_cam else {
continue;
};
let Ok((mut skybox_transform, mut skybox_projection, mut skybox_camera)) =
skybox_cams.get_mut(skybox_entity)
else {
continue;
};
skybox_camera.viewport = camera.viewport.clone();
if let Projection::Perspective(editor_perspective) = editor_projection {
*skybox_projection = Projection::Perspective(PerspectiveProjection {
fov: match editor_cam.fov {
SkyboxFov::Auto => editor_perspective.fov,
SkyboxFov::Fixed(fov) => fov,
},
..editor_perspective.clone()
})
}
*skybox_transform = *editor_transform;
}
}
}