UNPKG

@polygonjs/polygonjs

Version:

node-based WebGL 3D engine https://polygonjs.com

634 lines (615 loc) 19.9 kB
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; } }