timeline-2.js

total 0
used 0
limit 0
/* --- title: Timeline (Second Attempt) categories: timeline files: ../point_src/core/head.js ../point_src/pointpen.js ../point_src/pointdraw.js ../point_src/point-content.js ../point_src/easing.js ../point_src/pointlist.js ../point_src/point.js ../point_src/events.js ../point_src/automouse.js ../point_src/distances.js ../point_src/dragging.js ../point_src/functions/clamp.js ../point_src/stage.js ../point_src/iter/lerp.js Run a Timeline, with key events. I wasn't satified with the other attempt. In this version we do better. */ /* + A timeline runs a X.fps, until a length. + It may cycle at the end + keyframe aligned along the timeline for each item:value + lerping between values (linear by default.) + timeline stepping is per frame tick. + because the timeline end is transient, if the timeline loops, the _old_ values from the initial set can _lerp_ into the incoming future values; the new future can be at 0 index. */ const TIMELINE_START = -1 class TimelineBase { constructor(stage, target) { this.stage = stage /* The _point_ if available. */ this.target = target /* The _current_ tick value. A _null_ value is -1 as 0 is the root keyframe.*/ this.ticks = TIMELINE_START /* Value to _add_ per tick. If -1, the timeline will render in reverse. */ this.tickValue = 1 /* Every Tip hit, the bounce is +1.*/ this.bounces = 5 /* The internal bounce counter. */ this._bouncesCount = 0 this.speed = 1 // bounce, loop, stop this.cycle = 'bounce' // this.cycle = 'loop' this.tickFunction = ()=>{} this.keyframeMap = new NestedMap this.setup() } setup() {} perform(ctx) { /* Run _perform_ at every draw, performing a step if enabled */ this.tickFunction.apply(this, arguments) } step() { this.ticks += (this.tickValue * this.speed); } start() { this.running = true console.log('start timeline') this.tickFunction = this.step.bind(this) } stop() { this.tickFunction = ()=>{} this.running = false console.log('stop timeline') } reset(){ this.ticks = TIMELINE_START this.tickValue = 1 this._bouncesCount = 0 } toggleRunning() { if(this.running) { return this.stop() } return this.start() } } class NestedMap extends Map { append(key, value) { if(this.has(key) ){ let v = this.get(key) v.push(value) let l = v.length this.set(key, v) return l } this.set(key, [value]) return 1 } } const bounce = function(timeline) { // console.log('bounce', timeline) if(timeline._bouncesCount >= timeline.bounces) { return } timeline.tickValue *= -1 timeline._bouncesCount += 1 } const loop = function(timeline) { // first or last. let newTick = TIMELINE_START let isRev = timeline.tickValue < 0; if(isRev) { // Go to the end newTick = timeline.getLastIndex() + 1 } timeline.ticks = newTick timeline.lerpers = {} } class Timeline extends TimelineBase { add(index, keyframe) { if(!(keyframe instanceof KeyFrame)) { keyframe = new KeyFrame(keyframe) } this.keyframeMap.append(index, keyframe) keyframe.onTimelineApply(this, index) } insert(many) { for(let index in many) { let kf = many[index] this.add(Number(index), kf) } } setup() { this._doInd = true this.indicatorPoint = new Point(20, 20) this.indicatortickRate = 30 this.lerpers = {} this.defaultEasingFunction = easingFunctions.linear.inOut this.start() } step(ctx) { this.ticks += this.tickValue * this.speed if(this.ticks < 0) { this.ticks = -1 } // lerp existing. // capture ticked. this.actionHits(Math.trunc(this.ticks)) this.updateLerpers(this.ticks) this.ticker(ctx) } getLastIndex(){ /* return this last keyframe index. */ return Math.max(...this.keyframeMap.keys()) } actionHits(index=this.ticks) { // at index fins any keyframes. if(!this.keyframeMap.has(index)) { return } /* work to spool. */ let kfs = this.keyframeMap.get(index) if(kfs == undefined) { // no here. return } let tl = this; // console.log('!Action - at frame', index, kfs) // For each Key, // //set value trhough the given smoothing function /* For all the discovered keyframes*/ let res = {} const forEachPropInKeyFrames = function(kfs, caller) { kfs.forEach(function(kf){ kf.forEach((propertyKey, value)=>{ caller(propertyKey, value, kf) }); }); } /* A list of all integer indexes, from this index forward.*/ let isRev = this.tickValue < 0; let dir = (isRev) ? (v)=>vv>index let futureIndexKeys = Array.from(this.keyframeMap.keys()).filter(dir) if(isRev) { futureIndexKeys.reverse() } // console.log('Looking ahead at timepoints', futureIndexKeys) const getMatchingFutureKeyFrame = function(propertyKey, indexKeys) { /*return keyframes with the same property */ for(let futureIndex of indexKeys) { let futureKfs = tl.keyframeMap.get(futureIndex) for(let fkf of futureKfs) { if(fkf.has(propertyKey)) { // a future key with the same property. // console.log('First Future', futureIndex, propertyKey) return [futureIndex, fkf] } } } } /* -------------------------------------------------------------------*/ forEachPropInKeyFrames(kfs, (propertyKey, value, kf)=>{ let futureResult = getMatchingFutureKeyFrame(propertyKey, futureIndexKeys) if(!futureResult) { // The key has no lerp future. // console.log('No future for', propertyKey) /* Just put the current value on the target.*/ this.target[propertyKey] = value return } let [futureIndex, fkf] = futureResult let toValue = fkf.get(propertyKey) /* Grab the easing function from the _value_ keyFrame = { rotation: withEasing(100, easingFunc) } If in reverse, we capture the easing future in the opposite direction. */ let valueEasingFunction; if(isRev) { if(toValue.reverseEasingFunction){ valueEasingFunction = toValue.reverseEasingFunction // if(valueEasingFunction == REV_REV) { // // valueEasingFunction = value.easingFunction // valueEasingFunction = getMatchingFutureKeyFrame(propertyKey, // futureIndexKeys.slice(1) // )[1].get(propertyKey).easingFunction // } } else { valueEasingFunction = toValue.easingFunction } } else { valueEasingFunction = value.easingFunction } let easer = valueEasingFunction || kf.easingFunction if(easer == undefined) { easer = this.defaultEasingFunction } /* Install the Value, of which is ticked per step().*/ let lerper = new Value(value, toValue, easer, true) if(this.lerpers[propertyKey] !== undefined) { let entry = this.lerpers[propertyKey] let switchly = index == entry.toIndex if(!switchly) { /* This lerper intends to replace another lerper, of which is not ending exactly now. */ // console.warn('Installing existing key', propertyKey, 'ends at:', index, entry.toIndex) } } this.lerpers[propertyKey] = { lerper, fromIndex: index, toIndex: futureIndex, } let s = [ `Found: "${propertyKey}" ` , `from(#${index}, ${value}) => ` , `to(#${futureIndex}, ${toValue})` ] // console.log(s.join('')) /* The property found exists in one of the _current_ keyframes And also exists in the _give_ future key frame. The property value should _lerp_ through the distance of these keyframes. width = futureIndex - index from = the current value to = the future keyframe value (e.g. X) `toValue` */ /* Just put the current value on the target.*/ // this.target[propertyKey] = value }) if(futureIndexKeys.length==0) { console.log('!Last Keframe:', index) /* There are no more future indexes. the timeline should cycle, stop, or bounce */ let cycleMap = { 'bounce': bounce , 'loop': loop } cycleMap[this.cycle](tl) // if(this.cycle == 'bounce') { // return this.bounce() // } // if(this.cycle == 'loop') { // // first or last. // let newTick = -1 // if(isRev) { // // Go to the end // newTick = 300 // } // this.ticks = newTick // } } } updateLerpers(index){ for(let k in this.lerpers) { let entry = this.lerpers[k] let lerper = entry.lerper; let relTicks = index - entry.fromIndex let width = entry.toIndex - entry.fromIndex // let width = lerper.width() let oneBit = 1 / width let along = oneBit * relTicks let newValue = lerper.get(along) this.target[k] = newValue } } ticker(ctx){ let doInd = this._doInd if(this.ticks % this.indicatortickRate == 0) { // ind. color flip this._doInd = !this._doInd } if(doInd) { this.indicatorPoint.pen.fill(ctx, '#880000') } } } class KeyFrame { constructor(values={}, easing='linear'){ this.values = values this.easingFunction = easingFunctions.get(easing) } onTimelineApply(timeline, index) { /* This keyframe was applied to this timeline. */ } set(k, v) { this.values[k] = v } get(k) { return this.values[k] } has(k) { return k in this.values; } forEach(caller) { // return this.values.forEach(caller) let vs = this.values for(let k in vs) { caller(k, vs[k]) } } } class KeyValue extends Number { } const REV_REV = 'rev_rev' class MainStage extends Stage { canvas = 'playspace' mounted(){ this.point = new Point(50, 50, 20) // this.events.wake() this.dragging.add(this.point) this.makeTimeline() this.events.wake() this.clickEvent = false } onMouseUp() { let tl = this.timeline this.clickEvent = !this.clickEvent if(this.clickEvent == false) { console.log('Reseting time') this._runClicker = false this.timeline.speed = 1 } else { console.log('Pausing') this._runClicker = true } } makeTimeline(){ let tl = this.timeline = new Timeline(this, this.point) // If a keyframe is 0, the property is set, not lerp. let kv = 50 const withEasing = function(value, easer, reverseEaser) { let v = new KeyValue(value) if(easer!=undefined) { v.easingFunction = easer } if(reverseEaser!=undefined) { v.reverseEasingFunction = reverseEaser } return v } // kv.easingFunction = easingFunctions.elastic.inOut tl.add(0, new KeyFrame({ x: 20 , y: withEasing(20, easingFunctions.bounce.out, easingFunctions.sine.inOut) // , y: withEasing(20, easingFunctions.bounce.out, easingFunctions.quad.inOut) , radius: 50 })) let kf = new KeyFrame({ x: 100 , rotation: UP_DEG , radius: 100 }) // kf.easingFunction = easingFunctions.elastic.inOut tl.add(60, kf) tl.add(120, { // y: 400 y: withEasing(400, undefined, easingFunctions.bounce.out) , radius: 70 }) tl.add(180, { y: 200 // y: withEasing(200, undefined, easingFunctions.elastic.out) , rotation: 190 }) tl.insert({ 240: { x: 200 , radius: 20 } /*, 300: new KeyFrame({ x: 300 , y: 300 , rotation: 0 , radius: 50 }) , 360: new KeyFrame({ x: 400 , rotation: 0 }) , 400: { rotation: UP_DEG }*/ }) } draw(ctx){ this.clear(ctx) if(this._runClicker == true){ this.timeline.speed *= .92 if(this.timeline.speed < .01) { this._runClicker = false console.log('Stopping clicker') // this.timeline.speed = 1 // this.timeline.reset() // this.timeline.toggleRunning() } } this.timeline.perform(ctx) // this.point.pen.fill(ctx, '#880000') this.point.pen.indicator(ctx, '#880000') } } stage = MainStage.go(/*{ loop: true }*/)
Run
Meta Data
title Timeline (Second Attempt)
imports ()
files ('../point_src/core/head.js', '../point_src/pointpen.js', '../point_src/pointdraw.js', '../point_src/point-content.js', '../point_src/easing.js', '../point_src/pointlist.js', '../point_src/point.js', '../point_src/events.js', '../point_src/automouse.js', '../point_src/distances.js', '../point_src/dragging.js', '../point_src/functions/clamp.js', '../point_src/stage.js', '../point_src/iter/lerp.js')
unused_keys ()
unknown_keys ('categories',)
categories ['timeline']
filepath_exists True
path timeline-2.js
filepath timeline-2.js
clean_files ('../point_src/core/head.js', '../point_src/pointpen.js', '../point_src/pointdraw.js', '../point_src/compass.js', '../point_src/center.js', '../point_src/point-content.js', '../point_src/easing.js', '../point_src/pointlistdraw.js', '../point_src/pointlistgradient.js', '../point_src/pointlistshape.js', '../point_src/pointlistgenerator.js', '../point_src/unpack.js', '../point_src/pointlist.js', '../point_src/relative-xy.js', '../point_src/pointcast.js', '../point_src/point.js', '../point_src/events.js', '../point_src/automouse.js', '../point_src/distances.js', '../point_src/protractor.js', '../point_src/text/beta.js', '../point_src/dragging.js', '../point_src/functions/clamp.js', '../point_src/stage-resize.js', '../point_src/functions/resolve.js', '../point_src/stage.js', '../point_src/iter/lerp.js')
markdown {'html': "<pre><code>Run a Timeline, with key events.\nI wasn't satified with the other attempt. In this version we do better.\n</code></pre>", 'content': "---\ntitle: Timeline (Second Attempt)\ncategories: timeline\nfiles:\n ../point_src/core/head.js\n ../point_src/pointpen.js\n ../point_src/pointdraw.js\n ../point_src/point-content.js\n ../point_src/easing.js\n ../point_src/pointlist.js\n ../point_src/point.js\n ../point_src/events.js\n ../point_src/automouse.js\n ../point_src/distances.js\n ../point_src/dragging.js\n ../point_src/functions/clamp.js\n ../point_src/stage.js\n ../point_src/iter/lerp.js\n\n Run a Timeline, with key events.\n I wasn't satified with the other attempt. In this version we do better."}