diff --git a/common/renderer.go b/common/renderer.go index 4b82402..c60473e 100644 --- a/common/renderer.go +++ b/common/renderer.go @@ -11,6 +11,11 @@ import ( "math" ) +type Area struct { + X1, Z1 int16 + X2, Z2 int16 +} + type Renderer struct { width int height int @@ -19,7 +24,6 @@ type Renderer struct { yBuffer []int32 yMin []int32 cBuffer []int32 - filled int Rejected int } @@ -140,7 +144,12 @@ func NewRenderer(xOfs, zOfs int16, width, height int) (renderer *Renderer) { } func (r *Renderer) IsFilled() bool { - return r.filled == r.width<<4*r.height<<4 + for _, y := range r.yMin { + if y == math.MinInt32 { + return false + } + } + return true } func (r *Renderer) RenderBlock(block *Block, nameIndex map[string]int32) (err error) { @@ -165,20 +174,18 @@ func (r *Renderer) RenderBlock(block *Block, nameIndex map[string]int32) (err er 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 := r.yBuffer[ofs] + currentY := yB[ofs] if currentY < blockY { for y := 15; y >= 0; y-- { if c, ok := db.Content(x, y, z); ok { - if r.cBuffer[ofs] == -1 { - r.filled++ - } r.cBuffer[ofs] = c currentY = blockY + int32(y) - r.yBuffer[ofs] = currentY + yB[ofs] = currentY break } } @@ -195,6 +202,99 @@ func (r *Renderer) RenderBlock(block *Block, nameIndex map[string]int32) (err er 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))