implemented png export so that we get a overlay of the splitt lines and also directly export a node tree visualization of the node tree with graphviz.

This commit is contained in:
Doc
2025-09-28 12:50:16 +02:00
parent 72fa5e900c
commit c908193986
3 changed files with 203 additions and 3 deletions

View File

@@ -5,6 +5,7 @@ import (
"bytes"
"fmt"
"os"
"os/exec"
)
// EmitDOT serialisiert den BSP-Baum mit Wurzel root und schreibt ihn als DOT-Datei nach path.
@@ -39,3 +40,12 @@ func EmitDOT(root *bsp.Node, path string) error {
buf.WriteString("}\n")
return os.WriteFile(path, buf.Bytes(), 0644)
}
func RunGraphviz(dotFile, pngFile string) error {
cmd := exec.Command("dot", "-Tpng", dotFile, "-o", pngFile)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("graphviz failed: %v\n%s", err, string(out))
}
return nil
}

View File

@@ -1,3 +1,177 @@
package viz
//init
import (
"bspviz/internal/bsp"
"bspviz/internal/geom"
"bspviz/internal/mapfmt"
"image"
"image/color"
"image/png"
"math"
"math/rand"
"os"
"time"
)
// worldToScreen transformiert Doom-Koordinaten in Bildkoordinaten
type worldToScreen struct {
minX, minY float64
scale float64
W, H int
margin float64
}
func makeTransform(verts []mapfmt.Vertex, W, H int) worldToScreen {
minX, minY := math.MaxFloat64, math.MaxFloat64
maxX, maxY := -math.MaxFloat64, -math.MaxFloat64
for _, v := range verts {
x, y := float64(v.X), float64(v.Y)
if x < minX {
minX = x
}
if y < minY {
minY = y
}
if x > maxX {
maxX = x
}
if y > maxY {
maxY = y
}
}
margin := 20.0
w := maxX - minX
h := maxY - minY
if w < 1 {
w = 1
}
if h < 1 {
h = 1
}
scale := math.Min((float64(W)-2*margin)/w, (float64(H)-2*margin)/h)
return worldToScreen{minX, minY, scale, W, H, margin}
}
func (t worldToScreen) P(p geom.Vec) (int, int) {
x := t.margin + (p.X-t.minX)*t.scale
y := t.margin + (p.Y-t.minY)*t.scale
// Y-Achse flippen (Doom: +Y nach oben, Bild: +Y nach unten)
yy := float64(t.H) - y
return int(x + 0.5), int(yy + 0.5)
}
// einfache Bresenham-Linie
func drawLine(img *image.RGBA, x0, y0, x1, y1 int, c color.Color) {
dx := int(math.Abs(float64(x1 - x0)))
dy := -int(math.Abs(float64(y1 - y0)))
sx := -1
if x0 < x1 {
sx = 1
}
sy := -1
if y0 < y1 {
sy = 1
}
err := dx + dy
for {
if x0 >= 0 && y0 >= 0 && x0 < img.Bounds().Dx() && y0 < img.Bounds().Dy() {
img.Set(x0, y0, c)
}
if x0 == x1 && y0 == y1 {
break
}
e2 := 2 * err
if e2 >= dy {
err += dy
x0 += sx
}
if e2 <= dx {
err += dx
y0 += sy
}
}
}
// RenderPNG zeichnet die Map und den BSP-Baum
func RenderPNG(m *mapfmt.MapData, root *bsp.Node, outPath string) error {
W, H := 1200, 900
img := image.NewRGBA(image.Rect(0, 0, W, H))
// Hintergrund
bg := color.RGBA{20, 20, 24, 255}
for y := 0; y < H; y++ {
for x := 0; x < W; x++ {
img.Set(x, y, bg)
}
}
tr := makeTransform(m.Vertices, W, H)
// 1) Linedefs grau
gray := color.RGBA{180, 180, 180, 255}
for _, L := range m.Linedefs {
a := m.Vertices[L.V1]
b := m.Vertices[L.V2]
x0, y0 := tr.P(geom.V(float64(a.X), float64(a.Y)))
x1, y1 := tr.P(geom.V(float64(b.X), float64(b.Y)))
drawLine(img, x0, y0, x1, y1, gray)
}
// 2) Split-Linien (gelb)
yellow := color.RGBA{240, 210, 40, 255}
var drawSplits func(*bsp.Node)
drawSplits = func(n *bsp.Node) {
if n == nil || n.Leaf != nil {
return
}
D := n.D
L := geom.Len(D)
if L < 1e-9 {
return
}
dx, dy := D.X/L, D.Y/L
k := 1e6 // "lange" Linie
p0 := geom.V(n.O.X-k*dx, n.O.Y-k*dy)
p1 := geom.V(n.O.X+k*dx, n.O.Y+k*dy)
x0, y0 := tr.P(p0)
x1, y1 := tr.P(p1)
drawLine(img, x0, y0, x1, y1, yellow)
drawSplits(n.Left)
drawSplits(n.Right)
}
drawSplits(root)
// 3) Leaves farbig
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
var paintLeaves func(*bsp.Node)
paintLeaves = func(n *bsp.Node) {
if n == nil {
return
}
if n.Leaf != nil {
col := color.RGBA{
uint8(100 + rng.Intn(100)),
uint8(100 + rng.Intn(100)),
uint8(100 + rng.Intn(100)),
255,
}
for _, s := range n.Leaf.Segs {
x0, y0 := tr.P(s.A)
x1, y1 := tr.P(s.B)
drawLine(img, x0, y0, x1, y1, col)
}
return
}
paintLeaves(n.Left)
paintLeaves(n.Right)
}
paintLeaves(root)
// speichern
f, err := os.Create(outPath)
if err != nil {
return err
}
defer f.Close()
return png.Encode(f, img)
}

20
main.go
View File

@@ -32,6 +32,8 @@ func main() {
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()
@@ -197,8 +199,22 @@ func main() {
if err := viz.EmitDOT(root, *dotOut); err != nil {
log.Fatalf("write DOT: %v", err)
}
fmt.Printf("DOT export geschrieben: %s (mit 'dot -Tpng %s -o tree.png' rendern)\n",
*dotOut, *dotOut)
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
}