diff options
Diffstat (limited to 'region_wrapper.go')
| -rw-r--r-- | region_wrapper.go | 363 | 
1 files changed, 363 insertions, 0 deletions
| diff --git a/region_wrapper.go b/region_wrapper.go new file mode 100644 index 0000000..1139de5 --- /dev/null +++ b/region_wrapper.go @@ -0,0 +1,363 @@ +package main + +import ( +	"fmt" +	"github.com/kch42/gomcmap/mcmap" +	"github.com/mattn/go-gtk/gdk" +) + +const cacheSize = 4 + +type RegionWrapper struct { +	region *CachedRegion + +	tileUpdates chan bool + +	Maptiles map[XZPos]*gdk.Pixmap +	Biotiles map[XZPos]*gdk.Pixmap +	bioCache map[XZPos][]mcmap.Biome + +	redraw chan<- bool +	guicbs GUICallbacks + +	toolsEnabled bool +	tool         Tool + +	fixSnowIce bool + +	bio mcmap.Biome + +	startX, startZ, endX, endZ int +} + +func renderTile(chunk *mcmap.Chunk) (maptile, biotile *gdk.Pixmap, biocache []mcmap.Biome) { +	maptile = emptyPixmap(tileSize, tileSize, 24) +	mtDrawable := maptile.GetDrawable() +	mtGC := gdk.NewGC(mtDrawable) + +	biotile = emptyPixmap(tileSize, tileSize, 24) +	btDrawable := biotile.GetDrawable() +	btGC := gdk.NewGC(btDrawable) + +	biocache = make([]mcmap.Biome, mcmap.ChunkRectXZ) + +	i := 0 +	for z := 0; z < mcmap.ChunkSizeXZ; z++ { +	scanX: +		for x := 0; x < mcmap.ChunkSizeXZ; x++ { +			bio := chunk.Biome(x, z) +			btGC.SetRgbFgColor(bioColors[bio]) +			btDrawable.DrawRectangle(btGC, true, x*zoom, z*zoom, zoom, zoom) + +			biocache[i] = bio +			i++ + +			for y := chunk.Height(x, z); y >= 0; y-- { +				if col, ok := blockColors[chunk.Block(x, y, z).ID]; ok { +					mtGC.SetRgbFgColor(col) +					mtDrawable.DrawRectangle(mtGC, true, x*zoom, z*zoom, zoom, zoom) +					continue scanX +				} +			} + +			mtGC.SetRgbFgColor(gdk.NewColor("#ffffff")) +			mtDrawable.DrawRectangle(mtGC, true, x*zoom, z*zoom, zoom, zoom) +		} +	} + +	return +} + +func (rw *RegionWrapper) tileUpdater() { +	for _ = range rw.tileUpdates { +		todelete := make(map[XZPos]bool) + +		for pos := range rw.Maptiles { +			if (pos.X < rw.startX) || (pos.Z < rw.startZ) || (pos.X >= rw.endX) || (pos.Z >= rw.endZ) { +				todelete[pos] = true +			} +		} + +		gdk.ThreadsEnter() +		for pos := range todelete { +			if tile, ok := rw.Maptiles[pos]; ok { +				tile.Unref() +				delete(rw.Maptiles, pos) +			} + +			if tile, ok := rw.Biotiles[pos]; ok { +				tile.Unref() +				delete(rw.Biotiles, pos) +			} + +			if _, ok := rw.bioCache[pos]; ok { +				delete(rw.bioCache, pos) +			} +		} + +		if rw.region != nil { +			for z := rw.startZ; z < rw.endZ; z++ { +			scanX: +				for x := rw.startX; x < rw.endX; x++ { +					pos := XZPos{x, z} + +					if _, ok := rw.Biotiles[pos]; ok { +						continue scanX +					} + +					chunk, err := rw.region.Chunk(x, z) +					switch err { +					case nil: +					case mcmap.NotAvailable: +						continue scanX +					default: +						rw.guicbs.reportFail(fmt.Sprintf("Could not get chunk %d, %d: %s", x, z, err)) +						return +					} + +					rw.Maptiles[pos], rw.Biotiles[pos], rw.bioCache[pos] = renderTile(chunk) +					chunk.MarkUnused() + +					gdk.ThreadsLeave() +					rw.redraw <- true +					gdk.ThreadsEnter() +				} +			} +		} + +		gdk.ThreadsLeave() +	} +} + +func (rw *RegionWrapper) SetRegion(region *mcmap.Region) { +	if rw.RegionLoaded() { +		rw.flushTiles() +	} +	rw.region = NewCachedRegion(region, cacheSize) + +	rw.tileUpdates <- true +} + +func (rw *RegionWrapper) SetChunkBounds(startX, startZ, endX, endZ int) { +	rw.startX = startX +	rw.startZ = startZ +	rw.endX = endX +	rw.endZ = endZ +} + +func (rw *RegionWrapper) SetTool(t Tool)           { rw.tool = t } +func (rw *RegionWrapper) SetBiome(bio mcmap.Biome) { rw.bio = bio } +func (rw *RegionWrapper) SetFixSnowIce(b bool)     { rw.fixSnowIce = b } + +func (rw *RegionWrapper) RegionLoaded() bool    { return rw.region != nil } +func (rw *RegionWrapper) ToolSingleClick() bool { return rw.tool.SingleClick() } + +func (rw *RegionWrapper) flushTiles() { +	if err := rw.region.Flush(); err != nil { +		rw.guicbs.reportFail(fmt.Sprintf("Error while flushing cache: %s", err)) +		return +	} + +	gdk.ThreadsEnter() +	for _, mt := range rw.Maptiles { +		mt.Unref() +	} +	for _, bt := range rw.Biotiles { +		bt.Unref() +	} +	gdk.ThreadsLeave() + +	rw.Maptiles = make(map[XZPos]*gdk.Pixmap) +	rw.Biotiles = make(map[XZPos]*gdk.Pixmap) +	rw.bioCache = make(map[XZPos][]mcmap.Biome) +} + +func (rw *RegionWrapper) Save() { +	gdk.ThreadsLeave() +	rw.flushTiles() +	gdk.ThreadsEnter() + +	if err := rw.region.Flush(); err != nil { +		rw.guicbs.reportFail(fmt.Sprintf("Error while flushing cache: %s", err)) +		return +	} +	if err := rw.region.Region.Save(); err != nil { +		rw.guicbs.reportFail(fmt.Sprintf("Error while saving: %s", err)) +		return +	} +} + +func (rw *RegionWrapper) UseTool(x, z int) { +	gdk.ThreadsLeave() +	defer gdk.ThreadsEnter() + +	if !rw.toolsEnabled { +		return +	} + +	if rw.tool.IsSlow() { +		rw.toolsEnabled = false +		rw.guicbs.setBusy(true) + +		go func() { +			rw.tool.Do(rw.bio, rw, x, z) +			rw.guicbs.setBusy(false) +			rw.toolsEnabled = true + +			rw.redraw <- true +		}() +	} else { +		rw.tool.Do(rw.bio, rw, x, z) +	} +} + +func (rw *RegionWrapper) GetBiomeAt(x, z int) (mcmap.Biome, bool) { +	cx, cz, bx, bz := mcmap.BlockToChunk(x, z) +	pos := XZPos{cx, cz} + +	if bc, ok := rw.bioCache[pos]; ok { +		return bc[bz*mcmap.ChunkSizeXZ+bx], true +	} + +	if !rw.RegionLoaded() { +		return mcmap.BioUncalculated, false +	} + +	chunk, err := rw.region.Chunk(cx, cz) +	switch err { +	case nil: +	case mcmap.NotAvailable: +		return mcmap.BioUncalculated, false +	default: +		rw.guicbs.reportFail(fmt.Sprintf("Error while getting chunk %d, %d: %s", cx, cz, err)) +		return mcmap.BioUncalculated, false +	} + +	bc := make([]mcmap.Biome, mcmap.ChunkRectXZ) +	i := 0 +	for z := 0; z < mcmap.ChunkSizeXZ; z++ { +		for x := 0; x < mcmap.ChunkSizeXZ; x++ { +			bc[i] = chunk.Biome(x, z) +			i++ +		} +	} +	rw.bioCache[pos] = bc + +	return chunk.Biome(bx, bz), true +} + +func fixFreeze(bx, bz int, chunk *mcmap.Chunk) (newcol *gdk.Color) { +	for y := chunk.Height(bx, bz); y >= 0; y-- { +		if blk := chunk.Block(bx, y, bz); blk.ID != mcmap.BlkAir { +			if (blk.ID == mcmap.BlkStationaryWater) || (blk.ID == mcmap.BlkWater) { +				blk.ID = mcmap.BlkIce +				newcol = blockColors[mcmap.BlkIce] +			} else if blockCanSnowIn[blk.ID] { +				if yFix := y + 1; yFix < mcmap.ChunkSizeY { +					blkFix := chunk.Block(bx, yFix, bz) +					blkFix.ID = mcmap.BlkSnow +					blkFix.Data = 0x0 +					newcol = blockColors[mcmap.BlkSnow] +				} +			} + +			break +		} +	} + +	return +} + +func fixMelt(bx, bz int, chunk *mcmap.Chunk) (newcol *gdk.Color) { +	for y := chunk.Height(bx, bz); y >= 0; y-- { +		if blk := chunk.Block(bx, y, bz); blk.ID != mcmap.BlkAir { +			if blk.ID == mcmap.BlkIce { +				blk.ID = mcmap.BlkStationaryWater +				blk.Data = 0x0 +				newcol = blockColors[mcmap.BlkStationaryWater] +			} else if blk.ID == mcmap.BlkSnow { +				blk.ID = mcmap.BlkAir +				for y2 := y - 1; y2 >= 0; y2-- { +					if col, ok := blockColors[chunk.Block(bx, y2, bz).ID]; ok { +						newcol = col +						break +					} +				} +			} + +			break +		} +	} + +	return +} + +func (rw *RegionWrapper) SetBiomeAt(x, z int, bio mcmap.Biome) { +	cx, cz, bx, bz := mcmap.BlockToChunk(x, z) +	pos := XZPos{cx, cz} + +	chunk, err := rw.region.Chunk(cx, cz) +	switch err { +	case nil: +	case mcmap.NotAvailable: +		return +	default: +		rw.guicbs.reportFail(fmt.Sprintf("Error while getting chunk %d, %d: %s", cx, cz, err)) +		return +	} + +	chunk.SetBiome(bx, bz, bio) + +	var newcol *gdk.Color +	if rw.fixSnowIce { +		if coldBiome[bio] { +			newcol = fixFreeze(bx, bz, chunk) +		} else { +			newcol = fixMelt(bx, bz, chunk) +		} +	} + +	chunk.MarkModified() + +	// Update cache +	if bc, ok := rw.bioCache[pos]; ok { +		bc[bz*mcmap.ChunkSizeXZ+bx] = bio +	} + +	// Update tile +	if biotile, ok := rw.Biotiles[pos]; ok { +		gdk.ThreadsEnter() + +		drawable := biotile.GetDrawable() +		gc := gdk.NewGC(drawable) +		gc.SetRgbFgColor(bioColors[bio]) +		drawable.DrawRectangle(gc, true, bx*zoom, bz*zoom, zoom, zoom) + +		if newcol != nil { +			drawable = rw.Maptiles[pos].GetDrawable() +			gc = gdk.NewGC(drawable) +			gc.SetRgbFgColor(newcol) +			drawable.DrawRectangle(gc, true, bx*zoom, bz*zoom, zoom, zoom) +		} + +		gdk.ThreadsLeave() +	} +} + +func (rw *RegionWrapper) UpdateTiles() { +	rw.tileUpdates <- true +} + +func NewRegionWrapper(redraw chan<- bool, guicbs GUICallbacks) *RegionWrapper { +	rw := &RegionWrapper{ +		redraw:       redraw, +		tileUpdates:  make(chan bool), +		toolsEnabled: true, +		guicbs:       guicbs, +		Maptiles:     make(map[XZPos]*gdk.Pixmap), +		Biotiles:     make(map[XZPos]*gdk.Pixmap), +		bioCache:     make(map[XZPos][]mcmap.Biome), +	} +	go rw.tileUpdater() +	return rw +} | 
