|
|
@@ -27,9 +27,22 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
|
const scenes = Array.from(document.querySelectorAll('scene, .scene'))
|
|
|
scenes.forEach(scene => {
|
|
|
-
|
|
|
+ const beatsElement = scene.querySelector('beats')
|
|
|
+ let beats = []
|
|
|
+ if (beatsElement) {
|
|
|
+ beatsElement.style.whiteSpace = 'pre'
|
|
|
+ beats = Object.fromEntries(
|
|
|
+ beatsElement.innerText
|
|
|
+ .split('\n')
|
|
|
+ .map(line => line.trim().replace(/\s+/g, ' '))
|
|
|
+ .filter(x => x)
|
|
|
+ .map(x => x.split(' '))
|
|
|
+ .map(([key, time, ...words]) => [key, {time, words: words.join(' ')}])
|
|
|
+ )
|
|
|
+ beatsElement.style.display = 'none'
|
|
|
+ }
|
|
|
const sprites = Array.from(scene.querySelectorAll('sprite, .sprite'))
|
|
|
-
|
|
|
+ const preroll = +scene.getAttribute('preroll') || 0
|
|
|
const spritesById = {}
|
|
|
sprites
|
|
|
.filter(sprite => sprite.id)
|
|
|
@@ -48,7 +61,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
|
sprite.keyFrames = {}
|
|
|
sprite.moverFrames = {}
|
|
|
- ;['left', 'right', 'top', 'bottom', 'width', 'height', 'opacity'].forEach(key => {
|
|
|
+ ;['left', 'right', 'top', 'bottom', 'width', 'height', 'opacity', 'zoom'].forEach(key => {
|
|
|
const val = sprite.attributes[key]
|
|
|
if (val) mover.style[key] = unit(val)
|
|
|
})
|
|
|
@@ -68,11 +81,38 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
return sprite.moverFrames[key]
|
|
|
}
|
|
|
|
|
|
+ const parseTime = (x, withPreroll = preroll) => {
|
|
|
+ const b = /^([^+-]*)([+-]?)(.*)$/.exec(x)
|
|
|
+ if (b && beats[b[1]]) {
|
|
|
+ const key = b[1]
|
|
|
+ const operator = b[2]
|
|
|
+ const operand = b[3]
|
|
|
+ let opValue = 0
|
|
|
+ const beat = beats[key]
|
|
|
+ const beatTime = parseTime(beat.time, 0)
|
|
|
+ if (operand) {
|
|
|
+ opValue = parseTime(operand, 0)
|
|
|
+ if (operator === '-') opValue = -opValue
|
|
|
+ }
|
|
|
+ let time = (beatTime + opValue)
|
|
|
+ time = time && (withPreroll + time)
|
|
|
+ return time
|
|
|
+ } else {
|
|
|
+ let {1:seconds, 2:frame} = /([^:]*):?(.*)/.exec(x)
|
|
|
+ seconds = +(seconds || 0)
|
|
|
+ frame = +(frame || 0)
|
|
|
+ let time = seconds + (frame / 30)
|
|
|
+ // Only apply preroll when time > 0
|
|
|
+ time = time && (withPreroll + time)
|
|
|
+ return time
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
Array.from(sprite.attributes).forEach(attr => {
|
|
|
let {1:name, 2:time} = (/^([^\]]*)\[([^\]]*)\]$/.exec(attr.name) || [null, attr.name, "0"])
|
|
|
const transform = transforms[name]
|
|
|
if (transform) {
|
|
|
- const times = time.split(',').map(x => +x)
|
|
|
+ const times = time.split(',').map(x => parseTime(x))
|
|
|
const values = attr.value.split(',').map(x => x.trim())
|
|
|
if (times.length !== values.length) return console.error('Mismatched time, value arrays', attr)
|
|
|
times.forEach((time, i) => {
|
|
|
@@ -101,34 +141,81 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
|
const allFrames = []
|
|
|
sprites.forEach(({keyFrames, moverFrames}) => {
|
|
|
- const frames = [...Object.entries(keyFrames), ...Object.entries(moverFrames)].map(([time, frame]) => [+time * 1000, frame])
|
|
|
- frames.sort((a, b) => a[0] - b[0])
|
|
|
+ const frames = [...Object.entries(keyFrames), ...Object.entries(moverFrames)].map(([time, frame]) => ({time: +time * 1000, keyFrame: frame}))
|
|
|
+ frames.sort((a, b) => a.time - b.time)
|
|
|
frames.forEach(frame => {
|
|
|
- frame[1].targets.lastTime = 0
|
|
|
- if (frame[1].duration) {
|
|
|
- frame[1].duration = 1000 * +frame[1].duration
|
|
|
+ frame.keyFrame.targets.lastTime = 0
|
|
|
+ if (frame.keyFrame.duration) {
|
|
|
+ frame.keyFrame.duration = 1000 * +frame.keyFrame.duration
|
|
|
}
|
|
|
})
|
|
|
- frames.forEach(frame => {
|
|
|
+ frames.forEach((frame, i) => {
|
|
|
+ const nextFrame = frames[i + 1]
|
|
|
allFrames.push(frame)
|
|
|
- if (frame[0] == 0) {
|
|
|
- frame[0] = Number.EPSILON
|
|
|
- frame[1].easing = 'steps(1)'
|
|
|
+ if (frame.time == 0) {
|
|
|
+ //frame.time = Number.EPSILON
|
|
|
+ frame.keyFrame.easing = 'steps(1)'
|
|
|
} else {
|
|
|
- frame[1].duration = frame[1].duration || Math.min(1000, frame[0] - frame[1].targets.lastTime)
|
|
|
+ const lastFrame = frame.keyFrame.targets.lastFrame
|
|
|
+ frame.keyFrame.duration = frame.keyFrame.duration || Math.min(1000, frame.time - frame.keyFrame.targets.lastTime - 1)
|
|
|
+ if (lastFrame) {
|
|
|
+ const maxDuration = frame.time - lastFrame.time
|
|
|
+ if (lastFrame.keyFrame.duration > maxDuration) lastFrame.keyFrame.duration = maxDuration
|
|
|
+ lastFrame.maxDuration = maxDuration
|
|
|
+ lastFrame.lastTime = lastFrame.time
|
|
|
+ }
|
|
|
+ // if (nextFrame) {
|
|
|
+ // const maxDuration = nextFrame.time - frame.time
|
|
|
+ // if (frame.keyFrame.duration > maxDuration) frame.keyFrame.duration = maxDuration
|
|
|
+ // }
|
|
|
}
|
|
|
- frame[1].targets.lastTime = frame[0]
|
|
|
+ frame.keyFrame.targets.lastTime = frame.time
|
|
|
+ frame.keyFrame.targets.lastFrame = frame
|
|
|
})
|
|
|
})
|
|
|
- allFrames.sort((a, b) => a[0] - b[0])
|
|
|
+ const targets = new Set(allFrames.map(x => x.keyFrame.targets))
|
|
|
+ const maxTime = Array.from(targets).map(x => x.lastTime).reduce((a, b) => Math.max(a, b), 0)
|
|
|
+ for (let target of targets) {
|
|
|
+ if (target.lastTime !== maxTime) {
|
|
|
+ console.log('Adding lastframe')
|
|
|
+// allFrames.push({time: maxTime, keyFrame: { ...target.lastFrame.keyFrame, duration: 0}})
|
|
|
+ }
|
|
|
+ }
|
|
|
+ allFrames.sort((a, b) => a.time - b.time)
|
|
|
|
|
|
console.log(allFrames)
|
|
|
- allFrames.forEach(([time, keyFrame]) => {
|
|
|
- timeline.add(keyFrame, time)
|
|
|
- console.log(time, keyFrame)
|
|
|
+ window.allFrames = allFrames
|
|
|
+ const timelineAdd = (frame, time) => {
|
|
|
+ timeline.add(frame, time)
|
|
|
+ console.log(time, frame)
|
|
|
+ }
|
|
|
+ allFrames.forEach(({time, keyFrame}) => {
|
|
|
+ if (time == 0) {
|
|
|
+ timelineAdd({...keyFrame, duration: 0.001}, Number.EPSILON)
|
|
|
+ timelineAdd({...keyFrame, duration: 0.001}, Number.EPSILON * 2)
|
|
|
+ } else {
|
|
|
+ timelineAdd(keyFrame, time)
|
|
|
+ }
|
|
|
})
|
|
|
|
|
|
- console.log(timeline)
|
|
|
+ const timecodes = Array.from(document.querySelectorAll('.timecode'))
|
|
|
+ if (timecodes.length) {
|
|
|
+ timeline.add({
|
|
|
+ update(anim) {
|
|
|
+ const ms = Math.floor((timeline.progress / 100) * timeline.duration)
|
|
|
+ const fps = 30
|
|
|
+ const seconds = Math.floor(ms / 1000)
|
|
|
+ const leftover = ms % 1000
|
|
|
+ const frames = Math.floor(leftover / 1000 * fps)
|
|
|
+ const timecodeString = `${seconds}:${frames.toString().padStart(2, '0')}`
|
|
|
+ timecodes.forEach(x => x.innerText = timecodeString)
|
|
|
+ },
|
|
|
+ loop: true,
|
|
|
+ duration: 100,
|
|
|
+ targets: document.documentElement
|
|
|
+ }, 0)
|
|
|
+ }
|
|
|
+ console.log('timeline', timeline)
|
|
|
})
|
|
|
})
|
|
|
|