Skip to content

Commit 0fa30bd

Browse files
committed
metadata: clean up & use for heatmap
1 parent eef000b commit 0fa30bd

File tree

6 files changed

+197
-81
lines changed

6 files changed

+197
-81
lines changed

README.md

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ Check out the [examples](examples) folder for more examples and the [godoc of th
2828
package main
2929

3030
import (
31+
"fmt"
3132
"image"
32-
"image/png"
33+
"image/draw"
34+
"image/jpeg"
3335
"log"
3436
"os"
3537

@@ -38,37 +40,86 @@ import (
3840

3941
dem "github.com/markus-wa/demoinfocs-golang"
4042
events "github.com/markus-wa/demoinfocs-golang/events"
43+
metadata "github.com/markus-wa/demoinfocs-golang/metadata"
4144
)
4245

43-
// Run like this: go run heatmap.go > out.png
46+
// Run like this: go run heatmap.go > out.jpg
4447
func main() {
4548
f, err := os.Open("/path/to/demo.dem")
46-
checkErr(err)
49+
checkError(err)
4750
defer f.Close()
4851

4952
p := dem.NewParser(f)
5053

5154
// Parse header (contains map-name etc.)
52-
_, err = p.ParseHeader()
53-
checkErr(err)
55+
header, err := p.ParseHeader()
56+
checkError(err)
57+
58+
// Get metadata for the map that's being played
59+
m := metadata.MapNameToMap[header.MapName]
5460

5561
// Register handler for WeaponFiredEvent, triggered every time a shot is fired
5662
points := []heatmap.DataPoint{}
63+
var bounds image.Rectangle
5764
p.RegisterEventHandler(func(e events.WeaponFiredEvent) {
65+
// Translate positions from in-game coordinates to radar overview
66+
x, y := m.TranslateScale(e.Shooter.Position.X, e.Shooter.Position.Y)
67+
68+
// Track bounds to get around the normalization done by the heatmap library
69+
bounds = updatedBounds(bounds, int(x), int(y))
70+
5871
// Add shooter's position as datapoint
59-
points = append(points, heatmap.P(e.Shooter.Position.X, e.Shooter.Position.Y))
72+
// Invert Y since it expects data to be ordered from bottom to top
73+
points = append(points, heatmap.P(x, y*-1))
6074
})
6175

6276
// Parse to end
6377
err = p.ParseToEnd()
64-
checkErr(err)
78+
checkError(err)
79+
80+
// Get map overview as base image
81+
fMap, err := os.Open(fmt.Sprintf("../../metadata/maps/%s.jpg", header.MapName))
82+
checkError(err)
83+
84+
imgMap, _, err := image.Decode(fMap)
85+
checkError(err)
86+
87+
// Create output canvas
88+
img := image.NewRGBA(imgMap.Bounds())
89+
90+
draw.Draw(img, imgMap.Bounds(), imgMap, image.ZP, draw.Over)
91+
92+
// Generate heatmap
93+
width := bounds.Max.X - bounds.Min.X
94+
height := bounds.Max.Y - bounds.Min.Y
95+
imgHeatmap := heatmap.Heatmap(image.Rect(0, 0, width, height), points, 15, 128, schemes.AlphaFire)
96+
97+
// Draw it on top of the overview
98+
draw.Draw(img, bounds, imgHeatmap, image.ZP, draw.Over)
6599

66-
// Generate heatmap and write to standard output
67-
img := heatmap.Heatmap(image.Rect(0, 0, 1024, 1024), points, 15, 128, schemes.AlphaFire)
68-
png.Encode(os.Stdout, img)
100+
// Write to stdout
101+
jpeg.Encode(os.Stdout, img, &jpeg.Options{
102+
Quality: 90,
103+
})
104+
}
105+
106+
func updatedBounds(b image.Rectangle, x, y int) image.Rectangle {
107+
if b.Min.X > x || b.Min.X == 0 {
108+
b.Min.X = x
109+
} else if b.Max.X < x {
110+
b.Max.X = x
111+
}
112+
113+
if b.Min.Y > y || b.Min.Y == 0 {
114+
b.Min.Y = y
115+
} else if b.Max.Y < y {
116+
b.Max.Y = y
117+
}
118+
119+
return b
69120
}
70121

71-
func checkErr(err error) {
122+
func checkError(err error) {
72123
if err != nil {
73124
log.Fatal(err)
74125
}
@@ -77,11 +128,9 @@ func checkErr(err error) {
77128

78129
### Result
79130

80-
Running the above code (`go run heatmap.go > heatmap.png`) will create a PNG with dots on all the locations where shots were fired (the heatmap 'overlay').
81-
82-
This doesn't look too interesting on it's own but that can be helped by quickly mapping it to the map overview in an image editing tool (2 min tops, no skills required).
131+
Running the above code (`go run heatmap.go > heatmap.png`) will create a JPEG of a radar overview with dots on all the locations where shots were fired.
83132

84-
![Resulting heatmap before and after mapping to map overview](https://raw.githubusercontent.com/markus-wa/demoinfocs-golang/master/examples/heatmap/heatmap.jpg)
133+
![Resulting heatmap](https://raw.githubusercontent.com/markus-wa/demoinfocs-golang/master/examples/heatmap/heatmap.jpg)
85134

86135
## Features
87136

examples/heatmap/README.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@ This example shows how to create a heatmap from positions where players fired th
44

55
## Running the example
66

7-
`go run heatmap.go -demo /path/to/demo > out.png`
7+
`go run heatmap.go -demo /path/to/demo > out.jpg`
88

9-
This will create a PNG with dots on all the locations where shots were fired (the heatmap 'overlay').
9+
This will create a JPEG of a radar overview with dots on all the locations where shots were fired.
1010

11-
This doesn't look too interesting on it's own but that can be helped by quickly mapping it to the map overview in an image editing tool (2 min tops, no skills required).
12-
13-
![Resulting heatmap before and after mapping to map overview](https://raw.githubusercontent.com/markus-wa/demoinfocs-golang/master/examples/heatmap/heatmap.jpg)
11+
![Resulting heatmap](https://raw.githubusercontent.com/markus-wa/demoinfocs-golang/master/examples/heatmap/heatmap.jpg)

examples/heatmap/heatmap.go

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package main
22

33
import (
4+
"fmt"
45
"image"
5-
"image/png"
6+
"image/draw"
7+
"image/jpeg"
68
"log"
79
"os"
810

@@ -12,9 +14,10 @@ import (
1214
dem "github.com/markus-wa/demoinfocs-golang"
1315
events "github.com/markus-wa/demoinfocs-golang/events"
1416
ex "github.com/markus-wa/demoinfocs-golang/examples"
17+
metadata "github.com/markus-wa/demoinfocs-golang/metadata"
1518
)
1619

17-
// Run like this: go run heatmap.go -demo /path/to/demo.dem > out.png
20+
// Run like this: go run heatmap.go -demo /path/to/demo.dem > out.jpg
1821
func main() {
1922
f, err := os.Open(ex.DemoPathFromArgs())
2023
checkError(err)
@@ -23,23 +26,71 @@ func main() {
2326
p := dem.NewParser(f)
2427

2528
// Parse header (contains map-name etc.)
26-
_, err = p.ParseHeader()
29+
header, err := p.ParseHeader()
2730
checkError(err)
2831

32+
// Get metadata for the map that's being played
33+
m := metadata.MapNameToMap[header.MapName]
34+
2935
// Register handler for WeaponFiredEvent, triggered every time a shot is fired
3036
points := []heatmap.DataPoint{}
37+
var bounds image.Rectangle
3138
p.RegisterEventHandler(func(e events.WeaponFiredEvent) {
39+
// Translate positions from in-game coordinates to radar overview
40+
x, y := m.TranslateScale(e.Shooter.Position.X, e.Shooter.Position.Y)
41+
42+
// Track bounds to get around the normalization done by the heatmap library
43+
bounds = updatedBounds(bounds, int(x), int(y))
44+
3245
// Add shooter's position as datapoint
33-
points = append(points, heatmap.P(e.Shooter.Position.X, e.Shooter.Position.Y))
46+
// Invert Y since it expects data to be ordered from bottom to top
47+
points = append(points, heatmap.P(x, y*-1))
3448
})
3549

3650
// Parse to end
3751
err = p.ParseToEnd()
3852
checkError(err)
3953

40-
// Generate heatmap and write to standard output
41-
img := heatmap.Heatmap(image.Rect(0, 0, 1024, 1024), points, 15, 128, schemes.AlphaFire)
42-
png.Encode(os.Stdout, img)
54+
// Get map overview as base image
55+
fMap, err := os.Open(fmt.Sprintf("../../metadata/maps/%s.jpg", header.MapName))
56+
checkError(err)
57+
58+
imgMap, _, err := image.Decode(fMap)
59+
checkError(err)
60+
61+
// Create output canvas
62+
img := image.NewRGBA(imgMap.Bounds())
63+
64+
draw.Draw(img, imgMap.Bounds(), imgMap, image.ZP, draw.Over)
65+
66+
// Generate heatmap
67+
width := bounds.Max.X - bounds.Min.X
68+
height := bounds.Max.Y - bounds.Min.Y
69+
imgHeatmap := heatmap.Heatmap(image.Rect(0, 0, width, height), points, 15, 128, schemes.AlphaFire)
70+
71+
// Draw it on top of the overview
72+
draw.Draw(img, bounds, imgHeatmap, image.ZP, draw.Over)
73+
74+
// Write to stdout
75+
jpeg.Encode(os.Stdout, img, &jpeg.Options{
76+
Quality: 90,
77+
})
78+
}
79+
80+
func updatedBounds(b image.Rectangle, x, y int) image.Rectangle {
81+
if b.Min.X > x || b.Min.X == 0 {
82+
b.Min.X = x
83+
} else if b.Max.X < x {
84+
b.Max.X = x
85+
}
86+
87+
if b.Min.Y > y || b.Min.Y == 0 {
88+
b.Min.Y = y
89+
} else if b.Max.Y < y {
90+
b.Max.Y = y
91+
}
92+
93+
return b
4394
}
4495

4596
func checkError(err error) {

examples/heatmap/heatmap.jpg

14 KB
Loading

examples/nade-trajectories/nade_trajectories.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@ var (
3535
colorFlash color.Color = color.RGBA{0x00, 0x00, 0xff, 0xff} // Blue, because of the color on the nade
3636
colorSmoke color.Color = color.RGBA{0xbe, 0xbe, 0xbe, 0xff} // Light gray
3737
colorDecoy color.Color = color.RGBA{0x96, 0x4b, 0x00, 0xff} // Brown, because it's shit :)
38-
curMap metadata.Map
3938
)
4039

40+
// Store the curret map so we don't have to pass it to functions
41+
var curMap metadata.Map
42+
4143
// Run like this: go run nade_trajectories.go -demo /path/to/demo.dem > nade_trajectories.jpg
4244
func main() {
4345
f, err := os.Open(ex.DemoPathFromArgs())
@@ -94,7 +96,7 @@ func main() {
9496
err = p.ParseToEnd()
9597
checkError(err)
9698

97-
// Use cache map overview as base
99+
// Use map overview as base image
98100
fMap, err := os.Open(fmt.Sprintf("../../metadata/maps/%s.jpg", hd.MapName))
99101
checkError(err)
100102

@@ -105,7 +107,7 @@ func main() {
105107
dest := image.NewRGBA(imgMap.Bounds())
106108

107109
// Draw image
108-
draw.Draw(dest, dest.Bounds(), imgMap, image.Point{0, 0}, draw.Src)
110+
draw.Draw(dest, dest.Bounds(), imgMap, image.ZP, draw.Src)
109111

110112
// Initialize the graphic context
111113
gc := draw2dimg.NewGraphicContext(dest)
@@ -149,11 +151,11 @@ func drawInfernos(gc *draw2dimg.GraphicContext, infernos []*common.Inferno) {
149151

150152
func buildInfernoPath(gc *draw2dimg.GraphicContext, hull *s2.Loop) {
151153
vertices := hull.Vertices()
152-
x, y, _ := curMap.TranslateScale(vertices[0].X, vertices[0].Y, 0)
154+
x, y := curMap.TranslateScale(vertices[0].X, vertices[0].Y)
153155
gc.MoveTo(x, y)
154156

155157
for _, fire := range vertices[1:] {
156-
x, y, _ := curMap.TranslateScale(fire.X, fire.Y, 0)
158+
x, y := curMap.TranslateScale(fire.X, fire.Y)
157159
gc.LineTo(x, y)
158160
}
159161

@@ -191,11 +193,11 @@ func drawTrajectories(gc *draw2dimg.GraphicContext, trajectories []*nadePath) {
191193
}
192194

193195
// Draw path
194-
x, y, _ := curMap.TranslateScale(np.path[0].X, np.path[0].Y, 0)
196+
x, y := curMap.TranslateScale(np.path[0].X, np.path[0].Y)
195197
gc.MoveTo(x, y) // Move to a position to start the new path
196198

197199
for _, pos := range np.path[1:] {
198-
x, y, _ := curMap.TranslateScale(pos.X, pos.Y, 0)
200+
x, y := curMap.TranslateScale(pos.X, pos.Y)
199201
gc.LineTo(x, y)
200202
}
201203

0 commit comments

Comments
 (0)