1use super::super::{AaMode, GlobalsLayouts, Vertex as VertexTrait};
2use bytemuck::{Pod, Zeroable};
3use std::mem;
4use vek::*;
5
6#[repr(C)]
7#[derive(Copy, Clone, Debug, Zeroable, Pod)]
8pub struct Vertex {
9 pub pos: [f32; 3],
10 norm_ao: u32,
16}
17
18impl Vertex {
19 pub fn new(pos: Vec3<f32>, norm: Vec3<f32>) -> Self {
20 #[expect(clippy::bool_to_int_with_if)]
21 let norm_bits = if norm.x != 0.0 {
22 if norm.x < 0.0 { 0 } else { 1 }
23 } else if norm.y != 0.0 {
24 if norm.y < 0.0 { 2 } else { 3 }
25 } else {
26 if norm.z < 0.0 { 4 } else { 5 }
27 };
28
29 Self {
30 pos: pos.into_array(),
31 norm_ao: norm_bits,
32 }
33 }
34
35 fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
36 const ATTRIBUTES: [wgpu::VertexAttribute; 2] =
37 wgpu::vertex_attr_array![0 => Float32x3, 1 => Uint32];
38 wgpu::VertexBufferLayout {
39 array_stride: Self::STRIDE,
40 step_mode: wgpu::VertexStepMode::Vertex,
41 attributes: &ATTRIBUTES,
42 }
43 }
44}
45
46impl VertexTrait for Vertex {
47 const QUADS_INDEX: Option<wgpu::IndexFormat> = Some(wgpu::IndexFormat::Uint16);
48 const STRIDE: wgpu::BufferAddress = mem::size_of::<Self>() as wgpu::BufferAddress;
49}
50
51#[derive(Copy, Clone)]
52pub enum ParticleMode {
53 CampfireSmoke = 0,
54 CampfireFire = 1,
55 GunPowderSpark = 2,
56 Shrapnel = 3,
57 FireworkBlue = 4,
58 FireworkGreen = 5,
59 FireworkPurple = 6,
60 FireworkRed = 7,
61 FireworkWhite = 8,
62 FireworkYellow = 9,
63 Leaf = 10,
64 Firefly = 11,
65 Bee = 12,
66 GroundShockwave = 13,
67 EnergyHealing = 14,
68 EnergyNature = 15,
69 FlameThrower = 16,
70 FireShockwave = 17,
71 FireBowl = 18,
72 Snow = 19,
73 Explosion = 20,
74 Ice = 21,
75 LifestealBeam = 22,
76 CultistFlame = 23,
77 StaticSmoke = 24,
78 Blood = 25,
79 Enraged = 26,
80 BigShrapnel = 27,
81 Laser = 28,
82 Bubbles = 29,
83 Water = 30,
84 IceSpikes = 31,
85 Drip = 32,
86 Tornado = 33,
87 Death = 34,
88 EnergyBuffing = 35,
89 WebStrand = 36,
90 BlackSmoke = 37,
91 Lightning = 38,
92 Steam = 39,
93 BarrelOrgan = 40,
94 PotionSickness = 41,
95 GigaSnow = 42,
96 CyclopsCharge = 43,
97 SnowStorm = 44,
98 PortalFizz = 45,
99 Ink = 46,
100 IceWhirlwind = 47,
101 FieryBurst = 48,
102 FieryBurstVortex = 49,
103 FieryBurstSparks = 50,
104 FieryBurstAsh = 51,
105 FieryTornado = 52,
106 PhoenixCloud = 53,
107 FieryDropletTrace = 54,
108 EnergyPhoenix = 55,
109 PhoenixBeam = 56,
110 PhoenixBuildUpAim = 57,
111 ClayShrapnel = 58,
112 Airflow = 59,
113 Spore = 60,
114 SurpriseEgg = 61,
115 FlameTornado = 62,
116 Poison = 63,
117 WaterFoam = 64,
118 EngineJet = 65,
119 Transformation = 66,
120 FireGigasAsh = 67,
121 FireGigasWhirlwind = 68,
122 FireGigasOverheat = 69,
123 FireGigasExplosion = 70,
124 FirePillarIndicator = 71,
125 FirePillar = 72,
126 FireLowShockwave = 73,
127 PipeSmoke = 74,
128 TrainSmoke = 75,
129 Bubble = 76,
130 ElephantVacuum = 77,
131 ElectricSparks = 78,
132 FlamethrowerBlue = 79,
133 FlameCloakOrbit = 80,
134}
135
136impl ParticleMode {
137 pub fn into_uint(self) -> u32 { self as u32 }
138}
139
140#[repr(C)]
141#[derive(Copy, Clone, Debug, Zeroable, Pod)]
142pub struct Instance {
143 inst_time: f32,
146
147 inst_lifespan: f32,
149
150 inst_entropy: f32,
153
154 inst_mode: i32,
157
158 inst_dir: [f32; 3],
160
161 inst_pos: [f32; 3],
172
173 inst_start_wind_vel: [f32; 2],
174}
175
176impl Instance {
177 pub fn new(
178 inst_time: f64,
179 lifespan: f32,
180 inst_mode: ParticleMode,
181 inst_pos: Vec3<f32>,
182 inst_start_wind_vel: Vec2<f32>,
183 ) -> Self {
184 use rand::RngExt;
185 Self {
186 inst_time: (inst_time % super::TIME_OVERFLOW) as f32,
187 inst_lifespan: lifespan,
188 inst_entropy: rand::rng().random(),
189 inst_mode: inst_mode as i32,
190 inst_pos: inst_pos.into_array(),
191 inst_start_wind_vel: inst_start_wind_vel.into_array(),
192 inst_dir: [0.0, 0.0, 0.0],
193 }
194 }
195
196 pub fn new_directed(
197 inst_time: f64,
198 lifespan: f32,
199 inst_mode: ParticleMode,
200 inst_pos: Vec3<f32>,
201 inst_pos2: Vec3<f32>,
202 inst_start_wind_vel: Vec2<f32>,
203 ) -> Self {
204 use rand::RngExt;
205 Self {
206 inst_time: (inst_time % super::TIME_OVERFLOW) as f32,
207 inst_lifespan: lifespan,
208 inst_entropy: rand::rng().random(),
209 inst_mode: inst_mode as i32,
210 inst_pos: inst_pos.into_array(),
211 inst_start_wind_vel: inst_start_wind_vel.into_array(),
212 inst_dir: (inst_pos2 - inst_pos).into_array(),
213 }
214 }
215
216 fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
217 const ATTRIBUTES: [wgpu::VertexAttribute; 7] = wgpu::vertex_attr_array![2 => Float32, 3 => Float32, 4 => Float32, 5 => Sint32, 6 => Float32x3, 7 => Float32x3, 8 => Float32x2];
218 wgpu::VertexBufferLayout {
219 array_stride: mem::size_of::<Self>() as wgpu::BufferAddress,
220 step_mode: wgpu::VertexStepMode::Instance,
221 attributes: &ATTRIBUTES,
222 }
223 }
224}
225
226impl Default for Instance {
227 fn default() -> Self {
228 Self::new(
229 0.0,
230 0.0,
231 ParticleMode::CampfireSmoke,
232 Vec3::zero(),
233 Vec2::zero(),
234 )
235 }
236}
237
238pub struct ParticlePipeline {
239 pub pipeline: wgpu::RenderPipeline,
240}
241
242impl ParticlePipeline {
243 pub fn new(
244 device: &wgpu::Device,
245 vs_module: &wgpu::ShaderModule,
246 fs_module: &wgpu::ShaderModule,
247 global_layout: &GlobalsLayouts,
248 aa_mode: AaMode,
249 format: wgpu::TextureFormat,
250 ) -> Self {
251 common_base::span!(_guard, "ParticlePipeline::new");
252 let render_pipeline_layout =
253 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
254 label: Some("Particle pipeline layout"),
255 push_constant_ranges: &[],
256 bind_group_layouts: &[&global_layout.globals, &global_layout.shadow_textures],
257 });
258
259 let samples = aa_mode.samples();
260
261 let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
262 label: Some("Particle pipeline"),
263 layout: Some(&render_pipeline_layout),
264 vertex: wgpu::VertexState {
265 module: vs_module,
266 entry_point: Some("main"),
267 buffers: &[Vertex::desc(), Instance::desc()],
268 compilation_options: Default::default(),
269 },
270 primitive: wgpu::PrimitiveState {
271 topology: wgpu::PrimitiveTopology::TriangleList,
272 strip_index_format: None,
273 front_face: wgpu::FrontFace::Ccw,
274 cull_mode: Some(wgpu::Face::Back),
275 unclipped_depth: false,
276 polygon_mode: wgpu::PolygonMode::Fill,
277 conservative: false,
278 },
279 depth_stencil: Some(wgpu::DepthStencilState {
280 format: wgpu::TextureFormat::Depth32Float,
281 depth_write_enabled: true,
282 depth_compare: wgpu::CompareFunction::GreaterEqual,
283 stencil: wgpu::StencilState {
284 front: wgpu::StencilFaceState::IGNORE,
285 back: wgpu::StencilFaceState::IGNORE,
286 read_mask: !0,
287 write_mask: 0,
288 },
289 bias: wgpu::DepthBiasState {
290 constant: 0,
291 slope_scale: 0.0,
292 clamp: 0.0,
293 },
294 }),
295 multisample: wgpu::MultisampleState {
296 count: samples,
297 mask: !0,
298 alpha_to_coverage_enabled: false,
299 },
300 fragment: Some(wgpu::FragmentState {
301 module: fs_module,
302 entry_point: Some("main"),
303 targets: &[
304 Some(wgpu::ColorTargetState {
305 format,
306 blend: Some(wgpu::BlendState {
307 color: wgpu::BlendComponent {
308 src_factor: wgpu::BlendFactor::SrcAlpha,
309 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
310 operation: wgpu::BlendOperation::Add,
311 },
312 alpha: wgpu::BlendComponent {
313 src_factor: wgpu::BlendFactor::One,
314 dst_factor: wgpu::BlendFactor::One,
315 operation: wgpu::BlendOperation::Add,
316 },
317 }),
318 write_mask: wgpu::ColorWrites::ALL,
319 }),
320 Some(wgpu::ColorTargetState {
321 format: wgpu::TextureFormat::Rgba8Uint,
322 blend: None,
323 write_mask: wgpu::ColorWrites::ALL,
324 }),
325 ],
326 compilation_options: Default::default(),
327 }),
328 multiview: None,
329 cache: None,
330 });
331
332 Self {
333 pipeline: render_pipeline,
334 }
335 }
336}