2020-02-01 18:55:56 -05:00
|
|
|
package d2asset
|
2019-12-21 20:53:18 -05:00
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
2020-06-30 12:43:13 -04:00
|
|
|
"image"
|
2019-12-21 20:53:18 -05:00
|
|
|
"image/color"
|
|
|
|
|
2020-06-30 09:58:53 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
|
|
|
|
2020-07-05 13:01:44 -04:00
|
|
|
d2iface "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
2020-06-29 00:41:58 -04:00
|
|
|
|
2020-02-01 21:06:22 -05:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
2020-01-31 23:18:11 -05:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dcc"
|
2019-12-21 20:53:18 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
type playMode int
|
|
|
|
|
|
|
|
const (
|
|
|
|
playModePause playMode = iota
|
|
|
|
playModeForward
|
|
|
|
playModeBackward
|
|
|
|
)
|
|
|
|
|
2020-07-01 14:09:18 -04:00
|
|
|
const defaultPlayLength = 1.0
|
|
|
|
|
2019-12-21 20:53:18 -05:00
|
|
|
type animationFrame struct {
|
|
|
|
width int
|
|
|
|
height int
|
|
|
|
offsetX int
|
|
|
|
offsetY int
|
|
|
|
|
2020-07-05 13:01:44 -04:00
|
|
|
image d2iface.Surface
|
2019-12-21 20:53:18 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
type animationDirection struct {
|
2020-07-06 20:13:06 -04:00
|
|
|
decoded bool
|
|
|
|
frames []*animationFrame
|
2019-12-21 20:53:18 -05:00
|
|
|
}
|
|
|
|
|
2020-07-06 20:13:06 -04:00
|
|
|
// animation has directionality, play modes, and frame counting
|
|
|
|
type animation struct {
|
|
|
|
directions []animationDirection
|
2020-07-08 21:57:35 -04:00
|
|
|
effect d2enum.DrawEffect
|
2020-07-05 13:01:44 -04:00
|
|
|
colorMod color.Color
|
|
|
|
frameIndex int
|
|
|
|
directionIndex int
|
|
|
|
lastFrameTime float64
|
|
|
|
playedCount int
|
2020-02-23 03:23:18 -05:00
|
|
|
playMode playMode
|
|
|
|
playLength float64
|
|
|
|
subStartingFrame int
|
|
|
|
subEndingFrame int
|
2020-07-05 13:01:44 -04:00
|
|
|
originAtBottom bool
|
|
|
|
playLoop bool
|
|
|
|
hasSubLoop bool // runs after first animation ends
|
2019-12-21 20:53:18 -05:00
|
|
|
}
|
|
|
|
|
2020-07-05 13:01:44 -04:00
|
|
|
// SetSubLoop sets a sub loop for the animation
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) SetSubLoop(startFrame, endFrame int) {
|
2020-02-23 03:23:18 -05:00
|
|
|
a.subStartingFrame = startFrame
|
2020-07-05 13:01:44 -04:00
|
|
|
a.subEndingFrame = endFrame
|
2020-02-23 03:23:18 -05:00
|
|
|
a.hasSubLoop = true
|
|
|
|
}
|
|
|
|
|
2020-07-05 13:01:44 -04:00
|
|
|
// Advance advances the animation state
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) Advance(elapsed float64) error {
|
2019-12-21 20:53:18 -05:00
|
|
|
if a.playMode == playModePause {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
frameCount := a.GetFrameCount()
|
|
|
|
frameLength := a.playLength / float64(frameCount)
|
|
|
|
a.lastFrameTime += elapsed
|
|
|
|
framesAdvanced := int(a.lastFrameTime / frameLength)
|
|
|
|
a.lastFrameTime -= float64(framesAdvanced) * frameLength
|
|
|
|
|
|
|
|
for i := 0; i < framesAdvanced; i++ {
|
2020-02-23 03:23:18 -05:00
|
|
|
startIndex := 0
|
|
|
|
endIndex := frameCount
|
2020-07-05 13:01:44 -04:00
|
|
|
|
2020-02-23 03:23:18 -05:00
|
|
|
if a.hasSubLoop && a.playedCount > 0 {
|
|
|
|
startIndex = a.subStartingFrame
|
|
|
|
endIndex = a.subEndingFrame
|
|
|
|
}
|
|
|
|
|
2019-12-21 20:53:18 -05:00
|
|
|
switch a.playMode {
|
|
|
|
case playModeForward:
|
|
|
|
a.frameIndex++
|
2020-02-23 03:23:18 -05:00
|
|
|
if a.frameIndex >= endIndex {
|
2019-12-24 01:48:45 -05:00
|
|
|
a.playedCount++
|
2019-12-21 20:53:18 -05:00
|
|
|
if a.playLoop {
|
2020-02-23 03:23:18 -05:00
|
|
|
a.frameIndex = startIndex
|
2019-12-21 20:53:18 -05:00
|
|
|
} else {
|
2020-02-23 03:23:18 -05:00
|
|
|
a.frameIndex = endIndex - 1
|
2019-12-21 20:53:18 -05:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case playModeBackward:
|
|
|
|
a.frameIndex--
|
2020-02-23 03:23:18 -05:00
|
|
|
if a.frameIndex < startIndex {
|
2019-12-24 01:48:45 -05:00
|
|
|
a.playedCount++
|
2019-12-21 20:53:18 -05:00
|
|
|
if a.playLoop {
|
2020-02-23 03:23:18 -05:00
|
|
|
a.frameIndex = endIndex - 1
|
2019-12-21 20:53:18 -05:00
|
|
|
} else {
|
2020-02-23 03:23:18 -05:00
|
|
|
a.frameIndex = startIndex
|
2019-12-21 20:53:18 -05:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-07-05 13:01:44 -04:00
|
|
|
// Render renders the animation to the given surface
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) Render(target d2iface.Surface) error {
|
2019-12-21 20:53:18 -05:00
|
|
|
direction := a.directions[a.directionIndex]
|
|
|
|
frame := direction.frames[a.frameIndex]
|
|
|
|
|
2019-12-28 16:46:08 -05:00
|
|
|
target.PushTranslation(frame.offsetX, frame.offsetY)
|
2020-07-01 14:09:18 -04:00
|
|
|
defer target.Pop()
|
|
|
|
|
2020-07-08 21:57:35 -04:00
|
|
|
target.PushEffect(a.effect)
|
2020-07-01 14:09:18 -04:00
|
|
|
defer target.Pop()
|
|
|
|
|
2019-12-28 16:46:08 -05:00
|
|
|
target.PushColor(a.colorMod)
|
2020-07-01 14:09:18 -04:00
|
|
|
defer target.Pop()
|
|
|
|
|
2019-12-28 16:46:08 -05:00
|
|
|
return target.Render(frame.image)
|
2019-12-21 20:53:18 -05:00
|
|
|
}
|
|
|
|
|
2020-07-05 13:01:44 -04:00
|
|
|
// RenderFromOrigin renders the animation from the animation origin
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) RenderFromOrigin(target d2iface.Surface) error {
|
2020-02-24 23:04:01 -05:00
|
|
|
if a.originAtBottom {
|
|
|
|
direction := a.directions[a.directionIndex]
|
|
|
|
frame := direction.frames[a.frameIndex]
|
|
|
|
target.PushTranslation(0, -frame.height)
|
2020-07-05 13:01:44 -04:00
|
|
|
|
2020-02-24 23:04:01 -05:00
|
|
|
defer target.Pop()
|
|
|
|
}
|
|
|
|
|
|
|
|
return a.Render(target)
|
|
|
|
}
|
|
|
|
|
2020-06-30 12:43:13 -04:00
|
|
|
// RenderSection renders the section of the animation frame enclosed by bounds
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) RenderSection(sfc d2iface.Surface, bound image.Rectangle) error {
|
2020-06-30 12:43:13 -04:00
|
|
|
direction := a.directions[a.directionIndex]
|
|
|
|
frame := direction.frames[a.frameIndex]
|
|
|
|
|
|
|
|
sfc.PushTranslation(frame.offsetX, frame.offsetY)
|
2020-07-08 21:57:35 -04:00
|
|
|
sfc.PushEffect(a.effect)
|
2020-06-30 12:43:13 -04:00
|
|
|
sfc.PushColor(a.colorMod)
|
2020-07-05 13:01:44 -04:00
|
|
|
|
2020-06-30 12:43:13 -04:00
|
|
|
defer sfc.PopN(3)
|
2020-07-05 13:01:44 -04:00
|
|
|
|
2020-06-30 12:43:13 -04:00
|
|
|
return sfc.RenderSection(frame.image, bound)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetFrameSize gets the Size(width, height) of a indexed frame.
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) GetFrameSize(frameIndex int) (width, height int, err error) {
|
2019-12-21 20:53:18 -05:00
|
|
|
direction := a.directions[a.directionIndex]
|
|
|
|
if frameIndex >= len(direction.frames) {
|
|
|
|
return 0, 0, errors.New("invalid frame index")
|
|
|
|
}
|
|
|
|
|
|
|
|
frame := direction.frames[frameIndex]
|
2020-07-05 13:01:44 -04:00
|
|
|
|
2019-12-21 20:53:18 -05:00
|
|
|
return frame.width, frame.height, nil
|
|
|
|
}
|
|
|
|
|
2020-06-30 12:43:13 -04:00
|
|
|
// GetCurrentFrameSize gets the Size(width, height) of the current frame.
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) GetCurrentFrameSize() (width, height int) {
|
2020-07-05 13:01:44 -04:00
|
|
|
width, height, _ = a.GetFrameSize(a.frameIndex)
|
2019-12-21 20:53:18 -05:00
|
|
|
return width, height
|
|
|
|
}
|
|
|
|
|
2020-06-30 12:43:13 -04:00
|
|
|
// GetFrameBounds gets maximum Size(width, height) of all frame.
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) GetFrameBounds() (maxWidth, maxHeight int) {
|
2020-07-05 13:01:44 -04:00
|
|
|
maxWidth, maxHeight = 0, 0
|
2019-12-21 20:53:18 -05:00
|
|
|
|
|
|
|
direction := a.directions[a.directionIndex]
|
|
|
|
for _, frame := range direction.frames {
|
2020-02-01 21:06:22 -05:00
|
|
|
maxWidth = d2common.MaxInt(maxWidth, frame.width)
|
|
|
|
maxHeight = d2common.MaxInt(maxHeight, frame.height)
|
2019-12-21 20:53:18 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return maxWidth, maxHeight
|
|
|
|
}
|
|
|
|
|
2020-06-30 12:43:13 -04:00
|
|
|
// GetCurrentFrame gets index of current frame in animation
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) GetCurrentFrame() int {
|
2019-12-21 20:53:18 -05:00
|
|
|
return a.frameIndex
|
|
|
|
}
|
|
|
|
|
2020-06-30 12:43:13 -04:00
|
|
|
// GetFrameCount gets number of frames in animation
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) GetFrameCount() int {
|
2019-12-21 20:53:18 -05:00
|
|
|
direction := a.directions[a.directionIndex]
|
|
|
|
return len(direction.frames)
|
|
|
|
}
|
|
|
|
|
2020-06-30 12:43:13 -04:00
|
|
|
// IsOnFirstFrame gets if the animation on its first frame
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) IsOnFirstFrame() bool {
|
2019-12-21 20:53:18 -05:00
|
|
|
return a.frameIndex == 0
|
|
|
|
}
|
|
|
|
|
2020-06-30 12:43:13 -04:00
|
|
|
// IsOnLastFrame gets if the animation on its last frame
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) IsOnLastFrame() bool {
|
2019-12-21 20:53:18 -05:00
|
|
|
return a.frameIndex == a.GetFrameCount()-1
|
|
|
|
}
|
|
|
|
|
2020-06-30 12:43:13 -04:00
|
|
|
// GetDirectionCount gets the number of animation direction
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) GetDirectionCount() int {
|
2019-12-21 20:53:18 -05:00
|
|
|
return len(a.directions)
|
|
|
|
}
|
|
|
|
|
2020-06-30 12:43:13 -04:00
|
|
|
// SetDirection places the animation in the direction of an animation
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) SetDirection(directionIndex int) error {
|
2020-07-01 14:09:18 -04:00
|
|
|
const smallestInvalidDirectionIndex = 64
|
|
|
|
if directionIndex >= smallestInvalidDirectionIndex {
|
2019-12-21 20:53:18 -05:00
|
|
|
return errors.New("invalid direction index")
|
|
|
|
}
|
2020-07-01 14:09:18 -04:00
|
|
|
|
2020-02-22 22:33:58 -05:00
|
|
|
a.directionIndex = d2dcc.Dir64ToDcc(directionIndex, len(a.directions))
|
2019-12-21 20:53:18 -05:00
|
|
|
a.frameIndex = 0
|
2020-02-22 22:33:58 -05:00
|
|
|
|
2019-12-21 20:53:18 -05:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-06-30 12:43:13 -04:00
|
|
|
// GetDirection get the current animation direction
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) GetDirection() int {
|
2019-12-21 20:53:18 -05:00
|
|
|
return a.directionIndex
|
|
|
|
}
|
|
|
|
|
2020-06-30 12:43:13 -04:00
|
|
|
// SetCurrentFrame sets animation at a specific frame
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) SetCurrentFrame(frameIndex int) error {
|
2019-12-21 20:53:18 -05:00
|
|
|
if frameIndex >= a.GetFrameCount() {
|
|
|
|
return errors.New("invalid frame index")
|
|
|
|
}
|
|
|
|
|
|
|
|
a.frameIndex = frameIndex
|
|
|
|
a.lastFrameTime = 0
|
2020-07-01 14:09:18 -04:00
|
|
|
|
2019-12-21 20:53:18 -05:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-06-30 12:43:13 -04:00
|
|
|
// Rewind animation to beginning
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) Rewind() {
|
2020-07-03 14:00:56 -04:00
|
|
|
_ = a.SetCurrentFrame(0)
|
2019-12-21 20:53:18 -05:00
|
|
|
}
|
|
|
|
|
2020-06-30 12:43:13 -04:00
|
|
|
// PlayForward plays animation forward
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) PlayForward() {
|
2019-12-21 20:53:18 -05:00
|
|
|
a.playMode = playModeForward
|
|
|
|
a.lastFrameTime = 0
|
|
|
|
}
|
|
|
|
|
2020-06-30 12:43:13 -04:00
|
|
|
// PlayBackward plays animation backward
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) PlayBackward() {
|
2019-12-21 20:53:18 -05:00
|
|
|
a.playMode = playModeBackward
|
|
|
|
a.lastFrameTime = 0
|
|
|
|
}
|
|
|
|
|
2020-06-30 12:43:13 -04:00
|
|
|
// Pause animation
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) Pause() {
|
2019-12-21 20:53:18 -05:00
|
|
|
a.playMode = playModePause
|
|
|
|
a.lastFrameTime = 0
|
|
|
|
}
|
|
|
|
|
2020-06-30 12:43:13 -04:00
|
|
|
// SetPlayLoop sets whether to loop the animation
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) SetPlayLoop(loop bool) {
|
2020-02-01 21:51:49 -05:00
|
|
|
a.playLoop = loop
|
2019-12-21 20:53:18 -05:00
|
|
|
}
|
|
|
|
|
2020-06-30 12:43:13 -04:00
|
|
|
// SetPlaySpeed sets play speed of the animation
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) SetPlaySpeed(playSpeed float64) {
|
2019-12-24 01:48:45 -05:00
|
|
|
a.SetPlayLength(playSpeed * float64(a.GetFrameCount()))
|
|
|
|
}
|
|
|
|
|
2020-07-01 14:09:18 -04:00
|
|
|
// SetPlayLength sets the Animation's play length in seconds
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) SetPlayLength(playLength float64) {
|
2020-07-05 13:01:44 -04:00
|
|
|
// TODO refactor to use time.Duration instead of float64
|
2019-12-21 20:53:18 -05:00
|
|
|
a.playLength = playLength
|
|
|
|
a.lastFrameTime = 0
|
|
|
|
}
|
|
|
|
|
2020-07-01 14:09:18 -04:00
|
|
|
// SetPlayLengthMs sets the Animation's play length in milliseconds
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) SetPlayLengthMs(playLengthMs int) {
|
2020-07-05 13:01:44 -04:00
|
|
|
// TODO remove this method
|
2020-07-01 14:09:18 -04:00
|
|
|
const millisecondsPerSecond = 1000.0
|
2020-07-23 12:56:50 -04:00
|
|
|
|
2020-07-01 14:09:18 -04:00
|
|
|
a.SetPlayLength(float64(playLengthMs) / millisecondsPerSecond)
|
2019-12-21 20:53:18 -05:00
|
|
|
}
|
|
|
|
|
2020-07-01 14:09:18 -04:00
|
|
|
// SetColorMod sets the Animation's color mod
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) SetColorMod(colorMod color.Color) {
|
2020-07-01 14:09:18 -04:00
|
|
|
a.colorMod = colorMod
|
2019-12-21 20:53:18 -05:00
|
|
|
}
|
|
|
|
|
2020-06-30 12:43:13 -04:00
|
|
|
// GetPlayedCount gets the number of times the application played
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) GetPlayedCount() int {
|
2019-12-24 01:48:45 -05:00
|
|
|
return a.playedCount
|
|
|
|
}
|
|
|
|
|
2020-06-30 12:43:13 -04:00
|
|
|
// ResetPlayedCount resets the play count
|
2020-07-06 20:13:06 -04:00
|
|
|
func (a *animation) ResetPlayedCount() {
|
2019-12-24 01:48:45 -05:00
|
|
|
a.playedCount = 0
|
|
|
|
}
|
|
|
|
|
2020-07-08 21:57:35 -04:00
|
|
|
func (a *animation) SetEffect(e d2enum.DrawEffect) {
|
|
|
|
a.effect = e
|
2019-12-21 20:53:18 -05:00
|
|
|
}
|