threepipe
Version:
A modern 3D viewer framework built on top of three.js, written in TypeScript, designed to make creating high-quality, modular, and extensible 3D experiences on the web simple and enjoyable.
164 lines • 6.23 kB
JavaScript
import { Color } from 'three';
import { whiteTexture } from '../three';
// See https://repalash.com/blog/interactive-lerp-animation for details
export function lerpVal(time, val, end) {
if (typeof val === 'number' && typeof end === 'number') {
return lerpNumber(time, val, end);
}
if (typeof val?.r === 'number' && typeof end?.r === 'number') {
return new Color().set(lerpNumber(time.from ? { ...time, from: time.from.r } : time, val.r, end.r), lerpNumber(time.from ? { ...time, from: time.from.g } : time, val.g, end.g), lerpNumber(time.from ? { ...time, from: time.from.b } : time, val.b, end.b));
}
if (val?.toArray && end?.toArray && val.clone && val.fromArray) {
return lerpVector(time, val, end);
}
// not handling textures here specifically, to avoid texture arrays for now
return time.t >= 1 ? end : val;
}
export function lerpNumber(time, val, end) {
const { t, dt } = time;
let newVal;
if (t <= 0)
newVal = time.from !== undefined ? time.from : val;
else if (t >= 1.)
newVal = end;
else if (time.from !== undefined) {
newVal = time.from * (1 - t) + end * t;
}
else {
const l = 1 - t;
const k = Math.max(0, Math.min(dt, l) / l);
newVal = val + (end - val) * k;
}
return newVal;
}
export function lerpVector(time, val, end) {
const valA = val.toArray();
const endA = end.toArray();
const fromA = time.from ? time.from.toArray() : undefined;
const newValA = valA.map((v, i) => lerpVal(fromA ? { ...time, from: fromA[i] } : time, v, endA[i]));
const newVal = val.clone().fromArray(newValA);
return newVal;
}
export function lerpTexture(time, val, end) {
if (!time.rm) {
console.warn('MaterialManager: RenderManager is required for interpolating textures');
return end;
}
const { t, dt } = time;
let newVal;
const fromVal = time.from !== undefined ? time.from : val;
if (t <= 0 || t > 0.99 || fromVal === end) {
newVal = t <= 0 ? fromVal : end;
let rt;
if (val && val.userData?._lerpTexture && val.isRenderTargetTexture) {
rt = val._target;
}
if (rt) {
time.rm.releaseTempTarget(rt);
}
}
else {
const val1 = fromVal || whiteTexture;
let needsInit = false;
let rt;
if (val && val.userData?._lerpTexture && val.isRenderTargetTexture) {
rt = val._target;
}
else {
const anyTex = fromVal || end || whiteTexture;
const size = {
width: anyTex.image?.width || anyTex.image?.naturalWidth || anyTex.image?.videoWidth || 1,
height: anyTex.image?.height || anyTex.image?.naturalHeight || anyTex.image?.videoHeight || 1,
};
rt = time.rm.getTempTarget({
size: size,
colorSpace: anyTex.colorSpace,
type: anyTex.type,
format: anyTex.format,
generateMipmaps: anyTex.generateMipmaps,
minFilter: anyTex.minFilter,
magFilter: anyTex.magFilter,
wrapS: anyTex.wrapS,
wrapT: anyTex.wrapT,
});
rt.texture.userData._lerpTexture = true;
needsInit = true;
}
if (rt) {
if (time.from !== undefined || needsInit) {
time.rm.blit(rt, {
source: val1,
respectColorSpace: false,
transparent: true,
clear: true,
});
}
const l = 1 - t;
const k = Math.max(0, Math.min(dt, l) / l);
// newVal = val + (end - val) * k
// newVal = val * (1 - k) + end * k
time.rm.blit(rt, {
source: end || whiteTexture,
respectColorSpace: false,
transparent: true,
clear: false,
blendAlpha: time.from !== undefined ? t : k,
// blending: NormalBlending,
});
newVal = rt.texture;
}
else {
newVal = val;
}
}
return newVal;
}
export function lerpParams(params, obj, interpolateProps, time) {
for (const key of Object.keys(params)) {
if (!interpolateProps.has(key))
continue;
const val = obj[key];
if (val === undefined)
continue;
const time2 = time.from ? { ...time, from: time.from[key] } : time;
const end = params[key];
if (typeof val === 'number' && typeof end === 'number') {
const newVal = lerpNumber(time2, val, end);
if (newVal === val)
delete params[key]; // no change
else
params[key] = newVal;
}
if (val?.isColor && end?.isColor) {
const newVal = lerpVal(time2, val, end);
if (newVal.getHex() === val.getHex())
delete params[key]; // no change
else
params[key] = newVal;
}
if ((val === undefined || val === null || val.isTexture) && (end === undefined || end === null || end.isTexture) && (val || end)) {
const newVal = lerpTexture(time2, val || null, end || null);
if (newVal === val)
delete params[key]; // no change
else
params[key] = newVal;
}
// vectors and custom stuff
if (val?.toArray && end?.toArray && val.clone && val.fromArray && val.equals) {
const newVal = lerpVector(time2, val, end);
if (newVal.equals(val))
delete params[key]; // no change
else
params[key] = newVal;
}
if (Array.isArray(val) && Array.isArray(end) && val.length === end.length) {
// interpolate arrays
const newVal = val.map((v, i) => lerpVal(time2, v, end[i]));
if (newVal.every((v, i) => v === val[i]))
delete params[key]; // no change
else
params[key] = newVal;
}
}
}
//# sourceMappingURL=lerp.js.map