UNPKG

@polygonjs/polygonjs

Version:

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

484 lines (482 loc) 16.1 kB
"use strict"; import { Vector4 } from "three"; import { LiteralConstructsController } from "../LiteralConstructsController"; import { Attribute, CoreAttribute } from "../../../core/geometry/Attribute"; import { BaseTraverser } from "./_Base"; import { VAR_ENTITY, VAR_ENTITIES, FUNC_GET_ENTITIES_ATTRIBUTE, FUNC_GET_ENTITY_ATTRIBUTE_VALUE, FUNC_GET_ENTITY_ATTRIBUTE_VALUE_FUNC } from "../Common"; import { AttributeRequirementsController } from "../AttributeRequirementsController"; import { CoreMath } from "../../../core/math/_Module"; import { CoreString } from "../../../core/String"; import { Poly } from "../../Poly"; import { CoreType, isString, isArray, isVector, isColor } from "../../../core/Type"; import { ThreejsPoint } from "../../../core/geometry/modules/three/ThreejsPoint"; import { corePointClassFactory } from "../../../core/geometry/CoreObjectFactory"; import { VARIABLE_PREFIX } from "./_Base"; const QUOTE = "'"; const ARGUMENTS_SEPARATOR = ", "; const ATTRIBUTE_PREFIX = "@"; const NATIVE_MATH_METHODS = [ "abs", "acos", "acosh", "asin", "asinh", "atan", "atan2", "atanh", "ceil", "cos", "cosh", "exp", "expm1", "floor", "log", "log1p", "log2", "log10", "max", "min", "pow", "round", "sign", "sin", "sinh", "sqrt", "tan", "tanh" ]; const NATIVE_ES6_MATH_METHODS = ["cbrt", "hypot", "log10", "trunc"]; const NATIVE_MATH_METHODS_RENAMED = { math_random: "random" }; const CORE_MATH_METHODS = ["fit", "fit01", "fract", "deg2rad", "rad2deg", "rand", "clamp"]; import { Easing } from "../../../core/math/Easing"; const EASING_METHODS = Object.keys(Easing); const CORE_STRING_METHODS = ["precision"]; const NATIVE_MATH_CONSTANTS = ["E", "LN2", "LN10", "LOG10E", "LOG2E", "PI", "SQRT1_2", "SQRT2"]; const DIRECT_EXPRESSION_FUNCTIONS = {}; NATIVE_MATH_METHODS.forEach((name) => { DIRECT_EXPRESSION_FUNCTIONS[name] = `Math.${name}`; }); NATIVE_ES6_MATH_METHODS.forEach((name) => { DIRECT_EXPRESSION_FUNCTIONS[name] = `Math.${name}`; }); Object.keys(NATIVE_MATH_METHODS_RENAMED).forEach((name) => { const remaped = NATIVE_MATH_METHODS_RENAMED[name]; DIRECT_EXPRESSION_FUNCTIONS[name] = `Math.${remaped}`; }); CORE_MATH_METHODS.forEach((name) => { DIRECT_EXPRESSION_FUNCTIONS[name] = `Core.Math.${name}`; }); EASING_METHODS.forEach((name) => { DIRECT_EXPRESSION_FUNCTIONS[name] = `Core.Math.Easing.${name}`; }); CORE_STRING_METHODS.forEach((name) => { DIRECT_EXPRESSION_FUNCTIONS[name] = `Core.String.${name}`; }); const LITERAL_CONSTRUCT = { if: LiteralConstructsController.if }; const GLOBAL_CONSTANTS = {}; NATIVE_MATH_CONSTANTS.forEach((name) => { GLOBAL_CONSTANTS[name] = `Math.${name}`; }); const PROPERTY_OFFSETS = { x: 0, y: 1, z: 2, w: 3, r: 0, g: 1, b: 2 }; const Core = { Math: CoreMath, String: CoreString }; function getEntitiesAttributes(entities, attribName) { const firstEntity = entities[0]; if (firstEntity && firstEntity instanceof ThreejsPoint) { return firstEntity.attribute(attribName); } else { return entities.map((e) => e.attribValue(attribName, new Vector4())); } } function getCorePointAttribValue(entity, attribName, array, attributeSize, propertyOffset) { return array[entity.index() * attributeSize + propertyOffset]; } const VECTOR_PROPERTY_NAME_BY_OFFSET = { 0: "x", 1: "y", 2: "z", 3: "w" }; const COLOR_PROPERTY_NAME_BY_OFFSET = { 0: "r", 1: "g", 2: "b", 3: "r" }; const _target = new Vector4(); function getCoreEntityAttribValue(entity, attribName, array, attributeSize, propertyOffset) { const value = entity.attribValue(attribName, _target); if (isArray(value)) { return value[propertyOffset]; } if (isVector(value)) { return value[VECTOR_PROPERTY_NAME_BY_OFFSET[propertyOffset]]; } if (isColor(value)) { return value[COLOR_PROPERTY_NAME_BY_OFFSET[propertyOffset]]; } return value; } function getCoreEntityAttribValueFunc(entity) { if (entity instanceof ThreejsPoint) { return getCorePointAttribValue; } return getCoreEntityAttribValue; } const FUNCTION_ARGS_DICT = { corePointClassFactory, ThreejsPoint, Core, CoreType, [FUNC_GET_ENTITIES_ATTRIBUTE]: getEntitiesAttributes, [FUNC_GET_ENTITY_ATTRIBUTE_VALUE_FUNC]: getCoreEntityAttribValueFunc }; const FUNCTION_ARG_NAMES = Object.keys(FUNCTION_ARGS_DICT); const FUNCTION_ARGS = FUNCTION_ARG_NAMES.map((argName) => FUNCTION_ARGS_DICT[argName]); export class FunctionGenerator extends BaseTraverser { // public jsep_dependencies: JsepDependency[] = [] // public jsep_nodes_by_missing_paths: JsepsByString = {} // private string_generator: ExpressionStringGenerator = new ExpressionStringGenerator() constructor(param) { super(param); this.param = param; this._entitiesDependent = false; this._attribute_requirements_controller = new AttributeRequirementsController(); this.methods = []; this.method_index = -1; this.methodDependencies = []; this.immutableDependencies = []; } entitiesDependent() { return this._entitiesDependent; } parseTree(parsedTree) { this.reset(); if (!parsedTree.errorMessage()) { try { this._attribute_requirements_controller = new AttributeRequirementsController(); const node = parsedTree.node(); if (node) { const function_main_string = this.traverse_node(node); if (function_main_string && !this.isErrored()) { this.function_main_string = function_main_string; } } else { console.warn("no parsedTree.node"); } } catch (e) { console.warn(`error in expression for param ${this.param.path()}`); console.warn(e); } if (this.function_main_string) { try { const body = this._functionBody(); this.function = new Function( ...FUNCTION_ARG_NAMES, "param", "methods", "_set_error_from_error", ` try { ${body} } catch(e) { _set_error_from_error(e) return null; }` ); } catch (e) { console.warn(e); this.setError("cannot generate function"); } } else { this.setError("cannot generate function body"); } } else { this.setError("cannot parse expression"); } } reset() { super.reset(); this.function_main_string = void 0; this.methods = []; this.method_index = -1; this.function = void 0; this._entitiesDependent = false; this.methodDependencies = []; this.immutableDependencies = []; } _functionBody() { const entitiesDependent = this._entitiesDependent; if (entitiesDependent) { return ` const ${VAR_ENTITIES} = param.expressionController.entities(); if(${VAR_ENTITIES} && ${VAR_ENTITIES}.length > 0){ return new Promise( async (resolve, reject)=>{ try { const entityCallback = param.expressionController.entityCallback(); // assign_attributes_lines ${this._attribute_requirements_controller.assignAttributesLines()} // check if attributes are present if( ${this._attribute_requirements_controller.attributePresenceCheckLine()} ){ // assign function const ${FUNC_GET_ENTITY_ATTRIBUTE_VALUE} = ${FUNC_GET_ENTITY_ATTRIBUTE_VALUE_FUNC}(entities[0]); // assign_arrays_lines ${this._attribute_requirements_controller.assignArraysLines()} for(const ${VAR_ENTITY} of ${VAR_ENTITIES}){ result = ${this.function_main_string}; entityCallback(${VAR_ENTITY}, result); } resolve() } else { const missingAttributes = ${this._attribute_requirements_controller.missingAttributesLine()}().join(', '); const error = new Error('attribute ' + missingAttributes + ' not found') _set_error_from_error(error) reject(error) } }catch(e){ _set_error_from_error(e) reject(e) } }) } return []`; } else { return ` return new Promise( async (resolve, reject)=>{ try { const value = ${this.function_main_string} resolve(value) } catch(e) { _set_error_from_error(e) reject() } }) `; } } evalAllowed() { return this.function != null; } evalFunction() { if (this.function) { this.clearError(); const result = this.function(...FUNCTION_ARGS, this.param, this.methods, this._set_error_from_error_bound); return result; } } // // // TRAVERSE METHODS // // traverse_CallExpression(node) { const methodArguments = node.arguments.map((arg) => { return this.traverse_node(arg); }); const callee = node.callee; const method_name = callee.name; if (method_name) { const literal_contruct = LITERAL_CONSTRUCT[method_name]; if (literal_contruct) { return literal_contruct(methodArguments); } const arguments_joined = `${methodArguments.join(ARGUMENTS_SEPARATOR)}`; const direct_function_name = DIRECT_EXPRESSION_FUNCTIONS[method_name]; if (direct_function_name) { return `${direct_function_name}(${arguments_joined})`; } const expressionRegister = Poly.expressionsRegister; const indirect_method = expressionRegister.getMethod(method_name); if (indirect_method) { const pathNode = node.arguments[0]; const functionString = `return ${methodArguments[0]}`; let pathArgumentFunction; let pathArgument; try { pathArgumentFunction = new Function(functionString); pathArgument = pathArgumentFunction(); } catch { } this._createMethodAndDependencies(method_name, pathArgument, pathNode); return `(await methods[${this.method_index}].processArguments([${arguments_joined}]))`; } else { const available_methods = expressionRegister.availableMethods().join(", "); const message = `method not found (${method_name}), available methods are: ${available_methods}`; Poly.warn(message); } } this.setError(`unknown method: ${method_name}`); } traverse_BinaryExpression(node) { return `(${this.traverse_node(node.left)} ${node.operator} ${this.traverse_node(node.right)})`; } // protected override traverse_LogicalExpression(node: jsep.LogicalExpression): string { // // || or && // // if(node.right.type == 'Identifier'){ // // this.set_error(`cannot have identifier after ${node.operator}`) // // return "" // // } // return `(${this.traverse_node(node.left)} ${node.operator} ${this.traverse_node(node.right)})`; // } // protected override traverse_MemberExpression(node: jsep.MemberExpression): string { // return `${this.traverse_node(node.object)}.${this.traverse_node(node.property)}`; // } traverse_UnaryExpression(node) { if (node.operator === ATTRIBUTE_PREFIX) { this._entitiesDependent = true; let argument = node.argument; let attributeName; let property; switch (argument.type) { case "Identifier": { const argument_identifier = argument; attributeName = argument_identifier.name; break; } case "MemberExpression": { const argument_member_expression = argument; const attrib_node = argument_member_expression.object; const property_node = argument_member_expression.property; attributeName = attrib_node.name; property = property_node.name; break; } } if (attributeName) { attributeName = CoreAttribute.remapName(attributeName); if (attributeName == Attribute.POINT_INDEX || attributeName == Attribute.OBJECT_INDEX) { return `((${VAR_ENTITY} != null) ? ${VAR_ENTITY}.index() : 0)`; } else { const var_attribute_size = this._attribute_requirements_controller.varAttributeSize(attributeName); const var_array = this._attribute_requirements_controller.varArray(attributeName); this._attribute_requirements_controller.add(attributeName); let propertyOffset = property ? PROPERTY_OFFSETS[property] : 0; if (propertyOffset == null) { propertyOffset = 0; } return `${FUNC_GET_ENTITY_ATTRIBUTE_VALUE}(${VAR_ENTITY}, '${attributeName}', ${var_array}, ${var_attribute_size}, ${propertyOffset})`; } } else { console.warn("attribute not found"); return ""; } } else { return `${node.operator}${this.traverse_node(node.argument)}`; } } // protected override traverse_Literal(node: jsep.Literal): string { // return `${node.raw}`; // 5 or 'string' (raw will include quotes) // } traverse_Identifier(node) { const identifier_first_char = node.name[0]; if (identifier_first_char == VARIABLE_PREFIX) { const identifier_name_without_dollar_sign = node.name.substring(1); const direct_constant_name = GLOBAL_CONSTANTS[identifier_name_without_dollar_sign]; if (direct_constant_name) { return direct_constant_name; } const method_name = `traverse_Identifier_${identifier_name_without_dollar_sign}`; const method = this[method_name]; if (method) { return this[method_name](); } else { this.setError(`identifier unknown: ${node.name}`); } } else { return node.name; } } // // // Identifier methods (called from Identifier_body) // // traverse_Identifier_F() { this.immutableDependencies.push(this.param.scene().timeController.graphNode); return `param.scene().timeController.frame()`; } // protected traverse_Identifier_FPS(): string { // this.immutable_dependencies.push(this.param.scene().timeController.graphNode); // return `param.scene().timeController.fps`; // } traverse_Identifier_T() { this.immutableDependencies.push(this.param.scene().timeController.graphNode); return `param.scene().timeController.time()`; } traverse_Identifier_OS() { const nameNode = this.param.node.nameController.graphNode(); this.param.addGraphInput(nameNode); return `param.node.name()`; } traverse_Identifier_CH() { return `${QUOTE}${this.param.name()}${QUOTE}`; } traverse_Identifier_CEX() { return this._method_centroid("x"); } traverse_Identifier_CEY() { return this._method_centroid("y"); } traverse_Identifier_CEZ() { return this._method_centroid("z"); } // TODO: // '$OS': '_eval_identifier_as_node_name', // '$BBX': '_eval_identifier_as_bounding_box_relative', _method_centroid(component) { const method_arguments = [0, `${QUOTE}${component}${QUOTE}`]; const arguments_joined = method_arguments.join(ARGUMENTS_SEPARATOR); this._createMethodAndDependencies("centroid", 0); return `(await methods[${this.method_index}].processArguments([${arguments_joined}]))`; } // // // Methods dependencies // // _createMethodAndDependencies(methodName, pathArgument, pathNode) { const expressionRegister = Poly.expressionsRegister; const methodConstructor = expressionRegister.getMethod(methodName); if (!methodConstructor) { const availableMethods = expressionRegister.availableMethods(); const message = `method not found (${methodName}), available methods are: ${availableMethods.join(", ")}`; this.setError(message); Poly.warn(message); return; } const method = new methodConstructor(this.param); this.method_index += 1; this.methods[this.method_index] = method; const methodDependency = method.findDependency({ indexOrPath: pathArgument }); if (methodDependency) { if (pathNode) { methodDependency.set_jsep_node(pathNode); } this.methodDependencies.push(methodDependency); } else { if (pathNode && isString(pathArgument)) { this.param.scene().missingExpressionReferencesController.register(this.param, pathArgument, pathNode); } } } }