1use rand::prelude::*;
2use rand_chacha::ChaCha8Rng;
3use vek::*;
4
5fn seed_from_pos(pos: Vec3<i32>) -> [u8; 32] {
7 [
8 pos.x as u8,
9 (pos.x >> 8) as u8,
10 (pos.x >> 16) as u8,
11 (pos.x >> 24) as u8,
12 0,
13 0,
14 0,
15 0,
16 pos.y as u8,
17 (pos.y >> 8) as u8,
18 (pos.y >> 16) as u8,
19 (pos.y >> 24) as u8,
20 0,
21 0,
22 0,
23 0,
24 pos.z as u8,
25 (pos.z >> 8) as u8,
26 (pos.z >> 16) as u8,
27 (pos.z >> 24) as u8,
28 0,
29 0,
30 0,
31 0,
32 0,
33 0,
34 0,
35 0,
36 0,
37 0,
38 0,
39 0,
40 ]
41}
42
43#[derive(Debug)]
44struct FireplaceTiming {
45 breakfast: f32, dinner: f32, daily_cycle: f32, }
50
51const SMOKE_BREAKFAST_STRENGTH: f32 = 96.0;
52const SMOKE_BREAKFAST_HALF_DURATION: f32 = 45.0 * 60.0;
53const SMOKE_BREAKFAST_START: f32 = 5.0 * 60.0 * 60.0;
54const SMOKE_BREAKFAST_RANGE: f32 = 2.0 * 60.0 * 60.0;
55const SMOKE_DINNER_STRENGTH: f32 = 128.0;
56const SMOKE_DINNER_HALF_DURATION: f32 = 60.0 * 60.0;
57const SMOKE_DINNER_START: f32 = 17.0 * 60.0 * 60.0;
58const SMOKE_DINNER_RANGE: f32 = 2.0 * 60.0 * 60.0;
59const SMOKE_DAILY_CYCLE_MIN: f32 = 30.0 * 60.0;
60const SMOKE_DAILY_CYCLE_MAX: f32 = 120.0 * 60.0;
61const SMOKE_MAX_TEMPERATURE: f32 = 0.0; const SMOKE_MAX_TEMP_VALUE: f32 = 1.0;
63const SMOKE_TEMP_MULTIPLIER: f32 = 96.0;
64const SMOKE_DAILY_VARIATION: f32 = 32.0;
65
66#[derive(Debug)]
67struct FireplaceClimate {
68 daily_strength: f32, day_start: f32, day_end: f32, }
72
73fn create_timing(rng: &mut ChaCha8Rng) -> FireplaceTiming {
74 let breakfast: f32 = SMOKE_BREAKFAST_START + rng.gen::<f32>() * SMOKE_BREAKFAST_RANGE;
75 let dinner: f32 = SMOKE_DINNER_START + rng.gen::<f32>() * SMOKE_DINNER_RANGE;
76 let daily_cycle: f32 =
77 SMOKE_DAILY_CYCLE_MIN + rng.gen::<f32>() * (SMOKE_DAILY_CYCLE_MAX - SMOKE_DAILY_CYCLE_MIN);
78 FireplaceTiming {
79 breakfast,
80 dinner,
81 daily_cycle,
82 }
83}
84
85fn create_climate(temperature: f32) -> FireplaceClimate {
86 let daily_strength =
88 (SMOKE_MAX_TEMPERATURE - temperature).min(SMOKE_MAX_TEMP_VALUE) * SMOKE_TEMP_MULTIPLIER;
89 let day_start = (SMOKE_BREAKFAST_STRENGTH - daily_strength.max(0.0))
96 * (SMOKE_BREAKFAST_HALF_DURATION / SMOKE_BREAKFAST_STRENGTH);
97 let day_end = (SMOKE_DINNER_STRENGTH - daily_strength.max(0.0))
98 * (SMOKE_DINNER_HALF_DURATION / SMOKE_DINNER_STRENGTH);
99 FireplaceClimate {
100 daily_strength,
101 day_start,
102 day_end,
103 }
104}
105
106pub type Increasing = bool;
107
108pub fn smoke_at_time(position: Vec3<i32>, temperature: f32, time_of_day: f32) -> (f32, Increasing) {
109 let mut pseudorandom = ChaCha8Rng::from_seed(seed_from_pos(position));
110 let timing = create_timing(&mut pseudorandom);
111 let climate = create_climate(temperature);
112 let after_breakfast = time_of_day - timing.breakfast;
113 if after_breakfast < -SMOKE_BREAKFAST_HALF_DURATION {
114 (0.0, false)
116 } else if after_breakfast < 0.0 {
117 (
119 (SMOKE_BREAKFAST_HALF_DURATION + after_breakfast)
120 * (SMOKE_BREAKFAST_STRENGTH / SMOKE_BREAKFAST_HALF_DURATION),
121 true,
122 )
123 } else if after_breakfast < climate.day_start {
124 (
126 (SMOKE_BREAKFAST_HALF_DURATION - after_breakfast)
127 * (SMOKE_BREAKFAST_STRENGTH / SMOKE_BREAKFAST_HALF_DURATION),
128 false,
129 )
130 } else if time_of_day < timing.dinner - climate.day_end {
131 let day_phase = ((after_breakfast - climate.day_start) / timing.daily_cycle).fract();
133 if day_phase < 0.5 {
134 (
135 (climate.daily_strength + day_phase * (2.0 * SMOKE_DAILY_VARIATION)).max(0.0),
136 true,
137 )
138 } else {
139 (
140 (climate.daily_strength + (1.0 - day_phase) * (2.0 * SMOKE_DAILY_VARIATION))
141 .max(0.0),
142 false,
143 )
144 }
145 } else if time_of_day < timing.dinner {
146 (
148 (SMOKE_DINNER_HALF_DURATION + time_of_day - timing.dinner)
149 * (SMOKE_DINNER_STRENGTH / SMOKE_DINNER_HALF_DURATION),
150 true,
151 )
152 } else {
153 (
155 (SMOKE_DINNER_HALF_DURATION - time_of_day + timing.dinner).max(0.0)
156 * (SMOKE_DINNER_STRENGTH / SMOKE_DINNER_HALF_DURATION),
157 false,
158 )
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 fn test_conditions(position: Vec3<i32>, temperature: f32) {
167 print!("{} T{:.1} ", position, temperature);
168 let mut pseudorandom = ChaCha8Rng::from_seed(seed_from_pos(position));
169 if true {
170 let timing = create_timing(&mut pseudorandom);
171 let climate = create_climate(temperature);
172 print!(
173 "B{:.1}+{:.1} D{:.1}-{:.1} C{:.0} S{:.0} ",
174 timing.breakfast / 3600.0,
175 climate.day_start / 3600.0,
176 timing.dinner / 3600.0,
177 climate.day_end / 3600.0,
178 timing.daily_cycle / 60.0,
179 climate.daily_strength
180 );
181 }
182 for i in 0..24 {
183 print!(" {}:", i);
184 for j in 0..6 {
185 let time_of_day = 60.0 * 60.0 * (i as f32) + 60.0 * 10.0 * (j as f32);
186 let res = smoke_at_time(position, temperature, time_of_day);
187 print!("{:.0}{} ", res.0, if res.1 { "^" } else { "" },);
188 assert!(res.0 >= 0.0);
189 assert!(res.0 <= SMOKE_DINNER_STRENGTH);
190 }
191 }
192 println!();
193 }
194
195 #[test]
196 fn test_smoke() {
197 test_conditions(Vec3::new(25_i32, 11, 33), -1.0);
198 test_conditions(Vec3::new(22_i32, 11, 33), -0.5);
199 test_conditions(Vec3::new(27_i32, 11, 33), 0.0);
200 test_conditions(Vec3::new(24_i32, 11, 33), 0.5);
201 test_conditions(Vec3::new(26_i32, 11, 33), 1.0);
202 }
203}