Moved bloch decoding and rendering from tilemapper to common.

This commit is contained in:
Sascha L. Teichmann
2014-09-12 20:22:34 +02:00
parent a73e9b35c5
commit b445cfb33a
4 changed files with 208 additions and 220 deletions

View File

@ -1,11 +1,24 @@
package common
import (
"compress/zlib"
"encoding/binary"
"errors"
"io"
"io/ioutil"
"log"
)
// Error returned if a Producer has run to its end.
var ErrNoMoreBlocks = errors.New("No more blocks.")
var (
ErrNoMoreBlocks = errors.New("No more blocks.")
ErrMapContentSizeMismatch = errors.New("Content size does not match.")
)
const (
mapBlockSize = 16
nodeCount = mapBlockSize * mapBlockSize * mapBlockSize
)
type (
// Block data from Minetest database.
@ -28,4 +41,185 @@ type (
// Closes the open database connections.
Close() error
}
DecodedBlock struct {
Version byte
MapContent []byte
AirId int32
IgnoreId int32
IndexMap map[int32]int32
}
)
// The content of the map and the meta data are compressed with zlib.
// Unfortunately the byte length of this two structures are not stored
// explicitly in the block data. To access the informations behind
// them (e.g. the node id mappings) we have to count the bytes consumed
// by the zlib reader and continue our extraction process behind this
// offset. posBuf implements such a counting reader source.
type posBuf struct {
Data []byte
Pos int
}
func NewDecodedBlock(data []byte, nameIndex map[string]int32) (db *DecodedBlock, err error) {
version := data[0]
contentWidth := int(data[2])
paramsWidth := int(data[3])
uncompressedLen := nodeCount * (contentWidth + paramsWidth)
offset := 2
if version >= 22 {
offset = 4
}
buf := NewposBuf(data[offset:])
var zr io.ReadCloser
if zr, err = zlib.NewReader(buf); err != nil {
return
}
var mapContent []byte
mapContent, err = ioutil.ReadAll(zr)
zr.Close()
if err != nil {
return
}
if uncompressedLen != len(mapContent) {
err = ErrMapContentSizeMismatch
return
}
offset += buf.Pos
buf.Pos = 0
buf.Data = data[offset:]
if zr, err = zlib.NewReader(buf); err != nil {
return
}
// Discard the meta data.
_, err = io.Copy(ioutil.Discard, zr)
zr.Close()
if err != nil {
return
}
offset += buf.Pos
switch {
case version <= 21:
offset += 2
case version == 23:
offset++
case version == 24:
ver := data[offset]
offset++
if ver == 1 {
num := int(binary.BigEndian.Uint16(data[offset:]))
offset += 2 + 10*num
}
}
offset++
numStaticObjects := int(binary.BigEndian.Uint16(data[offset:]))
offset += 2
for i := 0; i < numStaticObjects; i++ {
offset += 13
dataSize := int(binary.BigEndian.Uint16(data[offset:]))
offset += dataSize + 2
}
offset += 4
airId, ignoreId := int32(-1), int32(-1)
indexMap := make(map[int32]int32)
if version >= 22 {
offset++
numMappings := int(binary.BigEndian.Uint16(data[offset:]))
offset += 2
for i := 0; i < numMappings; i++ {
nodeId := int32(binary.BigEndian.Uint16(data[offset:]))
offset += 2
nameLen := int(binary.BigEndian.Uint16(data[offset:]))
offset += 2
name := string(data[offset : offset+nameLen])
offset += nameLen
switch name {
case "air":
airId = nodeId
case "ignore":
ignoreId = nodeId
default:
if index, found := nameIndex[name]; found {
indexMap[nodeId] = index
} else {
log.Printf("Missing color entry for %s.", name)
}
}
}
}
db = &DecodedBlock{
Version: version,
MapContent: mapContent,
AirId: airId,
IgnoreId: ignoreId,
IndexMap: indexMap}
return
}
func (db *DecodedBlock) Content(x, y, z int) (content int32, found bool) {
pos := z<<8 + y<<4 + x
switch {
case db.Version >= 24:
pos <<= 1
content = int32(db.MapContent[pos])<<8 | int32(db.MapContent[pos+1])
case db.Version >= 20:
if db.MapContent[pos] <= 0x80 {
content = int32(db.MapContent[pos])
} else {
content = int32(db.MapContent[pos])<<4 | int32(db.MapContent[pos+0x2000])>>4
}
default:
return
}
if content != db.AirId && content != db.IgnoreId {
content, found = db.IndexMap[content]
}
return
}
func NewposBuf(data []byte) *posBuf {
return &posBuf{Data: data}
}
func (pb *posBuf) Read(p []byte) (int, error) {
pl := len(p)
ml := len(pb.Data)
if pb.Pos >= ml {
return 0, io.EOF
}
rest := ml - pb.Pos
if pl > rest {
copy(p, pb.Data[pb.Pos:])
pb.Pos = ml
return rest, io.EOF
}
copy(p, pb.Data[pb.Pos:pb.Pos+pl])
pb.Pos += pl
return pl, nil
}
func (pb *posBuf) ReadByte() (byte, error) {
if pb.Pos >= len(pb.Data) {
return 0, io.EOF
}
c := pb.Data[pb.Pos]
pb.Pos++
return c, nil
}

273
common/renderer.go Normal file
View File

@ -0,0 +1,273 @@
// 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 Renderer struct {
width int
height int
xOfs int16
zOfs int16
yBuffer []int32
yMin []int32
cBuffer []int32
filled int
Rejected 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 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, nameIndex map[string]int32) (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, nameIndex)
return
}
// Render old one. Store copy of new in heap.
heap.Pop(yo)
err = yo.Renderer.RenderBlock(oblock, nameIndex)
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(nameIndex map[string]int32) (err error) {
for len(yo.blocks) > 0 {
if err = yo.Renderer.RenderBlock(heap.Pop(yo).(*Block), nameIndex); 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(xOfs, zOfs int16, width, height int) (renderer *Renderer) {
dim := width * height
pixSize := dim * 16 * 16
yBuffer := make([]int32, pixSize)
cBuffer := make([]int32, pixSize)
yMin := make([]int32, dim)
for i := 0; i < pixSize; i++ {
yBuffer[i] = math.MinInt32
cBuffer[i] = -1
}
for i := 0; i < dim; i++ {
yMin[i] = math.MinInt32
}
renderer = &Renderer{
width: width,
height: height,
xOfs: xOfs,
zOfs: zOfs,
yBuffer: yBuffer,
cBuffer: cBuffer,
yMin: yMin}
return
}
func (r *Renderer) IsFilled() bool {
return r.filled == r.width<<4*r.height<<4
}
func (r *Renderer) RenderBlock(block *Block, nameIndex map[string]int32) (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.Rejected++
return
}
// Decoding is pretty expensive so do it that late.
var db *DecodedBlock
if db, err = NewDecodedBlock(block.Data, nameIndex); err != nil {
return
}
w := r.width << 4
ofs := int(bz)*w<<4 + int(bx)<<4
yMin := int32(math.MaxInt32)
for z := 0; z < 16; z++ {
for x := 0; x < 16; x++ {
currentY := r.yBuffer[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
break
}
}
}
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 (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
for z := height - 1; z >= 0; z-- {
for x := 0; x < width; x++ {
colIdx := r.cBuffer[ofs]
if colIdx < 0 || colIdx >= numCols {
image.Set(x, z, background)
} 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]
image.Set(x, z, color.RGBA{
R: safeColor(int32(col.R) + d),
G: safeColor(int32(col.G) + d),
B: safeColor(int32(col.B) + d),
A: 0xff})
}
ofs++
}
ofs += stride
}
return image
}