Skip to content

Commit 08ba703

Browse files
committed
New heatmap type: lines between the killers and the victims
1 parent d89980c commit 08ba703

4 files changed

Lines changed: 156 additions & 37 deletions

File tree

Cargo.lock

Lines changed: 35 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ iced_native = { git = "https://github.com/hecrj/iced.git", rev = "ff15ebc54778de
1919
tokio = { version = "0.2", features = ["blocking"] }
2020
rayon = "1.4.0"
2121
nfd2 = "=0.2.1"
22+
line_drawing = "=0.8.0"
2223

2324
# [profile.release]
2425
# lto = true

src/heatmap.rs

Lines changed: 86 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ impl Display for CoordsType {
3030
pub enum HeatmapType {
3131
VictimPosition,
3232
KillerPosition,
33+
Lines,
3334
}
3435

3536
impl Default for HeatmapType {
@@ -43,6 +44,7 @@ impl Display for HeatmapType {
4344
match self {
4445
HeatmapType::VictimPosition => write!(f, "Victim position"),
4546
HeatmapType::KillerPosition => write!(f, "Killer position"),
47+
HeatmapType::Lines => write!(f, "Killer -> victim lines"),
4648
}
4749
}
4850
}
@@ -104,11 +106,84 @@ impl HeatMapGenerator {
104106
deaths: impl IntoIterator<Item = &'a Death>,
105107
image: &mut ImageBuffer<Rgb<u8>, Vec<u8>>,
106108
) {
107-
let nb_pixels = (image.width() * image.height()) as usize;
108-
let mut intensities = Vec::with_capacity(nb_pixels);
109-
intensities.resize_with(nb_pixels, || 0.0);
110-
let mut max_intensity = f32::NEG_INFINITY;
111-
let gradient = Gradient::new(vec![
109+
// lines
110+
if heatmap_type == HeatmapType::Lines {
111+
let line_gradient = Gradient::new(vec![
112+
LinSrgba::new(0.0, 0.6, 1.0, 1.0),
113+
LinSrgba::new(0.067, 0.8, 1.0, 1.0),
114+
LinSrgba::new(0.33, 1.0, 0.33, 1.0),
115+
LinSrgba::new(1.0, 0.0, 0.0, 1.0),
116+
LinSrgba::new(1.0, 0.8, 0.0, 1.0),
117+
// LinSrgba::new(1.0, 0.0, 0.0, 1.0),
118+
// LinSrgba::new(1.0, 1.0, 0.0, 1.0),
119+
// LinSrgba::new(0.0, 1.0, 0.0, 1.0),
120+
// LinSrgba::new(0.0, 1.0, 1.0, 1.0),
121+
// LinSrgba::new(0.0, 0.0, 1.0, 1.0),
122+
]);
123+
for death in deaths {
124+
if let (Some(killer_entity), Some(victim_entity)) =
125+
(&death.killer_entity_state, &death.victim_entity_state)
126+
{
127+
let killer_coords = self.game_coords_to_screen_coords(
128+
killer_entity.position.x,
129+
killer_entity.position.y,
130+
);
131+
let victim_coords = self.game_coords_to_screen_coords(
132+
victim_entity.position.x,
133+
victim_entity.position.y,
134+
);
135+
let points: Vec<((i32, i32), f32)> =
136+
line_drawing::XiaolinWu::<f32, i32>::new(killer_coords, victim_coords)
137+
.collect();
138+
139+
// this is needed because the line drawing algorithm doesn't always go in the start-end order, we need to check what order was used and invert the gradient as needed
140+
let (first_point_x, first_point_y) = match points.get(0) {
141+
Some(((first_point_x, first_point_y), _)) => {
142+
(*first_point_x as f32, *first_point_y as f32)
143+
}
144+
None => continue,
145+
};
146+
let dist_killer_x = killer_coords.0 - first_point_x;
147+
let dist_killer_y = killer_coords.1 - first_point_y;
148+
let dist_victim_x = victim_coords.0 - first_point_x;
149+
let dist_victim_y = victim_coords.1 - first_point_y;
150+
let invert_gradient = dist_killer_x * dist_killer_x
151+
+ dist_killer_y * dist_killer_y
152+
> dist_victim_x * dist_victim_x + dist_victim_y * dist_victim_y;
153+
154+
let len = points.len() as f32;
155+
for (index, ((x, y), alpha)) in points.iter().enumerate() {
156+
let (x, y) = (*x, *y);
157+
if y < 0 || y >= image.height() as i32 || x < 0 || x >= image.width() as i32
158+
{
159+
continue;
160+
}
161+
let color = if invert_gradient {
162+
line_gradient.get(1.0 - ((index + 1) as f32 / len))
163+
} else {
164+
line_gradient.get((index + 1) as f32 / len)
165+
};
166+
let pixel = image.get_pixel_mut(x as u32, y as u32);
167+
if let [r, g, b] = pixel.channels() {
168+
*pixel = Rgb::from([
169+
((alpha * color.red + (1.0 - alpha) * (*r as f32 / 255.0)) * 255.0)
170+
as u8,
171+
((alpha * color.green + (1.0 - alpha) * (*g as f32 / 255.0))
172+
* 255.0) as u8,
173+
((alpha * color.blue + (1.0 - alpha) * (*b as f32 / 255.0)) * 255.0)
174+
as u8,
175+
]);
176+
} else {
177+
unreachable!();
178+
}
179+
}
180+
}
181+
}
182+
return;
183+
}
184+
185+
// heatmap
186+
let heatmap_gradient = Gradient::new(vec![
112187
// LinSrgba::new(0.0, 0.0, 0.0, 0.0),
113188
LinSrgba::new(0.0, 0.0, 1.0, 0.0),
114189
LinSrgba::new(0.0, 1.0, 1.0, 0.25),
@@ -117,10 +192,15 @@ impl HeatMapGenerator {
117192
LinSrgba::new(1.0, 0.0, 0.0, 1.0),
118193
// LinSrgba::new(1.0, 1.0, 1.0, 1.0),
119194
]);
195+
let nb_pixels = (image.width() * image.height()) as usize;
196+
let mut intensities = Vec::with_capacity(nb_pixels);
197+
intensities.resize_with(nb_pixels, || 0.0);
198+
let mut max_intensity = f32::NEG_INFINITY;
120199
for death in deaths {
121200
let entity_state = match heatmap_type {
122201
HeatmapType::VictimPosition => &death.victim_entity_state,
123202
HeatmapType::KillerPosition => &death.killer_entity_state,
203+
HeatmapType::Lines => unreachable!(),
124204
};
125205
if let Some(entity_state) = entity_state {
126206
let game_coords = entity_state.position;
@@ -152,7 +232,7 @@ impl HeatMapGenerator {
152232
}
153233
for (pixel, base_intensity) in image.pixels_mut().zip(intensities) {
154234
let intensity = base_intensity / max_intensity;
155-
let heat_color = gradient.get(intensity);
235+
let heat_color = heatmap_gradient.get(intensity);
156236
if let [r, g, b] = pixel.channels() {
157237
*pixel = Rgb::from([
158238
((heat_color.alpha * heat_color.red

src/main.rs

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -273,22 +273,26 @@ impl SettingsPane {
273273
)
274274
},
275275
);
276-
let choose_heatmap_type = [HeatmapType::VictimPosition, HeatmapType::KillerPosition]
277-
.iter()
278-
.fold(
279-
Column::new().spacing(10).push(Text::new("Heatmap type:")),
280-
|column, heatmap_type| {
281-
column.push(
282-
Radio::new(
283-
*heatmap_type,
284-
&format!("{}", heatmap_type),
285-
Some(self.heatmap_type),
286-
Message::HeatmapTypeChanged,
287-
)
288-
.style(self.theme),
276+
let choose_heatmap_type = [
277+
HeatmapType::VictimPosition,
278+
HeatmapType::KillerPosition,
279+
HeatmapType::Lines,
280+
]
281+
.iter()
282+
.fold(
283+
Column::new().spacing(10).push(Text::new("Heatmap type:")),
284+
|column, heatmap_type| {
285+
column.push(
286+
Radio::new(
287+
*heatmap_type,
288+
&format!("{}", heatmap_type),
289+
Some(self.heatmap_type),
290+
Message::HeatmapTypeChanged,
289291
)
290-
},
291-
);
292+
.style(self.theme),
293+
)
294+
},
295+
);
292296

293297
let x_pos_input = TextInput::new(
294298
&mut self.x_pos_input_state,
@@ -607,18 +611,33 @@ impl Application for App {
607611
Message::XPosInputChanged(input) => {
608612
let settings_pane = self.get_settings_pane();
609613
settings_pane.x_pos = input.parse().ok();
614+
if let Some(x_pos) = settings_pane.x_pos {
615+
if !x_pos.is_normal() && x_pos != 0.0 {
616+
settings_pane.x_pos = None;
617+
}
618+
}
610619
settings_pane.x_pos_input = input;
611620
self.try_generate_heatmap();
612621
}
613622
Message::YPosInputChanged(input) => {
614623
let settings_pane = self.get_settings_pane();
615624
settings_pane.y_pos = input.parse().ok();
625+
if let Some(y_pos) = settings_pane.y_pos {
626+
if !y_pos.is_normal() && y_pos != 0.0 {
627+
settings_pane.y_pos = None;
628+
}
629+
}
616630
settings_pane.y_pos_input = input;
617631
self.try_generate_heatmap();
618632
}
619633
Message::ScaleInputChanged(input) => {
620634
let settings_pane = self.get_settings_pane();
621635
settings_pane.scale = input.parse().ok();
636+
if let Some(scale) = settings_pane.scale {
637+
if !scale.is_normal() {
638+
settings_pane.scale = None;
639+
}
640+
}
622641
settings_pane.scale_input = input;
623642
self.try_generate_heatmap();
624643
}

0 commit comments

Comments
 (0)