1use super::super::{Draw, Fill, Shadow, Shape, Stroke, place};
2use super::between;
3use pfx_core::sim::Rng;
4use pfx_physics::rigid::{
5 BodyDesc, BodyId, ColliderDesc, Combine, Friction, Layers, Locks, Pose, World,
6};
7
8#[derive(Clone, Copy, Debug, PartialEq)]
9pub struct Shatter {
10 pub fill: Fill,
11 pub stroke: Option<Stroke>,
12 pub count: u32,
13 pub half: [[f32; 2]; 2],
14 pub radius: f32,
15 pub depth: f32,
16 pub speed: [f32; 2],
17 pub direction: f32,
18 pub spread: f32,
19 pub spin: [f32; 2],
20 pub life: [f32; 2],
21 pub fade: f32,
22 pub density: f32,
23 pub restitution: f32,
24 pub friction: f32,
25 pub layers: Layers,
26 pub elevation: f32,
27 pub shadow: Shadow,
28}
29
30impl Shatter {
31 pub fn new(fill: Fill, half: [[f32; 2]; 2]) -> Self {
32 Self {
33 fill,
34 stroke: None,
35 count: 8,
36 half,
37 radius: 2.0,
38 depth: 4.0,
39 speed: [200.0, 500.0],
40 direction: -std::f32::consts::FRAC_PI_2,
41 spread: std::f32::consts::PI,
42 spin: [0.0, 6.0],
43 life: [1.5, 2.5],
44 fade: 0.4,
45 density: 1.0,
46 restitution: 0.3,
47 friction: 0.6,
48 layers: Layers::ALL,
49 elevation: 1.0,
50 shadow: Shadow::Cast,
51 }
52 }
53}
54
55#[derive(Clone, Copy, Debug, PartialEq)]
56struct Shard {
57 body: BodyId,
58 half: [f32; 2],
59 radius: f32,
60 age: f32,
61 life: f32,
62 fade: f32,
63 fill: Fill,
64 stroke: Option<Stroke>,
65 elevation: f32,
66 shadow: Shadow,
67}
68
69pub struct Shards {
70 items: Vec<Shard>,
71 cap: usize,
72 rng: Rng,
73}
74
75pub fn angle_of(rotation: [f32; 4]) -> f32 {
76 2.0 * rotation[2].atan2(rotation[3])
77}
78
79impl Shards {
80 pub fn new(cap: usize, seed: u64) -> Self {
81 Self {
82 items: Vec::with_capacity(cap),
83 cap,
84 rng: Rng::new(seed),
85 }
86 }
87
88 pub fn live(&self) -> usize {
89 self.items.len()
90 }
91
92 pub fn bodies(&self) -> impl Iterator<Item = BodyId> + '_ {
93 self.items.iter().map(|shard| shard.body)
94 }
95
96 pub fn spawn(&mut self, world: &mut World, shatter: &Shatter, at: [f32; 2]) -> u32 {
97 let mut spawned = 0;
98 for _ in 0..shatter.count {
99 if self.items.len() >= self.cap {
100 break;
101 }
102 let rng = &mut self.rng;
103 let half = [
104 between(rng, [shatter.half[0][0], shatter.half[1][0]]),
105 between(rng, [shatter.half[0][1], shatter.half[1][1]]),
106 ];
107 let angle = shatter.direction + (rng.next_f32() - 0.5) * shatter.spread;
108 let speed = between(rng, shatter.speed);
109 let spin = between(rng, shatter.spin) * if rng.next_bool() { 1.0 } else { -1.0 };
110 let life = between(rng, shatter.life).max(1e-3);
111 let turn = rng.next_f32() * std::f32::consts::TAU;
112 let pose = Pose::at([at[0], at[1], 0.0]).turned([0.0, 0.0, 1.0], turn);
113 let body = world.add_body(
114 BodyDesc::dynamic(pose)
115 .with_velocity([angle.cos() * speed, angle.sin() * speed, 0.0])
116 .with_spin([0.0, 0.0, spin])
117 .with_locks(Locks::PLANE_XY),
118 );
119 let radius = shatter.radius.clamp(0.0, half[0].min(half[1]));
120 let collider =
121 ColliderDesc::round_box([half[0], half[1], shatter.depth.max(0.5)], radius)
122 .with_density(shatter.density)
123 .with_friction(Friction::uniform(shatter.friction))
124 .with_restitution(shatter.restitution, Combine::Max)
125 .with_layers(shatter.layers);
126 if world.add_collider(body, collider).is_none() {
127 world.remove_body(body);
128 continue;
129 }
130 self.items.push(Shard {
131 body,
132 half,
133 radius,
134 age: 0.0,
135 life,
136 fade: shatter.fade.max(0.0),
137 fill: shatter.fill,
138 stroke: shatter.stroke,
139 elevation: shatter.elevation,
140 shadow: shatter.shadow,
141 });
142 spawned += 1;
143 }
144 spawned
145 }
146
147 pub fn step(&mut self, world: &mut World, dt: f32) {
148 if dt.is_nan() || dt <= 0.0 {
149 return;
150 }
151 let mut index = 0;
152 while index < self.items.len() {
153 let shard = &mut self.items[index];
154 shard.age += dt;
155 if shard.age >= shard.life {
156 world.remove_body(shard.body);
157 self.items.swap_remove(index);
158 } else {
159 index += 1;
160 }
161 }
162 }
163
164 pub fn clear(&mut self, world: &mut World) {
165 for shard in self.items.drain(..) {
166 world.remove_body(shard.body);
167 }
168 }
169
170 pub fn draws(&self, world: &World, out: &mut Vec<Draw>) {
171 for shard in &self.items {
172 let Some(pose) = world.pose(shard.body) else {
173 continue;
174 };
175 let left = shard.life - shard.age;
176 let alpha = if shard.fade > 0.0 {
177 (left / shard.fade).clamp(0.0, 1.0)
178 } else {
179 1.0
180 };
181 let mut draw = Draw::new(Shape::Rect {
182 half: shard.half,
183 radii: [shard.radius; 4],
184 })
185 .transform(place(
186 [pose.position[0], pose.position[1]],
187 angle_of(pose.rotation),
188 ))
189 .elevation(shard.elevation)
190 .shadow(shard.shadow)
191 .opacity(alpha)
192 .unpicked();
193 draw.fill = shard.fill;
194 draw.stroke = shard.stroke;
195 out.push(draw);
196 }
197 }
198}