/
scanner3d.go
340 lines (267 loc) · 7.14 KB
/
scanner3d.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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
package scanner3d
import (
"errors"
"fmt"
"sort"
"strings"
"github.com/bozdoz/advent-of-code-2021/types"
"github.com/bozdoz/advent-of-code-2021/utils"
)
const minSharedBeacons = 12
type Beacon3d struct {
position types.Vector3d
edges map[string][]types.Vector3d
}
type Scanner struct {
Beacons []*Beacon3d
Name string
}
// custom logger extended from the "log" package
var log = utils.Logger("3d")
func ParseScanners(data []string) []*Scanner {
scanners := []*Scanner{}
var curScanner *Scanner
for _, line := range data {
switch {
case strings.Contains(line, "---"):
curScanner = &Scanner{
Name: strings.Trim(line, "- "),
}
scanners = append(scanners, curScanner)
case strings.TrimSpace(line) == "":
continue
default:
var x, y, z int
count, err := fmt.Sscanf(line, "%d,%d,%d", &x, &y, &z)
if count != 3 || err != nil {
panic(fmt.Sprint("Could not parse beacon x,y,z: ", err))
}
curScanner.Beacons = append(curScanner.Beacons, &Beacon3d{
position: types.NewVector3d(x, y, z),
})
}
}
for _, scanner := range scanners {
scanner.updateEdges()
}
return scanners
}
func (scanner *Scanner) updateEdges() {
for _, a := range scanner.Beacons {
a.edges = map[string][]types.Vector3d{}
for _, b := range scanner.Beacons {
if a == b {
continue
}
addEdge(a, b)
}
}
}
// removes negative signs so we can get the un-oriented numbers
// for somewhat more efficient searching
func getVectorKey(vector types.Vector3d) string {
sorted := []int{utils.Abs(vector.X), utils.Abs(vector.Y), utils.Abs(vector.Z)}
sort.Ints(sorted)
return fmt.Sprint(sorted[0], sorted[1], sorted[2])
}
// edges could have different orientations: +-x,+-y,+-z
// AND the number could appear anywhere
func (this *Beacon3d) findEdge(edge types.Vector3d) ([]types.Vector3d, bool) {
edgeStr := getVectorKey(edge)
edges, ok := this.edges[edgeStr]
return edges, ok
}
func (this *Beacon3d) sharesAtLeastNEdges(n int, beacon *Beacon3d) bool {
shared := 0
for _, arr := range this.edges {
for _, edge := range arr {
_, ok := beacon.findEdge(edge)
if ok {
shared++
}
if shared == n {
return true
}
}
}
return false
}
func (this *Beacon3d) getOrientationDiff(alt *Beacon3d) (signs types.Vector3d, order [3]int) {
for _, arr := range this.edges {
for _, edge := range arr {
edges, ok := alt.findEdge(edge)
if ok && len(edges) == 1 {
// this edge has all the same absolute numbers in some orientation
altEdge := edges[0]
reOrderedEdge := types.Vector3d{}
axes := [3]int{edge.X, edge.Y, edge.Z}
altAxes := [3]int{altEdge.X, altEdge.Y, altEdge.Z}
for i, axis := range axes {
for j, altAxis := range altAxes {
if utils.Abs(axis) == utils.Abs(altAxis) {
// reorder y,z,x to x,y,z for example
order[i] = j
switch i {
case 0:
reOrderedEdge.X = altAxis
case 1:
reOrderedEdge.Y = altAxis
case 2:
reOrderedEdge.Z = altAxis
}
break
}
}
}
// signs is what to multiply origin vector by
// to get the same signs:
// {1,2,3} -> {-1,2,-3} = {-1,1,-1}
signs = edge.Divide(reOrderedEdge)
// TODO: lazy, grabbing just the first?
return
}
}
}
return
}
func align(vec types.Vector3d, order [3]int, signs types.Vector3d) types.Vector3d {
arr := [3]int{vec.X, vec.Y, vec.Z}
vec = types.NewVector3d(
arr[order[0]],
arr[order[1]],
arr[order[2]],
)
vec = vec.Multiply(signs)
return vec
}
// returns new Beacons in the correct projection if enough matched
func (this *Scanner) CompareScanner(scanner *Scanner) (
newBeacons []*Beacon3d,
shared int,
scannerPosition types.Vector3d,
) {
// make a copy to alter the list within the loop
unmatched := make([]*Beacon3d, len(scanner.Beacons))
copy(unmatched, scanner.Beacons)
remaining := len(this.Beacons)
var selfBeacon, altBeacon *Beacon3d
// compare all scanners' beacons' edges
for _, a := range this.Beacons {
if (remaining + shared) < minSharedBeacons {
// not enough beacons to be a match
break
}
remaining--
for key, b := range unmatched {
// does this beacon have a crossover of edges
// if scanners share 12 beacons, the beacons will share 11 edges
if a.sharesAtLeastNEdges(minSharedBeacons-1, b) {
shared++
// remove from list
unmatched = append(unmatched[:key], unmatched[key+1:]...)
// save to find transformation later
selfBeacon = a
altBeacon = b
break
}
}
}
didMatch := shared >= minSharedBeacons
if didMatch {
signs, order := selfBeacon.getOrientationDiff(altBeacon)
alt := align(altBeacon.position, order, signs)
// need to transform unmatched
for _, b := range unmatched {
// compare unmatched to an edge in the same orientation
aligned := align(b.position, order, signs)
diff := aligned.Subtract(alt)
newPosition := selfBeacon.position.Add(diff)
newBeacons = append(newBeacons, &Beacon3d{
position: newPosition,
})
}
scannerPosition = selfBeacon.position.Subtract(alt)
}
return
}
func addEdge(a *Beacon3d, b *Beacon3d) {
edge := a.position.Subtract(b.position)
key := getVectorKey(edge)
if a.edges == nil {
// TODO: still no idea when this happens
a.edges = map[string][]types.Vector3d{}
}
a.edges[key] = append(a.edges[key], edge)
}
func (scanner *Scanner) AddBeacons(beacons []*Beacon3d) {
scanner.Beacons = append(scanner.Beacons, beacons...)
// update edges just for new beacons
for _, a := range beacons {
a.edges = map[string][]types.Vector3d{}
for _, b := range scanner.Beacons {
if a == b {
continue
}
addEdge(a, b)
addEdge(b, a)
}
}
}
func MergeScanners(content []string) (
composite *Scanner,
relativePositions []types.Vector3d,
err error,
) {
scanners := ParseScanners(content)
relativePositions = make([]types.Vector3d, 0, len(scanners))
composite = scanners[0]
// scanner 0 is at 0,0,0, relatively
relativePositions = append(relativePositions, types.NewVector3d(0, 0, 0))
queue := types.Queue[Scanner]{}
for _, scanner := range scanners[1:] {
queue.Push(scanner)
}
lastScanner := composite
for len(queue) > 0 {
scanner := queue.Shift()
if scanner == lastScanner {
err = errors.New(fmt.Sprint("repeat scanner found beacons:", len(scanner.Beacons)))
return
}
lastScanner = scanner
newBeacons, count, relativeScanner := composite.CompareScanner(scanner)
if count > 0 {
composite.AddBeacons(newBeacons)
relativePositions = append(relativePositions, relativeScanner)
} else {
queue.Push(scanner)
}
}
return
}
func ManhattanDistance(a types.Vector3d, b types.Vector3d) int {
x := a.X - b.X
y := a.Y - b.Y
z := a.Z - b.Z
return utils.Abs(x) + utils.Abs(y) + utils.Abs(z)
}
//
// String Representations
//
func (scanner *Scanner) String() (output string) {
output += fmt.Sprintf("\n--- %s ---", scanner.Name)
for _, beacon := range scanner.Beacons {
output += beacon.String()
}
output += "\n"
return
}
func (beacon *Beacon3d) String() (output string) {
output += "\n"
output += fmt.Sprintf("position: %v\n", beacon.position)
output += "edges:\n"
for _, edge := range beacon.edges {
output += fmt.Sprintf("%v ", edge)
}
return
}