// 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 ( "container/heap" "image" "image/color" "math" ) type Area struct { X1, Z1 int16 X2, Z2 int16 } type Renderer struct { width int height int xOfs int16 zOfs int16 yBuffer []int32 yMin []int32 cBuffer []int32 RejectedBlocks int SolidBlocks int TransparentBlocks int } type YOrder struct { Renderer *Renderer blocks []*Block capacity int } func NewYOrder(renderer *Renderer, capacity int) *YOrder { return &YOrder{ Renderer: renderer, blocks: make([]*Block, 0, capacity), capacity: capacity} } func (yo *YOrder) Reset() { yo.blocks = yo.blocks[0:0] } func max(a, b int) int { if a > b { return a } return b } func copyData(data []byte) []byte { l := len(data) ndata := make([]byte, l, max(l, 8*1024)) copy(ndata, data) return ndata } func (yo *YOrder) RenderBlock(block *Block, colors *Colors) (err error) { var nblock *Block if len(yo.blocks) == yo.capacity { oblock := yo.blocks[0] if oblock.Coord.Y < block.Coord.Y { // New one is above highest old. Directly render new. err = yo.Renderer.RenderBlock(block, colors) return } // Render old one. Store copy of new in heap. heap.Pop(yo) err = yo.Renderer.RenderBlock(oblock, colors) l := len(block.Data) if cap(oblock.Data) < l { oblock.Data = make([]byte, l, max(l, 8*1024)) } else { oblock.Data = oblock.Data[0:l] } copy(oblock.Data, block.Data) oblock.Coord = block.Coord nblock = oblock } else { nblock = &Block{Coord: block.Coord, Data: copyData(block.Data)} } heap.Push(yo, nblock) return } func (yo *YOrder) Drain(colors *Colors) (err error) { for len(yo.blocks) > 0 { if err = yo.Renderer.RenderBlock(heap.Pop(yo).(*Block), colors); err != nil { return } } return } func (yo *YOrder) Len() int { return len(yo.blocks) } func (yo *YOrder) Swap(i, j int) { yo.blocks[i], yo.blocks[j] = yo.blocks[j], yo.blocks[i] } func (yo *YOrder) Less(i, j int) bool { // Reverse order intented. return yo.blocks[i].Coord.Y > yo.blocks[j].Coord.Y } func (yo *YOrder) Push(x interface{}) { yo.blocks = append(yo.blocks, x.(*Block)) } func (yo *YOrder) Pop() (x interface{}) { l := len(yo.blocks) x = yo.blocks[l-1] yo.blocks = yo.blocks[0 : l-1] return x } func NewRenderer(width, height int) (renderer *Renderer) { dim := width * height pixSize := dim * 16 * 16 yBuffer := make([]int32, pixSize) cBuffer := make([]int32, pixSize) yMin := make([]int32, dim) renderer = &Renderer{ width: width, height: height, yBuffer: yBuffer, cBuffer: cBuffer, yMin: yMin} renderer.Reset() return } func (r *Renderer) SetPos(xOfs, zOfs int16) { r.xOfs = xOfs r.zOfs = zOfs } func (r *Renderer) Reset() { for i, n := 0, len(r.yBuffer); i < n; i++ { r.yBuffer[i] = math.MinInt32 r.cBuffer[i] = -1 } for i, n := 0, len(r.yMin); i < n; i++ { r.yMin[i] = math.MinInt32 } } func (r *Renderer) IsFilled() bool { for _, y := range r.yMin { if y == math.MinInt32 { return false } } return true } func (r *Renderer) RenderBlock(block *Block, colors *Colors) (err error) { bx := block.Coord.X - r.xOfs bz := block.Coord.Z - r.zOfs // We do not need to render the block if the whole 16x16 area // is already filled and the block is strictly below. blockY := int32(block.Coord.Y) << 4 pos := int(bz)*r.width + int(bx) if blockY < r.yMin[pos] { r.RejectedBlocks++ return } // Decoding is pretty expensive so do it that late. var db *DecodedBlock if db, err = NewDecodedBlock(block.Data, colors); err != nil { return } if db.AirOnly { r.RejectedBlocks++ return } if db.Transparent { r.TransparentBlocks++ } else { r.SolidBlocks++ } w := r.width << 4 ofs := int(bz)*w<<4 + int(bx)<<4 yB := r.yBuffer yMin := int32(math.MaxInt32) for z := 0; z < 16; z++ { for x := 0; x < 16; x++ { currentY := yB[ofs] if currentY < blockY { for y := 15; y >= 0; y-- { if c, ok := db.Content(x, y, z); ok { r.cBuffer[ofs] = c currentY = blockY + int32(y) yB[ofs] = currentY break } } } if currentY < yMin { yMin = currentY } ofs++ } ofs += w - 16 } r.yMin[pos] = yMin return } func (a Area) Contains(x, z int16) bool { return x >= a.X1 && x <= a.X2 && z >= a.Z1 && z <= a.Z2 } func (a Area) IsHigher() bool { return a.Z2-a.Z1 > a.X2-a.X1 } func areasContain(areas []Area, x, z int16) bool { for _, r := range areas { if r.Contains(x, z) { return true } } return false } // Greedy algorithm to figure out a list of disjunct areas // of free regions in the domain to the (x, z) block plane. // oldAreas are searched and found free areas are appended // to newAreas which ist return. // This is useful to spatial query only blocks from db // that are not below already rendered blocks. func (r *Renderer) UncoveredAreas(newAreas, oldAreas []Area) []Area { yM := r.yMin // Scan old areas. for _, oldArea := range oldAreas { for z := oldArea.Z1; z <= oldArea.Z2; z++ { row := z * int16(r.width) for x := oldArea.X1; x <= oldArea.X2; x++ { // Uncovered and not in list of new areas? if yM[row+x] > math.MinInt32 || areasContain(newAreas, x, z) { continue } area := Area{X1: x, Z1: z, X2: x, Z2: z} // Try to extend the area in x and/or z till no further extension is possible. for extendDirs := 1 | 2; extendDirs != 0; { var xFirst bool // Try to extend in the direction with most gain // of blocks. if area.IsHigher() { // Higher means to win more blocks in x direction. xFirst = true } dirs: for i := 0; i < 2; i++ { if xFirst { // Extension in x possible? if extendDirs&1 == 1 { nx := area.X2 + 1 if nx >= int16(r.width) { extendDirs &= ^1 continue } // Scan line below current area if its fully free. for nz := area.Z1; nz <= area.Z2; nz++ { if yM[nz*int16(r.width)+nx] > math.MinInt32 || areasContain(newAreas, nx, nz) { extendDirs &= ^1 continue dirs } } // free -> extend area.X2 = nx } } else if extendDirs&2 == 2 { // Symmetric case in z direction nz := area.Z2 + 1 if nz >= int16(r.height) { extendDirs &= ^2 continue } // Scan line right beside the area if its free. row2 := nz * int16(r.width) for nx := area.X1; nx <= area.X2; nx++ { if yM[row2+nx] > math.MinInt32 || areasContain(newAreas, nx, nz) { extendDirs &= ^2 continue dirs } } area.Z2 = nz } // Switch to other search direction (x -> z or z -> x) xFirst = !xFirst } } // At this point the area is extended to max. newAreas = append(newAreas, area) } } } return newAreas } func (r *Renderer) CreateImage(colors []color.RGBA, background color.RGBA) *image.RGBA { pw, ph := r.width<<4, r.height<<4 image := image.NewRGBA(image.Rect(0, 0, pw, ph)) ofs, numCols := 0, int32(len(colors)) for z := ph - 1; z >= 0; z-- { for x := 0; x < pw; x++ { colIdx := r.cBuffer[ofs] if colIdx >= 0 && colIdx < numCols { image.Set(x, z, colors[colIdx]) } else { image.Set(x, z, background) } ofs++ } } return image } func safeColor(x int32) uint8 { switch { case x < 0: return 0 case x > 255: return 255 default: return uint8(x) } } func (r *Renderer) CreateShadedImage( xOfs, zOfs, width, height int, colors []color.RGBA, background color.RGBA) *image.RGBA { image := image.NewRGBA(image.Rect(0, 0, width, height)) pw := r.width << 4 ofs, numCols := zOfs*pw+xOfs, int32(len(colors)) stride := pw - width istride := image.Stride + 4*width iofs := image.PixOffset(0, height-1) pix := image.Pix for z := height - 1; z >= 0; z-- { for x := 0; x < width; x++ { colIdx := r.cBuffer[ofs] if colIdx < 0 || colIdx >= numCols { pix[iofs] = background.R pix[iofs+1] = background.G pix[iofs+2] = background.B pix[iofs+3] = 0xff } else { var y, y1, y2 int32 y = r.yBuffer[ofs] if x == 0 { y1 = y } else { y1 = r.yBuffer[ofs-1] } if z == 0 { y2 = y } else { y2 = r.yBuffer[ofs+pw] } d := ((y - y1) + (y - y2)) * 12 if d > 36 { d = 36 } col := colors[colIdx] pix[iofs] = safeColor(int32(col.R) + d) pix[iofs+1] = safeColor(int32(col.G) + d) pix[iofs+2] = safeColor(int32(col.B) + d) pix[iofs+3] = 0xff } iofs += 4 ofs++ } ofs += stride iofs -= istride } return image }