-
Notifications
You must be signed in to change notification settings - Fork 95
/
heatmap.go
111 lines (86 loc) · 2.98 KB
/
heatmap.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
package main
import (
"image"
"image/draw"
"image/jpeg"
"os"
r2 "github.com/golang/geo/r2"
heatmap "github.com/markus-wa/go-heatmap/v2"
schemes "github.com/markus-wa/go-heatmap/v2/schemes"
ex "github.com/markus-wa/demoinfocs-golang/v4/examples"
demoinfocs "github.com/markus-wa/demoinfocs-golang/v4/pkg/demoinfocs"
events "github.com/markus-wa/demoinfocs-golang/v4/pkg/demoinfocs/events"
"github.com/markus-wa/demoinfocs-golang/v4/pkg/demoinfocs/msg"
)
const (
dotSize = 15
opacity = 128
jpegQuality = 90
)
// Run like this: go run heatmap.go -demo /path/to/demo.dem > out.jpg
func main() {
//
// Parsing
//
f, err := os.Open(ex.DemoPathFromArgs())
checkError(err)
defer f.Close()
p := demoinfocs.NewParser(f)
defer p.Close()
// Parse header (contains map-name etc.)
header, err := p.ParseHeader()
checkError(err)
var (
mapMetadata ex.Map
mapRadarImg image.Image
)
p.RegisterNetMessageHandler(func(msg *msg.CSVCMsg_ServerInfo) {
// Get metadata for the map that the game was played on for coordinate translations
mapMetadata = ex.GetMapMetadata(header.MapName, msg.GetMapCrc())
// Load map overview image
mapRadarImg = ex.GetMapRadar(header.MapName, msg.GetMapCrc())
})
// Register handler for WeaponFire, triggered every time a shot is fired
var points []r2.Point
p.RegisterEventHandler(func(e events.WeaponFire) {
// Translate positions from in-game coordinates to radar overview image pixels
x, y := mapMetadata.TranslateScale(e.Shooter.Position().X, e.Shooter.Position().Y)
points = append(points, r2.Point{X: x, Y: y})
})
// Parse the whole demo
err = p.ParseToEnd()
checkError(err)
//
// Preparation of heatmap data
//
// Find bounding rectangle for points to get around the normalization done by the heatmap library
r2Bounds := r2.RectFromPoints(points...)
padding := float64(dotSize) / 2.0 // Calculating padding amount to avoid shrinkage by the heatmap library
bounds := image.Rectangle{
Min: image.Point{X: int(r2Bounds.X.Lo - padding), Y: int(r2Bounds.Y.Lo - padding)},
Max: image.Point{X: int(r2Bounds.X.Hi + padding), Y: int(r2Bounds.Y.Hi + padding)},
}
// Transform r2.Points into heatmap.DataPoints
data := make([]heatmap.DataPoint, 0, len(points))
for _, p := range points[1:] {
// Invert Y since go-heatmap expects data to be ordered from bottom to top
data = append(data, heatmap.P(p.X, p.Y*-1))
}
//
// Drawing the image
//
// Create output canvas and use map overview image as base
img := image.NewRGBA(mapRadarImg.Bounds())
draw.Draw(img, mapRadarImg.Bounds(), mapRadarImg, image.Point{}, draw.Over)
// Generate and draw heatmap overlay on top of the overview
imgHeatmap := heatmap.Heatmap(image.Rect(0, 0, bounds.Dx(), bounds.Dy()), data, dotSize, opacity, schemes.AlphaFire)
draw.Draw(img, bounds, imgHeatmap, image.Point{}, draw.Over)
// Write to stdout
err = jpeg.Encode(os.Stdout, img, &jpeg.Options{Quality: jpegQuality})
checkError(err)
}
func checkError(err error) {
if err != nil {
panic(err)
}
}