use berdicles::{
templates::{ExpDecayTrail, WidthCurve},
trail::{TrailMaterial, TrailMeshOf},
util::transform_from_derivative,
ExpirationState, InstancedMaterial3d, Projectile, ProjectileCluster, ProjectilePlugin,
ProjectileSystem, StandardParticle,
};
use bevy::{
pbr::{NotShadowCaster, NotShadowReceiver},
prelude::*,
window::PresentMode,
};
use std::f32::consts::PI;
use util::{uv_debug_texture, FPSPlugin};
mod util;
fn main() {
App::new()
.add_plugins(
DefaultPlugins
.set(ImagePlugin::default_nearest())
.set(WindowPlugin {
primary_window: Some(Window {
present_mode: PresentMode::AutoNoVsync,
..Default::default()
}),
..Default::default()
}),
)
.add_plugins(FPSPlugin)
.add_plugins(ProjectilePlugin)
.add_systems(Startup, setup)
.run();
}
#[derive(Component)]
struct Shape;
#[derive(Debug, Clone, Copy)]
pub struct MainParticle {
pub seed: f32,
pub life_time: f32,
pub trail: ExpDecayTrail<16>,
}
impl Projectile for MainParticle {
fn get_transform(&self) -> Transform {
let f = |t: f32| {
let front = Vec2::from_angle(self.seed * PI * 4.);
let front = Vec3::new(front.x, 0., front.y);
let tangent = front.cross(Vec3::Y);
let angle = t * 12.;
front * t * 10. + tangent * angle.cos() + Vec3::Y * angle.sin()
};
transform_from_derivative(f, self.life_time)
}
fn get_color(&self) -> Srgba {
Srgba::WHITE
}
fn update(&mut self, dt: f32) {
if !self.is_expired() {
self.life_time += dt;
}
self.trail.update(dt);
self.trail.set_first(self.get_position())
}
fn trail(&self) -> &[(Vec3, f32)] {
&self.trail.buffer
}
fn expiration_state(&self) -> ExpirationState {
if self.life_time > 8.0 {
ExpirationState::FadeOut
} else {
ExpirationState::None
}
}
fn should_despawn(&self) -> bool {
self.is_expired() && self.trail.is_expired()
}
}
pub struct MainSpawner(f32);
impl ProjectileSystem for MainSpawner {
type Projectile = MainParticle;
fn capacity(&self) -> usize {
100
}
fn spawn_step(&mut self, time: f32) -> usize {
self.0 += time * 4.;
let result = self.0.floor() as usize;
self.0 = self.0.fract();
result
}
fn build_particle(&self, seed: f32) -> Self::Projectile {
MainParticle {
seed,
life_time: 0.,
trail: ExpDecayTrail {
width_curve: WidthCurve::Fac(|x| (1. - x * x / 2.) * 0.25),
eps: 0.5,
..Default::default()
},
}
}
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut images: ResMut<Assets<Image>>,
server: Res<AssetServer>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut materials2: ResMut<Assets<StandardParticle>>,
mut materials3: ResMut<Assets<TrailMaterial>>,
) {
let mut mesh = Mesh::from(Sphere { radius: 0.2 }.mesh());
let _ = mesh.generate_tangents();
let root = commands
.spawn((
ProjectileCluster::new(MainSpawner(0.)),
Mesh3d(meshes.add(mesh)),
InstancedMaterial3d(materials2.add(StandardParticle {
base_color: LinearRgba::new(2., 2., 2., 1.),
texture: images.add(uv_debug_texture()),
alpha_mode: AlphaMode::Opaque,
..Default::default()
})),
))
.id();
commands.spawn((
MeshMaterial3d(materials3.add(TrailMaterial {
base: StandardMaterial {
base_color: Color::srgba(0., 1., 1., 1.),
base_color_texture: Some(server.load("lightning.png")),
alpha_mode: AlphaMode::Blend,
cull_mode: None,
double_sided: true,
unlit: true,
..Default::default()
},
..Default::default()
})),
NotShadowCaster,
NotShadowReceiver,
TrailMeshOf(root),
));
commands.spawn((
PointLight {
shadows_enabled: true,
intensity: 10_000_000.,
range: 100.0,
shadow_depth_bias: 0.2,
..default()
},
Transform::from_xyz(8.0, 16.0, 8.0),
));
commands.spawn((
Mesh3d(meshes.add(Plane3d::default().mesh().size(100.0, 100.0).subdivisions(5))),
MeshMaterial3d(materials.add(StandardMaterial::from_color(Srgba::GREEN))),
Transform::from_xyz(0., -10., 0.),
));
commands.spawn((
Camera3d::default(),
Transform::from_xyz(7.0, 30., 7.0).looking_at(Vec3::new(0., 0., 0.), Vec3::Y),
));
}