Files
go-mc/examples/genmaps/genmaps.go
2021-03-13 10:13:50 +08:00

174 lines
3.8 KiB
Go

package main
import (
"flag"
"fmt"
"github.com/Tnze/go-mc/data/block"
"github.com/Tnze/go-mc/save"
"github.com/Tnze/go-mc/save/region"
"image"
"image/color"
"image/draw"
"log"
"os"
"path/filepath"
"runtime"
"sync"
"unsafe"
)
var colors []color.RGBA64
var regionWorkerNum = runtime.NumCPU()
var sectionWorkerNum = 1
var (
regionsFold = flag.String("region", filepath.Join(os.Getenv("AppData"), ".minecraft", "saves", "World", "region"), "region directory path")
)
func main() {
flag.Usage = usage
flag.Parse()
de, err := os.ReadDir(*regionsFold)
if err != nil {
log.Fatal(err)
}
var min, max [2]int
updateMinMax := func(pos [2]int) {
mkmax(&max[0], &pos[0])
mkmax(&max[1], &pos[1])
mkmin(&min[0], &pos[0])
mkmin(&min[1], &pos[1])
}
// Open mca files
var rs = make(map[[2]int]*region.Region, len(de))
for _, dir := range de {
name := dir.Name()
path := filepath.Join(*regionsFold, name)
var pos [2]int // {x, z}
if _, err := fmt.Sscanf(name, "r.%d.%d.mca", &pos[0], &pos[1]); err != nil {
log.Printf("Error parsing file name of %s: %v, ignoring", name, err)
continue
}
updateMinMax(pos)
r, err := region.Open(path)
if err != nil {
log.Printf("Error when opening %s: %v, ignoring", name, err)
continue
}
rs[pos] = r
}
// To close mca files
defer func() {
for pos, r := range rs {
if err := r.Close(); err != nil {
log.Printf("Close r.%d.%d.mca error: %v", pos[0], pos[1], err)
}
}
}()
// draw columns
for pos, r := range rs {
img := image.NewRGBA(image.Rect(0, 0, 32*16, 32*16))
type task struct {
data []byte
pos [2]int
}
c := make(chan task)
var wg sync.WaitGroup
for i := 0; i < regionWorkerNum; i++ {
go func() {
var column save.Column
for task := range c {
if err := column.Load(task.data); err != nil {
log.Printf("Decode column (%d.%d) error: %v", task.pos[0], task.pos[1], err)
}
pos := [2]int{int(column.Level.PosX), int(column.Level.PosZ)}
if pos != task.pos {
fmt.Printf("chunk position not match: want %v, get %v\n", task.pos, pos)
}
draw.Draw(
img, image.Rect(task.pos[0]*16, task.pos[1]*16, task.pos[0]*16+16, task.pos[1]*16+16),
drawColumn(&column), image.Pt(0, 0),
draw.Over,
)
wg.Done()
}
}()
}
for x := 0; x < 32; x++ {
for z := 0; z < 32; z++ {
if !r.ExistSector(x, z) {
continue
}
data, err := r.ReadSector(x, z)
if err != nil {
log.Printf("Read sector (%d.%d) error: %v", x, z, err)
}
wg.Add(1)
c <- task{data: data, pos: [2]int{x, z}}
}
}
close(c)
wg.Wait()
savePng(img, fmt.Sprintf("r.%d.%d.png", pos[0], pos[1]))
log.Print("Draw: ", pos)
}
}
func drawColumn(column *save.Column) (img *image.RGBA) {
img = image.NewRGBA(image.Rect(0, 0, 16, 16))
s := column.Level.Sections
c := make(chan *save.Chunk)
var wg sync.WaitGroup
for i := 0; i < sectionWorkerNum; i++ {
go func() {
for s := range c {
drawSection(s, img)
wg.Done()
}
}()
}
defer close(c)
wg.Add(len(s))
for i := range s {
c <- &s[i]
}
wg.Wait()
return
}
func drawSection(s *save.Chunk, img *image.RGBA) {
// calculate bits per block
bpb := len(s.BlockStates) * 64 / (16 * 16 * 16)
// skip empty
if len(s.BlockStates) == 0 {
return
}
// decode section
//n := int(math.Max(4, math.Ceil(math.Log2(float64(len(s.Palette))))))
// decode status
data := *(*[]uint64)(unsafe.Pointer(&s.BlockStates)) // convert []int64 into []uint64
bs := save.NewBitStorage(bpb, 4096, data)
for y := 0; y < 16; y++ {
layerImg := image.NewRGBA(image.Rect(0, 0, 16, 16))
for i := 16*16 - 1; i >= 0; i-- {
b := block.ByID[block.StateID[uint32(bs.Get(y*16*16+i))]]
c := colors[b.ID]
layerImg.Set(i/16, i%16, c)
}
draw.Draw(
img, image.Rect(0, 0, 16, 16),
layerImg, image.Pt(0, 0),
draw.Over,
)
}
return
}