threepipe
Version:
A 3D viewer framework built on top of three.js in TypeScript with a focus on quality rendering, modularity and extensibility.
192 lines • 9.83 kB
JavaScript
import { FnCaller, getOrCall, objectHasOwn, safeSetProperty } from 'ts-browser-helpers';
/**
*
* @param uniforms - object for setting uniform value (like ShaderMaterial.uniforms
* @param propKey - uniform name
* @param thisTarget - if `this` is the uniform (because uniforms = this wont work). It also adds _ in front of the name
*/
export function uniform({ uniforms, propKey, thisTarget = false, onChange } = {}) {
// backing up properties as values are different when called again, no idea why.
const cUniforms = !!uniforms;
const cPropKey = !!propKey;
const isThis = thisTarget;
return (targetPrototype, propertyKey, descriptor) => {
const getUniform = (target) => {
const uniforms1 = isThis ? target : cUniforms ? uniforms : target.uniforms || target._uniforms || target.extraUniforms;
let propKey1 = cPropKey ? propKey : propertyKey;
if (isThis)
propKey1 = '_' + propKey1;
let a = uniforms1[propKey1];
if (!a) {
a = { value: null };
uniforms1[propKey1] = a;
}
return a;
};
const prop = {
get() {
return getUniform(this).value;
},
set(newVal) {
const u = getUniform(this);
const val = u.value;
if (val === newVal)
return;
u.value = newVal;
safeSetProperty(this, 'uniformsNeedUpdate', true, true);
onChange && FnCaller.callFunction(onChange, this, [propertyKey, newVal]);
},
// configurable: true,
// enumerable: true,
};
// https://github.com/babel/babel/blob/909ed3473968c2ccd75f89e17c37ef4771cc3ff8/packages/babel-helpers/src/helpers/applyDecoratedDescriptor.ts#L11
if (descriptor) {
if (objectHasOwn(descriptor, 'value'))
delete descriptor.value;
if (objectHasOwn(descriptor, 'writable'))
delete descriptor.writable;
if (objectHasOwn(descriptor, 'initializer'))
delete descriptor.initializer;
return Object.assign(descriptor, prop);
}
Object.defineProperty(targetPrototype, propertyKey, prop);
};
}
// todo migrate to new decorators - https://2ality.com/2022/10/javascript-decorators.html
/**
* Decorator to create a three.js style define in this.material or this and bind to a property.
* see also - {@link matDefineBool}
* @param key - define name
* @param customDefines - object for setting define value (like ShaderMaterial.defines), otherwise this.material.defines is taken
* @param thisMat - access this.defines instead of this.material.defines
* @param onChange - function to call when the value changes. The function is called with the following parameters: [key, newVal]. Note: needsUpdate is set to true for this/material if onChange is not provided.
* @param processVal - function that processes the value before setting it.
* @param invProcessVal - function that processes the value before returning it.
*/
export function matDefine(key, customDefines, thisMat = false, onChange, processVal, invProcessVal) {
// backing up properties as values are different when called again, no idea why.
const cDefines = !!customDefines;
const cPropKey = !!key;
return (targetPrototype, propertyKey, descriptor) => {
const getTarget = (mat) => {
const t = cDefines ? customDefines : mat.defines || mat._defines || mat.extraDefines;
const p = cPropKey ? key : propertyKey;
return { t, p };
};
const prop = {
get() {
const { t, p } = getTarget(thisMat ? this : this.material);
let res = t[p];
if (invProcessVal)
res = invProcessVal(res);
return res;
},
set(newVal) {
const { t, p } = getTarget(thisMat ? this : this.material);
if (processVal)
newVal = processVal(newVal);
// boolean values are supported in material extender.
// else if (typeof newVal === 'boolean') { // just in case
// console.error('Boolean values are not supported for defines. Use @matDefineBool instead.')
// newVal = newVal ? '1' : '0'
// }
safeSetProperty(t, p, newVal, true);
if (newVal === undefined)
delete t[p];
if (onChange && typeof onChange === 'function') {
FnCaller.callFunction(onChange, this, [p, newVal]);
}
else {
safeSetProperty(thisMat ? this : this.material, 'needsUpdate', true, true);
}
},
// configurable: true,
// enumerable: true,
};
// https://github.com/babel/babel/blob/909ed3473968c2ccd75f89e17c37ef4771cc3ff8/packages/babel-helpers/src/helpers/applyDecoratedDescriptor.ts#L11
if (descriptor) {
if (objectHasOwn(descriptor, 'value'))
delete descriptor.value;
if (objectHasOwn(descriptor, 'writable'))
delete descriptor.writable;
if (objectHasOwn(descriptor, 'initializer'))
delete descriptor.initializer;
return Object.assign(descriptor, prop);
}
Object.defineProperty(targetPrototype, propertyKey, prop);
};
}
/**
* Same as {@link matDefine} but for boolean values. It sets the value to '1' or '0'/undefined.
* @param key - define name
* @param customDefines - object for setting define value (like ShaderMaterial.defines), otherwise this.material.defines is taken
* @param thisMat - access this.defines instead of this.material.defines
* @param onChange - function to call when the value changes. If a string, it is used as a property name in `this` and called. If a function, it is called. The function is called with the following parameters: key, newVal
* @param deleteOnFalse - sets to undefined instead of '0' when false. Note deleteOnFalse doesn't work with tweakpane ui because the value will be undefined.
*/
export function matDefineBool(key, customDefines, thisMat = false, onChange, deleteOnFalse = false) {
// noinspection RedundantConditionalExpressionJS
return matDefine(key, customDefines, thisMat, onChange, (v) => v ? '1' : deleteOnFalse ? undefined : '0', (v) => v && v !== '0' ? true : false);
}
/**
* Binds a property to a value in an object. If the object is a string, it is used as a property name in `this`.
*
* @param obj - object to bind to. If a string, it is used as a property name in `this`. If a function, it is called and the result is used as the object/string.
* @param key - key to bind to. If a string, it is used as a property name in `this`. If a function, it is called and the result is used as the key/string.
* @param onChange - function to call when the value changes. If a string, it is used as a property name in `this` and called. If a function, it is called. The function is called with the following parameters: key, newVal
* @param onChangeParams - if true, the parameters passed to the onChange function are [key, newVal]. If false, no parameters are passed. Default = `true`
* @param processVal - function that processes the value before setting it.
* @param invProcessVal - function that processes the value before returning it.
*/
export function bindToValue({ obj, key, processVal, invProcessVal, onChange, onChangeParams = true }) {
const cPropKey = !!key;
return (targetPrototype, propertyKey, descriptor) => {
const getTarget = (_this) => {
let t = getOrCall(obj) || _this;
if (typeof t === 'string')
t = _this[t];
const p = cPropKey ? getOrCall(key) || propertyKey : propertyKey;
return { t, p };
};
const prop = {
get() {
const { t, p } = getTarget(this);
if (!t || typeof t !== 'object')
return;
let res = t[p];
if (invProcessVal)
res = invProcessVal(res);
return res;
},
set(newVal) {
const { t, p } = getTarget(this);
if (!t || typeof t !== 'object')
return;
if (processVal)
newVal = processVal(newVal);
safeSetProperty(t, p, newVal, true);
if (newVal === undefined)
delete t[p];
let oc = onChange;
if (oc && (typeof oc === 'string' || typeof oc === 'symbol'))
oc = this[oc]; // todo just call it here directly
if (oc && typeof oc === 'function')
FnCaller.callFunction(oc, this, onChangeParams ? [p, newVal] : []);
},
// configurable: true,
// enumerable: true,
};
// https://github.com/babel/babel/blob/909ed3473968c2ccd75f89e17c37ef4771cc3ff8/packages/babel-helpers/src/helpers/applyDecoratedDescriptor.ts#L11
if (descriptor) {
if (objectHasOwn(descriptor, 'value'))
delete descriptor.value;
if (objectHasOwn(descriptor, 'writable'))
delete descriptor.writable;
if (objectHasOwn(descriptor, 'initializer'))
delete descriptor.initializer;
return Object.assign(descriptor, prop);
}
Object.defineProperty(targetPrototype, propertyKey, prop);
};
}
//# sourceMappingURL=decorators.js.map