@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
183 lines (170 loc) • 5.52 kB
text/typescript
import {Constructor} from '../../../../types/GlobalTypes';
import {BaseController} from './_BaseController';
import {Material} from 'three';
import {Texture} from 'three';
import {BaseMatNodeType} from '../_Base';
import {ParamConfig} from '../../utils/params/ParamsConfig';
import {NodeContext} from '../../../poly/NodeContext';
import {NodePathParam} from '../../../params/NodePath';
import {BooleanParam} from '../../../params/Boolean';
import {BaseNodeType} from '../../_Base';
import {BaseParamType} from '../../../params/_Base';
import {Poly} from '../../../Poly';
export function TextureMapParamConfig<TBase extends Constructor>(Base: TBase) {
return class Mixin extends Base {
useMap = ParamConfig.BOOLEAN(0);
map = ParamConfig.NODE_PATH('', {visibleIf: {useMap: 1}});
};
}
type FilterFlags<Base, Condition> = {
[Key in keyof Base]: Base[Key] extends Condition ? Key : never;
};
type AllowedNames<Base, Condition> = FilterFlags<Base, Condition>[keyof Base];
type SubType<Base, Condition> = Pick<Base, AllowedNames<Base, Condition>>;
export function BooleanParamOptions(controller_class: typeof BaseTextureMapController) {
return {
cook: false,
callback: (node: BaseNodeType, param: BaseParamType) => {
controller_class.update(node as BaseMatNodeType);
},
};
}
interface NodePathOptionsOptions {
types?: string[];
}
export function NodePathOptions(
controller: typeof BaseTextureMapController,
use_map_name: string,
options?: NodePathOptionsOptions
) {
return {
visibleIf: {[use_map_name]: 1},
nodeSelection: {context: NodeContext.COP, types: options?.types},
cook: false,
callback: (node: BaseNodeType, param: BaseParamType) => {
controller.update(node as BaseMatNodeType);
},
};
}
type TextureUpdateCallback<O extends Object> = (
material: Material,
object: O,
mat_attrib_name: keyof SubType<O, Texture | null>,
texture: Texture
) => void;
type TextureRemoveCallback<O extends Object> = (
material: Material,
object: O,
mat_attrib_name: keyof SubType<O, Texture | null>
) => void;
type BaseTextureControllerCurrentMaterial = Material;
const CALLBACK_NAME = 'TextureController';
export abstract class BaseTextureMapController extends BaseController {
constructor(protected override node: BaseMatNodeType) {
super(node);
}
protected add_hooks(use_map_param: BooleanParam, path_param: NodePathParam) {
use_map_param.addPostDirtyHook(CALLBACK_NAME, this.updateBound);
path_param.addPostDirtyHook(CALLBACK_NAME, this.updateBound);
}
static async update(node: BaseNodeType) {}
private updateBound = this.update.bind(this);
async update() {}
async _update<M extends BaseTextureControllerCurrentMaterial>(
material: M,
mat_attrib_name: string,
use_map_param: BooleanParam,
path_param: NodePathParam
) {
const mat = material as Material;
const attr_name = mat_attrib_name as keyof SubType<Material, Texture | null>;
await this._update_texture_on_material(mat, attr_name, use_map_param, path_param);
}
//
//
// FOR CASES WHERE THE TEXTURE IS ON THE MATERIAL
//
//
async _update_texture_on_material<M extends Material>(
material: M,
mat_attrib_name: keyof SubType<M, Texture | null>,
use_map_param: BooleanParam,
path_param: NodePathParam
) {
await this._update_required_attribute(
material,
material,
mat_attrib_name,
use_map_param,
path_param,
this._apply_texture_on_material.bind(this),
this._remove_texture_from_material.bind(this)
);
}
private _apply_texture_on_material<M extends Material>(
material: Material,
texture_owner: M,
mat_attrib_name: keyof SubType<M, Texture | null>,
newTexture: Texture
) {
const currentTexture = (<unknown>texture_owner[mat_attrib_name]) as Texture | undefined;
let textureChangeRequired = false;
if (currentTexture) {
if (currentTexture.uuid != newTexture.uuid) {
textureChangeRequired = true;
}
}
if (currentTexture == null || textureChangeRequired) {
texture_owner[mat_attrib_name] = newTexture as any;
material.needsUpdate = true;
}
Poly.onSceneUpdatedHooks.runHooks();
}
private _remove_texture_from_material<M extends Material>(
material: Material,
texture_owner: M,
mat_attrib_name: keyof SubType<M, Texture | null>
) {
if (texture_owner[mat_attrib_name]) {
texture_owner[mat_attrib_name] = null as any;
material.needsUpdate = true;
}
Poly.onSceneUpdatedHooks.runHooks();
}
//
//
// MAIN ALGO to decide if texture should be updated
//
//
private async _update_required_attribute<O extends Object>(
material: Material,
texture_owner: O,
mat_attrib_name: keyof SubType<O, Texture | null>,
use_map_param: BooleanParam,
path_param: NodePathParam,
update_callback: TextureUpdateCallback<O>,
remove_callback: TextureRemoveCallback<O>
) {
if (use_map_param.isDirty()) {
await use_map_param.compute();
}
const use_map: boolean = use_map_param.value;
if (use_map) {
if (path_param.isDirty()) {
await path_param.compute();
}
const texture_node = path_param.value.nodeWithContext(NodeContext.COP);
if (texture_node) {
const container = await texture_node.compute();
const texture = container.texture();
if (texture) {
await update_callback(material, texture_owner, mat_attrib_name, texture);
return;
}
}
}
// this is not wrapped in an else clause after the "if (use_map) {"
// as we should come here after any of the errors above, if any is triggered
remove_callback(material, texture_owner, mat_attrib_name);
}
}