178 lines
3.5 KiB
Go
178 lines
3.5 KiB
Go
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)
|
|
}
|