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