Advance M8-M11 parity workflows
This commit is contained in:
@@ -5,12 +5,17 @@
|
||||
/** \file
|
||||
* \ingroup modifiers
|
||||
*
|
||||
* Native Web evaluator for the first bounded Geometry Nodes closure. It
|
||||
* accepts a single Group Input -> Transform Geometry -> Group Output graph.
|
||||
* Unsupported graphs remain visible and report an explicit modifier error.
|
||||
* Native Web evaluator for the bounded Geometry Nodes allowlist. Unsupported
|
||||
* graphs remain visible and report an explicit modifier error.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
@@ -18,19 +23,32 @@
|
||||
#include "BLI_math_rotation.hh"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_geometry_set.hh"
|
||||
#include "BKE_instances.hh"
|
||||
#include "BKE_lib_query.hh"
|
||||
#include "BKE_mesh.hh"
|
||||
#include "BKE_modifier.hh"
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_legacy_types.hh"
|
||||
#include "BKE_object.hh"
|
||||
|
||||
#include "BLO_read_write.hh"
|
||||
|
||||
#include "DEG_depsgraph_build.hh"
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
#include "DNA_collection_types.h"
|
||||
#include "DNA_image_types.h"
|
||||
#include "DNA_modifier_types.h"
|
||||
#include "DNA_node_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include "GEO_join_geometries.hh"
|
||||
#include "GEO_realize_instances.hh"
|
||||
#include "GEO_transform.hh"
|
||||
|
||||
#include "RNA_prototypes.hh"
|
||||
#include "UI_resources.hh"
|
||||
@@ -89,8 +107,39 @@ static void foreach_ID_link(ModifierData *md, Object *object, IDWalkFunc walk, v
|
||||
static void update_depsgraph(ModifierData *md, const ModifierUpdateDepsgraphContext *ctx)
|
||||
{
|
||||
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
||||
if (nmd->node_group != nullptr) {
|
||||
DEG_add_node_tree_output_relation(ctx->node, nmd->node_group, "Web Geometry Nodes Modifier");
|
||||
if (nmd->node_group == nullptr) {
|
||||
return;
|
||||
}
|
||||
DEG_add_node_tree_output_relation(ctx->node, nmd->node_group, "Web Geometry Nodes Modifier");
|
||||
for (const bNode &node : nmd->node_group->nodes) {
|
||||
for (const bNodeSocket &socket : node.inputs) {
|
||||
if (socket.default_value == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (socket.type == SOCK_OBJECT) {
|
||||
Object *object = static_cast<const bNodeSocketValueObject *>(socket.default_value)->value;
|
||||
if (object != nullptr && object != ctx->object) {
|
||||
DEG_add_object_relation(
|
||||
ctx->node, object, DEG_OB_COMP_TRANSFORM, "Web Geometry Nodes Object Info");
|
||||
DEG_add_object_relation(
|
||||
ctx->node, object, DEG_OB_COMP_GEOMETRY, "Web Geometry Nodes Object Info");
|
||||
}
|
||||
}
|
||||
else if (socket.type == SOCK_COLLECTION) {
|
||||
Collection *collection =
|
||||
static_cast<const bNodeSocketValueCollection *>(socket.default_value)->value;
|
||||
if (collection != nullptr) {
|
||||
DEG_add_collection_geometry_relation(
|
||||
ctx->node, collection, "Web Geometry Nodes Collection Info");
|
||||
}
|
||||
}
|
||||
else if (socket.type == SOCK_IMAGE) {
|
||||
Image *image = static_cast<const bNodeSocketValueImage *>(socket.default_value)->value;
|
||||
if (image != nullptr) {
|
||||
DEG_add_generic_id_relation(ctx->node, &image->id, "Web Geometry Nodes Image Info");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +151,7 @@ static bool is_disabled(const Scene *, ModifierData *md, bool)
|
||||
static const bNodeSocket *find_input(const bNode &node, const char *identifier)
|
||||
{
|
||||
for (const bNodeSocket &socket : node.inputs) {
|
||||
if (STREQ(socket.identifier, identifier)) {
|
||||
if (STREQ(socket.identifier, identifier) || STREQ(socket.name, identifier)) {
|
||||
return &socket;
|
||||
}
|
||||
}
|
||||
@@ -119,24 +168,12 @@ static const bNodeSocket *find_output(const bNode &node, const char *identifier)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bool has_link(const bNodeTree &tree,
|
||||
const bNode &from_node,
|
||||
const char *from_socket,
|
||||
const bNode &to_node,
|
||||
const char *to_socket)
|
||||
static bool node_is(const bNode &node, const char *idname)
|
||||
{
|
||||
const auto matches_socket = [](const bNodeSocket &socket, const char *name) {
|
||||
return STREQ(socket.identifier, name) || STREQ(socket.name, name);
|
||||
};
|
||||
for (const bNodeLink &link : tree.links) {
|
||||
if (link.fromnode == &from_node && link.tonode == &to_node && link.fromsock != nullptr &&
|
||||
link.tosock != nullptr && matches_socket(*link.fromsock, from_socket) &&
|
||||
matches_socket(*link.tosock, to_socket))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (STREQ(node.idname, idname)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return std::string(node.idname) == "Undefined[" + std::string(idname) + "]";
|
||||
}
|
||||
|
||||
static bool has_input_link(const bNodeTree &tree, const bNode &node, const char *socket_name)
|
||||
@@ -151,6 +188,659 @@ static bool has_input_link(const bNodeTree &tree, const bNode &node, const char
|
||||
return false;
|
||||
}
|
||||
|
||||
using WebNodeValue = std::variant<std::monostate,
|
||||
bke::GeometrySet,
|
||||
bool,
|
||||
int,
|
||||
float,
|
||||
float3,
|
||||
std::string,
|
||||
Object *,
|
||||
Collection *,
|
||||
Image *>;
|
||||
|
||||
class WebGeometryNodeEvaluator {
|
||||
public:
|
||||
static constexpr int max_evaluations = 4096;
|
||||
static constexpr int max_point_elements = 1'000'000;
|
||||
static constexpr int max_edge_elements = 2'000'000;
|
||||
static constexpr int max_face_elements = 2'000'000;
|
||||
static constexpr int max_corner_elements = 4'000'000;
|
||||
static constexpr int max_instance_elements = 100'000;
|
||||
static constexpr int64_t max_field_bytes = 64 * 1024 * 1024;
|
||||
|
||||
private:
|
||||
const bNodeTree &tree_;
|
||||
const ModifierEvalContext &ctx_;
|
||||
const Mesh &input_mesh_;
|
||||
std::string error_;
|
||||
int evaluation_count_ = 0;
|
||||
|
||||
bool mesh_within_domain_budget(const Mesh &mesh)
|
||||
{
|
||||
if (mesh.verts_num > max_point_elements || mesh.edges_num > max_edge_elements ||
|
||||
mesh.faces_num > max_face_elements || mesh.corners_num > max_corner_elements)
|
||||
{
|
||||
error_ = "field/domain element budget exceeded";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool geometry_within_domain_budget(const bke::GeometrySet &geometry)
|
||||
{
|
||||
if (const Mesh *mesh = geometry.get_mesh()) {
|
||||
if (!mesh_within_domain_budget(*mesh)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (const bke::Instances *instances = geometry.get_instances()) {
|
||||
if (instances->instances_num() > max_instance_elements) {
|
||||
error_ = "instance domain element budget exceeded";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
WebNodeValue bounded_geometry(bke::GeometrySet geometry)
|
||||
{
|
||||
if (!geometry_within_domain_budget(geometry)) {
|
||||
return {};
|
||||
}
|
||||
return std::move(geometry);
|
||||
}
|
||||
|
||||
const bNode *owner_of(const bNodeSocket &socket) const
|
||||
{
|
||||
for (const bNode &node : tree_.nodes) {
|
||||
for (const bNodeSocket &candidate : node.inputs) {
|
||||
if (&candidate == &socket) {
|
||||
return &node;
|
||||
}
|
||||
}
|
||||
for (const bNodeSocket &candidate : node.outputs) {
|
||||
if (&candidate == &socket) {
|
||||
return &node;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Vector<const bNodeLink *> input_links(const bNodeSocket &socket) const
|
||||
{
|
||||
Vector<const bNodeLink *> links;
|
||||
for (const bNodeLink &link : tree_.links) {
|
||||
if (link.tosock == &socket && link.fromnode != nullptr && link.fromsock != nullptr) {
|
||||
links.append(&link);
|
||||
}
|
||||
}
|
||||
std::ranges::sort(links, [](const bNodeLink *a, const bNodeLink *b) {
|
||||
return a->multi_input_sort_id > b->multi_input_sort_id;
|
||||
});
|
||||
return links;
|
||||
}
|
||||
|
||||
WebNodeValue default_value(const bNodeSocket &socket)
|
||||
{
|
||||
if (socket.default_value == nullptr) {
|
||||
return {};
|
||||
}
|
||||
switch (socket.type) {
|
||||
case SOCK_BOOLEAN:
|
||||
return static_cast<const bNodeSocketValueBoolean *>(socket.default_value)->value != 0;
|
||||
case SOCK_INT:
|
||||
return static_cast<const bNodeSocketValueInt *>(socket.default_value)->value;
|
||||
case SOCK_FLOAT:
|
||||
return static_cast<const bNodeSocketValueFloat *>(socket.default_value)->value;
|
||||
case SOCK_VECTOR:
|
||||
return float3(static_cast<const bNodeSocketValueVector *>(socket.default_value)->value);
|
||||
case SOCK_STRING:
|
||||
return std::string(
|
||||
static_cast<const bNodeSocketValueString *>(socket.default_value)->value);
|
||||
case SOCK_OBJECT:
|
||||
return static_cast<const bNodeSocketValueObject *>(socket.default_value)->value;
|
||||
case SOCK_COLLECTION:
|
||||
return static_cast<const bNodeSocketValueCollection *>(socket.default_value)->value;
|
||||
case SOCK_IMAGE:
|
||||
return static_cast<const bNodeSocketValueImage *>(socket.default_value)->value;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<float> number(const WebNodeValue &value) const
|
||||
{
|
||||
if (const int *integer = std::get_if<int>(&value)) {
|
||||
return float(*integer);
|
||||
}
|
||||
if (const float *scalar = std::get_if<float>(&value)) {
|
||||
return *scalar;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bke::GeometrySet copy_geometry(const bke::GeometrySet &geometry) const
|
||||
{
|
||||
if (const Mesh *mesh = geometry.get_mesh()) {
|
||||
return bke::GeometrySet::from_mesh(BKE_mesh_copy_for_eval(*mesh));
|
||||
}
|
||||
bke::GeometrySet copy = geometry;
|
||||
copy.ensure_owns_all_data();
|
||||
return copy;
|
||||
}
|
||||
|
||||
WebNodeValue evaluate_input(const bNodeSocket &socket)
|
||||
{
|
||||
const Vector<const bNodeLink *> links = input_links(socket);
|
||||
if (links.size() == 1) {
|
||||
return evaluate_output(*links.first()->fromsock);
|
||||
}
|
||||
if (links.size() > 1) {
|
||||
error_ = "multiple links on a non-multi-input socket";
|
||||
return {};
|
||||
}
|
||||
return default_value(socket);
|
||||
}
|
||||
|
||||
std::optional<bke::GeometrySet> geometry_input(const bNode &node, const char *name)
|
||||
{
|
||||
const bNodeSocket *socket = find_input(node, name);
|
||||
if (socket == nullptr) {
|
||||
error_ = std::string("missing geometry input: ") + name;
|
||||
return std::nullopt;
|
||||
}
|
||||
WebNodeValue value = evaluate_input(*socket);
|
||||
if (bke::GeometrySet *geometry = std::get_if<bke::GeometrySet>(&value)) {
|
||||
return std::move(*geometry);
|
||||
}
|
||||
error_ = std::string("geometry input did not evaluate to geometry: ") + name;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
WebNodeValue evaluate_compare(const bNode &node)
|
||||
{
|
||||
const NodeFunctionCompare *storage = static_cast<const NodeFunctionCompare *>(node.storage);
|
||||
const bNodeSocket *a_socket = find_input(node, "A");
|
||||
const bNodeSocket *b_socket = find_input(node, "B");
|
||||
if (storage == nullptr || a_socket == nullptr || b_socket == nullptr ||
|
||||
!ELEM(storage->data_type, SOCK_INT, SOCK_FLOAT))
|
||||
{
|
||||
error_ = "Compare supports bounded Int/Float scalar inputs only";
|
||||
return {};
|
||||
}
|
||||
const std::optional<float> a = number(evaluate_input(*a_socket));
|
||||
const std::optional<float> b = number(evaluate_input(*b_socket));
|
||||
if (!a || !b) {
|
||||
error_ = "Compare scalar input is unavailable";
|
||||
return {};
|
||||
}
|
||||
switch (storage->operation) {
|
||||
case NODE_COMPARE_LESS_THAN:
|
||||
return *a < *b;
|
||||
case NODE_COMPARE_LESS_EQUAL:
|
||||
return *a <= *b;
|
||||
case NODE_COMPARE_GREATER_THAN:
|
||||
return *a > *b;
|
||||
case NODE_COMPARE_GREATER_EQUAL:
|
||||
return *a >= *b;
|
||||
case NODE_COMPARE_EQUAL:
|
||||
return *a == *b;
|
||||
case NODE_COMPARE_NOT_EQUAL:
|
||||
return *a != *b;
|
||||
default:
|
||||
error_ = "Compare operation is outside the bounded evaluator";
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
WebNodeValue evaluate_math(const bNode &node)
|
||||
{
|
||||
const bNodeSocket *a_socket = find_input(node, "Value");
|
||||
const bNodeSocket *b_socket = nullptr;
|
||||
for (const bNodeSocket &socket : node.inputs) {
|
||||
if (STREQ(socket.identifier, "Value_001")) {
|
||||
b_socket = &socket;
|
||||
}
|
||||
}
|
||||
if (a_socket == nullptr || b_socket == nullptr) {
|
||||
error_ = "Math sockets are incomplete";
|
||||
return {};
|
||||
}
|
||||
const std::optional<float> a = number(evaluate_input(*a_socket));
|
||||
const std::optional<float> b = number(evaluate_input(*b_socket));
|
||||
if (!a || !b) {
|
||||
error_ = "Math scalar input is unavailable";
|
||||
return {};
|
||||
}
|
||||
switch (node.custom1) {
|
||||
case NODE_MATH_ADD:
|
||||
return *a + *b;
|
||||
case NODE_MATH_SUBTRACT:
|
||||
return *a - *b;
|
||||
case NODE_MATH_MULTIPLY:
|
||||
return *a * *b;
|
||||
case NODE_MATH_DIVIDE:
|
||||
return *b == 0.0f ? 0.0f : *a / *b;
|
||||
case NODE_MATH_MINIMUM:
|
||||
return std::min(*a, *b);
|
||||
case NODE_MATH_MAXIMUM:
|
||||
return std::max(*a, *b);
|
||||
default:
|
||||
error_ = "Math operation is outside the bounded evaluator";
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
WebNodeValue evaluate_collection_info(const bNode &node)
|
||||
{
|
||||
const bNodeSocket *collection_socket = find_input(node, "Collection");
|
||||
const bNodeSocket *separate_socket = find_input(node, "Separate Children");
|
||||
const bNodeSocket *reset_socket = find_input(node, "Reset Children");
|
||||
if (collection_socket == nullptr || separate_socket == nullptr || reset_socket == nullptr) {
|
||||
error_ = "Collection Info sockets are incomplete";
|
||||
return {};
|
||||
}
|
||||
const WebNodeValue collection_value = evaluate_input(*collection_socket);
|
||||
const WebNodeValue separate_value = evaluate_input(*separate_socket);
|
||||
const WebNodeValue reset_value = evaluate_input(*reset_socket);
|
||||
Collection *const *collection = std::get_if<Collection *>(&collection_value);
|
||||
const bool *separate_children = std::get_if<bool>(&separate_value);
|
||||
const bool *reset_children = std::get_if<bool>(&reset_value);
|
||||
if (collection == nullptr || *collection == nullptr || separate_children == nullptr ||
|
||||
reset_children == nullptr || !*separate_children)
|
||||
{
|
||||
error_ = "Collection Info requires a concrete collection with Separate Children enabled";
|
||||
return {};
|
||||
}
|
||||
|
||||
Vector<Object *> objects;
|
||||
for (const CollectionObject &entry : (*collection)->gobject) {
|
||||
if (entry.ob != nullptr && entry.ob != ctx_.object) {
|
||||
objects.append(entry.ob);
|
||||
}
|
||||
}
|
||||
if (objects.size() > max_instance_elements) {
|
||||
error_ = "Collection Info instance domain budget exceeded";
|
||||
return {};
|
||||
}
|
||||
auto instances = std::make_unique<bke::Instances>();
|
||||
instances->resize(objects.size());
|
||||
MutableSpan<int> handles = instances->reference_handles_for_write();
|
||||
MutableSpan<float4x4> transforms = instances->transforms_for_write();
|
||||
for (const int index : objects.index_range()) {
|
||||
Object *evaluated = reinterpret_cast<Object *>(
|
||||
DEG_get_evaluated_id(ctx_.depsgraph, &objects[index]->id));
|
||||
if (evaluated == nullptr) {
|
||||
error_ = "Collection Info object is not available in the evaluated dependency graph";
|
||||
return {};
|
||||
}
|
||||
handles[index] = instances->add_reference(*evaluated);
|
||||
transforms[index] = *reset_children ? float4x4::identity() : evaluated->object_to_world();
|
||||
}
|
||||
instances->tag_reference_handles_changed();
|
||||
return bounded_geometry(bke::GeometrySet::from_instances(std::move(instances)));
|
||||
}
|
||||
|
||||
WebNodeValue evaluate_output(const bNodeSocket &socket)
|
||||
{
|
||||
if (++evaluation_count_ > max_evaluations) {
|
||||
error_ = "evaluation budget exceeded";
|
||||
return {};
|
||||
}
|
||||
const bNode *node = owner_of(socket);
|
||||
if (node == nullptr) {
|
||||
error_ = "output socket owner is missing";
|
||||
return {};
|
||||
}
|
||||
if (node_is(*node, "NodeGroupInput")) {
|
||||
if (socket.type != SOCK_GEOMETRY) {
|
||||
error_ = "only the Geometry group input is supported";
|
||||
return {};
|
||||
}
|
||||
if (!mesh_within_domain_budget(input_mesh_)) {
|
||||
return {};
|
||||
}
|
||||
return bounded_geometry(
|
||||
bke::GeometrySet::from_mesh(BKE_mesh_copy_for_eval(input_mesh_)));
|
||||
}
|
||||
if (node_is(*node, "FunctionNodeInputInt")) {
|
||||
const NodeInputInt *storage = static_cast<const NodeInputInt *>(node->storage);
|
||||
return storage == nullptr ? WebNodeValue{} : WebNodeValue{storage->integer};
|
||||
}
|
||||
if (node_is(*node, "FunctionNodeInputVector")) {
|
||||
const NodeInputVector *storage = static_cast<const NodeInputVector *>(node->storage);
|
||||
if (storage == nullptr || storage->dimensions != 3) {
|
||||
error_ = "Vector input requires exactly three dimensions";
|
||||
return {};
|
||||
}
|
||||
return float3(storage->vector);
|
||||
}
|
||||
if (node_is(*node, "ShaderNodeValue")) {
|
||||
return default_value(socket);
|
||||
}
|
||||
if (node_is(*node, "ShaderNodeMath")) {
|
||||
return evaluate_math(*node);
|
||||
}
|
||||
if (node_is(*node, "FunctionNodeCompare")) {
|
||||
return evaluate_compare(*node);
|
||||
}
|
||||
if (node_is(*node, "GeometryNodeImageInfo")) {
|
||||
const bNodeSocket *image_socket = find_input(*node, "Image");
|
||||
if (image_socket == nullptr) {
|
||||
error_ = "Image Info image input is missing";
|
||||
return {};
|
||||
}
|
||||
const WebNodeValue image_value = evaluate_input(*image_socket);
|
||||
Image *const *image = std::get_if<Image *>(&image_value);
|
||||
if (image == nullptr || *image == nullptr) {
|
||||
error_ = "Image Info image is unavailable";
|
||||
return {};
|
||||
}
|
||||
if (STREQ(socket.identifier, "Width")) {
|
||||
return (*image)->gen_x;
|
||||
}
|
||||
if (STREQ(socket.identifier, "Height")) {
|
||||
return (*image)->gen_y;
|
||||
}
|
||||
if (STREQ(socket.identifier, "Has Alpha")) {
|
||||
return true;
|
||||
}
|
||||
if (STREQ(socket.identifier, "Frame Count")) {
|
||||
return 1;
|
||||
}
|
||||
if (STREQ(socket.identifier, "FPS")) {
|
||||
return 0.0f;
|
||||
}
|
||||
error_ = "Image Info output is unsupported";
|
||||
return {};
|
||||
}
|
||||
if (node_is(*node, "GeometryNodeObjectInfo")) {
|
||||
const bNodeSocket *object_socket = find_input(*node, "Object");
|
||||
if (object_socket == nullptr) {
|
||||
error_ = "Object Info object input is missing";
|
||||
return {};
|
||||
}
|
||||
const WebNodeValue object_value = evaluate_input(*object_socket);
|
||||
Object *const *object = std::get_if<Object *>(&object_value);
|
||||
if (object == nullptr || *object == nullptr || *object == ctx_.object) {
|
||||
error_ = "Object Info target is unavailable or recursive";
|
||||
return {};
|
||||
}
|
||||
Object *evaluated = reinterpret_cast<Object *>(
|
||||
DEG_get_evaluated_id(ctx_.depsgraph, &(*object)->id));
|
||||
if (evaluated == nullptr) {
|
||||
error_ = "Object Info target is not evaluated";
|
||||
return {};
|
||||
}
|
||||
if (STREQ(socket.identifier, "Location")) {
|
||||
return float3(evaluated->object_to_world().location());
|
||||
}
|
||||
if (STREQ(socket.identifier, "Scale")) {
|
||||
return float3(evaluated->scale);
|
||||
}
|
||||
if (STREQ(socket.identifier, "Geometry")) {
|
||||
Mesh *target_mesh = BKE_object_get_evaluated_mesh(evaluated);
|
||||
if (target_mesh == nullptr) {
|
||||
error_ = "Object Info target has no evaluated mesh";
|
||||
return {};
|
||||
}
|
||||
if (!mesh_within_domain_budget(*target_mesh)) {
|
||||
return {};
|
||||
}
|
||||
return bounded_geometry(
|
||||
bke::GeometrySet::from_mesh(BKE_mesh_copy_for_eval(*target_mesh)));
|
||||
}
|
||||
error_ = "Object Info output is outside the bounded evaluator";
|
||||
return {};
|
||||
}
|
||||
if (node_is(*node, "GeometryNodeCollectionInfo")) {
|
||||
return evaluate_collection_info(*node);
|
||||
}
|
||||
if (node_is(*node, "GeometryNodeJoinGeometry")) {
|
||||
const bNodeSocket *input = find_input(*node, "Geometry");
|
||||
if (input == nullptr) {
|
||||
error_ = "Join Geometry input is missing";
|
||||
return {};
|
||||
}
|
||||
Vector<bke::GeometrySet> geometries;
|
||||
for (const bNodeLink *link : input_links(*input)) {
|
||||
WebNodeValue value = evaluate_output(*link->fromsock);
|
||||
bke::GeometrySet *geometry = std::get_if<bke::GeometrySet>(&value);
|
||||
if (geometry == nullptr) {
|
||||
error_ = "Join Geometry received a non-geometry input";
|
||||
return {};
|
||||
}
|
||||
geometries.append(std::move(*geometry));
|
||||
}
|
||||
if (geometries.is_empty()) {
|
||||
return bke::GeometrySet();
|
||||
}
|
||||
int64_t points = 0;
|
||||
int64_t edges = 0;
|
||||
int64_t faces = 0;
|
||||
int64_t corners = 0;
|
||||
int64_t instances = 0;
|
||||
for (const bke::GeometrySet &geometry : geometries) {
|
||||
if (const Mesh *mesh = geometry.get_mesh()) {
|
||||
points += mesh->verts_num;
|
||||
edges += mesh->edges_num;
|
||||
faces += mesh->faces_num;
|
||||
corners += mesh->corners_num;
|
||||
}
|
||||
if (const bke::Instances *geometry_instances = geometry.get_instances()) {
|
||||
instances += geometry_instances->instances_num();
|
||||
}
|
||||
}
|
||||
if (points > max_point_elements || edges > max_edge_elements ||
|
||||
faces > max_face_elements || corners > max_corner_elements ||
|
||||
instances > max_instance_elements)
|
||||
{
|
||||
error_ = "Join Geometry field/domain budget exceeded";
|
||||
return {};
|
||||
}
|
||||
return bounded_geometry(geometry::join_geometries(
|
||||
geometries.as_span(), bke::AttributeFilter::default_filter()));
|
||||
}
|
||||
if (node_is(*node, "GeometryNodeSeparateGeometry")) {
|
||||
std::optional<bke::GeometrySet> geometry = geometry_input(*node, "Geometry");
|
||||
const bNodeSocket *selection_socket = find_input(*node, "Selection");
|
||||
if (!geometry || selection_socket == nullptr) {
|
||||
return {};
|
||||
}
|
||||
const WebNodeValue selection_value = evaluate_input(*selection_socket);
|
||||
const bool *selection = std::get_if<bool>(&selection_value);
|
||||
if (selection == nullptr) {
|
||||
error_ = "Separate Geometry requires a bounded constant selection";
|
||||
return {};
|
||||
}
|
||||
const bool selected_output = STREQ(socket.identifier, "Selection");
|
||||
const bool inverted_output = STREQ(socket.identifier, "Inverted");
|
||||
if (!selected_output && !inverted_output) {
|
||||
error_ = "Separate Geometry output is invalid";
|
||||
return {};
|
||||
}
|
||||
return bounded_geometry((*selection == selected_output) ? std::move(*geometry) :
|
||||
bke::GeometrySet());
|
||||
}
|
||||
if (node_is(*node, "GeometryNodeRealizeInstances")) {
|
||||
std::optional<bke::GeometrySet> geometry = geometry_input(*node, "Geometry");
|
||||
if (!geometry) {
|
||||
return {};
|
||||
}
|
||||
geometry::RealizeInstancesOptions options;
|
||||
options.keep_original_ids = false;
|
||||
options.realize_instance_attributes = true;
|
||||
options.realize_to_point_domain = true;
|
||||
geometry::RealizeInstancesResult result = geometry::realize_instances(std::move(*geometry),
|
||||
options);
|
||||
if (!result.errors.is_empty()) {
|
||||
error_ = result.errors.first();
|
||||
return {};
|
||||
}
|
||||
return bounded_geometry(std::move(result.geometry));
|
||||
}
|
||||
if (node_is(*node, "GeometryNodeStoreNamedAttribute")) {
|
||||
std::optional<bke::GeometrySet> geometry = geometry_input(*node, "Geometry");
|
||||
const bNodeSocket *selection_socket = find_input(*node, "Selection");
|
||||
const bNodeSocket *name_socket = find_input(*node, "Name");
|
||||
const bNodeSocket *value_socket = find_input(*node, "Value");
|
||||
const NodeGeometryStoreNamedAttribute *storage =
|
||||
static_cast<const NodeGeometryStoreNamedAttribute *>(node->storage);
|
||||
if (!geometry || selection_socket == nullptr || name_socket == nullptr ||
|
||||
value_socket == nullptr || storage == nullptr || storage->data_type != CD_PROP_FLOAT ||
|
||||
storage->domain != int8_t(bke::AttrDomain::Point))
|
||||
{
|
||||
error_ = "Store Named Attribute supports Float point attributes only";
|
||||
return {};
|
||||
}
|
||||
const WebNodeValue selection_value = evaluate_input(*selection_socket);
|
||||
const WebNodeValue name_value = evaluate_input(*name_socket);
|
||||
const std::optional<float> attribute_value = number(evaluate_input(*value_socket));
|
||||
const bool *selection = std::get_if<bool>(&selection_value);
|
||||
const std::string *name = std::get_if<std::string>(&name_value);
|
||||
if (selection == nullptr || name == nullptr || name->empty() || name->size() > 64 ||
|
||||
!attribute_value)
|
||||
{
|
||||
error_ = "Store Named Attribute inputs are invalid";
|
||||
return {};
|
||||
}
|
||||
bke::GeometrySet result = copy_geometry(*geometry);
|
||||
if (*selection) {
|
||||
Mesh *result_mesh = result.get_mesh_for_write();
|
||||
if (result_mesh == nullptr) {
|
||||
error_ = "Store Named Attribute requires mesh geometry";
|
||||
return {};
|
||||
}
|
||||
if (!mesh_within_domain_budget(*result_mesh) ||
|
||||
int64_t(result_mesh->verts_num) * int64_t(sizeof(float)) > max_field_bytes)
|
||||
{
|
||||
error_ = "Store Named Attribute field budget exceeded";
|
||||
return {};
|
||||
}
|
||||
bke::SpanAttributeWriter<float> writer =
|
||||
result_mesh->attributes_for_write().lookup_or_add_for_write_span<float>(
|
||||
*name, bke::AttrDomain::Point);
|
||||
if (!writer) {
|
||||
error_ = "Store Named Attribute could not allocate the point attribute";
|
||||
return {};
|
||||
}
|
||||
writer.span.fill(*attribute_value);
|
||||
writer.finish();
|
||||
}
|
||||
return bounded_geometry(std::move(result));
|
||||
}
|
||||
if (node_is(*node, "GeometryNodeTransform")) {
|
||||
std::optional<bke::GeometrySet> geometry = geometry_input(*node, "Geometry");
|
||||
const bNodeSocket *translation_socket = find_input(*node, "Translation");
|
||||
const bNodeSocket *rotation_socket = find_input(*node, "Rotation");
|
||||
const bNodeSocket *scale_socket = find_input(*node, "Scale");
|
||||
if (!geometry || translation_socket == nullptr || rotation_socket == nullptr ||
|
||||
scale_socket == nullptr || translation_socket->default_value == nullptr ||
|
||||
rotation_socket->default_value == nullptr || scale_socket->default_value == nullptr ||
|
||||
has_input_link(tree_, *node, "Rotation") || has_input_link(tree_, *node, "Scale"))
|
||||
{
|
||||
error_ = "Transform Geometry inputs are outside the bounded evaluator";
|
||||
return {};
|
||||
}
|
||||
WebNodeValue translation_value = evaluate_input(*translation_socket);
|
||||
const float3 *translation = std::get_if<float3>(&translation_value);
|
||||
if (translation == nullptr) {
|
||||
error_ = "Transform Geometry translation is unavailable";
|
||||
return {};
|
||||
}
|
||||
const bNodeSocketValueRotation &rotation_value =
|
||||
*static_cast<const bNodeSocketValueRotation *>(rotation_socket->default_value);
|
||||
const math::Quaternion rotation = math::to_quaternion(
|
||||
math::EulerXYZ(float3(rotation_value.value_euler)));
|
||||
const float3 scale(
|
||||
static_cast<const bNodeSocketValueVector *>(scale_socket->default_value)->value);
|
||||
bke::GeometrySet result = copy_geometry(*geometry);
|
||||
geometry::transform_geometry(
|
||||
result, math::from_loc_rot_scale<float4x4>(*translation, rotation, scale));
|
||||
return bounded_geometry(std::move(result));
|
||||
}
|
||||
if (node_is(*node, "GeometryNodeSetPosition")) {
|
||||
std::optional<bke::GeometrySet> geometry = geometry_input(*node, "Geometry");
|
||||
const bNodeSocket *selection_socket = find_input(*node, "Selection");
|
||||
const bNodeSocket *offset_socket = find_input(*node, "Offset");
|
||||
if (!geometry || selection_socket == nullptr || offset_socket == nullptr ||
|
||||
has_input_link(tree_, *node, "Position"))
|
||||
{
|
||||
error_ = "Set Position inputs are outside the bounded evaluator";
|
||||
return {};
|
||||
}
|
||||
const WebNodeValue selection_value = evaluate_input(*selection_socket);
|
||||
const WebNodeValue offset_value = evaluate_input(*offset_socket);
|
||||
const bool *selection = std::get_if<bool>(&selection_value);
|
||||
const float3 *offset = std::get_if<float3>(&offset_value);
|
||||
if (selection == nullptr || offset == nullptr) {
|
||||
error_ = "Set Position requires a constant selection and vector offset";
|
||||
return {};
|
||||
}
|
||||
bke::GeometrySet result = copy_geometry(*geometry);
|
||||
if (*selection) {
|
||||
geometry::translate_geometry(result, *offset);
|
||||
}
|
||||
return bounded_geometry(std::move(result));
|
||||
}
|
||||
|
||||
error_ = std::string("unsupported node type: ") + node->idname;
|
||||
return {};
|
||||
}
|
||||
|
||||
public:
|
||||
WebGeometryNodeEvaluator(const bNodeTree &tree,
|
||||
const ModifierEvalContext &ctx,
|
||||
const Mesh &input_mesh)
|
||||
: tree_(tree), ctx_(ctx), input_mesh_(input_mesh)
|
||||
{
|
||||
}
|
||||
|
||||
Mesh *evaluate()
|
||||
{
|
||||
const bNode *group_output = nullptr;
|
||||
for (const bNode &node : tree_.nodes) {
|
||||
if (node_is(node, "NodeGroupOutput")) {
|
||||
if (group_output != nullptr) {
|
||||
error_ = "multiple Group Output nodes are unsupported";
|
||||
return nullptr;
|
||||
}
|
||||
group_output = &node;
|
||||
}
|
||||
}
|
||||
const bNodeSocket *geometry_socket = group_output == nullptr ? nullptr :
|
||||
find_input(*group_output,
|
||||
"Geometry");
|
||||
if (geometry_socket == nullptr) {
|
||||
error_ = "Geometry Group Output is missing";
|
||||
return nullptr;
|
||||
}
|
||||
WebNodeValue value = evaluate_input(*geometry_socket);
|
||||
bke::GeometrySet *geometry = std::get_if<bke::GeometrySet>(&value);
|
||||
if (geometry == nullptr || !error_.empty()) {
|
||||
if (error_.empty()) {
|
||||
error_ = "Geometry Group Output did not evaluate to geometry";
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
if (!geometry_within_domain_budget(*geometry)) {
|
||||
return nullptr;
|
||||
}
|
||||
bke::MeshComponent &mesh_component =
|
||||
geometry->get_component_for_write<bke::MeshComponent>();
|
||||
mesh_component.ensure_owns_direct_data();
|
||||
Mesh *result = mesh_component.release();
|
||||
return result == nullptr ? BKE_mesh_new_nomain(0, 0, 0, 0) : result;
|
||||
}
|
||||
|
||||
const std::string &error() const
|
||||
{
|
||||
return error_;
|
||||
}
|
||||
};
|
||||
|
||||
static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *mesh)
|
||||
{
|
||||
const NodesModifierData *nmd = reinterpret_cast<const NodesModifierData *>(md);
|
||||
@@ -168,13 +858,8 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh
|
||||
return mesh;
|
||||
}
|
||||
|
||||
const bNode *group_input = nullptr;
|
||||
const bNode *group_output = nullptr;
|
||||
const bNode *transform = nullptr;
|
||||
const bNode *set_position = nullptr;
|
||||
bool has_simulation_node = false;
|
||||
int node_count = 0;
|
||||
int link_count = 0;
|
||||
for (const bNode &node : tree->nodes) {
|
||||
node_count++;
|
||||
if (ELEM(node.type_legacy,
|
||||
@@ -188,25 +873,6 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh
|
||||
{
|
||||
has_simulation_node = true;
|
||||
}
|
||||
if (find_input(node, "Translation") != nullptr && find_input(node, "Rotation") != nullptr &&
|
||||
find_input(node, "Scale") != nullptr)
|
||||
{
|
||||
transform = &node;
|
||||
}
|
||||
else if (find_input(node, "Offset") != nullptr && find_input(node, "Position") != nullptr &&
|
||||
find_input(node, "Selection") != nullptr)
|
||||
{
|
||||
set_position = &node;
|
||||
}
|
||||
else if (node.inputs.first == nullptr && node.outputs.first != nullptr) {
|
||||
group_input = &node;
|
||||
}
|
||||
else if (node.inputs.first != nullptr && node.outputs.first == nullptr) {
|
||||
group_output = &node;
|
||||
}
|
||||
}
|
||||
for ([[maybe_unused]] const bNodeLink &link : tree->links) {
|
||||
link_count++;
|
||||
}
|
||||
|
||||
if (has_simulation_node) {
|
||||
@@ -217,85 +883,20 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh
|
||||
return mesh;
|
||||
}
|
||||
|
||||
const bNode *geometry_node = transform != nullptr ? transform : set_position;
|
||||
const bool input_link_valid = group_input != nullptr && geometry_node != nullptr &&
|
||||
has_link(*tree, *group_input, "Geometry", *geometry_node, "Geometry");
|
||||
const bool output_link_valid = geometry_node != nullptr && group_output != nullptr &&
|
||||
has_link(*tree, *geometry_node, "Geometry", *group_output, "Geometry");
|
||||
if (node_count != 3 || link_count != 2 || group_input == nullptr ||
|
||||
group_output == nullptr || geometry_node == nullptr || (transform != nullptr && set_position != nullptr) ||
|
||||
!input_link_valid || !output_link_valid)
|
||||
{
|
||||
BKE_modifier_set_error(
|
||||
ctx->object,
|
||||
md,
|
||||
"Web Geometry Nodes supports only a single constant Transform Geometry or Set Position node");
|
||||
return mesh;
|
||||
}
|
||||
|
||||
|
||||
if (set_position != nullptr) {
|
||||
const bNodeSocket *selection_socket = find_input(*set_position, "Selection");
|
||||
const bNodeSocket *offset_socket = find_input(*set_position, "Offset");
|
||||
if (selection_socket == nullptr || offset_socket == nullptr ||
|
||||
selection_socket->default_value == nullptr || offset_socket->default_value == nullptr ||
|
||||
has_input_link(*tree, *set_position, "Selection") ||
|
||||
has_input_link(*tree, *set_position, "Position") ||
|
||||
has_input_link(*tree, *set_position, "Offset"))
|
||||
{
|
||||
BKE_modifier_set_error(
|
||||
ctx->object, md, "Web Geometry Nodes Set Position supports only unlinked constant inputs");
|
||||
return mesh;
|
||||
}
|
||||
Mesh *result = BKE_mesh_copy_for_eval(*mesh);
|
||||
const bool selected = static_cast<const bNodeSocketValueBoolean *>(
|
||||
selection_socket->default_value)
|
||||
->value;
|
||||
if (selected) {
|
||||
const float3 offset(
|
||||
static_cast<const bNodeSocketValueVector *>(offset_socket->default_value)->value);
|
||||
for (float3 &position : result->vert_positions_for_write()) {
|
||||
position += offset;
|
||||
}
|
||||
result->tag_positions_changed();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const bNodeSocket *mode_socket = find_input(*transform, "Mode");
|
||||
const int mode = mode_socket && mode_socket->default_value ?
|
||||
static_cast<const bNodeSocketValueMenu *>(mode_socket->default_value)->value :
|
||||
GEO_NODE_TRANSFORM_MODE_COMPONENTS;
|
||||
if (mode != GEO_NODE_TRANSFORM_MODE_COMPONENTS) {
|
||||
if (node_count > WebGeometryNodeEvaluator::max_evaluations) {
|
||||
BKE_modifier_set_error(ctx->object,
|
||||
md,
|
||||
"Web Geometry Nodes Transform supports Components mode only");
|
||||
"WEB_GEOMETRY_NODES_EVALUATOR_UNSUPPORTED: node budget exceeded");
|
||||
return mesh;
|
||||
}
|
||||
|
||||
const bNodeSocket *translation_socket = find_input(*transform, "Translation");
|
||||
const bNodeSocket *rotation_socket = find_input(*transform, "Rotation");
|
||||
const bNodeSocket *scale_socket = find_input(*transform, "Scale");
|
||||
if (translation_socket == nullptr || rotation_socket == nullptr || scale_socket == nullptr ||
|
||||
translation_socket->default_value == nullptr || rotation_socket->default_value == nullptr ||
|
||||
scale_socket->default_value == nullptr)
|
||||
{
|
||||
BKE_modifier_set_error(ctx->object, md, "Web Geometry Nodes Transform sockets are incomplete");
|
||||
WebGeometryNodeEvaluator evaluator(*tree, *ctx, *mesh);
|
||||
Mesh *result = evaluator.evaluate();
|
||||
if (result == nullptr) {
|
||||
const std::string message = "WEB_GEOMETRY_NODES_EVALUATOR_UNSUPPORTED: " + evaluator.error();
|
||||
BKE_modifier_set_error(ctx->object, md, "%s", message.c_str());
|
||||
return mesh;
|
||||
}
|
||||
|
||||
const float3 translation(
|
||||
static_cast<const bNodeSocketValueVector *>(translation_socket->default_value)->value);
|
||||
const bNodeSocketValueRotation &rotation_value =
|
||||
*static_cast<const bNodeSocketValueRotation *>(rotation_socket->default_value);
|
||||
const math::Quaternion rotation = math::to_quaternion(
|
||||
math::EulerXYZ(float3(rotation_value.value_euler)));
|
||||
const float3 scale(
|
||||
static_cast<const bNodeSocketValueVector *>(scale_socket->default_value)->value);
|
||||
|
||||
Mesh *result = BKE_mesh_copy_for_eval(*mesh);
|
||||
bke::mesh_transform(
|
||||
*result, math::from_loc_rot_scale<float4x4>(translation, rotation, scale), false);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user