motion_content/particles/components/
image.rs1use super::*;
4
5#[derive(Clone, Copy, Debug)]
7pub enum ImageDensity {
8 Alpha,
10 Luminance,
12}
13
14#[derive(Clone)]
15pub(super) struct ImageDistribution {
16 image: Arc<crate::ImageAsset>,
17 bounds: Rect,
18 ends: Arc<[f64]>,
19 total: f64,
20}
21
22impl ImageEmitter {
23 pub fn new(
25 image: Arc<crate::ImageAsset>,
26 bounds: Rect,
27 density: ImageDensity,
28 limits: SimulationLimits,
29 ) -> Result<Self, ValidationError> {
30 motion_core::drawing::validate_rect(bounds, "particles.image", "bounds")?;
31 let count = u64::from(image.width()) * u64::from(image.height());
32 if bounds.width() <= 0.0
33 || bounds.height() <= 0.0
34 || count > 1_000_000
35 || count * 64 > limits.max_bytes
36 || count * 8 > limits.max_work
37 {
38 return Err(limit("Image emitter preparation allowance exhausted"));
39 }
40 let mut ends = Vec::new();
41 ends.try_reserve_exact(count as usize)
42 .map_err(|_| limit("Image CDF allocation failed"))?;
43 let mut total = 0.0;
44 for row in 0..image.height() {
45 for column in 0..image.width() {
46 let pixel = image.levels()[0]
47 .pixel(column, row)
48 .ok_or_else(|| invalid("image", "Missing source pixel"))?;
49 let weight = match density {
50 ImageDensity::Alpha => f64::from(pixel[3]),
51 ImageDensity::Luminance => (f64::from(pixel[0]) * 0.2126
52 + f64::from(pixel[1]) * 0.7152
53 + f64::from(pixel[2]) * 0.0722)
54 .max(0.0),
55 };
56 let next = total + weight;
57 if !next.is_finite() || (weight > 0.0 && next == total) {
58 return Err(invalid(
59 "image",
60 "Image density cannot represent finite positive weights",
61 ));
62 }
63 total = next;
64 ends.push(total);
65 }
66 }
67 if total == 0.0 {
68 return Err(invalid(
69 "image",
70 "Image contains no positive emission density",
71 ));
72 }
73 Ok(Self(EmitterSettings::new(EmitterSource::Image(Arc::new(
74 ImageDistribution {
75 image,
76 bounds,
77 ends: ends.into(),
78 total,
79 },
80 )))))
81 }
82}
83
84impl ImageDistribution {
85 pub(super) fn sample(&self, random: &mut Random) -> Result<(Point, [f32; 4]), ValidationError> {
86 let choice = random.next_float() * self.total;
87 let index = self
88 .ends
89 .partition_point(|end| *end <= choice)
90 .min(self.ends.len() - 1);
91 let column = index as u32 % self.image.width();
92 let row = index as u32 / self.image.width();
93 let point = Point::new(
94 self.bounds.left
95 + (f64::from(column) + random.next_float()) / f64::from(self.image.width())
96 * self.bounds.width(),
97 self.bounds.top
98 + (f64::from(row) + random.next_float()) / f64::from(self.image.height())
99 * self.bounds.height(),
100 );
101 Ok((
102 point,
103 self.image.levels()[0]
104 .pixel(column, row)
105 .ok_or_else(|| invalid("image", "Image CDF pixel outside source"))?,
106 ))
107 }
108}