Compare commits

5 Commits

Author SHA1 Message Date
Lukas Gau
ab64703190 added first project documentation 2025-10-18 14:04:44 +02:00
Lukas Gau
3cecdeda39 added test wad for round room 2025-09-30 13:38:39 +02:00
Lukas Gau
ec83b13e1f updated readme with repo url 2025-09-28 14:47:12 +02:00
Doc
0986556c8c updated readme to include test info 2025-09-28 14:06:44 +02:00
Doc
9acc31e6c5 added test for all implementations 2025-09-28 14:04:22 +02:00
8 changed files with 564 additions and 5 deletions

BIN
MYMAP3.wad Normal file

Binary file not shown.

View File

@@ -27,7 +27,7 @@ Alternativ stehen aktuelle Pakete und Installationshinweise auch auf der offizie
### Installation
```bash
git clone <dein-repo-url>
git clone https://git.protron.dev/Seminar/bspviz.git
cd bspviz
go build ./...
```
@@ -59,7 +59,7 @@ dot -Tpng tree.dot -o tree.png
### Entwicklung
- Code formatieren: `gofmt -w .`
- Tests ausführen: `go test ./...`
- Temporäre Artefakte (DOT/PNG) sind über `.gitignore` bereits ausgeschlossen.
Beiträge, Erweiterungen oder neue Heuristiken sind willkommen.
- Tests ausführen: `go test ./...` (Top-Level-Paket `bspviz` enthält bewusst keine eigenen Tests; der Aufruf über `./...` zieht die Test-Suites der `internal/*`-Pakete automatisch nach.)
- Abhängig vom Fokus können Teilbereiche separat geprüft werden, z.B. `go test ./internal/geom` oder `go test ./internal/bsp`.
- Die vorhandenen Tests decken u.a. Geometrie-Primitive, BSP-Build-Heuristiken, Map-/WAD-Parsing sowie DOT-/PNG-Export ab.
- Temporäre Artefakte (DOT/PNG) sind über `.gitignore` bereits ausgeschlossen.

View File

@@ -0,0 +1,90 @@
# BSPViz Projektdokumentation
## 1. Projektueberblick
- **Zielsetzung:** BSPViz ist ein Go-gestuetztes CLI-Tool zum Analysieren klassischer Doom-WAD-Dateien. Es entstand im Rahmen einer Seminararbeit, um Parsing, Geometrieverarbeitung und BSP-Heuristiken prototypisch zu untersuchen.
- **Funktionsumfang:** Lesen von IWAD/PWAD-Archiven, Extraktion einzelner Map-Lumps, Generieren von Geometrie-Statistiken, Aufbau und Bewertung von BSP-Baeumen sowie Export als DOT- oder Overlay-PNG.
- **Technologiestack:** Go 1.25, optionale Nutzung von Graphviz (dot) fuer PNG-Renderings, reine Standardbibliothek ohne externe Abhaengigkeiten.
- **Artefakte im Repository:** Beispiel-WAD-Dateien (`MYMAP*.wad`), Einstiegspunkt `main.go`, modulare Pakete unter `internal/`.
## 2. Architektur und Codeorganisation
- **CLI (main.go):** Verantwortlich fuer Flag-Parsing, orchestriert WAD-Laden, optionales Listing, Informationsausgabe, Lump-Export und BSP-Erstellung. Triggert Visualisierungen ueber `internal/viz`.
- **WAD-Layer (`internal/wad`):** Kapselt Dateizugriff, Directory-Parsen und sichere Lump-Lesungen. `Open` validiert Header und Directory (Dateigroesse, Grenzen, Marker), `LoadMapLumps` liefert selektierte Rohbytes fuer Map-spezifische Lumps.
- **Map-Parsing (`internal/mapfmt`):** Wandelt Lump-Rohdaten (`VERTEXES`, `LINEDEFS`) in typsichere Strukturen (`MapData`) und erzeugt Segmentlisten fuer BSP-Builds.
- **Geometrie (`internal/geom`):** Stellt Vektor-Operationen, AABB-Berechnung, Seitenklassifikation, Segment-Splitting und Line-Segment-Schnittpunkte bereit. Alle Funktionen operieren auf float64 und nutzen ein globales Epsilon (`EPS`).
- **BSP-Engine (`internal/bsp`):** Implementiert heuristischen Baumaufbau. `Build` rekursiert ueber Segmente, waehlt Splitkandidaten per Kostenfunktion (`evalCost`) und liefert Knoten/Leaf-Strukturen inklusive Metriken (`Measure`).
- **Visualisierung (`internal/viz`):**
- `dot.go`: Serialisiert BSP-Baeume in Graphviz-DOT, optionaler Aufruf von `dot` fuer PNG.
- `png.go`: Rendered Map-Linien, Split-Ebenen und Leaf-Segmente in ein Overlay-Bild (RGB, Bresenham-basierte Linien).
Die Module folgen einer klaren Einbahn-Abhaengigkeit: `main` -> `wad` -> `mapfmt`/`geom` -> `bsp` -> `viz`. Tests fuer Kernpakete liegen jeweils in `*_test.go`.
## 3. Ablauf und Datenfluss
1. **CLI-Start:** Flags validieren (`-wad` Pflicht, Default-Map `MAP01`). Bei `-list` erfolgt ein frueher Exit nach Directory-Ausgabe.
2. **Map-Lesen:** `wad.Open` laedt das WAD, `FindMap` bestimmt Marker-Bereich, `LoadMapLumps` liest benoetigte Lumps in Memory.
3. **Parsing:** `mapfmt.LoadMap` interpretiert Rohbytes zu `MapData` (Vertices, Linedefs). `LinedefsToSegs` erstellt Segmentreprasentationen fuer weitere Verarbeitung.
4. **Geometrieanalyse:** Je nach Flag:
- `-info`: Zaehlt Vertex/Linedef-Anzahlen, Pruefung auf Formatabweichungen.
- `-geomtest`: Fuehrt Segment-Split-Probe inklusive Bounding-Box-Analyse aus.
- `-extract`: Schreibt gewuenschte Lumps als `.lmp` in Zielordner.
5. **BSP-Bau (`-buildbsp`):**
- `bsp.Build` waehlt Split-Ebenen anhand von Gewichtung (`alpha` Splits, `beta` Balance).
- Rekursion stoppt kontrolliert via `leafmax`, `maxdepth`.
- `bsp.Measure` liefert Kennzahlen wie Knotenzahl, Leaf-Segmente, Baumtiefe.
6. **Visualisierung:**
- `-dot`: Speichert DOT-Datei; `-treepng` ruft optional Graphviz fuer Renderings.
- `-overlay`: Zeichnet Map und Splits als PNG mit zufaellig gefaerbten Leaves.
## 4. Kommandozeilenreferenz
| Flag | Pflicht | Beschreibung |
|------|---------|--------------|
| `-wad <pfad>` | Ja | Pfad zu IWAD/PWAD-Datei. |
| `-map <name>` | Nein | Map-Marker (Default `MAP01`). |
| `-list` | Nein | Listet Directory und beendet. |
| `-info` | Nein | Zeigt Vertex-/Linedef-Statistiken nach Parsing. |
| `-extract <L1,L2>` | Nein | Extrahiert benannte Lumps als `.lmp`. Nutzt `-out` als Ziel (Default `.`). |
| `-geomtest` | Nein | Fuehrt Geometriediagnose (Seg-Splits, AABB) durch. |
| `-buildbsp` | Nein | Startet BSP-Aufbau mit Parametern `-alpha`, `-beta`, `-eps`, `-leafmax`, `-maxdepth`, `-cands`, `-seed`. |
| `-dot <pfad>` | Nein | Speichert DOT-Export (setzt `-buildbsp` voraus). |
| `-treepng <pfad>` | Nein | Erstellt PNG ueber Graphviz (benoetigt installierten `dot`). |
| `-overlay <pfad>` | Nein | Zeichnet Map+Splits als PNG ohne Graphviz-Abhaengigkeit. |
### Beispiel-Workflows
- Map-Verzeichnis einsehen: `go run ./main.go -wad MYMAP.wad -list`
- Statistik-Aufruf: `go run ./main.go -wad MYMAP3.wad -map MAP01 -info`
- Lumps extrahieren: `go run ./main.go -wad MYMAP2.wad -map E1M1 -extract VERTEXES,LINEDEFS -out dumps`
- BSP bauen & visualisieren:
```
go run ./main.go -wad MYMAP.wad -map MAP01 -buildbsp -alpha 8 -beta 2 -dot tree.dot -overlay overlay.png
dot -Tpng tree.dot -o tree.png # optionales Rendering
```
## 5. Implementierungsdetails
- **Robustes WAD-Parsen:** Integritaetschecks fuer Header, Directory-Grenzen sowie Lump-Adressen verhindern Out-of-Bounds-Zugriffe. `trimName` normalisiert Lump-Namen auf 8 Zeichen.
- **Geometrie-Splitting:** `geom.SplitSeg` klassifiziert Segmente anhand der orientierten Distanz (`Side`). Schnittpunkte werden nur akzeptiert, falls sie klar innerhalb des Segments liegen (`EPS`-Toleranz).
- **BSP-Heuristik:** `selectSplit` sampelt Segmente in regulierten Schritten (`Cands`). Die Kostenfunktion kombiniert Split-Anzahl und Balance; Parameter koennen zur Evaluierung verschiedener Heuristiken variiert werden.
- **Overlay-Rendering:** Transformation `worldToScreen` skaliert Doom-Koordinaten auf Bildgroesse, invertiert Y-Achse und ergaenzt Rand. Splits erscheinen als gelbe Linien, Leafs werden farblich zufaellig hervorgehoben.
- **Determinismus:** `-seed` beeinflusst den RNG der BSP-Heuristik; `seed=0` wird intern auf `1` gesetzt, um nachvollziehbare Ergebnisse zu erhalten.
## 6. Entwicklungs- und Testleitfaden
- **Abhaengigkeiten:** Go >= 1.25, optional Graphviz (`dot`) fuer DOT-zu-PNG-Konvertierung.
- **Build:** `go build ./...` erzeugt ein lokales Binary.
- **Direktausfuehrung:** `go run ./main.go <flags>` vermeidet separaten Build.
- **Codeformat:** `gofmt -w .`
- **Tests:** `go test ./internal/...` (Pakete `wad`, `mapfmt`, `geom`, `bsp`, `viz` verfuegen ueber eigenstaendige Test-Suites). Top-Level-`main` enthaelt bewusst keine Tests.
- **Beispieldaten:** Die mitgelieferten `MYMAP*.wad` dienen als kleinskalige Input-Setups fuer manuelle und automatisierte Checks.
## 7. Grenzen und Erweiterungsideen
- **Nicht abgedeckt:** Parsing weiterer Lumps (SEGS, SSECTORS etc.), 3D-Features moderner Ports, automatisierte DOT->PNG-Pipeline ohne Graphviz-Installation.
- **Moegliche Erweiterungen:**
1. Zusae tzliche Heuristiken (z.B. zufaellige Kandidatenauswahl, Hybridkosten).
2. Erweiterte Statistiken (Split-Histogramme, Leaf-Flachenberechnung).
3. CLI-Subcommands oder Konfigdatei-Unterstuetzung.
4. Export weiterer Formate (JSON-Serialisierung der BSP-Struktur).
## 8. Projektressourcen
- **Quellcode:** Einstieg `main.go`, Modulverzeichnis `internal/`.
- **Dokumentation:** Diese Datei, bestehende README.md als Schnellstart.
- **Kontakt & Lizenz:** Nicht im Repository hinterlegt; fuer Seminarzwecke bitte Betreuer bzw. Repository-Inhaber ansprechen.
> Hinweis: Alle Beschreibungen basieren auf dem Stand der Quelldateien vom 30.09.2025. Aenderungen im Code sollten zeitnah in dieser Dokumentation nachvollzogen werden.

View File

@@ -0,0 +1,97 @@
package bsp
import (
"math"
"testing"
"bspviz/internal/geom"
)
func TestStopUsesDefaults(t *testing.T) {
segs := make([]geom.Seg, 13)
if stop(segs, 0, Params{}) {
t.Fatalf("stop should continue when using default LeafMax with 13 segs")
}
if !stop(segs, 33, Params{}) {
t.Fatalf("stop should trigger when depth exceeds default MaxDepth")
}
}
func TestEvalCostSplitsAndBalance(t *testing.T) {
line := []geom.Seg{
{A: geom.V(-1, 1), B: geom.V(1, 1)},
{A: geom.V(-1, -1), B: geom.V(1, -1)},
{A: geom.V(-1, -1), B: geom.V(1, 1)},
}
p := Params{Alpha: 10, Beta: 2, Eps: geom.EPS}
splits, balance, cost := evalCost(line, geom.V(0, 0), geom.V(1, 0), p)
if splits != 1 {
t.Fatalf("splits=%d want 1", splits)
}
if balance != 0 {
t.Fatalf("balance=%d want 0", balance)
}
if cost != 10 {
t.Fatalf("cost=%v want 10", cost)
}
}
func TestSplitAllProducesFrontAndBack(t *testing.T) {
segs := []geom.Seg{{A: geom.V(-1, -1), B: geom.V(1, 1)}}
left, right, splits := splitAll(segs, geom.V(0, 0), geom.V(1, 0), Params{Eps: geom.EPS})
if splits != 1 {
t.Fatalf("splits=%d want 1", splits)
}
if len(left) != 1 || len(right) != 1 {
t.Fatalf("expected split into two segments, left=%d right=%d", len(left), len(right))
}
share := func(a, b geom.Vec) bool {
return math.Abs(a.X-b.X) < geom.EPS && math.Abs(a.Y-b.Y) < geom.EPS
}
if !share(left[0].A, right[0].B) && !share(left[0].B, right[0].A) {
t.Fatalf("split point mismatch")
}
}
func TestBuildRespectsLeafAndDepth(t *testing.T) {
segs := []geom.Seg{
{A: geom.V(-2, -2), B: geom.V(2, -2)},
{A: geom.V(2, -2), B: geom.V(2, 2)},
{A: geom.V(2, 2), B: geom.V(-2, 2)},
{A: geom.V(-2, 2), B: geom.V(-2, -2)},
{A: geom.V(-2, -2), B: geom.V(2, 2)},
{A: geom.V(-2, 2), B: geom.V(2, -2)},
}
p := Params{LeafMax: 2, MaxDepth: 3, Seed: 1, Cands: 4, Alpha: 5, Beta: 1, Eps: geom.EPS}
root := Build(segs, p)
if root == nil {
t.Fatalf("Build returned nil")
}
stats := Measure(root)
if stats.MaxDepth > p.MaxDepth {
t.Fatalf("MaxDepth=%d exceeds limit %d", stats.MaxDepth, p.MaxDepth)
}
if stats.Leaves == 0 {
t.Fatalf("expected some leaves in tree")
}
verifyLeaves(t, root, 0, p)
}
func verifyLeaves(t *testing.T, n *Node, depth int, p Params) {
t.Helper()
if n == nil {
return
}
if n.Leaf != nil {
if depth < p.MaxDepth && len(n.Leaf.Segs) > p.LeafMax {
t.Fatalf("leaf at depth %d has %d segs (limit %d)", depth, len(n.Leaf.Segs), p.LeafMax)
}
return
}
if depth >= p.MaxDepth {
t.Fatalf("internal node at depth %d exceeds MaxDepth %d", depth, p.MaxDepth)
}
verifyLeaves(t, n.Left, depth+1, p)
verifyLeaves(t, n.Right, depth+1, p)
}

View File

@@ -0,0 +1,83 @@
package geom
import (
"math"
"testing"
)
func TestSide(t *testing.T) {
tests := []struct {
name string
P, O, D Vec
want float64
}{
{"point left of line", V(1, 1), V(0, 0), V(1, 0), 1},
{"point right of line", V(1, -1), V(0, 0), V(1, 0), -1},
{"point on line", V(2, 0), V(0, 0), V(1, 0), 0},
}
for _, tc := range tests {
if got := Side(tc.P, tc.O, tc.D); got < tc.want-EPS || got > tc.want+EPS {
t.Fatalf("%s: Side()=%v want %v", tc.name, got, tc.want)
}
}
}
func TestSegLineIntersect(t *testing.T) {
O := V(0, 0)
D := V(1, 0)
tests := []struct {
name string
A, B Vec
wantOK bool
wantT float64
}{
{"hits interior", V(0, -1), V(0, 1), true, 0.5},
{"parallel", V(0, 1), V(1, 1), false, 0},
{"touches endpoint", V(0, 0), V(0, 1), false, 0},
}
for _, tc := range tests {
ok, tVal := SegLineIntersect(tc.A, tc.B, O, D)
if ok != tc.wantOK {
t.Fatalf("%s: ok=%v want %v", tc.name, ok, tc.wantOK)
}
if !ok {
continue
}
if diff := tVal - tc.wantT; diff > 1e-6 || diff < -1e-6 {
t.Fatalf("%s: t=%v want %v", tc.name, tVal, tc.wantT)
}
}
}
func TestSplitSeg(t *testing.T) {
O := V(0, 0)
D := V(1, 0)
seg := Seg{A: V(0, -1), B: V(0, 1)}
front, back := SplitSeg(seg, O, D)
if len(front) != 1 || len(back) != 1 {
t.Fatalf("expected split into two pieces got front=%d back=%d", len(front), len(back))
}
share := func(a, b Vec) bool {
return math.Abs(a.X-b.X) < EPS && math.Abs(a.Y-b.Y) < EPS
}
if !share(front[0].A, back[0].B) && !share(front[0].B, back[0].A) {
t.Fatalf("split point mismatch: front=%v back=%v", front[0], back[0])
}
sameSide := Seg{A: V(0, 1), B: V(1, 1)}
front, back = SplitSeg(sameSide, O, D)
if len(back) != 0 || len(front) != 1 {
t.Fatalf("expected no split for same side got front=%d back=%d", len(front), len(back))
}
}
func TestBounds(t *testing.T) {
pts := []Vec{V(-1, 2), V(3, -4), V(0, 0)}
box := Bounds(pts)
if box.Min != V(-1, -4) {
t.Fatalf("Min=%v want (-1,-4)", box.Min)
}
if box.Max != V(3, 2) {
t.Fatalf("Max=%v want (3,2)", box.Max)
}
}

View File

@@ -0,0 +1,76 @@
package mapfmt
import (
"testing"
)
func TestParseVertices(t *testing.T) {
data := []byte{
0, 0, 0, 0, // (0,0)
255, 255, 10, 0, // (-1,10)
}
verts, err := ParseVertices(data)
if err != nil {
t.Fatalf("ParseVertices error: %v", err)
}
if len(verts) != 2 {
t.Fatalf("got %d vertices", len(verts))
}
if verts[1].X != -1 || verts[1].Y != 10 {
t.Fatalf("unexpected vertex %+v", verts[1])
}
}
func TestParseVerticesBadSize(t *testing.T) {
if _, err := ParseVertices([]byte{0}); err == nil {
t.Fatalf("expected error on odd sized vertex data")
}
}
func TestParseLinedefs(t *testing.T) {
data := make([]byte, 14)
data[0] = 1 // V1
data[2] = 2 // V2
lines, err := ParseLinedefs(data)
if err != nil {
t.Fatalf("ParseLinedefs error: %v", err)
}
if len(lines) != 1 {
t.Fatalf("expected one linedef")
}
if lines[0].V1 != 1 || lines[0].V2 != 2 {
t.Fatalf("unexpected linedef %+v", lines[0])
}
}
func TestParseLinedefsBadSize(t *testing.T) {
if _, err := ParseLinedefs(make([]byte, 13)); err == nil {
t.Fatalf("expected error on odd sized linedef data")
}
}
func TestLoadMap(t *testing.T) {
raw := map[string][]byte{
"VERTEXES": {0, 0, 0, 0},
"LINEDEFS": make([]byte, 14),
}
m, err := LoadMap(raw)
if err != nil {
t.Fatalf("LoadMap error: %v", err)
}
if len(m.Vertices) != 1 || len(m.Linedefs) != 1 {
t.Fatalf("unexpected map data %+v", m)
}
}
func TestLinedefsToSegs(t *testing.T) {
verts := []Vertex{{X: 0, Y: 0}, {X: 10, Y: 0}, {X: 10, Y: 0}}
lines := []Linedef{{V1: 0, V2: 1}, {V1: 1, V2: 2}} // second is degenerate
segs := LinedefsToSegs(verts, lines)
if len(segs) != 1 {
t.Fatalf("expected one segment, got %d", len(segs))
}
if segs[0].A.X != 0 || segs[0].B.X != 10 {
t.Fatalf("unexpected segment: %+v", segs[0])
}
}

73
internal/viz/viz_test.go Normal file
View File

@@ -0,0 +1,73 @@
package viz
import (
"bytes"
"os"
"path/filepath"
"testing"
"bspviz/internal/bsp"
"bspviz/internal/geom"
"bspviz/internal/mapfmt"
)
func TestEmitDOT(t *testing.T) {
root := &bsp.Node{
O: geom.V(0, 0),
D: geom.V(1, 0),
Left: &bsp.Node{
Leaf: &bsp.Leaf{Segs: []geom.Seg{{A: geom.V(0, 0), B: geom.V(1, 1)}}},
},
Right: &bsp.Node{
Leaf: &bsp.Leaf{},
},
}
out := filepath.Join(t.TempDir(), "tree.dot")
if err := EmitDOT(root, out); err != nil {
t.Fatalf("EmitDOT error: %v", err)
}
got, err := os.ReadFile(out)
if err != nil {
t.Fatalf("ReadFile: %v", err)
}
expect := "digraph BSP {\n" +
" node [fontname=\"Helvetica\"];\n" +
" n0 [label=\"Split\\nO=(0,0) D=(1,0)\"];\n" +
" n1 [label=\"Leaf\\nSegs=1\", shape=ellipse, style=filled, fillcolor=lightgray];\n" +
" n2 [label=\"Leaf\\nSegs=0\", shape=ellipse, style=filled, fillcolor=lightgray];\n" +
" n0 -> n1 [label=\"L\"];\n" +
" n0 -> n2 [label=\"R\"];\n" +
"}\n"
if string(got) != expect {
t.Fatalf("unexpected DOT output:\n%s", got)
}
}
func TestRenderPNGProducesFile(t *testing.T) {
m := &mapfmt.MapData{
Vertices: []mapfmt.Vertex{{X: 0, Y: 0}, {X: 64, Y: 0}, {X: 64, Y: 64}},
Linedefs: []mapfmt.Linedef{
{V1: 0, V2: 1},
{V1: 1, V2: 2},
{V1: 2, V2: 0},
},
}
root := &bsp.Node{
O: geom.V(0, 0),
D: geom.V(1, 0),
Left: &bsp.Node{Leaf: &bsp.Leaf{Segs: []geom.Seg{{A: geom.V(0, 0), B: geom.V(64, 64)}}}},
Right: &bsp.Node{Leaf: &bsp.Leaf{}},
}
out := filepath.Join(t.TempDir(), "render.png")
if err := RenderPNG(m, root, out); err != nil {
t.Fatalf("RenderPNG error: %v", err)
}
data, err := os.ReadFile(out)
if err != nil {
t.Fatalf("ReadFile: %v", err)
}
pngMagic := []byte{0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n'}
if len(data) < len(pngMagic) || !bytes.Equal(data[:len(pngMagic)], pngMagic) {
t.Fatalf("output is not a PNG")
}
}

140
internal/wad/wad_test.go Normal file
View File

@@ -0,0 +1,140 @@
package wad
import (
"bytes"
"encoding/binary"
"io"
"os"
"testing"
)
type lumpDef struct {
name string
data []byte
}
func writeTestWAD(t *testing.T, lumps []lumpDef) string {
t.Helper()
tmp, err := os.CreateTemp(t.TempDir(), "wad-*.wad")
if err != nil {
t.Fatalf("CreateTemp: %v", err)
}
hdr := header{Ident: [4]byte{'P', 'W', 'A', 'D'}, NumLumps: int32(len(lumps))}
if err := binary.Write(tmp, binary.LittleEndian, hdr); err != nil {
t.Fatalf("write header: %v", err)
}
dir := make([]DirEntry, 0, len(lumps))
for _, l := range lumps {
pos, err := tmp.Seek(0, io.SeekCurrent)
if err != nil {
t.Fatalf("seek: %v", err)
}
if len(l.data) > 0 {
if _, err := tmp.Write(l.data); err != nil {
t.Fatalf("write lump %s: %v", l.name, err)
}
}
var name [8]byte
copy(name[:], []byte(l.name))
dir = append(dir, DirEntry{
FilePos: int32(pos),
Size: int32(len(l.data)),
Name8: name,
})
}
dirOffset, err := tmp.Seek(0, io.SeekCurrent)
if err != nil {
t.Fatalf("seek dir: %v", err)
}
if err := binary.Write(tmp, binary.LittleEndian, dir); err != nil {
t.Fatalf("write dir: %v", err)
}
hdr.DirOffset = int32(dirOffset)
if _, err := tmp.Seek(0, io.SeekStart); err != nil {
t.Fatalf("seek hdr: %v", err)
}
if err := binary.Write(tmp, binary.LittleEndian, hdr); err != nil {
t.Fatalf("rewrite header: %v", err)
}
if err := tmp.Close(); err != nil {
t.Fatalf("close temp wad: %v", err)
}
return tmp.Name()
}
func TestOpenAndReadLump(t *testing.T) {
name := writeTestWAD(t, []lumpDef{
{name: "MAP01", data: nil},
{name: "DATA", data: []byte{1, 2, 3}},
})
w, err := Open(name)
if err != nil {
t.Fatalf("Open: %v", err)
}
t.Cleanup(func() { _ = w.Close() })
if len(w.Dir()) != 2 {
t.Fatalf("Dir entries=%d want 2", len(w.Dir()))
}
data, _, err := w.ReadLumpByName("data")
if err != nil {
t.Fatalf("ReadLumpByName: %v", err)
}
if !bytes.Equal(data, []byte{1, 2, 3}) {
t.Fatalf("unexpected lump data: %v", data)
}
}
func TestFindMapAndLoadMapLumps(t *testing.T) {
verts := make([]byte, 4)
lines := make([]byte, 14)
name := writeTestWAD(t, []lumpDef{
{name: "MAP01", data: nil},
{name: "VERTEXES", data: verts},
{name: "LINEDEFS", data: lines},
{name: "MAP02", data: nil},
})
w, err := Open(name)
if err != nil {
t.Fatalf("Open: %v", err)
}
t.Cleanup(func() { _ = w.Close() })
start, end, err := w.FindMap("map01")
if err != nil {
t.Fatalf("FindMap: %v", err)
}
if start != 0 || end != 3 {
t.Fatalf("unexpected marker bounds start=%d end=%d", start, end)
}
lumps, err := w.LoadMapLumps("map01", "VERTEXES", "LINEDEFS")
if err != nil {
t.Fatalf("LoadMapLumps: %v", err)
}
if len(lumps) != 2 {
t.Fatalf("expected 2 lumps, got %d", len(lumps))
}
if _, ok := lumps["VERTEXES"]; !ok {
t.Fatalf("missing VERTEXES lump")
}
}
func TestLoadMapLumpsMissing(t *testing.T) {
name := writeTestWAD(t, []lumpDef{{name: "MAP01", data: nil}})
w, err := Open(name)
if err != nil {
t.Fatalf("Open: %v", err)
}
t.Cleanup(func() { _ = w.Close() })
if _, err := w.LoadMapLumps("map01", "VERTEXES"); err == nil {
t.Fatalf("expected error for missing lump")
}
}