@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
634 lines (615 loc) • 19.9 kB
text/typescript
import {Constructor} from '../../../../types/GlobalTypes';
import {TypedCopNode} from '../_Base';
import {ColorSpace, Texture} from 'three';
import {
RGBAFormat,
UnsignedByteType,
NoColorSpace,
UVMapping,
RepeatWrapping,
LinearFilter,
AnyPixelFormat,
TextureDataType,
AnyMapping,
Wrapping,
MinificationTextureFilter,
MagnificationTextureFilter,
} from 'three';
import {
MAG_FILTERS,
MAG_FILTER_DEFAULT_VALUE,
MAG_FILTER_MENU_ENTRIES,
MIN_FILTERS,
MIN_FILTER_DEFAULT_VALUE,
MIN_FILTER_MENU_ENTRIES,
} from '../../../../core/cop/Filter';
import {NodeParamsConfig, ParamConfig} from '../../utils/params/ParamsConfig';
import {CopRendererController} from './RendererController';
import {BaseNodeType} from '../../_Base';
import {isBooleanTrue} from '../../../../core/BooleanValue';
import {ParamsValueAccessorType} from '../../utils/params/ParamsValueAccessor';
import {COLOR_SPACES, COLOR_SPACE_NAME_BY_COLOR_SPACE} from '../../../../core/cop/ColorSpace';
import {WRAPPINGS} from '../../../../core/cop/Wrapping';
import {MAPPINGS} from '../../../../core/cop/Mapping';
import {TEXTURE_TYPES} from '../../../../core/cop/Type';
import {TEXTURE_FORMATS} from '../../../../core/cop/Format';
type AvailableCallbackMethod = Extract<
keyof typeof TextureParamsController,
| 'PARAM_CALLBACK_update_encoding'
| 'PARAM_CALLBACK_update_mapping'
| 'PARAM_CALLBACK_update_wrap'
| 'PARAM_CALLBACK_update_filter'
| 'PARAM_CALLBACK_update_anisotropy'
| 'PARAM_CALLBACK_update_flipY'
| 'PARAM_CALLBACK_update_transform'
| 'PARAM_CALLBACK_update_repeat'
| 'PARAM_CALLBACK_update_offset'
| 'PARAM_CALLBACK_update_center'
| 'PARAM_CALLBACK_update_rotation'
| 'PARAM_CALLBACK_update_advanced'
>;
function callbackParams(method: AvailableCallbackMethod) {
return {
cook: false,
callback: (node: BaseNodeType) => {
TextureParamsController[method](node as TextureCopNode);
},
};
}
const DEFAULT = {
COLOR_SPACE: NoColorSpace,
FORMAT: RGBAFormat,
MAPPING: UVMapping,
MIN_FILTER: LinearFilter,
MAG_FILTER: LinearFilter,
TYPE: UnsignedByteType,
WRAPPING: RepeatWrapping,
};
const CALLBACK_PARAMS_ENCODING = callbackParams('PARAM_CALLBACK_update_encoding');
const CALLBACK_PARAMS_MAPPING = callbackParams('PARAM_CALLBACK_update_mapping');
const CALLBACK_PARAMS_WRAP = callbackParams('PARAM_CALLBACK_update_wrap');
const CALLBACK_PARAMS_FILTER = callbackParams('PARAM_CALLBACK_update_filter');
const CALLBACK_PARAMS_ANISOTROPY = callbackParams('PARAM_CALLBACK_update_anisotropy');
const CALLBACK_PARAMS_FLIPY = callbackParams('PARAM_CALLBACK_update_flipY');
const CALLBACK_PARAMS_TRANSFORM_TRANSFORM = callbackParams('PARAM_CALLBACK_update_transform');
const CALLBACK_PARAMS_TRANSFORM_REPEAT = callbackParams('PARAM_CALLBACK_update_repeat');
const CALLBACK_PARAMS_TRANSFORM_OFFSET = callbackParams('PARAM_CALLBACK_update_offset');
const CALLBACK_PARAMS_TRANSFORM_ROTATION = callbackParams('PARAM_CALLBACK_update_rotation');
const CALLBACK_PARAMS_TRANSFORM_CENTER = callbackParams('PARAM_CALLBACK_update_center');
const CALLBACK_PARAMS_ADVANCED = callbackParams('PARAM_CALLBACK_update_advanced');
interface TextureParamConfigDefaults {
tcolorSpace: boolean | number;
colorSpace: string;
}
export function TextureParamConfig<TBase extends Constructor>(Base: TBase, defaults?: TextureParamConfigDefaults) {
return class Mixin extends Base {
/** @param toggle on to allow updating the texture encoding */
tcolorSpace = ParamConfig.BOOLEAN(defaults?.tcolorSpace || 0, {
...CALLBACK_PARAMS_ENCODING,
});
/** @param sets the texture encoding */
colorSpace = ParamConfig.STRING(defaults?.colorSpace || DEFAULT.COLOR_SPACE, {
visibleIf: {tcolorSpace: 1},
menuString: {
entries: COLOR_SPACES.map((colorSpace) => ({
name: COLOR_SPACE_NAME_BY_COLOR_SPACE[colorSpace],
value: colorSpace,
})),
},
...CALLBACK_PARAMS_ENCODING,
});
/** @param toggle on to allow updating the texture mapping */
tmapping = ParamConfig.BOOLEAN(0, {
...CALLBACK_PARAMS_MAPPING,
});
/** @param sets the texture mapping */
mapping = ParamConfig.INTEGER(DEFAULT.MAPPING, {
visibleIf: {tmapping: 1},
menu: {
entries: MAPPINGS.map((m) => {
return {
name: Object.keys(m)[0],
value: Object.values(m)[0] as number,
};
}),
},
...CALLBACK_PARAMS_MAPPING,
});
/** @param toggle on to allow updating the texture wrap */
twrap = ParamConfig.BOOLEAN(0, {
...CALLBACK_PARAMS_WRAP,
});
/** @param sets the texture wrapS */
wrapS = ParamConfig.INTEGER(DEFAULT.WRAPPING, {
visibleIf: {twrap: 1},
menu: {
entries: WRAPPINGS.map((m) => {
return {
name: Object.keys(m)[0],
value: Object.values(m)[0] as number,
};
}),
},
...CALLBACK_PARAMS_WRAP,
});
/** @param sets the texture wrapT */
wrapT = ParamConfig.INTEGER(DEFAULT.WRAPPING, {
visibleIf: {twrap: 1},
menu: {
entries: WRAPPINGS.map((m) => {
return {
name: Object.keys(m)[0],
value: Object.values(m)[0] as number,
};
}),
},
separatorAfter: true,
...CALLBACK_PARAMS_WRAP,
});
/** @param toggle on to allow updating the texture min filter */
tminFilter = ParamConfig.BOOLEAN(0, {
...CALLBACK_PARAMS_FILTER,
});
/** @param sets the texture min filter */
minFilter = ParamConfig.INTEGER(MIN_FILTER_DEFAULT_VALUE, {
visibleIf: {tminFilter: 1},
menu: {
entries: MIN_FILTER_MENU_ENTRIES,
},
...CALLBACK_PARAMS_FILTER,
});
/** @param toggle on to allow updating the texture mag filter */
tmagFilter = ParamConfig.BOOLEAN(0, {
...CALLBACK_PARAMS_FILTER,
});
/** @param sets the texture mag filter */
magFilter = ParamConfig.INTEGER(MAG_FILTER_DEFAULT_VALUE, {
visibleIf: {tmagFilter: 1},
menu: {
entries: MAG_FILTER_MENU_ENTRIES,
},
...CALLBACK_PARAMS_FILTER,
});
/** @param toggle on to allow updating the texture anisotropy */
tanisotropy = ParamConfig.BOOLEAN(0, {
...CALLBACK_PARAMS_ANISOTROPY,
});
/** @param sets the anisotropy from the max value allowed by the renderer */
useRendererMaxAnisotropy = ParamConfig.BOOLEAN(0, {
visibleIf: {tanisotropy: 1},
...CALLBACK_PARAMS_ANISOTROPY,
});
/** @param sets the anisotropy manually */
anisotropy = ParamConfig.INTEGER(2, {
visibleIf: {tanisotropy: 1, useRendererMaxAnisotropy: 0},
range: [0, 32],
rangeLocked: [true, false],
...CALLBACK_PARAMS_ANISOTROPY,
});
/** @param Toggle on to update the flipY */
tflipY = ParamConfig.BOOLEAN(0, {
...CALLBACK_PARAMS_FLIPY,
});
/** @param sets the flipY */
flipY = ParamConfig.BOOLEAN(0, {
visibleIf: {tflipY: 1},
...CALLBACK_PARAMS_FLIPY,
});
/** @param toggle on to update the texture transform */
ttransform = ParamConfig.BOOLEAN(0, {
...CALLBACK_PARAMS_TRANSFORM_TRANSFORM,
});
/** @param updates the texture offset */
offset = ParamConfig.VECTOR2([0, 0], {
visibleIf: {ttransform: 1},
...CALLBACK_PARAMS_TRANSFORM_OFFSET,
});
/** @param updates the texture repeat */
repeat = ParamConfig.VECTOR2([1, 1], {
visibleIf: {ttransform: 1},
...CALLBACK_PARAMS_TRANSFORM_REPEAT,
});
/** @param updates the texture rotation */
rotation = ParamConfig.FLOAT(0, {
range: [-1, 1],
visibleIf: {ttransform: 1},
...CALLBACK_PARAMS_TRANSFORM_ROTATION,
});
/** @param updates the texture center */
center = ParamConfig.VECTOR2([0, 0], {
visibleIf: {ttransform: 1},
...CALLBACK_PARAMS_TRANSFORM_CENTER,
});
/** @param toggle on to display advanced parameters */
tadvanced = ParamConfig.BOOLEAN(0, {
...CALLBACK_PARAMS_ADVANCED,
separatorBefore: true,
});
/** @param toggle on to allow overriding the texture format */
tformat = ParamConfig.BOOLEAN(0, {
visibleIf: {tadvanced: 1},
...CALLBACK_PARAMS_ADVANCED,
});
/** @param sets the texture format */
format = ParamConfig.INTEGER(DEFAULT.FORMAT, {
visibleIf: {tadvanced: 1, tformat: 1},
menu: {
entries: TEXTURE_FORMATS.map((m) => {
return {
name: Object.keys(m)[0],
value: Object.values(m)[0] as number,
};
}),
},
...CALLBACK_PARAMS_ADVANCED,
});
/** @param toggle on to allow overriding the texture type */
ttype = ParamConfig.BOOLEAN(0, {
visibleIf: {tadvanced: 1},
...CALLBACK_PARAMS_ADVANCED,
});
/** @param sets the texture ty[e] */
type = ParamConfig.INTEGER(DEFAULT.TYPE, {
visibleIf: {tadvanced: 1, ttype: 1},
menu: {
entries: TEXTURE_TYPES.map((m) => {
return {
name: Object.keys(m)[0],
value: Object.values(m)[0] as number,
};
}),
},
...CALLBACK_PARAMS_ADVANCED,
});
};
}
// export function TextureParamConfigFactory<TBase extends Constructor>(defaults: TextureParamConfigDefaults) {
// return (Base: TBase) => {
// return TextureParamConfig(Base, defaults);
// };
// }
class CopTextureParamsConfig extends TextureParamConfig(NodeParamsConfig) {}
const ParamsConfig = new CopTextureParamsConfig();
class TextureCopNode extends TypedCopNode<CopTextureParamsConfig> {
override paramsConfig = ParamsConfig;
public readonly textureParamsController = new TextureParamsController(this);
}
export class TextureParamsController {
constructor(protected node: TextureCopNode) {}
async update(texture: Texture) {
const pv = this.node.pv;
this._updateColorSpace(texture, pv);
this._updateAdvanced(texture, pv);
this._updateMapping(texture, pv);
this._updateWrap(texture, pv);
this._updateFilter(texture, pv);
this._updateFlip(texture, pv);
await this._updateAnisotropy(texture, pv);
this._updateTransform(texture);
}
private _updateColorSpace(texture: Texture, pv: ParamsValueAccessorType<CopTextureParamsConfig>) {
if (isBooleanTrue(pv.tcolorSpace)) {
texture.colorSpace = pv.colorSpace as ColorSpace;
} else {
texture.colorSpace = DEFAULT.COLOR_SPACE;
}
texture.needsUpdate = true;
}
private _updateAdvanced(texture: Texture, pv: ParamsValueAccessorType<CopTextureParamsConfig>) {
if (isBooleanTrue(pv.tadvanced)) {
if (isBooleanTrue(pv.tformat)) {
texture.format = pv.format as AnyPixelFormat;
} else {
texture.format = DEFAULT.FORMAT;
}
if (isBooleanTrue(pv.ttype)) {
texture.type = pv.type as TextureDataType;
} else {
texture.type = DEFAULT.TYPE;
}
}
texture.needsUpdate = true;
}
private _updateMapping(texture: Texture, pv: ParamsValueAccessorType<CopTextureParamsConfig>) {
if (isBooleanTrue(pv.tmapping)) {
texture.mapping = pv.mapping as AnyMapping;
} else {
texture.mapping = DEFAULT.MAPPING;
}
texture.needsUpdate = true;
}
private _updateWrap(texture: Texture, pv: ParamsValueAccessorType<CopTextureParamsConfig>) {
if (isBooleanTrue(pv.twrap)) {
texture.wrapS = pv.wrapS as Wrapping;
texture.wrapT = pv.wrapT as Wrapping;
} else {
texture.wrapS = DEFAULT.WRAPPING;
texture.wrapT = DEFAULT.WRAPPING;
}
texture.needsUpdate = true;
}
private _updateFilter(texture: Texture, pv: ParamsValueAccessorType<CopTextureParamsConfig>) {
if (isBooleanTrue(pv.tminFilter)) {
texture.minFilter = pv.minFilter as MinificationTextureFilter;
} else {
// It makes more sense to:
// - use LinearFilter by default when tfilter is off
// - show LinearMipMapLinearFilter + LinearFilter when on to show the right combination.
// rather than:
// - use LinearMipMapLinearFilter + LinearFilter
// as this would not work when importing a texture to be fed to a cop/envMap
texture.minFilter = LinearFilter;
}
if (isBooleanTrue(pv.tmagFilter)) {
texture.magFilter = pv.magFilter as MagnificationTextureFilter;
} else {
texture.magFilter = LinearFilter;
}
texture.needsUpdate = true;
}
private _updateFlip(texture: Texture, pv: ParamsValueAccessorType<CopTextureParamsConfig>) {
// do not have this in an if block,
// as to be sure this is set to false in case it is set to true
// by the texture loader
texture.flipY = pv.tflipY && pv.flipY;
texture.needsUpdate = true;
}
private _rendererController: CopRendererController | undefined;
private async _updateAnisotropy(texture: Texture, pv: ParamsValueAccessorType<CopTextureParamsConfig>) {
if (!isBooleanTrue(pv.tanisotropy)) {
texture.anisotropy = 1;
return;
}
if (isBooleanTrue(pv.useRendererMaxAnisotropy)) {
texture.anisotropy = await this._maxRendererAnisotropy();
} else {
const anisotropy = pv.anisotropy;
// if the requested anisotropy is 2 or less,
// we can assume that the current renderer can provide it,
// without having to wait for it to be created
if (anisotropy <= 2) {
texture.anisotropy = anisotropy;
} else {
texture.anisotropy = Math.min(anisotropy, await this._maxRendererAnisotropy());
}
}
texture.needsUpdate = true;
}
private async _maxRendererAnisotropy(): Promise<number> {
this._rendererController = this._rendererController || new CopRendererController(this.node);
const renderer = await this._rendererController.waitForRenderer();
if (!renderer) {
return 1;
}
const maxAnisotropy = renderer.capabilities.getMaxAnisotropy();
return maxAnisotropy;
}
private _updateTransform(texture: Texture) {
if (!isBooleanTrue(this.node.pv.ttransform)) {
texture.offset.set(0, 0);
texture.rotation = 0;
texture.repeat.set(1, 1);
texture.center.set(0, 0);
return;
}
this._updateTransformOffset(texture, false);
this._updateTransformRepeat(texture, false);
this._updateTransformRotation(texture, false);
this._updateTransformCenter(texture, false);
texture.updateMatrix();
}
private async _updateTransformOffset(texture: Texture, updateMatrix: boolean) {
texture.offset.copy(this.node.pv.offset);
if (updateMatrix) {
texture.updateMatrix();
}
}
private async _updateTransformRepeat(texture: Texture, updateMatrix: boolean) {
texture.repeat.copy(this.node.pv.repeat);
if (updateMatrix) {
texture.updateMatrix();
}
}
private async _updateTransformRotation(texture: Texture, updateMatrix: boolean) {
texture.rotation = this.node.pv.rotation;
if (updateMatrix) {
texture.updateMatrix();
}
}
private async _updateTransformCenter(texture: Texture, updateMatrix: boolean) {
texture.center.copy(this.node.pv.center);
if (updateMatrix) {
texture.updateMatrix();
}
}
//
//
// CALLBACK
//
//
static PARAM_CALLBACK_update_encoding(node: TextureCopNode) {
const texture = node.containerController.container().texture();
if (!texture) {
return;
}
node.textureParamsController._updateColorSpace(texture, node.pv);
}
static PARAM_CALLBACK_update_mapping(node: TextureCopNode) {
const texture = node.containerController.container().texture();
if (!texture) {
return;
}
node.textureParamsController._updateMapping(texture, node.pv);
}
static PARAM_CALLBACK_update_wrap(node: TextureCopNode) {
const texture = node.containerController.container().texture();
if (!texture) {
return;
}
node.textureParamsController._updateWrap(texture, node.pv);
}
static PARAM_CALLBACK_update_filter(node: TextureCopNode) {
const texture = node.containerController.container().texture();
if (!texture) {
return;
}
node.textureParamsController._updateFilter(texture, node.pv);
}
static PARAM_CALLBACK_update_anisotropy(node: TextureCopNode) {
const texture = node.containerController.container().texture();
if (!texture) {
return;
}
node.textureParamsController._updateAnisotropy(texture, node.pv);
}
static PARAM_CALLBACK_update_flipY(node: TextureCopNode) {
const texture = node.containerController.container().texture();
if (!texture) {
return;
}
node.textureParamsController._updateFlip(texture, node.pv);
}
static PARAM_CALLBACK_update_transform(node: TextureCopNode) {
const texture = node.containerController.container().texture();
if (!texture) {
return;
}
node.textureParamsController._updateTransform(texture);
}
static PARAM_CALLBACK_update_offset(node: TextureCopNode) {
const texture = node.containerController.container().texture();
if (!texture) {
return;
}
node.textureParamsController._updateTransformOffset(texture, true);
}
static PARAM_CALLBACK_update_repeat(node: TextureCopNode) {
const texture = node.containerController.container().texture();
if (!texture) {
return;
}
node.textureParamsController._updateTransformRepeat(texture, true);
}
static PARAM_CALLBACK_update_rotation(node: TextureCopNode) {
const texture = node.containerController.container().texture();
if (!texture) {
return;
}
node.textureParamsController._updateTransformRotation(texture, true);
}
static PARAM_CALLBACK_update_center(node: TextureCopNode) {
const texture = node.containerController.container().texture();
if (!texture) {
return;
}
node.textureParamsController._updateTransformCenter(texture, true);
}
static PARAM_CALLBACK_update_advanced(node: TextureCopNode) {
const texture = node.containerController.container().texture();
if (!texture) {
return;
}
node.textureParamsController._updateAdvanced(texture, node.pv);
}
//
//
// UTILS
//
//
static copyTextureAttributes(texture: Texture, inputTexture: Texture) {
texture.colorSpace = inputTexture.colorSpace;
texture.mapping = inputTexture.mapping;
texture.wrapS = inputTexture.wrapS;
texture.wrapT = inputTexture.wrapT;
texture.minFilter = inputTexture.minFilter;
texture.magFilter = inputTexture.magFilter;
texture.magFilter = inputTexture.magFilter;
texture.anisotropy = inputTexture.anisotropy;
texture.flipY = inputTexture.flipY;
texture.repeat.copy(inputTexture.repeat);
texture.offset.copy(inputTexture.offset);
texture.center.copy(inputTexture.center);
texture.rotation = inputTexture.rotation;
texture.type = inputTexture.type;
texture.format = inputTexture.format;
texture.needsUpdate = true;
}
paramLabelsParams() {
const p = this.node.p;
return [
// colorSpace
p.tcolorSpace,
p.colorSpace,
// mapping
p.tmapping,
p.mapping,
// wrap
p.twrap,
p.wrapS,
p.wrapT,
// filter
p.tminFilter,
p.minFilter,
p.tmagFilter,
p.magFilter,
// flipY
p.tflipY,
p.flipY,
];
}
paramLabels() {
const labels: string[] = [];
const pv = this.node.pv;
if (isBooleanTrue(pv.tcolorSpace)) {
for (const colorSpace of COLOR_SPACES) {
if (colorSpace == pv.colorSpace) {
labels.push(`colorSpace: ${colorSpace}`);
}
}
}
if (isBooleanTrue(pv.tmapping)) {
for (const mapping of MAPPINGS) {
const mappingName = Object.keys(mapping)[0];
const mappingValue = (mapping as any)[mappingName];
if (mappingValue == pv.mapping) {
labels.push(`mapping: ${mappingName}`);
}
}
}
if (isBooleanTrue(pv.twrap)) {
function setWrapping(wrappingAxis: 'wrapS' | 'wrapT') {
for (const wrapping of WRAPPINGS) {
const wrappingName = Object.keys(wrapping)[0];
const wrappingValue = (wrapping as any)[wrappingName];
if (wrappingValue == pv[wrappingAxis]) {
labels.push(`${wrappingAxis}: ${wrappingName}`);
}
}
}
setWrapping('wrapS');
setWrapping('wrapT');
}
if (isBooleanTrue(pv.tminFilter)) {
for (const minFilter of MIN_FILTERS) {
const minFilterName = Object.keys(minFilter)[0];
const minFilterValue = (minFilter as any)[minFilterName];
if (minFilterValue == pv.minFilter) {
labels.push(`minFilter: ${minFilterName}`);
}
}
}
if (isBooleanTrue(pv.tmagFilter)) {
for (const magFilter of MAG_FILTERS) {
const magFilterName = Object.keys(magFilter)[0];
const magFilterValue = (magFilter as any)[magFilterName];
if (magFilterValue == pv.magFilter) {
labels.push(`magFilter: ${magFilterName}`);
}
}
}
if (isBooleanTrue(pv.tflipY)) {
labels.push(`flipY: ${pv.flipY}`);
}
return labels;
}
}