From 4d655416c266533208f15d702047b307db161624 Mon Sep 17 00:00:00 2001 From: Ziemas Date: Tue, 26 Nov 2019 02:05:18 +0100 Subject: [PATCH] Cache each cof layer individually and set blending (#238) --- d2render/animated_entity.go | 195 +++++++++++++----------------------- 1 file changed, 67 insertions(+), 128 deletions(-) diff --git a/d2render/animated_entity.go b/d2render/animated_entity.go index 00fb29db..7f636f7b 100644 --- a/d2render/animated_entity.go +++ b/d2render/animated_entity.go @@ -28,6 +28,12 @@ var DccLayerNames = []string{"HD", "TR", "LG", "RA", "LA", "RH", "LH", "SH", "S1 // DirectionLookup is used to decode the direction offset indexes var DirectionLookup = []int{9, 15, 5, 6, 4, 12, 10, 2, 8, 13, 1, 7, 0, 14, 11, 3} +type LayerCacheEntry struct { + frames []*ebiten.Image + compositeMode ebiten.CompositeMode + offsetX, offsetY int32 +} + // AnimatedEntity represents an entity on the map that can be animated type AnimatedEntity struct { fileProvider d2interface.FileProvider @@ -49,9 +55,9 @@ type AnimatedEntity struct { direction int currentFrame int offsetX, offsetY int32 - frames []*ebiten.Image //frameLocations []d2common.Rectangle - object *d2datadict.ObjectLookupRecord + object *d2datadict.ObjectLookupRecord + layerCache []LayerCacheEntry } // CreateAnimatedEntity creates an instance of AnimatedEntity @@ -62,7 +68,6 @@ func CreateAnimatedEntity(x, y int32, object *d2datadict.ObjectLookupRecord, fil token: object.Token, object: object, palette: palette, - frames: []*ebiten.Image{}, //frameLocations: []d2common.Rectangle{}, } result.dccLayers = make(map[string]d2dcc.DCC) @@ -85,8 +90,6 @@ func (v *AnimatedEntity) SetMode(animationMode, weaponClass string, direction in if v.direction >= v.Cof.NumberOfDirections { v.direction = v.Cof.NumberOfDirections - 1 } - //v.frames = make(map[string][]*ebiten.Image) - //v.frameLocations = make(map[string][]d2common.Rectangle) v.dccLayers = make(map[string]d2dcc.DCC) for _, cofLayer := range v.Cof.CofLayers { layerName := DccLayerNames[cofLayer.Type] @@ -97,7 +100,6 @@ func (v *AnimatedEntity) SetMode(animationMode, weaponClass string, direction in } v.updateFrameCache() - //v.cacheFrames() } func (v *AnimatedEntity) LoadLayer(layer string, fileProvider d2interface.FileProvider) d2dcc.DCC { @@ -161,38 +163,22 @@ func (v *AnimatedEntity) Render(target *ebiten.Image, offsetX, offsetY int) { } } } - if v.currentFrame < 0 || v.frames == nil || v.currentFrame >= len(v.frames) || v.frames[v.currentFrame] == nil { - return - } localX := (v.subcellX - v.subcellY) * 16 localY := ((v.subcellX + v.subcellY) * 8) - 5 - opts := &ebiten.DrawImageOptions{} - opts.GeoM.Translate(float64(v.offsetX)+float64(offsetX)+localX, float64(v.offsetY)+float64(offsetY)+localY) - if err := target.DrawImage(v.frames[v.currentFrame], opts); err != nil { - log.Panic(err.Error()) + + for layerIdx := range v.layerCache { + if v.currentFrame < 0 || v.layerCache[layerIdx].frames == nil || v.currentFrame >= len(v.layerCache[layerIdx].frames) || v.layerCache[layerIdx].frames[v.currentFrame] == nil { + return + } + opts := &ebiten.DrawImageOptions{} + x := float64(v.offsetX) + float64(offsetX) + localX + float64(v.layerCache[layerIdx].offsetX) + y := float64(v.offsetY) + float64(offsetY) + localY + float64(v.layerCache[layerIdx].offsetY) + opts.GeoM.Translate(x, y) + opts.CompositeMode = v.layerCache[layerIdx].compositeMode + if err := target.DrawImage(v.layerCache[layerIdx].frames[v.currentFrame], opts); err != nil { + log.Panic(err.Error()) + } } - //for idx := 0; idx < v.Cof.NumberOfLayers; idx++ { - // priority := v.Cof.Priority[v.direction][v.currentFrame][idx] - // if int(priority) >= len(DccLayerNames) { - // continue - // } - // frameName := DccLayerNames[priority] - // if v.frames[frameName] == nil { - // continue - // } - // - // // Location within the current tile - // localX := (v.subcellX - v.subcellY) * 16 - // localY := ((v.subcellX + v.subcellY) * 8) - 5 - // - // // TODO: Transparency op maybe, but it'l murder batch calls - // opts := &ebiten.DrawImageOptions{} - // opts.GeoM.Translate(float64(v.frameLocations[frameName][v.currentFrame].Left+offsetX)+localX, - // float64(v.frameLocations[frameName][v.currentFrame].Top+offsetY)+localY) - // if err := target.DrawImage(v.frames[frameName][v.currentFrame], opts); err != nil { - // log.Panic(err.Error()) - // } - //} } func (v *AnimatedEntity) updateFrameCache() { @@ -206,52 +192,69 @@ func (v *AnimatedEntity) updateFrameCache() { v.animationSpeed = 1.0 / ((float64(animationData.AnimationSpeed) * 25.0) / 256.0) v.framesToAnimate = animationData.FramesPerDirection v.lastFrameTime = d2helper.Now() - minX := int32(10000) - minY := int32(10000) - maxX := int32(-10000) - maxY := int32(-10000) + + v.layerCache = make([]LayerCacheEntry, len(v.Cof.CofLayers)) + for layerIdx := range v.layerCache { + v.layerCache[layerIdx].frames = make([]*ebiten.Image, v.framesToAnimate) + } + for cofLayerIdx := range v.Cof.CofLayers { layerName := DccLayerNames[v.Cof.CofLayers[cofLayerIdx].Type] dccLayer := v.dccLayers[layerName] if !dccLayer.IsValid() { continue } + + minX := int32(10000) + minY := int32(10000) + maxX := int32(-10000) + maxY := int32(-10000) for frameIdx := range dccLayer.Directions[v.direction].Frames { minX = d2helper.MinInt32(minX, int32(dccLayer.Directions[v.direction].Frames[frameIdx].Box.Left)) minY = d2helper.MinInt32(minY, int32(dccLayer.Directions[v.direction].Frames[frameIdx].Box.Top)) maxX = d2helper.MaxInt32(maxX, int32(dccLayer.Directions[v.direction].Frames[frameIdx].Box.Right())) maxY = d2helper.MaxInt32(maxY, int32(dccLayer.Directions[v.direction].Frames[frameIdx].Box.Bottom())) } - } - v.offsetX = minX - v.offsetY = minY - actualWidth := maxX - minX - actualHeight := maxY - minY - if (actualWidth <= 0) || (actualHeight < 0) { - log.Printf("Animated entity created with an invalid size of (%d, %d)", actualWidth, actualHeight) - return - } - v.frames = make([]*ebiten.Image, v.framesToAnimate) - pixels := make([]byte, actualWidth*actualHeight*4) - for animationIdx := 0; animationIdx < v.framesToAnimate; animationIdx++ { - // This should be faster than allocating all the bytes all over again... - for i := 0; i < int(actualWidth*actualHeight); i++ { - pixels[(i*4)+3] = 0 + + v.layerCache[cofLayerIdx].offsetX = minX + v.layerCache[cofLayerIdx].offsetY = minY + actualWidth := maxX - minX + actualHeight := maxY - minY + + if (actualWidth <= 0) || (actualHeight < 0) { + log.Printf("Animated entity created with an invalid size of (%d, %d)", actualWidth, actualHeight) + return } - for cofLayerIdx := range v.Cof.CofLayers { - layerName := DccLayerNames[v.Cof.CofLayers[cofLayerIdx].Type] - dccLayer := v.dccLayers[layerName] - if !dccLayer.IsValid() { - continue - } - transparency := byte(255) - if v.Cof.CofLayers[cofLayerIdx].Transparent { + + transparency := byte(255) + if v.Cof.CofLayers[cofLayerIdx].Transparent { + switch v.Cof.CofLayers[cofLayerIdx].DrawEffect { + //Lets pick whatever we have that's closest. + case d2enum.DrawEffectPctTransparency25: + transparency = byte(64) + case d2enum.DrawEffectPctTransparency50: transparency = byte(128) + case d2enum.DrawEffectPctTransparency75: + transparency = byte(192) + case d2enum.DrawEffectModulate: + v.layerCache[cofLayerIdx].compositeMode = ebiten.CompositeModeLighter + case d2enum.DrawEffectBurn: + // Flies in tal rasha's tomb use this + case d2enum.DrawEffectNormal: + } + } + + pixels := make([]byte, actualWidth*actualHeight*4) + + for animationIdx := 0; animationIdx < v.framesToAnimate; animationIdx++ { + for i := 0; i < int(actualWidth*actualHeight); i++ { + pixels[(i*4)+3] = 0 } if animationIdx >= len(dccLayer.Directions[v.direction].Frames) { log.Printf("Invalid animation index of %d for animated entity", animationIdx) continue } + frame := dccLayer.Directions[v.direction].Frames[animationIdx] for y := 0; y < dccLayer.Directions[v.direction].Box.Height; y++ { for x := 0; x < dccLayer.Directions[v.direction].Box.Width; x++ { @@ -268,76 +271,12 @@ func (v *AnimatedEntity) updateFrameCache() { pixels[(actualX*4)+(actualY*int(actualWidth)*4)+3] = transparency } } + v.layerCache[cofLayerIdx].frames[animationIdx], _ = ebiten.NewImage(int(actualWidth), int(actualHeight), ebiten.FilterNearest) + _ = v.layerCache[cofLayerIdx].frames[animationIdx].ReplacePixels(pixels) } - v.frames[animationIdx], _ = ebiten.NewImage(int(actualWidth), int(actualHeight), ebiten.FilterNearest) - _ = v.frames[animationIdx].ReplacePixels(pixels) } } -//func (v *AnimatedEntity) cacheFrames(layerName string) { -// dcc := v.dccLayers[layerName] -// v.currentFrame = 0 -// animationData := d2data.AnimationData[strings.ToLower(v.token+v.animationMode+v.weaponClass)][0] -// v.animationSpeed = 1.0 / ((float64(animationData.AnimationSpeed) * 25.0) / 256.0) -// v.framesToAnimate = animationData.FramesPerDirection -// v.lastFrameTime = d2helper.Now() -// minX := int32(10000) -// minY := int32(10000) -// maxX := int32(-10000) -// maxY := int32(-10000) -// for _, layer := range dcc.Directions { -// minX = d2helper.MinInt32(minX, int32(layer.Box.Left)) -// minY = d2helper.MinInt32(minY, int32(layer.Box.Top)) -// maxX = d2helper.MaxInt32(maxX, int32(layer.Box.Right())) -// maxY = d2helper.MaxInt32(maxY, int32(layer.Box.Bottom())) -// } -// frameW := maxX - minX -// frameH := maxY - minY -// v.frames[layerName] = make([]*ebiten.Image, v.framesToAnimate) -// v.frameLocations[layerName] = make([]d2common.Rectangle, v.framesToAnimate) -// for frameIndex := range v.frames[layerName] { -// v.frames[layerName][frameIndex], _ = ebiten.NewImage(int(frameW), int(frameH), ebiten.FilterNearest) -// for layerIdx := 0; layerIdx < v.Cof.NumberOfLayers; layerIdx++ { -// transparency := byte(255) -// if v.Cof.CofLayers[layerIdx].Transparent { -// transparency = byte(128) -// } -// -// direction := dcc.Directions[v.direction] -// if frameIndex >= len(direction.Frames) { -// continue -// } -// frame := direction.Frames[frameIndex] -// img := image.NewRGBA(image.Rect(0, 0, int(frameW), int(frameH))) -// for y := 0; y < direction.Box.Height; y++ { -// for x := 0; x < direction.Box.Width; x++ { -// paletteIndex := frame.PixelData[x+(y*direction.Box.Width)] -// -// if paletteIndex == 0 { -// continue -// } -// color := d2datadict.Palettes[v.palette].Colors[paletteIndex] -// actualX := x + direction.Box.Left - int(minX) -// actualY := y + direction.Box.Top - int(minY) -// img.Pix[(actualX*4)+(actualY*int(frameW)*4)] = color.R -// img.Pix[(actualX*4)+(actualY*int(frameW)*4)+1] = color.G -// img.Pix[(actualX*4)+(actualY*int(frameW)*4)+2] = color.B -// img.Pix[(actualX*4)+(actualY*int(frameW)*4)+3] = transparency -// } -// } -// newImage, _ := ebiten.NewImageFromImage(img, ebiten.FilterNearest) -// img = nil -// v.frames[layerName][frameIndex] = newImage -// v.frameLocations[layerName][frameIndex] = d2common.Rectangle{ -// Left: int(minX), -// Top: int(minY), -// Width: int(frameW), -// Height: int(frameH), -// } -// } -// } -//} - func (v AnimatedEntity) GetDirection() int { return v.direction }