Minor enhancements to map loading (#202)

* Minor improvements to map loading/rendering speed.

* Update region.go
This commit is contained in:
Tim Sarbin 2019-11-17 14:21:48 -05:00 committed by GitHub
parent 5eafa9cc0b
commit 0c7f4e0647
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 131 additions and 141 deletions

View File

@ -148,36 +148,33 @@ func (v *Engine) GenTiles(region *EngineRegion) {
func (v *Engine) GenTilesCache(region *EngineRegion) {
n := 0
for _, t := range region.Tiles {
for tileIdx := range region.Tiles {
t := &region.Tiles[tileIdx]
if t.tileY < len(region.Region.DS1.Tiles) && t.tileX < len(region.Region.DS1.Tiles[t.tileY]) {
tile := region.Region.DS1.Tiles[t.tileY][t.tileX]
for i := range tile.Floors {
if tile.Floors[i].Hidden || tile.Floors[i].Prop1 == 0 {
continue
}
tileCacheIndex := fmt.Sprintf("%v-%v-%v-%v", t.tileY, t.tileX, tile.Floors[i].MainIndex, tile.Floors[i].SubIndex)
region.Region.FloorCache[tileCacheIndex] = region.Region.generateFloorCache(tile.Floors[i])
region.Region.generateFloorCache(tile.Floors[i])
n++
}
for i, shadow := range tile.Shadows {
if tile.Shadows[i].Hidden || tile.Shadows[i].Prop1 == 0 {
continue
}
tileCacheIndex := fmt.Sprintf("%v-%v-%v-%v", t.tileY, t.tileX, shadow.MainIndex, shadow.SubIndex)
region.Region.ShadowCache[tileCacheIndex] = region.Region.generateShadowCache(shadow)
region.Region.generateShadowCache(shadow)
n++
}
for i, wall := range tile.Walls {
if tile.Walls[i].Hidden {
continue
}
tileCacheIndex := fmt.Sprintf("%v-%v-%v-%v-%v", t.tileY, t.tileX, wall.MainIndex, wall.SubIndex, wall.Orientation)
region.Region.WallCache[tileCacheIndex] = region.Region.generateWallCache(wall)
region.Region.generateWallCache(wall)
n++
}
}
}
fmt.Printf("generated: %v cached tiles\n", n)
}
func (v *Engine) RenderRegion(region EngineRegion, target *ebiten.Image) {

View File

@ -1,14 +1,12 @@
package d2mapengine
import (
"fmt"
"image/color"
"log"
"math"
"math/rand"
"sort"
"strconv"
"sync"
"time"
"github.com/OpenDiablo2/D2Shared/d2data/d2dt1"
@ -26,8 +24,6 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2render"
"github.com/OpenDiablo2/D2Shared/d2data"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/hajimehoshi/ebiten"
@ -49,23 +45,19 @@ type Region struct {
Tiles []d2dt1.Tile
DS1 d2ds1.DS1
Palette d2datadict.PaletteRec
FloorCache map[string]*TileCacheRecord
ShadowCache map[string]*TileCacheRecord
WallCache map[string]*TileCacheRecord
AnimationEntities []d2render.AnimatedEntity
NPCs []*d2core.NPC
StartX float64
StartY float64
imageCacheRecords map[uint32]*ebiten.Image
}
func LoadRegion(seed rand.Source, levelType d2enum.RegionIdType, levelPreset int, fileProvider d2interface.FileProvider, fileIndex int) *Region {
result := &Region{
LevelType: d2datadict.LevelTypes[levelType],
LevelPreset: d2datadict.LevelPresets[levelPreset],
Tiles: make([]d2dt1.Tile, 0),
FloorCache: make(map[string]*TileCacheRecord),
ShadowCache: make(map[string]*TileCacheRecord),
WallCache: make(map[string]*TileCacheRecord),
LevelType: d2datadict.LevelTypes[levelType],
LevelPreset: d2datadict.LevelPresets[levelPreset],
Tiles: make([]d2dt1.Tile, 0),
imageCacheRecords: map[uint32]*ebiten.Image{},
}
result.Palette = d2datadict.Palettes[d2enum.PaletteType("act"+strconv.Itoa(int(result.LevelType.Act)))]
//bm := result.levelPreset.Dt1Mask
@ -123,33 +115,27 @@ func (v *Region) loadSpecials() {
}
func (v *Region) loadObjects(fileProvider d2interface.FileProvider) {
var wg sync.WaitGroup
wg.Add(len(v.DS1.Objects))
v.AnimationEntities = make([]d2render.AnimatedEntity, 0)
v.NPCs = make([]*d2core.NPC, 0)
for _, object := range v.DS1.Objects {
go func(object d2data.Object) {
defer wg.Done()
switch object.Lookup.Type {
case d2datadict.ObjectTypeCharacter:
// Temp code, maybe..
if object.Lookup.Base == "" || object.Lookup.Token == "" || object.Lookup.TR == "" {
return
}
npc := d2core.CreateNPC(object.X, object.Y, object.Lookup, fileProvider, 1)
npc.SetPaths(object.Paths)
v.NPCs = append(v.NPCs, npc)
case d2datadict.ObjectTypeItem:
if object.ObjectInfo == nil || !object.ObjectInfo.Draw || object.Lookup.Base == "" || object.Lookup.Token == "" {
return
}
entity := d2render.CreateAnimatedEntity(object.X, object.Y, object.Lookup, fileProvider, d2enum.Units)
entity.SetMode(object.Lookup.Mode, object.Lookup.Class, 0)
v.AnimationEntities = append(v.AnimationEntities, entity)
switch object.Lookup.Type {
case d2datadict.ObjectTypeCharacter:
// Temp code, maybe..
if object.Lookup.Base == "" || object.Lookup.Token == "" || object.Lookup.TR == "" {
continue
}
}(object)
npc := d2core.CreateNPC(object.X, object.Y, object.Lookup, fileProvider, 1)
npc.SetPaths(object.Paths)
v.NPCs = append(v.NPCs, npc)
case d2datadict.ObjectTypeItem:
if object.ObjectInfo == nil || !object.ObjectInfo.Draw || object.Lookup.Base == "" || object.Lookup.Token == "" {
continue
}
entity := d2render.CreateAnimatedEntity(object.X, object.Y, object.Lookup, fileProvider, d2enum.Units)
entity.SetMode(object.Lookup.Mode, object.Lookup.Class, 0)
v.AnimationEntities = append(v.AnimationEntities, entity)
}
}
wg.Wait()
}
func (v *Region) RenderTile(offsetX, offsetY, tileX, tileY int, layerType d2enum.RegionLayerType, layerIndex int, target *ebiten.Image) {
@ -203,71 +189,76 @@ func (v *Region) getTile(mainIndex, subIndex, orientation int32) *d2dt1.Tile {
}
func (v *Region) renderFloor(tile d2ds1.FloorShadowRecord, offsetX, offsetY int, target *ebiten.Image, tileX, tileY int) {
tileCacheIndex := fmt.Sprintf("%v-%v-%v-%v", tileY, tileX, tile.MainIndex, tile.SubIndex)
tileCache, exists := v.FloorCache[tileCacheIndex]
if !exists {
log.Printf("floor cache missed: %v of %v", tileCacheIndex, len(v.FloorCache))
v.FloorCache[tileCacheIndex] = v.generateFloorCache(tile)
tileCache = v.FloorCache[tileCacheIndex]
if tileCache == nil {
log.Println("Could not load floor tile: " + tileCacheIndex)
return
}
}
if tileCache == nil {
log.Println("Nil tile cache: " + tileCacheIndex)
return
}
opts := &ebiten.DrawImageOptions{}
opts.GeoM.Translate(float64(offsetX+tileCache.XOffset), float64(offsetY+tileCache.YOffset))
target.DrawImage(tileCache.Image, opts)
img := v.GetImageCacheRecord(tile.MainIndex, tile.SubIndex, 0)
if img == nil {
img = v.generateFloorCache(tile)
}
opts.GeoM.Translate(float64(offsetX), float64(offsetY))
_ = target.DrawImage(img, opts)
return
}
func (v *Region) renderWall(tile d2ds1.WallRecord, offsetX, offsetY int, target *ebiten.Image, tileX, tileY int) {
tileCacheIndex := fmt.Sprintf("%v-%v-%v-%v-%v", tileY, tileX, tile.MainIndex, tile.SubIndex, tile.Orientation)
tileCache, exists := v.WallCache[tileCacheIndex]
if !exists {
log.Println("wall cache missed")
v.WallCache[tileCacheIndex] = v.generateWallCache(tile)
if v.WallCache[tileCacheIndex] == nil {
log.Println("Could not generate wall: " + tileCacheIndex)
return
}
tileCache = v.WallCache[tileCacheIndex]
img := v.GetImageCacheRecord(tile.MainIndex, tile.SubIndex, tile.Orientation)
if img == nil {
img = v.generateWallCache(tile)
}
if tileCache == nil {
log.Println("Nil tile cache: " + tileCacheIndex)
tileData := v.getTile(int32(tile.MainIndex), int32(tile.SubIndex), int32(tile.Orientation))
if tileData == nil {
return
}
var newTileData *d2dt1.Tile = nil
if tile.Orientation == 3 {
newTileData = v.getTile(int32(tile.MainIndex), int32(tile.SubIndex), int32(4))
}
tileMinY := int32(0)
tileMaxY := int32(0)
targetTileData := tileData
if newTileData != nil && newTileData.Height < tileData.Height {
targetTileData = newTileData
}
for _, block := range targetTileData.Blocks {
tileMinY = d2helper.MinInt32(tileMinY, int32(block.Y))
tileMaxY = d2helper.MaxInt32(tileMaxY, int32(block.Y+32))
}
yAdjust := 0
if tile.Orientation > 15 {
// Lower Walls
yAdjust = 80
} else if tile.Orientation == 15 {
// Roof
yAdjust = -int(tileData.RoofHeight)
} else {
// Upper Walls, Special Tiles
yAdjust = int(tileMinY) + 80
}
opts := &ebiten.DrawImageOptions{}
opts.GeoM.Translate(float64(offsetX+tileCache.XOffset), float64(offsetY+tileCache.YOffset))
target.DrawImage(tileCache.Image, opts)
opts.GeoM.Translate(float64(offsetX), float64(offsetY+yAdjust))
target.DrawImage(img, opts)
}
func (v *Region) renderShadow(tile d2ds1.FloorShadowRecord, offsetX, offsetY int, target *ebiten.Image, tileX, tileY int) {
tileCacheIndex := fmt.Sprintf("%v-%v-%v-%v", tileY, tileX, tile.MainIndex, tile.SubIndex)
tileCache, exists := v.ShadowCache[tileCacheIndex]
if !exists {
log.Println("shadow cache missed")
v.ShadowCache[tileCacheIndex] = v.generateShadowCache(tile)
tileCache = v.ShadowCache[tileCacheIndex]
if tileCache == nil {
log.Println("Could not load shadow tile: " + tileCacheIndex)
return
}
img := v.GetImageCacheRecord(tile.MainIndex, tile.SubIndex, 13)
if img == nil {
img = v.generateShadowCache(tile)
}
if tileCache == nil {
log.Println("Nil tile cache: " + tileCacheIndex)
tileData := v.getTile(int32(tile.MainIndex), int32(tile.SubIndex), 13)
if tileData == nil {
return
}
tileMinY := int32(0)
for _, block := range tileData.Blocks {
tileMinY = d2helper.MinInt32(tileMinY, int32(block.Y))
}
opts := &ebiten.DrawImageOptions{}
opts.GeoM.Translate(float64(offsetX+tileCache.XOffset), float64(offsetY+tileCache.YOffset))
opts.GeoM.Translate(float64(offsetX), float64(offsetY+int(tileMinY)+80))
opts.ColorM = d2corehelper.ColorToColorM(color.RGBA{255, 255, 255, 160})
target.DrawImage(tileCache.Image, opts)
target.DrawImage(img, opts)
}
func (v *Region) decodeTileGfxData(blocks []d2dt1.Block, pixels []byte, tileYOffset int32, tileWidth int32) {
func (v *Region) decodeTileGfxData(blocks []d2dt1.Block, pixels *[]byte, tileYOffset int32, tileWidth int32) {
for _, block := range blocks {
if block.Format == d2dt1.BlockFormatIsometric {
// 3D isometric decoding
@ -288,10 +279,10 @@ func (v *Region) decodeTileGfxData(blocks []d2dt1.Block, pixels []byte, tileYOff
if colorIndex != 0 {
pixelColor := v.Palette.Colors[colorIndex]
offset := 4 * (((blockY + y + tileYOffset) * tileWidth) + (blockX + x))
pixels[offset] = pixelColor.R
pixels[offset+1] = pixelColor.G
pixels[offset+2] = pixelColor.B
pixels[offset+3] = 255
(*pixels)[offset] = pixelColor.R
(*pixels)[offset+1] = pixelColor.G
(*pixels)[offset+2] = pixelColor.B
(*pixels)[offset+3] = 255
}
x++
n--
@ -324,10 +315,10 @@ func (v *Region) decodeTileGfxData(blocks []d2dt1.Block, pixels []byte, tileYOff
if colorIndex != 0 {
pixelColor := v.Palette.Colors[colorIndex]
offset := 4 * (((blockY + y + tileYOffset) * tileWidth) + (blockX + x))
pixels[offset] = pixelColor.R
pixels[offset+1] = pixelColor.G
pixels[offset+2] = pixelColor.B
pixels[offset+3] = 255
(*pixels)[offset] = pixelColor.R
(*pixels)[offset+1] = pixelColor.G
(*pixels)[offset+2] = pixelColor.B
(*pixels)[offset+3] = 255
}
idx++
@ -339,7 +330,7 @@ func (v *Region) decodeTileGfxData(blocks []d2dt1.Block, pixels []byte, tileYOff
}
}
func (v *Region) generateFloorCache(tile d2ds1.FloorShadowRecord) *TileCacheRecord {
func (v *Region) generateFloorCache(tile d2ds1.FloorShadowRecord) *ebiten.Image {
tileData := v.getTile(int32(tile.MainIndex), int32(tile.SubIndex), 0)
if tileData == nil {
log.Printf("Could not locate tile Idx:%d, Sub: %d, Ori: %d\n", tile.MainIndex, tile.SubIndex, 0)
@ -347,6 +338,10 @@ func (v *Region) generateFloorCache(tile d2ds1.FloorShadowRecord) *TileCacheReco
tileData.Width = 10
tileData.Height = 10
}
cachedImage := v.GetImageCacheRecord(tile.MainIndex, tile.SubIndex, 0)
if cachedImage != nil {
return cachedImage
}
tileYMinimum := int32(0)
for _, block := range tileData.Blocks {
tileYMinimum = d2helper.MinInt32(tileYMinimum, int32(block.Y))
@ -355,16 +350,21 @@ func (v *Region) generateFloorCache(tile d2ds1.FloorShadowRecord) *TileCacheReco
tileHeight := d2helper.AbsInt32(tileData.Height)
image, _ := ebiten.NewImage(int(tileData.Width), int(tileHeight), ebiten.FilterNearest)
pixels := make([]byte, 4*tileData.Width*tileHeight)
v.decodeTileGfxData(tileData.Blocks, pixels, tileYOffset, tileData.Width)
v.decodeTileGfxData(tileData.Blocks, &pixels, tileYOffset, tileData.Width)
image.ReplacePixels(pixels)
return &TileCacheRecord{image, 0, 0}
v.SetImageCacheRecord(tile.MainIndex, tile.SubIndex, 0, image)
return image
}
func (v *Region) generateShadowCache(tile d2ds1.FloorShadowRecord) *TileCacheRecord {
func (v *Region) generateShadowCache(tile d2ds1.FloorShadowRecord) *ebiten.Image {
tileData := v.getTile(int32(tile.MainIndex), int32(tile.SubIndex), 13)
if tileData == nil {
return nil
}
cachedImage := v.GetImageCacheRecord(tile.MainIndex, tile.SubIndex, 13)
if cachedImage != nil {
return cachedImage
}
tileMinY := int32(0)
tileMaxY := int32(0)
for _, block := range tileData.Blocks {
@ -375,12 +375,13 @@ func (v *Region) generateShadowCache(tile d2ds1.FloorShadowRecord) *TileCacheRec
tileHeight := int(tileMaxY - tileMinY)
image, _ := ebiten.NewImage(int(tileData.Width), int(tileHeight), ebiten.FilterNearest)
pixels := make([]byte, 4*tileData.Width*int32(tileHeight))
v.decodeTileGfxData(tileData.Blocks, pixels, tileYOffset, tileData.Width)
v.decodeTileGfxData(tileData.Blocks, &pixels, tileYOffset, tileData.Width)
image.ReplacePixels(pixels)
return &TileCacheRecord{image, 0, int(tileMinY) + 80}
v.SetImageCacheRecord(tile.MainIndex, tile.SubIndex, 13, image)
return image
}
func (v *Region) generateWallCache(tile d2ds1.WallRecord) *TileCacheRecord {
func (v *Region) generateWallCache(tile d2ds1.WallRecord) *ebiten.Image {
tileData := v.getTile(int32(tile.MainIndex), int32(tile.SubIndex), int32(tile.Orientation))
if tileData == nil {
return nil
@ -403,31 +404,30 @@ func (v *Region) generateWallCache(tile d2ds1.WallRecord) *TileCacheRecord {
realHeight := d2helper.MaxInt32(d2helper.AbsInt32(tileData.Height), tileMaxY-tileMinY)
tileYOffset := -tileMinY
//tileHeight := int(tileMaxY - tileMinY)
cachedImage := v.GetImageCacheRecord(tile.MainIndex, tile.SubIndex, tile.Orientation)
if cachedImage != nil {
return cachedImage //, 0, yAdjust}
}
image, _ := ebiten.NewImage(160, int(realHeight), ebiten.FilterNearest)
pixels := make([]byte, 4*160*realHeight)
v.decodeTileGfxData(tileData.Blocks, pixels, tileYOffset, 160)
v.decodeTileGfxData(tileData.Blocks, &pixels, tileYOffset, 160)
if newTileData != nil {
v.decodeTileGfxData(newTileData.Blocks, pixels, tileYOffset, 160)
v.decodeTileGfxData(newTileData.Blocks, &pixels, tileYOffset, 160)
}
yAdjust := 0
if tile.Orientation > 15 {
// Lower Walls
yAdjust = 80
} else if tile.Orientation == 15 {
// Roof
yAdjust = -int(tileData.RoofHeight)
} else {
// Upper Walls, Special Tiles
yAdjust = int(tileMinY) + 80
}
// TODO: This may also need to be an atlas, but could get pretty large...
if err := image.ReplacePixels(pixels); err != nil {
log.Panicf(err.Error())
}
return &TileCacheRecord{
image,
0,
yAdjust,
}
v.SetImageCacheRecord(tile.MainIndex, tile.SubIndex, tile.Orientation, image)
return image //,0,yAdjust,
}
func (v *Region) GetImageCacheRecord(mainIndex, subIndex, orientation byte) *ebiten.Image {
lookupIndex := uint32(mainIndex)<<16 | uint32(subIndex)<<8 | uint32(orientation)
return v.imageCacheRecords[lookupIndex]
}
func (v *Region) SetImageCacheRecord(mainIndex, subIndex, orientation byte, image *ebiten.Image) {
lookupIndex := uint32(mainIndex)<<16 | uint32(subIndex)<<8 | uint32(orientation)
v.imageCacheRecords[lookupIndex] = image
}

View File

@ -1,9 +0,0 @@
package d2mapengine
import "github.com/hajimehoshi/ebiten"
type TileCacheRecord struct {
Image *ebiten.Image
XOffset int
YOffset int
}

View File

@ -2,7 +2,6 @@ package d2render
import (
"encoding/binary"
"image"
"image/color"
"log"
"sync"
@ -126,17 +125,18 @@ func CreateSprite(data []byte, palette d2datadict.PaletteRec) Sprite {
x += uint32(b)
}
}
var img = image.NewRGBA(image.Rect(0, 0, int(result.Frames[i].Width), int(result.Frames[i].Height)))
var img = make([]byte, int(result.Frames[i].Width)*int(result.Frames[i].Height)*4)
for ii := uint32(0); ii < result.Frames[i].Width*result.Frames[i].Height; ii++ {
if result.Frames[i].ImageData[ii] < 1 { // TODO: Is this == -1 or < 1?
continue
}
img.Pix[ii*4] = palette.Colors[result.Frames[i].ImageData[ii]].R
img.Pix[(ii*4)+1] = palette.Colors[result.Frames[i].ImageData[ii]].G
img.Pix[(ii*4)+2] = palette.Colors[result.Frames[i].ImageData[ii]].B
img.Pix[(ii*4)+3] = 0xFF
img[ii*4] = palette.Colors[result.Frames[i].ImageData[ii]].R
img[(ii*4)+1] = palette.Colors[result.Frames[i].ImageData[ii]].G
img[(ii*4)+2] = palette.Colors[result.Frames[i].ImageData[ii]].B
img[(ii*4)+3] = 0xFF
}
newImage, _ := ebiten.NewImageFromImage(img, ebiten.FilterNearest)
newImage, _ := ebiten.NewImage(int(result.Frames[i].Width), int(result.Frames[i].Height), ebiten.FilterNearest)
newImage.ReplacePixels(img)
result.Frames[i].Image = newImage
img = nil
}(i)

2
go.mod
View File

@ -7,7 +7,7 @@ require (
github.com/OpenDiablo2/D2Shared v0.0.0-20191117053631-b0c159330365
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect
github.com/hajimehoshi/ebiten v1.11.0-alpha.0.20191117051038-800b98a0c66d
github.com/hajimehoshi/ebiten v1.11.0-alpha.0.20191117152313-63f9ac2ccc2a
github.com/mitchellh/go-homedir v1.1.0
github.com/pkg/profile v1.3.0
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136 // indirect

2
go.sum
View File

@ -27,6 +27,8 @@ github.com/hajimehoshi/ebiten v1.11.0-alpha.0.20191116200143-acc933b7c399 h1:Uey
github.com/hajimehoshi/ebiten v1.11.0-alpha.0.20191116200143-acc933b7c399/go.mod h1:52cGPFR+BqkkBap9Ue4BEKkfvICkvXoONeWCYPEFTPo=
github.com/hajimehoshi/ebiten v1.11.0-alpha.0.20191117051038-800b98a0c66d h1:dJL9ygMUE9U5WOEwgQ+D33t0dDRm3VkwNi2lzaoGQ6A=
github.com/hajimehoshi/ebiten v1.11.0-alpha.0.20191117051038-800b98a0c66d/go.mod h1:6ax6p5ui8fuQ/+00sQ79oTy4OfrythHfDEYV4yni5So=
github.com/hajimehoshi/ebiten v1.11.0-alpha.0.20191117152313-63f9ac2ccc2a h1:EoW3KYFBekMCBuCxnmseVGV0wh6pgmkZGJP7pRWK4ek=
github.com/hajimehoshi/ebiten v1.11.0-alpha.0.20191117152313-63f9ac2ccc2a/go.mod h1:6ax6p5ui8fuQ/+00sQ79oTy4OfrythHfDEYV4yni5So=
github.com/hajimehoshi/go-mp3 v0.2.1/go.mod h1:Rr+2P46iH6PwTPVgSsEwBkon0CK5DxCAeX/Rp65DCTE=
github.com/hajimehoshi/oto v0.3.4/go.mod h1:PgjqsBJff0efqL2nlMJidJgVJywLn6M4y8PI4TfeWfA=
github.com/hajimehoshi/oto v0.5.2 h1:5FEPlejAsR2PVRqiW7h2PIwp9UWR+8zxj2And102YU4=