Files
bspviz/main.go

227 lines
6.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"bspviz/internal/bsp"
"bspviz/internal/geom"
"bspviz/internal/mapfmt"
"bspviz/internal/viz"
"bspviz/internal/wad"
"flag"
"fmt"
"log"
"os"
"path/filepath"
"strings"
)
func usageAndExit(msg string, code int) {
fmt.Fprintf(os.Stderr, "Fehler: %s\n\n", msg)
fmt.Fprintf(os.Stderr, "Beispiel:\n go run ./main.go -wad MYMAP.wad -map MAP01 -info\n\n")
fmt.Fprintf(os.Stderr, "Verfügbare Flags:\n")
flag.PrintDefaults()
os.Exit(code)
}
func main() {
// Flags
wadPath := flag.String("wad", "", "Pfad zur WAD (required)")
mapMarker := flag.String("map", "MAP01", "Map-Marker (z.B. MAP01, E1M1, MYMAP)")
listOnly := flag.Bool("list", false, "Nur Directory auflisten und beenden")
info := flag.Bool("info", false, "Roh-Infos zur Map (Counts von VERTEXES/LINEDEFS)")
extract := flag.String("extract", "", "Kommagetrennte Lump-Namen aus der Map extrahieren (z.B. VERTEXES,LINEDEFS)")
outdir := flag.String("out", ".", "Zielordner für -extract")
geomtest := flag.Bool("geomtest", false, "Geometrie-Check: Segmente/AABB/Probe-Split ausgeben")
buildbsp := flag.Bool("buildbsp", false, "BSP bauen und Metriken ausgeben")
alpha := flag.Float64("alpha", 10, "Kosten: Gewicht für Splits")
beta := flag.Float64("beta", 1, "Kosten: Gewicht für Balance")
eps := flag.Float64("eps", geom.EPS, "Epsilon für Geometrie")
leaf := flag.Int("leafmax", 12, "max. Segmente pro Leaf")
depth := flag.Int("maxdepth", 32, "max. Rekursionstiefe")
cands := flag.Int("cands", 16, "Anzahl Kandidaten (Subsample)")
seed := flag.Int64("seed", 0, "RNG-Seed (0 = default)")
dotOut := flag.String("dot", "", "DOT-Export-Datei (optional)")
treePNG := flag.String("treepng", "", "Graphviz-Baum als PNG (optional, benötigt -dot)")
overlay := flag.String("overlay", "", "Map-Overlay als PNG (optional)")
flag.Parse()
if strings.TrimSpace(*wadPath) == "" {
usageAndExit("Flag -wad fehlt. Bitte Pfad zu einer Doom-kompatiblen WAD-Datei angeben.", 2)
}
w, err := wad.Open(*wadPath)
if err != nil {
log.Fatalf("open wad: %v", err)
}
defer w.Close()
if *listOnly {
fmt.Printf("WAD: %s\n", *wadPath)
for i, d := range w.Dir() {
fmt.Printf("%3d: %-8s size=%-7d pos=%-8d\n", i, d.Name(), d.Size, d.FilePos)
}
return
}
start, end, err := w.FindMap(*mapMarker)
if err != nil {
log.Fatalf("find map: %v", err)
}
fmt.Printf("Map %s: Directory [%d, %d)\n", strings.ToUpper(*mapMarker), start, end)
//info über die daten in WAD
if *info {
lumps, err := w.LoadMapLumps(*mapMarker, "VERTEXES", "LINEDEFS")
if err != nil {
log.Fatalf("load map lumps: %v", err)
}
vb := lumps["VERTEXES"]
lb := lumps["LINEDEFS"]
m, err := mapfmt.LoadMap(lumps)
if err != nil {
log.Fatal(err)
}
verts := len(vb) / 4
lines := len(lb) / 14
fmt.Printf("VERTEXES: bytes=%d count=%d\n", len(vb), verts)
fmt.Printf("LINEDEFS: bytes=%d count=%d\n", len(lb), lines)
fmt.Printf("Map has %d vertices and %d linedefs\n", len(m.Vertices), len(m.Linedefs))
fmt.Printf("First vertex: %+v\n", m.Vertices[0])
fmt.Printf("First linedef: %+v\n", m.Linedefs[0])
if len(vb)%4 != 0 {
fmt.Println("WARN: VERTEXES size ist kein Vielfaches von 4 → Format prüfen")
}
if len(lb)%14 != 0 {
fmt.Println("WARN: LINEDEFS size ist kein Vielfaches von 14 → Format prüfen")
}
}
// Generiert einzelne Lump Dateien zum Debugen
if *extract != "" {
want := strings.Split(*extract, ",")
for i := range want {
want[i] = strings.ToUpper(strings.TrimSpace(want[i]))
}
lumps, err := w.LoadMapLumps(*mapMarker, want...)
if err != nil {
log.Fatalf("extract: %v", err)
}
if err := os.MkdirAll(*outdir, 0o755); err != nil {
log.Fatalf("mkdir %s: %v", *outdir, err)
}
for name, data := range lumps {
dst := filepath.Join(*outdir, fmt.Sprintf("%s.lmp", name))
if err := os.WriteFile(dst, data, 0o644); err != nil {
log.Fatalf("write %s: %v", dst, err)
}
fmt.Printf("wrote %s (%d bytes)\n", dst, len(data))
}
}
// Zum debug test der geo functions
if *geomtest {
raw, err := w.LoadMapLumps(*mapMarker, "VERTEXES", "LINEDEFS")
if err != nil {
log.Fatalf("load map lumps: %v", err)
}
m, err := mapfmt.LoadMap(raw)
if err != nil {
log.Fatalf("parse map: %v", err)
}
segs := mapfmt.LinedefsToSegs(m.Vertices, m.Linedefs)
fmt.Printf("GEOM: vertices=%d linedefs=%d segs=%d\n", len(m.Vertices), len(m.Linedefs), len(segs))
if len(segs) == 0 {
fmt.Println("GEOM: keine Segmente gefunden prüfe LINEDEFS/VERTEXES")
return
}
pts := make([]geom.Vec, 0, len(m.Vertices))
for _, v := range m.Vertices {
pts = append(pts, geom.V(float64(v.X), float64(v.Y)))
}
bb := geom.Bounds(pts)
w := bb.Max.X - bb.Min.X
h := bb.Max.Y - bb.Min.Y
fmt.Printf("AABB: min=(%.1f,%.1f) max=(%.1f,%.1f) size=(%.1f×%.1f)\n",
bb.Min.X, bb.Min.Y, bb.Max.X, bb.Max.Y, w, h)
O := segs[0].A
D := geom.Sub(segs[0].B, segs[0].A)
if geom.Len(D) < 1e-9 && len(segs) > 1 {
O = segs[1].A
D = geom.Sub(segs[1].B, segs[1].A)
}
var left, right, splits, degens int
for _, s := range segs {
f, b := geom.SplitSeg(s, O, D)
if len(f) == 0 && len(b) == 0 {
degens++
continue
}
left += len(f)
right += len(b)
if len(f) > 0 && len(b) > 0 {
splits++
}
}
fmt.Printf("PROBE-SPLIT: O=(%.1f,%.1f) D=(%.1f,%.1f)\n", O.X, O.Y, D.X, D.Y)
fmt.Printf("PROBE-SPLIT: left=%d right=%d splits=%d degens=%d (EPS=%.1e)\n",
left, right, splits, degens, geom.EPS)
return
}
if *buildbsp {
raw, err := w.LoadMapLumps(*mapMarker, "VERTEXES", "LINEDEFS")
if err != nil {
log.Fatalf("load map lumps: %v", err)
}
m, err := mapfmt.LoadMap(raw)
if err != nil {
log.Fatalf("parse map: %v", err)
}
segs := mapfmt.LinedefsToSegs(m.Vertices, m.Linedefs)
p := bsp.Params{
Alpha: *alpha, Beta: *beta, Eps: *eps,
MaxDepth: *depth, LeafMax: *leaf, Cands: *cands, Seed: *seed,
}
root := bsp.Build(segs, p)
st := bsp.Measure(root)
fmt.Printf("BSP built.\n")
fmt.Printf(" nodes=%d leaves=%d maxDepth=%d totalLeafSegs=%d\n",
st.Nodes, st.Leaves, st.MaxDepth, st.TotalSegs)
fmt.Printf(" params: alpha=%.2f beta=%.2f eps=%.1e leafMax=%d maxDepth=%d cands=%d seed=%d\n",
p.Alpha, p.Beta, p.Eps, p.LeafMax, p.MaxDepth, p.Cands, p.Seed)
if *dotOut != "" {
if err := viz.EmitDOT(root, *dotOut); err != nil {
log.Fatalf("write DOT: %v", err)
}
fmt.Printf("DOT export geschrieben: %s\n", *dotOut)
if *treePNG != "" {
if err := viz.RunGraphviz(*dotOut, *treePNG); err != nil {
log.Printf("Graphviz fehlgeschlagen: %v", err)
} else {
fmt.Printf("Graphviz PNG gebaut: %s\n", *treePNG)
}
}
}
if *overlay != "" {
if err := viz.RenderPNG(m, root, *overlay); err != nil {
log.Fatalf("write overlay PNG: %v", err)
}
fmt.Printf("Overlay PNG geschrieben: %s\n", *overlay)
}
return
}
}