WIP: Add a math expression geometry node #115112

Draft
Andrew-Downing wants to merge 19 commits from Andrew-Downing/blender:node_geo_math_expression into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
15 changed files with 1741 additions and 0 deletions

View File

@ -595,6 +595,7 @@ class NODE_MT_category_GEO_UTILITIES_MATH(Menu):
node_add_menu.add_node_type(layout, "ShaderNodeMapRange")
node_add_menu.add_node_type(layout, "ShaderNodeMath")
node_add_menu.add_node_type(layout, "ShaderNodeMix")
node_add_menu.add_node_type(layout, "GeometryNodeMathExpression")
node_add_menu.draw_assets_for_catalog(layout, "Utilities/Math")

View File

@ -1316,6 +1316,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i
#define GEO_NODE_INPUT_EDGE_SMOOTH 2115
#define GEO_NODE_SPLIT_TO_INSTANCES 2116
#define GEO_NODE_INPUT_NAMED_LAYER_SELECTION 2117
#define GEO_NODE_MATH_EXPRESSION 0x3A84977D // TODO: this
/** \} */

View File

@ -1892,6 +1892,12 @@ typedef struct NodeShaderMix {
char _pad[3];
} NodeShaderMix;
typedef struct NodeGeometryMathExpression {
char variables[1024]; // TODO: maybe change these this to work like NodeInputString
char expression[1024];
uint32_t output_type;
} NodeGeometryMathExpression;
/* script node mode */
enum {
NODE_SCRIPT_INTERNAL = 0,
@ -2760,3 +2766,8 @@ typedef enum NodeCombSepColorMode {
NODE_COMBSEP_COLOR_HSV = 1,
NODE_COMBSEP_COLOR_HSL = 2,
} NodeCombSepColorMode;
typedef enum GeometryNodeMathExpressionOutput {
GEO_NODE_MATH_EXPRESSION_OUTPUT_FLOAT = 0,
GEO_NODE_MATH_EXPRESSION_OUTPUT_VECTOR = 1,
} GeometryNodeMathExpressionOutput;

View File

@ -9327,6 +9327,42 @@ static void def_geo_string_to_curves(StructRNA *srna)
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
}
static void def_geo_math_expression(StructRNA *srna)
{
static const EnumPropertyItem rna_node_geometry_math_expression_output[] = {
{GEO_NODE_MATH_EXPRESSION_OUTPUT_FLOAT,
"OUTPUT_FLOAT",
0,
"Float",
"Float"},
{GEO_NODE_MATH_EXPRESSION_OUTPUT_VECTOR,
"OUTPUT_VECTOR",
0,
"Vector",
"Vector"},
{0, nullptr, 0, nullptr, nullptr},
};
PropertyRNA *prop;
RNA_def_struct_sdna_from(srna, "NodeGeometryMathExpression", "storage");
prop = RNA_def_property(srna, "variables", PROP_STRING, PROP_NONE);
RNA_def_property_ui_text(prop, "Input Variables", "The input variables");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
prop = RNA_def_property(srna, "output_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, nullptr, "output_type");
RNA_def_property_enum_items(prop, rna_node_geometry_math_expression_output);
RNA_def_property_enum_default(prop, GEO_NODE_MATH_EXPRESSION_OUTPUT_FLOAT);
RNA_def_property_ui_text(prop, "Output Type", "Output type of the node");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
prop = RNA_def_property(srna, "expression", PROP_STRING, PROP_NONE);
RNA_def_property_ui_text(prop, "Expression", "The math Expression");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
}
static void rna_def_shader_node(BlenderRNA *brna)
{
StructRNA *srna;

View File

@ -370,6 +370,7 @@ DefNode(GeometryNode, GEO_NODE_INSTANCES_TO_POINTS, 0, "INSTANCES_TO_POINTS",Ins
DefNode(GeometryNode, GEO_NODE_IS_VIEWPORT, 0, "IS_VIEWPORT", IsViewport, "Is Viewport", "Retrieve whether the nodes are being evaluated for the viewport rather than the final render")
DefNode(GeometryNode, GEO_NODE_JOIN_GEOMETRY, 0, "JOIN_GEOMETRY", JoinGeometry, "Join Geometry", "Merge separately generated geometries into a single one")
DefNode(GeometryNode, GEO_NODE_MATERIAL_SELECTION, 0, "MATERIAL_SELECTION", MaterialSelection, "Material Selection", "Provide a selection of faces that use the specified material")
DefNode(GeometryNode, GEO_NODE_MATH_EXPRESSION, def_geo_math_expression, "MATH_EXPRESSION", MathExpression, "Math Expression", "Evaluate a math expression")
DefNode(GeometryNode, GEO_NODE_MEAN_FILTER_SDF_VOLUME, 0, "MEAN_FILTER_SDF_VOLUME", MeanFilterSDFVolume, "Mean Filter SDF Volume", "Smooth the surface of an SDF volume by applying a mean filter")
DefNode(GeometryNode, GEO_NODE_MERGE_BY_DISTANCE, 0, "MERGE_BY_DISTANCE", MergeByDistance, "Merge by Distance", "Merge vertices or points within a given distance")
DefNode(GeometryNode, GEO_NODE_MESH_BOOLEAN, 0, "MESH_BOOLEAN", MeshBoolean, "Mesh Boolean", "Cut, subtract, or join multiple mesh inputs")

View File

@ -119,6 +119,10 @@ set(SRC
nodes/node_geo_join_geometry.cc
nodes/node_geo_material_replace.cc
nodes/node_geo_material_selection.cc
nodes/node_geo_math_expression/node_geo_math_expression.cc
nodes/node_geo_math_expression/evaluation_context.cc
nodes/node_geo_math_expression/expression.cc
nodes/node_geo_math_expression/parser.cc
nodes/node_geo_mean_filter_sdf_volume.cc
nodes/node_geo_merge_by_distance.cc
nodes/node_geo_mesh_face_group_boundaries.cc

View File

@ -0,0 +1,15 @@
#include "evaluation_context.hh"
namespace blender::nodes::node_geo_math_expression_cc {
EvaluationContext::EvaluationContext(const std::function<fn::GField(std::string_view)> variable_cb)
: variable_cb(std::move(variable_cb))
{
}
fn::GField EvaluationContext::get_variable(std::string_view name)
{
return variable_cb(name);
}
} // namespace blender::nodes::node_geo_math_expression_cc

View File

@ -0,0 +1,20 @@
#pragma once
#include <functional>
#include <memory>
#include <string_view>
#include "FN_field.hh"
namespace blender::nodes::node_geo_math_expression_cc {
class EvaluationContext {
const std::function<fn::GField(std::string_view)> variable_cb;
public:
EvaluationContext(const std::function<fn::GField(std::string_view)> variable_cb);
fn::GField get_variable(std::string_view name);
};
} // namespace blender::nodes::node_geo_math_expression_cc

View File

@ -0,0 +1,567 @@
#include "expression.hh"
namespace blender::nodes::node_geo_math_expression_cc {
template<typename... T, typename... Args> bool args_are(Args... args)
{
return (args.cpp_type().template is<T>() && ...);
}
static fn::GField fl_to_fl(fn::GField a, NodeMathOperation op)
{
const mf::MultiFunction *fn = nullptr;
try_dispatch_float_math_fl_to_fl(
op, [&fn](auto devi_fn, auto function, const blender::nodes::FloatMathOperationInfo &info) {
static auto _fn = mf::build::SI1_SO<float, float>(
info.title_case_name.c_str(), function, devi_fn);
fn = &_fn;
});
BLI_assert(fn != nullptr);
return fn::Field<float>(fn::FieldOperation::Create(*fn, {std::move(a)}));
}
static fn::GField fl_fl_to_fl(fn::GField a, fn::GField b, NodeMathOperation op)
{
const mf::MultiFunction *fn = nullptr;
try_dispatch_float_math_fl_fl_to_fl(
op, [&fn](auto devi_fn, auto function, const blender::nodes::FloatMathOperationInfo &info) {
static auto _fn = mf::build::SI2_SO<float, float, float>(
info.title_case_name.c_str(), function, devi_fn);
fn = &_fn;
});
BLI_assert(fn != nullptr);
return fn::Field<float>(fn::FieldOperation::Create(*fn, {std::move(a), std::move(b)}));
}
static fn::GField fl_fl_fl_to_fl(fn::GField a, fn::GField b, fn::GField c, NodeMathOperation op)
{
const mf::MultiFunction *fn = nullptr;
try_dispatch_float_math_fl_fl_fl_to_fl(
op, [&fn](auto devi_fn, auto function, const blender::nodes::FloatMathOperationInfo &info) {
static auto _fn = mf::build::SI3_SO<float, float, float, float>(
info.title_case_name.c_str(), function, devi_fn);
fn = &_fn;
});
BLI_assert(fn != nullptr);
return fn::Field<float>(
fn::FieldOperation::Create(*fn, {std::move(a), std::move(b), std::move(c)}));
}
static fn::GField fl3_to_fl3(fn::GField a, NodeVectorMathOperation op)
{
const mf::MultiFunction *fn = nullptr;
try_dispatch_float_math_fl3_to_fl3(
op, [&fn](auto devi_fn, auto function, const blender::nodes::FloatMathOperationInfo &info) {
static auto _fn = mf::build::SI1_SO<float3, float3>(
info.title_case_name.c_str(), function, devi_fn);
fn = &_fn;
});
BLI_assert(fn != nullptr);
return fn::Field<float3>(fn::FieldOperation::Create(*fn, {std::move(a)}));
}
static fn::GField fl3_fl3_to_fl3(fn::GField a, fn::GField b, NodeVectorMathOperation op)
{
const mf::MultiFunction *fn = nullptr;
try_dispatch_float_math_fl3_fl3_to_fl3(
op, [&fn](auto devi_fn, auto function, const blender::nodes::FloatMathOperationInfo &info) {
static auto _fn = mf::build::SI2_SO<float3, float3, float3>(
info.title_case_name.c_str(), function, devi_fn);
fn = &_fn;
});
BLI_assert(fn != nullptr);
return fn::Field<float3>(fn::FieldOperation::Create(*fn, {std::move(a), std::move(b)}));
}
static fn::GField fl3_fl3_fl3_to_fl3(fn::GField a,
fn::GField b,
fn::GField c,
NodeVectorMathOperation op)
{
const mf::MultiFunction *fn = nullptr;
try_dispatch_float_math_fl3_fl3_fl3_to_fl3(
op, [&fn](auto devi_fn, auto function, const blender::nodes::FloatMathOperationInfo &info) {
static auto _fn = mf::build::SI3_SO<float3, float3, float3, float3>(
info.title_case_name.c_str(), function, devi_fn);
fn = &_fn;
});
BLI_assert(fn != nullptr);
return fn::Field<float3>(
fn::FieldOperation::Create(*fn, {std::move(a), std::move(b), std::move(c)}));
}
static fn::GField fl3_fl3_fl_to_fl3(fn::GField a,
fn::GField b,
fn::GField c,
NodeVectorMathOperation op)
{
const mf::MultiFunction *fn = nullptr;
try_dispatch_float_math_fl3_fl3_fl_to_fl3(
op, [&fn](auto devi_fn, auto function, const blender::nodes::FloatMathOperationInfo &info) {
static auto _fn = mf::build::SI3_SO<float3, float3, float, float3>(
info.title_case_name.c_str(), function, devi_fn);
fn = &_fn;
});
BLI_assert(fn != nullptr);
return fn::Field<float3>(
fn::FieldOperation::Create(*fn, {std::move(a), std::move(b), std::move(c)}));
}
static fn::GField fl3_fl3_to_fl(fn::GField a, fn::GField b, NodeVectorMathOperation op)
{
const mf::MultiFunction *fn = nullptr;
try_dispatch_float_math_fl3_fl3_to_fl(
op, [&fn](auto devi_fn, auto function, const blender::nodes::FloatMathOperationInfo &info) {
static auto _fn = mf::build::SI2_SO<float3, float3, float>(
info.title_case_name.c_str(), function, devi_fn);
fn = &_fn;
});
BLI_assert(fn != nullptr);
return fn::Field<float>(fn::FieldOperation::Create(*fn, {std::move(a), std::move(b)}));
}
static fn::GField fl3_fl_to_fl3(fn::GField a, fn::GField b, NodeVectorMathOperation op)
{
const mf::MultiFunction *fn = nullptr;
try_dispatch_float_math_fl3_fl_to_fl3(
op, [&fn](auto devi_fn, auto function, const blender::nodes::FloatMathOperationInfo &info) {
static auto _fn = mf::build::SI2_SO<float3, float, float3>(
info.title_case_name.c_str(), function, devi_fn);
fn = &_fn;
});
BLI_assert(fn != nullptr);
return fn::Field<float3>(fn::FieldOperation::Create(*fn, {std::move(a), std::move(b)}));
}
static fn::GField fl3_to_fl(fn::GField a, NodeVectorMathOperation op)
{
const mf::MultiFunction *fn = nullptr;
try_dispatch_float_math_fl3_to_fl(
op, [&fn](auto devi_fn, auto function, const blender::nodes::FloatMathOperationInfo &info) {
static auto _fn = mf::build::SI1_SO<float3, float>(
info.title_case_name.c_str(), function, devi_fn);
fn = &_fn;
});
BLI_assert(fn != nullptr);
return fn::Field<float>(fn::FieldOperation::Create(*fn, {std::move(a)}));
}
fn::GField CallExpression::compile(EvaluationContext &ctx)
{
// At this point the number of arguments has already been validated by the parser.
// Custom functions.
switch (name) {
case FunctionName::LERP:
return lerp(ctx, args[0].get(), args[1].get(), args[2].get());
case FunctionName::VEC:
return vec(ctx, args[0].get(), args[1].get(), args[2].get());
case FunctionName::X:
return x(ctx, args[0].get());
case FunctionName::Y:
return y(ctx, args[0].get());
case FunctionName::Z:
return z(ctx, args[0].get());
default:
break;
}
// The rest is for the built-in math functions.
Vector<fn::GField> _args;
for (auto &arg : args) {
_args.append(arg->compile(ctx));
}
auto error = EvaluationError{this, "invalid arguments"};
// This code was generated with a JSON description of the existing functions.
if (_args.size() == 1) {
if (args_are<float>(_args[0])) {
switch (name) {
case FunctionName::EXPONENT:
return fl_to_fl(_args[0], NODE_MATH_EXPONENT);
case FunctionName::SQRT:
return fl_to_fl(_args[0], NODE_MATH_SQRT);
case FunctionName::INV_SQRT:
return fl_to_fl(_args[0], NODE_MATH_INV_SQRT);
case FunctionName::ABSOLUTE:
return fl_to_fl(_args[0], NODE_MATH_ABSOLUTE);
case FunctionName::RADIANS:
return fl_to_fl(_args[0], NODE_MATH_RADIANS);
case FunctionName::DEGREES:
return fl_to_fl(_args[0], NODE_MATH_DEGREES);
case FunctionName::SIGN:
return fl_to_fl(_args[0], NODE_MATH_SIGN);
case FunctionName::ROUND:
return fl_to_fl(_args[0], NODE_MATH_ROUND);
case FunctionName::FLOOR:
return fl_to_fl(_args[0], NODE_MATH_FLOOR);
case FunctionName::CEIL:
return fl_to_fl(_args[0], NODE_MATH_CEIL);
case FunctionName::FRACTION:
return fl_to_fl(_args[0], NODE_MATH_FRACTION);
case FunctionName::TRUNC:
return fl_to_fl(_args[0], NODE_MATH_TRUNC);
case FunctionName::SINE:
return fl_to_fl(_args[0], NODE_MATH_SINE);
case FunctionName::COSINE:
return fl_to_fl(_args[0], NODE_MATH_COSINE);
case FunctionName::TANGENT:
return fl_to_fl(_args[0], NODE_MATH_TANGENT);
case FunctionName::SINH:
return fl_to_fl(_args[0], NODE_MATH_SINH);
case FunctionName::COSH:
return fl_to_fl(_args[0], NODE_MATH_COSH);
case FunctionName::TANH:
return fl_to_fl(_args[0], NODE_MATH_TANH);
case FunctionName::ARCSINE:
return fl_to_fl(_args[0], NODE_MATH_ARCSINE);
case FunctionName::ARCCOSINE:
return fl_to_fl(_args[0], NODE_MATH_ARCCOSINE);
case FunctionName::ARCTANGENT:
return fl_to_fl(_args[0], NODE_MATH_ARCTANGENT);
default:
throw error;
}
}
if (args_are<float3>(_args[0])) {
switch (name) {
case FunctionName::ABSOLUTE:
return fl3_to_fl3(_args[0], NODE_VECTOR_MATH_ABSOLUTE);
case FunctionName::FLOOR:
return fl3_to_fl3(_args[0], NODE_VECTOR_MATH_FLOOR);
case FunctionName::CEIL:
return fl3_to_fl3(_args[0], NODE_VECTOR_MATH_CEIL);
case FunctionName::FRACTION:
return fl3_to_fl3(_args[0], NODE_VECTOR_MATH_FRACTION);
case FunctionName::SINE:
return fl3_to_fl3(_args[0], NODE_VECTOR_MATH_SINE);
case FunctionName::COSINE:
return fl3_to_fl3(_args[0], NODE_VECTOR_MATH_COSINE);
case FunctionName::TANGENT:
return fl3_to_fl3(_args[0], NODE_VECTOR_MATH_TANGENT);
case FunctionName::LENGTH:
return fl3_to_fl(_args[0], NODE_VECTOR_MATH_LENGTH);
case FunctionName::NORMALIZE:
return fl3_to_fl3(_args[0], NODE_VECTOR_MATH_NORMALIZE);
default:
throw error;
}
}
}
if (_args.size() == 2) {
if (args_are<float, float>(_args[0], _args[1])) {
switch (name) {
case FunctionName::ADD:
return fl_fl_to_fl(_args[0], _args[1], NODE_MATH_ADD);
case FunctionName::SUBTRACT:
return fl_fl_to_fl(_args[0], _args[1], NODE_MATH_SUBTRACT);
case FunctionName::MULTIPLY:
return fl_fl_to_fl(_args[0], _args[1], NODE_MATH_MULTIPLY);
case FunctionName::DIVIDE:
return fl_fl_to_fl(_args[0], _args[1], NODE_MATH_DIVIDE);
case FunctionName::POWER:
return fl_fl_to_fl(_args[0], _args[1], NODE_MATH_POWER);
case FunctionName::LOGARITHM:
return fl_fl_to_fl(_args[0], _args[1], NODE_MATH_LOGARITHM);
case FunctionName::MINIMUM:
return fl_fl_to_fl(_args[0], _args[1], NODE_MATH_MINIMUM);
case FunctionName::MAXIMUM:
return fl_fl_to_fl(_args[0], _args[1], NODE_MATH_MAXIMUM);
case FunctionName::LESS_THAN:
return fl_fl_to_fl(_args[0], _args[1], NODE_MATH_LESS_THAN);
case FunctionName::GREATER_THAN:
return fl_fl_to_fl(_args[0], _args[1], NODE_MATH_GREATER_THAN);
case FunctionName::MODULO:
return fl_fl_to_fl(_args[0], _args[1], NODE_MATH_MODULO);
case FunctionName::FLOORED_MODULO:
return fl_fl_to_fl(_args[0], _args[1], NODE_MATH_FLOORED_MODULO);
case FunctionName::SNAP:
return fl_fl_to_fl(_args[0], _args[1], NODE_MATH_SNAP);
case FunctionName::ARCTAN2:
return fl_fl_to_fl(_args[0], _args[1], NODE_MATH_ARCTAN2);
case FunctionName::PINGPONG:
return fl_fl_to_fl(_args[0], _args[1], NODE_MATH_PINGPONG);
default:
throw error;
}
}
if (args_are<float3, float3>(_args[0], _args[1])) {
switch (name) {
case FunctionName::ADD:
return fl3_fl3_to_fl3(_args[0], _args[1], NODE_VECTOR_MATH_ADD);
case FunctionName::SUBTRACT:
return fl3_fl3_to_fl3(_args[0], _args[1], NODE_VECTOR_MATH_SUBTRACT);
case FunctionName::MULTIPLY:
return fl3_fl3_to_fl3(_args[0], _args[1], NODE_VECTOR_MATH_MULTIPLY);
case FunctionName::DIVIDE:
return fl3_fl3_to_fl3(_args[0], _args[1], NODE_VECTOR_MATH_DIVIDE);
case FunctionName::MINIMUM:
return fl3_fl3_to_fl3(_args[0], _args[1], NODE_VECTOR_MATH_MINIMUM);
case FunctionName::MAXIMUM:
return fl3_fl3_to_fl3(_args[0], _args[1], NODE_VECTOR_MATH_MAXIMUM);
case FunctionName::MODULO:
return fl3_fl3_to_fl3(_args[0], _args[1], NODE_VECTOR_MATH_MODULO);
case FunctionName::SNAP:
return fl3_fl3_to_fl3(_args[0], _args[1], NODE_VECTOR_MATH_SNAP);
case FunctionName::CROSS_PRODUCT:
return fl3_fl3_to_fl3(_args[0], _args[1], NODE_VECTOR_MATH_CROSS_PRODUCT);
case FunctionName::PROJECT:
return fl3_fl3_to_fl3(_args[0], _args[1], NODE_VECTOR_MATH_PROJECT);
case FunctionName::REFLECT:
return fl3_fl3_to_fl3(_args[0], _args[1], NODE_VECTOR_MATH_REFLECT);
case FunctionName::DOT_PRODUCT:
return fl3_fl3_to_fl(_args[0], _args[1], NODE_VECTOR_MATH_DOT_PRODUCT);
case FunctionName::DISTANCE:
return fl3_fl3_to_fl(_args[0], _args[1], NODE_VECTOR_MATH_DISTANCE);
default:
throw error;
}
}
if (args_are<float3, float>(_args[0], _args[1])) {
switch (name) {
case FunctionName::SCALE:
return fl3_fl_to_fl3(_args[0], _args[1], NODE_VECTOR_MATH_SCALE);
default:
throw error;
}
}
}
if (_args.size() == 3) {
if (args_are<float, float, float>(_args[0], _args[1], _args[2])) {
switch (name) {
case FunctionName::MULTIPLY_ADD:
return fl_fl_fl_to_fl(_args[0], _args[1], _args[2], NODE_MATH_MULTIPLY_ADD);
case FunctionName::COMPARE:
return fl_fl_fl_to_fl(_args[0], _args[1], _args[2], NODE_MATH_COMPARE);
case FunctionName::SMOOTH_MIN:
return fl_fl_fl_to_fl(_args[0], _args[1], _args[2], NODE_MATH_SMOOTH_MIN);
case FunctionName::SMOOTH_MAX:
return fl_fl_fl_to_fl(_args[0], _args[1], _args[2], NODE_MATH_SMOOTH_MAX);
case FunctionName::WRAP:
return fl_fl_fl_to_fl(_args[0], _args[1], _args[2], NODE_MATH_WRAP);
default:
throw error;
}
}
if (args_are<float3, float3, float3>(_args[0], _args[1], _args[2])) {
switch (name) {
case FunctionName::MULTIPLY_ADD:
return fl3_fl3_fl3_to_fl3(_args[0], _args[1], _args[2], NODE_VECTOR_MATH_MULTIPLY_ADD);
case FunctionName::WRAP:
return fl3_fl3_fl3_to_fl3(_args[0], _args[1], _args[2], NODE_VECTOR_MATH_WRAP);
case FunctionName::FACEFORWARD:
return fl3_fl3_fl3_to_fl3(_args[0], _args[1], _args[2], NODE_VECTOR_MATH_FACEFORWARD);
default:
throw error;
}
}
if (args_are<float3, float3, float>(_args[0], _args[1], _args[2])) {
switch (name) {
case FunctionName::REFRACT:
return fl3_fl3_fl_to_fl3(_args[0], _args[1], _args[2], NODE_VECTOR_MATH_REFRACT);
default:
throw error;
}
}
}
throw error;
}
fn::GField Expression::add(EvaluationContext &ctx, Expression *left, Expression *right)
{
fn::GField _left = left->compile(ctx);
fn::GField _right = right->compile(ctx);
if (args_are<float, float>(_left, _right)) {
return fl_fl_to_fl(_left, _right, NODE_MATH_ADD);
}
if (args_are<float3, float3>(_left, _right)) {
return fl3_fl3_to_fl3(_left, _right, NODE_VECTOR_MATH_ADD);
}
throw "invalid operands";
}
fn::GField Expression::sub(EvaluationContext &ctx, Expression *left, Expression *right)
{
fn::GField _left = left->compile(ctx);
fn::GField _right = right->compile(ctx);
if (args_are<float, float>(_left, _right)) {
return fl_fl_to_fl(_left, _right, NODE_MATH_SUBTRACT);
}
if (args_are<float3, float3>(_left, _right)) {
return fl3_fl3_to_fl3(_left, _right, NODE_VECTOR_MATH_SUBTRACT);
}
throw "invalid operands";
}
fn::GField Expression::mul(EvaluationContext &ctx, Expression *left, Expression *right)
{
fn::GField _left = left->compile(ctx);
fn::GField _right = right->compile(ctx);
if (args_are<float, float>(_left, _right)) {
return fl_fl_to_fl(_left, _right, NODE_MATH_MULTIPLY);
}
if (args_are<float3, float>(_left, _right)) {
return fl3_fl_to_fl3(_left, _right, NODE_VECTOR_MATH_SCALE);
}
if (args_are<float, float3>(_left, _right)) {
return fl3_fl_to_fl3(_right, _left, NODE_VECTOR_MATH_SCALE);
}
throw "invalid operands";
}
fn::GField Expression::div(EvaluationContext &ctx, Expression *left, Expression *right)
{
fn::GField _left = left->compile(ctx);
fn::GField _right = right->compile(ctx);
if (args_are<float, float>(_left, _right)) {
return fl_fl_to_fl(_left, _right, NODE_MATH_DIVIDE);
}
if (args_are<float3, float>(_left, _right)) {
auto one_div_right = fl_fl_to_fl(constant(1.0f), _right, NODE_MATH_DIVIDE);
return fl3_fl_to_fl3(_left, one_div_right, NODE_VECTOR_MATH_SCALE);
}
throw "invalid operands";
}
fn::GField Expression::negate(EvaluationContext &ctx, Expression *x)
{
fn::GField _x = x->compile(ctx);
if (args_are<float>(_x)) {
return fl_fl_to_fl(_x, constant(-1.0f), NODE_MATH_MULTIPLY);
}
if (args_are<float3>(_x)) {
return fl3_fl_to_fl3(_x, constant(-1.0f), NODE_VECTOR_MATH_SCALE);
}
throw "invalid operand";
}
fn::GField Expression::lerp(EvaluationContext &ctx, Expression *a, Expression *b, Expression *t)
{
// (b - a) * t + a
fn::GField _a = a->compile(ctx);
fn::GField _b = b->compile(ctx);
fn::GField _t = t->compile(ctx);
if (args_are<float, float, float>(_a, _b, _t)) {
auto b_sub_a = fl_fl_to_fl(_b, _a, NODE_MATH_SUBTRACT);
auto mul_t = fl_fl_to_fl(b_sub_a, _t, NODE_MATH_MULTIPLY);
return fl_fl_to_fl(mul_t, _a, NODE_MATH_ADD);
}
if (args_are<float3, float3, float>(_a, _b, _t)) {
auto b_sub_a = fl3_fl3_to_fl3(_b, _a, NODE_VECTOR_MATH_SUBTRACT);
auto mul_t = fl3_fl_to_fl3(b_sub_a, _t, NODE_VECTOR_MATH_SCALE);
return fl3_fl3_to_fl3(mul_t, _a, NODE_VECTOR_MATH_ADD);
}
throw "invalid operands";
}
fn::GField Expression::vec(EvaluationContext &ctx, Expression *x, Expression *y, Expression *z)
{
fn::GField _x = x->compile(ctx);
fn::GField _y = y->compile(ctx);
fn::GField _z = z->compile(ctx);
if (args_are<float, float, float>(_x, _y, _z)) {
static auto fn = mf::build::SI3_SO<float, float, float, float3>("vec",
[](float x, float y, float z) {
return float3{x, y, z};
});
return fn::Field<float3>(
fn::FieldOperation::Create(fn, {std::move(_x), std::move(_y), std::move(_z)}));
}
throw "invalid operands";
}
fn::GField Expression::x(EvaluationContext &ctx, Expression *v)
{
auto _v = v->compile(ctx);
if (args_are<float3>(_v)) {
static auto fn = mf::build::SI1_SO<float3, float>("x", [](float3 v) { return v.x; });
return fn::Field<float>(fn::FieldOperation::Create(fn, {std::move(_v)}));
}
throw "invalid operands";
}
fn::GField Expression::y(EvaluationContext &ctx, Expression *v)
{
auto _v = v->compile(ctx);
if (args_are<float3>(_v)) {
static auto fn = mf::build::SI1_SO<float3, float>("y", [](float3 v) { return v.y; });
return fn::Field<float>(fn::FieldOperation::Create(fn, {std::move(_v)}));
}
throw "invalid operands";
}
fn::GField Expression::z(EvaluationContext &ctx, Expression *v)
{
auto _v = v->compile(ctx);
if (args_are<float3>(_v)) {
static auto fn = mf::build::SI1_SO<float3, float>("z", [](float3 v) { return v.z; });
return fn::Field<float>(fn::FieldOperation::Create(fn, {std::move(_v)}));
}
throw "invalid operands";
}
} // namespace blender::nodes::node_geo_math_expression_cc

View File

@ -0,0 +1,247 @@
#pragma once
#include <charconv>
#include <memory>
#include <string>
#include "BLI_vector.hh"
#include "FN_field.hh"
#include "NOD_math_functions.hh"
#include "evaluation_context.hh"
#include "lexer.hh"
#include "parser.hh"
namespace blender::nodes::node_geo_math_expression_cc {
struct EvaluationError {
class Expression *expression;
const char *message;
};
class Expression {
protected:
Token token;
static int _type_id()
{
static int type_id = 0;
return type_id++;
}
template<typename T> static int _type_id()
{
static int type_id = _type_id();
return type_id;
}
virtual int type_id() const = 0;
static fn::GField constant(float f)
{
auto c = std::make_shared<mf::CustomMF_Constant<float>>(f);
return fn::Field<float>(fn::FieldOperation::Create(std::move(c)));
}
static fn::GField constant(float3 f3)
{
auto c = std::make_shared<mf::CustomMF_Constant<float3>>(f3);
return fn::Field<float3>(fn::FieldOperation::Create(std::move(c)));
}
public:
Expression(Token token) : token(token) {}
virtual ~Expression() = default;
const Token &get_token() const
{
return token;
}
virtual fn::GField compile(EvaluationContext &ctx) = 0;
template<typename T> T *as()
{
return type_id() == _type_id<T>() ? static_cast<T *>(this) : nullptr;
}
protected:
static fn::GField add(EvaluationContext &ctx, Expression *left, Expression *right);
static fn::GField sub(EvaluationContext &ctx, Expression *left, Expression *right);
static fn::GField mul(EvaluationContext &ctx, Expression *left, Expression *right);
static fn::GField div(EvaluationContext &ctx, Expression *left, Expression *right);
static fn::GField negate(EvaluationContext &ctx, Expression *x);
static fn::GField lerp(EvaluationContext &ctx, Expression *a, Expression *b, Expression *t);
static fn::GField vec(EvaluationContext &ctx, Expression *x, Expression *y, Expression *z);
static fn::GField x(EvaluationContext &ctx, Expression *v);
static fn::GField y(EvaluationContext &ctx, Expression *v);
static fn::GField z(EvaluationContext &ctx, Expression *v);
};
class NumberExpression : public Expression {
float _value;
protected:
int type_id() const override
{
return _type_id<NumberExpression>();
}
public:
NumberExpression(float value, Token token) : Expression(token), _value(value) {}
float value()
{
return _value;
}
fn::GField compile(EvaluationContext & /*ctx*/) override
{
return constant(_value);
}
};
class GroupExpression : public Expression {
std::unique_ptr<Expression> expr;
protected:
int type_id() const override
{
return _type_id<GroupExpression>();
}
public:
GroupExpression(std::unique_ptr<Expression> expr, Token token)
: Expression(token), expr(std::move(expr))
{
}
fn::GField compile(EvaluationContext &ctx) override
{
return expr->compile(ctx);
}
};
class VariableExpression : public Expression {
protected:
int type_id() const override
{
return _type_id<VariableExpression>();
}
public:
VariableExpression(Token token) : Expression(token) {}
fn::GField compile(EvaluationContext &ctx) override
{
fn::GField value;
try {
value = ctx.get_variable(token.value);
}
catch (const char *err) {
throw EvaluationError{this, err};
}
return value;
}
};
class CallExpression : public Expression {
FunctionName name;
const Vector<std::unique_ptr<Expression>> args;
protected:
int type_id() const override
{
return _type_id<CallExpression>();
}
public:
CallExpression(FunctionName name, Vector<std::unique_ptr<Expression>> args, Token token)
: Expression(token), name(name), args(std::move(args))
{
}
fn::GField compile(EvaluationContext &ctx) override;
};
class UnaryExpression : public Expression {
std::unique_ptr<Expression> expr;
protected:
int type_id() const override
{
return _type_id<UnaryExpression>();
}
public:
UnaryExpression(std::unique_ptr<Expression> expr, Token token)
: Expression(token), expr(std::move(expr))
{
}
fn::GField compile(EvaluationContext &ctx) override
{
if (token.kind != TokenKind::MINUS) {
throw EvaluationError{this, "invalid unary operator"};
}
try {
auto number_expr = expr->as<NumberExpression>();
if (number_expr) {
// optimize to constant
return constant(-number_expr->value());
}
return negate(ctx, expr.get());
}
catch (const char *err) {
throw EvaluationError{this, err};
}
}
};
class BinaryExpression : public Expression {
std::unique_ptr<Expression> left;
std::unique_ptr<Expression> right;
protected:
int type_id() const override
{
return _type_id<BinaryExpression>();
}
public:
BinaryExpression(std::unique_ptr<Expression> left,
std::unique_ptr<Expression> right,
Token token)
: Expression(token), left(std::move(left)), right(std::move(right))
{
}
fn::GField compile(EvaluationContext &ctx) override
{
try {
switch (token.kind) {
case TokenKind::PLUS:
return add(ctx, left.get(), right.get());
case TokenKind::MINUS:
return sub(ctx, left.get(), right.get());
case TokenKind::MUL:
return mul(ctx, left.get(), right.get());
case TokenKind::DIV:
return div(ctx, left.get(), right.get());
default:
throw EvaluationError{this, "invalid binary operator"};
}
}
catch (const char *err) {
throw EvaluationError{this, err};
}
}
};
} // namespace blender::nodes::node_geo_math_expression_cc

View File

@ -0,0 +1,157 @@
#pragma once
#include <optional>
#include "BLI_map.hh"
namespace blender::nodes::node_geo_math_expression_cc {
enum class FunctionName {
EXPONENT,
SQRT,
INV_SQRT,
ABSOLUTE,
RADIANS,
DEGREES,
SIGN,
ROUND,
FLOOR,
CEIL,
FRACTION,
TRUNC,
SINE,
COSINE,
TANGENT,
SINH,
COSH,
TANH,
ARCSINE,
ARCCOSINE,
ARCTANGENT,
ADD,
SUBTRACT,
MULTIPLY,
DIVIDE,
POWER,
LOGARITHM,
MINIMUM,
MAXIMUM,
LESS_THAN,
GREATER_THAN,
MODULO,
FLOORED_MODULO,
SNAP,
ARCTAN2,
PINGPONG,
MULTIPLY_ADD,
COMPARE,
SMOOTH_MIN,
SMOOTH_MAX,
WRAP,
CROSS_PRODUCT,
PROJECT,
REFLECT,
DOT_PRODUCT,
DISTANCE,
FACEFORWARD,
REFRACT,
LENGTH,
SCALE,
NORMALIZE,
// Custom functions.
LERP,
VEC,
X,
Y,
Z
};
struct FunctionDef {
FunctionName name;
int arg_count;
};
class FunctionLookup {
Map<std::string_view, FunctionDef> funcs;
public:
FunctionLookup()
{
funcs.add("exponent", {FunctionName::EXPONENT, 1});
funcs.add("sqrt", {FunctionName::SQRT, 1});
funcs.add("inv_sqrt", {FunctionName::INV_SQRT, 1});
funcs.add("absolute", {FunctionName::ABSOLUTE, 1});
funcs.add("radians", {FunctionName::RADIANS, 1});
funcs.add("degrees", {FunctionName::DEGREES, 1});
funcs.add("sign", {FunctionName::SIGN, 1});
funcs.add("round", {FunctionName::ROUND, 1});
funcs.add("floor", {FunctionName::FLOOR, 1});
funcs.add("ceil", {FunctionName::CEIL, 1});
funcs.add("fraction", {FunctionName::FRACTION, 1});
funcs.add("trunc", {FunctionName::TRUNC, 1});
funcs.add("sine", {FunctionName::SINE, 1});
funcs.add("cosine", {FunctionName::COSINE, 1});
funcs.add("tangent", {FunctionName::TANGENT, 1});
funcs.add("sinh", {FunctionName::SINH, 1});
funcs.add("cosh", {FunctionName::COSH, 1});
funcs.add("tanh", {FunctionName::TANH, 1});
funcs.add("arcsine", {FunctionName::ARCSINE, 1});
funcs.add("arccosine", {FunctionName::ARCCOSINE, 1});
funcs.add("arctangent", {FunctionName::ARCTANGENT, 1});
funcs.add("add", {FunctionName::ADD, 2});
funcs.add("subtract", {FunctionName::SUBTRACT, 2});
funcs.add("multiply", {FunctionName::MULTIPLY, 2});
funcs.add("divide", {FunctionName::DIVIDE, 2});
funcs.add("power", {FunctionName::POWER, 2});
funcs.add("logarithm", {FunctionName::LOGARITHM, 2});
funcs.add("minimum", {FunctionName::MINIMUM, 2});
funcs.add("maximum", {FunctionName::MAXIMUM, 2});
funcs.add("less_than", {FunctionName::LESS_THAN, 2});
funcs.add("greater_than", {FunctionName::GREATER_THAN, 2});
funcs.add("modulo", {FunctionName::MODULO, 2});
funcs.add("floored_modulo", {FunctionName::FLOORED_MODULO, 2});
funcs.add("snap", {FunctionName::SNAP, 2});
funcs.add("arctan2", {FunctionName::ARCTAN2, 2});
funcs.add("pingpong", {FunctionName::PINGPONG, 2});
funcs.add("multiply_add", {FunctionName::MULTIPLY_ADD, 3});
funcs.add("compare", {FunctionName::COMPARE, 3});
funcs.add("smooth_min", {FunctionName::SMOOTH_MIN, 3});
funcs.add("smooth_max", {FunctionName::SMOOTH_MAX, 3});
funcs.add("wrap", {FunctionName::WRAP, 3});
funcs.add("cross_product", {FunctionName::CROSS_PRODUCT, 2});
funcs.add("project", {FunctionName::PROJECT, 2});
funcs.add("reflect", {FunctionName::REFLECT, 2});
funcs.add("dot_product", {FunctionName::DOT_PRODUCT, 2});
funcs.add("distance", {FunctionName::DISTANCE, 2});
funcs.add("faceforward", {FunctionName::FACEFORWARD, 3});
funcs.add("refract", {FunctionName::REFRACT, 3});
funcs.add("length", {FunctionName::LENGTH, 1});
funcs.add("scale", {FunctionName::SCALE, 2});
funcs.add("normalize", {FunctionName::NORMALIZE, 1});
// Custom functions.
funcs.add("lerp", {FunctionName::LERP, 3});
funcs.add("vec", {FunctionName::VEC, 3});
funcs.add("x", {FunctionName::X, 1});
funcs.add("y", {FunctionName::Y, 1});
funcs.add("z", {FunctionName::Z, 1});
}
std::optional<FunctionDef> lookup(std::string_view name) const
{
if (funcs.contains(name)) {
return funcs.lookup(name);
}
return std::nullopt;
}
};
inline std::optional<FunctionDef> lookup_function(std::string_view name)
{
static const FunctionLookup funcs{};
return funcs.lookup(name);
}
} // namespace blender::nodes::node_geo_math_expression_cc

View File

@ -0,0 +1,196 @@
#pragma once
#include <cctype>
#include <cstdint>
#include <string_view>
namespace blender::nodes::node_geo_math_expression_cc {
enum class TokenKind { IDENT, NUMBER, LPAREN, RPAREN, DOT, COMMA, PLUS, MINUS, MUL, DIV, END };
struct Token {
TokenKind kind;
size_t index;
std::string_view value;
const char *kind_str()
{
const char *names[] = {"IDENT",
"NUMBER",
"LPAREN",
"RPAREN",
"DOT",
"COMMA",
"PLUS",
"MINUS",
"MUL",
"DIV",
"END"};
return names[static_cast<size_t>(kind)];
}
};
struct LexerError {
size_t index;
char c;
const char *message;
LexerError(size_t index, char c, const char *message) : index(index), c(c), message(message) {}
};
class Lexer {
std::string_view text;
size_t index;
size_t start;
public:
void init(std::string_view text)
{
this->text = text;
index = 0;
start = 0;
}
Token next_token()
{
if (end()) {
start = index;
return make_token(TokenKind::END);
}
start = index;
char c = next();
if (isspace(c)) {
return next_token();
}
switch (c) {
case '(':
return make_token(TokenKind::LPAREN);
break;
case ')':
return make_token(TokenKind::RPAREN);
break;
case '.':
if (!std::isdigit(peek())) {
return make_token(TokenKind::DOT);
}
break;
case ',':
return make_token(TokenKind::COMMA);
break;
case '+':
return make_token(TokenKind::PLUS);
break;
case '-':
return make_token(TokenKind::MINUS);
break;
case '*':
return make_token(TokenKind::MUL);
break;
case '/':
return make_token(TokenKind::DIV);
break;
}
if (c == '_' || isalpha(c)) {
return parse_identifier();
}
if (c == '.' || isdigit(c)) {
return parse_number(c);
}
throw make_error("unexpected character");
}
private:
Token parse_number(char c)
{
if (c == '.') {
if (end() || !isdigit(peek())) {
throw make_error("expected digit");
}
consume_number();
}
else {
consume_number();
if (match('.')) {
consume_number();
}
}
return make_token(TokenKind::NUMBER);
}
void consume_number()
{
while (!end()) {
char c = peek();
if (isdigit(c)) {
next();
}
else {
break;
}
}
}
Token parse_identifier()
{
while (!end()) {
char c = peek();
if (c == '_' || isalnum(c)) {
next();
}
else {
break;
}
}
return make_token(TokenKind::IDENT);
}
LexerError make_error(const char *message)
{
return LexerError(index - 1, text[index - 1], message);
}
Token make_token(TokenKind kind)
{
return Token{kind, start, text.substr(start, index - start)};
}
bool match(char c)
{
if (!end() && peek() == c) {
next();
return true;
}
return false;
}
bool end()
{
return index >= text.size();
}
char next()
{
return text[index++];
}
char peek()
{
return text[index];
}
};
} // namespace blender::nodes::node_geo_math_expression_cc

View File

@ -0,0 +1,182 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_map.hh"
#include "BLI_set.hh"
#include "BLI_vector.hh"
#include "DNA_node_types.h"
#include "UI_interface.hh"
#include <fmt/format.h>
#include "evaluation_context.hh"
#include "expression.hh"
#include "lexer.hh"
#include "parser.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_math_expression_cc {
NODE_STORAGE_FUNCS(NodeGeometryMathExpression)
static void node_declare(blender::nodes::NodeDeclarationBuilder &b)
{
auto node = b.node_or_null();
if (node == nullptr) {
return;
}
NodeGeometryMathExpression *storage = static_cast<NodeGeometryMathExpression *>(node->storage);
b.is_function_node();
Set<std::string_view> vars;
parse_var_names(storage->variables, [&b, &vars](std::string_view name) {
if (vars.contains(name)) {
return;
}
if (name[0] == 'f') {
b.add_input<decl::Float>(name);
}
else if (name[0] == 'v') {
b.add_input<decl::Vector>(name);
}
else {
return;
}
vars.add(name);
});
if (storage->output_type == GEO_NODE_MATH_EXPRESSION_OUTPUT_FLOAT) {
b.add_output<decl::Float>("Value");
}
else if (storage->output_type == GEO_NODE_MATH_EXPRESSION_OUTPUT_VECTOR) {
b.add_output<decl::Vector>("Value");
}
}
static void node_geo_exec(GeoNodeExecParams params)
{
SCOPED_TIMER(__func__);
// Most of the stuff here only needs to be done once after the expression text has changed.
// The list of operations should be cached somewhere to avoid reparsing every time this function
// is called.
const NodeGeometryMathExpression &storage = node_storage(params.node());
Map<std::string_view, fn::GField> vars;
parse_var_names(storage.variables, [&vars, &params](std::string_view name) {
if (vars.contains(name)) {
return;
}
if (name[0] == 'f') {
vars.add(name, params.extract_input<Field<float>>(name));
}
else if (name[0] == 'v') {
vars.add(name, params.extract_input<Field<float3>>(name));
}
});
Parser parser;
std::unique_ptr<Expression> expr;
try {
expr = parser.parse(storage.expression);
}
catch (LexerError err) {
params.error_message_add(
NodeWarningType::Error,
fmt::format("{}: col:{} '{}'", err.message, err.index + 1, err.c).c_str());
params.set_default_remaining_outputs();
return;
}
catch (ParserError err) {
params.error_message_add(
NodeWarningType::Error,
fmt::format(TIP_("{}: col:{} '{}'"), err.message, err.token.index + 1, err.token.value)
.c_str());
params.set_default_remaining_outputs();
return;
}
EvaluationContext ctx([&params, &vars](std::string_view name) -> fn::GField {
if (!vars.contains(name)) {
throw "variable does not exist";
}
return vars.lookup(name);
});
try {
fn::GField field = expr->compile(ctx);
if (field.cpp_type().is<float>() &&
storage.output_type != GEO_NODE_MATH_EXPRESSION_OUTPUT_FLOAT) {
params.error_message_add(
NodeWarningType::Error,
TIP_("The result of the expression (Float) does not match the ouput type of the node"));
params.set_default_remaining_outputs();
return;
}
if (field.cpp_type().is<float3>() &&
storage.output_type != GEO_NODE_MATH_EXPRESSION_OUTPUT_VECTOR) {
params.error_message_add(
NodeWarningType::Error,
TIP_("The result of the expression (Vector) does not match the ouput type of the node"));
params.set_default_remaining_outputs();
return;
}
params.set_output("Value", field);
}
catch (EvaluationError err) {
params.error_message_add(NodeWarningType::Error,
fmt::format("{}: col:{} '{}'",
err.message,
err.expression->get_token().index + 1,
err.expression->get_token().value)
.c_str());
params.set_default_remaining_outputs();
return;
}
}
static void node_layout(uiLayout *layout, bContext * /*c*/, PointerRNA *ptr)
{
uiItemR(layout, ptr, "variables", UI_ITEM_NONE, "Variables", ICON_NONE);
uiItemR(layout, ptr, "output_type", UI_ITEM_NONE, "Output Type", ICON_NONE);
uiItemR(layout, ptr, "expression", UI_ITEM_NONE, "Expression", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->storage = MEM_callocN(sizeof(NodeGeometryMathExpression), __func__);
NodeGeometryMathExpression &storage = node_storage(*node);
storage.output_type = GEO_NODE_MATH_EXPRESSION_OUTPUT_VECTOR;
strcpy(storage.variables, "v1, fa, fb, fc");
strcpy(storage.expression, "vec(fa, fb, fc) + v1");
}
inline void node_register()
{
namespace file_ns = blender::nodes::node_geo_math_expression_cc;
static bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_MATH_EXPRESSION, "Math Expression", NODE_CLASS_CONVERTER);
ntype.declare = file_ns::node_declare;
ntype.initfunc = file_ns::node_init;
ntype.geometry_node_execute = file_ns::node_geo_exec;
ntype.draw_buttons = file_ns::node_layout;
node_type_storage(&ntype,
"NodeGeometryMathExpression",
node_free_standard_storage,
node_copy_standard_storage);
nodeRegisterType(&ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_math_expression_cc

View File

@ -0,0 +1,246 @@
#include <cmath>
#include "expression.hh"
#include "parser.hh"
namespace blender::nodes::node_geo_math_expression_cc {
std::unique_ptr<Expression> Parser::parse(std::string_view text)
{
peeked = false;
lexer.init(text);
auto expr = parse_expression();
expect(TokenKind::END, "expected end of input");
return expr;
}
std::unique_ptr<Expression> Parser::parse_expression()
{
return parse_addition();
}
std::unique_ptr<Expression> Parser::parse_addition()
{
auto expr = parse_multiplication();
Token token;
while (match({TokenKind::PLUS, TokenKind::MINUS}, token)) {
expr = std::make_unique<BinaryExpression>(std::move(expr), parse_multiplication(), token);
}
return expr;
}
std::unique_ptr<Expression> Parser::parse_multiplication()
{
auto expr = parse_unary();
Token token;
while (match({TokenKind::MUL, TokenKind::DIV}, token)) {
expr = std::make_unique<BinaryExpression>(std::move(expr), parse_unary(), token);
}
return expr;
}
std::unique_ptr<Expression> Parser::parse_unary()
{
Token token;
if (match({TokenKind::MINUS}, token)) {
return std::make_unique<UnaryExpression>(parse_unary(), token);
}
return parse_ufcs();
}
std::unique_ptr<Expression> Parser::parse_ufcs()
{
auto expr = parse_primary();
Token token;
while (match({TokenKind::DOT}, token)) {
Token ident = expect(TokenKind::IDENT, "expected identifier");
Vector<std::unique_ptr<Expression>> args;
args.append(std::move(expr));
if (match(TokenKind::LPAREN)) {
expr = parse_call(ident, std::move(args));
}
else {
expr = make_call_expression(std::move(args), ident);
}
}
return expr;
}
std::unique_ptr<Expression> Parser::parse_primary()
{
Token token = next();
if (token.kind == TokenKind::IDENT) {
if (match(TokenKind::LPAREN)) {
return parse_call(token);
}
return parse_identifier(token);
}
if (token.kind == TokenKind::NUMBER) {
return std::make_unique<NumberExpression>(parse_number(token.value), token);
}
if (token.kind == TokenKind::LPAREN) {
auto expr = std::make_unique<GroupExpression>(parse_expression(), token);
expect(TokenKind::RPAREN, "expected closing paren");
return expr;
}
if (token.kind == TokenKind::END) {
throw ParserError(token, "unexpected end of expression");
}
else {
throw ParserError(token, "unexpected token");
}
}
std::unique_ptr<Expression> Parser::parse_call(Token token,
Vector<std::unique_ptr<Expression>> args)
{
if (!check(TokenKind::RPAREN)) {
do {
args.append(parse_expression());
} while (match(TokenKind::COMMA));
}
expect(TokenKind::RPAREN, "expected closing paren");
return make_call_expression(std::move(args), token);
}
std::unique_ptr<Expression> Parser::parse_identifier(Token token)
{
if (token.value == "pi") {
return std::make_unique<NumberExpression>(M_PI, token);
}
else if (token.value == "tau") {
return std::make_unique<NumberExpression>(M_PI * 2, token);
}
else if (token.value == "e") {
return std::make_unique<NumberExpression>(M_E, token);
}
return std::make_unique<VariableExpression>(token);
}
std::unique_ptr<Expression> Parser::make_call_expression(Vector<std::unique_ptr<Expression>> args,
Token token)
{
auto def = lookup_function(token.value);
if (!def.has_value()) {
throw ParserError(token, "invalid function");
}
if (args.size() != def->arg_count) {
throw ParserError(token, "incorrect number of arguments");
}
return std::make_unique<CallExpression>(def->name, std::move(args), token);
}
float Parser::parse_number(std::string_view text)
{
double d;
auto result = std::from_chars(text.data(), text.data() + text.size(), d);
if (result.ec != std::errc()) {
// The number has already been parsed and is valid.
BLI_assert_unreachable();
}
return d;
}
[[maybe_unused]] Token Parser::expect(TokenKind kind, const char *message)
{
if (peek().kind != kind) {
throw ParserError(peek(), message);
}
return next();
}
template<size_t N> bool Parser::match(const TokenKind (&kind)[N], Token &r_token)
{
for (size_t i = 0; i < N; i++) {
if (peek().kind == kind[i]) {
r_token = next();
return true;
}
}
return false;
}
bool Parser::match(const TokenKind kind)
{
if (peek().kind == kind) {
next();
return true;
}
return false;
}
bool Parser::check(const TokenKind kind)
{
if (peek().kind == kind) {
return true;
}
return false;
}
Token Parser::next()
{
Token token = peek();
peeked = false;
return token;
}
Token Parser::peek()
{
if (peeked) {
return peeked_token;
}
peeked_token = lexer.next_token();
peeked = true;
return peeked_token;
}
void parse_var_names(std::string_view vars, std::function<void(std::string_view)> cb)
{
size_t start = vars.size();
for (size_t i = 0; i < vars.size(); i++) {
char c = vars[i];
if (start == vars.size() && (c == '_' || isalpha(c))) {
start = i;
}
else if (start != vars.size() && (c != '_' && !isalnum(c))) {
cb(vars.substr(start, i - start));
start = vars.size();
}
if (start != vars.size() && i == vars.size() - 1) {
cb(vars.substr(start, i + 1 - start));
}
}
}
}; // namespace blender::nodes::node_geo_math_expression_cc

View File

@ -0,0 +1,57 @@
#pragma once
#include <memory>
#include <string_view>
#include "BLI_vector.hh"
#include "functions.hh"
#include "lexer.hh"
namespace blender::nodes::node_geo_math_expression_cc {
class Expression;
struct ParserError {
Token token;
const char *message;
ParserError(Token token, const char *message) : token(token), message(message) {}
};
class Parser {
Lexer lexer;
Token peeked_token;
bool peeked;
public:
std::unique_ptr<Expression> parse(std::string_view text);
std::unique_ptr<Expression> parse_expression();
std::unique_ptr<Expression> parse_addition();
std::unique_ptr<Expression> parse_multiplication();
std::unique_ptr<Expression> parse_unary();
std::unique_ptr<Expression> parse_ufcs();
std::unique_ptr<Expression> parse_primary();
std::unique_ptr<Expression> parse_call(
Token token,
Vector<std::unique_ptr<Expression>> args = Vector<std::unique_ptr<Expression>>());
std::unique_ptr<Expression> parse_identifier(Token token);
std::unique_ptr<Expression> make_call_expression(Vector<std::unique_ptr<Expression>> args,
Token token);
float parse_number(std::string_view text);
[[maybe_unused]] Token expect(TokenKind kind, const char *message);
template<size_t N> bool match(const TokenKind (&kind)[N], Token &r_token);
bool match(const TokenKind kind);
bool check(const TokenKind kind);
Token next();
Token peek();
};
void parse_var_names(std::string_view vars, std::function<void(std::string_view)> cb);
} // namespace blender::nodes::node_geo_math_expression_cc