Files
bspviz/internal/app/app.go
2025-09-28 13:48:56 +02:00

217 lines
5.8 KiB
Go
Raw 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 app
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"bspviz/internal/bsp"
"bspviz/internal/geom"
"bspviz/internal/mapfmt"
"bspviz/internal/viz"
"bspviz/internal/wad"
)
type Options struct {
WadPath string
MapMarker string
ListOnly bool
Info bool
Extract []string
OutDir string
GeomTest bool
BuildBSP bool
Alpha float64
Beta float64
Eps float64
LeafMax int
MaxDepth int
Cands int
Seed int64
DotOut string
TreePNG string
Overlay string
Out io.Writer
}
func Run(opts Options) error {
if opts.Out == nil {
opts.Out = os.Stdout
}
if strings.TrimSpace(opts.WadPath) == "" {
return fmt.Errorf("wad path required")
}
w, err := wad.Open(opts.WadPath)
if err != nil {
return fmt.Errorf("open wad: %w", err)
}
defer w.Close()
if opts.ListOnly {
fmt.Fprintf(opts.Out, "WAD: %s\n", opts.WadPath)
for i, d := range w.Dir() {
fmt.Fprintf(opts.Out, "%3d: %-8s size=%-7d pos=%-8d\n", i, d.Name(), d.Size, d.FilePos)
}
return nil
}
start, end, err := w.FindMap(opts.MapMarker)
if err != nil {
return fmt.Errorf("find map: %w", err)
}
fmt.Fprintf(opts.Out, "Map %s: Directory [%d, %d)\n", strings.ToUpper(opts.MapMarker), start, end)
if opts.Info {
lumps, err := w.LoadMapLumps(opts.MapMarker, "VERTEXES", "LINEDEFS")
if err != nil {
return fmt.Errorf("load map lumps: %w", err)
}
vb := lumps["VERTEXES"]
lb := lumps["LINEDEFS"]
m, err := mapfmt.LoadMap(lumps)
if err != nil {
return err
}
verts := len(vb) / 4
lines := len(lb) / 14
fmt.Fprintf(opts.Out, "VERTEXES: bytes=%d count=%d\n", len(vb), verts)
fmt.Fprintf(opts.Out, "LINEDEFS: bytes=%d count=%d\n", len(lb), lines)
fmt.Fprintf(opts.Out, "Map has %d vertices and %d linedefs\n", len(m.Vertices), len(m.Linedefs))
fmt.Fprintf(opts.Out, "First vertex: %+v\n", m.Vertices[0])
fmt.Fprintf(opts.Out, "First linedef: %+v\n", m.Linedefs[0])
if len(vb)%4 != 0 {
fmt.Fprintln(opts.Out, "WARN: VERTEXES size ist kein Vielfaches von 4 → Format prüfen")
}
if len(lb)%14 != 0 {
fmt.Fprintln(opts.Out, "WARN: LINEDEFS size ist kein Vielfaches von 14 → Format prüfen")
}
}
if len(opts.Extract) > 0 {
lumps, err := w.LoadMapLumps(opts.MapMarker, opts.Extract...)
if err != nil {
return fmt.Errorf("extract: %w", err)
}
if err := os.MkdirAll(opts.OutDir, 0o755); err != nil {
return fmt.Errorf("mkdir %s: %w", opts.OutDir, err)
}
for name, data := range lumps {
dst := filepath.Join(opts.OutDir, fmt.Sprintf("%s.lmp", name))
if err := os.WriteFile(dst, data, 0o644); err != nil {
return fmt.Errorf("write %s: %w", dst, err)
}
fmt.Fprintf(opts.Out, "wrote %s (%d bytes)\n", dst, len(data))
}
}
if opts.GeomTest {
raw, err := w.LoadMapLumps(opts.MapMarker, "VERTEXES", "LINEDEFS")
if err != nil {
return fmt.Errorf("load map lumps: %w", err)
}
m, err := mapfmt.LoadMap(raw)
if err != nil {
return fmt.Errorf("parse map: %w", err)
}
segs := mapfmt.LinedefsToSegs(m.Vertices, m.Linedefs)
fmt.Fprintf(opts.Out, "GEOM: vertices=%d linedefs=%d segs=%d\n", len(m.Vertices), len(m.Linedefs), len(segs))
if len(segs) == 0 {
fmt.Fprintln(opts.Out, "GEOM: keine Segmente gefunden prüfe LINEDEFS/VERTEXES")
return nil
}
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.Fprintf(opts.Out, "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.Fprintf(opts.Out, "PROBE-SPLIT: O=(%.1f,%.1f) D=(%.1f,%.1f)\n", O.X, O.Y, D.X, D.Y)
fmt.Fprintf(opts.Out, "PROBE-SPLIT: left=%d right=%d splits=%d degens=%d (EPS=%.1e)\n",
left, right, splits, degens, geom.EPS)
return nil
}
if opts.BuildBSP {
raw, err := w.LoadMapLumps(opts.MapMarker, "VERTEXES", "LINEDEFS")
if err != nil {
return fmt.Errorf("load map lumps: %w", err)
}
m, err := mapfmt.LoadMap(raw)
if err != nil {
return fmt.Errorf("parse map: %w", err)
}
segs := mapfmt.LinedefsToSegs(m.Vertices, m.Linedefs)
p := bsp.Params{
Alpha: opts.Alpha, Beta: opts.Beta, Eps: opts.Eps,
MaxDepth: opts.MaxDepth, LeafMax: opts.LeafMax, Cands: opts.Cands, Seed: opts.Seed,
}
root := bsp.Build(segs, p)
st := bsp.Measure(root)
fmt.Fprintln(opts.Out, "BSP built.")
fmt.Fprintf(opts.Out, " nodes=%d leaves=%d maxDepth=%d totalLeafSegs=%d\n",
st.Nodes, st.Leaves, st.MaxDepth, st.TotalSegs)
fmt.Fprintf(opts.Out, " 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 opts.DotOut != "" {
if err := viz.EmitDOT(root, opts.DotOut); err != nil {
return fmt.Errorf("write DOT: %w", err)
}
fmt.Fprintf(opts.Out, "DOT export geschrieben: %s\n", opts.DotOut)
if opts.TreePNG != "" {
if err := viz.RunGraphviz(opts.DotOut, opts.TreePNG); err != nil {
fmt.Fprintf(opts.Out, "Graphviz fehlgeschlagen: %v\n", err)
} else {
fmt.Fprintf(opts.Out, "Graphviz PNG gebaut: %s\n", opts.TreePNG)
}
}
}
if opts.Overlay != "" {
if err := viz.RenderPNG(m, root, opts.Overlay); err != nil {
return fmt.Errorf("write overlay PNG: %w", err)
}
fmt.Fprintf(opts.Out, "Overlay PNG geschrieben: %s\n", opts.Overlay)
}
return nil
}
return nil
}