// anim.js (C) Walter Bislin, Januar 2005 // // description and download: // http://walter.bislins.ch/doku/animation // include x.js // function Range( aValue, aMin, aMax ) { // expands aValue(0..1) to span from aMin to aMax return aMin + (aMax-aMin)*aValue; } // ------ Globals var MaxFrameRate = 10; // 40 ms (= 25 fps) // ------ CPolygon (implements an Animator) function CPolygon( aCoord, aOffsetX, aOffsetY, aPathModifyFunc ) // aCoord: [[x,y],...] // aPathModifyFunc(aParam) must return f(aParam) so, that aParam transforms to Values 0..1 { if (!xDef(aPathModifyFunc)) aPathModifyFunc = null; if (!xNum(aOffsetX)) aOffsetX = 0; if (!xNum(aOffsetY)) aOffsetY = 0; this.PathX = new Array(); this.PathY = new Array(); this.PathP = new Array(); this.PathModifyFunc = aPathModifyFunc; for (var i = 0; i < aCoord.length; i++) { this.PathX[i] = aCoord[i][0] + aOffsetX; this.PathY[i] = aCoord[i][1] + aOffsetY; } this.PathP[0] = 0.0; for (var i = 1; i < aCoord.length; i++) { var dx = this.PathX[i] - this.PathX[i-1]; var dy = this.PathY[i] - this.PathY[i-1]; this.PathP[i] = this.PathP[i-1] + Math.sqrt( dx * dx + dy * dy ); } var dp = this.PathP[this.PathP.length-1]; if (dp > 0) { for (var i = 1; i < this.PathP.length; i++) { this.PathP[i] /= dp; } } } CPolygon.prototype.FindSegment = function( aParam ) { for (var i = 1; i < this.PathP.length; i++) { if (this.PathP[i] > aParam) return i-1; } return this.PathP.length-1; } CPolygon.prototype.OverrideMove = function( aActor, aTrack, aParam ) { var x, y; if (aParam < 0) aParam = 0; if (aParam > 1) aParam = 1; var pp = (this.PathModifyFunc) ? this.PathModifyFunc(aParam) : aParam; if (pp == 0) { x = this.PathX[0]; y = this.PathY[0]; } else if (pp == 1) { x = this.PathX[this.PathX.length-1]; y = this.PathY[this.PathY.length-1]; } else { var i = this.FindSegment(pp); var ps = (pp - this.PathP[i]) / (this.PathP[i+1] - this.PathP[i]); x = Range( ps, this.PathX[i], this.PathX[i+1] ); y = Range( ps, this.PathY[i], this.PathY[i+1] ); } aActor.MoveTo( x, y ); } // ------ CObjState function CObjState( aData, aOffsetX, aOffsetY ) // CObjState( aData, aOffsetX=0, aOffsetY=aOffsetX ) // aData = [aPosX=0,aPosY=aPosX,aOpacity=-1,aWidth=-1,aHeight=aWidth,aAny=''] { var aPosX = 0, aPosY = 0, aOpacity = -1, aWidth = -1, aHeight = -1, aAny = ''; if (!xDef(aData) || aData == null) aData = []; if (!xNum(aOffsetX)) aOffsetX = 0; if (!xNum(aOffsetY)) aOffsetY = aOffsetX; if (xNum(aData[0])) aPosX = aPosY = aData[0]; if (xNum(aData[1])) aPosY = aData[1]; if (xNum(aData[2])) aOpacity = aData[2]; if (xNum(aData[3])) aWidth = aHeight = aData[3]; if (xNum(aData[4])) aHeight = aData[4]; if (xDef(aData[5])) aAny = aData[5]; this.PosX = aPosX + aOffsetX; this.PosY = aPosY + aOffsetY; this.Opacity = aOpacity; this.Width = aWidth; this.Height = aHeight; this.Any = aAny; } // ------ CFrame function CFrame( aTime ) { this.ID = -1; this.Time = aTime; this.Stop = false; this.StopTime = 0; // 0 -> stop; > 0 -> stop n ms then continue this.NRepeats = 0; this.RepeatFrame = 0; this.RepeatCount = 0; this.UnloadedTracks = new Array(); // for next animation part this.LoadComplete = false; } CFrame.prototype.SetRepeat = function( aNRepeats, aRepeatFrame ) // aNRepeats = -1 -> for ever { this.NRepeats = aNRepeats; this.RepeatFrame = aRepeatFrame; this.RepeatCount = 0; } CFrame.prototype.SetStop = function( aStopB, aStopTime ) { this.Stop = aStopB; this.StopTime = aStopTime; } CFrame.prototype.FindUnloadedTrack = function( aTrack ) { for (var i = 0; i < this.UnloadedTracks.length; i++) { if (this.UnloadedTracks[i] == aTrack) return i; } return -1; } CFrame.prototype.AddUnloadedTrack = function( aTrack ) { var i = this.FindUnloadedTrack( aTrack ); if (i == -1) { this.UnloadedTracks[this.UnloadedTracks.length] = aTrack; } } CFrame.prototype.ClearUnloadedTracks = function() { this.UnloadedTracks = new Array(); } // ------ CActor function CActor( aAnim, aDivID, aPathModifyFunc, aObjAnimator ) // CActor( aAnim, aDivID, aPathModifyFunc=null, aObjAnimator=null ) { if (!xDef(aPathModifyFunc)) aPathModifyFunc = null; if (!xDef(aObjAnimator)) aObjAnimator = null; this.Anim = aAnim; this.ID = aDivID; this.DocObj = xGet( aDivID ); this.ObjAnimator = aObjAnimator; this.CurrAnimator = null; this.PathModifyFunc = aPathModifyFunc; this.Visible = false; this.PendingVisi = false; this.OpacityUsed = false; } CActor.prototype.Free = function() { this.Anim = null; this.ObjAnimator = null; this.CurrAnimator = null; } CActor.prototype.Show = function() { if (!this.Visible) { xShow( this.DocObj ); this.Visible = true; if (this.CurrAnimator && xDef(this.CurrAnimator.OnShow)) this.CurrAnimator.OnShow(this); } } CActor.prototype.Hide = function() { if (this.Visible) { xHide( this.DocObj ); this.Visible = false; if (this.CurrAnimator && xDef(this.CurrAnimator.OnHide)) this.CurrAnimator.OnHide(this); } } CActor.prototype.SetVisi = function( aVisi ) // defer visibility change until UpdateVisi is called for optimization { this.PendingVisi = aVisi; } CActor.prototype.UpdateVisi = function() { if (this.PendingVisi != this.Visible) { if (this.PendingVisi) { this.Show(); } else { this.Hide(); } } } CActor.prototype.MoveTo = function( x, y ) { xMoveTo( this.DocObj, x, y ); if (this.CurrAnimator && xDef(this.CurrAnimator.OnMove)) this.CurrAnimator.OnMove( this, x, y ); } CActor.prototype.ResizeTo = function( w, h ) { xResizeTo( this.DocObj, w, h ); if (this.CurrAnimator && xDef(this.CurrAnimator.OnResize)) this.CurrAnimator.OnResize( this, w, h ); } CActor.prototype.SetOpacity = function( o ) { xOpacity( this.DocObj, o ); this.OpacityUsed = (o != 1); } // ------ CTrack function CTrack( aAnim, aDivID, aStartFrameIx, aEndFrameIx, aStartState, aEndState, aPathModifyFunc, aObjAnimator ) { this.Anim = aAnim; this.Actor = aAnim.AddActor( aDivID ); if (!xDef(aObjAnimator) || aObjAnimator == null) aObjAnimator = this.Actor.ObjAnimator; if (!xDef(aPathModifyFunc) || aPathModifyFunc == null) aPathModifyFunc = this.Actor.PathModifyFunc; this.ObjAnimator = aObjAnimator; this.StartFrameIx = aStartFrameIx; this.EndFrameIx = aEndFrameIx; this.StartState = aStartState; this.EndState = aEndState; this.StartTime = aAnim.Frames[aStartFrameIx].Time; this.EndTime = aAnim.Frames[aEndFrameIx].Time; this.DeltaTime = this.EndTime - this.StartTime; this.PathModifyFunc = aPathModifyFunc; } CTrack.prototype.Free = function() { this.Anim = null; this.Actor = null; this.ObjAnimator = null; } CTrack.prototype.InTime = function( aTime ) { if (aTime >= this.StartTime && aTime < this.EndTime) return true; return (aTime == this.EndTime && this.EndFrameIx == this.Anim.NFrames-1); } CTrack.prototype.CompParam = function( aTime ) { var p = (aTime - this.StartTime) / this.DeltaTime; if (this.PathModifyFunc) p = this.PathModifyFunc(p); return p; } CTrack.prototype.InFrame = function( aFrameIx ) { if (aFrameIx >= this.StartFrameIx && aFrameIx < this.EndFrameIx) return true; return (aFrameIx == this.EndFrameIx && aFrameIx == this.Anim.NFrames-1); } CTrack.prototype.GetCurrAnimator = function() { var animator = this.ObjAnimator; if (!animator) animator = this.Actor.ObjAnimator; return animator; } CTrack.prototype.OnFrame = function( ) { var a = this.GetCurrAnimator(); if (a && xDef(a.OnFrame)) a.OnFrame( this.Actor, this ); } CTrack.prototype.Update = function( aTime, aFrameChangedB ) { if (this.InTime(aTime)) { var param = this.CompParam(aTime); var actor = this.Actor; var a = this.GetCurrAnimator(); actor.CurrAnimator = a; if (aFrameChangedB) { if (a && xDef(a.OnFrame)) a.OnFrame( actor, this ); } if (a && xDef(a.OverrideUpdate)) { a.OverrideUpdate( actor, aTrack, param ); } else { this.ActorMove( param ); this.ActorResize( param ); this.ActorOpacity( param ); if (a && xDef(a.OnUpdate)) a.OnUpdate( actor, aTrack, param ); } actor.SetVisi(true); } } CTrack.prototype.ActorMove = function( aParam ) { var actor = this.Actor; var a = this.GetCurrAnimator(); if (a && xDef(a.OverrideMove)) { a.OverrideMove( actor, this, aParam ); } else { var currX = Range( aParam, this.StartState.PosX, this.EndState.PosX ); var currY = Range( aParam, this.StartState.PosY, this.EndState.PosY ); actor.MoveTo( currX, currY ); } } CTrack.prototype.ActorOpacity = function( aParam ) { var actor = this.Actor; var a = this.GetCurrAnimator(); if (a && xDef(a.OverrideOpacity)) { a.OverrideOpacity( actor, this, aParam ); } else { if (this.StartState.Opacity >= 0) { var currO = Range( aParam, this.StartState.Opacity, this.EndState.Opacity ); actor.SetOpacity( currO ); } else if (actor.OpacityUsed) { actor.SetOpacity( 1 ); } } } CTrack.prototype.ActorResize = function( aParam ) { var actor = this.Actor; var a = this.GetCurrAnimator(); if (a && xDef(a.OverrideResize)) { a.OverrideResize( actor, this, aParam ); } else { if (this.StartState.Width >= 0) { var currW = Range( aParam, this.StartState.Width, this.EndState.Width ); var currH = Range( aParam, this.StartState.Height, this.EndState.Height ); actor.ResizeTo( currW, currH ); } } } CTrack.prototype.IsLoaded = function() { var a = this.GetCurrAnimator(); if (a && xDef(a.IsLoaded)) { return a.IsLoaded( this.Actor, this ) } return true; } CTrack.prototype.Unload = function() { var a = this.GetCurrAnimator(); if (a && xDef(a.Unload)) { a.Unload( this ); } } CTrack.prototype.Load = function() { var a = this.GetCurrAnimator(); if (a && xDef(a.Load)) { var me = this; a.Load( function(){ me.Anim.OnTrackLoad(me); }, this ); } } CTrack.prototype.Preload = function() { var a = this.GetCurrAnimator(); if (a) { var me = this; if (xDef(a.Preload)) { a.Preload( function(){ me.Anim.OnTrackLoad(me); }, this ); } else if (xDef(a.Load)) { a.Load( function(){ me.Anim.OnTrackLoad(me); }, this ); } } } // ---- CAnim function CAnim( aTimeArray ) // aTimeArray: rel Times between two frames in ms { this.Init(); if (xDef(aTimeArray)) this.AddFrames( aTimeArray ); var me = this; xOnUnload( function() { me.Unload(); } ); } CAnim.prototype.Init = function() { // public this.TimerInterval = MaxFrameRate; this.OffsetX = 0; this.OffsetY = 0; this.FrameOffset = 0; this.AutoPlay = false; this.AutoPlayTime = 1000; this.Loop = false; this.PathModifyFunc = null; this.PathModifyForAll = false; // private this.Frames = new Array(); this.Frames[0] = new CFrame( 0 ); this.Frames[0].ID = 0; this.Frames[0].Stop = true; this.NFrames = 1; this.Tracks = new Array(); this.NTracks = 0; this.Actors = new Array(); this.NActors = 0; this.StartTimeClock = 0; this.CurrTime = 0; this.CurrFrameIx = 0; this.LastFrameIx = -1; this.CurrFrameID = 1; this.Timer = null; this.PauseTimer = null; this.Running = false; this.Loaded = false; // callbacks this.OnStop = new xCallbackChain(); this.OnPlay = new xCallbackChain(); this.OnFrame = new xCallbackChain(); this.OnUpdate = new xCallbackChain(); this.OnPartLoaded = new xCallbackChain(); this.OnReadyState = new xCallbackChain(); this.ReadyState = -1; } CAnim.prototype.GetCurrFrame = function() { return this.CurrFrameIx; } CAnim.prototype.GetCurrFrameID = function() { return this.Frames[this.CurrFrameIx].ID; } CAnim.prototype.GetCurrTime = function() { return this.CurrTime; } CAnim.prototype.GetLastFrame = function() { return this.NFrames-1; } CAnim.prototype.GetLastFrameID = function() { return this.Frames[this.NFrames-1].ID; } CAnim.prototype.UsePathModifyFunc = function( aPathModifyFunc, aForAllTracksB ) { if (!xDef(aForAllTracksB)) aForAllTracksB = false; this.PathModifyFunc = aPathModifyFunc; this.PathModifyForAll = aForAllTracksB; } CAnim.prototype.AddOnReadyState = function( aOnReadyStateFunc ) // aOnReadyStateFunc( aReadyB ) { this.OnReadyState.Add( aOnReadyStateFunc ); } CAnim.prototype.AddOnStop = function( aOnStopFunc ) { this.OnStop.Add(aOnStopFunc); } CAnim.prototype.AddOnPlay = function( aOnPlayFunc ) { this.OnPlay.Add(aOnPlayFunc); } CAnim.prototype.AddOnFrame = function( aOnFrameFunc ) // aOnFrameFunc( aFrameIx ) { this.OnFrame.Add(aOnFrameFunc); } CAnim.prototype.AddOnUpdate = function( aOnUpdateFunc ) // aOnUpdateFunc( aCurrTime ) { this.OnUpdate.Add(aOnUpdateFunc); } CAnim.prototype.AddOnPartLoaded = function( aOnPartLoadedFunc, aObj ) // aOnPartLoadedFunc( aStopFrameID ) { this.OnPartLoaded.Add(aOnPartLoadedFunc); } CAnim.prototype.AddFrames = function( aTimeArray, aResetOffsetB ) // AddFrames( aTimeArray, aResetOffsetB=true ) // aTimeArray: rel Times between two frames in ms { if (!xDef(aResetOffsetB)) aResetOffsetB = true; if (aResetOffsetB) this.FrameOffset = this.NFrames-1; var absTime = this.Frames[this.NFrames-1].Time; for (var i = 0; i < aTimeArray.length; i++) { absTime += aTimeArray[i]; this.Frames[this.Frames.length] = new CFrame( absTime ); } this.NFrames = this.Frames.length; this.EndTime = this.Frames[this.NFrames-1].Time; var saveFrameOffset = this.FrameOffset; this.FrameOffset = 0; this.SetFrameStop( this.NFrames-1 ); this.FrameOffset = saveFrameOffset; } CAnim.prototype.AddTrack = function( aDivID, aStartFrameIx, aNFrames, aStartState, aEndState, a1, a2 ) // AddTrack( aDivID, aStartFrameIx, aNFrames, aStartState=[], aEndState=aStartState, a1=null, a2=null ) // aStartState, aEndState: [aPosX=0,aPosY=aPosX,aOpacity=-1,aWidth=-1,aHeight=aWidth,aAny=''] // a1, a2: null | aPathModifyFunc | aAnimator { var aAnimator = null, aPathModifyFunc = this.PathModifyFunc; if (xDef(a1)) { if (xFunc(a1)) aPathModifyFunc = a1; else aAnimator = a1; } if (xDef(a2)) { if (xFunc(a2)) aPathModifyFunc = a2; else aAnimator = a2; } var aEndFrameIx; aStartFrameIx += this.FrameOffset; if (aStartFrameIx < 0 || aStartFrameIx >= this.NFrames-1) { alert( 'CAnim.AddTrack: nonexistent Frame '+aStartFrameIx ); return; } aEndFrameIx = aStartFrameIx + aNFrames; if (aNFrames == 0) aEndFrameIx = this.NFrames-1; if (aEndFrameIx >= this.NFrames) aEndFrameIx = this.NFrames-1; if (!xDef(aStartState) || aStartState == null) { aStartState = new CObjState( [], this.OffsetX, this.OffsetY ); } else { aStartState = new CObjState( aStartState, this.OffsetX, this.OffsetY ); } if (!xDef(aEndState) || aEndState == null) { aEndState = aStartState; } else { aEndState = new CObjState( aEndState, this.OffsetX, this.OffsetY ); } var trk = new CTrack( this, aDivID, aStartFrameIx, aEndFrameIx, aStartState, aEndState, aPathModifyFunc, aAnimator ); this.Tracks[this.NTracks++] = trk; if (!this.PathModifyForAll) this.PathModifyFunc = null; } CAnim.prototype.AddTracks = function( aDivID, aStartFrameIx ) // AddTracks( aDivID, aStartFrameIx, [...], <n>, [...], <n>, ... ) { var a = this.AddTracks.arguments, startState, endState; if (a.length == 3 || !xDef(a[3])) { this.AddTrack( aDivID, aStartFrameIx, 0, a[2] ); return; } var startFrameIx, endFrameIx; startFrameIx = aStartFrameIx + this.FrameOffset; if (startFrameIx < 0 || startFrameIx >= this.NFrames-1) { alert( 'CAnim.AddTracks(\''+aDivID+'\'): nonexistent Frame '+startFrameIx ); return; } var commonPathModifyFunc = null; var pathModifyFunc = this.PathModifyFunc; var i = 2; if (xFunc(a[i])) { pathModifyFunc = commonPathModifyFunc = a[i]; i++; if (i >= a.length) return; } startState = new CObjState( a[i], this.OffsetX, this.OffsetY ); i++; while (i < a.length && xDef(a[i])) { endFrameIx = startFrameIx + 1; while (i < a.length && (xNum(a[i]) || xFunc(a[i]))) { if (xNum(a[i])) { if (a[i] == 0) { endFrameIx = this.NFrames-1; } else { endFrameIx = startFrameIx + a[i]; } } else if (xFunc(a[i])) { pathModifyFunc = a[i]; } i++; } if (endFrameIx >= this.NFrames) endFrameIx = this.NFrames-1; if (startFrameIx == endFrameIx) { alert('CAnim.AddTracks(\''+aDivID+'\'): tracks exceeds end of animation');return; } if (i < a.length && xDef(a[i])) { if (a[i] == null) { endState = startState; } else { endState = new CObjState( a[i], this.OffsetX, this.OffsetY ); } i++; } else { endState = startState; } var trk = new CTrack( this, aDivID, startFrameIx, endFrameIx, startState, endState, pathModifyFunc ); pathModifyFunc = commonPathModifyFunc || this.PathModifyFunc; this.Tracks[this.NTracks++] = trk; startFrameIx = endFrameIx; startState = endState; } if (!this.PathModifyForAll) this.PathModifyFunc = null; } CAnim.prototype.SetFrameStop = function( aFrameIx, aStopTime, aStopB ) // SetFrameStop( aFrameIx, aStopTime=0, aStopB=true ) { aFrameIx += this.FrameOffset; if (aFrameIx < 0 || aFrameIx >= this.NFrames) return; if (!xNum(aStopTime)) aStopTime = 0; if (!xDef(aStopB)) aStopB = true; this.Frames[aFrameIx].SetStop( aStopB, aStopTime ); if (aStopTime == 0) this.Frames[aFrameIx].ID = this.CurrFrameID++; } CAnim.prototype.SetFrameRepeat = function( aFrameIx, aNRepeats, aRepeatFrame ) // SetFrameRepeat( aFrameIx, aNRepeats=1, aRepeatFrame=-1 ) // aRepeatFrame < 0: -> n Frames back // aRepeatFrame >= 0: -> to that absolute FrameIx { aFrameIx += this.FrameOffset; if (!xNum(aNRepeats)) aNRepeats = 1; if (!xNum(aRepeatFrame)) aRepeatFrame = -1; if (aRepeatFrame < 0) aRepeatFrame = aFrameIx + aRepeatFrame; if (aFrameIx < 0 || aFrameIx >= this.NFrames) { alert('CAnim.SetFrameRepeat: nonexistent Frame '+aFrameIx); return; } this.Frames[aFrameIx].SetRepeat( aNRepeats, aRepeatFrame ); } CAnim.prototype.CheckReadyState = function() { var stopFrameIx = this.GetCurrStopFrameIx(this.CurrFrameIx); var loaded = (this.IsPartLoaded(this.Frames[stopFrameIx].ID)); var lastState = (this.ReadyState == -1) ? !loaded : ((this.ReadyState == 0) ? false : true); if (lastState != loaded) { this.OnReadyState.Call(loaded); } return loaded; } CAnim.prototype.Reset = function() { if (!this.Loaded) return; this.JumpToStopFrame( 0 ); this.ClearRepeats(); } CAnim.prototype.Stop = function() { if (!this.Loaded) return; if (this.Timer) clearInterval( this.Timer ); if (this.PauseTimer) clearTimeout( this.PauseTimer ); this.Timer = this.PauseTimer = null; this.Running = false; this.OnStop.Call(); } CAnim.prototype.Play = function() { if (!this.Loaded) return; if (this.Running) return; if (this.CheckReadyState()) { this.DoPlay(); } } CAnim.prototype.DoPlay = function() { if (this.Running) return; if (this.PauseTimer) clearTimeout( this.PauseTimer ); this.PauseTimer = null; if (this.CurrTime >= this.EndTime) { this.JumpToFrame(0); if (this.AutoPlay) { // closure -> http://walter.bislins.ch/lexi/closure.html var me = this; this.PauseTimer = setTimeout( function(){ me.Play(); }, this.AutoPlayTime ); } } else { // closure -> http://walter.bislins.ch/lexi/closure.html var me = this; this.StartTimeClock = xTimeMS() - this.CurrTime; this.Timer = setInterval( function(){ me.OnTimer(); }, this.TimerInterval ); this.Running = true; } if (this.GetState() != 0) { this.OnPlay.Call(); } } CAnim.prototype.PlayOrStop = function() { if (!this.Loaded) return; if (this.GetState() != 0) this.Stop(); else this.Play(); } CAnim.prototype.GetState = function() // return 0: stop, 1: run, 2: pause { if (this.Running) return 1; if (this.PauseTimer != null) return 2; return 0; } CAnim.prototype.JumpToFrame = function( aFrameIx ) { if (!this.Loaded) return; this.Stop(); if (aFrameIx < 0) aFrameIx = 0; if (aFrameIx >= this.NFrames) aFrameIx = this.NFrames-1; this.CurrTime = this.Frames[aFrameIx].Time; this.CurrFrameIx = aFrameIx; this.UpdateObjects(); this.OnFrame.Call(aFrameIx); this.CheckReadyState(); } CAnim.prototype.GetNextStopFrameIx = function( aFrameIx ) { for (var i = aFrameIx+1; i < this.NFrames; i++) { if (this.Frames[i].ID >= 0) return i; } return this.NFrames-1; } CAnim.prototype.GetPrevStopFrameIx = function( aFrameIx ) { for (var i = aFrameIx-1; i >= 0; i--) { if (this.Frames[i].ID >= 0) return i; } return 0; } CAnim.prototype.GetCurrStopFrameIx = function( aFrameIx ) { for (var i = aFrameIx; i >= 0; i--) { if (this.Frames[i].ID >= 0) return i; } return 0; } CAnim.prototype.JumpToStopFrame = function( aFrameID ) { if (!this.Loaded) return; var frameIx = this.FindStopFrameIx(aFrameID); if (frameIx == -1) frameIx = this.NFrames-1; this.JumpToFrame( frameIx ); this.LoadPart( this.Frames[frameIx].ID ); } CAnim.prototype.JumpNext = function() { this.JumpNextStopFrame(); } CAnim.prototype.JumpPrev = function() { this.JumpPrevStopFrame(); } CAnim.prototype.JumpNextStopFrame = function() { if (!this.Loaded) return; var frameIx = this.GetNextStopFrameIx(this.CurrFrameIx); this.JumpToStopFrame( this.Frames[frameIx].ID ); } CAnim.prototype.JumpPrevStopFrame = function() { if (!this.Loaded) return; var m = (this.CurrTime == this.Frames[this.CurrFrameIx].Time) ? 0 : 1; var frameIx = this.GetPrevStopFrameIx( this.CurrFrameIx+m); this.JumpToStopFrame( this.Frames[frameIx].ID ); } CAnim.prototype.FindActor = function( aID ) { for (var i = 0; i < this.NActors; i++) if (this.Actors[i].ID == aID) return i; return -1; } CAnim.prototype.AddActor = function( aDivID, a1, a2 ) // returns new or existing CActor // aAnimator [a1 or a2] can define the following functions: // aAnimator.Preload( aCallbackFunc, aTrack ) -> called to preload animator. Load() is called instead, if Preloed is not defined // aAnimator.Load( aCallbackFunc, aTrack ) -> called to load animator // aCallbackFunc( aTrack ) -> call this, if load complete // aAnimator.IsLoaded(aActor,aTrack) -> called to check load state // aAnimotor.Unload( aTrack ) -> called on unload to free some circular object references (gabage collection) // aAnimator.OverrideUpdate(aActor,aTrack,aParam) // aAnimator.OverrideMove(aActor,aTrack,aParam) // aAnimator.OverrideResize(aActor,aTrack,aParam) // aAnimator.OnShow(aActor) -> called when div of aAnimator becomes visible // aAnimator.OnHide(aActor) -> called when div of aAnimator becomes invisible // aAnimator.OnMove(aActor,x,y) -> called when div of aAnimator is moved // aAnimator.OnResize(aActor,w,h) -> called when div of aAnimator is resized // aAnimator.OnFrame(aActor,aTrack) -> called when frame changed // aAnimator.OnUpdate(aActor,aTrack,aParam) -> called on each timer event of active track { var aAnimator = null, aPathModifyFunc = null; var actor, actorIx = this.FindActor(aDivID); if (xDef(a1)) { if (xFunc(a1)) aPathModifyFunc = a1; else aAnimator = a1; } if (xDef(a2)) { if (xFunc(a2)) aPathModifyFunc = a2; else aAnimator = a2; } if (actorIx == -1) { actor = new CActor( this, aDivID, aPathModifyFunc, aAnimator ); this.Actors[this.NActors++] = actor; } else { actor = this.Actors[actorIx]; } return actor; } CAnim.prototype.UpdateObjects = function() { if (this.CurrFrameIx != this.LastFrameIx) { for (var i = 0; i < this.NActors; i++) this.Actors[i].SetVisi(false); } for (var i = 0; i < this.NTracks; i++) { this.Tracks[i].Update( this.CurrTime, (this.CurrFrameIx != this.LastFrameIx) ); } if (this.CurrFrameIx != this.LastFrameIx) { this.LastFrameIx = this.CurrFrameIx; for (var i = 0; i < this.NActors; i++) this.Actors[i].UpdateVisi(); } this.OnUpdate.Call(this.CurrTime); } CAnim.prototype.ClearRepeats = function() { for (var i = 0; i < this.NFrames; i++) this.Frames[i].RepeatCount = 0; } CAnim.prototype.OnTimer = function() { this.CurrTime = xTimeMS() - this.StartTimeClock; var nextFrameIx = this.CurrFrameIx+1; while (nextFrameIx < this.NFrames) { var nextFrame = this.Frames[nextFrameIx]; if (this.CurrTime < nextFrame.Time) { this.UpdateObjects(); return; } if (nextFrame.NRepeats != 0) { if (nextFrame.NRepeats == -1 || nextFrame.RepeatCount < nextFrame.NRepeats) { nextFrame.RepeatCount++; this.JumpToFrame( nextFrame.RepeatFrame ); this.DoPlay(); return; } else { nextFrame.RepeatCount = 0; } } if (nextFrame.Stop || nextFrameIx == this.NFrames-1) { this.JumpToFrame( nextFrameIx ); if (nextFrame.StopTime > 0) { // closure -> http://walter.bislins.ch/lexi/closure.html var me = this; this.PauseTimer = setTimeout( function(){ me.DoPlay(); }, nextFrame.StopTime ); this.OnPlay.Call(); return; } if (this.AutoPlay) { if (nextFrameIx < this.NFrames-1 || this.Loop) { // closure -> http://walter.bislins.ch/lexi/closure.html var me = this; this.PauseTimer = setTimeout( function(){ me.Play(); }, this.AutoPlayTime ); this.OnPlay.Call(); return; } } return; } this.CurrFrameIx = nextFrameIx; this.OnFrame.Call(this.CurrFrameIx); nextFrameIx++; } this.JumpToFrame( this.NFrames-1 ); // fallback } CAnim.prototype.CollectUnloadedTracksInPart = function( aFrameIx ) { var lastFrameIx = this.GetNextStopFrameIx(aFrameIx); if (this.Frames[aFrameIx].ID == -1) aFrameIx = this.GetPrevStopFrameIx(aFrameIx); if (this.Frames[aFrameIx].LoadComplete) return 0; this.Frames[aFrameIx].ClearUnloadedTracks(); for (var i = aFrameIx; i <= lastFrameIx; i++) { this.CollectUnloadedTracksInFrame( i, aFrameIx ); } return this.Frames[aFrameIx].UnloadedTracks.length; } CAnim.prototype.CollectUnloadedTracksInFrame = function( aFrameIx, aStopFrameIx ) { if (aFrameIx < 0 || aFrameIx >= this.NFrames) return; for (var i = 0; i < this.NTracks; i++) { var trk = this.Tracks[i]; if (trk.InFrame(aFrameIx)) { if (!trk.IsLoaded()) this.Frames[aStopFrameIx].AddUnloadedTrack(trk); } } return; } CAnim.prototype.OnTrackLoad = function( aTrack ) { aTrack.Update( this.CurrTime, true ); this.CollectUnloadedTracks(false); this.CheckReadyState(); } CAnim.prototype.OnLoadComplete = function( aStopFrameID ) { this.OnPartLoaded.Call(aStopFrameID); } CAnim.prototype.CollectUnloadedTracks = function(aInitB) { for (var i = 0; i < this.NFrames; i++) { var frame = this.Frames[i]; if (frame.ID >= 0) { if (aInitB) frame.LoadComplete = false; if (!frame.LoadComplete && this.CollectUnloadedTracksInPart(i) == 0) { frame.LoadComplete = true; if (!aInitB) this.OnLoadComplete(frame.ID); } } } } CAnim.prototype.Load = function() { this.CurrTime = 0; this.CurrFrameIx = 0; this.Stop(); this.CollectUnloadedTracks(true); this.UpdateObjects(); // save to call play() here this.OnFrame.Call(this.CurrFrameIx); this.CheckReadyState(); for (var i = 0; i < this.NTracks; i++) { this.Tracks[i].Preload(); } this.LoadPart( 0 ); this.Loaded = true; } CAnim.prototype.LoadPart = function( aStopFrameID ) { var thisStopFrameIx = this.FindStopFrameIx(aStopFrameID); if (thisStopFrameIx == -1) return; var nextStopFrameIx = this.GetNextStopFrameIx( thisStopFrameIx ); for (var ti = 0; ti < this.NTracks; ti++) { for (var i = thisStopFrameIx; i <= nextStopFrameIx; i++) { if (this.Tracks[ti].InFrame(i)) this.Tracks[ti].Load(); } } this.CollectUnloadedTracks(false); } CAnim.prototype.FindStopFrameIx = function( aStopFrameID ) { for (var i = 0; i < this.NFrames; i++) if (this.Frames[i].ID == aStopFrameID) return i; return -1; } CAnim.prototype.GetNUnloadedOfPart = function( aStopFrameID ) { var thisStopFrameIx = this.FindStopFrameIx(aStopFrameID); if (thisStopFrameIx == -1) return 0; var nUnloaded = this.Frames[thisStopFrameIx].UnloadedTracks.length; return nUnloaded; } CAnim.prototype.IsPartLoaded = function( aStopFrameID ) { return this.GetNUnloadedOfPart(aStopFrameID) == 0; } CAnim.prototype.Unload = function() { if (!this.Loaded) return; for (var i = 0; i < this.NFrames; i++) this.Frames[i].ClearUnloadedTracks(); for (var i = 0; i < this.NTracks; i++) this.Tracks[i].Unload(); for (var i = 0; i < this.NTracks; i++) this.Tracks[i].Free(); for (var i = 0; i < this.NActors; i++) this.Actors[i].Free(); this.Init(); }