mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2025-02-05 08:07:51 -05:00
Split up DCC/DC6 animation implementations, decode directions on demand (#548)
* Split DCC and decode direction on demand * Clean up * More cleanup * Make dc6 animation it's own type * Make base animation private * Also decode DC6 directions on demand
This commit is contained in:
parent
e473dc2cc5
commit
5212270c44
@ -15,12 +15,15 @@ type DCC struct {
|
|||||||
Version int
|
Version int
|
||||||
NumberOfDirections int
|
NumberOfDirections int
|
||||||
FramesPerDirection int
|
FramesPerDirection int
|
||||||
Directions []*DCCDirection
|
directionOffsets []int
|
||||||
|
fileData []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load loads a DCC file.
|
// Load loads a DCC file.
|
||||||
func Load(fileData []byte) (*DCC, error) {
|
func Load(fileData []byte) (*DCC, error) {
|
||||||
result := &DCC{}
|
result := &DCC{
|
||||||
|
fileData: fileData,
|
||||||
|
}
|
||||||
|
|
||||||
var bm = d2common.CreateBitMuncher(fileData, 0)
|
var bm = d2common.CreateBitMuncher(fileData, 0)
|
||||||
|
|
||||||
@ -40,18 +43,17 @@ func Load(fileData []byte) (*DCC, error) {
|
|||||||
|
|
||||||
bm.GetInt32() // TotalSizeCoded
|
bm.GetInt32() // TotalSizeCoded
|
||||||
|
|
||||||
directionOffsets := make([]int, result.NumberOfDirections)
|
result.directionOffsets = make([]int, result.NumberOfDirections)
|
||||||
|
|
||||||
for i := 0; i < result.NumberOfDirections; i++ {
|
for i := 0; i < result.NumberOfDirections; i++ {
|
||||||
directionOffsets[i] = int(bm.GetInt32())
|
result.directionOffsets[i] = int(bm.GetInt32())
|
||||||
}
|
|
||||||
|
|
||||||
result.Directions = make([]*DCCDirection, result.NumberOfDirections)
|
|
||||||
|
|
||||||
for i := 0; i < result.NumberOfDirections; i++ {
|
|
||||||
result.Directions[i] = CreateDCCDirection(d2common.CreateBitMuncher(
|
|
||||||
fileData, directionOffsets[i]*directionOffsetMultiplier), result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DecodeDirection decodes and returns the given direction
|
||||||
|
func (dcc *DCC) DecodeDirection(direction int) *DCCDirection {
|
||||||
|
return CreateDCCDirection(d2common.CreateBitMuncher(dcc.fileData,
|
||||||
|
dcc.directionOffsets[direction]*directionOffsetMultiplier), dcc)
|
||||||
|
}
|
||||||
|
@ -4,14 +4,12 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"math"
|
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||||
|
|
||||||
d2iface "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
d2iface "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dc6"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dcc"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dcc"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,12 +33,13 @@ type animationFrame struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type animationDirection struct {
|
type animationDirection struct {
|
||||||
frames []*animationFrame
|
decoded bool
|
||||||
|
frames []*animationFrame
|
||||||
}
|
}
|
||||||
|
|
||||||
// Animation has directionality, play modes, and frame counting
|
// animation has directionality, play modes, and frame counting
|
||||||
type Animation struct {
|
type animation struct {
|
||||||
directions []*animationDirection
|
directions []animationDirection
|
||||||
colorMod color.Color
|
colorMod color.Color
|
||||||
frameIndex int
|
frameIndex int
|
||||||
directionIndex int
|
directionIndex int
|
||||||
@ -56,179 +55,15 @@ type Animation struct {
|
|||||||
hasSubLoop bool // runs after first animation ends
|
hasSubLoop bool // runs after first animation ends
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateAnimationFromDCC creates an animation from d2dcc.DCC and d2dat.DATPalette
|
|
||||||
func CreateAnimationFromDCC(renderer d2iface.Renderer, dcc *d2dcc.DCC, palette d2iface.Palette,
|
|
||||||
transparency int) (d2iface.Animation, error) {
|
|
||||||
animation := &Animation{
|
|
||||||
playLength: defaultPlayLength,
|
|
||||||
playLoop: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
for directionIndex, dccDirection := range dcc.Directions {
|
|
||||||
for _, dccFrame := range dccDirection.Frames {
|
|
||||||
minX, minY := math.MaxInt32, math.MaxInt32
|
|
||||||
maxX, maxY := math.MinInt32, math.MinInt32
|
|
||||||
|
|
||||||
for _, dccFrame := range dccDirection.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())
|
|
||||||
}
|
|
||||||
|
|
||||||
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 := 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(transparency)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sfc, err := renderer.NewSurface(frameWidth, frameHeight, d2iface.FilterNearest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := sfc.ReplacePixels(pixels); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if directionIndex >= len(animation.directions) {
|
|
||||||
animation.directions = append(animation.directions, new(animationDirection))
|
|
||||||
}
|
|
||||||
|
|
||||||
direction := animation.directions[directionIndex]
|
|
||||||
direction.frames = append(direction.frames, &animationFrame{
|
|
||||||
width: dccFrame.Width,
|
|
||||||
height: dccFrame.Height,
|
|
||||||
offsetX: minX,
|
|
||||||
offsetY: minY,
|
|
||||||
image: sfc,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return animation, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateAnimationFromDC6 creates an Animation from d2dc6.DC6 and d2dat.DATPalette
|
|
||||||
func CreateAnimationFromDC6(renderer d2iface.Renderer, dc6 *d2dc6.DC6,
|
|
||||||
palette d2iface.Palette) (d2iface.Animation, error) {
|
|
||||||
animation := &Animation{
|
|
||||||
playLength: defaultPlayLength,
|
|
||||||
playLoop: true,
|
|
||||||
originAtBottom: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
for frameIndex, dc6Frame := range dc6.Frames {
|
|
||||||
sfc, err := renderer.NewSurface(int(dc6Frame.Width), int(dc6Frame.Height),
|
|
||||||
d2iface.FilterNearest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
indexData := make([]int, dc6Frame.Width*dc6Frame.Height)
|
|
||||||
for i := range indexData {
|
|
||||||
indexData[i] = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
x := 0
|
|
||||||
y := int(dc6Frame.Height) - 1
|
|
||||||
offset := 0
|
|
||||||
|
|
||||||
for {
|
|
||||||
b := int(dc6Frame.FrameData[offset])
|
|
||||||
offset++
|
|
||||||
|
|
||||||
if b == 0x80 {
|
|
||||||
if y == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
y--
|
|
||||||
x = 0
|
|
||||||
} else if b&0x80 > 0 {
|
|
||||||
transparentPixels := b & 0x7f
|
|
||||||
for i := 0; i < transparentPixels; i++ {
|
|
||||||
indexData[x+y*int(dc6Frame.Width)+i] = -1
|
|
||||||
}
|
|
||||||
x += transparentPixels
|
|
||||||
} else {
|
|
||||||
for i := 0; i < b; i++ {
|
|
||||||
indexData[x+y*int(dc6Frame.Width)+i] = int(dc6Frame.FrameData[offset])
|
|
||||||
offset++
|
|
||||||
}
|
|
||||||
x += b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bytesPerPixel := 4
|
|
||||||
colorData := make([]byte, int(dc6Frame.Width)*int(dc6Frame.Height)*bytesPerPixel)
|
|
||||||
|
|
||||||
for i := 0; i < int(dc6Frame.Width*dc6Frame.Height); i++ {
|
|
||||||
// TODO: Is this == -1 or < 1?
|
|
||||||
if indexData[i] < 1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
c := palette.GetColors()[indexData[i]]
|
|
||||||
colorData[i*bytesPerPixel] = c.R()
|
|
||||||
colorData[i*bytesPerPixel+1] = c.G()
|
|
||||||
colorData[i*bytesPerPixel+2] = c.B()
|
|
||||||
colorData[i*bytesPerPixel+3] = c.A()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := sfc.ReplacePixels(colorData); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
directionIndex := frameIndex / int(dc6.FramesPerDirection)
|
|
||||||
if directionIndex >= len(animation.directions) {
|
|
||||||
animation.directions = append(animation.directions, new(animationDirection))
|
|
||||||
}
|
|
||||||
|
|
||||||
direction := animation.directions[directionIndex]
|
|
||||||
direction.frames = append(direction.frames, &animationFrame{
|
|
||||||
width: int(dc6Frame.Width),
|
|
||||||
height: int(dc6Frame.Height),
|
|
||||||
offsetX: int(dc6Frame.OffsetX),
|
|
||||||
offsetY: int(dc6Frame.OffsetY),
|
|
||||||
image: sfc,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return animation, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clone creates a copy of the animation
|
|
||||||
func (a *Animation) Clone() d2iface.Animation {
|
|
||||||
animation := *a
|
|
||||||
return &animation
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSubLoop sets a sub loop for the animation
|
// SetSubLoop sets a sub loop for the animation
|
||||||
func (a *Animation) SetSubLoop(startFrame, endFrame int) {
|
func (a *animation) SetSubLoop(startFrame, endFrame int) {
|
||||||
a.subStartingFrame = startFrame
|
a.subStartingFrame = startFrame
|
||||||
a.subEndingFrame = endFrame
|
a.subEndingFrame = endFrame
|
||||||
a.hasSubLoop = true
|
a.hasSubLoop = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Advance advances the animation state
|
// Advance advances the animation state
|
||||||
func (a *Animation) Advance(elapsed float64) error {
|
func (a *animation) Advance(elapsed float64) error {
|
||||||
if a.playMode == playModePause {
|
if a.playMode == playModePause {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -278,7 +113,7 @@ func (a *Animation) Advance(elapsed float64) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Render renders the animation to the given surface
|
// Render renders the animation to the given surface
|
||||||
func (a *Animation) Render(target d2iface.Surface) error {
|
func (a *animation) Render(target d2iface.Surface) error {
|
||||||
direction := a.directions[a.directionIndex]
|
direction := a.directions[a.directionIndex]
|
||||||
frame := direction.frames[a.frameIndex]
|
frame := direction.frames[a.frameIndex]
|
||||||
|
|
||||||
@ -295,7 +130,7 @@ func (a *Animation) Render(target d2iface.Surface) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RenderFromOrigin renders the animation from the animation origin
|
// RenderFromOrigin renders the animation from the animation origin
|
||||||
func (a *Animation) RenderFromOrigin(target d2iface.Surface) error {
|
func (a *animation) RenderFromOrigin(target d2iface.Surface) error {
|
||||||
if a.originAtBottom {
|
if a.originAtBottom {
|
||||||
direction := a.directions[a.directionIndex]
|
direction := a.directions[a.directionIndex]
|
||||||
frame := direction.frames[a.frameIndex]
|
frame := direction.frames[a.frameIndex]
|
||||||
@ -308,7 +143,7 @@ func (a *Animation) RenderFromOrigin(target d2iface.Surface) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RenderSection renders the section of the animation frame enclosed by bounds
|
// RenderSection renders the section of the animation frame enclosed by bounds
|
||||||
func (a *Animation) RenderSection(sfc d2iface.Surface, bound image.Rectangle) error {
|
func (a *animation) RenderSection(sfc d2iface.Surface, bound image.Rectangle) error {
|
||||||
direction := a.directions[a.directionIndex]
|
direction := a.directions[a.directionIndex]
|
||||||
frame := direction.frames[a.frameIndex]
|
frame := direction.frames[a.frameIndex]
|
||||||
|
|
||||||
@ -322,7 +157,7 @@ func (a *Animation) RenderSection(sfc d2iface.Surface, bound image.Rectangle) er
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetFrameSize gets the Size(width, height) of a indexed frame.
|
// GetFrameSize gets the Size(width, height) of a indexed frame.
|
||||||
func (a *Animation) GetFrameSize(frameIndex int) (width, height int, err error) {
|
func (a *animation) GetFrameSize(frameIndex int) (width, height int, err error) {
|
||||||
direction := a.directions[a.directionIndex]
|
direction := a.directions[a.directionIndex]
|
||||||
if frameIndex >= len(direction.frames) {
|
if frameIndex >= len(direction.frames) {
|
||||||
return 0, 0, errors.New("invalid frame index")
|
return 0, 0, errors.New("invalid frame index")
|
||||||
@ -334,13 +169,13 @@ func (a *Animation) GetFrameSize(frameIndex int) (width, height int, err error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetCurrentFrameSize gets the Size(width, height) of the current frame.
|
// GetCurrentFrameSize gets the Size(width, height) of the current frame.
|
||||||
func (a *Animation) GetCurrentFrameSize() (width, height int) {
|
func (a *animation) GetCurrentFrameSize() (width, height int) {
|
||||||
width, height, _ = a.GetFrameSize(a.frameIndex)
|
width, height, _ = a.GetFrameSize(a.frameIndex)
|
||||||
return width, height
|
return width, height
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFrameBounds gets maximum Size(width, height) of all frame.
|
// GetFrameBounds gets maximum Size(width, height) of all frame.
|
||||||
func (a *Animation) GetFrameBounds() (maxWidth, maxHeight int) {
|
func (a *animation) GetFrameBounds() (maxWidth, maxHeight int) {
|
||||||
maxWidth, maxHeight = 0, 0
|
maxWidth, maxHeight = 0, 0
|
||||||
|
|
||||||
direction := a.directions[a.directionIndex]
|
direction := a.directions[a.directionIndex]
|
||||||
@ -353,33 +188,33 @@ func (a *Animation) GetFrameBounds() (maxWidth, maxHeight int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetCurrentFrame gets index of current frame in animation
|
// GetCurrentFrame gets index of current frame in animation
|
||||||
func (a *Animation) GetCurrentFrame() int {
|
func (a *animation) GetCurrentFrame() int {
|
||||||
return a.frameIndex
|
return a.frameIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFrameCount gets number of frames in animation
|
// GetFrameCount gets number of frames in animation
|
||||||
func (a *Animation) GetFrameCount() int {
|
func (a *animation) GetFrameCount() int {
|
||||||
direction := a.directions[a.directionIndex]
|
direction := a.directions[a.directionIndex]
|
||||||
return len(direction.frames)
|
return len(direction.frames)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsOnFirstFrame gets if the animation on its first frame
|
// IsOnFirstFrame gets if the animation on its first frame
|
||||||
func (a *Animation) IsOnFirstFrame() bool {
|
func (a *animation) IsOnFirstFrame() bool {
|
||||||
return a.frameIndex == 0
|
return a.frameIndex == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsOnLastFrame gets if the animation on its last frame
|
// IsOnLastFrame gets if the animation on its last frame
|
||||||
func (a *Animation) IsOnLastFrame() bool {
|
func (a *animation) IsOnLastFrame() bool {
|
||||||
return a.frameIndex == a.GetFrameCount()-1
|
return a.frameIndex == a.GetFrameCount()-1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDirectionCount gets the number of animation direction
|
// GetDirectionCount gets the number of animation direction
|
||||||
func (a *Animation) GetDirectionCount() int {
|
func (a *animation) GetDirectionCount() int {
|
||||||
return len(a.directions)
|
return len(a.directions)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDirection places the animation in the direction of an animation
|
// SetDirection places the animation in the direction of an animation
|
||||||
func (a *Animation) SetDirection(directionIndex int) error {
|
func (a *animation) SetDirection(directionIndex int) error {
|
||||||
const smallestInvalidDirectionIndex = 64
|
const smallestInvalidDirectionIndex = 64
|
||||||
if directionIndex >= smallestInvalidDirectionIndex {
|
if directionIndex >= smallestInvalidDirectionIndex {
|
||||||
return errors.New("invalid direction index")
|
return errors.New("invalid direction index")
|
||||||
@ -392,12 +227,12 @@ func (a *Animation) SetDirection(directionIndex int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetDirection get the current animation direction
|
// GetDirection get the current animation direction
|
||||||
func (a *Animation) GetDirection() int {
|
func (a *animation) GetDirection() int {
|
||||||
return a.directionIndex
|
return a.directionIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCurrentFrame sets animation at a specific frame
|
// SetCurrentFrame sets animation at a specific frame
|
||||||
func (a *Animation) SetCurrentFrame(frameIndex int) error {
|
func (a *animation) SetCurrentFrame(frameIndex int) error {
|
||||||
if frameIndex >= a.GetFrameCount() {
|
if frameIndex >= a.GetFrameCount() {
|
||||||
return errors.New("invalid frame index")
|
return errors.New("invalid frame index")
|
||||||
}
|
}
|
||||||
@ -409,69 +244,69 @@ func (a *Animation) SetCurrentFrame(frameIndex int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Rewind animation to beginning
|
// Rewind animation to beginning
|
||||||
func (a *Animation) Rewind() {
|
func (a *animation) Rewind() {
|
||||||
_ = a.SetCurrentFrame(0)
|
_ = a.SetCurrentFrame(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlayForward plays animation forward
|
// PlayForward plays animation forward
|
||||||
func (a *Animation) PlayForward() {
|
func (a *animation) PlayForward() {
|
||||||
a.playMode = playModeForward
|
a.playMode = playModeForward
|
||||||
a.lastFrameTime = 0
|
a.lastFrameTime = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlayBackward plays animation backward
|
// PlayBackward plays animation backward
|
||||||
func (a *Animation) PlayBackward() {
|
func (a *animation) PlayBackward() {
|
||||||
a.playMode = playModeBackward
|
a.playMode = playModeBackward
|
||||||
a.lastFrameTime = 0
|
a.lastFrameTime = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pause animation
|
// Pause animation
|
||||||
func (a *Animation) Pause() {
|
func (a *animation) Pause() {
|
||||||
a.playMode = playModePause
|
a.playMode = playModePause
|
||||||
a.lastFrameTime = 0
|
a.lastFrameTime = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPlayLoop sets whether to loop the animation
|
// SetPlayLoop sets whether to loop the animation
|
||||||
func (a *Animation) SetPlayLoop(loop bool) {
|
func (a *animation) SetPlayLoop(loop bool) {
|
||||||
a.playLoop = loop
|
a.playLoop = loop
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPlaySpeed sets play speed of the animation
|
// SetPlaySpeed sets play speed of the animation
|
||||||
func (a *Animation) SetPlaySpeed(playSpeed float64) {
|
func (a *animation) SetPlaySpeed(playSpeed float64) {
|
||||||
a.SetPlayLength(playSpeed * float64(a.GetFrameCount()))
|
a.SetPlayLength(playSpeed * float64(a.GetFrameCount()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPlayLength sets the Animation's play length in seconds
|
// SetPlayLength sets the Animation's play length in seconds
|
||||||
func (a *Animation) SetPlayLength(playLength float64) {
|
func (a *animation) SetPlayLength(playLength float64) {
|
||||||
// TODO refactor to use time.Duration instead of float64
|
// TODO refactor to use time.Duration instead of float64
|
||||||
a.playLength = playLength
|
a.playLength = playLength
|
||||||
a.lastFrameTime = 0
|
a.lastFrameTime = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPlayLengthMs sets the Animation's play length in milliseconds
|
// SetPlayLengthMs sets the Animation's play length in milliseconds
|
||||||
func (a *Animation) SetPlayLengthMs(playLengthMs int) {
|
func (a *animation) SetPlayLengthMs(playLengthMs int) {
|
||||||
// TODO remove this method
|
// TODO remove this method
|
||||||
const millisecondsPerSecond = 1000.0
|
const millisecondsPerSecond = 1000.0
|
||||||
a.SetPlayLength(float64(playLengthMs) / millisecondsPerSecond)
|
a.SetPlayLength(float64(playLengthMs) / millisecondsPerSecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetColorMod sets the Animation's color mod
|
// SetColorMod sets the Animation's color mod
|
||||||
func (a *Animation) SetColorMod(colorMod color.Color) {
|
func (a *animation) SetColorMod(colorMod color.Color) {
|
||||||
a.colorMod = colorMod
|
a.colorMod = colorMod
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPlayedCount gets the number of times the application played
|
// GetPlayedCount gets the number of times the application played
|
||||||
func (a *Animation) GetPlayedCount() int {
|
func (a *animation) GetPlayedCount() int {
|
||||||
return a.playedCount
|
return a.playedCount
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResetPlayedCount resets the play count
|
// ResetPlayedCount resets the play count
|
||||||
func (a *Animation) ResetPlayedCount() {
|
func (a *animation) ResetPlayedCount() {
|
||||||
a.playedCount = 0
|
a.playedCount = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBlend sets the Animation alpha blending status
|
// SetBlend sets the Animation alpha blending status
|
||||||
func (a *Animation) SetBlend(blend bool) {
|
func (a *animation) SetBlend(blend bool) {
|
||||||
if blend {
|
if blend {
|
||||||
a.compositeMode = d2enum.CompositeModeLighter
|
a.compositeMode = d2enum.CompositeModeLighter
|
||||||
} else {
|
} else {
|
||||||
|
@ -39,7 +39,7 @@ func (am *animationManager) LoadAnimation(
|
|||||||
transparency int ) (d2interface.Animation, error) {
|
transparency int ) (d2interface.Animation, error) {
|
||||||
cachePath := fmt.Sprintf("%s;%s;%d", animationPath, palettePath, transparency)
|
cachePath := fmt.Sprintf("%s;%s;%d", animationPath, palettePath, transparency)
|
||||||
if animation, found := am.cache.Retrieve(cachePath); found {
|
if animation, found := am.cache.Retrieve(cachePath); found {
|
||||||
return animation.(*Animation).Clone(), nil
|
return animation.(d2interface.Animation).Clone(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var animation d2interface.Animation
|
var animation d2interface.Animation
|
||||||
@ -47,32 +47,22 @@ func (am *animationManager) LoadAnimation(
|
|||||||
ext := strings.ToLower(filepath.Ext(animationPath))
|
ext := strings.ToLower(filepath.Ext(animationPath))
|
||||||
switch ext {
|
switch ext {
|
||||||
case ".dc6":
|
case ".dc6":
|
||||||
dc6, err := loadDC6(animationPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
palette, err := LoadPalette(palettePath)
|
palette, err := LoadPalette(palettePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
animation, err = CreateAnimationFromDC6(am.renderer, dc6, palette)
|
animation, err = CreateDC6Animation(am.renderer, animationPath, palette)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case ".dcc":
|
case ".dcc":
|
||||||
dcc, err := loadDCC(animationPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
palette, err := LoadPalette(palettePath)
|
palette, err := LoadPalette(palettePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
animation, err = CreateAnimationFromDCC(am.renderer, dcc, palette, transparency)
|
animation, err = CreateDCCAnimation(am.renderer, animationPath, palette, transparency)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package d2asset
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data"
|
||||||
@ -249,6 +250,8 @@ func (c *Composite) createMode(animationMode, weaponClass string) (*compositeMod
|
|||||||
transparency = 192
|
transparency = 192
|
||||||
case d2enum.DrawEffectModulate:
|
case d2enum.DrawEffectModulate:
|
||||||
blend = true
|
blend = true
|
||||||
|
default:
|
||||||
|
log.Println("Unhandled DrawEffect ", cofLayer.DrawEffect, " requested for layer ", cofLayer.Type.String(), " of ", c.token)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
154
d2core/d2asset/dc6_animation.go
Normal file
154
d2core/d2asset/dc6_animation.go
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
package d2asset
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dcc"
|
||||||
|
d2iface "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DC6Animation is an animation made from a DC6 file
|
||||||
|
type DC6Animation struct {
|
||||||
|
animation
|
||||||
|
dc6Path string
|
||||||
|
palette d2iface.Palette
|
||||||
|
renderer d2iface.Renderer
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDC6Animation creates an Animation from d2dc6.DC6 and d2dat.DATPalette
|
||||||
|
func CreateDC6Animation(renderer d2iface.Renderer, dc6Path string,
|
||||||
|
palette d2iface.Palette) (d2iface.Animation, error) {
|
||||||
|
dc6, err := loadDC6(dc6Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
animation := animation{
|
||||||
|
directions: make([]animationDirection, dc6.Directions),
|
||||||
|
playLength: defaultPlayLength,
|
||||||
|
playLoop: true,
|
||||||
|
originAtBottom: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
anim := DC6Animation{
|
||||||
|
animation: animation,
|
||||||
|
dc6Path: dc6Path,
|
||||||
|
palette: palette,
|
||||||
|
renderer: renderer,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = anim.SetDirection(0)
|
||||||
|
|
||||||
|
return &anim, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDirection decodes and sets the direction
|
||||||
|
func (a *DC6Animation) 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 *DC6Animation) decodeDirection(directionIndex int) error {
|
||||||
|
dc6, err := loadDC6(a.dc6Path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
startFrame := directionIndex * int(dc6.FramesPerDirection)
|
||||||
|
|
||||||
|
for i := 0; i < int(dc6.FramesPerDirection); i++ {
|
||||||
|
dc6Frame := dc6.Frames[startFrame+i]
|
||||||
|
|
||||||
|
sfc, err := a.renderer.NewSurface(int(dc6Frame.Width), int(dc6Frame.Height),
|
||||||
|
d2iface.FilterNearest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
indexData := make([]int, dc6Frame.Width*dc6Frame.Height)
|
||||||
|
for i := range indexData {
|
||||||
|
indexData[i] = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
x := 0
|
||||||
|
y := int(dc6Frame.Height) - 1
|
||||||
|
offset := 0
|
||||||
|
|
||||||
|
for {
|
||||||
|
b := int(dc6Frame.FrameData[offset])
|
||||||
|
offset++
|
||||||
|
|
||||||
|
if b == 0x80 {
|
||||||
|
if y == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
y--
|
||||||
|
x = 0
|
||||||
|
} else if b&0x80 > 0 {
|
||||||
|
transparentPixels := b & 0x7f
|
||||||
|
for i := 0; i < transparentPixels; i++ {
|
||||||
|
indexData[x+y*int(dc6Frame.Width)+i] = -1
|
||||||
|
}
|
||||||
|
x += transparentPixels
|
||||||
|
} else {
|
||||||
|
for i := 0; i < b; i++ {
|
||||||
|
indexData[x+y*int(dc6Frame.Width)+i] = int(dc6Frame.FrameData[offset])
|
||||||
|
offset++
|
||||||
|
}
|
||||||
|
x += b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesPerPixel := 4
|
||||||
|
colorData := make([]byte, int(dc6Frame.Width)*int(dc6Frame.Height)*bytesPerPixel)
|
||||||
|
|
||||||
|
for i := 0; i < int(dc6Frame.Width*dc6Frame.Height); i++ {
|
||||||
|
// TODO: Is this == -1 or < 1?
|
||||||
|
if indexData[i] < 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
c := a.palette.GetColors()[indexData[i]]
|
||||||
|
colorData[i*bytesPerPixel] = c.R()
|
||||||
|
colorData[i*bytesPerPixel+1] = c.G()
|
||||||
|
colorData[i*bytesPerPixel+2] = c.B()
|
||||||
|
colorData[i*bytesPerPixel+3] = c.A()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sfc.ReplacePixels(colorData); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.directions[directionIndex].decoded = true
|
||||||
|
a.directions[directionIndex].frames = append(a.directions[directionIndex].frames, &animationFrame{
|
||||||
|
width: int(dc6Frame.Width),
|
||||||
|
height: int(dc6Frame.Height),
|
||||||
|
offsetX: int(dc6Frame.OffsetX),
|
||||||
|
offsetY: int(dc6Frame.OffsetY),
|
||||||
|
image: sfc,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone creates a copy of the animation
|
||||||
|
func (a *DC6Animation) Clone() d2iface.Animation {
|
||||||
|
animation := *a
|
||||||
|
return &animation
|
||||||
|
}
|
141
d2core/d2asset/dcc_animation.go
Normal file
141
d2core/d2asset/dcc_animation.go
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
package d2asset
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"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, d2iface.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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user