package tins2020 import ( "fmt" "log" "opslag.de/schobers/geom" "github.com/veandco/go-sdl2/sdl" ) type terrainRenderer struct { terrain *Map hover PointF project projection interact interaction } type interaction struct { mousePos Point mouseLeftDown bool mouseDrag *Point } type projection struct { center PointF zoom float32 zoomInv float32 tileScreenDelta PointF tileScreenDeltaInv PointF tileScreenOffset Point tileScreenSize Point windowCenter Point } func newProjection() projection { return projection{zoom: 1, tileScreenDelta: PtF(65, 32), tileScreenDeltaInv: PtF(1./65, 1./32)} } func (p *projection) update(renderer *sdl.Renderer) { p.zoomInv = 1 / p.zoom p.tileScreenOffset = Pt(int32(p.zoomInv*256), int32(p.zoomInv*300)) p.tileScreenSize = Pt(int32(p.zoomInv*512), int32(p.zoomInv*512)) windowW, windowH, err := renderer.GetOutputSize() if err != nil { log.Fatal(err) } p.windowCenter = Pt(windowW/2, windowH/2) } func (p *projection) mapToScreen(x, y int32) Point { return p.mapToScreenF(float32(x), float32(y)) } func (p *projection) mapToScreenF(x, y float32) Point { translated := PtF(x-p.center.X, y-p.center.Y) return Pt(p.windowCenter.X+int32((translated.X-translated.Y)*65*p.zoomInv), p.windowCenter.Y+int32((translated.X+translated.Y)*32*p.zoomInv)) } func (p *projection) screenToMapRel(x, y int32) PointF { normX := p.zoom * float32(x) normY := p.zoom * float32(y) return PtF(.5*(p.tileScreenDeltaInv.X*normX+p.tileScreenDeltaInv.Y*normY), .5*(-p.tileScreenDeltaInv.X*normX+p.tileScreenDeltaInv.Y*normY)) } func (p *projection) screenToMap(x, y int32) PointF { pos := p.screenToMapRel(x-p.windowCenter.X, y-p.windowCenter.Y) return p.center.Add(pos) } func (p *projection) screenToTileRect(pos Point) *sdl.Rect { return &sdl.Rect{X: pos.X - p.tileScreenOffset.X, Y: pos.Y - p.tileScreenOffset.Y, W: p.tileScreenSize.X, H: p.tileScreenSize.Y} } func NewTerrainRenderer(terrain *Map) Control { return &terrainRenderer{terrain: terrain, project: newProjection()} } func (r *terrainRenderer) Init(ctx *Context) error { r.project.update(ctx.Renderer) return nil } func (r *terrainRenderer) Handle(ctx *Context, event sdl.Event) { switch e := event.(type) { case *sdl.MouseButtonEvent: if e.Button == sdl.BUTTON_LEFT { r.interact.mouseLeftDown = e.Type == sdl.MOUSEBUTTONDOWN if r.interact.mouseLeftDown && r.interact.mouseDrag == nil { r.interact.mouseDrag = &Point{e.X, e.Y} } else if !r.interact.mouseLeftDown && r.interact.mouseDrag != nil { r.interact.mouseDrag = nil } } case *sdl.MouseMotionEvent: r.hover = r.project.screenToMap(e.X, e.Y) if r.interact.mouseDrag != nil { r.project.center = r.project.center.Sub(r.project.screenToMapRel(e.X-r.interact.mouseDrag.X, e.Y-r.interact.mouseDrag.Y)) r.project.update(ctx.Renderer) r.interact.mouseDrag = &Point{e.X, e.Y} } case *sdl.MouseWheelEvent: if e.Y > 0 && r.project.zoom > .5 { r.project.zoom *= .5 r.project.update(ctx.Renderer) } else if e.Y < 0 && r.project.zoom < 4 { r.project.zoom *= 2 r.project.update(ctx.Renderer) } case *sdl.WindowEvent: if e.Event == sdl.WINDOWEVENT_RESIZED { r.project.update(ctx.Renderer) } } } func (r *terrainRenderer) Render(ctx *Context) { toTileTexture := func(temp, humid float64) *Texture { if temp < .35 { return ctx.Textures.Texture("tile-snow") } if temp > .65 { return ctx.Textures.Texture("tile-dirt") } return ctx.Textures.Texture("tile-grass") } variantToInt := func(variant float64) int { if variant < .25 { return 1 } if variant < .5 { return 2 } if variant < .75 { return 3 } if variant < 1 { return 4 } return -1 } variantToTexture := func(format string, variant float64) *Texture { textName := fmt.Sprintf(format, variantToInt(variant)) return ctx.Textures.Texture(textName) } stretch := func(x, from, to float64) float64 { return (x - from) * 1 / (to - from) } toPropTexture := func(temp, humid, variant float64) *Texture { if temp < .35 { if humid < .2 { return nil } else if humid < .7 { return variantToTexture("bush-small-%d", variant*5) } return variantToTexture("tree-pine-%d", variant*5) } if temp > .65 { if humid < .2 { return nil } if humid < .7 { return variantToTexture("cactus-short-%d", variant*7) } return variantToTexture("cactus-tall-%d", variant*2) } if humid < .2 { return nil } multiplier := 1 - stretch(humid, 0.2, 1) if variant < .5 { return variantToTexture("tree-fat-%d", stretch(variant, 0, .5)*multiplier*3) } else if variant < .8 { return variantToTexture("grass-small-%d", stretch(variant, .5, .8)*multiplier*2) } return variantToTexture("bush-large-%d", stretch(variant, .8, 1)*multiplier) } // horizontal: [191, 321) = 130 // vertical: [267,332) = 65 hover := Pt(int32(geom.Round32(r.hover.X)), int32(geom.Round32(r.hover.Y))) for y := int32(-100); y < 100; y++ { for x := int32(-100); x < 100; x++ { if x == hover.X && y == hover.Y { continue } temp := r.terrain.Temp.Value(x, y) humid := r.terrain.Humid.Value(x, y) text := toTileTexture(temp, humid) pos := r.project.mapToScreen(x, y) text.Copy(ctx.Renderer, r.project.screenToTileRect(pos)) } } for y := int32(-100); y < 100; y++ { for x := int32(-100); x < 100; x++ { variant := r.terrain.Variant.Value(x, y) if variant < -1 || variant > 1 { continue } temp := r.terrain.Temp.Value(x, y) humid := r.terrain.Humid.Value(x, y) text := toPropTexture(temp, humid, variant) if text == nil { continue } placeX, placeY := r.terrain.PlaceX.Value(x, y), r.terrain.PlaceY.Value(x, y) pos := r.project.mapToScreenF(float32(x)-.2+float32(.8*placeX-.4), float32(y)-.2+float32(.8*placeY-.4)) text.Copy(ctx.Renderer, r.project.screenToTileRect(pos)) } } }