UNPKG

@polygonjs/polygonjs

Version:

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

400 lines (378 loc) 12.4 kB
/** * allows to set up complex animations using keyframes * * */ import {NodeParamsConfig, ParamConfig} from './../utils/params/ParamsConfig'; import {TypedJsNode} from './_Base'; import {JsConnectionPointType} from '../utils/io/connections/Js'; import {ChannelData} from '../../../core/keyframes/KeyframeCommon'; import {sampleData, sampleData0} from '../../../core/keyframes/KeyframeSamples'; import {rangeStartEnd} from '../../../core/ArrayUtils'; import {IntegerParam} from '../../params/Integer'; import {StringParam} from '../../params/String'; import {ConstantJsDefinition} from './utils/JsDefinition'; import {Poly} from '../../Poly'; import {JsLinesCollectionController} from './code/utils/JsLinesCollectionController'; import {JsType} from '../../poly/registers/nodes/types/Js'; import {channelDataToString} from '../../../core/keyframes/KeyframeSerialize'; import {createVariable} from './code/assemblers/_BaseJsPersistedConfigUtils'; import {isArray} from '../../../core/Type'; import {_setArrayLength} from '../../functions/_ArrayUtils'; interface VectorLinesOptions { outputName: string; channelIndex: number; dataType: JsConnectionPointType.VECTOR2 | JsConnectionPointType.VECTOR3 | JsConnectionPointType.VECTOR4; channelCreate: 'channelVector2' | 'channelVector3' | 'channelVector4'; channelGetValue: 'channelValueVector2' | 'channelValueVector3' | 'channelValueVector4'; } // const INIT_DATA: ChannelsData = { // depth: SAMPLE_DATA // }; enum KeyframesJsNodeInputName { time = 'time', } type AvailableJsConnectionType = | JsConnectionPointType.FLOAT | JsConnectionPointType.VECTOR2 | JsConnectionPointType.VECTOR3 | JsConnectionPointType.VECTOR4; export const AVAILABLE_JS_CONNECTION_POINT_TYPES: AvailableJsConnectionType[] = [ JsConnectionPointType.FLOAT, JsConnectionPointType.VECTOR2, JsConnectionPointType.VECTOR3, JsConnectionPointType.VECTOR4, ]; export const ARRAY_SIZE_BY_TYPE: Record<AvailableJsConnectionType, number> = { [JsConnectionPointType.FLOAT]: 1, [JsConnectionPointType.VECTOR2]: 2, [JsConnectionPointType.VECTOR3]: 3, [JsConnectionPointType.VECTOR4]: 4, }; export const CHANNEL_SUFFIX_BY_CHANNEL_INDEX: string[] = ['x', 'y', 'z', 'w']; function visibleIfChannelsCountAtLeast(index: number) { return { visibleIf: rangeStartEnd(index + 1, 10).map((i) => ({channelsCount: i})), }; } function channelTypeParam(index: number) { return ParamConfig.INTEGER(AVAILABLE_JS_CONNECTION_POINT_TYPES.indexOf(JsConnectionPointType.FLOAT), { menu: { entries: AVAILABLE_JS_CONNECTION_POINT_TYPES.map((name, i) => { return {name: name, value: i}; }), }, separatorBefore: true, ...visibleIfChannelsCountAtLeast(index), }); } function channelNameParam(index: number) { return ParamConfig.STRING(`channel${index}`, { ...visibleIfChannelsCountAtLeast(index), }); } function channelDataParam(index: number) { return ParamConfig.STRING(channelDataToString(sampleData()), { ...visibleIfChannelsCountAtLeast(index), }); } export function channelDataInputName(index: number) { return `data${index}`; } class KeyframesJsParamsConfig extends NodeParamsConfig { // main = ParamConfig.FOLDER(); // time = ParamConfig.FLOAT(0, { // step: 0.001, // }); channels = ParamConfig.FOLDER(); channelsCount = ParamConfig.INTEGER(1, { range: [0, 10], rangeLocked: [true, true], }); channelType0 = channelTypeParam(0); channelName0 = channelNameParam(0); data0 = channelDataParam(0); channelType1 = channelTypeParam(1); channelName1 = channelNameParam(1); data1 = channelDataParam(1); channelType2 = channelTypeParam(2); channelName2 = channelNameParam(2); data2 = channelDataParam(2); channelType3 = channelTypeParam(3); channelName3 = channelNameParam(3); data3 = channelDataParam(3); channelType4 = channelTypeParam(4); channelName4 = channelNameParam(4); data4 = channelDataParam(4); channelType5 = channelTypeParam(5); channelName5 = channelNameParam(5); data5 = channelDataParam(5); channelType6 = channelTypeParam(6); channelName6 = channelNameParam(6); data6 = channelDataParam(6); channelType7 = channelTypeParam(7); channelName7 = channelNameParam(7); data7 = channelDataParam(7); channelType8 = channelTypeParam(8); channelName8 = channelNameParam(8); data8 = channelDataParam(8); channelType9 = channelTypeParam(9); channelName9 = channelNameParam(9); data9 = channelDataParam(9); spare = ParamConfig.FOLDER(); } const ParamsConfig = new KeyframesJsParamsConfig(); export class KeyframesJsNode extends TypedJsNode<KeyframesJsParamsConfig> { override readonly paramsConfig = ParamsConfig; static override type() { return JsType.KEYFRAMES; } override initializeNode() { super.initializeNode(); this.io.connection_points.set_input_name_function(this._expectedInputName.bind(this)); this.io.connection_points.set_expected_input_types_function(this._expectedInputTypes.bind(this)); this.io.connection_points.set_expected_output_types_function(this._expectedOutputTypes.bind(this)); this.io.connection_points.set_output_name_function(this._expectedOutputName.bind(this)); } channelTypeParams(): IntegerParam[] { return [ this.p.channelType0, this.p.channelType1, this.p.channelType2, this.p.channelType3, this.p.channelType4, this.p.channelType5, this.p.channelType6, this.p.channelType7, this.p.channelType8, this.p.channelType9, ]; } channelNameParams(): StringParam[] { return [ this.p.channelName0, this.p.channelName1, this.p.channelName2, this.p.channelName3, this.p.channelName4, this.p.channelName5, this.p.channelName6, this.p.channelName7, this.p.channelName8, this.p.channelName9, ]; } channelDataParams(): StringParam[] { return [ this.p.data0, this.p.data1, this.p.data2, this.p.data3, this.p.data4, this.p.data5, this.p.data6, this.p.data7, this.p.data8, this.p.data9, ]; } // dataParam(channelName: string) { // const paramIndex = this.channelNameParams().findIndex((param) => param.value === channelName); // return this.channelDataParams()[paramIndex]; // } // channelsData(target: ChannelsData) { // // const data: ChannelsData = {}; // const existingChannelNames = Object.keys(target); // for (const channelName of existingChannelNames) { // delete target[channelName]; // } // const count = this.pv.channelsCount; // for (let i = 0; i < count; i++) { // const channelName = this._channelNameParams()[i].value; // const channelData = JSON.parse(this.channelDataParams()[i].value); // target[channelName] = channelData; // } // } setChannelType(index: number, type: AvailableJsConnectionType) { const param = this.channelTypeParams()[index]; if (!param) { return; } param.set(AVAILABLE_JS_CONNECTION_POINT_TYPES.indexOf(type)); } setChannelName(index: number, inputName: string) { const param = this.channelNameParams()[index]; if (!param) { return; } param.set(inputName); } setChannelData(index: number, data: ChannelData[]) { const param = this.channelDataParams()[index]; if (!param) { return; } const expectedArraySize = ARRAY_SIZE_BY_TYPE[this._expectedOutputTypes()[index]]; if (expectedArraySize > 1) { param.set(JSON.stringify(data)); } else { const firstElem = data[0]; param.set(JSON.stringify(firstElem)); } } channelData(index: number): ChannelData | ChannelData[] | undefined { const param = this.channelDataParams()[index]; if (!param) { return; } const data = JSON.parse(param.value) as ChannelData | ChannelData[]; const expectedArraySize = ARRAY_SIZE_BY_TYPE[this._expectedOutputTypes()[index]]; if (expectedArraySize > 1) { // make array if not already one if (isArray(data)) { if (data.length != expectedArraySize) { _setArrayLength(data, expectedArraySize, sampleData0); } return data as ChannelData[]; } else { const newData = [data]; _setArrayLength(newData, expectedArraySize, sampleData0); return newData; } } else { // use first element if array if (isArray(data)) { return data[0]; } else { return data; } } } protected _channelsCount(): number { return this.pv.channelsCount; } protected _expectedInputTypes(): JsConnectionPointType[] { // const count = this.pv.channelsCount; return [JsConnectionPointType.FLOAT]; //, ...ArrayUtils.range(0, count).map(() => JsConnectionPointType.STRING)]; } protected _expectedInputName(index: number) { // const count = this.pv.channelsCount; // const dataNames: string[] = ArrayUtils.range(0, count).map((i) => // channelDataInputName(this._expectedOutputName(i)) // ); return [KeyframesJsNodeInputName.time][index]; } protected _expectedOutputTypes() { const count = this._channelsCount(); const params: IntegerParam[] = this.channelTypeParams(); return rangeStartEnd(0, count).map((value, i) => AVAILABLE_JS_CONNECTION_POINT_TYPES[params[i].value]); } protected _expectedOutputName(index: number) { const params: StringParam[] = this.channelNameParams(); return params[index].value; } override setLines(linesController: JsLinesCollectionController) { const time = this.variableForInput(linesController, KeyframesJsNodeInputName.time); const usedOutputNames = this.io.outputs.used_output_names(); const _f = (outputName: string, channelIndex: number) => { if (!usedOutputNames.includes(outputName)) { return; } const funcCurve = Poly.namedFunctionsRegister.getFunction('channelFloat', this, linesController); const funcCurveValue = Poly.namedFunctionsRegister.getFunction('channelValueFloat', this, linesController); const curve = this.jsVarName(`${outputName}_CURVE`); const out = this.jsVarName(outputName); linesController.addDefinitions(this, [ new ConstantJsDefinition( this, linesController, JsConnectionPointType.FLOAT, curve, funcCurve.asString(this.channelDataParams()[channelIndex].value) ), ]); linesController.addBodyOrComputed(this, [ { dataType: JsConnectionPointType.FLOAT, varName: out, value: funcCurveValue.asString(linesController.assembler().memberReference(curve), time), }, ]); }; const _v = (options: VectorLinesOptions) => { const {outputName, channelIndex, dataType, channelCreate, channelGetValue} = options; if (!usedOutputNames.includes(outputName)) { return; } const funcCurve = Poly.namedFunctionsRegister.getFunction(channelCreate, this, linesController); const funcCurveValue = Poly.namedFunctionsRegister.getFunction(channelGetValue, this, linesController); const variable = createVariable(dataType); const curve = this.jsVarName(`${outputName}_CURVE`); const out = this.jsVarName(outputName); linesController.addDefinitions(this, [ new ConstantJsDefinition( this, linesController, dataType, curve, funcCurve.asString(this.channelDataParams()[channelIndex].value) ), ]); if (variable) { const tmpVarName = linesController.addVariable(this, variable); linesController.addBodyOrComputed(this, [ { dataType, varName: out, value: funcCurveValue.asString( linesController.assembler().memberReference(curve), time, tmpVarName ), }, ]); } }; const channelsCount = this._channelsCount(); for (let channelIndex = 0; channelIndex < channelsCount; channelIndex++) { const outputName = this._expectedOutputName(channelIndex); const channelType = this._expectedOutputTypes()[channelIndex]; switch (channelType) { case JsConnectionPointType.FLOAT: { _f(outputName, channelIndex); break; } case JsConnectionPointType.VECTOR2: { _v({ outputName, channelIndex, dataType: channelType, channelCreate: 'channelVector2', channelGetValue: 'channelValueVector2', }); break; } case JsConnectionPointType.VECTOR3: { _v({ outputName, channelIndex, dataType: channelType, channelCreate: 'channelVector3', channelGetValue: 'channelValueVector3', }); break; } case JsConnectionPointType.VECTOR4: { _v({ outputName, channelIndex, dataType: channelType, channelCreate: 'channelVector4', channelGetValue: 'channelValueVector4', }); break; } } } } }