mtsatellite/common/basetilecreator.go

184 lines
4.1 KiB
Go

// Copyright 2014 by Sascha L. Teichmann
// Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
package common
import (
"image/color"
"io/ioutil"
"log"
"path/filepath"
"strconv"
)
const (
tileWidth = 18
tileHeight = 18
yOrderCapacity = 512
)
const (
MaxHeight = 1934
MinHeight = -1934
)
// To scan the whole height in terms of the y coordinate
// the database is queried in height units defined in the tileDepths table.
var tileDepths = [...][2]int16{
{1024, MaxHeight},
{256, 1023},
{128, 255},
{64, 127},
{32, 63},
{16, 31},
{8, 15},
{4, 7},
{2, 3},
{0, 1},
{-1, 0},
{-4, -2},
{-8, -5},
{-16, -9},
{-32, -17},
{-64, -33},
{-128, -65},
{-256, -129},
{-1024, -257},
{MinHeight, -1025}}
var BackgroundColor = color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
type BaseTileUpdateFunc func(x, y int, hash []byte) bool
type BaseTileCreator struct {
client *RedisClient
colors *Colors
renderer *Renderer
yOrder *YOrder
yMin int16
yMax int16
baseDir string
update BaseTileUpdateFunc
emptyImage []byte
bg color.RGBA
}
func NewBaseTileCreator(
client *RedisClient,
colors *Colors,
bg color.RGBA,
yMin, yMax int16,
transparent bool,
baseDir string,
update BaseTileUpdateFunc) *BaseTileCreator {
renderer := NewRenderer(tileWidth, tileHeight, transparent)
yMin, yMax = Order16(yMin, yMax)
return &BaseTileCreator{
client: client,
colors: colors,
bg: bg,
renderer: renderer,
yOrder: NewYOrder(renderer, yOrderCapacity),
yMin: yMin,
yMax: yMax,
baseDir: baseDir,
update: update}
}
func (btc *BaseTileCreator) Close() error {
return btc.client.Close()
}
func (btc *BaseTileCreator) drawBlock(block *Block) {
if err := btc.yOrder.RenderBlock(block, btc.colors); err != nil {
log.Printf("WARN: rendering block failed: %s\n", err)
}
}
func (btc *BaseTileCreator) CreateTile(x, z int16, i, j int) (bool, error) {
btc.renderer.Reset()
btc.renderer.SetPos(x, z)
btc.yOrder.Reset()
var c1, c2 Coord
nareas := make([]Area, 0, tileWidth*tileHeight/2)
areas := make([]Area, 1, tileWidth*tileHeight/2)
areas[0] = Area{
X1: 0, Z1: 0,
X2: int16(tileWidth) - 1, Z2: int16(tileHeight) - 1}
for _, yRange := range tileDepths {
if yRange[0] > btc.yMax || yRange[1] < btc.yMin {
continue
}
c1.Y = max16(yRange[0], btc.yMin)
c2.Y = min16(yRange[1], btc.yMax)
for _, area := range areas {
c1.X = area.X1 + x
c1.Z = area.Z1 + z
c2.X = area.X2 + x
c2.Z = area.Z2 + z
query := Cuboid{P1: c1, P2: c2}
var count int
var err error
if count, err = btc.client.QueryCuboid(query, btc.drawBlock); err != nil {
return false, err
}
if err = btc.yOrder.Drain(btc.colors); err != nil {
log.Printf("WARN: rendering block failed: %s\n", err)
}
// If there where loaded blocks in this area recalculate coverage.
if count > 0 {
nareas = area.recalculate(btc.renderer, nareas)
} else {
nareas = append(nareas, area)
}
}
if len(nareas) == 0 {
break
}
areas, nareas = nareas, areas[:0]
}
path := filepath.Join(btc.baseDir, strconv.Itoa(i), strconv.Itoa(j)+".png")
// Empty images are likely to be produced during seeding.
if btc.update == nil && btc.renderer.IsEmpty() {
// To avoid redundant encoding cache the resulting empty image.
if btc.emptyImage == nil {
var err error
m := BackgroundImage((tileWidth-2)*16, (tileHeight-2)*16, btc.bg)
if btc.emptyImage, err = EncodeToMem(m); err != nil {
return false, err
}
}
//log.Printf("Writing empty (%d, %d) to file %s\n", x, z, path)
return true, ioutil.WriteFile(path, btc.emptyImage, 0666)
}
image := btc.renderer.CreateShadedImage(
16, 16, (tileWidth-2)*16, (tileHeight-2)*16,
btc.colors, btc.bg)
if btc.update == nil {
log.Printf("Writing (%d, %d) to file %s.\n", x, z, path)
return true, SaveAsPNG(path, image)
}
if btc.update(i, j, HashImage(image)) {
log.Printf("Writing (%d, %d) to file %s.\n", x, z, path)
return true, SaveAsPNGAtomic(path, image)
}
log.Printf("(%d, %d) is unchanged.\n", x, z)
return false, nil
}