package viz 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) }