2018-08-26 07:45:01 -05:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"math"
|
|
|
|
"sync"
|
|
|
|
|
2018-09-03 17:12:02 -05:00
|
|
|
gg "github.com/faiface/pixel-examples/community/amidakuji/glossary"
|
2018-08-26 07:45:01 -05:00
|
|
|
|
|
|
|
"github.com/faiface/pixel"
|
|
|
|
"github.com/faiface/pixel/imdraw"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Path is for animating a path to the prize in a ladder.
|
|
|
|
type Path struct {
|
|
|
|
imd *imdraw.IMDraw // shared variable
|
|
|
|
mutex sync.Mutex // synchronize
|
|
|
|
//
|
|
|
|
roads []pixel.Vec // A list of vectors - each vector for a position where a road starts.
|
|
|
|
prize *int
|
|
|
|
tip *pixel.Vec
|
|
|
|
tipDir pixel.Vec
|
|
|
|
iroad int
|
|
|
|
//
|
|
|
|
watchAnim gg.DtWatch // When it started to animate.
|
|
|
|
timeLimitAnimInSec float64
|
|
|
|
animateInTime bool
|
|
|
|
isAnimating bool
|
|
|
|
|
|
|
|
// -----------------------------------------------------------
|
|
|
|
// exported callbacks(listeners) regarding animation
|
|
|
|
|
|
|
|
// callback on reaching the prize level of a ladder.
|
|
|
|
OnFinishedAnimation func()
|
|
|
|
|
|
|
|
// callback when the animating 'tip' passes a point of a road.
|
|
|
|
// pt: a point(road) just passed.
|
|
|
|
// dir: ...
|
|
|
|
// dir is a normalized vector. (pixel.ZV) is passed if the direction can't be found.
|
|
|
|
// dir can be different depending on how fast this Path is updated.
|
|
|
|
OnPassedEachPoint func(pt pixel.Vec, dir pixel.Vec)
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewPath is a contructor.
|
|
|
|
func NewPath(_roads []pixel.Vec, _prize *int) *Path {
|
|
|
|
newTip := func() *pixel.Vec {
|
|
|
|
if _roads != nil {
|
|
|
|
if len(_roads) > 0 {
|
|
|
|
v := _roads[0]
|
|
|
|
return &v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return &Path{
|
|
|
|
roads: _roads,
|
|
|
|
prize: _prize,
|
|
|
|
tip: newTip(),
|
|
|
|
tipDir: pixel.ZV,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewPathEmpty is a contructor.
|
|
|
|
func NewPathEmpty() *Path {
|
|
|
|
return &Path{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
// Important methods
|
|
|
|
|
|
|
|
// Draw guarantees the thread safety, though it's not a necessary condition.
|
|
|
|
// It is quite dangerous to access this struct's member (imdraw) directly from outside these methods.
|
|
|
|
func (path *Path) Draw(t pixel.Target) {
|
|
|
|
path.mutex.Lock()
|
|
|
|
defer path.mutex.Unlock()
|
|
|
|
|
|
|
|
if path.imd == nil { // isInvisible set to true.
|
|
|
|
return // An empty image is drawn.
|
|
|
|
}
|
|
|
|
|
|
|
|
path.imd.Draw(t)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update animates a path. A path is drawn on an imdraw.
|
|
|
|
func (path *Path) Update(color pixel.RGBA) {
|
|
|
|
var (
|
|
|
|
iroad = len(path.roads) - 1
|
|
|
|
from = path.roads[len(path.roads)-1]
|
|
|
|
to = path.roads[len(path.roads)-1]
|
|
|
|
dir = pixel.ZV
|
|
|
|
)
|
|
|
|
|
|
|
|
if path.isAnimating {
|
|
|
|
// get where it is abstract // get a scalar
|
|
|
|
dt := path.watchAnim.DtSinceStart()
|
|
|
|
const distPerSec = 500
|
|
|
|
scalarProgress := dt * distPerSec
|
|
|
|
// log.Println(dt, path.Len(), scalarProgress) //
|
|
|
|
|
|
|
|
if path.animateInTime { // overwrite scalarProgress
|
|
|
|
fromLot := pixel.V(0, 0)
|
|
|
|
toPrize := pixel.V(path.Len(), 0)
|
|
|
|
percentagePointPerSec := 1 / path.timeLimitAnimInSec
|
|
|
|
scalarProgress = pixel.Lerp(fromLot, toPrize, dt*percentagePointPerSec).X
|
|
|
|
// log.Println(dt, path.Len(), scalarProgress, dt*percentagePointPerSec) //
|
|
|
|
}
|
|
|
|
|
|
|
|
// get where it is concrete // turn a scalar into a set of vectors
|
|
|
|
iroad, from, to, dir = path.FindRoadByDist(scalarProgress)
|
|
|
|
if iroad > path.iroad {
|
|
|
|
if path.OnPassedEachPoint != nil {
|
|
|
|
go path.OnPassedEachPoint(from, dir)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
path.iroad = iroad
|
|
|
|
// log.Println(iroad, len(path.roads), iroad == len(path.roads)-1, path.isAnimating) //
|
|
|
|
if iroad >= len(path.roads)-1 { // the end
|
|
|
|
path.isAnimating = false
|
|
|
|
// log.Println(path.Len(), dt) //
|
|
|
|
if path.OnFinishedAnimation != nil {
|
|
|
|
go path.OnFinishedAnimation()
|
|
|
|
} // callback
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// lock before imdraw update
|
|
|
|
path.mutex.Lock()
|
|
|
|
defer path.mutex.Unlock()
|
|
|
|
|
|
|
|
// imdraw (a state machine)
|
|
|
|
if path.imd == nil { // lazy creation
|
|
|
|
path.imd = imdraw.New(nil)
|
|
|
|
}
|
|
|
|
imd := path.imd
|
|
|
|
imd.Clear()
|
|
|
|
|
|
|
|
// draw path
|
|
|
|
imd.Color = color
|
|
|
|
imd.EndShape = imdraw.RoundEndShape
|
|
|
|
for i := 0; i < iroad; i++ {
|
|
|
|
imd.Push(path.roads[i], path.roads[i+1])
|
|
|
|
imd.Line(9)
|
|
|
|
}
|
|
|
|
imd.Push(from, to)
|
|
|
|
imd.Line(9)
|
|
|
|
|
|
|
|
// save where the tip is
|
|
|
|
path.tip = &to
|
|
|
|
}
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
// Read only methods
|
|
|
|
|
|
|
|
// IsAnimating determines whether this Path is about to be updated or not.
|
|
|
|
// Pass lock by value warning from (path Path) should be ignored,
|
|
|
|
// because a Path here is just passed as a read only argument.
|
|
|
|
func (path Path) IsAnimating() bool {
|
|
|
|
return path.isAnimating
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetPrize is just an average getter.
|
|
|
|
// It returns -1 if the receiver is not initialized with that member(prize).
|
|
|
|
// Pass lock by value warning from (path Path) should be ignored,
|
|
|
|
// because a Path here is just passed as a read only argument.
|
|
|
|
func (path Path) GetPrize() int {
|
|
|
|
if path.prize == nil {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
return *path.prize
|
|
|
|
}
|
|
|
|
|
|
|
|
// PosTip returns a vector that tells you how far the animating path currently has reached.
|
|
|
|
// A non-ptr Path as a read only argument passes lock by value within itself but that seems totally fine.
|
|
|
|
func (path Path) PosTip() (v pixel.Vec) {
|
|
|
|
return *path.tip
|
|
|
|
}
|
|
|
|
|
|
|
|
// Len returns the total length of all roads.
|
|
|
|
// A non-ptr Path as a read only argument passes lock by value within itself but that seems totally fine.
|
|
|
|
func (path Path) Len() (sum float64) {
|
|
|
|
for i := 0; i < len(path.roads)-1; i++ {
|
|
|
|
sum += math.Abs(path.roads[i].Sub(path.roads[i+1]).Len())
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// FindRoadByDist converts a scalar into a set of vectors.
|
|
|
|
// A non-ptr Path as a read only argument passes lock by value within itself but that seems totally fine.
|
|
|
|
//
|
|
|
|
// Returns
|
|
|
|
// iroad: The index of a road found.
|
|
|
|
// road: The vector representation of a road found. A road is a line from pt A to B, and that vector points to where pt A is.
|
|
|
|
// pos: A position(point) found which is in the middle of that found road(line).
|
|
|
|
// dirVecNormalized: A direction as a normalized vector. This vector always has a length of 1.
|
|
|
|
func (path Path) FindRoadByDist(distProgress float64) (iroad int, road pixel.Vec, pos pixel.Vec, dirVecNormalized pixel.Vec) {
|
|
|
|
lengthOfTraveledRoads := float64(0.0)
|
|
|
|
for iroad = 0; iroad < len(path.roads)-1; iroad++ {
|
|
|
|
var lengthOfThisRoad float64
|
|
|
|
iroadNext := iroad + 1
|
|
|
|
lengthOfThisRoad = math.Abs(path.roads[iroad].Sub(path.roads[iroadNext]).Len())
|
|
|
|
lengthOfTraveledRoads += lengthOfThisRoad
|
|
|
|
// For loop breaker: distProgress is somewhere between the total length of a path.
|
|
|
|
if lengthOfTraveledRoads > distProgress {
|
|
|
|
scalar := lengthOfThisRoad - (lengthOfTraveledRoads - distProgress)
|
|
|
|
if path.roads[iroad].Y == path.roads[iroadNext].Y &&
|
|
|
|
path.roads[iroad].X < path.roads[iroadNext].X { // to the bottom (east)
|
|
|
|
pos = path.roads[iroad]
|
|
|
|
pos.X += scalar
|
|
|
|
dirVecNormalized = pixel.V(1, 0)
|
|
|
|
} else if path.roads[iroad].X == path.roads[iroadNext].X &&
|
|
|
|
path.roads[iroad].Y > path.roads[iroadNext].Y { // to the left (south)
|
|
|
|
pos = path.roads[iroad]
|
|
|
|
pos.Y -= scalar
|
|
|
|
dirVecNormalized = pixel.V(0, -1)
|
|
|
|
} else if path.roads[iroad].X == path.roads[iroadNext].X &&
|
|
|
|
path.roads[iroad].Y < path.roads[iroadNext].Y { // to the right (north)
|
|
|
|
pos = path.roads[iroad]
|
|
|
|
pos.Y += scalar
|
|
|
|
dirVecNormalized = pixel.V(0, 1)
|
|
|
|
} else if path.roads[iroad].Y == path.roads[iroadNext].Y &&
|
|
|
|
path.roads[iroad].X > path.roads[iroadNext].X { // to the top (west)
|
|
|
|
// Placed at the end of an elif statement,
|
|
|
|
// since this case is of no possibility unless the path finding is going reverse.
|
|
|
|
pos = path.roads[iroad]
|
|
|
|
pos.X -= scalar
|
|
|
|
dirVecNormalized = pixel.V(-1, 0)
|
|
|
|
} else {
|
|
|
|
panic("unhandled exception: it may be a diagonal bridge")
|
|
|
|
} // elif
|
|
|
|
return iroad, path.roads[iroad], pos, dirVecNormalized
|
|
|
|
} // if - for loop breaker
|
|
|
|
} // for
|
|
|
|
|
|
|
|
// coming down to here means that the case is (road == pos)
|
|
|
|
from := iroad - 1
|
|
|
|
to := iroad
|
|
|
|
if iroad == 0 {
|
|
|
|
from = iroad
|
|
|
|
to = iroad + 1
|
|
|
|
}
|
|
|
|
if from < 0 || to >= len(path.roads) {
|
|
|
|
dirVecNormalized = pixel.ZV
|
|
|
|
} else {
|
|
|
|
dirVecNormalized = gg.Direction(path.roads[from], path.roads[to])
|
|
|
|
}
|
|
|
|
return iroad, path.roads[iroad], path.roads[iroad], dirVecNormalized
|
|
|
|
}
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
// Methods that write to itself
|
|
|
|
|
|
|
|
// Animate a path.
|
|
|
|
func (path *Path) Animate() {
|
|
|
|
path.watchAnim.Start()
|
|
|
|
path.animateInTime = false
|
|
|
|
path.isAnimating = true
|
|
|
|
}
|
|
|
|
|
|
|
|
// AnimateInTime animates a path in given time.
|
|
|
|
func (path *Path) AnimateInTime(sec float64) {
|
|
|
|
path.watchAnim.Start()
|
|
|
|
path.timeLimitAnimInSec = sec
|
|
|
|
path.animateInTime = true
|
|
|
|
path.isAnimating = true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pause a path's clock.
|
|
|
|
func (path *Path) Pause() {
|
|
|
|
if path.watchAnim.IsStarted() {
|
|
|
|
path.watchAnim.Dt()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resume after pause.
|
|
|
|
func (path *Path) Resume() {
|
|
|
|
if path.watchAnim.IsStarted() {
|
|
|
|
started := path.watchAnim.GetTimeStarted()
|
|
|
|
dtPause := path.watchAnim.DtNano()
|
|
|
|
path.watchAnim.SetTimeStarted(started.Add(dtPause))
|
|
|
|
// log.Println(dtPause, started, path.watchAnim.GetTimeStarted()) //
|
|
|
|
}
|
|
|
|
}
|