Initial work on the actual maps for the heatmap overview

This commit is contained in:
Lol3rrr
2024-10-05 05:18:28 +02:00
parent ae85177697
commit f7273b5a39
15 changed files with 228 additions and 55 deletions

1
.gitattributes vendored
View File

@@ -1 +1,2 @@
testfiles/* filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text

69
Cargo.lock generated
View File

@@ -4,9 +4,9 @@ version = 4
[[package]]
name = "addr2line"
version = "0.24.1"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375"
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
dependencies = [
"gimli",
]
@@ -289,7 +289,7 @@ dependencies = [
"futures-util",
"image",
"memmap2",
"reqwest 0.12.7",
"reqwest 0.12.8",
"serde",
"serde_json",
"steam-openid",
@@ -411,9 +411,9 @@ checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3"
[[package]]
name = "cc"
version = "1.1.22"
version = "1.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9540e661f81799159abee814118cc139a2004b3a3aa3ea37724a1b66530b90e0"
checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938"
dependencies = [
"jobserver",
"libc",
@@ -465,9 +465,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.18"
version = "4.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3"
checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615"
dependencies = [
"clap_builder",
"clap_derive",
@@ -475,9 +475,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.18"
version = "4.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b"
checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b"
dependencies = [
"anstream",
"anstyle",
@@ -666,7 +666,7 @@ dependencies = [
[[package]]
name = "csdemo"
version = "0.1.0"
source = "git+https://github.com/Lol3rrr/csdemo.git#777248ab3c597b36103a9e0fa05e606134a1503c"
source = "git+https://github.com/Lol3rrr/csdemo.git#4671d0cbde48800dbb99cabba27ccb408df9fc3f"
dependencies = [
"bitter",
"phf",
@@ -719,7 +719,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
dependencies = [
"cfg-if",
"hashbrown",
"hashbrown 0.14.5",
"lock_api",
"once_cell",
"parking_lot_core",
@@ -1113,9 +1113,9 @@ dependencies = [
[[package]]
name = "gimli"
version = "0.31.0"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "gloo-net"
@@ -1238,6 +1238,12 @@ version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "hashbrown"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
[[package]]
name = "heck"
version = "0.5.0"
@@ -1332,9 +1338,9 @@ checksum = "08a397c49fec283e3d6211adbe480be95aae5f304cfb923e9970e08956d5168a"
[[package]]
name = "httparse"
version = "1.9.4"
version = "1.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9"
checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946"
[[package]]
name = "httpdate"
@@ -1509,12 +1515,12 @@ checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126"
[[package]]
name = "indexmap"
version = "2.5.0"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
dependencies = [
"equivalent",
"hashbrown",
"hashbrown 0.15.0",
]
[[package]]
@@ -1542,9 +1548,9 @@ checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767"
[[package]]
name = "ipnet"
version = "2.10.0"
version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4"
checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708"
[[package]]
name = "is_terminal_polyfill"
@@ -2114,9 +2120,9 @@ dependencies = [
[[package]]
name = "object"
version = "0.36.4"
version = "0.36.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a"
checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
dependencies = [
"memchr",
]
@@ -2819,9 +2825,9 @@ dependencies = [
[[package]]
name = "reqwest"
version = "0.12.7"
version = "0.12.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63"
checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b"
dependencies = [
"base64 0.22.1",
"bytes",
@@ -2844,7 +2850,7 @@ dependencies = [
"once_cell",
"percent-encoding",
"pin-project-lite",
"rustls-pemfile 2.1.3",
"rustls-pemfile 2.2.0",
"serde",
"serde_json",
"serde_urlencoded",
@@ -2947,11 +2953,10 @@ dependencies = [
[[package]]
name = "rustls-pemfile"
version = "2.1.3"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425"
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
dependencies = [
"base64 0.22.1",
"rustls-pki-types",
]
@@ -3927,9 +3932,9 @@ dependencies = [
[[package]]
name = "unicode-bidi"
version = "0.3.15"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893"
[[package]]
name = "unicode-ident"
@@ -3948,9 +3953,9 @@ dependencies = [
[[package]]
name = "unicode-properties"
version = "0.1.2"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524"
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
[[package]]
name = "unicode-segmentation"

View File

@@ -4,19 +4,27 @@ pub struct Config {
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct HeatMap {
#[serde(default)]
min_x: usize,
#[serde(default)]
min_y: usize,
max_x: usize,
max_y: usize,
max_value: usize,
rows: Vec<Vec<usize>>,
block_size: f32,
}
impl HeatMap {
fn new() -> Self {
fn new(block_size: f32) -> Self {
Self {
min_x: 0,
min_y: 0,
max_x: 0,
max_y: 0,
max_value: 0,
rows: Vec::new(),
block_size,
}
}
@@ -133,6 +141,8 @@ fn get_entityid(props: &[csdemo::parser::entities::EntityProp]) -> Option<i32> {
})
}
pub const MAX_COORD: f32 = (1 << 14) as f32;
fn process_tick(
config: &Config,
tick_state: &csdemo::parser::EntityTickStates,
@@ -146,7 +156,7 @@ fn process_tick(
for entity_state in tick_state
.states
.iter()
.filter(|s| s.class == "CCSPlayerPawn")
.filter(|s| s.class.as_ref() == "CCSPlayerPawn")
{
if let Some(pawn_id) = get_entityid(&entity_state.props) {
let user_id = pawn_ids.get(&pawn_id).cloned().unwrap();
@@ -192,8 +202,6 @@ fn process_tick(
assert!(y_coord >= 0.0);
assert!(z_coord >= 0.0);
const MAX_COORD: f32 = (1 << 14) as f32;
let x_cell_coord = ((x_cell as f32 * (1 << 9) as f32)) as f32;
let y_cell_coord = ((y_cell as f32 * (1 << 9) as f32)) as f32;
let z_cell_coord = ((z_cell as f32 * (1 << 9) as f32)) as f32;
@@ -235,7 +243,7 @@ fn process_tick(
// tracing::trace!("Coord (X, Y, Z): {:?} -> {:?}", (x_coord, y_coord, z_coord), (x_cell, y_cell));
let heatmap = heatmaps.entry(user_id.clone()).or_insert(HeatMap::new());
let heatmap = heatmaps.entry(user_id.clone()).or_insert(HeatMap::new(config.cell_size));
heatmap.increment(x_cell, y_cell);
}
}
@@ -256,13 +264,20 @@ impl core::fmt::Display for HeatMap {
}
impl HeatMap {
pub fn coords(&self) -> ((f32, f32), (f32, f32)) {
(
(self.min_x as f32 * self.block_size - MAX_COORD, self.max_x as f32 * self.block_size - MAX_COORD),
(self.min_y as f32 * self.block_size - MAX_COORD, self.max_y as f32 * self.block_size - MAX_COORD)
)
}
pub fn as_image(&self) -> image::RgbImage {
use colors_transform::Color;
let mut buffer = image::RgbImage::new(self.max_x as u32 + 1, self.max_y as u32 + 1);
let mut buffer = image::RgbImage::new((self.max_x - self.min_x) as u32 + 1, (self.max_y - self.min_y) as u32 + 1);
for (y, row) in self.rows.iter().rev().enumerate() {
for (x, cell) in row.iter().copied().chain(core::iter::repeat(0)).enumerate().take(self.max_x) {
for (x, cell) in row.iter().copied().chain(core::iter::repeat(0)).enumerate().take((self.max_x - self.min_x)) {
let scaled = (1.0/(1.0 + (cell as f32))) * 240.0;
let raw_rgb = colors_transform::Hsl::from(scaled, 100.0, 50.0).to_rgb();
@@ -273,17 +288,101 @@ impl HeatMap {
buffer
}
pub fn shrink(&mut self) {
let min_x = self.rows.iter().filter_map(|row| row.iter().enumerate().filter(|(_, v)| **v != 0).map(|(i, _)| i).next()).min().unwrap_or(0);
let min_y = self.rows.iter().enumerate().filter(|(y, row)| row.iter().any(|v| *v != 0)).map(|(i, _)| i).min().unwrap_or(0);
pub fn fit(&mut self, xs: core::ops::Range<f32>, ys: core::ops::Range<f32>) {
let min_x = (xs.start / self.block_size - self.min_x as f32) as usize;
let min_y = (ys.start / self.block_size - self.min_y as f32) as usize;
let _ = self.rows.drain(0..min_y);
for row in self.rows.iter_mut() {
let _ = row.drain(0..min_x);
let _ = row.drain(0..min_x.min(row.len()));
}
let x_steps = ((xs.end - xs.start) / self.block_size) as usize;
let y_steps = ((ys.end - ys.start) / self.block_size) as usize;
self.max_y = self.rows.len();
self.max_x = self.rows.iter().map(|r| r.len()).max().unwrap_or(0);
for row in self.rows.iter_mut() {
row.resize(x_steps, 0);
}
self.rows.resize_with(y_steps, || vec![0; x_steps]);
self.min_y += (0..min_y).len();
self.min_x += (0..min_x).len();
self.max_y = self.min_y + self.rows.len();
self.max_x = self.min_x + self.rows.iter().map(|r| r.len()).max().unwrap_or(0);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fit_no_cutoff() {
let mut input = HeatMap::new(2.0);
input.increment(3, 3);
input.increment(2, 2);
assert_eq!(input.min_x, 0);
assert_eq!(input.min_y, 0);
assert_eq!(input.max_x, 3);
assert_eq!(input.max_y, 3);
assert_eq!(
&vec![
vec![],
vec![],
vec![0, 0, 1],
vec![0, 0, 0, 1]
],
&input.rows
);
input.fit(2.0..10.0, 2.0..10.0);
assert_eq!(
&vec![
vec![0, 0, 0, 0],
vec![0, 1, 0, 0],
vec![0, 0, 1, 0],
vec![0, 0, 0, 0],
],
&input.rows
);
}
#[test]
fn fit_cutoff() {
let mut input = HeatMap::new(2.0);
input.increment(3, 3);
input.increment(2, 2);
assert_eq!(input.min_x, 0);
assert_eq!(input.min_y, 0);
assert_eq!(input.max_x, 3);
assert_eq!(input.max_y, 3);
assert_eq!(
&vec![
vec![],
vec![],
vec![0, 0, 1],
vec![0, 0, 0, 1]
],
&input.rows
);
input.fit(6.0..10.0, 6.0..10.0);
assert_eq!(
&vec![
vec![1, 0],
vec![0, 0]
],
&input.rows
);
}
}

View File

@@ -11,7 +11,8 @@ fn heatmap_nuke() {
let config = heatmap::Config { cell_size: 5.0 };
let result = heatmap::parse(&config, &input_bytes).unwrap();
assert_eq!(result.player_heatmaps.len(), result.player_info.len());
assert_eq!(result.player_heatmaps.len(), 11);
assert_eq!(result.entity_to_player.len(), 12);
}
#[test]

View File

@@ -72,7 +72,13 @@ async fn upload(
tracing::info!("Upload for Session: {:?}", steam_id);
let file_content = crate::get_demo_from_upload("demo", form).await.unwrap();
let file_content = match crate::get_demo_from_upload("demo", form).await {
Some(c) => c,
None => {
tracing::error!("Getting File content from request");
return Err((axum::http::StatusCode::BAD_REQUEST, "Failed to get file-content from upload"));
}
};
let user_folder = std::path::Path::new(&state.upload_folder).join(format!("{}/", steam_id));
if !tokio::fs::try_exists(&user_folder).await.unwrap_or(false) {
@@ -260,13 +266,28 @@ async fn heatmap(
Ok(d) => d,
Err(e) => {
tracing::error!("Querying DB: {:?}", e);
return Err(axum::http::StatusCode::INTERNAL_SERVER_ERROR);;
return Err(axum::http::StatusCode::INTERNAL_SERVER_ERROR);
}
};
// TODO
// These are currently the values for de_inferno
// The corresponding values for each map can be found using the Source2 Viewer and opening the
// files in 'game/csgo/pak01_dir.vpk' and then 'resource/overviews/{map}.txt'
let pos_x: f32 = 2087.0;
let pos_y: f32 = 3870.0;
let scale: f32 = 4.9;
let x = |map_coord: f32| {
(map_coord * scale) - pos_x + analysis::heatmap::MAX_COORD
};
let y = |map_coord: f32| {
-(map_coord * scale) + pos_y + analysis::heatmap::MAX_COORD
};
let data: Vec<common::demo_analysis::PlayerHeatmap> = result.into_iter().map(|(player, heatmap)| {
let mut heatmap: analysis::heatmap::HeatMap = serde_json::from_str(&heatmap.data).unwrap();
heatmap.shrink();
heatmap.fit(x(0.0)..x(1024.0), y(1024.0)..y(0.0));
let h_image = heatmap.as_image();
let mut buffer = std::io::Cursor::new(Vec::new());

View File

@@ -66,8 +66,8 @@ pub async fn run_api(
"/api/",
crate::api::router(crate::api::RouterConfig {
steam_api_key: steam_api_key.into(),
steam_callback_base_url: "http://localhost:3000".into(),
// steam_callback_base_url: "http://localhost:3000".into(),
steam_callback_base_url: "http://192.168.0.156:3000".into(),
steam_callback_path: "/api/steam/callback".into(),
upload_dir: upload_folder.clone(),
}),

View File

@@ -3,6 +3,8 @@
<head>
<link rel="stylesheet" href="/main.css">
<link data-trunk rel="copy-dir" href="static/"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body></body>

View File

@@ -3,11 +3,17 @@ use leptos_router::{Outlet, A};
pub mod heatmap;
#[derive(Debug, Clone)]
struct CurrentDemoName(ReadSignal<String>);
#[leptos::component]
pub fn demo() -> impl leptos::IntoView {
let params = leptos_router::use_params_map();
let id = move || params.with(|params| params.get("id").cloned().unwrap_or_default());
let (rx, map_tx) = create_signal(String::new());
provide_context(CurrentDemoName(rx.clone()));
let demo_info = create_resource(
|| (),
move |_| async move {
@@ -15,7 +21,11 @@ pub fn demo() -> impl leptos::IntoView {
.send()
.await
.unwrap();
res.json::<common::DemoInfo>().await.unwrap()
let value = res.json::<common::DemoInfo>().await.unwrap();
map_tx.set(value.map.clone());
value
},
);

View File

@@ -1,5 +1,7 @@
use leptos::*;
use super::CurrentDemoName;
#[leptos::component]
pub fn heatmaps() -> impl leptos::IntoView {
let heatmaps_resource =
@@ -40,12 +42,22 @@ fn heatmap_view(heatmaps: Vec<common::demo_analysis::PlayerHeatmap>) -> impl lep
let h1 = heatmaps.clone();
let map = use_context::<CurrentDemoName>().unwrap();
let style = stylers::style! {
"Heatmap-View",
img {
width: 75vw;
height: 75vw;
.heatmap_image {
width: 1024px;
height: 1024px;
display: block;
position: relative;
}
.heatmap_image > * {
position: absolute;
}
.heatmap_image > .heatmap {
opacity: 0.5;
}
};
@@ -72,7 +84,11 @@ fn heatmap_view(heatmaps: Vec<common::demo_analysis::PlayerHeatmap>) -> impl lep
move || {
match value.get() {
Some(heatmap) => view! {
<img class="heatmap_img" src=format!("data:image/png;base64,{}", heatmap.png_data) />
class=style,
<div class="heatmap_image">
<img class="radar" src=format!("/static/minimaps/{}.png", map.0.get()) />
<img class="heatmap" width=1024 height=1024 src=format!("data:image/png;base64,{}", heatmap.png_data) />
</div>
}.into_any(),
None => view! { <p>ERROR</p> }.into_any(),
}

BIN
frontend/static/minimaps/de_ancient.png LFS Executable file

Binary file not shown.

BIN
frontend/static/minimaps/de_anubis.png LFS Executable file

Binary file not shown.

BIN
frontend/static/minimaps/de_dust2.png LFS Executable file

Binary file not shown.

BIN
frontend/static/minimaps/de_inferno.png LFS Executable file

Binary file not shown.

BIN
frontend/static/minimaps/de_mirage.png LFS Executable file

Binary file not shown.

Binary file not shown.