@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
484 lines (482 loc) • 16.1 kB
JavaScript
"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);
}
}
}
}