2015-07-26 16:44:51 +02:00
|
|
|
// Copyright 2014, 2015 by Sascha L. Teichmann
|
2014-09-10 01:21:55 +02:00
|
|
|
// Use of this source code is governed by the MIT license
|
|
|
|
// that can be found in the LICENSE file.
|
|
|
|
|
2014-09-12 20:22:34 +02:00
|
|
|
package common
|
2014-09-10 01:21:55 +02:00
|
|
|
|
|
|
|
import (
|
2014-09-10 18:58:12 +02:00
|
|
|
"image"
|
|
|
|
"image/color"
|
2015-07-22 01:11:14 +02:00
|
|
|
"image/draw"
|
2014-09-10 01:21:55 +02:00
|
|
|
"math"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Renderer struct {
|
2014-10-26 17:09:23 +01:00
|
|
|
width int
|
|
|
|
height int
|
|
|
|
xOfs int16
|
|
|
|
zOfs int16
|
|
|
|
yBuffer []int32
|
|
|
|
yMin []int32
|
|
|
|
cBuffer []int32
|
|
|
|
RejectedBlocks int
|
|
|
|
SolidBlocks int
|
|
|
|
TransparentBlocks int
|
2014-10-26 18:36:47 +01:00
|
|
|
spans *SpanPool
|
|
|
|
tBuffer []*Span
|
2014-09-10 01:21:55 +02:00
|
|
|
}
|
|
|
|
|
2014-10-26 18:36:47 +01:00
|
|
|
func NewRenderer(width, height int, transparent bool) (renderer *Renderer) {
|
2014-09-10 01:21:55 +02:00
|
|
|
dim := width * height
|
|
|
|
pixSize := dim * 16 * 16
|
|
|
|
yBuffer := make([]int32, pixSize)
|
|
|
|
cBuffer := make([]int32, pixSize)
|
2014-09-11 22:20:04 +02:00
|
|
|
yMin := make([]int32, dim)
|
2014-09-10 01:21:55 +02:00
|
|
|
|
2014-10-26 18:36:47 +01:00
|
|
|
var tBuffer []*Span
|
|
|
|
var spans *SpanPool
|
|
|
|
|
|
|
|
if transparent {
|
|
|
|
tBuffer = make([]*Span, pixSize)
|
|
|
|
spans = NewSpanPool()
|
|
|
|
}
|
|
|
|
|
2014-09-10 01:21:55 +02:00
|
|
|
renderer = &Renderer{
|
|
|
|
width: width,
|
|
|
|
height: height,
|
|
|
|
yBuffer: yBuffer,
|
2014-09-11 22:38:01 +02:00
|
|
|
cBuffer: cBuffer,
|
2014-10-26 18:36:47 +01:00
|
|
|
yMin: yMin,
|
|
|
|
tBuffer: tBuffer,
|
|
|
|
spans: spans}
|
2014-09-14 14:57:49 +02:00
|
|
|
|
2017-02-27 23:50:15 +01:00
|
|
|
renderer.initBuffers()
|
2014-09-10 01:21:55 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2014-09-14 14:57:49 +02:00
|
|
|
func (r *Renderer) SetPos(xOfs, zOfs int16) {
|
|
|
|
r.xOfs = xOfs
|
|
|
|
r.zOfs = zOfs
|
|
|
|
}
|
|
|
|
|
2017-02-27 23:50:15 +01:00
|
|
|
func (r *Renderer) initBuffers() {
|
2017-02-24 17:59:41 +01:00
|
|
|
yb := r.yBuffer
|
|
|
|
yb = yb[:len(yb)]
|
|
|
|
for i := range yb {
|
|
|
|
yb[i] = math.MinInt32
|
2014-09-14 14:57:49 +02:00
|
|
|
}
|
2017-02-24 17:59:41 +01:00
|
|
|
|
|
|
|
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
|
2014-09-14 14:57:49 +02:00
|
|
|
}
|
2017-02-27 23:50:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Renderer) Reset() {
|
|
|
|
|
|
|
|
r.RejectedBlocks = 0
|
|
|
|
|
|
|
|
if r.SolidBlocks > 0 || r.TransparentBlocks > 0 {
|
|
|
|
r.SolidBlocks = 0
|
|
|
|
r.initBuffers()
|
|
|
|
}
|
2014-10-26 18:36:47 +01:00
|
|
|
|
2014-10-26 22:36:57 +01:00
|
|
|
if r.TransparentBlocks > 0 {
|
|
|
|
r.TransparentBlocks = 0
|
2017-02-27 23:50:15 +01:00
|
|
|
|
2017-02-24 17:59:41 +01:00
|
|
|
tb := r.tBuffer
|
|
|
|
for i, t := range tb {
|
2014-10-26 18:36:47 +01:00
|
|
|
if t != nil {
|
|
|
|
r.spans.FreeAll(t)
|
2017-02-24 17:59:41 +01:00
|
|
|
tb[i] = nil
|
2014-10-26 18:36:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-09-14 14:57:49 +02:00
|
|
|
}
|
|
|
|
|
2014-09-11 02:28:09 +02:00
|
|
|
func (r *Renderer) IsFilled() bool {
|
2014-09-14 11:41:25 +02:00
|
|
|
for _, y := range r.yMin {
|
|
|
|
if y == math.MinInt32 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
2014-09-11 02:28:09 +02:00
|
|
|
}
|
|
|
|
|
2015-07-22 01:11:14 +02:00
|
|
|
func (r *Renderer) IsEmpty() bool {
|
|
|
|
return r.SolidBlocks == 0 && r.TransparentBlocks == 0
|
|
|
|
}
|
|
|
|
|
2017-03-01 13:06:51 +01:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2014-10-19 21:05:38 +02:00
|
|
|
func (r *Renderer) RenderBlock(block *Block, colors *Colors) (err error) {
|
2014-09-10 23:49:27 +02:00
|
|
|
|
2014-09-10 18:58:12 +02:00
|
|
|
bx := block.Coord.X - r.xOfs
|
|
|
|
bz := block.Coord.Z - r.zOfs
|
2014-09-11 22:20:04 +02:00
|
|
|
|
2014-09-10 01:21:55 +02:00
|
|
|
// We do not need to render the block if the whole 16x16 area
|
|
|
|
// is already filled and the block is strictly below.
|
2014-09-10 23:49:27 +02:00
|
|
|
blockY := int32(block.Coord.Y) << 4
|
2014-09-11 22:20:04 +02:00
|
|
|
pos := int(bz)*r.width + int(bx)
|
|
|
|
if blockY < r.yMin[pos] {
|
2014-10-26 17:09:23 +01:00
|
|
|
r.RejectedBlocks++
|
2014-09-10 01:21:55 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2014-09-11 22:20:04 +02:00
|
|
|
// Decoding is pretty expensive so do it that late.
|
2014-09-10 01:21:55 +02:00
|
|
|
var db *DecodedBlock
|
2014-10-19 21:05:38 +02:00
|
|
|
if db, err = NewDecodedBlock(block.Data, colors); err != nil {
|
2014-09-10 01:21:55 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-07-20 13:27:46 +02:00
|
|
|
if db.AirOnly() {
|
2014-10-26 17:09:23 +01:00
|
|
|
r.RejectedBlocks++
|
2014-10-19 17:15:33 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2014-09-11 22:38:01 +02:00
|
|
|
w := r.width << 4
|
|
|
|
ofs := int(bz)*w<<4 + int(bx)<<4
|
2014-09-14 11:41:25 +02:00
|
|
|
yB := r.yBuffer
|
2014-09-11 22:20:04 +02:00
|
|
|
yMin := int32(math.MaxInt32)
|
2014-10-26 18:36:47 +01:00
|
|
|
|
|
|
|
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 {
|
2017-03-01 13:06:51 +01:00
|
|
|
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
|
2014-10-26 18:36:47 +01:00
|
|
|
}
|
2014-09-10 23:49:27 +02:00
|
|
|
}
|
|
|
|
}
|
2014-10-26 18:36:47 +01:00
|
|
|
if currentY < yMin {
|
|
|
|
yMin = currentY
|
|
|
|
}
|
|
|
|
ofs++
|
2014-09-10 01:21:55 +02:00
|
|
|
}
|
2014-10-26 18:36:47 +01:00
|
|
|
ofs += w - 16
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
r.SolidBlocks++
|
|
|
|
for z := 0; z < 16; z++ {
|
|
|
|
for x := 0; x < 16; x++ {
|
|
|
|
currentY := yB[ofs]
|
|
|
|
if currentY < blockY {
|
2017-03-01 13:06:51 +01:00
|
|
|
if c, y := down(db, x, 15, z); y >= 0 {
|
|
|
|
r.cBuffer[ofs] = c
|
|
|
|
currentY = blockY + int32(y)
|
|
|
|
yB[ofs] = currentY
|
2014-10-26 18:36:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if currentY < yMin {
|
|
|
|
yMin = currentY
|
|
|
|
}
|
|
|
|
ofs++
|
2014-09-11 22:20:04 +02:00
|
|
|
}
|
2014-10-26 18:36:47 +01:00
|
|
|
ofs += w - 16
|
2014-09-10 01:21:55 +02:00
|
|
|
}
|
|
|
|
}
|
2014-10-26 18:36:47 +01:00
|
|
|
|
2014-09-11 22:20:04 +02:00
|
|
|
r.yMin[pos] = yMin
|
2014-09-10 01:21:55 +02:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
2014-09-10 18:58:12 +02:00
|
|
|
|
|
|
|
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))
|
2014-09-10 23:49:27 +02:00
|
|
|
for z := ph - 1; z >= 0; z-- {
|
2014-09-10 18:58:12 +02:00
|
|
|
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
|
|
|
|
}
|
2014-09-11 17:35:03 +02:00
|
|
|
|
|
|
|
func safeColor(x int32) uint8 {
|
2014-09-12 10:45:36 +02:00
|
|
|
switch {
|
|
|
|
case x < 0:
|
|
|
|
return 0
|
|
|
|
case x > 255:
|
|
|
|
return 255
|
|
|
|
default:
|
|
|
|
return uint8(x)
|
2014-09-11 17:35:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-22 01:11:14 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2014-09-12 17:10:05 +02:00
|
|
|
func (r *Renderer) CreateShadedImage(
|
|
|
|
xOfs, zOfs, width, height int,
|
2014-10-26 18:36:47 +01:00
|
|
|
cols *Colors, background color.RGBA) *image.RGBA {
|
2014-09-12 17:10:05 +02:00
|
|
|
|
|
|
|
image := image.NewRGBA(image.Rect(0, 0, width, height))
|
|
|
|
|
|
|
|
pw := r.width << 4
|
|
|
|
|
2014-10-26 18:36:47 +01:00
|
|
|
cs := cols.Colors
|
|
|
|
|
|
|
|
ofs, numCols := zOfs*pw+xOfs, int32(len(cs))
|
2014-09-12 17:10:05 +02:00
|
|
|
|
|
|
|
stride := pw - width
|
|
|
|
|
2014-09-16 00:08:31 +02:00
|
|
|
istride := image.Stride + 4*width
|
|
|
|
|
|
|
|
iofs := image.PixOffset(0, height-1)
|
|
|
|
|
|
|
|
pix := image.Pix
|
|
|
|
|
2014-10-26 22:36:57 +01:00
|
|
|
if r.TransparentBlocks > 0 { // Fast path for transparent images.
|
2014-10-26 18:36:47 +01:00
|
|
|
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
|
2014-09-11 17:35:03 +02:00
|
|
|
} else {
|
2014-10-28 11:53:18 +01:00
|
|
|
y := r.yBuffer[ofs]
|
|
|
|
t := r.tBuffer[ofs]
|
|
|
|
|
|
|
|
opaque := t == nil || t.Top() < y
|
|
|
|
|
|
|
|
var y1, y2 int32
|
|
|
|
|
2014-10-26 18:36:47 +01:00
|
|
|
if x == 0 {
|
|
|
|
y1 = y
|
|
|
|
} else {
|
|
|
|
y1 = r.yBuffer[ofs-1]
|
2014-10-28 11:53:18 +01:00
|
|
|
if opaque {
|
|
|
|
if s := r.tBuffer[ofs-1]; s != nil {
|
|
|
|
y1 = max32(y1, s.Top())
|
|
|
|
}
|
|
|
|
}
|
2014-10-26 18:36:47 +01:00
|
|
|
}
|
|
|
|
if z == 0 {
|
|
|
|
y2 = y
|
|
|
|
} else {
|
|
|
|
y2 = r.yBuffer[ofs+pw]
|
2014-10-28 11:53:18 +01:00
|
|
|
if opaque {
|
|
|
|
if s := r.tBuffer[ofs+pw]; s != nil {
|
|
|
|
y1 = max32(y1, s.Top())
|
|
|
|
}
|
|
|
|
}
|
2014-10-26 18:36:47 +01:00
|
|
|
}
|
|
|
|
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}
|
2014-10-28 11:53:18 +01:00
|
|
|
if !opaque {
|
|
|
|
col = cols.BlendColors(t, col, y)
|
2014-10-26 18:36:47 +01:00
|
|
|
}
|
|
|
|
pix[iofs] = col.R
|
|
|
|
pix[iofs+1] = col.G
|
|
|
|
pix[iofs+2] = col.B
|
|
|
|
pix[iofs+3] = col.A
|
2014-09-11 17:35:03 +02:00
|
|
|
}
|
2014-10-26 18:36:47 +01:00
|
|
|
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
|
2014-09-11 17:35:03 +02:00
|
|
|
} else {
|
2014-10-26 18:36:47 +01:00
|
|
|
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
|
2014-09-11 17:35:03 +02:00
|
|
|
}
|
2014-10-26 18:36:47 +01:00
|
|
|
iofs += 4
|
|
|
|
ofs++
|
2014-09-11 17:35:03 +02:00
|
|
|
}
|
2014-10-26 18:36:47 +01:00
|
|
|
ofs += stride
|
|
|
|
iofs -= istride
|
2014-09-11 17:35:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return image
|
|
|
|
}
|