217 lines
5.8 KiB
Go
217 lines
5.8 KiB
Go
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
|
||
}
|