use bevy_app::prelude::*;
use bevy_asset::Handle;
use bevy_camera::{prelude::*, visibility::RenderLayers};
use bevy_core_pipeline::Skybox;
use bevy_ecs::prelude::*;
use bevy_image::Image;
use bevy_math::Quat;
use bevy_reflect::prelude::*;
use bevy_render::view::{Hdr, Msaa};
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,
ApplyDeferred,
IndependentSkyboxCamera::update,
)
.chain(),
);
}
}
#[derive(Debug, Clone, Reflect, Component)]
pub struct IndependentSkybox {
pub skybox: Handle<Image>,
pub brightness: f32,
pub rotation: Quat,
pub skybox_cam_order_offset: isize,
pub fov: SkyboxFov,
skybox_cam: Option<Entity>,
}
impl IndependentSkybox {
pub fn new(skybox: Handle<Image>, brightness: f32, rotation: Quat) -> Self {
Self {
skybox,
brightness,
rotation,
..Default::default()
}
}
}
impl Default for IndependentSkybox {
fn default() -> Self {
Self {
skybox: Default::default(),
brightness: 500.0,
rotation: Quat::IDENTITY,
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, &Msaa)>,
skybox_cams: Query<&IndependentSkyboxCamera>,
) {
for (editor_cam_entity, mut editor_without_skybox, mut camera, msaa) 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;
commands.entity(editor_cam_entity).insert(Hdr);
let entity = commands
.spawn((
Camera3d::default(),
Hdr,
Camera {
order: camera.order + editor_without_skybox.skybox_cam_order_offset,
clear_color: ClearColorConfig::None,
..Default::default()
},
Projection::Perspective(PerspectiveProjection {
fov: match editor_without_skybox.fov {
SkyboxFov::Auto => PerspectiveProjection::default().fov,
SkyboxFov::Fixed(fov) => fov,
},
..Default::default()
}),
RenderLayers::none(),
Skybox {
image: editor_without_skybox.skybox.clone(),
brightness: editor_without_skybox.brightness,
rotation: editor_without_skybox.rotation,
},
IndependentSkyboxCamera {
driven_by: editor_cam_entity,
},
*msaa,
))
.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();
}
}
}
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.clone_from(&camera.viewport);
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;
}
}
}