UNPKG

polygonjs-engine

Version:

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

602 lines (563 loc) 17.7 kB
/** * The following expressions are available to use in most parameters: * * ## variables * * - $F: current frame * - $T: current time * - $CH: current param name * - $CEX: input centroid x component * - $CEY: input centroid y component * - $CEZ: input centroid z component * * Those variables are aliases to the javascript math module: * * - $E * - $LN2 * - $LN10 * - $LOG10E * - $LOG2E * - $PI * - $SQRT1_2 * - $SQRT2 * * ## math expressions * * The following are native javascript functions: * * - abs * - acos * - acosh * - asin * - asinh * - atan * - atan2 * - atanh * - ceil * - cos * - cosh * - exp * - expm1 * - floor * - log * - log1p * - log2 * - log10 * - max * - min * - pow * - random (which aliases to Math.rand()) * - round * - sign * - sin * - sinh * - sqrt * - tan * - tanh * * If you are targetting ES6 (available in modern browsers), you can also have: * * - cbrt * - hypot * - log10 * - trunc * * The following are aliases from the [Polygonjs CoreMath](https://github.com/polygonjs/polygonjs-engine/blob/master/src/core/math/_Module.ts) module: * * - fit * - fit01 * - fract * - deg2rad * - rad2deg * - rand * - clamp * * And the following are alias to the [Polygonjs Easing](https://github.com/polygonjs/polygonjs-engine/blob/master/src/core/math/Easing.ts) module: * * - linear * - ease_i * - ease_o * - ease_io * - ease_i2 * - ease_o2 * - ease_io2 * - ease_i3 * - ease_o3 * - ease_io3 * - ease_i4 * - ease_o4 * - ease_io4 * - ease_i_sin * - ease_o_sin * - ease_io_sin * - ease_i_elastic * - ease_o_elastic * - ease_io_elastic * * * ## string expressions: * * - precision (alias to the [CoreString](https://github.com/polygonjs/polygonjs-engine/blob/master/src/core/String.ts) module precision method) * - [strCharsCount](/docs/expressions/strCharsCount) * - [strConcat](/docs/expressions/strConcat) * - [strIndex](/docs/expressions/strIndex) * - [strSub](/docs/expressions/strSub) * * */ import {BaseParamType} from '../../params/_Base'; import {CoreGraphNode} from '../../../core/graph/CoreGraphNode'; import {ParsedTree} from './ParsedTree'; import {LiteralConstructsController, LiteralConstructMethod} from '../LiteralConstructsController'; import {BaseMethod} from '../methods/_Base'; import {CoreAttribute} from '../../../core/geometry/Attribute'; // import {JsepsByString} from '../DependenciesController' import jsep from 'jsep'; // import {Vector3} from 'three/src/math/Vector3' type LiteralConstructDictionary = PolyDictionary<LiteralConstructMethod>; type AnyDictionary = PolyDictionary<any>; 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: AnyDictionary = { 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: AnyDictionary = {}; 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: LiteralConstructDictionary = { if: LiteralConstructsController.if, }; const GLOBAL_CONSTANTS: PolyDictionary<string> = {}; NATIVE_MATH_CONSTANTS.forEach((name) => { GLOBAL_CONSTANTS[name] = `Math.${name}`; }); const QUOTE = "'"; const ARGUMENTS_SEPARATOR = ', '; const ATTRIBUTE_PREFIX = '@'; import {VARIABLE_PREFIX} from './_Base'; const PROPERTY_OFFSETS: AnyDictionary = { x: 0, y: 1, z: 2, w: 3, r: 0, g: 1, b: 2, }; import {BaseTraverser} from './_Base'; import {MethodDependency} from '../MethodDependency'; import {AttributeRequirementsController} from '../AttributeRequirementsController'; import {CoreMath} from '../../../core/math/_Module'; import {CoreString} from '../../../core/String'; // import {AsyncFunction} from '../../../core/AsyncFunction'; import {Poly} from '../../Poly'; import {CoreType} from '../../../core/Type'; import {PolyDictionary} from '../../../types/GlobalTypes'; export class FunctionGenerator extends BaseTraverser { private function: Function | undefined; private _attribute_requirements_controller = new AttributeRequirementsController(); private function_main_string: string | undefined; private methods: BaseMethod[] = []; private method_index: number = -1; public method_dependencies: MethodDependency[] = []; public immutable_dependencies: CoreGraphNode[] = []; // public jsep_dependencies: JsepDependency[] = [] // public jsep_nodes_by_missing_paths: JsepsByString = {} // private string_generator: ExpressionStringGenerator = new ExpressionStringGenerator() constructor(public param: BaseParamType) { super(param); } public parse_tree(parsed_tree: ParsedTree) { this.reset(); if (parsed_tree.error_message == null) { try { // this.function_pre_entities_loop_lines = []; this._attribute_requirements_controller.reset(); // this.function_pre_body = '' if (parsed_tree.node) { const function_main_string = this.traverse_node(parsed_tree.node); if (function_main_string && !this.is_errored()) { this.function_main_string = function_main_string; } } else { console.warn('no parsed_tree.node'); } } catch (e) { console.warn(`error in expression for param ${this.param.fullPath()}`); console.warn(e); } if (this.function_main_string) { try { // not sure why I needed AsyncFunction this.function = new Function( 'Core', 'param', 'methods', '_set_error_from_error', ` try { ${this.function_body()} } catch(e) { _set_error_from_error(e) return null; }` ); } catch (e) { console.warn(e); this.set_error('cannot generate function'); } } else { this.set_error('cannot generate function body'); } } else { this.set_error('cannot parse expression'); } } reset() { super.reset(); this.function_main_string = undefined; this.methods = []; this.method_index = -1; this.function = undefined; this.method_dependencies = []; this.immutable_dependencies = []; } function_body() { if (this.param.options.is_expression_for_entities()) { return ` const entities = param.expression_controller.entities(); if(entities){ return new Promise( async (resolve, reject)=>{ let entity; const entity_callback = param.expression_controller.entity_callback(); ${this._attribute_requirements_controller.assign_attributes_lines()} if( ${this._attribute_requirements_controller.attribute_presence_check_line()} ){ ${this._attribute_requirements_controller.assign_arrays_lines()} for(let index=0; index < entities.length; index++){ entity = entities[index]; result = ${this.function_main_string}; entity_callback(entity, result); } resolve() } else { const error = new Error('attribute not found') _set_error_from_error(error) reject(error) } }) } 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() } }) `; } } eval_allowed(): boolean { return this.function != null; } eval_function() { // this.param.entity_attrib_values = this.param.entity_attrib_values || {} // this.param.entity_attrib_values.position = // this.param.entity_attrib_values.position || new THREE.Vector3() if (this.function) { this.clear_error(); const Core = { Math: CoreMath, String: CoreString, }; const result = this.function(Core, this.param, this.methods, this._set_error_from_error_bound); return result; } } // // // TRAVERSE METHODS // // protected traverse_CallExpression(node: jsep.CallExpression): string | undefined { const method_arguments = node.arguments.map((arg) => { return this.traverse_node(arg); }); const callee = node.callee as jsep.Identifier; const method_name = callee.name; if (method_name) { // literal construct (if...) const literal_contruct = LITERAL_CONSTRUCT[method_name]; if (literal_contruct) { return literal_contruct(method_arguments); } // direct expressions (Math.floor, Math.sin...) const arguments_joined = `${method_arguments.join(ARGUMENTS_SEPARATOR)}`; const direct_function_name = DIRECT_EXPRESSION_FUNCTIONS[method_name]; if (direct_function_name) { return `${direct_function_name}(${arguments_joined})`; } // indirect methods (points_count, asset...) const expressionRegister = Poly.expressionsRegister; const indirect_method = expressionRegister.getMethod(method_name); if (indirect_method) { const path_node = node.arguments[0]; // const path_argument = this.string_generator.traverse_node(path_node) const function_string = `return ${method_arguments[0]}`; let path_argument_function; let path_argument = []; try { path_argument_function = new Function(function_string); path_argument = path_argument_function(); } catch { // path_argument_function = new AsyncFunction(function_string) // it looks like if the input contains an await, // it is because it has been generated by another indirect function. // This means that the dependencies have been generated already // so we may not need to do it now } this._create_method_and_dependencies(method_name, path_argument, path_node); return `(await methods[${this.method_index}].process_arguments([${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.set_error(`unknown method: ${method_name}`); } protected traverse_BinaryExpression(node: jsep.BinaryExpression): string { // 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 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 traverse_MemberExpression(node: jsep.MemberExpression): string { return `${this.traverse_node(node.object)}.${this.traverse_node(node.property)}`; } protected traverse_UnaryExpression(node: jsep.UnaryExpression): string { if (node.operator === ATTRIBUTE_PREFIX) { let argument = node.argument; let attribute_name; let property; switch (argument.type) { case 'Identifier': { const argument_identifier = (<unknown>argument) as jsep.Identifier; attribute_name = argument_identifier.name; break; } case 'MemberExpression': { const argument_member_expression = (<unknown>argument) as jsep.MemberExpression; const attrib_node = argument_member_expression.object as jsep.Identifier; const property_node = argument_member_expression.property as jsep.Identifier; attribute_name = attrib_node.name; property = property_node.name; break; } } // this.function_pre_body += ` // param.entity_attrib_value(${QUOTE}${attrib_node.name}${QUOTE}, param.entity_attrib_values.position); // ` if (attribute_name) { attribute_name = CoreAttribute.remap_name(attribute_name); if (attribute_name == 'ptnum') { return '((entity != null) ? entity.index() : 0)'; } else { const var_attribute_size = this._attribute_requirements_controller.var_attribute_size( attribute_name ); const var_array = this._attribute_requirements_controller.var_array(attribute_name); this._attribute_requirements_controller.add(attribute_name); if (property) { const property_offset = PROPERTY_OFFSETS[property]; return `${var_array}[entity.index()*${var_attribute_size}+${property_offset}]`; } else { return `${var_array}[entity.index()*${var_attribute_size}]`; } } } else { console.warn('attribute not found'); return ''; } } else { return `${node.operator}${this.traverse_node(node.argument)}`; // -5 } } protected traverse_Literal(node: jsep.Literal): string { return `${node.raw}`; // 5 or 'string' (raw will include quotes) } protected traverse_Identifier(node: jsep.Identifier): string | undefined { const identifier_first_char = node.name[0]; if (identifier_first_char == VARIABLE_PREFIX) { const identifier_name_without_dollar_sign = node.name.substr(1); // globals constants: Math.PI or Math.E const direct_constant_name = GLOBAL_CONSTANTS[identifier_name_without_dollar_sign]; if (direct_constant_name) { return direct_constant_name; } // scene or node globals: $F, $FPS, $T, $CH, $OS const method_name = `traverse_Identifier_${identifier_name_without_dollar_sign}`; const method = (this as any)[method_name]; if (method) { return (this as any)[method_name](); } else { this.set_error(`identifier unknown: ${node.name}`); } } else { return node.name; // @ptnum will call this method and return "ptnum" } } // // // Identifier methods (called from Identifier_body) // // protected traverse_Identifier_F(): string { this.immutable_dependencies.push(this.param.scene().timeController.graph_node); return `param.scene().timeController.frame`; } protected traverse_Identifier_FPS(): string { this.immutable_dependencies.push(this.param.scene().timeController.graph_node); return `param.scene().timeController.fps`; } protected traverse_Identifier_T(): string { this.immutable_dependencies.push(this.param.scene().timeController.graph_node); return `param.scene().timeController.time`; } protected traverse_Identifier_CH(): string { return `${QUOTE}${this.param.name()}${QUOTE}`; } protected traverse_Identifier_CEX(): string { return this._method_centroid('x'); } protected traverse_Identifier_CEY(): string { return this._method_centroid('y'); } protected traverse_Identifier_CEZ(): string { return this._method_centroid('z'); } // TODO: // '$OS': '_eval_identifier_as_node_name', // '$BBX': '_eval_identifier_as_bounding_box_relative', private _method_centroid(component: string): string { const method_arguments = [0, `${QUOTE}${component}${QUOTE}`]; const arguments_joined = method_arguments.join(ARGUMENTS_SEPARATOR); this._create_method_and_dependencies('centroid', 0); return `(await methods[${this.method_index}].process_arguments([${arguments_joined}]))`; } // // // Methods dependencies // // private _create_method_and_dependencies( method_name: string, path_argument: number | string, path_node?: jsep.Expression ) { const expressionRegister = Poly.expressionsRegister; const method_constructor = expressionRegister.getMethod(method_name); if (!method_constructor) { const available_methods = expressionRegister.availableMethods(); const message = `method not found (${method_name}), available methods are: ${available_methods.join(', ')}`; this.set_error(message); Poly.warn(message); return; } const method = new method_constructor(this.param) as BaseMethod; this.method_index += 1; this.methods[this.method_index] = method; if (method.require_dependency()) { const method_dependency = method.find_dependency(path_argument); if (method_dependency) { if (path_node) { method_dependency.set_jsep_node(path_node); } this.method_dependencies.push(method_dependency); } else { if (path_node && CoreType.isString(path_argument)) { this.param .scene() .missingExpressionReferencesController.register(this.param, path_node, path_argument); } } } // method_dependencies.resolved_graph_nodes.forEach((graph_node)=>{ // if(path_node){ // const jsep_dependency = new JsepDependency(graph_node, path_node) // this.jsep_dependencies.push(jsep_dependency) // } else { // this.immutable_dependencies.push(graph_node) // } // }) // if(path_node){ // reference_search_result.missing_paths.forEach((path)=>{ // this.jsep_nodes_by_missing_paths[path] = this.jsep_nodes_by_missing_paths[path] || [] // this.jsep_nodes_by_missing_paths[path].push(path_node) // }) // } } }