@lightningtv/renderer
Version:
Lightning 3 Renderer
228 lines (191 loc) • 5.67 kB
text/typescript
/*
* If not stated otherwise in this file or this component's LICENSE file the
* following copyright and licenses apply:
*
* Copyright 2023 Comcast Cable Communications Management, LLC.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Core Utility Functions
*
* @module
*/
export const EPSILON = 0.000001;
export let ARRAY_TYPE =
typeof Float32Array !== 'undefined' ? Float32Array : Array;
export const RANDOM = Math.random;
export const ANGLE_ORDER = 'zyx';
const degree = Math.PI / 180;
export const setMatrixArrayType = (
type: Float32ArrayConstructor | ArrayConstructor,
) => {
ARRAY_TYPE = type;
};
export const toRadian = (a: number) => {
return a * degree;
};
export const equals = (a: number, b: number) => {
return Math.abs(a - b) <= EPSILON * Math.max(1.0, Math.abs(a), Math.abs(b));
};
export const rand = (min: number, max: number) => {
return Math.floor(Math.random() * (max - min + 1) + min);
};
export const isPowerOfTwo = (value: number) => {
return value && !(value & (value - 1));
};
const getTimingBezier = (
a: number,
b: number,
c: number,
d: number,
): ((time: number) => number | undefined) => {
const xc = 3.0 * a;
const xb = 3.0 * (c - a) - xc;
const xa = 1.0 - xc - xb;
const yc = 3.0 * b;
const yb = 3.0 * (d - b) - yc;
const ya = 1.0 - yc - yb;
return function (time: number): number | undefined {
if (time >= 1.0) {
return 1;
}
if (time <= 0) {
return 0;
}
let t = 0.5,
cbx,
cbxd,
dx;
for (let it = 0; it < 20; it++) {
cbx = t * (t * (t * xa + xb) + xc);
dx = time - cbx;
if (dx > -1e-8 && dx < 1e-8) {
return t * (t * (t * ya + yb) + yc);
}
// Cubic bezier derivative.
cbxd = t * (t * (3 * xa) + 2 * xb) + xc;
if (cbxd > 1e-10 && cbxd < 1e-10) {
// Problematic. Fall back to binary search method.
break;
}
t += dx / cbxd;
}
// Fallback: binary search method. This is more reliable when there are near-0 slopes.
let minT = 0;
let maxT = 1;
for (let it = 0; it < 20; it++) {
t = 0.5 * (minT + maxT);
cbx = t * (t * (t * xa + xb) + xc);
dx = time - cbx;
if (dx > -1e-8 && dx < 1e-8) {
// Solution found!
return t * (t * (t * ya + yb) + yc);
}
if (dx < 0) {
maxT = t;
} else {
minT = t;
}
}
};
};
interface TimingFunctionMap {
[key: string]: (time: number) => number | undefined;
}
type TimingLookupArray = number[];
interface TimingLookup {
[key: string]: TimingLookupArray;
}
const timingMapping: TimingFunctionMap = {};
const timingLookup: TimingLookup = {
ease: [0.25, 0.1, 0.25, 1.0],
'ease-in': [0.42, 0, 1.0, 1.0],
'ease-out': [0, 0, 0.58, 1.0],
'ease-in-out': [0.42, 0, 0.58, 1.0],
'ease-in-sine': [0.12, 0, 0.39, 0],
'ease-out-sine': [0.12, 0, 0.39, 0],
'ease-in-out-sine': [0.37, 0, 0.63, 1],
'ease-in-cubic': [0.32, 0, 0.67, 0],
'ease-out-cubic': [0.33, 1, 0.68, 1],
'ease-in-out-cubic': [0.65, 0, 0.35, 1],
'ease-in-circ': [0.55, 0, 1, 0.45],
'ease-out-circ': [0, 0.55, 0.45, 1],
'ease-in-out-circ': [0.85, 0, 0.15, 1],
'ease-in-back': [0.36, 0, 0.66, -0.56],
'ease-out-back': [0.34, 1.56, 0.64, 1],
'ease-in-out-back': [0.68, -0.6, 0.32, 1.6],
};
const defaultTiming = (t: number): number => t;
const parseCubicBezier = (str: string) => {
//cubic-bezier(0.84, 0.52, 0.56, 0.6)
const regex = /-?\d*\.?\d+/g;
const match = str.match(regex);
if (match) {
const [num1, num2, num3, num4] = match;
const a = parseFloat(num1 || '0.42');
const b = parseFloat(num2 || '0');
const c = parseFloat(num3 || '1');
const d = parseFloat(num4 || '1');
const timing = getTimingBezier(a, b, c, d);
timingMapping[str] = timing;
return timing;
}
// parse failed, return linear
console.warn('Unknown cubic-bezier timing: ' + str);
return defaultTiming;
};
export const getTimingFunction = (
str: string,
): ((time: number) => number | undefined) => {
if (str === 'linear') {
return defaultTiming;
}
if (timingMapping[str] !== undefined) {
return timingMapping[str] || defaultTiming;
}
if (str === 'step-start') {
return () => {
return 1;
};
}
if (str === 'step-end') {
return (time: number) => {
return time === 1 ? 1 : 0;
};
}
const lookup = timingLookup[str];
if (lookup !== undefined) {
const [a, b, c, d] = lookup;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - TS doesn't understand that we've checked for undefined
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const timing = getTimingBezier(a, b, c, d);
timingMapping[str] = timing;
return timing;
}
if (str.startsWith('cubic-bezier')) {
return parseCubicBezier(str);
}
console.warn('Unknown timing function: ' + str);
return defaultTiming;
};
/**
* Convert bytes to string of megabytes with 2 decimal points
*
* @param bytes
* @returns
*/
export function bytesToMb(bytes: number) {
return (bytes / 1024 / 1024).toFixed(2);
}