1use super::super::{ExperimentalShader, GlobalsLayouts, PipelineModes, 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 Dust = 81,
135 CaveDust = 82,
136 BubbleAmbient = 83,
137}
138
139impl ParticleMode {
140 pub fn into_uint(self) -> u32 { self as u32 }
141}
142
143#[repr(C)]
144#[derive(Copy, Clone, Debug, Zeroable, Pod)]
145pub struct Instance {
146 inst_time: f32,
149
150 inst_lifespan: f32,
152
153 inst_entropy: f32,
156
157 inst_mode: i32,
160
161 inst_dir_color: [f32; 3],
163
164 inst_pos: [f32; 3],
175
176 inst_start_wind_vel: [f32; 2],
177
178 inst_voxel_light: [f32; 2],
184}
185
186impl Instance {
187 pub fn new(
188 inst_time: f64,
189 lifespan: f32,
190 inst_mode: ParticleMode,
191 inst_pos: Vec3<f32>,
192 inst_start_wind_vel: Vec2<f32>,
193 ) -> Self {
194 use rand::RngExt;
195 Self {
196 inst_time: (inst_time % super::TIME_OVERFLOW) as f32,
197 inst_lifespan: lifespan,
198 inst_entropy: rand::rng().random(),
199 inst_mode: inst_mode as i32,
200 inst_pos: inst_pos.into_array(),
201 inst_start_wind_vel: inst_start_wind_vel.into_array(),
202 inst_dir_color: [0.0, 0.0, 0.0],
203 inst_voxel_light: [1.0, 0.0],
204 }
205 }
206
207 pub fn new_directed(
208 inst_time: f64,
209 lifespan: f32,
210 inst_mode: ParticleMode,
211 inst_pos: Vec3<f32>,
212 inst_pos2: Vec3<f32>,
213 inst_start_wind_vel: Vec2<f32>,
214 ) -> Self {
215 use rand::RngExt;
216 Self {
217 inst_time: (inst_time % super::TIME_OVERFLOW) as f32,
218 inst_lifespan: lifespan,
219 inst_entropy: rand::rng().random(),
220 inst_mode: inst_mode as i32,
221 inst_pos: inst_pos.into_array(),
222 inst_start_wind_vel: inst_start_wind_vel.into_array(),
223 inst_dir_color: (inst_pos2 - inst_pos).into_array(),
224 inst_voxel_light: [1.0, 0.0],
225 }
226 }
227
228 pub fn new_colored(
229 inst_time: f64,
230 lifespan: f32,
231 inst_mode: ParticleMode,
232 inst_pos: Vec3<f32>,
233 col: Rgb<f32>,
234 inst_start_wind_vel: Vec2<f32>,
235 ) -> Self {
236 use rand::RngExt;
237 Self {
238 inst_time: (inst_time % super::TIME_OVERFLOW) as f32,
239 inst_lifespan: lifespan,
240 inst_entropy: rand::rng().random(),
241 inst_mode: inst_mode as i32,
242 inst_pos: inst_pos.into_array(),
243 inst_start_wind_vel: inst_start_wind_vel.into_array(),
244 inst_dir_color: col.into_array(),
245 inst_voxel_light: [1.0, 0.0],
246 }
247 }
248
249 pub fn with_light(self, sun_light: f32, glow_light: f32) -> Self {
250 Self {
251 inst_voxel_light: [sun_light, glow_light],
252 ..self
253 }
254 }
255
256 fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
257 const ATTRIBUTES: [wgpu::VertexAttribute; 8] = wgpu::vertex_attr_array![2 => Float32, 3 => Float32, 4 => Float32, 5 => Sint32, 6 => Float32x3, 7 => Float32x3, 8 => Float32x2, 9 => Float32x2];
258 wgpu::VertexBufferLayout {
259 array_stride: mem::size_of::<Self>() as wgpu::BufferAddress,
260 step_mode: wgpu::VertexStepMode::Instance,
261 attributes: &ATTRIBUTES,
262 }
263 }
264}
265
266impl Default for Instance {
267 fn default() -> Self {
268 Self::new(
269 0.0,
270 0.0,
271 ParticleMode::CampfireSmoke,
272 Vec3::zero(),
273 Vec2::zero(),
274 )
275 }
276}
277
278pub struct ParticlePipeline {
279 pub pipeline: wgpu::RenderPipeline,
280}
281
282impl ParticlePipeline {
283 pub fn new(
284 device: &wgpu::Device,
285 vs_module: &wgpu::ShaderModule,
286 fs_module: &wgpu::ShaderModule,
287 global_layout: &GlobalsLayouts,
288 format: wgpu::TextureFormat,
289 pipeline_modes: &PipelineModes,
290 ) -> Self {
291 common_base::span!(_guard, "ParticlePipeline::new");
292 let render_pipeline_layout =
293 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
294 label: Some("Particle pipeline layout"),
295 push_constant_ranges: &[],
296 bind_group_layouts: &[&global_layout.globals, &global_layout.shadow_textures],
297 });
298
299 let samples = pipeline_modes.aa.samples();
300
301 let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
302 label: Some("Particle pipeline"),
303 layout: Some(&render_pipeline_layout),
304 vertex: wgpu::VertexState {
305 module: vs_module,
306 entry_point: Some("main"),
307 buffers: &[Vertex::desc(), Instance::desc()],
308 compilation_options: Default::default(),
309 },
310 primitive: wgpu::PrimitiveState {
311 topology: wgpu::PrimitiveTopology::TriangleList,
312 strip_index_format: None,
313 front_face: wgpu::FrontFace::Ccw,
314 cull_mode: Some(wgpu::Face::Back),
315 unclipped_depth: false,
316 polygon_mode: if pipeline_modes
317 .experimental_shaders
318 .contains(&ExperimentalShader::Wireframe)
319 {
320 wgpu::PolygonMode::Line
321 } else {
322 wgpu::PolygonMode::Fill
323 },
324 conservative: false,
325 },
326 depth_stencil: Some(wgpu::DepthStencilState {
327 format: wgpu::TextureFormat::Depth32Float,
328 depth_write_enabled: true,
329 depth_compare: wgpu::CompareFunction::GreaterEqual,
330 stencil: wgpu::StencilState {
331 front: wgpu::StencilFaceState::IGNORE,
332 back: wgpu::StencilFaceState::IGNORE,
333 read_mask: !0,
334 write_mask: 0,
335 },
336 bias: wgpu::DepthBiasState {
337 constant: 0,
338 slope_scale: 0.0,
339 clamp: 0.0,
340 },
341 }),
342 multisample: wgpu::MultisampleState {
343 count: samples,
344 mask: !0,
345 alpha_to_coverage_enabled: false,
346 },
347 fragment: Some(wgpu::FragmentState {
348 module: fs_module,
349 entry_point: Some("main"),
350 targets: &[
351 Some(wgpu::ColorTargetState {
352 format,
353 blend: Some(wgpu::BlendState {
354 color: wgpu::BlendComponent {
355 src_factor: wgpu::BlendFactor::SrcAlpha,
356 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
357 operation: wgpu::BlendOperation::Add,
358 },
359 alpha: wgpu::BlendComponent {
360 src_factor: wgpu::BlendFactor::One,
361 dst_factor: wgpu::BlendFactor::One,
362 operation: wgpu::BlendOperation::Add,
363 },
364 }),
365 write_mask: wgpu::ColorWrites::ALL,
366 }),
367 Some(wgpu::ColorTargetState {
368 format: wgpu::TextureFormat::Rgba8Uint,
369 blend: None,
370 write_mask: wgpu::ColorWrites::ALL,
371 }),
372 ],
373 compilation_options: Default::default(),
374 }),
375 multiview: None,
376 cache: None,
377 });
378
379 Self {
380 pipeline: render_pipeline,
381 }
382 }
383}