init
This commit is contained in:
commit
c6863f2bfc
34 files changed
+6909
No files matched your search
@@ -0,0 +1,75 @@
|
||||
import type { AnimatableProps, Easing, Keyframe, Layer } from '@/types/scene'
|
||||
|
||||
const easings: Record<Easing, (t: number) => number> = {
|
||||
linear: (t) => t,
|
||||
easeIn: (t) => t * t,
|
||||
easeOut: (t) => 1 - (1 - t) ** 2,
|
||||
easeInOut: (t) => (t < 0.5 ? 2 * t * t : 1 - ((-2 * t + 2) ** 2) / 2),
|
||||
}
|
||||
|
||||
function merge(base: AnimatableProps, partial: Partial<AnimatableProps>): AnimatableProps {
|
||||
return { ...base, ...partial }
|
||||
}
|
||||
|
||||
function lerp(a: number, b: number, t: number) {
|
||||
return a + (b - a) * t
|
||||
}
|
||||
|
||||
function parseHex(hex: string): [number, number, number] | null {
|
||||
const h = hex.trim().replace('#', '')
|
||||
const full = h.length === 3 ? h.split('').map((c) => c + c).join('') : h
|
||||
if (full.length < 6) return null
|
||||
const n = parseInt(full.slice(0, 6), 16)
|
||||
if (Number.isNaN(n)) return null
|
||||
return [(n >> 16) & 255, (n >> 8) & 255, n & 255]
|
||||
}
|
||||
|
||||
function formatHex(r: number, g: number, b: number): string {
|
||||
const clamp = (v: number) => Math.max(0, Math.min(255, Math.round(v)))
|
||||
const h = (v: number) => clamp(v).toString(16).padStart(2, '0')
|
||||
return `#${h(r)}${h(g)}${h(b)}`
|
||||
}
|
||||
|
||||
export function lerpColor(a: string, b: string, t: number): string {
|
||||
const pa = parseHex(a)
|
||||
const pb = parseHex(b)
|
||||
if (!pa || !pb) return t < 0.5 ? a : b
|
||||
return formatHex(lerp(pa[0], pb[0], t), lerp(pa[1], pb[1], t), lerp(pa[2], pb[2], t))
|
||||
}
|
||||
|
||||
function lerpProps(a: AnimatableProps, b: AnimatableProps, t: number): AnimatableProps {
|
||||
const out: AnimatableProps = {
|
||||
x: lerp(a.x, b.x, t),
|
||||
y: lerp(a.y, b.y, t),
|
||||
scaleX: lerp(a.scaleX, b.scaleX, t),
|
||||
scaleY: lerp(a.scaleY, b.scaleY, t),
|
||||
rotation: lerp(a.rotation, b.rotation, t),
|
||||
opacity: lerp(a.opacity, b.opacity, t),
|
||||
}
|
||||
if (a.fill && b.fill) out.fill = lerpColor(a.fill, b.fill, t)
|
||||
else if (b.fill) out.fill = b.fill
|
||||
else if (a.fill) out.fill = a.fill
|
||||
return out
|
||||
}
|
||||
|
||||
export function sortedKeyframes(layer: Layer): Keyframe[] {
|
||||
return [...layer.keyframes].sort((a, b) => a.time - b.time)
|
||||
}
|
||||
|
||||
export function sampleLayer(layer: Layer, time: number): AnimatableProps {
|
||||
const kfs = sortedKeyframes(layer)
|
||||
if (!kfs.length) return { ...layer.base }
|
||||
if (time <= kfs[0].time) return merge(layer.base, kfs[0].props)
|
||||
const last = kfs[kfs.length - 1]
|
||||
if (time >= last.time) return merge(layer.base, last.props)
|
||||
for (let i = 0; i < kfs.length - 1; i++) {
|
||||
const a = kfs[i]
|
||||
const b = kfs[i + 1]
|
||||
if (time >= a.time && time <= b.time) {
|
||||
const raw = (time - a.time) / (b.time - a.time)
|
||||
const t = easings[b.easing](raw)
|
||||
return lerpProps(merge(layer.base, a.props), merge(layer.base, b.props), t)
|
||||
}
|
||||
}
|
||||
return { ...layer.base }
|
||||
}
|
||||
Reference in new issue
Block a user