// Copyright 2014, 2015 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" "image/color" "image/draw" "math" ) type Renderer struct { width int height int xOfs int16 zOfs int16 yBuffer []int32 yMin []int32 cBuffer []int32 RejectedBlocks int SolidBlocks int TransparentBlocks int spans *SpanPool tBuffer []*Span } func NewRenderer(width, height int, transparent bool) (renderer *Renderer) { dim := width * height pixSize := dim * 16 * 16 yBuffer := make([]int32, pixSize) cBuffer := make([]int32, pixSize) yMin := make([]int32, dim) var tBuffer []*Span var spans *SpanPool if transparent { tBuffer = make([]*Span, pixSize) spans = NewSpanPool() } renderer = &Renderer{ width: width, height: height, yBuffer: yBuffer, cBuffer: cBuffer, yMin: yMin, tBuffer: tBuffer, spans: spans} renderer.initBuffers() return } func (r *Renderer) SetPos(xOfs, zOfs int16) { r.xOfs = xOfs r.zOfs = zOfs } func (r *Renderer) initBuffers() { yb := r.yBuffer yb = yb[:len(yb)] for i := range yb { yb[i] = math.MinInt32 } cb := r.cBuffer cb = cb[:len(cb)] for i := range cb { cb[i] = -1 } ym := r.yMin ym = ym[:len(ym)] for i := range ym { ym[i] = math.MinInt32 } } func (r *Renderer) Reset() { r.RejectedBlocks = 0 if r.SolidBlocks > 0 || r.TransparentBlocks > 0 { r.SolidBlocks = 0 r.initBuffers() } if r.TransparentBlocks > 0 { r.TransparentBlocks = 0 tb := r.tBuffer for i, t := range tb { if t != nil { r.spans.FreeAll(t) tb[i] = nil } } } } func (r *Renderer) IsFilled() bool { for _, y := range r.yMin { if y == math.MinInt32 { return false } } return true } func (r *Renderer) IsEmpty() bool { return r.SolidBlocks == 0 && r.TransparentBlocks == 0 } // down goes down the y direction in a block from top to bottom. // In its loop it copies the logic of Block.Content pulling some // things like the version check and common indexing out to // save some cycles. func down(db *DecodedBlock, x, y, z int) (int32, int) { mc := db.MapContent switch { case db.Version >= 24: for sliver := (z<<8 + x) << 1; y >= 0; y-- { pos := sliver + y<<5 content := int32(mc[pos])<<8 | int32(mc[pos+1]) if content != db.AirID && content != db.IgnoreID { if c, found := db.IndexMap[content]; found { return c, y } } } case db.Version >= 20: for sliver := z<<8 + x; y >= 0; y-- { pos := sliver + y<<4 var content int32 if c := mc[pos]; c <= 0x80 { content = int32(c) } else { content = int32(c)<<4 | int32(mc[pos+0x2000])>>4 } if content != db.AirID && content != db.IgnoreID { if c, found := db.IndexMap[content]; found { return c, y } } } } return -1, -1 } 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 } w := r.width << 4 ofs := int(bz)*w<<4 + int(bx)<<4 yB := r.yBuffer yMin := int32(math.MaxInt32) if db.Transparent && r.tBuffer != nil { r.TransparentBlocks++ for z := 0; z < 16; z++ { for x := 0; x < 16; x++ { currentY := yB[ofs] if currentY < blockY { var c int32 for y := 15; ; y-- { if c, y = down(db, x, y, z); y < 0 { break } cY := blockY + int32(y) if colors.IsTransparent(c) { r.tBuffer[ofs] = r.spans.Insert(r.tBuffer[ofs], cY, c) // We need to continue to go down because we // can see through this node. } else { r.cBuffer[ofs] = c currentY = cY yB[ofs] = currentY break } } } if currentY < yMin { yMin = currentY } ofs++ } ofs += w - 16 } } else { r.SolidBlocks++ for z := 0; z < 16; z++ { for x := 0; x < 16; x++ { currentY := yB[ofs] if currentY < blockY { if c, y := down(db, x, 15, z); y >= 0 { r.cBuffer[ofs] = c currentY = blockY + int32(y) yB[ofs] = currentY } } if currentY < yMin { yMin = currentY } ofs++ } ofs += w - 16 } } r.yMin[pos] = yMin return } 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 BackgroundImage(width, height int, bg color.RGBA) *image.RGBA { m := image.NewRGBA(image.Rect(0, 0, width, height)) draw.Draw(m, m.Bounds(), &image.Uniform{bg}, image.ZP, draw.Src) return m } func (r *Renderer) CreateShadedImage( xOfs, zOfs, width, height int, cols *Colors, background color.RGBA) *image.RGBA { image := image.NewRGBA(image.Rect(0, 0, width, height)) pw := r.width << 4 cs := cols.Colors ofs, numCols := zOfs*pw+xOfs, int32(len(cs)) stride := pw - width istride := image.Stride + 4*width iofs := image.PixOffset(0, height-1) pix := image.Pix if r.TransparentBlocks > 0 { // Fast path for transparent images. 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 { y := r.yBuffer[ofs] t := r.tBuffer[ofs] opaque := t == nil || t.Top() < y var y1, y2 int32 if x == 0 { y1 = y } else { y1 = r.yBuffer[ofs-1] if opaque { if s := r.tBuffer[ofs-1]; s != nil { y1 = max32(y1, s.Top()) } } } if z == 0 { y2 = y } else { y2 = r.yBuffer[ofs+pw] if opaque { if s := r.tBuffer[ofs+pw]; s != nil { y1 = max32(y1, s.Top()) } } } d := ((y - y1) + (y - y2)) * 12 if d > 36 { d = 36 } col := cs[colIdx] col = color.RGBA{ R: safeColor(int32(col.R) + d), G: safeColor(int32(col.G) + d), B: safeColor(int32(col.B) + d), A: 0xff} if !opaque { col = cols.BlendColors(t, col, y) } pix[iofs] = col.R pix[iofs+1] = col.G pix[iofs+2] = col.B pix[iofs+3] = col.A } iofs += 4 ofs++ } ofs += stride iofs -= istride } } else { // Solid images. 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 := cs[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 }