timeline-example.js

total 0
used 0
limit 0
/* --- title: Timeline Example 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. */ /* Note: I'm unhappy with this. It's too clunky and complicated with a bug limiting the animation steps --- A timeline should have keyframes - tl = new Timeline() tl.add(100, KeyFrame()) The keyframe has a lerpy value steped by the timeline stepper. tl.play() functionally, any key value should be manipulatable. a keyframe is a function executor. The timeline has steps. at ~60fps. The timeline manages time stepping, frame slotting (ordering), and a intermediate values cache The keyframe is dedicated to a Point, and its _frame_ values, and a curve method. The timeline will meet a keyframe and initiate the key values, set those values on the respective point and step, resetting the lerp value timeline.step() => 50 point.x = 100 timeline.step() => 51 compute line lerp to next. set x --- It should only lerp <_from_ - _to_> - therefore if the keyframe does not exist before the step time, the timeline does not lerp to the next keyframe. Instead, the first keyframe "activates" the value, allowing lerping to occur. If a user wants to lerp into an position, the keyframe should be applied before (e.g index 0). --- A timeline has a cycle method to apply how the timeline will end and restart In `cycle` mode, the timeline restarts at 0. A -> B -> A -> B ... `bounce` reverses the timeline as it hits end and start forever A -> B -> B -> A ... `reverse` reverse the timeline as it hits end and stop at frame 0 A -> B -> B -> A `stop` will end the animation at the last keyframe. A -> B -- this infers a _limit_ to the timeline. By default, it's the _last frame_, however this can be set to a target, such as 500. Cycling through the exact number. Animations will end and wait accordindgly. */ class OrderedMap extends Map { /* A map with _next_ method for numerical keys */ getNextFrame(){} } class Timeline { constructor(stage) { this.stage = stage /* Store a num to a keyframe.*/ this.numMap = new Map this.tickFunction = ()=>{} this.ticks = 0 this.running = false /* The value per running step to apply.*/ this.tickValue = 1 this.steppers = {} console.log('Steppers created;') } add(index, keyFrame) { let existing = this.numMap.get(index) if(existing == undefined) { existing = new Set; this.numMap.set(index, existing) } existing.add(keyFrame) } perform(ctx) { /* Run _perform_ at every draw, performing a step if enabled */ this.tickFunction.call(this, arguments) } step() { this.ticks += this.tickValue // Every step checks to see an activation. // Running keys should be lerped to the value. // Discover and produce values for current step const discovered = this.numMap.get(this.ticks) if(discovered) { console.log('Found KeyFrame for tick', this.ticks) for(let keyFrame of discovered) { // lerping values should be initiated. const target = keyFrame.target keyFrame.valueMap.forEach((value, key, a)=>{ /* This key should be set at this value.*/ target[key] = value let nextFrame = this.stackNextFrame(keyFrame) }) // Value() } } // lerp existing. this.lerpExisting(this.ticks) // This means any previously lerping values will _set_ to the new // discovery. Luckily this start value should be (nearly) the same as // the concurrent lerping value. // // When setting a value - the _next_ step is computed through the // lerper; A cached Value is stored and iterated per step. // } lerpExisting(ticks) { let steppers = this.steppers; for(let k in steppers) { let entry = steppers[k] if(entry === undefined) { continue } let lerper = entry.lerper; let relTicks = ticks - entry.delta let width = entry.deltaTo - entry.delta // let width = lerper.width() let oneBit = 1 / width let along = oneBit * relTicks let newValue = lerper.get(along) entry.frames[0].target[entry.key] = newValue } } stackNextFrame(keyFrame, delta=this.ticks) { /* Return the next frame applicable to this keyFrame, specfic to its point and keys. This is used for the value lerping from an existing key value to the next discovered. If a _next frame_ is found with the same target but not the same keys, target next frame is not applicable. All keyframes are iterated until a next key for the target is found, if no next frame is found, nothing is returned. */ // Look at keyframes _after_ this time delta. // // get all sets from the nummap > delta. let m = this.numMap /* Keys we want to lerp. If a wanted key is undiscovered, it doesn't lerp - having no end state, makes it the end state - this has already been set, when the keyframe was discovered. */ let wantedKeys = new Set(keyFrame.valueMap.keys()) // targets[wantedKey] = keyFrameItem /* A place to stash found lerp frames*/ let targets = {} m.forEach((valueSet, deltaKey, i)=>{ if(deltaKey <= delta) { return } // keep any item in the set with the same target valueSet.forEach((keyFrameItem)=>{ if(keyFrameItem === keyFrame) { /* don't check the same frame (note: this will change)*/ return } if(keyFrameItem.target !== keyFrame.target) { /* ignore other targets*/ return } // pop the discovered keys from the items tested console.log('Found potential next at', deltaKey) let hasKeys = new Set(keyFrameItem.valueMap.keys()) wantedKeys.forEach((wantedKey) => { if(!hasKeys.has(wantedKey)) { /* No matching target keys*/ return } /* this keyframe for this target does have a matching key, therefore we can lerp to this value. */ // debugger; wantedKeys.delete(wantedKey) targets[wantedKey] = {keyFrameItem, deltaKey} }) }) }) // discover all next. // Create lerpy values from _existing_ key value to target key value, // over the perform of [distance A & B] const res = {} /* The keyframe targets for all available future values exist in targets. Apply a lerper and stash*/ for(let key in targets) { let {keyFrameItem:targetKeyFrame, deltaKey} = targets[key] let value = targetKeyFrame.valueMap.get(key) let existing = keyFrame.valueMap.get(key) let stepperPath = `${keyFrame.target.uuid}_${keyFrame.uuid}_${key}` let lerper = new Value(existing, value, keyFrame.easing, true) /* The entity to store for later.*/ res[stepperPath] = { lerper , delta , deltaTo: deltaKey , key , frames: [keyFrame, targetKeyFrame] } } /* Store down. Some steppers die. Their values end.*/ let undeleted = new Set(Object.keys(this.steppers)) for(let inKey in res) { if(this.steppers != undefined) { /* replacing an item*/ this.steppers[inKey] = res[inKey] } else { // new item } // const deleted = undeleted.delete(inKey) } // these keys will end now. no lerp. undeleted.forEach(k=>{ // console.log('Deleting', k) // this.steppers[k] = undefined }) // debugger; // Object.assign(this.steppers, res) if(undeleted.length > 10) { // cleanup let newSteppers = {} for(let key in this.steppers) { let val = this.steppers[key] if(val == undefined) { continue } newSteppers[key] = val } this.steppers = newSteppers } } start() { this.running = true console.log('start timeline') this.tickFunction = this.step.bind(this) } stop() { this.tickFunction = ()=>{} this.running = false console.log('stop timeline') } toggleRunning() { if(this.running) { return this.stop() } return this.start() } } class KeyFrame { /* A Keyframe maintains the point and the expected value. animate through points by applying keyframes to a timeline. kf_start = new KeyFrame(point) kf_start.set('x', 10) kf_end = new KeyFrame(point) kf_end.set('x', 200) tl = new Timeline(stage) tl.add(0, kf_start) tl.add(120, kf_end) */ constructor(target, easing) { this.target = target this.valueMap = new Map; this.uuid = Math.random().toString(32) this.easing = easing } set(key, value){ /* Set the value of a key for this point. kf = new KeyFrame(point) kf.set('x', 200) The KeyFrame is immediate defined - meaning this keyFrame should represent the expected value when met. The timeline will lerp to this value. Applying the same key twice will overwrite the original value kf = new KeyFrame(point) kf.set('x', 200) kf.set('x', 12) kf.get('x') */ this.valueMap.set(key, value) } } class MainStage extends Stage { canvas = 'playspace' mounted(){ this.point = new Point(100, 100, 20) // this.events.wake() this.dragging.add(this.point) this.makeTimeline() this.events.wake() } onMouseUp() { this.timeline.ticks = 0 this.timeline.toggleRunning() } makeTimeline(){ let tl = this.timeline = new Timeline(this) let kf = new KeyFrame(this.point, sineEaseInOut) kf.set('x', 100) // kf.x = 200 tl.add(30, kf) let kf2 = new KeyFrame(this.point) kf2.set('x', 400) kf2.set('y', 100) // kf2.x = 200 tl.add(100, kf2) let kf3 = new KeyFrame(this.point) kf3.set('x', 40) // kf3.set('x', 50) // kf3.x = 200 tl.add(160, kf3) let kf4 = new KeyFrame(this.point) kf4.set('y', 550) // kf4.set('x', 50) // kf4.x = 200 tl.add(220, kf4) } draw(ctx){ this.clear(ctx) this.timeline.perform(ctx) this.point.pen.fill(ctx, '#880000') } } stage = MainStage.go(/*{ loop: true }*/)
Run
Meta Data
title Timeline Example
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-example.js
filepath timeline-example.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': '<p>Run a Timeline, with key events.</p>', 'content': '---\ntitle: Timeline Example\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\nRun a Timeline, with key events.'}