// 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 emptyImage []byte bg color.RGBA } func NewBaseTileCreator( client *RedisClient, colors *Colors, bg color.RGBA, yMin, yMax int16, transparent bool, baseDir string) *BaseTileCreator { renderer := NewRenderer(tileWidth, tileHeight, transparent) yMin, yMax = Order16(yMin, yMax) btc := &BaseTileCreator{ client: client, colors: colors, bg: bg, renderer: renderer, yMin: yMin, yMax: yMax, baseDir: baseDir, } btc.yOrder = NewYOrder(btc.renderBlock, yOrderCapacity) return btc } func (btc *BaseTileCreator) Close() error { return btc.client.Close() } // renderBlock is a callback to draw a block with a YOrder. func (btc *BaseTileCreator) renderBlock(block *Block) error { return btc.renderer.RenderBlock(block, btc.colors) } // blockLoaded is a callback for RedisClient.QueryCuboid. func (btc *BaseTileCreator) blockLoaded(block *Block) *Block { block, err := btc.yOrder.RenderBlock(block) if err != nil { log.Printf("WARN: rendering block failed: %s\n", err) } return block } func (btc *BaseTileCreator) RenderArea(x, z int16) 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.blockLoaded); err != nil { return err } if err = btc.yOrder.Drain(); 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] } return nil } func (btc *BaseTileCreator) blankImage() []byte { // To avoid redundant encoding cache the resulting empty image. if btc.emptyImage == nil { m := BackgroundImage((tileWidth-2)*16, (tileHeight-2)*16, btc.bg) btc.emptyImage = EncodeToMem(m) } return btc.emptyImage } // WriteFunc returns a function intended to be run in background so // the creation of the next tile with this creator can be done // concurrently. func (btc *BaseTileCreator) WriteFunc(i, j int, update BaseTileUpdateFunc) func() (bool, error) { path := filepath.Join(btc.baseDir, strconv.Itoa(i), strconv.Itoa(j)+".png") // Empty images are likely to be produced during seeding. if update == nil && btc.renderer.IsEmpty() { return func() (bool, error) { //log.Printf("Writing empty (%d, %d) to file %s\n", x, z, path) return true, ioutil.WriteFile(path, btc.blankImage(), 0666) } } image := btc.renderer.CreateShadedImage( 16, 16, (tileWidth-2)*16, (tileHeight-2)*16, btc.colors, btc.bg) x, z := btc.renderer.GetPos() if update == nil { return func() (bool, error) { log.Printf("Writing (%d, %d) to file %s.\n", x, z, path) return true, SaveAsPNG(path, image) } } return func() (bool, error) { if 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 } }