OpenDiablo2/d2core/d2asset/dcc_animation.go

143 lines
3.4 KiB
Go

package d2asset
import (
"errors"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"math"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dcc"
d2iface "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
)
// DCCAnimation represens an animation decoded from DCC
type DCCAnimation struct {
animation
dccPath string
transparency int
palette d2iface.Palette
renderer d2iface.Renderer
}
// CreateAnimationFromDCC creates an animation from d2dcc.DCC and d2dat.DATPalette
func CreateDCCAnimation(renderer d2iface.Renderer, dccPath string, palette d2iface.Palette,
transparency int) (d2iface.Animation, error) {
dcc, err := loadDCC(dccPath)
if err != nil {
return nil, err
}
anim := animation{
playLength: defaultPlayLength,
playLoop: true,
directions: make([]animationDirection, dcc.NumberOfDirections),
}
DCC := DCCAnimation{
animation: anim,
dccPath: dccPath,
palette: palette,
renderer: renderer,
transparency: transparency,
}
err = DCC.SetDirection(0)
if err != nil {
return nil, err
}
return &DCC, nil
}
// Clone creates a copy of the animation
func (a *DCCAnimation) Clone() d2iface.Animation {
animation := *a
return &animation
}
// SetDirection places the animation in the direction of an animation
func (a *DCCAnimation) SetDirection(directionIndex int) error {
const smallestInvalidDirectionIndex = 64
if directionIndex >= smallestInvalidDirectionIndex {
return errors.New("invalid direction index")
}
direction := d2dcc.Dir64ToDcc(directionIndex, len(a.directions))
if !a.directions[direction].decoded {
err := a.decodeDirection(direction)
if err != nil {
return err
}
}
a.directionIndex = direction
a.frameIndex = 0
return nil
}
func (a *DCCAnimation) decodeDirection(directionIndex int) error {
dcc, err := loadDCC(a.dccPath)
if err != nil {
return err
}
direction := dcc.DecodeDirection(directionIndex)
minX, minY := math.MaxInt32, math.MaxInt32
maxX, maxY := math.MinInt32, math.MinInt32
for _, dccFrame := range direction.Frames {
minX = d2common.MinInt(minX, dccFrame.Box.Left)
minY = d2common.MinInt(minY, dccFrame.Box.Top)
maxX = d2common.MaxInt(maxX, dccFrame.Box.Right())
maxY = d2common.MaxInt(maxY, dccFrame.Box.Bottom())
}
for _, dccFrame := range direction.Frames {
frameWidth := maxX - minX
frameHeight := maxY - minY
const bytesPerPixel = 4
pixels := make([]byte, frameWidth*frameHeight*bytesPerPixel)
for y := 0; y < frameHeight; y++ {
for x := 0; x < frameWidth; x++ {
paletteIndex := dccFrame.PixelData[y*frameWidth+x]
if paletteIndex == 0 {
continue
}
palColor := a.palette.GetColors()[paletteIndex]
offset := (x + y*frameWidth) * bytesPerPixel
pixels[offset] = palColor.R()
pixels[offset+1] = palColor.G()
pixels[offset+2] = palColor.B()
pixels[offset+3] = byte(a.transparency)
}
}
sfc, err := a.renderer.NewSurface(frameWidth, frameHeight, d2enum.FilterNearest)
if err != nil {
return err
}
if err := sfc.ReplacePixels(pixels); err != nil {
return err
}
a.directions[directionIndex].decoded = true
a.directions[directionIndex].frames = append(a.directions[directionIndex].frames, &animationFrame{
width: dccFrame.Width,
height: dccFrame.Height,
offsetX: minX,
offsetY: minY,
image: sfc,
})
}
return nil
}