/* SPDX-FileCopyrightText: 2011-2022 Blender Authors * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "node_item.h" #include "node_graph.h" #include "node_parser.h" #include "BLI_assert.h" #include "BLI_utildefines.h" namespace blender::nodes::materialx { NodeItem::NodeItem(MaterialX::GraphElement *graph) : graph_(graph) {} NodeItem::Type NodeItem::type(const std::string &type_str) { /* Converting only MaterialX supported types */ if (type_str == "multioutput") { return Type::Multioutput; } if (type_str == "string") { return Type::String; } if (type_str == "filename") { return Type::Filename; } if (type_str == "boolean") { return Type::Boolean; } if (type_str == "integer") { return Type::Integer; } if (type_str == "float") { return Type::Float; } if (type_str == "vector2") { return Type::Vector2; } if (type_str == "vector3") { return Type::Vector3; } if (type_str == "vector4") { return Type::Vector4; } if (type_str == "color3") { return Type::Color3; } if (type_str == "color4") { return Type::Color4; } if (type_str == "BSDF") { return Type::BSDF; } if (type_str == "EDF") { return Type::EDF; } if (type_str == "displacementshader") { return Type::DisplacementShader; } if (type_str == "surfaceshader") { return Type::SurfaceShader; } if (type_str == "material") { return Type::Material; } BLI_assert_unreachable(); return Type::Empty; } std::string NodeItem::type(Type type) { switch (type) { case Type::Any: return "any"; case Type::Multioutput: return "multioutput"; case Type::String: return "string"; case Type::Filename: return "filename"; case Type::Boolean: return "boolean"; case Type::Integer: return "integer"; case Type::Float: return "float"; case Type::Vector2: return "vector2"; case Type::Vector3: return "vector3"; case Type::Vector4: return "vector4"; case Type::Color3: return "color3"; case Type::Color4: return "color4"; case Type::BSDF: return "BSDF"; case Type::EDF: return "EDF"; case Type::DisplacementShader: return "displacementshader"; case Type::SurfaceShader: return "surfaceshader"; case Type::Material: return "material"; case Type::SurfaceOpacity: return "opacity"; default: BLI_assert_unreachable(); } return ""; } bool NodeItem::is_arithmetic(Type type) { return type >= Type::Float && type <= Type::Color4; } NodeItem::operator bool() const { return value || node || input || output; } NodeItem NodeItem::operator+(const NodeItem &other) const { Type type = this->type(); if (ELEM(type, Type::BSDF, Type::EDF)) { /* Special case: add BSDF/EDF shaders */ NodeItem res = empty(); if (other.type() == type) { res = create_node("add", type, {{"in1", *this}, {"in2", other}}); } else { BLI_assert_unreachable(); } return res; } return arithmetic(other, "add", [](float a, float b) { return a + b; }); } NodeItem NodeItem::operator-(const NodeItem &other) const { return arithmetic(other, "subtract", [](float a, float b) { return a - b; }); } NodeItem NodeItem::operator-() const { return val(0.0f) - *this; } NodeItem NodeItem::operator*(const NodeItem &other) const { Type type = this->type(); if (ELEM(type, Type::BSDF, Type::EDF)) { /* Special case: multiple BSDF/EDF shader by Float or Color3 */ NodeItem res = empty(); Type other_type = other.type(); if (ELEM(other_type, Type::Float, Type::Color3)) { res = create_node("multiply", type, {{"in1", *this}, {"in2", other}}); } else { BLI_assert_unreachable(); } return res; } return arithmetic(other, "multiply", [](float a, float b) { return a * b; }); } NodeItem NodeItem::operator/(const NodeItem &other) const { return arithmetic(other, "divide", [](float a, float b) { return b == 0.0f ? 0.0f : a / b; }); } NodeItem NodeItem::operator%(const NodeItem &other) const { return arithmetic( other, "modulo", [](float a, float b) { return b == 0.0f ? 0.0f : std::fmod(a, b); }); } NodeItem NodeItem::operator^(const NodeItem &other) const { return arithmetic(other, "power", [](float a, float b) { return std::pow(a, b); }); } NodeItem NodeItem::operator[](int index) const { BLI_assert(is_arithmetic(type())); if (value) { float v = 0.0f; switch (type()) { case Type::Float: v = value->asA(); break; case Type::Vector2: v = value->asA()[index]; break; case Type::Vector3: v = value->asA()[index]; break; case Type::Vector4: v = value->asA()[index]; break; case Type::Color3: v = value->asA()[index]; break; case Type::Color4: v = value->asA()[index]; break; default: BLI_assert_unreachable(); } return val(v); } return create_node("extract", Type::Float, {{"in", *this}, {"index", val(index)}}); } bool NodeItem::operator==(const NodeItem &other) const { if (!*this) { return !other; } if (!other) { return !*this; } if (node && node == other.node) { return true; } if ((node && other.value) || (value && other.node)) { return false; } NodeItem item1 = *this; NodeItem item2 = other; Type to_type = cast_types(item1, item2); if (to_type == Type::Empty) { return false; } return item1.value->getValueString() == item2.value->getValueString(); } bool NodeItem::operator!=(const NodeItem &other) const { return !(*this == other); } NodeItem NodeItem::abs() const { return arithmetic("absval", [](float a) { return std::abs(a); }); } NodeItem NodeItem::floor() const { return arithmetic("floor", [](float a) { return std::floor(a); }); } NodeItem NodeItem::ceil() const { return arithmetic("ceil", [](float a) { return std::ceil(a); }); } NodeItem NodeItem::length() const { if (value) { return dotproduct(*this).sqrt(); } return create_node("magnitude", Type::Float, {{"in", to_vector()}}); } NodeItem NodeItem::normalize() const { if (value) { return *this / length(); } return create_node("normalize", Type::Vector3, {{"in", to_vector()}}); } NodeItem NodeItem::min(const NodeItem &other) const { return arithmetic(other, "min", [](float a, float b) { return std::min(a, b); }); } NodeItem NodeItem::max(const NodeItem &other) const { return arithmetic(other, "max", [](float a, float b) { return std::max(a, b); }); } NodeItem NodeItem::dotproduct(const NodeItem &other) const { if (value && other.value) { NodeItem d = *this * other; float f = 0.0f; switch (d.type()) { case Type::Float: { f = d.value->asA(); break; } case Type::Vector2: { auto v = d.value->asA(); f = v[0] + v[1]; break; } case Type::Vector3: { auto v = d.value->asA(); f = v[0] + v[1] + v[2]; break; } case Type::Vector4: { auto v = d.value->asA(); f = v[0] + v[1] + v[2] + v[3]; break; } case Type::Color3: { auto v = d.value->asA(); f = v[0] + v[1] + v[2]; break; } case Type::Color4: { auto v = d.value->asA(); f = v[0] + v[1] + v[2] + v[3]; break; } default: BLI_assert_unreachable(); } return val(f); } NodeItem item1 = to_vector(); NodeItem item2 = other.to_vector(); cast_types(item1, item2); return create_node("dotproduct", Type::Float, {{"in1", item1}, {"in2", item2}}); } NodeItem NodeItem::mix(const NodeItem &val1, const NodeItem &val2) const { if ((value && val1.value && val2.value) || type() != Type::Float) { return (val(1.0f) - *this) * val1 + *this * val2; } Type type1 = val1.type(); if (ELEM(type1, Type::BSDF, Type::EDF)) { BLI_assert(val2.type() == type1); /* Special case: mix BSDF/EDF shaders */ return create_node("mix", type1, {{"bg", val1}, {"fg", val2}, {"mix", *this}}); }; NodeItem item1 = val1; NodeItem item2 = val2; Type to_type = cast_types(item1, item2); return create_node("mix", to_type, {{"bg", item1}, {"fg", item2}, {"mix", *this}}); } NodeItem NodeItem::clamp(const NodeItem &min_val, const NodeItem &max_val) const { if (value && min_val.value && max_val.value) { return min(max_val).max(min_val); } if (min_val.type() == Type::Float && max_val.type() == Type::Float) { return create_node("clamp", type(), {{"in", *this}, {"low", min_val}, {"high", max_val}}); } Type type = this->type(); return create_node( "clamp", type, {{"in", *this}, {"low", min_val.convert(type)}, {"high", max_val.convert(type)}}); } NodeItem NodeItem::clamp(float min_val, float max_val) const { return clamp(val(min_val), val(max_val)); } NodeItem NodeItem::rotate(const NodeItem &angle, const NodeItem &axis) { BLI_assert(type() == Type::Vector3); BLI_assert(angle.type() == Type::Float); BLI_assert(axis.type() == Type::Vector3); return create_node( "rotate3d", NodeItem::Type::Vector3, {{"in", *this}, {"amount", angle}, {"axis", axis}}); } NodeItem NodeItem::rotate(const NodeItem &angle_xyz, bool invert) { NodeItem x = angle_xyz[0]; NodeItem y = angle_xyz[1]; NodeItem z = angle_xyz[2]; NodeItem x_axis = val(MaterialX::Vector3(1.0f, 0.0f, 0.0f)); NodeItem y_axis = val(MaterialX::Vector3(0.0f, 1.0f, 0.0f)); NodeItem z_axis = val(MaterialX::Vector3(0.0f, 0.0f, 1.0f)); if (invert) { return rotate(z, z_axis).rotate(y, y_axis).rotate(x, x_axis); } return rotate(x, x_axis).rotate(y, y_axis).rotate(z, z_axis); } NodeItem NodeItem::sin() const { return to_vector().arithmetic("sin", [](float a) { return std::sin(a); }); } NodeItem NodeItem::cos() const { return to_vector().arithmetic("cos", [](float a) { return std::cos(a); }); } NodeItem NodeItem::tan() const { return to_vector().arithmetic("tan", [](float a) { return std::tan(a); }); } NodeItem NodeItem::asin() const { return to_vector().arithmetic("asin", [](float a) { return std::asin(a); }); } NodeItem NodeItem::acos() const { return to_vector().arithmetic("acos", [](float a) { return std::acos(a); }); } NodeItem NodeItem::atan() const { return to_vector().arithmetic("atan", [](float a) { return std::atan(a); }); } NodeItem NodeItem::atan2(const NodeItem &other) const { return to_vector().arithmetic(other, "atan2", [](float a, float b) { return std::atan2(a, b); }); } NodeItem NodeItem::sinh() const { NodeItem v = to_vector(); return (v.exp() - (-v).exp()) / val(2.0f); } NodeItem NodeItem::cosh() const { NodeItem v = to_vector(); return (v.exp() + (-v).exp()) / val(2.0f); } NodeItem NodeItem::tanh() const { NodeItem v = to_vector(); NodeItem a = v.exp(); NodeItem b = (-v).exp(); return (a - b) / (a + b); } NodeItem NodeItem::ln() const { return to_vector().arithmetic("ln", [](float a) { return std::log(a); }); } NodeItem NodeItem::sqrt() const { return to_vector().arithmetic("sqrt", [](float a) { return std::sqrt(a); }); } NodeItem NodeItem::sign() const { return arithmetic("sign", [](float a) { return a < 0.0f ? -1.0f : (a == 0.0f ? 0.0f : 1.0f); }); } NodeItem NodeItem::exp() const { return to_vector().arithmetic("exp", [](float a) { return std::exp(a); }); } bool NodeItem::is_convertible(eNodeSocketDatatype from_type, Type to_type) { switch (to_type) { case Type::Any: return true; case Type::Empty: case Type::Multioutput: return false; case Type::String: case Type::Filename: return from_type == SOCK_STRING; case Type::Boolean: return from_type == SOCK_BOOLEAN; case Type::Integer: return from_type == SOCK_INT; case Type::Float: case Type::Vector2: case Type::Vector3: case Type::Color3: case Type::Vector4: case Type::Color4: case Type::DisplacementShader: return ELEM(from_type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA); case Type::EDF: return ELEM(from_type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_SHADER); case Type::BSDF: case Type::SurfaceShader: case Type::Material: case Type::SurfaceOpacity: return from_type == SOCK_SHADER; } return false; } NodeItem NodeItem::convert(Type to_type) const { Type from_type = type(); if (from_type == Type::Empty || from_type == to_type || to_type == Type::Any) { return *this; } switch (to_type) { /* Link arithmetic types to shader as EDF. */ case Type::EDF: if (is_arithmetic(from_type)) { return create_node("uniform_edf", NodeItem::Type::EDF, {{"color", convert(Type::Color3)}}); } return empty(); /* Displacement shader from arithmetic types, when not using (Vector) Displacement node. */ case Type::DisplacementShader: if (is_arithmetic(from_type)) { return create_node("displacement", NodeItem::Type::DisplacementShader, {{"displacement", convert(Type::Vector3)}}); } return empty(); /* Surface opacity is just a float. */ case Type::SurfaceOpacity: to_type = Type::Float; if (from_type == to_type || to_type == Type::Any) { return *this; } break; /* Material output will evaluate graph multiple times for different components, * when linking arithmetic types we want to leave those empty. */ case Type::BSDF: case Type::SurfaceShader: case Type::Material: return empty(); default: break; } if (!is_arithmetic(from_type) || !is_arithmetic(to_type)) { CLOG_WARN(LOG_MATERIALX_SHADER, "Cannot convert: %s -> %s", type(from_type).c_str(), type(to_type).c_str()); return empty(); } if (to_type == Type::Float) { return (*this)[0]; } /* Converting types which requires > 1 iteration */ switch (from_type) { case Type::Vector2: switch (to_type) { case Type::Vector4: return convert(Type::Vector3).convert(Type::Vector4); case Type::Color3: return convert(Type::Vector3).convert(Type::Color3); case Type::Color4: return convert(Type::Vector3).convert(Type::Color3).convert(Type::Color4); default: break; } break; case Type::Vector3: switch (to_type) { case Type::Color4: return convert(Type::Color3).convert(Type::Color4); default: break; } break; case Type::Vector4: switch (to_type) { case Type::Vector2: return convert(Type::Vector3).convert(Type::Vector2); case Type::Color3: return convert(Type::Vector3).convert(Type::Color3); default: break; } break; case Type::Color3: switch (to_type) { case Type::Vector2: return convert(Type::Vector3).convert(Type::Vector2); case Type::Vector4: return convert(Type::Vector3).convert(Type::Vector4); default: break; } break; case Type::Color4: switch (to_type) { case Type::Vector2: return convert(Type::Vector4).convert(Type::Vector3).convert(Type::Vector2); case Type::Vector3: return convert(Type::Vector4).convert(Type::Vector3); default: break; } break; default: break; } /* Converting 1 iteration types */ NodeItem res = empty(); if (value) { switch (from_type) { case Type::Float: { float v = value->asA(); switch (to_type) { case Type::Vector2: res.value = MaterialX::Value::createValue({v, v}); break; case Type::Vector3: res.value = MaterialX::Value::createValue({v, v, v}); break; case Type::Vector4: res.value = MaterialX::Value::createValue({v, v, v, 1.0f}); break; case Type::Color3: res.value = MaterialX::Value::createValue({v, v, v}); break; case Type::Color4: res.value = MaterialX::Value::createValue({v, v, v, 1.0f}); break; default: BLI_assert_unreachable(); } break; } case Type::Vector2: { auto v = value->asA(); switch (to_type) { case Type::Vector3: res.value = MaterialX::Value::createValue({v[0], v[1], 0.0f}); break; default: BLI_assert_unreachable(); } break; } case Type::Vector3: { auto v = value->asA(); switch (to_type) { case Type::Vector2: res.value = MaterialX::Value::createValue({v[0], v[1]}); break; case Type::Vector4: res.value = MaterialX::Value::createValue( {v[0], v[1], v[2], 0.0f}); break; case Type::Color3: res.value = MaterialX::Value::createValue({v[0], v[1], v[2]}); break; default: BLI_assert_unreachable(); } break; } case Type::Vector4: { auto v = value->asA(); switch (to_type) { case Type::Vector3: res.value = MaterialX::Value::createValue({v[0], v[1], v[2]}); break; case Type::Color4: res.value = MaterialX::Value::createValue({v[0], v[1], v[2], v[3]}); break; default: BLI_assert_unreachable(); } break; } case Type::Color3: { auto v = value->asA(); switch (to_type) { case Type::Vector3: res.value = MaterialX::Value::createValue({v[0], v[1], v[2]}); break; case Type::Color4: res.value = MaterialX::Value::createValue({v[0], v[1], v[2], 1.0f}); break; default: BLI_assert_unreachable(); } break; } case Type::Color4: { auto v = value->asA(); switch (to_type) { case Type::Vector4: res.value = MaterialX::Value::createValue( {v[0], v[1], v[2], v[3]}); break; case Type::Color3: res.value = MaterialX::Value::createValue({v[0], v[1], v[2]}); break; default: BLI_assert_unreachable(); } break; } default: BLI_assert_unreachable(); } } else { res = create_node("convert", to_type, {{"in", *this}}); } return res; } NodeItem NodeItem::to_vector() const { switch (type()) { case Type::Float: case Type::Vector2: case Type::Vector3: case Type::Vector4: return *this; case Type::Color3: return convert(Type::Vector3); case Type::Color4: return convert(Type::Vector4); default: BLI_assert_unreachable(); } return empty(); } NodeItem NodeItem::if_else(CompareOp op, const NodeItem &other, const NodeItem &if_val, const NodeItem &else_val) const { switch (op) { case CompareOp::Less: return if_else(CompareOp::GreaterEq, other, else_val, if_val); case CompareOp::LessEq: return if_else(CompareOp::Greater, other, else_val, if_val); case CompareOp::NotEq: return if_else(CompareOp::Eq, other, else_val, if_val); default: break; } NodeItem res = empty(); if (type() != Type::Float || other.type() != Type::Float) { return res; } auto item1 = if_val; auto item2 = else_val; Type to_type = cast_types(item1, item2); if (to_type == Type::Empty) { return res; } std::function func = nullptr; std::string category; switch (op) { case CompareOp::Greater: category = "ifgreater"; func = [](float a, float b) { return a > b; }; break; case CompareOp::GreaterEq: category = "ifgreatereq"; func = [](float a, float b) { return a >= b; }; break; case CompareOp::Eq: category = "ifequal"; func = [](float a, float b) { return a == b; }; break; default: BLI_assert_unreachable(); } if (value && other.value) { res = func(value->asA(), other.value->asA()) ? item1 : item2; } else { res = create_node( category, to_type, {{"value1", *this}, {"value2", other}, {"in1", item1}, {"in2", item2}}); } return res; } NodeItem NodeItem::empty() const { return NodeItem(graph_); } NodeItem::Type NodeItem::type() const { if (value) { return type(value->getTypeString()); } if (node) { return type(node->getType()); } if (output) { return type(output->getType()); } return Type::Empty; } NodeItem NodeItem::create_node(const std::string &category, Type type) const { const std::string name = NodeGraph::unique_anonymous_node_name(graph_); const std::string type_str = NodeItem::type(type); CLOG_INFO(LOG_MATERIALX_SHADER, 2, "<%s type=%s>", category.c_str(), type_str.c_str()); NodeItem res = empty(); /* Surface-shader nodes and materials are added directly to the document, * otherwise to the node-graph. */ if (ELEM(type, Type::SurfaceShader, Type::Material)) { res.node = graph_->getDocument()->addNode(category, name, type_str); } else { res.node = graph_->addNode(category, name, type_str); } return res; } NodeItem NodeItem::create_node(const std::string &category, Type type, const Inputs &inputs) const { NodeItem res = create_node(category, type); for (const auto &it : inputs) { if (it.second) { res.set_input(it.first, it.second); } } return res; } void NodeItem::set_input(const std::string &in_name, const NodeItem &item) { if (item.value) { Type item_type = item.type(); switch (item_type) { case Type::String: set_input(in_name, item.value->asA(), item_type); break; case Type::Boolean: set_input(in_name, item.value->asA(), item_type); break; case Type::Integer: set_input(in_name, item.value->asA(), item_type); break; case Type::Float: set_input(in_name, item.value->asA(), item_type); break; case Type::Vector2: set_input(in_name, item.value->asA(), item_type); break; case Type::Vector3: set_input(in_name, item.value->asA(), item_type); break; case Type::Vector4: set_input(in_name, item.value->asA(), item_type); break; case Type::Color3: set_input(in_name, item.value->asA(), item_type); break; case Type::Color4: set_input(in_name, item.value->asA(), item_type); break; default: BLI_assert_unreachable(); } } else if (item.node) { if (type() == Type::SurfaceShader) { auto output_name = item.node->getName() + "_out"; auto output = graph_->getOutput(output_name); if (!output) { output = graph_->addOutput(output_name, item.node->getType()); } output->setConnectedNode(item.node); node->setConnectedOutput(in_name, output); } else { node->setConnectedNode(in_name, item.node); } } else if (item.input) { node->setAttribute("interfacename", item.input->getName()); } else if (item.output) { node->setConnectedOutput(in_name, item.output); } else { CLOG_WARN(LOG_MATERIALX_SHADER, "Empty item to input: %s", in_name.c_str()); } } NodeItem NodeItem::add_output(const std::string &out_name, Type out_type) { NodeItem res = empty(); res.output = node->addOutput(out_name, type(out_type)); return res; } NodeItem NodeItem::create_input(const std::string &name, const NodeItem &item) const { NodeItem res = empty(); res.input = graph_->addInput(name); Type item_type = item.type(); if (item.node) { res.input->setConnectedNode(item.node); } else { BLI_assert_unreachable(); } res.input->setType(type(item_type)); return res; } NodeItem NodeItem::create_output(const std::string &name, const NodeItem &item) const { NodeItem res = empty(); res.output = graph_->addOutput(name); Type item_type = item.type(); if (item.node) { res.output->setConnectedNode(item.node); } else if (item.input) { res.output->setInterfaceName(item.input->getName()); } else { BLI_assert_unreachable(); } res.output->setType(type(item_type)); return res; } NodeItem::Type NodeItem::cast_types(NodeItem &item1, NodeItem &item2) { Type t1 = item1.type(); Type t2 = item2.type(); if (t1 == t2) { return t1; } if (!is_arithmetic(t1) || !is_arithmetic(t2)) { CLOG_WARN( LOG_MATERIALX_SHADER, "Can't adjust types: %s <-> %s", type(t1).c_str(), type(t2).c_str()); return Type::Empty; } if (t1 < t2) { item1 = item1.convert(t2); return t2; } item2 = item2.convert(t1); return t1; } bool NodeItem::is_arithmetic() const { return is_arithmetic(type()); } NodeItem NodeItem::arithmetic(const std::string &category, std::function func) const { NodeItem res = empty(); Type type = this->type(); BLI_assert(is_arithmetic(type)); if (value) { switch (type) { case Type::Float: { float v = value->asA(); res.value = MaterialX::Value::createValue(func(v)); break; } case Type::Color3: { auto v = value->asA(); res.value = MaterialX::Value::createValue( {func(v[0]), func(v[1]), func(v[2])}); break; } case Type::Color4: { auto v = value->asA(); res.value = MaterialX::Value::createValue( {func(v[0]), func(v[1]), func(v[2]), func(v[3])}); break; } case Type::Vector2: { auto v = value->asA(); res.value = MaterialX::Value::createValue({func(v[0]), func(v[1])}); break; } case Type::Vector3: { auto v = value->asA(); res.value = MaterialX::Value::createValue( {func(v[0]), func(v[1]), func(v[2])}); break; } case Type::Vector4: { auto v = value->asA(); res.value = MaterialX::Value::createValue( {func(v[0]), func(v[1]), func(v[2]), func(v[3])}); break; } default: BLI_assert_unreachable(); } } else { res = create_node(category, type, {{"in", *this}}); } return res; } NodeItem NodeItem::arithmetic(const NodeItem &other, const std::string &category, std::function func, Type to_type) const { NodeItem res = empty(); NodeItem item1 = *this; NodeItem item2 = other; to_type = (to_type == Type::Any) ? cast_types(item1, item2) : to_type; if (to_type == Type::Empty) { return res; } if (value && other.value) { switch (to_type) { case Type::Float: { float v1 = item1.value->asA(); float v2 = item2.value->asA(); res.value = MaterialX::Value::createValue(func(v1, v2)); break; } case Type::Color3: { auto v1 = item1.value->asA(); auto v2 = item2.value->asA(); res.value = MaterialX::Value::createValue( {func(v1[0], v2[0]), func(v1[1], v2[1]), func(v1[2], v2[2])}); break; } case Type::Color4: { auto v1 = item1.value->asA(); auto v2 = item2.value->asA(); res.value = MaterialX::Value::createValue( {func(v1[0], v2[0]), func(v1[1], v2[1]), func(v1[2], v2[2]), func(v1[3], v2[3])}); break; } case Type::Vector2: { auto v1 = item1.value->asA(); auto v2 = item2.value->asA(); res.value = MaterialX::Value::createValue( {func(v1[0], v2[0]), func(v1[1], v2[1])}); break; } case Type::Vector3: { auto v1 = item1.value->asA(); auto v2 = item2.value->asA(); res.value = MaterialX::Value::createValue( {func(v1[0], v2[0]), func(v1[1], v2[1]), func(v1[2], v2[2])}); break; } case Type::Vector4: { auto v1 = item1.value->asA(); auto v2 = item2.value->asA(); res.value = MaterialX::Value::createValue( {func(v1[0], v2[0]), func(v1[1], v2[1]), func(v1[2], v2[2]), func(v1[3], v2[3])}); break; } default: BLI_assert_unreachable(); } } else { #if !(MATERIALX_MAJOR_VERSION <= 1 && MATERIALX_MINOR_VERSION <= 38) if (category == "atan2") { res = create_node(category, to_type, {{"iny", item1}, {"inx", item2}}); } else #endif { res = create_node(category, to_type, {{"in1", item1}, {"in2", item2}}); } } return res; } } // namespace blender::nodes::materialx