use std::sync::RwLock;
use bevy::asset::{Asset, AssetId, Assets, Handle};
use bevy::ecs::prelude::*;
use bevy::math::{Vec3, Vec4};
use bevy::reflect::Reflect;
use bevy::render::storage::ShaderBuffer;
use bevy::shader::Shader;
use bevy::transform::components::GlobalTransform;
use crate::gpu::GrowthMaterial;
use crate::impostor::ImpostorMaterial;
use crate::shading::{FloraMaterial, FloraWindBuffer, TreeShakes};
#[derive(Component, Clone, Copy, Debug, PartialEq, Reflect)]
#[reflect(Component)]
pub struct SeeThroughTrees {
pub target: Vec3,
pub radius: f32,
}
impl Default for SeeThroughTrees {
fn default() -> Self {
Self { target: Vec3::ZERO, radius: 1.5 }
}
}
pub(crate) const SEE_THROUGH_SLOT: usize = 7 + 2 * TreeShakes::MAX;
#[derive(Resource, Clone, Copy, Debug, Default, PartialEq)]
pub(crate) struct SeeThroughCapsule(pub [Vec4; 2]);
impl SeeThroughCapsule {
fn of(camera: Option<(&SeeThroughTrees, &GlobalTransform)>) -> Self {
match camera {
Some((fade, at)) => Self([at.translation().extend(fade.radius.max(0.0)), fade.target.extend(1.0)]),
None => Self::default(),
}
}
}
pub(crate) fn sync_see_through(
cameras: Query<(&SeeThroughTrees, &GlobalTransform)>,
mut capsule: ResMut<SeeThroughCapsule>,
buffer: Option<Res<FloraWindBuffer>>,
buffers: Option<ResMut<Assets<ShaderBuffer>>>,
) {
let now = SeeThroughCapsule::of(cameras.iter().next());
if *capsule == now {
return;
}
*capsule = now;
let (Some(buffer), Some(mut buffers)) = (buffer, buffers) else { return };
let Some(mut shader_buffer) = buffers.get_mut(&buffer.0) else { return };
if let Some(slice) = shader_buffer.cast_slice_mut::<[f32; 4]>()
&& slice.len() >= SEE_THROUGH_SLOT + 2
{
slice[SEE_THROUGH_SLOT] = now.0[0].to_array();
slice[SEE_THROUGH_SLOT + 1] = now.0[1].to_array();
}
}
pub(crate) fn arm_see_through(
cameras: Query<(), With<SeeThroughTrees>>,
flora: Option<ResMut<Assets<FloraMaterial>>>,
growth: Option<ResMut<Assets<GrowthMaterial>>>,
impostors: Option<ResMut<Assets<ImpostorMaterial>>>,
) {
let on = !cameras.is_empty();
if let Some(mut assets) = flora {
arm(&mut assets, on, |m| m.extension.see_through, |m, on| m.extension.see_through = on);
}
if let Some(mut assets) = growth {
arm(&mut assets, on, |m| m.extension.see_through, |m, on| m.extension.see_through = on);
}
if let Some(mut assets) = impostors {
arm(&mut assets, on, |m| m.see_through, |m, on| m.see_through = on);
}
}
fn arm<A: Asset>(assets: &mut Assets<A>, on: bool, flag: fn(&A) -> bool, set: fn(&mut A, bool)) {
let stale: Vec<AssetId<A>> = assets.iter().filter(|(_, material)| flag(material) != on).map(|(id, _)| id).collect();
for id in stale {
if let Some(mut material) = assets.get_mut(id) {
set(&mut material, on);
}
}
}
static PREPASS_FRAGMENT: RwLock<Option<Handle<Shader>>> = RwLock::new(None);
pub(crate) fn set_prepass_fragment(shader: Handle<Shader>) {
if let Ok(mut slot) = PREPASS_FRAGMENT.write() {
*slot = Some(shader);
}
}
pub(crate) fn prepass_fragment() -> Option<Handle<Shader>> {
PREPASS_FRAGMENT.read().ok()?.clone()
}
#[cfg(test)]
mod tests {
use bevy::app::App;
use bevy::asset::{AssetApp, AssetPlugin};
use bevy::mesh::Mesh;
use bevy::pbr::StandardMaterial;
use super::*;
use crate::shading::{FloraMaterialSupport, FloraWind};
use crate::{FloraPlugin, ProceduralTree, TreePreset};
fn app() -> App {
let mut app = App::new();
app.add_plugins((bevy::MinimalPlugins, AssetPlugin::default()));
app.init_asset::<Mesh>();
app.init_asset::<StandardMaterial>();
app.add_plugins(FloraPlugin);
app.insert_resource(FloraMaterialSupport);
app
}
fn flags(app: &App) -> Vec<bool> {
app.world().resource::<Assets<FloraMaterial>>().iter().map(|(_, m)| m.extension.see_through).collect()
}
fn slots(app: &App) -> [[f32; 4]; 2] {
let buffer = app.world().resource::<FloraWindBuffer>();
let data = app.world().resource::<Assets<ShaderBuffer>>().get(&buffer.0).unwrap().cast_slice::<[f32; 4]>().unwrap();
[data[SEE_THROUGH_SLOT], data[SEE_THROUGH_SLOT + 1]]
}
#[test]
fn a_camera_that_sees_through_turns_the_fade_on_in_every_flora_material_until_it_goes() {
let mut app = app();
app.world_mut().spawn(ProceduralTree(TreePreset::Oak.params()));
app.update();
assert!(!flags(&app).is_empty() && flags(&app).iter().all(|on| !on), "off without a camera");
assert_eq!(slots(&app), [[0.0; 4]; 2]);
let camera = app
.world_mut()
.spawn((
SeeThroughTrees { target: Vec3::new(4.0, 5.0, 6.0), radius: 2.0 },
GlobalTransform::from_translation(Vec3::new(1.0, 2.0, 3.0)),
))
.id();
app.update();
assert!(flags(&app).iter().all(|on| *on));
assert_eq!(slots(&app), [[1.0, 2.0, 3.0, 2.0], [4.0, 5.0, 6.0, 1.0]]);
app.world_mut().spawn(ProceduralTree(TreePreset::Birch.params()));
app.world_mut().resource_mut::<FloraWind>().strength = 0.9;
app.update();
app.update();
assert!(flags(&app).len() > 2 && flags(&app).iter().all(|on| *on));
assert_eq!(slots(&app)[1], [4.0, 5.0, 6.0, 1.0]);
app.world_mut().entity_mut(camera).remove::<SeeThroughTrees>();
app.update();
assert!(flags(&app).iter().all(|on| !on), "off again once no camera sees through");
assert_eq!(slots(&app), [[0.0; 4]; 2]);
}
}