use crate::ecs::generational_registry::registry_entry_by_name_mut;
use crate::ecs::light::components::{Light, LightType};
use crate::ecs::material::components::AlphaMode;
use crate::ecs::mesh::commands::spawn_mesh;
use crate::ecs::particles::components::{
ColorGradient, EmitterShape, EmitterType, ParticleEmitter,
};
use crate::ecs::transform::commands::mark_local_transform_dirty;
use crate::ecs::vfx::components::{Beam, LightningBolt, Trail, VfxAnimator, VfxHandle};
use crate::ecs::vfx::textures::{FLARE_SLOT, GLOW_SLOT, SMOKE_SLOT};
use crate::ecs::world::commands::{
EcsCommand, queue_ecs_command, setup_entity_transforms, spawn_entities, spawn_light_entity,
};
use crate::ecs::world::components::{Lines, LocalTransform, Visibility};
use crate::ecs::world::{
BEAM, GLOBAL_TRANSFORM, LIGHTNING_BOLT, LINES, LOCAL_TRANSFORM, LOCAL_TRANSFORM_DIRTY,
PARTICLE_EMITTER, Quat, TRAIL, VFX_ANIMATOR, VISIBILITY, World,
};
use freecs::Entity;
use nalgebra_glm::{Vec3, vec3, vec4};
use std::f32::consts::{PI, TAU};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VfxFamily {
Particles,
TrailsAndBeams,
MeshEffects,
Showpieces,
}
impl VfxFamily {
pub fn label(self) -> &'static str {
match self {
VfxFamily::Particles => "Particles",
VfxFamily::TrailsAndBeams => "Trails & Beams",
VfxFamily::MeshEffects => "Mesh Effects",
VfxFamily::Showpieces => "Showpieces",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VfxPreset {
Bonfire,
Explosion,
MagicAura,
Fireworks,
Blizzard,
Flamethrower,
Fireflies,
Waterfall,
Laser,
EnergyBeam,
Lightning,
ChainLightning,
Comet,
Shockwave,
ForceField,
Portal,
TeslaCoil,
RuneCircle,
EnergyCore,
Supernova,
LightningStorm,
Singularity,
Orrery,
}
impl VfxPreset {
pub const ALL: [VfxPreset; 23] = [
VfxPreset::Bonfire,
VfxPreset::Explosion,
VfxPreset::MagicAura,
VfxPreset::Fireworks,
VfxPreset::Blizzard,
VfxPreset::Flamethrower,
VfxPreset::Fireflies,
VfxPreset::Waterfall,
VfxPreset::Laser,
VfxPreset::EnergyBeam,
VfxPreset::Lightning,
VfxPreset::ChainLightning,
VfxPreset::Comet,
VfxPreset::Shockwave,
VfxPreset::ForceField,
VfxPreset::Portal,
VfxPreset::TeslaCoil,
VfxPreset::RuneCircle,
VfxPreset::EnergyCore,
VfxPreset::Supernova,
VfxPreset::LightningStorm,
VfxPreset::Singularity,
VfxPreset::Orrery,
];
pub fn label(self) -> &'static str {
match self {
VfxPreset::Bonfire => "Bonfire",
VfxPreset::Explosion => "Explosion",
VfxPreset::MagicAura => "Magic Aura",
VfxPreset::Fireworks => "Fireworks",
VfxPreset::Blizzard => "Blizzard",
VfxPreset::Flamethrower => "Flamethrower",
VfxPreset::Fireflies => "Fireflies",
VfxPreset::Waterfall => "Waterfall",
VfxPreset::Laser => "Laser",
VfxPreset::EnergyBeam => "Energy Beam",
VfxPreset::Lightning => "Lightning",
VfxPreset::ChainLightning => "Chain Lightning",
VfxPreset::Comet => "Comet",
VfxPreset::Shockwave => "Shockwave",
VfxPreset::ForceField => "Force Field",
VfxPreset::Portal => "Portal",
VfxPreset::TeslaCoil => "Tesla Coil",
VfxPreset::RuneCircle => "Rune Circle",
VfxPreset::EnergyCore => "Energy Core",
VfxPreset::Supernova => "Supernova",
VfxPreset::LightningStorm => "Lightning Storm",
VfxPreset::Singularity => "Singularity",
VfxPreset::Orrery => "Orrery",
}
}
pub fn family(self) -> VfxFamily {
match self {
VfxPreset::Bonfire
| VfxPreset::Explosion
| VfxPreset::MagicAura
| VfxPreset::Fireworks
| VfxPreset::Blizzard
| VfxPreset::Flamethrower
| VfxPreset::Fireflies
| VfxPreset::Waterfall => VfxFamily::Particles,
VfxPreset::Laser
| VfxPreset::EnergyBeam
| VfxPreset::Lightning
| VfxPreset::ChainLightning
| VfxPreset::Comet => VfxFamily::TrailsAndBeams,
VfxPreset::Shockwave | VfxPreset::ForceField | VfxPreset::Portal => {
VfxFamily::MeshEffects
}
VfxPreset::TeslaCoil
| VfxPreset::RuneCircle
| VfxPreset::EnergyCore
| VfxPreset::Supernova
| VfxPreset::LightningStorm
| VfxPreset::Singularity
| VfxPreset::Orrery => VfxFamily::Showpieces,
}
}
}
pub fn spawn_vfx(world: &mut World, preset: VfxPreset, position: Vec3) -> VfxHandle {
match preset {
VfxPreset::Bonfire => bonfire(world, position),
VfxPreset::Explosion => explosion(world, position),
VfxPreset::MagicAura => magic_aura(world, position),
VfxPreset::Fireworks => fireworks(world, position),
VfxPreset::Blizzard => single(spawn_emitter(
world,
textured(blizzard_snow(position), GLOW_SLOT),
)),
VfxPreset::Flamethrower => flamethrower(world, position),
VfxPreset::Fireflies => single(spawn_emitter(
world,
textured(fireflies(position), GLOW_SLOT),
)),
VfxPreset::Waterfall => waterfall(world, position),
VfxPreset::Laser => laser(world, position),
VfxPreset::EnergyBeam => single(spawn_beam(world, energy_beam(position))),
VfxPreset::Lightning => strike(world, position, false),
VfxPreset::ChainLightning => strike(world, position, true),
VfxPreset::Comet => single(spawn_trail(world, comet_trail(position))),
VfxPreset::Shockwave => shockwave(world, position),
VfxPreset::ForceField => force_field(world, position),
VfxPreset::Portal => portal(world, position),
VfxPreset::TeslaCoil => tesla_coil(world, position),
VfxPreset::RuneCircle => rune_circle(world, position),
VfxPreset::EnergyCore => energy_core(world, position),
VfxPreset::Supernova => supernova(world, position),
VfxPreset::LightningStorm => lightning_storm(world, position),
VfxPreset::Singularity => singularity(world, position),
VfxPreset::Orrery => orrery(world, position),
}
}
pub fn despawn_vfx(world: &mut World, handle: &VfxHandle) {
for &entity in &handle.entities {
queue_ecs_command(world, EcsCommand::DespawnRecursive { entity });
}
}
fn single(entity: Entity) -> VfxHandle {
VfxHandle {
entities: vec![entity],
}
}
fn handle(entities: Vec<Entity>) -> VfxHandle {
VfxHandle { entities }
}
fn bonfire(world: &mut World, position: Vec3) -> VfxHandle {
handle(vec![
spawn_emitter(world, textured(bonfire_flames(position), GLOW_SLOT)),
spawn_emitter(world, textured(embers(position), FLARE_SLOT)),
spawn_emitter(
world,
textured(
ParticleEmitter {
spawn_rate: 26.0,
..ParticleEmitter::smoke(position + vec3(0.0, 0.6, 0.0))
},
SMOKE_SLOT,
),
),
spawn_point_light(
world,
position + vec3(0.0, 1.0, 0.0),
vec3(1.0, 0.5, 0.2),
55.0,
14.0,
),
])
}
fn explosion(world: &mut World, position: Vec3) -> VfxHandle {
let center = position + vec3(0.0, 1.3, 0.0);
handle(vec![
spawn_emitter(
world,
textured(
ParticleEmitter::firework_explosion(center, vec3(1.0, 0.45, 0.12), 500),
GLOW_SLOT,
),
),
spawn_emitter(
world,
textured(ParticleEmitter::flash_burst(center), GLOW_SLOT),
),
spawn_emitter(world, textured(debris_burst(center), FLARE_SLOT)),
spawn_shockwave_ring(world, position),
spawn_emitter(world, textured(ground_dust(position), SMOKE_SLOT)),
])
}
fn magic_aura(world: &mut World, position: Vec3) -> VfxHandle {
let center = position + vec3(0.0, 1.1, 0.0);
handle(vec![
spawn_emitter(world, textured(magic_motes(center), FLARE_SLOT)),
spawn_mesh_effect(
world,
"Sphere",
center,
Quat::identity(),
true,
0.5,
VfxAnimator {
lifetime: 0.0,
start_scale: vec3(0.35, 0.35, 0.35),
end_scale: vec3(0.35, 0.35, 0.35),
peak_color: vec3(0.8, 0.45, 1.7),
emissive_strength: 3.0,
base_alpha: 0.85,
pulse_amplitude: 0.3,
pulse_frequency: 1.6,
..Default::default()
},
),
spawn_point_light(world, center, vec3(0.6, 0.3, 1.2), 35.0, 11.0),
])
}
fn fireworks(world: &mut World, position: Vec3) -> VfxHandle {
handle(vec![
spawn_emitter(
world,
textured(
ParticleEmitter::firework_chrysanthemum(
position + vec3(-2.0, 4.5, 0.0),
vec3(1.0, 0.3, 0.3),
360,
),
GLOW_SLOT,
),
),
spawn_emitter(
world,
textured(
ParticleEmitter::firework_willow(
position + vec3(2.2, 3.6, 1.0),
vec3(0.4, 0.7, 1.0),
320,
),
GLOW_SLOT,
),
),
spawn_emitter(
world,
textured(
ParticleEmitter::firework_glitter(position + vec3(0.2, 5.2, -1.0), 260),
FLARE_SLOT,
),
),
])
}
fn laser(world: &mut World, position: Vec3) -> VfxHandle {
handle(vec![
spawn_beam(world, laser_beam(position)),
spawn_emitter(
world,
textured(impact_sparks(position + vec3(3.5, 1.2, 0.0)), FLARE_SLOT),
),
])
}
fn strike(world: &mut World, position: Vec3, chain: bool) -> VfxHandle {
handle(vec![
spawn_lightning(world, lightning_bolt(position, chain)),
spawn_emitter(
world,
textured(impact_sparks(position + vec3(0.0, 0.05, 0.0)), FLARE_SLOT),
),
])
}
fn shockwave(world: &mut World, position: Vec3) -> VfxHandle {
handle(vec![
spawn_shockwave_ring(world, position),
spawn_emitter(world, textured(ground_dust(position), SMOKE_SLOT)),
spawn_emitter(
world,
textured(debris_burst(position + vec3(0.0, 0.3, 0.0)), FLARE_SLOT),
),
])
}
fn force_field(world: &mut World, position: Vec3) -> VfxHandle {
let center = position + vec3(0.0, 1.3, 0.0);
handle(vec![
spawn_mesh_effect(
world,
"Sphere",
center,
Quat::identity(),
false,
0.08,
VfxAnimator {
lifetime: 0.0,
start_scale: vec3(1.7, 1.7, 1.7),
end_scale: vec3(1.7, 1.7, 1.7),
peak_color: vec3(0.3, 0.75, 1.3),
emissive_strength: 3.2,
base_alpha: 0.34,
pulse_amplitude: 0.35,
pulse_frequency: 1.2,
..Default::default()
},
),
spawn_emitter(world, textured(shield_sparks(center), FLARE_SLOT)),
])
}
fn portal(world: &mut World, position: Vec3) -> VfxHandle {
let center = position + vec3(0.0, 1.7, 0.0);
handle(vec![
spawn_mesh_effect(
world,
"Torus",
center,
nalgebra_glm::quat_angle_axis(PI / 2.0, &vec3(1.0, 0.0, 0.0)),
true,
0.5,
VfxAnimator {
lifetime: 0.0,
start_scale: vec3(1.7, 1.7, 1.7),
end_scale: vec3(1.7, 1.7, 1.7),
peak_color: vec3(1.4, 0.4, 1.8),
emissive_strength: 2.4,
base_alpha: 0.95,
pulse_amplitude: 0.15,
pulse_frequency: 2.0,
..Default::default()
},
),
spawn_emitter(world, textured(portal_swirl(center), FLARE_SLOT)),
spawn_point_light(world, center, vec3(1.0, 0.3, 1.4), 38.0, 11.0),
])
}
fn identity_transform() -> LocalTransform {
LocalTransform {
translation: Vec3::zeros(),
scale: Vec3::new(1.0, 1.0, 1.0),
rotation: Quat::identity(),
}
}
fn textured(mut emitter: ParticleEmitter, slot: u32) -> ParticleEmitter {
emitter.texture_index = slot;
emitter
}
fn spawn_emitter(world: &mut World, emitter: ParticleEmitter) -> Entity {
let entity = spawn_entities(
world,
PARTICLE_EMITTER | LOCAL_TRANSFORM | GLOBAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY,
1,
)[0];
setup_entity_transforms(world, entity, identity_transform());
world.core.set_particle_emitter(entity, emitter);
entity
}
fn spawn_point_light(
world: &mut World,
position: Vec3,
color: Vec3,
intensity: f32,
range: f32,
) -> Entity {
let entity = spawn_light_entity(world, position, "vfx_light");
world.core.set_light(
entity,
Light {
light_type: LightType::Point,
color,
intensity,
range,
..Default::default()
},
);
entity
}
fn spawn_line_entity(world: &mut World, effect_mask: u64) -> Entity {
let entity = spawn_entities(
world,
effect_mask
| LINES
| LOCAL_TRANSFORM
| GLOBAL_TRANSFORM
| LOCAL_TRANSFORM_DIRTY
| VISIBILITY,
1,
)[0];
setup_entity_transforms(world, entity, identity_transform());
world.core.set_lines(entity, Lines::default());
world.core.set_visibility(entity, Visibility::default());
entity
}
fn spawn_beam(world: &mut World, beam: Beam) -> Entity {
let entity = spawn_line_entity(world, BEAM);
world.core.set_beam(entity, beam);
entity
}
fn spawn_lightning(world: &mut World, bolt: LightningBolt) -> Entity {
let entity = spawn_line_entity(world, LIGHTNING_BOLT);
world.core.set_lightning_bolt(entity, bolt);
entity
}
fn spawn_trail(world: &mut World, trail: Trail) -> Entity {
let entity = spawn_line_entity(world, TRAIL);
world.core.set_trail(entity, trail);
entity
}
fn spawn_mesh_effect(
world: &mut World,
mesh: &str,
position: Vec3,
rotation: Quat,
unlit: bool,
roughness: f32,
animator: VfxAnimator,
) -> Entity {
let entity = spawn_mesh(world, mesh, position, animator.start_scale);
if let Some(transform) = world.core.get_local_transform_mut(entity) {
transform.rotation = rotation;
}
mark_local_transform_dirty(world, entity);
world.core.add_components(entity, VFX_ANIMATOR);
world.core.set_vfx_animator(entity, animator);
let material_name = world
.core
.get_material_ref(entity)
.map(|material_ref| material_ref.name.clone());
if let Some(name) = material_name {
if let Some(material) = registry_entry_by_name_mut(
&mut world.resources.assets.material_registry.registry,
&name,
) {
let color = animator.peak_color;
material.unlit = unlit;
material.double_sided = true;
material.alpha_mode = AlphaMode::Blend;
material.roughness = roughness;
material.metallic = 0.0;
material.base_color = [color.x, color.y, color.z, animator.base_alpha];
material.emissive_factor = [color.x, color.y, color.z];
material.emissive_strength = animator.emissive_strength;
}
world
.resources
.mesh_render_state
.mark_material_dirty(entity);
}
entity
}
fn spawn_shockwave_ring(world: &mut World, position: Vec3) -> Entity {
spawn_mesh_effect(
world,
"Torus",
position + vec3(0.0, 0.25, 0.0),
Quat::identity(),
true,
0.5,
VfxAnimator {
lifetime: 0.9,
despawn_on_end: true,
start_scale: vec3(0.4, 0.18, 0.4),
end_scale: vec3(5.5, 0.18, 5.5),
peak_color: vec3(1.6, 1.1, 0.5),
emissive_strength: 2.2,
base_alpha: 1.0,
fade_alpha: true,
..Default::default()
},
)
}
fn bonfire_flames(position: Vec3) -> ParticleEmitter {
ParticleEmitter {
spawn_rate: 380.0,
size_start: 0.5,
size_end: 0.14,
particle_lifetime_min: 0.5,
particle_lifetime_max: 1.5,
initial_velocity_min: 2.2,
initial_velocity_max: 5.0,
emissive_strength: 4.0,
..ParticleEmitter::fire(position)
}
}
fn embers(position: Vec3) -> ParticleEmitter {
ParticleEmitter {
emitter_type: EmitterType::Sparks,
shape: EmitterShape::Sphere { radius: 0.4 },
position,
direction: vec3(0.0, 1.0, 0.0),
spawn_rate: 60.0,
particle_lifetime_min: 1.0,
particle_lifetime_max: 2.4,
initial_velocity_min: 0.6,
initial_velocity_max: 1.8,
velocity_spread: 0.8,
gravity: vec3(0.0, 1.2, 0.0),
drag: 0.2,
size_start: 0.07,
size_end: 0.01,
color_gradient: ColorGradient::sparks(),
emissive_strength: 7.0,
turbulence_strength: 1.0,
turbulence_frequency: 1.5,
..Default::default()
}
}
fn magic_motes(position: Vec3) -> ParticleEmitter {
ParticleEmitter {
emitter_type: EmitterType::Fire,
shape: EmitterShape::Sphere { radius: 0.7 },
position,
direction: vec3(0.0, 1.0, 0.0),
spawn_rate: 170.0,
particle_lifetime_min: 1.4,
particle_lifetime_max: 2.8,
initial_velocity_min: 0.2,
initial_velocity_max: 0.8,
velocity_spread: 1.4,
gravity: vec3(0.0, 0.3, 0.0),
drag: 0.4,
size_start: 0.14,
size_end: 0.02,
color_gradient: ColorGradient {
colors: vec![
(0.0, vec4(0.9, 0.6, 1.0, 1.0)),
(0.4, vec4(0.55, 0.35, 1.0, 0.9)),
(0.75, vec4(0.3, 0.7, 1.0, 0.6)),
(1.0, vec4(0.2, 0.3, 0.7, 0.0)),
],
},
emissive_strength: 6.0,
turbulence_strength: 2.8,
turbulence_frequency: 1.8,
..Default::default()
}
}
fn debris_burst(position: Vec3) -> ParticleEmitter {
ParticleEmitter {
emitter_type: EmitterType::Sparks,
shape: EmitterShape::Sphere { radius: 0.2 },
position,
direction: vec3(0.0, 1.0, 0.0),
spawn_rate: 0.0,
burst_count: 160,
particle_lifetime_min: 0.4,
particle_lifetime_max: 1.0,
initial_velocity_min: 8.0,
initial_velocity_max: 18.0,
velocity_spread: PI,
gravity: vec3(0.0, -14.0, 0.0),
drag: 0.06,
size_start: 0.08,
size_end: 0.01,
color_gradient: ColorGradient::sparks(),
emissive_strength: 12.0,
one_shot: true,
turbulence_strength: 0.6,
turbulence_frequency: 3.0,
..Default::default()
}
}
fn impact_sparks(position: Vec3) -> ParticleEmitter {
ParticleEmitter {
emitter_type: EmitterType::Sparks,
shape: EmitterShape::Point,
position,
direction: vec3(0.0, 1.0, 0.0),
spawn_rate: 120.0,
particle_lifetime_min: 0.2,
particle_lifetime_max: 0.6,
initial_velocity_min: 2.0,
initial_velocity_max: 6.0,
velocity_spread: PI,
gravity: vec3(0.0, -10.0, 0.0),
drag: 0.05,
size_start: 0.05,
size_end: 0.01,
color_gradient: ColorGradient::sparks(),
emissive_strength: 10.0,
..Default::default()
}
}
fn ground_dust(position: Vec3) -> ParticleEmitter {
ParticleEmitter {
emitter_type: EmitterType::Smoke,
shape: EmitterShape::Sphere { radius: 0.3 },
position: position + vec3(0.0, 0.1, 0.0),
direction: vec3(0.0, 1.0, 0.0),
spawn_rate: 0.0,
burst_count: 60,
particle_lifetime_min: 0.6,
particle_lifetime_max: 1.2,
initial_velocity_min: 3.0,
initial_velocity_max: 6.0,
velocity_spread: 1.4,
gravity: vec3(0.0, -1.0, 0.0),
drag: 0.5,
size_start: 0.3,
size_end: 1.0,
color_gradient: ColorGradient {
colors: vec![
(0.0, vec4(0.6, 0.55, 0.5, 0.0)),
(0.2, vec4(0.65, 0.6, 0.55, 0.5)),
(1.0, vec4(0.7, 0.65, 0.6, 0.0)),
],
},
emissive_strength: 0.0,
one_shot: true,
..Default::default()
}
}
fn blizzard_snow(position: Vec3) -> ParticleEmitter {
ParticleEmitter {
emitter_type: EmitterType::Smoke,
shape: EmitterShape::Box {
half_extents: vec3(5.0, 0.1, 5.0),
},
position: position + vec3(0.0, 6.0, 0.0),
direction: vec3(0.3, -1.0, 0.1),
spawn_rate: 360.0,
particle_lifetime_min: 2.5,
particle_lifetime_max: 4.0,
initial_velocity_min: 1.5,
initial_velocity_max: 3.0,
velocity_spread: 0.3,
gravity: vec3(0.6, -1.2, 0.2),
drag: 0.05,
size_start: 0.06,
size_end: 0.06,
color_gradient: ColorGradient {
colors: vec![
(0.0, vec4(1.0, 1.0, 1.0, 0.0)),
(0.1, vec4(1.0, 1.0, 1.0, 0.95)),
(0.9, vec4(0.95, 0.97, 1.0, 0.95)),
(1.0, vec4(0.9, 0.95, 1.0, 0.0)),
],
},
emissive_strength: 0.3,
turbulence_strength: 0.8,
turbulence_frequency: 0.5,
..Default::default()
}
}
fn shield_sparks(position: Vec3) -> ParticleEmitter {
ParticleEmitter {
emitter_type: EmitterType::Sparks,
shape: EmitterShape::Sphere { radius: 1.7 },
position,
direction: vec3(0.0, 1.0, 0.0),
spawn_rate: 50.0,
particle_lifetime_min: 0.4,
particle_lifetime_max: 1.0,
initial_velocity_min: 0.1,
initial_velocity_max: 0.5,
velocity_spread: PI,
gravity: vec3(0.0, 0.0, 0.0),
drag: 0.4,
size_start: 0.05,
size_end: 0.01,
color_gradient: ColorGradient {
colors: vec![
(0.0, vec4(0.5, 0.9, 1.0, 1.0)),
(0.5, vec4(0.3, 0.7, 1.0, 0.8)),
(1.0, vec4(0.2, 0.5, 0.9, 0.0)),
],
},
emissive_strength: 8.0,
..Default::default()
}
}
fn portal_swirl(position: Vec3) -> ParticleEmitter {
ParticleEmitter {
emitter_type: EmitterType::Fire,
shape: EmitterShape::Sphere { radius: 1.2 },
position,
direction: vec3(0.0, 0.0, 1.0),
spawn_rate: 150.0,
particle_lifetime_min: 0.8,
particle_lifetime_max: 1.6,
initial_velocity_min: 0.1,
initial_velocity_max: 0.6,
velocity_spread: PI,
gravity: vec3(0.0, 0.0, 0.0),
drag: 0.5,
size_start: 0.12,
size_end: 0.02,
color_gradient: ColorGradient {
colors: vec![
(0.0, vec4(1.0, 0.5, 1.0, 1.0)),
(0.5, vec4(0.7, 0.3, 1.0, 0.8)),
(1.0, vec4(0.4, 0.2, 0.8, 0.0)),
],
},
emissive_strength: 7.0,
turbulence_strength: 3.0,
turbulence_frequency: 2.5,
..Default::default()
}
}
fn laser_beam(position: Vec3) -> Beam {
Beam {
start: position + vec3(-3.5, 1.2, 0.0),
end: position + vec3(3.5, 1.2, 0.0),
color: vec3(3.4, 0.3, 0.3),
intensity: 4.0,
alpha: 1.0,
width: 0.05,
strands: 3,
flicker: 0.1,
flicker_speed: 45.0,
age: 0.0,
}
}
fn energy_beam(position: Vec3) -> Beam {
Beam {
start: position + vec3(0.0, 0.1, 0.0),
end: position + vec3(0.0, 5.5, 0.0),
color: vec3(0.5, 1.3, 2.8),
intensity: 3.2,
alpha: 0.9,
width: 0.22,
strands: 8,
flicker: 0.3,
flicker_speed: 16.0,
age: 0.0,
}
}
fn lightning_bolt(position: Vec3, chain: bool) -> LightningBolt {
LightningBolt {
start: position + vec3(0.0, 5.5, 0.0),
end: position + vec3(0.0, 0.05, 0.0),
color: vec3(1.6, 1.9, 3.4),
intensity: 5.5,
alpha: 1.0,
segments: 24,
jaggedness: if chain { 0.7 } else { 0.45 },
branch_count: if chain { 10 } else { 3 },
branch_spread: 0.9,
regen_interval: 0.045,
timer: 0.0,
seed: 0x9E37_79B9,
}
}
fn comet_trail(position: Vec3) -> Trail {
Trail {
center: position + vec3(0.0, 1.8, 0.0),
radius: 2.6,
axis: vec3(0.25, 1.0, 0.0),
speed: 2.8,
color: vec3(2.8, 1.5, 0.5),
intensity: 1.6,
alpha: 1.0,
max_points: 80,
points: Vec::new(),
age: 0.0,
}
}
fn flamethrower(world: &mut World, position: Vec3) -> VfxHandle {
let nozzle = position + vec3(-1.8, 1.2, 0.0);
handle(vec![
spawn_emitter(world, textured(flame_jet(nozzle), GLOW_SLOT)),
spawn_emitter(
world,
textured(
ParticleEmitter {
emitter_type: EmitterType::Smoke,
shape: EmitterShape::Cone {
angle: 0.3,
height: 0.2,
},
position: nozzle,
direction: vec3(1.0, 0.4, 0.0),
spawn_rate: 55.0,
particle_lifetime_min: 1.0,
particle_lifetime_max: 2.0,
initial_velocity_min: 3.0,
initial_velocity_max: 6.0,
velocity_spread: 0.4,
gravity: vec3(0.0, 1.0, 0.0),
drag: 0.5,
size_start: 0.4,
size_end: 1.3,
color_gradient: ColorGradient::smoke(),
emissive_strength: 0.0,
turbulence_strength: 1.0,
turbulence_frequency: 0.6,
..Default::default()
},
SMOKE_SLOT,
),
),
spawn_point_light(
world,
position + vec3(0.5, 1.2, 0.0),
vec3(1.0, 0.5, 0.2),
45.0,
12.0,
),
])
}
fn waterfall(world: &mut World, position: Vec3) -> VfxHandle {
handle(vec![
spawn_emitter(
world,
textured(waterfall_water(position + vec3(0.0, 5.0, -0.5)), GLOW_SLOT),
),
spawn_emitter(
world,
textured(waterfall_mist(position + vec3(0.0, 0.3, -0.5)), SMOKE_SLOT),
),
])
}
fn tesla_coil(world: &mut World, position: Vec3) -> VfxHandle {
let center = position + vec3(0.0, 1.8, 0.0);
let mut entities = vec![
spawn_mesh_effect(
world,
"Sphere",
center,
Quat::identity(),
true,
0.5,
VfxAnimator {
lifetime: 0.0,
start_scale: vec3(0.3, 0.3, 0.3),
end_scale: vec3(0.3, 0.3, 0.3),
peak_color: vec3(0.6, 0.8, 2.0),
emissive_strength: 4.0,
base_alpha: 0.95,
pulse_amplitude: 0.3,
pulse_frequency: 6.0,
..Default::default()
},
),
spawn_emitter(world, textured(core_sparks(center), FLARE_SLOT)),
spawn_point_light(world, center, vec3(0.5, 0.7, 1.6), 45.0, 13.0),
];
let arcs = 8u32;
for index in 0..arcs {
let angle = index as f32 / arcs as f32 * TAU;
let elevation = (index as f32 * 0.9).sin() * 1.4;
let end = center + vec3(angle.cos() * 2.8, elevation, angle.sin() * 2.8);
let seed = 0x9E37_79B9u32.wrapping_add(index.wrapping_mul(2_246_822_519));
entities.push(spawn_bolt(
world,
center,
end,
vec3(0.7, 0.85, 2.2),
5.0,
seed,
));
}
handle(entities)
}
fn rune_circle(world: &mut World, position: Vec3) -> VfxHandle {
handle(vec![
spawn_mesh_effect(
world,
"Torus",
position + vec3(0.0, 0.15, 0.0),
Quat::identity(),
true,
0.5,
VfxAnimator {
lifetime: 0.0,
start_scale: vec3(2.4, 0.1, 2.4),
end_scale: vec3(2.4, 0.1, 2.4),
peak_color: vec3(0.7, 0.4, 1.6),
emissive_strength: 2.6,
base_alpha: 0.9,
pulse_amplitude: 0.2,
pulse_frequency: 1.0,
..Default::default()
},
),
spawn_mesh_effect(
world,
"Torus",
position + vec3(0.0, 0.15, 0.0),
Quat::identity(),
true,
0.5,
VfxAnimator {
lifetime: 0.0,
start_scale: vec3(1.3, 0.08, 1.3),
end_scale: vec3(1.3, 0.08, 1.3),
peak_color: vec3(0.4, 0.6, 1.6),
emissive_strength: 2.6,
base_alpha: 0.9,
..Default::default()
},
),
spawn_beam(
world,
Beam {
start: position + vec3(0.0, 0.1, 0.0),
end: position + vec3(0.0, 5.0, 0.0),
color: vec3(0.6, 0.4, 1.8),
intensity: 2.6,
alpha: 0.7,
width: 0.5,
strands: 9,
flicker: 0.2,
flicker_speed: 10.0,
age: 0.0,
},
),
spawn_emitter(world, textured(rune_motes(position), FLARE_SLOT)),
spawn_point_light(
world,
position + vec3(0.0, 1.5, 0.0),
vec3(0.7, 0.4, 1.6),
40.0,
12.0,
),
])
}
fn energy_core(world: &mut World, position: Vec3) -> VfxHandle {
let center = position + vec3(0.0, 1.6, 0.0);
handle(vec![
spawn_mesh_effect(
world,
"Sphere",
center,
Quat::identity(),
true,
0.3,
VfxAnimator {
lifetime: 0.0,
start_scale: vec3(0.6, 0.6, 0.6),
end_scale: vec3(0.6, 0.6, 0.6),
peak_color: vec3(0.4, 1.2, 2.4),
emissive_strength: 4.0,
base_alpha: 0.95,
pulse_amplitude: 0.3,
pulse_frequency: 2.5,
..Default::default()
},
),
spawn_spinning_ring(world, center, vec3(1.0, 0.0, 0.0), vec3(0.4, 1.0, 2.2), 3.0),
spawn_spinning_ring(
world,
center,
vec3(0.0, 0.0, 1.0),
vec3(0.5, 0.9, 2.0),
-2.4,
),
spawn_emitter(world, textured(core_sparks(center), FLARE_SLOT)),
spawn_point_light(world, center, vec3(0.4, 0.9, 2.0), 45.0, 13.0),
])
}
fn supernova(world: &mut World, position: Vec3) -> VfxHandle {
let center = position + vec3(0.0, 1.8, 0.0);
handle(vec![
spawn_emitter(
world,
textured(ParticleEmitter::flash_burst(center), GLOW_SLOT),
),
spawn_emitter(
world,
textured(
ParticleEmitter::firework_explosion(center, vec3(1.0, 0.7, 0.35), 700),
GLOW_SLOT,
),
),
spawn_emitter(world, textured(supernova_debris(center), FLARE_SLOT)),
spawn_mesh_effect(
world,
"Torus",
position + vec3(0.0, 0.3, 0.0),
Quat::identity(),
true,
0.5,
VfxAnimator {
lifetime: 1.4,
despawn_on_end: true,
start_scale: vec3(0.5, 0.2, 0.5),
end_scale: vec3(9.0, 0.2, 9.0),
peak_color: vec3(1.6, 1.2, 0.6),
emissive_strength: 2.6,
base_alpha: 1.0,
fade_alpha: true,
..Default::default()
},
),
spawn_mesh_effect(
world,
"Sphere",
center,
Quat::identity(),
true,
0.5,
VfxAnimator {
lifetime: 0.9,
despawn_on_end: true,
start_scale: vec3(0.3, 0.3, 0.3),
end_scale: vec3(4.5, 4.5, 4.5),
peak_color: vec3(1.4, 0.9, 0.5),
emissive_strength: 3.0,
base_alpha: 0.85,
fade_alpha: true,
..Default::default()
},
),
spawn_emitter(world, textured(ground_dust(position), SMOKE_SLOT)),
spawn_point_light(world, center, vec3(1.6, 1.1, 0.6), 130.0, 24.0),
])
}
fn lightning_storm(world: &mut World, position: Vec3) -> VfxHandle {
let mut entities = vec![
spawn_emitter(world, textured(storm_rain(position), GLOW_SLOT)),
spawn_point_light(
world,
position + vec3(0.0, 6.0, 0.0),
vec3(0.6, 0.7, 1.4),
45.0,
26.0,
),
];
let strikes = 9u32;
for index in 0..strikes {
let angle = index as f32 / strikes as f32 * TAU;
let radius = 2.5 + (index as f32 * 1.7).sin().abs() * 4.0;
let strike = position + vec3(angle.cos() * radius, 0.05, angle.sin() * radius);
let top = strike + vec3(0.0, 8.5, 0.0);
let seed = 0x51ED_2719u32.wrapping_add(index.wrapping_mul(2_654_435_761));
entities.push(spawn_bolt(
world,
top,
strike,
vec3(1.4, 1.7, 3.4),
5.0,
seed,
));
}
handle(entities)
}
fn singularity(world: &mut World, position: Vec3) -> VfxHandle {
let center = position + vec3(0.0, 2.0, 0.0);
let effects = &mut world.resources.renderer_state.effects;
effects.uniforms = Default::default();
effects.uniforms.chromatic_aberration = 0.5;
effects.uniforms.wave_distortion = 0.35;
effects.enabled = true;
let mut entities = vec![
spawn_mesh_effect(
world,
"Sphere",
center,
Quat::identity(),
true,
0.5,
VfxAnimator {
lifetime: 0.0,
start_scale: vec3(0.7, 0.7, 0.7),
end_scale: vec3(0.7, 0.7, 0.7),
peak_color: vec3(0.02, 0.0, 0.05),
emissive_strength: 0.0,
base_alpha: 1.0,
..Default::default()
},
),
spawn_emitter(world, textured(singularity_disk(center), GLOW_SLOT)),
spawn_mesh_effect(
world,
"Torus",
center,
nalgebra_glm::quat_angle_axis(1.2, &vec3(1.0, 0.0, 0.0)),
true,
0.4,
VfxAnimator {
lifetime: 0.0,
start_scale: vec3(1.4, 1.4, 1.4),
end_scale: vec3(1.4, 1.4, 1.4),
spin_axis: vec3(1.0, 0.0, 0.0),
spin_speed: 1.6,
peak_color: vec3(1.2, 0.5, 2.0),
emissive_strength: 3.0,
base_alpha: 0.9,
..Default::default()
},
),
spawn_point_light(world, center, vec3(0.8, 0.4, 1.6), 50.0, 16.0),
];
let beams = 10u32;
for index in 0..beams {
let angle = index as f32 / beams as f32 * TAU;
let elevation = (index as f32 * 0.8).sin() * 1.6;
let outer = center + vec3(angle.cos() * 4.2, elevation, angle.sin() * 4.2);
entities.push(spawn_beam(
world,
Beam {
start: outer,
end: center,
color: vec3(0.7, 0.3, 1.7),
intensity: 2.6,
alpha: 0.7,
width: 0.06,
strands: 3,
flicker: 0.4,
flicker_speed: 22.0,
age: 0.0,
},
));
}
handle(entities)
}
fn orrery(world: &mut World, position: Vec3) -> VfxHandle {
let center = position + vec3(0.0, 2.0, 0.0);
let mut entities = vec![
spawn_mesh_effect(
world,
"Sphere",
center,
Quat::identity(),
true,
0.4,
VfxAnimator {
lifetime: 0.0,
start_scale: vec3(0.6, 0.6, 0.6),
end_scale: vec3(0.6, 0.6, 0.6),
peak_color: vec3(2.0, 1.6, 0.8),
emissive_strength: 4.5,
base_alpha: 1.0,
pulse_amplitude: 0.12,
pulse_frequency: 1.5,
..Default::default()
},
),
spawn_point_light(world, center, vec3(1.4, 1.1, 0.6), 48.0, 18.0),
];
let orbits = [
(1.8, 2.2, vec3(0.0, 1.0, 0.12), vec3(0.4, 0.8, 2.2)),
(2.8, -1.5, vec3(0.32, 1.0, 0.0), vec3(2.2, 0.5, 0.6)),
(3.8, 1.05, vec3(0.12, 1.0, 0.4), vec3(0.5, 2.0, 0.7)),
];
for (radius, speed, axis, color) in orbits {
entities.push(spawn_mesh_effect(
world,
"Sphere",
center,
Quat::identity(),
true,
0.5,
VfxAnimator {
lifetime: 0.0,
start_scale: vec3(0.22, 0.22, 0.22),
end_scale: vec3(0.22, 0.22, 0.22),
peak_color: color,
emissive_strength: 3.5,
base_alpha: 1.0,
orbit_center: center,
orbit_radius: radius,
orbit_axis: axis,
orbit_speed: speed,
..Default::default()
},
));
entities.push(spawn_trail(
world,
Trail {
center,
radius,
axis,
speed,
color,
intensity: 1.4,
alpha: 1.0,
max_points: 96,
points: Vec::new(),
age: 0.0,
},
));
}
handle(entities)
}
fn supernova_debris(position: Vec3) -> ParticleEmitter {
ParticleEmitter {
emitter_type: EmitterType::Sparks,
shape: EmitterShape::Sphere { radius: 0.3 },
position,
direction: vec3(0.0, 1.0, 0.0),
spawn_rate: 0.0,
burst_count: 600,
particle_lifetime_min: 0.6,
particle_lifetime_max: 1.6,
initial_velocity_min: 14.0,
initial_velocity_max: 32.0,
velocity_spread: PI,
gravity: vec3(0.0, -6.0, 0.0),
drag: 0.05,
size_start: 0.1,
size_end: 0.01,
color_gradient: ColorGradient {
colors: vec![
(0.0, vec4(1.0, 0.95, 0.8, 1.0)),
(0.4, vec4(1.0, 0.6, 0.2, 0.9)),
(0.8, vec4(1.0, 0.3, 0.1, 0.5)),
(1.0, vec4(0.4, 0.1, 0.0, 0.0)),
],
},
emissive_strength: 12.0,
one_shot: true,
turbulence_strength: 0.5,
turbulence_frequency: 3.0,
..Default::default()
}
}
fn storm_rain(position: Vec3) -> ParticleEmitter {
ParticleEmitter {
emitter_type: EmitterType::Sparks,
shape: EmitterShape::Box {
half_extents: vec3(7.0, 0.1, 7.0),
},
position: position + vec3(0.0, 8.0, 0.0),
direction: vec3(0.0, -1.0, 0.0),
spawn_rate: 600.0,
particle_lifetime_min: 0.8,
particle_lifetime_max: 1.4,
initial_velocity_min: 6.0,
initial_velocity_max: 10.0,
velocity_spread: 0.05,
gravity: vec3(0.0, -18.0, 0.0),
drag: 0.0,
size_start: 0.05,
size_end: 0.04,
color_gradient: ColorGradient {
colors: vec![
(0.0, vec4(0.6, 0.7, 1.0, 0.0)),
(0.3, vec4(0.65, 0.75, 1.0, 0.7)),
(1.0, vec4(0.6, 0.7, 1.0, 0.4)),
],
},
emissive_strength: 1.0,
..Default::default()
}
}
fn singularity_disk(position: Vec3) -> ParticleEmitter {
ParticleEmitter {
emitter_type: EmitterType::Fire,
shape: EmitterShape::Box {
half_extents: vec3(3.6, 0.1, 3.6),
},
position,
direction: vec3(0.0, 1.0, 0.0),
spawn_rate: 420.0,
particle_lifetime_min: 0.8,
particle_lifetime_max: 1.8,
initial_velocity_min: 0.2,
initial_velocity_max: 0.6,
velocity_spread: PI,
gravity: vec3(0.0, 0.0, 0.0),
drag: 0.4,
size_start: 0.12,
size_end: 0.03,
color_gradient: ColorGradient {
colors: vec![
(0.0, vec4(1.0, 0.8, 1.0, 1.0)),
(0.4, vec4(0.8, 0.3, 1.0, 0.9)),
(0.8, vec4(0.5, 0.2, 1.0, 0.6)),
(1.0, vec4(0.2, 0.1, 0.5, 0.0)),
],
},
emissive_strength: 7.0,
turbulence_strength: 3.5,
turbulence_frequency: 2.0,
..Default::default()
}
}
fn spawn_bolt(
world: &mut World,
start: Vec3,
end: Vec3,
color: Vec3,
intensity: f32,
seed: u32,
) -> Entity {
spawn_lightning(
world,
LightningBolt {
start,
end,
color,
intensity,
alpha: 1.0,
segments: 14,
jaggedness: 0.25,
branch_count: 2,
branch_spread: 0.6,
regen_interval: 0.05,
timer: 0.0,
seed,
},
)
}
fn spawn_spinning_ring(
world: &mut World,
center: Vec3,
axis: Vec3,
color: Vec3,
speed: f32,
) -> Entity {
spawn_mesh_effect(
world,
"Torus",
center,
Quat::identity(),
true,
0.5,
VfxAnimator {
lifetime: 0.0,
start_scale: vec3(1.3, 1.3, 1.3),
end_scale: vec3(1.3, 1.3, 1.3),
spin_axis: axis,
spin_speed: speed,
peak_color: color,
emissive_strength: 2.5,
base_alpha: 0.85,
..Default::default()
},
)
}
fn flame_jet(position: Vec3) -> ParticleEmitter {
ParticleEmitter {
emitter_type: EmitterType::Fire,
shape: EmitterShape::Cone {
angle: 0.22,
height: 0.2,
},
position,
direction: vec3(1.0, 0.15, 0.0),
spawn_rate: 420.0,
particle_lifetime_min: 0.4,
particle_lifetime_max: 0.9,
initial_velocity_min: 9.0,
initial_velocity_max: 16.0,
velocity_spread: 0.25,
gravity: vec3(0.0, 1.0, 0.0),
drag: 0.7,
size_start: 0.28,
size_end: 0.7,
color_gradient: ColorGradient::fire(),
emissive_strength: 5.0,
turbulence_strength: 1.2,
turbulence_frequency: 2.0,
..Default::default()
}
}
fn fireflies(position: Vec3) -> ParticleEmitter {
ParticleEmitter {
emitter_type: EmitterType::Fire,
shape: EmitterShape::Box {
half_extents: vec3(4.0, 1.6, 4.0),
},
position: position + vec3(0.0, 1.4, 0.0),
direction: vec3(0.0, 1.0, 0.0),
spawn_rate: 45.0,
particle_lifetime_min: 3.0,
particle_lifetime_max: 6.0,
initial_velocity_min: 0.1,
initial_velocity_max: 0.4,
velocity_spread: PI,
gravity: vec3(0.0, 0.05, 0.0),
drag: 0.6,
size_start: 0.07,
size_end: 0.05,
color_gradient: ColorGradient {
colors: vec![
(0.0, vec4(1.0, 0.9, 0.4, 0.0)),
(0.2, vec4(1.0, 0.95, 0.5, 1.0)),
(0.5, vec4(0.7, 1.0, 0.4, 0.9)),
(0.8, vec4(1.0, 0.9, 0.4, 0.8)),
(1.0, vec4(0.8, 0.8, 0.3, 0.0)),
],
},
emissive_strength: 9.0,
turbulence_strength: 1.5,
turbulence_frequency: 0.4,
..Default::default()
}
}
fn waterfall_water(position: Vec3) -> ParticleEmitter {
ParticleEmitter {
emitter_type: EmitterType::Sparks,
shape: EmitterShape::Box {
half_extents: vec3(1.5, 0.1, 0.2),
},
position,
direction: vec3(0.0, -1.0, 0.0),
spawn_rate: 520.0,
particle_lifetime_min: 1.0,
particle_lifetime_max: 1.6,
initial_velocity_min: 2.0,
initial_velocity_max: 4.0,
velocity_spread: 0.1,
gravity: vec3(0.0, -12.0, 0.0),
drag: 0.02,
size_start: 0.1,
size_end: 0.06,
color_gradient: ColorGradient {
colors: vec![
(0.0, vec4(0.7, 0.85, 1.0, 0.9)),
(0.6, vec4(0.5, 0.7, 1.0, 0.8)),
(1.0, vec4(0.6, 0.8, 1.0, 0.3)),
],
},
emissive_strength: 1.2,
..Default::default()
}
}
fn waterfall_mist(position: Vec3) -> ParticleEmitter {
ParticleEmitter {
emitter_type: EmitterType::Smoke,
shape: EmitterShape::Box {
half_extents: vec3(1.6, 0.1, 0.6),
},
position,
direction: vec3(0.0, 1.0, 0.0),
spawn_rate: 80.0,
particle_lifetime_min: 1.0,
particle_lifetime_max: 2.2,
initial_velocity_min: 0.5,
initial_velocity_max: 1.5,
velocity_spread: 1.2,
gravity: vec3(0.0, 0.5, 0.0),
drag: 0.4,
size_start: 0.4,
size_end: 1.2,
color_gradient: ColorGradient {
colors: vec![
(0.0, vec4(0.8, 0.88, 1.0, 0.0)),
(0.3, vec4(0.85, 0.9, 1.0, 0.5)),
(1.0, vec4(0.9, 0.93, 1.0, 0.0)),
],
},
emissive_strength: 0.2,
turbulence_strength: 0.6,
turbulence_frequency: 0.5,
..Default::default()
}
}
fn core_sparks(position: Vec3) -> ParticleEmitter {
ParticleEmitter {
emitter_type: EmitterType::Sparks,
shape: EmitterShape::Sphere { radius: 0.4 },
position,
direction: vec3(0.0, 1.0, 0.0),
spawn_rate: 90.0,
particle_lifetime_min: 0.2,
particle_lifetime_max: 0.5,
initial_velocity_min: 1.0,
initial_velocity_max: 4.0,
velocity_spread: PI,
gravity: vec3(0.0, 0.0, 0.0),
drag: 0.3,
size_start: 0.05,
size_end: 0.01,
color_gradient: ColorGradient {
colors: vec![
(0.0, vec4(0.8, 0.95, 1.0, 1.0)),
(0.5, vec4(0.5, 0.8, 1.0, 0.8)),
(1.0, vec4(0.3, 0.6, 1.0, 0.0)),
],
},
emissive_strength: 10.0,
..Default::default()
}
}
fn rune_motes(position: Vec3) -> ParticleEmitter {
ParticleEmitter {
emitter_type: EmitterType::Fire,
shape: EmitterShape::Box {
half_extents: vec3(2.2, 0.1, 2.2),
},
position: position + vec3(0.0, 0.2, 0.0),
direction: vec3(0.0, 1.0, 0.0),
spawn_rate: 120.0,
particle_lifetime_min: 1.5,
particle_lifetime_max: 3.0,
initial_velocity_min: 1.0,
initial_velocity_max: 2.5,
velocity_spread: 0.4,
gravity: vec3(0.0, 0.5, 0.0),
drag: 0.3,
size_start: 0.1,
size_end: 0.02,
color_gradient: ColorGradient {
colors: vec![
(0.0, vec4(0.8, 0.5, 1.0, 1.0)),
(0.5, vec4(0.5, 0.4, 1.0, 0.9)),
(1.0, vec4(0.3, 0.3, 0.8, 0.0)),
],
},
emissive_strength: 6.0,
turbulence_strength: 1.5,
turbulence_frequency: 1.0,
..Default::default()
}
}