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 }