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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
20
main.go
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user