forked from MTSR/mapserver
251 lines
5.2 KiB
Go
251 lines
5.2 KiB
Go
package mapblockrenderer
|
|
|
|
import (
|
|
"errors"
|
|
"image"
|
|
"image/color"
|
|
"mapserver/mapblockaccessor"
|
|
"mapserver/types"
|
|
"time"
|
|
|
|
"github.com/minetest-go/colormapping"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type MapBlockRenderer struct {
|
|
accessor *mapblockaccessor.MapBlockAccessor
|
|
colors *colormapping.ColorMapping
|
|
enableShadow bool
|
|
enableTransparency bool
|
|
}
|
|
|
|
func NewMapBlockRenderer(accessor *mapblockaccessor.MapBlockAccessor, colors *colormapping.ColorMapping) *MapBlockRenderer {
|
|
return &MapBlockRenderer{
|
|
accessor: accessor,
|
|
colors: colors,
|
|
enableShadow: true,
|
|
enableTransparency: false,
|
|
}
|
|
}
|
|
|
|
const (
|
|
IMG_SCALE = 16
|
|
IMG_SIZE = IMG_SCALE * 16
|
|
EXPECTED_BLOCKS_PER_FLAT_MAPBLOCK = 16 * 16
|
|
)
|
|
|
|
func IsViewBlocking(nodeName string) bool {
|
|
if nodeName == "" {
|
|
return false
|
|
}
|
|
|
|
if nodeName == "vacuum:vacuum" {
|
|
return false
|
|
}
|
|
|
|
if nodeName == "air" {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func clamp(num int) uint8 {
|
|
if num < 0 {
|
|
return 0
|
|
}
|
|
|
|
if num > 255 {
|
|
return 255
|
|
}
|
|
|
|
return uint8(num)
|
|
}
|
|
|
|
func addColorComponent(c *color.RGBA, value int) *color.RGBA {
|
|
return &color.RGBA{
|
|
R: clamp(int(c.R) + value),
|
|
G: clamp(int(c.G) + value),
|
|
B: clamp(int(c.B) + value),
|
|
A: c.A,
|
|
}
|
|
}
|
|
|
|
func (r *MapBlockRenderer) Render(pos1, pos2 *types.MapBlockCoords) (*image.NRGBA, error) {
|
|
if pos1.X != pos2.X {
|
|
return nil, errors.New("x does not line up")
|
|
}
|
|
|
|
if pos1.Z != pos2.Z {
|
|
return nil, errors.New("z does not line up")
|
|
}
|
|
|
|
renderedMapblocks.Inc()
|
|
timer := prometheus.NewTimer(renderDuration)
|
|
defer timer.ObserveDuration()
|
|
|
|
start := time.Now()
|
|
defer func() {
|
|
t := time.Now()
|
|
elapsed := t.Sub(start)
|
|
log.WithFields(logrus.Fields{"elapsed": elapsed}).Debug("Rendering completed")
|
|
}()
|
|
|
|
upLeft := image.Point{0, 0}
|
|
lowRight := image.Point{IMG_SIZE, IMG_SIZE}
|
|
img := image.NewNRGBA(image.Rectangle{upLeft, lowRight})
|
|
|
|
maxY := pos1.Y
|
|
minY := pos2.Y
|
|
|
|
if minY > maxY {
|
|
maxY, minY = minY, maxY
|
|
}
|
|
|
|
foundBlocks := 0
|
|
xzOccupationMap := make([][]bool, 16)
|
|
for x := range xzOccupationMap {
|
|
xzOccupationMap[x] = make([]bool, 16)
|
|
}
|
|
|
|
for mapBlockY := maxY; mapBlockY >= minY; mapBlockY-- {
|
|
currentPos := types.NewMapBlockCoords(pos1.X, mapBlockY, pos1.Z)
|
|
mb, err := r.accessor.GetMapBlock(currentPos)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if mb == nil || mb.IsEmpty() {
|
|
continue
|
|
}
|
|
|
|
for x := 0; x < 16; x++ {
|
|
for z := 0; z < 16; z++ {
|
|
for y := 15; y >= 0; y-- {
|
|
if xzOccupationMap[x][z] {
|
|
break
|
|
}
|
|
|
|
nodeName := mb.GetNodeName(x, y, z)
|
|
param2 := mb.GetParam2(x, y, z)
|
|
|
|
if nodeName == "" {
|
|
continue
|
|
}
|
|
|
|
c := r.colors.GetColor(nodeName, param2)
|
|
|
|
if c == nil {
|
|
continue
|
|
}
|
|
|
|
// clamp alpha channel to max
|
|
c.A = 255
|
|
|
|
if r.enableShadow {
|
|
var left, leftAbove, top, topAbove string
|
|
|
|
if x > 0 {
|
|
//same mapblock
|
|
left = mb.GetNodeName(x-1, y, z)
|
|
if y < 15 {
|
|
leftAbove = mb.GetNodeName(x-1, y+1, z)
|
|
}
|
|
|
|
} else {
|
|
//neighbouring mapblock
|
|
neighbourPos := types.NewMapBlockCoords(currentPos.X-1, currentPos.Y, currentPos.Z)
|
|
neighbourMapblock, err := r.accessor.GetMapBlock(neighbourPos)
|
|
|
|
if neighbourMapblock != nil && err == nil {
|
|
left = neighbourMapblock.GetNodeName(15, y, z)
|
|
if y < 15 {
|
|
leftAbove = neighbourMapblock.GetNodeName(15, y+1, z)
|
|
}
|
|
}
|
|
}
|
|
|
|
if z < 14 {
|
|
//same mapblock
|
|
top = mb.GetNodeName(x, y, z+1)
|
|
if y < 15 {
|
|
topAbove = mb.GetNodeName(x, y+1, z+1)
|
|
}
|
|
|
|
} else {
|
|
//neighbouring mapblock
|
|
neighbourPos := types.NewMapBlockCoords(currentPos.X, currentPos.Y, currentPos.Z+1)
|
|
neighbourMapblock, err := r.accessor.GetMapBlock(neighbourPos)
|
|
|
|
if neighbourMapblock != nil && err == nil {
|
|
top = neighbourMapblock.GetNodeName(x, y, 0)
|
|
if y < 15 {
|
|
topAbove = neighbourMapblock.GetNodeName(x, y+1, 0)
|
|
}
|
|
}
|
|
}
|
|
|
|
if IsViewBlocking(leftAbove) {
|
|
//add shadow
|
|
c = addColorComponent(c, -10)
|
|
}
|
|
|
|
if IsViewBlocking(topAbove) {
|
|
//add shadow
|
|
c = addColorComponent(c, -10)
|
|
}
|
|
|
|
if !IsViewBlocking(left) {
|
|
//add light
|
|
c = addColorComponent(c, 10)
|
|
}
|
|
|
|
if !IsViewBlocking(top) {
|
|
//add light
|
|
c = addColorComponent(c, 10)
|
|
}
|
|
}
|
|
|
|
imgX := x * IMG_SCALE
|
|
imgY := (15 - z) * IMG_SCALE
|
|
|
|
r32, g32, b32, a32 := c.RGBA()
|
|
r8, g8, b8, a8 := uint8(r32), uint8(g32), uint8(b32), uint8(a32)
|
|
for Y := imgY; Y < imgY+IMG_SCALE; Y++ {
|
|
ix := (Y*IMG_SIZE + imgX) << 2
|
|
for X := 0; X < IMG_SCALE; X++ {
|
|
img.Pix[ix] = r8
|
|
ix++
|
|
img.Pix[ix] = g8
|
|
ix++
|
|
img.Pix[ix] = b8
|
|
ix++
|
|
img.Pix[ix] = a8
|
|
ix++
|
|
}
|
|
}
|
|
|
|
if c.A != 0xFF || !r.enableTransparency {
|
|
//not transparent, mark as rendered
|
|
foundBlocks++
|
|
xzOccupationMap[x][z] = true
|
|
}
|
|
|
|
if foundBlocks == EXPECTED_BLOCKS_PER_FLAT_MAPBLOCK {
|
|
return img, nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if foundBlocks == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
return img, nil
|
|
}
|