Files
test/source/blender/nodes/geometry/nodes/node_geo_simulation.cc
Jacques Lucke 9fd7a093c9 DNA: support getting sdna id for static DNA type
This adds a new `DNA_sdna_type_ids.hh` header:
```cpp
namespace blender::dna {

/**
 * Each DNA struct has an integer identifier which is unique within a specific
 * Blender build, but not necessarily across different builds. The identifier
 * can be used to index into `SDNA.structs`.
 */
template<typename T> int sdna_struct_id_get();

/**
 * The maximum identifier that will be returned by #sdna_struct_id_get in this
 * Blender build.
 */
int sdna_struct_id_get_max();

}  // namespace blender::dna
```

The `sdna_struct_id_get` function is used as replacement of
`SDNA_TYPE_FROM_STRUCT` in all places except the DNA defaults system. The
defaults system is C code and therefore can't use the template. There is ongoing
work to replace the defaults system as well though: #134531.

Using this templated function has some benefits over the old approach:
* No need to rely on macros.
* Can use type inferencing in functions like `BLO_write_struct` which avoids
  redundancy on the call site. E.g. `BLO_write_struct(writer, ActionStrip,
  strip);` can become `BLO_write_struct(writer, strip);` which could even become
  `writer.write_struct(strip);`. None of that is implemented as part of this
  patch though.
* No need to include the generated `dna_type_offsets.h` file which contains a
  huge enum.

Implementation wise, this is done using explicit template instantiations in a
new file generated by `makesdna.cc`: `dna_struct_ids.cc`. The generated file
looks like so:
```cpp
namespace blender::dna {

template<typename T> int sdna_struct_id_get();

int sdna_struct_id_get_max();
int sdna_struct_id_get_max() { return 951; }

}
struct IDPropertyUIData;
template<> int blender:🧬:sdna_struct_id_get<IDPropertyUIData>() { return 1; }
struct IDPropertyUIDataEnumItem;
template<> int blender:🧬:sdna_struct_id_get<IDPropertyUIDataEnumItem>() { return 2; }
```

I tried using static variables instead of separate functions, but I didn't
manage to link it properly. Not quite sure yet if that's an actual limitation or
if I was just missing something.

Pull Request: https://projects.blender.org/blender/blender/pulls/138706
2025-05-12 11:16:26 +02:00

1006 lines
37 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BKE_anonymous_attribute_make.hh"
#include "BKE_attribute_math.hh"
#include "BKE_bake_items_socket.hh"
#include "BKE_context.hh"
#include "BKE_curves.hh"
#include "BKE_instances.hh"
#include "BKE_modifier.hh"
#include "BKE_node_tree_zones.hh"
#include "BKE_screen.hh"
#include "DEG_depsgraph_query.hh"
#include "UI_interface.hh"
#include "NOD_common.hh"
#include "NOD_geo_bake.hh"
#include "NOD_geo_simulation.hh"
#include "NOD_node_extra_info.hh"
#include "NOD_socket.hh"
#include "NOD_socket_items_ops.hh"
#include "NOD_socket_items_ui.hh"
#include "NOD_socket_search_link.hh"
#include "DNA_mesh_types.h"
#include "DNA_pointcloud_types.h"
#include "ED_node.hh"
#include "RNA_access.hh"
#include "RNA_prototypes.hh"
#include "BLT_translation.hh"
#include "GEO_mix_geometries.hh"
#include "WM_api.hh"
#include "BLO_read_write.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_simulation_cc {
static const CPPType &get_simulation_item_cpp_type(const eNodeSocketDatatype socket_type)
{
const bke::bNodeSocketType *typeinfo = bke::node_socket_type_find_static(socket_type);
BLI_assert(typeinfo);
BLI_assert(typeinfo->geometry_nodes_cpp_type);
return *typeinfo->geometry_nodes_cpp_type;
}
static const CPPType &get_simulation_item_cpp_type(const NodeSimulationItem &item)
{
return get_simulation_item_cpp_type(eNodeSocketDatatype(item.socket_type));
}
static bke::bake::BakeSocketConfig make_bake_socket_config(
const Span<NodeSimulationItem> node_simulation_items)
{
bke::bake::BakeSocketConfig config;
const int items_num = node_simulation_items.size();
config.domains.resize(items_num);
config.names.resize(items_num);
config.types.resize(items_num);
config.geometries_by_attribute.resize(items_num);
int last_geometry_index = -1;
for (const int item_i : node_simulation_items.index_range()) {
const NodeSimulationItem &item = node_simulation_items[item_i];
config.types[item_i] = eNodeSocketDatatype(item.socket_type);
config.names[item_i] = item.name;
config.domains[item_i] = AttrDomain(item.attribute_domain);
if (item.socket_type == SOCK_GEOMETRY) {
last_geometry_index = item_i;
}
else if (last_geometry_index != -1) {
config.geometries_by_attribute[item_i].append(last_geometry_index);
}
}
return config;
}
static std::shared_ptr<AttributeFieldInput> make_attribute_field(
const Object &self_object,
const ComputeContext &compute_context,
const bNode &node,
const NodeSimulationItem &item,
const CPPType &type)
{
std::string attribute_name = bke::hash_to_anonymous_attribute_name(
self_object.id.name, compute_context.hash(), node.identifier, item.identifier);
std::string socket_inspection_name = make_anonymous_attribute_socket_inspection_string(
node.label_or_name(), item.name);
return std::make_shared<AttributeFieldInput>(
std::move(attribute_name), type, std::move(socket_inspection_name));
}
static void move_simulation_state_to_values(const Span<NodeSimulationItem> node_simulation_items,
bke::bake::BakeState zone_state,
const Object &self_object,
const ComputeContext &compute_context,
const bNode &node,
bke::bake::BakeDataBlockMap *data_block_map,
Span<void *> r_output_values)
{
const bke::bake::BakeSocketConfig config = make_bake_socket_config(node_simulation_items);
Vector<bke::bake::BakeItem *> bake_items;
for (const NodeSimulationItem &item : node_simulation_items) {
std::unique_ptr<bke::bake::BakeItem> *bake_item = zone_state.items_by_id.lookup_ptr(
item.identifier);
bake_items.append(bake_item ? bake_item->get() : nullptr);
}
bke::bake::move_bake_items_to_socket_values(
bake_items,
config,
data_block_map,
[&](const int i, const CPPType &type) {
return make_attribute_field(
self_object, compute_context, node, node_simulation_items[i], type);
},
r_output_values);
}
static void copy_simulation_state_to_values(const Span<NodeSimulationItem> node_simulation_items,
const bke::bake::BakeStateRef &zone_state,
const Object &self_object,
const ComputeContext &compute_context,
const bNode &node,
bke::bake::BakeDataBlockMap *data_block_map,
Span<void *> r_output_values)
{
const bke::bake::BakeSocketConfig config = make_bake_socket_config(node_simulation_items);
Vector<const bke::bake::BakeItem *> bake_items;
for (const NodeSimulationItem &item : node_simulation_items) {
const bke::bake::BakeItem *const *bake_item = zone_state.items_by_id.lookup_ptr(
item.identifier);
bake_items.append(bake_item ? *bake_item : nullptr);
}
bke::bake::copy_bake_items_to_socket_values(
bake_items,
config,
data_block_map,
[&](const int i, const CPPType &type) {
return make_attribute_field(
self_object, compute_context, node, node_simulation_items[i], type);
},
r_output_values);
}
static bke::bake::BakeState move_values_to_simulation_state(
const Span<NodeSimulationItem> node_simulation_items,
const Span<void *> input_values,
bke::bake::BakeDataBlockMap *data_block_map)
{
const bke::bake::BakeSocketConfig config = make_bake_socket_config(node_simulation_items);
Array<std::unique_ptr<bke::bake::BakeItem>> bake_items =
bke::bake::move_socket_values_to_bake_items(input_values, config, data_block_map);
bke::bake::BakeState bake_state;
for (const int i : node_simulation_items.index_range()) {
const NodeSimulationItem &item = node_simulation_items[i];
std::unique_ptr<bke::bake::BakeItem> &bake_item = bake_items[i];
if (bake_item) {
bake_state.items_by_id.add_new(item.identifier, std::move(bake_item));
}
}
return bake_state;
}
static void draw_simulation_state(const bContext *C,
uiLayout *layout,
bNodeTree &ntree,
bNode &output_node)
{
if (uiLayout *panel = layout->panel(
C, "simulation_state_items", false, IFACE_("Simulation State")))
{
socket_items::ui::draw_items_list_with_operators<SimulationItemsAccessor>(
C, panel, ntree, output_node);
auto &storage = *static_cast<NodeGeometrySimulationOutput *>(output_node.storage);
socket_items::ui::draw_active_item_props<SimulationItemsAccessor>(
ntree, output_node, [&](PointerRNA *item_ptr) {
NodeSimulationItem &active_item = storage.items[storage.active_index];
uiLayoutSetPropSep(panel, true);
uiLayoutSetPropDecorate(panel, false);
panel->prop(item_ptr, "socket_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
if (socket_type_supports_fields(eNodeSocketDatatype(active_item.socket_type))) {
panel->prop(item_ptr, "attribute_domain", UI_ITEM_NONE, std::nullopt, ICON_NONE);
}
});
}
}
/** Shared for simulation input and output node. */
static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *current_node_ptr)
{
bNodeTree &ntree = *reinterpret_cast<bNodeTree *>(current_node_ptr->owner_id);
bNode *current_node = static_cast<bNode *>(current_node_ptr->data);
const bke::bNodeTreeZones *zones = ntree.zones();
if (!zones) {
return;
}
const bke::bNodeTreeZone *zone = zones->get_zone_by_node(current_node->identifier);
if (!zone) {
return;
}
if (!zone->output_node) {
return;
}
bNode &output_node = const_cast<bNode &>(*zone->output_node);
BakeDrawContext ctx;
if (!get_bake_draw_context(C, output_node, ctx)) {
return;
}
draw_simulation_state(C, layout, ntree, output_node);
uiLayoutSetPropSep(layout, true);
uiLayoutSetPropDecorate(layout, false);
uiLayoutSetEnabled(layout, ID_IS_EDITABLE(ctx.object));
{
uiLayout *col = &layout->column(false);
draw_bake_button_row(ctx, col, true);
if (const std::optional<std::string> bake_state_str = get_bake_state_string(ctx)) {
uiLayout *row = &col->row(true);
row->label(*bake_state_str, ICON_NONE);
}
}
draw_common_bake_settings(C, ctx, layout);
draw_data_blocks(C, layout, ctx.bake_rna);
}
namespace sim_input_node {
NODE_STORAGE_FUNCS(NodeGeometrySimulationInput);
class LazyFunctionForSimulationInputNode final : public LazyFunction {
const bNode &node_;
int32_t output_node_id_;
Span<NodeSimulationItem> simulation_items_;
public:
LazyFunctionForSimulationInputNode(const bNodeTree &node_tree,
const bNode &node,
GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info)
: node_(node)
{
debug_name_ = "Simulation Input";
output_node_id_ = node_storage(node).output_node_id;
const bNode &output_node = *node_tree.node_by_id(output_node_id_);
const NodeGeometrySimulationOutput &storage = *static_cast<NodeGeometrySimulationOutput *>(
output_node.storage);
simulation_items_ = {storage.items, storage.items_num};
MutableSpan<int> lf_index_by_bsocket = own_lf_graph_info.mapping.lf_index_by_bsocket;
lf_index_by_bsocket[node.output_socket(0).index_in_tree()] = outputs_.append_and_get_index_as(
"Delta Time", CPPType::get<SocketValueVariant>());
for (const int i : simulation_items_.index_range()) {
const NodeSimulationItem &item = simulation_items_[i];
const bNodeSocket &input_bsocket = node.input_socket(i);
const bNodeSocket &output_bsocket = node.output_socket(i + 1);
const CPPType &type = get_simulation_item_cpp_type(item);
lf_index_by_bsocket[input_bsocket.index_in_tree()] = inputs_.append_and_get_index_as(
item.name, type, lf::ValueUsage::Maybe);
lf_index_by_bsocket[output_bsocket.index_in_tree()] = outputs_.append_and_get_index_as(
item.name, type);
}
}
void execute_impl(lf::Params &params, const lf::Context &context) const final
{
const GeoNodesLFUserData &user_data = *static_cast<const GeoNodesLFUserData *>(
context.user_data);
if (!user_data.call_data->simulation_params) {
this->set_default_outputs(params);
return;
}
if (!user_data.call_data->self_object()) {
/* Self object is currently required for creating anonymous attribute names. */
this->set_default_outputs(params);
return;
}
std::optional<FoundNestedNodeID> found_id = find_nested_node_id(user_data, output_node_id_);
if (!found_id) {
this->set_default_outputs(params);
return;
}
if (found_id->is_in_loop || found_id->is_in_closure) {
this->set_default_outputs(params);
return;
}
SimulationZoneBehavior *zone_behavior = user_data.call_data->simulation_params->get(
found_id->id);
if (!zone_behavior) {
this->set_default_outputs(params);
return;
}
sim_input::Behavior &input_behavior = zone_behavior->input;
float delta_time = 0.0f;
if (auto *info = std::get_if<sim_input::OutputCopy>(&input_behavior)) {
delta_time = info->delta_time;
this->output_simulation_state_copy(
params, user_data, zone_behavior->data_block_map, info->state);
}
else if (auto *info = std::get_if<sim_input::OutputMove>(&input_behavior)) {
delta_time = info->delta_time;
this->output_simulation_state_move(
params, user_data, zone_behavior->data_block_map, std::move(info->state));
}
else if (std::get_if<sim_input::PassThrough>(&input_behavior)) {
delta_time = 0.0f;
this->pass_through(params, user_data, zone_behavior->data_block_map);
}
else {
BLI_assert_unreachable();
}
if (!params.output_was_set(0)) {
params.set_output(0, SocketValueVariant(delta_time));
}
}
void set_default_outputs(lf::Params &params) const
{
set_default_remaining_node_outputs(params, node_);
}
void output_simulation_state_copy(lf::Params &params,
const GeoNodesLFUserData &user_data,
bke::bake::BakeDataBlockMap *data_block_map,
const bke::bake::BakeStateRef &zone_state) const
{
Array<void *> outputs(simulation_items_.size());
for (const int i : simulation_items_.index_range()) {
outputs[i] = params.get_output_data_ptr(i + 1);
}
copy_simulation_state_to_values(simulation_items_,
zone_state,
*user_data.call_data->self_object(),
*user_data.compute_context,
node_,
data_block_map,
outputs);
for (const int i : simulation_items_.index_range()) {
params.output_set(i + 1);
}
}
void output_simulation_state_move(lf::Params &params,
const GeoNodesLFUserData &user_data,
bke::bake::BakeDataBlockMap *data_block_map,
bke::bake::BakeState zone_state) const
{
Array<void *> outputs(simulation_items_.size());
for (const int i : simulation_items_.index_range()) {
outputs[i] = params.get_output_data_ptr(i + 1);
}
move_simulation_state_to_values(simulation_items_,
std::move(zone_state),
*user_data.call_data->self_object(),
*user_data.compute_context,
node_,
data_block_map,
outputs);
for (const int i : simulation_items_.index_range()) {
params.output_set(i + 1);
}
}
void pass_through(lf::Params &params,
const GeoNodesLFUserData &user_data,
bke::bake::BakeDataBlockMap *data_block_map) const
{
Array<void *> input_values(inputs_.size());
for (const int i : inputs_.index_range()) {
input_values[i] = params.try_get_input_data_ptr_or_request(i);
}
if (input_values.as_span().contains(nullptr)) {
/* Wait for inputs to be computed. */
return;
}
/* Instead of outputting the initial values directly, convert them to a simulation state and
* then back. This ensures that some geometry processing happens on the data consistently (e.g.
* removing anonymous attributes). */
bke::bake::BakeState bake_state = move_values_to_simulation_state(
simulation_items_, input_values, data_block_map);
this->output_simulation_state_move(params, user_data, data_block_map, std::move(bake_state));
}
};
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_output<decl::Float>("Delta Time");
const bNode *node = b.node_or_null();
const bNodeTree *node_tree = b.tree_or_null();
if (ELEM(nullptr, node, node_tree)) {
return;
}
const bNode *output_node = node_tree->node_by_id(node_storage(*node).output_node_id);
if (!output_node) {
return;
}
const auto &output_storage = *static_cast<const NodeGeometrySimulationOutput *>(
output_node->storage);
for (const int i : IndexRange(output_storage.items_num)) {
const NodeSimulationItem &item = output_storage.items[i];
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
if (socket_type == SOCK_GEOMETRY && i > 0) {
b.add_separator();
}
const StringRef name = item.name;
const std::string identifier = SimulationItemsAccessor::socket_identifier_for_item(item);
auto &input_decl = b.add_input(socket_type, name, identifier)
.socket_name_ptr(
&node_tree->id, SimulationItemsAccessor::item_srna, &item, "name");
auto &output_decl = b.add_output(socket_type, name, identifier).align_with_previous();
if (socket_type_supports_fields(socket_type)) {
input_decl.supports_field();
output_decl.dependent_field({input_decl.index()});
}
}
b.add_input<decl::Extend>("", "__extend__");
b.add_output<decl::Extend>("", "__extend__").align_with_previous();
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometrySimulationInput *data = MEM_callocN<NodeGeometrySimulationInput>(__func__);
/* Needs to be initialized for the node to work. */
data->output_node_id = 0;
node->storage = data;
}
static void node_label(const bNodeTree * /*ntree*/,
const bNode * /*node*/,
char *label,
const int label_maxncpy)
{
BLI_strncpy_utf8(label, IFACE_("Simulation"), label_maxncpy);
}
static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link)
{
bNode *output_node = ntree->node_by_id(node_storage(*node).output_node_id);
if (!output_node) {
return true;
}
return socket_items::try_add_item_via_any_extend_socket<SimulationItemsAccessor>(
*ntree, *node, *output_node, *link);
}
static void node_register()
{
static blender::bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeSimulationInput", GEO_NODE_SIMULATION_INPUT);
ntype.ui_name = "Simulation Input";
ntype.ui_description = "Input data for the simulation zone";
ntype.enum_name_legacy = "SIMULATION_INPUT";
ntype.nclass = NODE_CLASS_INTERFACE;
ntype.initfunc = node_init;
ntype.declare = node_declare;
ntype.labelfunc = node_label;
ntype.insert_link = node_insert_link;
ntype.gather_link_search_ops = nullptr;
ntype.no_muting = true;
ntype.draw_buttons_ex = node_layout_ex;
blender::bke::node_type_storage(ntype,
"NodeGeometrySimulationInput",
node_free_standard_storage,
node_copy_standard_storage);
blender::bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace sim_input_node
namespace sim_output_node {
NODE_STORAGE_FUNCS(NodeGeometrySimulationOutput);
class LazyFunctionForSimulationOutputNode final : public LazyFunction {
const bNode &node_;
Span<NodeSimulationItem> simulation_items_;
int skip_input_index_;
/**
* Start index of the simulation state inputs that are used when the simulation is skipped.
* Those inputs are linked directly to the simulation input node. Those inputs only exist
* internally, but not in the UI.
*/
int skip_inputs_offset_;
/**
* Start index of the simulation state inputs that are used when the simulation is actually
* computed. Those correspond to the sockets that are visible in the UI.
*/
int solve_inputs_offset_;
public:
LazyFunctionForSimulationOutputNode(const bNode &node,
GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info)
: node_(node)
{
debug_name_ = "Simulation Output";
const NodeGeometrySimulationOutput &storage = node_storage(node);
simulation_items_ = {storage.items, storage.items_num};
MutableSpan<int> lf_index_by_bsocket = own_lf_graph_info.mapping.lf_index_by_bsocket;
const bNodeSocket &skip_bsocket = node.input_socket(0);
skip_input_index_ = inputs_.append_and_get_index_as(
"Skip", *skip_bsocket.typeinfo->geometry_nodes_cpp_type, lf::ValueUsage::Maybe);
lf_index_by_bsocket[skip_bsocket.index_in_tree()] = skip_input_index_;
skip_inputs_offset_ = inputs_.size();
/* Add the skip inputs that are linked to the simulation input node. */
for (const int i : simulation_items_.index_range()) {
const NodeSimulationItem &item = simulation_items_[i];
const CPPType &type = get_simulation_item_cpp_type(item);
inputs_.append_as(item.name, type, lf::ValueUsage::Maybe);
}
solve_inputs_offset_ = inputs_.size();
/* Add the solve inputs that correspond to the simulation state inputs in the UI. */
for (const int i : simulation_items_.index_range()) {
const NodeSimulationItem &item = simulation_items_[i];
const bNodeSocket &input_bsocket = node.input_socket(i + 1);
const bNodeSocket &output_bsocket = node.output_socket(i);
const CPPType &type = get_simulation_item_cpp_type(item);
lf_index_by_bsocket[input_bsocket.index_in_tree()] = inputs_.append_and_get_index_as(
item.name, type, lf::ValueUsage::Maybe);
lf_index_by_bsocket[output_bsocket.index_in_tree()] = outputs_.append_and_get_index_as(
item.name, type);
}
}
void execute_impl(lf::Params &params, const lf::Context &context) const final
{
GeoNodesLFUserData &user_data = *static_cast<GeoNodesLFUserData *>(context.user_data);
if (!user_data.call_data->self_object()) {
/* The self object is currently required for generating anonymous attribute names. */
this->set_default_outputs(params);
return;
}
if (!user_data.call_data->simulation_params) {
this->set_default_outputs(params);
return;
}
std::optional<FoundNestedNodeID> found_id = find_nested_node_id(user_data, node_.identifier);
if (!found_id) {
this->set_default_outputs(params);
return;
}
if (found_id->is_in_loop || found_id->is_in_closure) {
this->set_default_outputs(params);
return;
}
SimulationZoneBehavior *zone_behavior = user_data.call_data->simulation_params->get(
found_id->id);
if (!zone_behavior) {
this->set_default_outputs(params);
return;
}
sim_output::Behavior &output_behavior = zone_behavior->output;
if (auto *info = std::get_if<sim_output::ReadSingle>(&output_behavior)) {
this->output_cached_state(params, user_data, zone_behavior->data_block_map, info->state);
}
else if (auto *info = std::get_if<sim_output::ReadInterpolated>(&output_behavior)) {
this->output_mixed_cached_state(params,
zone_behavior->data_block_map,
*user_data.call_data->self_object(),
*user_data.compute_context,
info->prev_state,
info->next_state,
info->mix_factor);
}
else if (std::get_if<sim_output::PassThrough>(&output_behavior)) {
this->pass_through(params, user_data, zone_behavior->data_block_map);
}
else if (auto *info = std::get_if<sim_output::StoreNewState>(&output_behavior)) {
this->store_new_state(params, user_data, zone_behavior->data_block_map, *info);
}
else {
BLI_assert_unreachable();
}
}
void set_default_outputs(lf::Params &params) const
{
set_default_remaining_node_outputs(params, node_);
}
void output_cached_state(lf::Params &params,
GeoNodesLFUserData &user_data,
bke::bake::BakeDataBlockMap *data_block_map,
const bke::bake::BakeStateRef &state) const
{
Array<void *> output_values(simulation_items_.size());
for (const int i : simulation_items_.index_range()) {
output_values[i] = params.get_output_data_ptr(i);
}
copy_simulation_state_to_values(simulation_items_,
state,
*user_data.call_data->self_object(),
*user_data.compute_context,
node_,
data_block_map,
output_values);
for (const int i : simulation_items_.index_range()) {
params.output_set(i);
}
}
void output_mixed_cached_state(lf::Params &params,
bke::bake::BakeDataBlockMap *data_block_map,
const Object &self_object,
const ComputeContext &compute_context,
const bke::bake::BakeStateRef &prev_state,
const bke::bake::BakeStateRef &next_state,
const float mix_factor) const
{
Array<void *> output_values(simulation_items_.size());
for (const int i : simulation_items_.index_range()) {
output_values[i] = params.get_output_data_ptr(i);
}
copy_simulation_state_to_values(simulation_items_,
prev_state,
self_object,
compute_context,
node_,
data_block_map,
output_values);
Array<void *> next_values(simulation_items_.size());
LinearAllocator<> allocator;
for (const int i : simulation_items_.index_range()) {
const CPPType &type = *outputs_[i].type;
next_values[i] = allocator.allocate(type);
}
copy_simulation_state_to_values(simulation_items_,
next_state,
self_object,
compute_context,
node_,
data_block_map,
next_values);
for (const int i : simulation_items_.index_range()) {
mix_baked_data_item(eNodeSocketDatatype(simulation_items_[i].socket_type),
output_values[i],
next_values[i],
mix_factor);
}
for (const int i : simulation_items_.index_range()) {
const CPPType &type = *outputs_[i].type;
type.destruct(next_values[i]);
}
for (const int i : simulation_items_.index_range()) {
params.output_set(i);
}
}
void pass_through(lf::Params &params,
GeoNodesLFUserData &user_data,
bke::bake::BakeDataBlockMap *data_block_map) const
{
std::optional<bke::bake::BakeState> bake_state = this->get_bake_state_from_inputs(
params, data_block_map, true);
if (!bake_state) {
/* Wait for inputs to be computed. */
return;
}
Array<void *> output_values(simulation_items_.size());
for (const int i : simulation_items_.index_range()) {
output_values[i] = params.get_output_data_ptr(i);
}
move_simulation_state_to_values(simulation_items_,
std::move(*bake_state),
*user_data.call_data->self_object(),
*user_data.compute_context,
node_,
data_block_map,
output_values);
for (const int i : simulation_items_.index_range()) {
params.output_set(i);
}
}
void store_new_state(lf::Params &params,
GeoNodesLFUserData &user_data,
bke::bake::BakeDataBlockMap *data_block_map,
const sim_output::StoreNewState &info) const
{
const SocketValueVariant *skip_variant =
params.try_get_input_data_ptr_or_request<SocketValueVariant>(skip_input_index_);
if (skip_variant == nullptr) {
/* Wait for skip input to be computed. */
return;
}
const bool skip = skip_variant->get<bool>();
/* Instead of outputting the values directly, convert them to a bake state and then back.
* This ensures that some geometry processing happens on the data consistently (e.g. removing
* anonymous attributes). */
std::optional<bke::bake::BakeState> bake_state = this->get_bake_state_from_inputs(
params, data_block_map, skip);
if (!bake_state) {
/* Wait for inputs to be computed. */
return;
}
this->output_cached_state(params, user_data, data_block_map, *bake_state);
info.store_fn(std::move(*bake_state));
}
std::optional<bke::bake::BakeState> get_bake_state_from_inputs(
lf::Params &params, bke::bake::BakeDataBlockMap *data_block_map, const bool skip) const
{
/* Choose which set of input parameters to use. The others are ignored. */
const int params_offset = skip ? skip_inputs_offset_ : solve_inputs_offset_;
Array<void *> input_values(simulation_items_.size());
for (const int i : simulation_items_.index_range()) {
input_values[i] = params.try_get_input_data_ptr_or_request(i + params_offset);
}
if (input_values.as_span().contains(nullptr)) {
/* Wait for inputs to be computed. */
return std::nullopt;
}
return move_values_to_simulation_state(simulation_items_, input_values, data_block_map);
}
};
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_input<decl::Bool>("Skip").hide_value().description(
"Forward the output of the simulation input node directly to the output node and ignore "
"the nodes in the simulation zone");
const bNodeTree *tree = b.tree_or_null();
const bNode *node = b.node_or_null();
if (node == nullptr) {
return;
}
const NodeGeometrySimulationOutput &storage = node_storage(*node);
for (const int i : IndexRange(storage.items_num)) {
const NodeSimulationItem &item = storage.items[i];
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
if (socket_type == SOCK_GEOMETRY && i > 0) {
b.add_separator();
}
const StringRef name = item.name;
const std::string identifier = SimulationItemsAccessor::socket_identifier_for_item(item);
auto &input_decl = b.add_input(socket_type, name, identifier)
.socket_name_ptr(
&tree->id, SimulationItemsAccessor::item_srna, &item, "name");
auto &output_decl = b.add_output(socket_type, name, identifier).align_with_previous();
if (socket_type_supports_fields(socket_type)) {
input_decl.supports_field();
output_decl.dependent_field({input_decl.index()});
}
}
b.add_input<decl::Extend>("", "__extend__");
b.add_output<decl::Extend>("", "__extend__").align_with_previous();
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometrySimulationOutput *data = MEM_callocN<NodeGeometrySimulationOutput>(__func__);
data->next_identifier = 0;
data->items = MEM_calloc_arrayN<NodeSimulationItem>(1, __func__);
data->items[0].name = BLI_strdup(DATA_("Geometry"));
data->items[0].socket_type = SOCK_GEOMETRY;
data->items[0].identifier = data->next_identifier++;
data->items_num = 1;
node->storage = data;
}
static void node_free_storage(bNode *node)
{
socket_items::destruct_array<SimulationItemsAccessor>(*node);
MEM_freeN(node->storage);
}
static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const bNode *src_node)
{
const NodeGeometrySimulationOutput &src_storage = node_storage(*src_node);
auto *dst_storage = MEM_dupallocN<NodeGeometrySimulationOutput>(__func__, src_storage);
dst_node->storage = dst_storage;
socket_items::copy_array<SimulationItemsAccessor>(*src_node, *dst_node);
}
static void node_operators()
{
socket_items::ops::make_common_operators<SimulationItemsAccessor>();
}
static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link)
{
return socket_items::try_add_item_via_any_extend_socket<SimulationItemsAccessor>(
*ntree, *node, *node, *link);
}
static void node_extra_info(NodeExtraInfoParams &params)
{
BakeDrawContext ctx;
if (!get_bake_draw_context(&params.C, params.node, ctx)) {
return;
}
if (ctx.is_baked) {
NodeExtraInfoRow row;
row.text = get_baked_string(ctx);
params.rows.append(std::move(row));
}
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const bNodeSocket &other_socket = params.other_socket();
if (!SimulationItemsAccessor::supports_socket_type(eNodeSocketDatatype(other_socket.type))) {
return;
}
params.add_item_full_name(IFACE_("Simulation"), [](LinkSearchOpParams &params) {
bNode &input_node = params.add_node("GeometryNodeSimulationInput");
bNode &output_node = params.add_node("GeometryNodeSimulationOutput");
output_node.location[0] = 300;
auto &input_storage = *static_cast<NodeGeometrySimulationInput *>(input_node.storage);
input_storage.output_node_id = output_node.identifier;
socket_items::clear<SimulationItemsAccessor>(output_node);
socket_items::add_item_with_socket_type_and_name<SimulationItemsAccessor>(
output_node, eNodeSocketDatatype(params.socket.type), params.socket.name);
update_node_declaration_and_sockets(params.node_tree, input_node);
update_node_declaration_and_sockets(params.node_tree, output_node);
if (params.socket.in_out == SOCK_IN) {
params.connect_available_socket(output_node, params.socket.name);
}
else {
params.connect_available_socket(input_node, params.socket.name);
}
params.node_tree.ensure_topology_cache();
bke::node_add_link(params.node_tree,
input_node,
input_node.output_socket(1),
output_node,
output_node.input_socket(1));
});
}
static void node_register()
{
static blender::bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeSimulationOutput", GEO_NODE_SIMULATION_OUTPUT);
ntype.ui_name = "Simulation Output";
ntype.ui_description = "Output data from the simulation zone";
ntype.enum_name_legacy = "SIMULATION_OUTPUT";
ntype.nclass = NODE_CLASS_INTERFACE;
ntype.initfunc = node_init;
ntype.declare = node_declare;
ntype.labelfunc = sim_input_node::node_label;
ntype.gather_link_search_ops = node_gather_link_searches;
ntype.insert_link = node_insert_link;
ntype.draw_buttons_ex = node_layout_ex;
ntype.no_muting = true;
ntype.register_operators = node_operators;
ntype.get_extra_info = node_extra_info;
blender::bke::node_type_storage(
ntype, "NodeGeometrySimulationOutput", node_free_storage, node_copy_storage);
blender::bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace sim_output_node
} // namespace blender::nodes::node_geo_simulation_cc
namespace blender::nodes {
std::unique_ptr<LazyFunction> get_simulation_input_lazy_function(
const bNodeTree &node_tree,
const bNode &node,
GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info)
{
BLI_assert(node.type_legacy == GEO_NODE_SIMULATION_INPUT);
return std::make_unique<
node_geo_simulation_cc::sim_input_node::LazyFunctionForSimulationInputNode>(
node_tree, node, own_lf_graph_info);
}
std::unique_ptr<LazyFunction> get_simulation_output_lazy_function(
const bNode &node, GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info)
{
BLI_assert(node.type_legacy == GEO_NODE_SIMULATION_OUTPUT);
return std::make_unique<
node_geo_simulation_cc::sim_output_node::LazyFunctionForSimulationOutputNode>(
node, own_lf_graph_info);
}
void mix_baked_data_item(const eNodeSocketDatatype socket_type,
void *prev,
const void *next,
const float factor)
{
switch (socket_type) {
case SOCK_GEOMETRY: {
GeometrySet &prev_geo = *static_cast<GeometrySet *>(prev);
const GeometrySet &next_geo = *static_cast<const GeometrySet *>(next);
prev_geo = geometry::mix_geometries(std::move(prev_geo), next_geo, factor);
break;
}
case SOCK_FLOAT:
case SOCK_VECTOR:
case SOCK_INT:
case SOCK_BOOLEAN:
case SOCK_ROTATION:
case SOCK_RGBA:
case SOCK_MATRIX: {
const CPPType &type = *bke::socket_type_to_geo_nodes_base_cpp_type(socket_type);
SocketValueVariant prev_value_variant = *static_cast<const SocketValueVariant *>(prev);
SocketValueVariant next_value_variant = *static_cast<const SocketValueVariant *>(next);
if (prev_value_variant.is_context_dependent_field() ||
next_value_variant.is_context_dependent_field())
{
/* Fields are evaluated on geometries and are mixed there. */
break;
}
prev_value_variant.convert_to_single();
next_value_variant.convert_to_single();
void *prev_value = prev_value_variant.get_single_ptr().get();
const void *next_value = next_value_variant.get_single_ptr().get();
bke::attribute_math::convert_to_static_type(type, [&](auto dummy) {
using T = decltype(dummy);
*static_cast<T *>(prev_value) = bke::attribute_math::mix2(
factor, *static_cast<T *>(prev_value), *static_cast<const T *>(next_value));
});
break;
}
default:
break;
}
}
StructRNA *SimulationItemsAccessor::item_srna = &RNA_SimulationStateItem;
int SimulationItemsAccessor::node_type = GEO_NODE_SIMULATION_OUTPUT;
int SimulationItemsAccessor::item_dna_type = dna::sdna_struct_id_get<NodeSimulationItem>();
void SimulationItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item)
{
BLO_write_string(writer, item.name);
}
void SimulationItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item)
{
BLO_read_string(reader, &item.name);
}
} // namespace blender::nodes
blender::Span<NodeSimulationItem> NodeGeometrySimulationOutput::items_span() const
{
return blender::Span<NodeSimulationItem>(items, items_num);
}
blender::MutableSpan<NodeSimulationItem> NodeGeometrySimulationOutput::items_span()
{
return blender::MutableSpan<NodeSimulationItem>(items, items_num);
}