UNPKG

threepipe

Version:

A 3D viewer framework built on top of three.js in TypeScript with a focus on quality rendering, modularity and extensibility.

168 lines (156 loc) 4.56 kB
import { animate, AnimationOptions, anticipate, backIn, backInOut, backOut, bounceIn, bounceInOut, bounceOut, circIn, circInOut, circOut, easeIn, easeInOut, easeOut, Easing, KeyframeOptions, linear, } from 'popmotion' import {timeout} from 'ts-browser-helpers' import {MathUtils} from 'three' export {animate} declare module 'popmotion'{ interface PlaybackOptions<V> { // throwOnStop?: boolean; // instead of this, user can simply throw an error in onStop. onEnd?: () => void; } } export type {AnimationOptions, KeyframeOptions, Easing} function easeInOutSine(x: number): number { return -(Math.cos(Math.PI * x) - 1) / 2 } // eslint-disable-next-line @typescript-eslint/naming-convention export const EasingFunctions = { linear: linear, easeIn: easeIn, easeOut: easeOut, easeInOut: easeInOut, circIn: circIn, circOut: circOut, circInOut: circInOut, backIn: backIn, backOut: backOut, backInOut: backInOut, anticipate: anticipate, bounceOut: bounceOut, bounceIn: bounceIn, bounceInOut: bounceInOut, easeInOutSine: easeInOutSine, } /** * EasingFunctionType: * anticipate, backIn, backInOut, backOut, bounceIn, bounceInOut, bounceOut, circIn, circInOut, circOut, easeIn, easeInOut, easeOut, easeInOutSine */ export type EasingFunctionType = keyof typeof EasingFunctions export type AnimateResult = ReturnType<typeof animate> export function makeSetterFor<V>(target: any, key: string, setDirty?: ()=>void) { const v = target[key] as any const dirty = ()=>{ // if (typeof target?.setDirty === 'function') target.setDirty() setDirty?.() } const isBool = typeof v === 'boolean' if (v && v.isColor) return (a: any) => { v.set(a) dirty() } else if (v && typeof v.copy === 'function') return (a: any) => { v.copy(a) dirty() } else return (a: V)=>{ target[key] = !isBool ? a : !!a dirty() } } export async function animateTarget<V>(target: any, key: string, options: AnimationOptions<V>, animations?: AnimateResult[], forceCurrent = false) { if (!(key in target)) { console.error('invalid key', key, target) } const setter = makeSetterFor(target, key) const fromVal = forceCurrent || options.from === undefined ? target[key] : options.from const onUpdate = (val: V)=>{ setter(val) options.onUpdate && options.onUpdate(val) } if (typeof fromVal === 'boolean') { const {duration} = options as KeyframeOptions // todo: divide by 2? or support keyframes. return timeout(duration ?? 0).then(()=>onUpdate(options.to as V)) } else { if (typeof options.to === 'function') { options = {...options, to: options.to(fromVal, target)} // need to duplicate options } return animateAsync({ ...options, from: fromVal, onUpdate, } as AnimationOptions<V>, animations) } } export async function animateAsync<V=number>(options: AnimationOptions<V>, animations?: AnimateResult[]) { const complete = options.onComplete const stop = options.onStop const end = options.onEnd options = {...options} return new Promise<void>((resolve, reject) => { const end2 = ()=>{ try { end?.() } catch (e: any) { reject(e) return false } return true } options.onComplete = ()=>{ try { complete?.() } catch (e: any) { if (!end2()) return reject(e) return } if (!end2()) return resolve() } options.onStop = ()=>{ try { stop?.() } catch (e: any) { if (!end2()) return reject(e) return } if (!end2()) return resolve() } const an = animate(options) if (animations) animations.push(an) }) } export function lerpAngle(a: number, b: number, t: number) { const d = b - a if (d >= Math.PI) { return a + (d - Math.PI * 2) * t } else if (d <= -Math.PI) { return a + (d + Math.PI * 2) * t } else { return a + d * t } } export const lerp = MathUtils.lerp