Add support for attributes storage in simulation state #107133
|
@ -256,6 +256,103 @@ static OutputFieldDependency find_group_output_dependencies(
|
|||
return OutputFieldDependency::ForPartiallyDependentField(std::move(linked_input_indices));
|
||||
}
|
||||
|
||||
/** Result of syncing two field states. */
|
||||
enum class eFieldStateSyncResult : char {
|
||||
|
||||
/* Nothing changed. */
|
||||
NONE = 0,
|
||||
/* State A has been modified. */
|
||||
CHANGED_A = (1 << 0),
|
||||
/* State B has been modified. */
|
||||
CHANGED_B = (1 << 1),
|
||||
};
|
||||
ENUM_OPERATORS(eFieldStateSyncResult, eFieldStateSyncResult::CHANGED_B)
|
||||
|
||||
/**
|
||||
Hans Goudey
commented
Blender's doxygen syntax uses Blender's doxygen syntax uses `\return` rather than `@return`
|
||||
* Compare both field states and select the most compatible.
|
||||
* Afterwards both field states will be the same.
|
||||
LukasTonne marked this conversation as resolved
Jacques Lucke
commented
Feels like it would be simpler to just change the values and then compare them to a copy afterwards to detect what has changed. Feels like it would be simpler to just change the values and then compare them to a copy afterwards to detect what has changed.
Lukas Tönne
commented
You're right, combining states first and then comparing is a lot shorter. You're right, combining states first and then comparing is a lot shorter.
|
||||
* \return eFieldStateSyncResult flags indicating which field states have changed.
|
||||
*/
|
||||
LukasTonne marked this conversation as resolved
Hans Goudey
commented
Rather than using Rather than using `int`, you should be able to use the `ENUM_OPERATORS` macro to define the necessary operations for the enum type
|
||||
static eFieldStateSyncResult sync_field_states(SocketFieldState &a, SocketFieldState &b)
|
||||
{
|
||||
const bool requires_single = a.requires_single || b.requires_single;
|
||||
const bool is_single = a.is_single && b.is_single;
|
||||
Jacques Lucke
commented
Use functional style cast for enums: https://wiki.blender.org/wiki/Style_Guide/C_Cpp#C.2B.2B_Type_Cast Use functional style cast for enums: `FieldStateSyncResult(0)`. Might be even better to simply add a `None` item to the enum.
https://wiki.blender.org/wiki/Style_Guide/C_Cpp#C.2B.2B_Type_Cast
|
||||
|
||||
eFieldStateSyncResult res = eFieldStateSyncResult::NONE;
|
||||
if (a.requires_single != requires_single || a.is_single != is_single) {
|
||||
res |= eFieldStateSyncResult::CHANGED_A;
|
||||
}
|
||||
if (b.requires_single != requires_single || b.is_single != is_single) {
|
||||
res |= eFieldStateSyncResult::CHANGED_B;
|
||||
}
|
||||
|
||||
a.requires_single = requires_single;
|
||||
b.requires_single = requires_single;
|
||||
a.is_single = is_single;
|
||||
b.is_single = is_single;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare field states of simulation nodes sockets and select the most compatible.
|
||||
* Afterwards all field states will be the same.
|
||||
* \return eFieldStateSyncResult flags indicating which field states have changed.
|
||||
*/
|
||||
static eFieldStateSyncResult simulation_nodes_field_state_sync(
|
||||
const bNode &input_node,
|
||||
const bNode &output_node,
|
||||
const MutableSpan<SocketFieldState> field_state_by_socket_id)
|
||||
{
|
||||
eFieldStateSyncResult res = eFieldStateSyncResult::NONE;
|
||||
for (const int i : output_node.output_sockets().index_range()) {
|
||||
/* First input node output is Delta Time which does not appear in the output node outputs. */
|
||||
const bNodeSocket &input_socket = input_node.output_socket(i + 1);
|
||||
const bNodeSocket &output_socket = output_node.output_socket(i);
|
||||
SocketFieldState &input_state = field_state_by_socket_id[input_socket.index_in_tree()];
|
||||
SocketFieldState &output_state = field_state_by_socket_id[output_socket.index_in_tree()];
|
||||
res |= sync_field_states(input_state, output_state);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static bool propagate_special_data_requirements(
|
||||
const bNodeTree &tree,
|
||||
const bNode &node,
|
||||
const MutableSpan<SocketFieldState> field_state_by_socket_id)
|
||||
{
|
||||
tree.ensure_topology_cache();
|
||||
|
||||
bool need_update = false;
|
||||
|
||||
/* Sync field state between simulation nodes and schedule another pass if necessary. */
|
||||
if (node.type == GEO_NODE_SIMULATION_INPUT) {
|
||||
const NodeGeometrySimulationInput &data = *static_cast<const NodeGeometrySimulationInput *>(
|
||||
node.storage);
|
||||
Jacques Lucke
commented
`const`, same below
|
||||
if (const bNode *output_node = tree.node_by_id(data.output_node_id)) {
|
||||
const eFieldStateSyncResult sync_result = simulation_nodes_field_state_sync(
|
||||
node, *output_node, field_state_by_socket_id);
|
||||
if (bool(sync_result & eFieldStateSyncResult::CHANGED_B)) {
|
||||
LukasTonne marked this conversation as resolved
Outdated
Hans Goudey
commented
Functional cast for Though I often see implicit conversions used for cases like this, which seems fine IMO: Functional cast for `bool`: `(bool)...` -> `bool(...)`
Though I often see implicit conversions used for cases like this, which seems fine IMO:
`if (sync_result & eFieldStateSyncResult::CHANGED_B) {`
Lukas Tönne
commented
Implicit cast to bool does not work with Implicit cast to bool does not work with `enum class` unfortunately (suggested by Jacques above). Functional cast should be fine though.
|
||||
need_update = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (node.type == GEO_NODE_SIMULATION_OUTPUT) {
|
||||
for (const bNode *input_node : tree.nodes_by_type("GeometryNodeSimulationInput")) {
|
||||
LukasTonne marked this conversation as resolved
Jacques Lucke
commented
Can use Can use `input_socket(index)` instead of `input_sockets()[index]`
|
||||
const NodeGeometrySimulationInput &data = *static_cast<const NodeGeometrySimulationInput *>(
|
||||
input_node->storage);
|
||||
if (node.identifier == data.output_node_id) {
|
||||
const eFieldStateSyncResult sync_result = simulation_nodes_field_state_sync(
|
||||
*input_node, node, field_state_by_socket_id);
|
||||
if (bool(sync_result & eFieldStateSyncResult::CHANGED_A)) {
|
||||
need_update = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return need_update;
|
||||
}
|
||||
|
||||
static void propagate_data_requirements_from_right_to_left(
|
||||
const bNodeTree &tree,
|
||||
const Span<const FieldInferencingInterface *> interface_by_node,
|
||||
|
@ -263,70 +360,85 @@ static void propagate_data_requirements_from_right_to_left(
|
|||
{
|
||||
const Span<const bNode *> toposort_result = tree.toposort_right_to_left();
|
||||
|
||||
Jacques Lucke
commented
`[...] caused by simulation input/output nodes.`
|
||||
for (const bNode *node : toposort_result) {
|
||||
const FieldInferencingInterface &inferencing_interface = *interface_by_node[node->index()];
|
||||
while (true) {
|
||||
/* Node updates may require sevaral passes due to cyclic dependencies caused by simulation
|
||||
* input/output nodes. */
|
||||
bool need_update = false;
|
||||
|
||||
for (const bNodeSocket *output_socket : node->output_sockets()) {
|
||||
SocketFieldState &state = field_state_by_socket_id[output_socket->index_in_tree()];
|
||||
for (const bNode *node : toposort_result) {
|
||||
const FieldInferencingInterface &inferencing_interface = *interface_by_node[node->index()];
|
||||
|
||||
const OutputFieldDependency &field_dependency =
|
||||
inferencing_interface.outputs[output_socket->index()];
|
||||
for (const bNodeSocket *output_socket : node->output_sockets()) {
|
||||
SocketFieldState &state = field_state_by_socket_id[output_socket->index_in_tree()];
|
||||
|
||||
if (field_dependency.field_type() == OutputSocketFieldType::FieldSource) {
|
||||
continue;
|
||||
}
|
||||
if (field_dependency.field_type() == OutputSocketFieldType::None) {
|
||||
state.requires_single = true;
|
||||
state.is_always_single = true;
|
||||
continue;
|
||||
}
|
||||
const OutputFieldDependency &field_dependency =
|
||||
inferencing_interface.outputs[output_socket->index()];
|
||||
|
||||
/* The output is required to be a single value when it is connected to any input that does
|
||||
* not support fields. */
|
||||
for (const bNodeSocket *target_socket : output_socket->directly_linked_sockets()) {
|
||||
if (target_socket->is_available()) {
|
||||
state.requires_single |=
|
||||
field_state_by_socket_id[target_socket->index_in_tree()].requires_single;
|
||||
if (field_dependency.field_type() == OutputSocketFieldType::FieldSource) {
|
||||
continue;
|
||||
}
|
||||
if (field_dependency.field_type() == OutputSocketFieldType::None) {
|
||||
state.requires_single = true;
|
||||
state.is_always_single = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (state.requires_single) {
|
||||
bool any_input_is_field_implicitly = false;
|
||||
const Vector<const bNodeSocket *> connected_inputs = gather_input_socket_dependencies(
|
||||
field_dependency, *node);
|
||||
for (const bNodeSocket *input_socket : connected_inputs) {
|
||||
if (!input_socket->is_available()) {
|
||||
continue;
|
||||
/* The output is required to be a single value when it is connected to any input that does
|
||||
* not support fields. */
|
||||
for (const bNodeSocket *target_socket : output_socket->directly_linked_sockets()) {
|
||||
if (target_socket->is_available()) {
|
||||
state.requires_single |=
|
||||
field_state_by_socket_id[target_socket->index_in_tree()].requires_single;
|
||||
}
|
||||
if (inferencing_interface.inputs[input_socket->index()] ==
|
||||
InputSocketFieldType::Implicit) {
|
||||
if (!input_socket->is_logically_linked()) {
|
||||
any_input_is_field_implicitly = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (state.requires_single) {
|
||||
bool any_input_is_field_implicitly = false;
|
||||
const Vector<const bNodeSocket *> connected_inputs = gather_input_socket_dependencies(
|
||||
field_dependency, *node);
|
||||
for (const bNodeSocket *input_socket : connected_inputs) {
|
||||
if (!input_socket->is_available()) {
|
||||
continue;
|
||||
}
|
||||
if (inferencing_interface.inputs[input_socket->index()] ==
|
||||
InputSocketFieldType::Implicit) {
|
||||
if (!input_socket->is_logically_linked()) {
|
||||
any_input_is_field_implicitly = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (any_input_is_field_implicitly) {
|
||||
/* This output isn't a single value actually. */
|
||||
state.requires_single = false;
|
||||
}
|
||||
else {
|
||||
/* If the output is required to be a single value, the connected inputs in the same
|
||||
* node must not be fields as well. */
|
||||
for (const bNodeSocket *input_socket : connected_inputs) {
|
||||
field_state_by_socket_id[input_socket->index_in_tree()].requires_single = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (any_input_is_field_implicitly) {
|
||||
/* This output isn't a single value actually. */
|
||||
state.requires_single = false;
|
||||
}
|
||||
else {
|
||||
/* If the output is required to be a single value, the connected inputs in the same node
|
||||
* must not be fields as well. */
|
||||
for (const bNodeSocket *input_socket : connected_inputs) {
|
||||
field_state_by_socket_id[input_socket->index_in_tree()].requires_single = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Some inputs do not require fields independent of what the outputs are connected to. */
|
||||
for (const bNodeSocket *input_socket : node->input_sockets()) {
|
||||
SocketFieldState &state = field_state_by_socket_id[input_socket->index_in_tree()];
|
||||
if (inferencing_interface.inputs[input_socket->index()] == InputSocketFieldType::None) {
|
||||
state.requires_single = true;
|
||||
state.is_always_single = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Find reverse dependencies and resolve conflicts, which may require another pass. */
|
||||
if (propagate_special_data_requirements(tree, *node, field_state_by_socket_id)) {
|
||||
need_update = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Some inputs do not require fields independent of what the outputs are connected to. */
|
||||
for (const bNodeSocket *input_socket : node->input_sockets()) {
|
||||
SocketFieldState &state = field_state_by_socket_id[input_socket->index_in_tree()];
|
||||
if (inferencing_interface.inputs[input_socket->index()] == InputSocketFieldType::None) {
|
||||
state.requires_single = true;
|
||||
state.is_always_single = true;
|
||||
}
|
||||
if (!need_update) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -382,68 +494,82 @@ static void propagate_field_status_from_left_to_right(
|
|||
{
|
||||
const Span<const bNode *> toposort_result = tree.toposort_left_to_right();
|
||||
|
||||
for (const bNode *node : toposort_result) {
|
||||
if (node->type == NODE_GROUP_INPUT) {
|
||||
continue;
|
||||
}
|
||||
while (true) {
|
||||
/* Node updates may require sevaral passes due to cyclic dependencies. */
|
||||
bool need_update = false;
|
||||
|
||||
const FieldInferencingInterface &inferencing_interface = *interface_by_node[node->index()];
|
||||
|
||||
/* Update field state of input sockets, also taking into account linked origin sockets. */
|
||||
for (const bNodeSocket *input_socket : node->input_sockets()) {
|
||||
SocketFieldState &state = field_state_by_socket_id[input_socket->index_in_tree()];
|
||||
if (state.is_always_single) {
|
||||
state.is_single = true;
|
||||
for (const bNode *node : toposort_result) {
|
||||
if (node->type == NODE_GROUP_INPUT) {
|
||||
continue;
|
||||
}
|
||||
state.is_single = true;
|
||||
if (!input_socket->is_directly_linked()) {
|
||||
if (inferencing_interface.inputs[input_socket->index()] ==
|
||||
InputSocketFieldType::Implicit) {
|
||||
state.is_single = false;
|
||||
|
||||
const FieldInferencingInterface &inferencing_interface = *interface_by_node[node->index()];
|
||||
|
||||
/* Update field state of input sockets, also taking into account linked origin sockets. */
|
||||
for (const bNodeSocket *input_socket : node->input_sockets()) {
|
||||
SocketFieldState &state = field_state_by_socket_id[input_socket->index_in_tree()];
|
||||
if (state.is_always_single) {
|
||||
state.is_single = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const bNodeSocket *origin_socket : input_socket->directly_linked_sockets()) {
|
||||
if (!field_state_by_socket_id[origin_socket->index_in_tree()].is_single) {
|
||||
state.is_single = true;
|
||||
if (!input_socket->is_directly_linked()) {
|
||||
if (inferencing_interface.inputs[input_socket->index()] ==
|
||||
InputSocketFieldType::Implicit) {
|
||||
state.is_single = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Update field state of output sockets, also taking into account input sockets. */
|
||||
for (const bNodeSocket *output_socket : node->output_sockets()) {
|
||||
SocketFieldState &state = field_state_by_socket_id[output_socket->index_in_tree()];
|
||||
const OutputFieldDependency &field_dependency =
|
||||
inferencing_interface.outputs[output_socket->index()];
|
||||
|
||||
switch (field_dependency.field_type()) {
|
||||
case OutputSocketFieldType::None: {
|
||||
state.is_single = true;
|
||||
break;
|
||||
}
|
||||
case OutputSocketFieldType::FieldSource: {
|
||||
state.is_single = false;
|
||||
state.is_field_source = true;
|
||||
break;
|
||||
}
|
||||
case OutputSocketFieldType::PartiallyDependent:
|
||||
case OutputSocketFieldType::DependentField: {
|
||||
for (const bNodeSocket *input_socket :
|
||||
gather_input_socket_dependencies(field_dependency, *node)) {
|
||||
if (!input_socket->is_available()) {
|
||||
continue;
|
||||
}
|
||||
if (!field_state_by_socket_id[input_socket->index_in_tree()].is_single) {
|
||||
else {
|
||||
for (const bNodeSocket *origin_socket : input_socket->directly_linked_sockets()) {
|
||||
if (!field_state_by_socket_id[origin_socket->index_in_tree()].is_single) {
|
||||
state.is_single = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update field state of output sockets, also taking into account input sockets. */
|
||||
for (const bNodeSocket *output_socket : node->output_sockets()) {
|
||||
SocketFieldState &state = field_state_by_socket_id[output_socket->index_in_tree()];
|
||||
const OutputFieldDependency &field_dependency =
|
||||
inferencing_interface.outputs[output_socket->index()];
|
||||
|
||||
switch (field_dependency.field_type()) {
|
||||
case OutputSocketFieldType::None: {
|
||||
state.is_single = true;
|
||||
break;
|
||||
}
|
||||
case OutputSocketFieldType::FieldSource: {
|
||||
state.is_single = false;
|
||||
state.is_field_source = true;
|
||||
break;
|
||||
}
|
||||
case OutputSocketFieldType::PartiallyDependent:
|
||||
case OutputSocketFieldType::DependentField: {
|
||||
for (const bNodeSocket *input_socket :
|
||||
gather_input_socket_dependencies(field_dependency, *node)) {
|
||||
if (!input_socket->is_available()) {
|
||||
continue;
|
||||
}
|
||||
if (!field_state_by_socket_id[input_socket->index_in_tree()].is_single) {
|
||||
state.is_single = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Find reverse dependencies and resolve conflicts, which may require another pass. */
|
||||
if (propagate_special_data_requirements(tree, *node, field_state_by_socket_id)) {
|
||||
need_update = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!need_update) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -134,8 +134,14 @@ class FieldAtIndexInput final : public bke::GeometryFieldInput {
|
|||
};
|
||||
|
||||
std::string socket_identifier_for_simulation_item(const NodeSimulationItem &item);
|
||||
|
||||
void socket_declarations_for_simulation_items(Span<NodeSimulationItem> items,
|
||||
NodeDeclaration &r_declaration);
|
||||
const CPPType &get_simulation_item_cpp_type(eNodeSocketDatatype socket_type);
|
||||
const CPPType &get_simulation_item_cpp_type(const NodeSimulationItem &item);
|
||||
void copy_simulation_state_to_output_param(lf::Params ¶ms,
|
||||
int index,
|
||||
eNodeSocketDatatype socket_type,
|
||||
const bke::sim::SimulationStateItem &state_item);
|
||||
|
||||
} // namespace blender::nodes
|
||||
|
|
|
@ -13,6 +13,26 @@
|
|||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
/** Copy a node input parameter to the matching output parameter. */
|
||||
static void copy_input_to_output_param(lf::Params ¶ms,
|
||||
const int index,
|
||||
const eNodeSocketDatatype socket_type)
|
||||
{
|
||||
Hans Goudey
commented
Might be worth adding a comment explaining the Might be worth adding a comment explaining the `+ 1` here, so the reader doesn't have to look elsewhere to figure it out.
|
||||
const CPPType &cpptype = get_simulation_item_cpp_type(socket_type);
|
||||
|
||||
void *src = params.try_get_input_data_ptr_or_request(index);
|
||||
if (src != nullptr) {
|
||||
/* First output parameter is "Delta Time", state item parameters start at index 1. */
|
||||
void *dst = params.get_output_data_ptr(index + 1);
|
||||
cpptype.move_construct(src, dst);
|
||||
params.output_set(index + 1);
|
||||
}
|
||||
}
|
||||
LukasTonne marked this conversation as resolved
Hans Goudey
commented
Maybe I'm being stupid, but I don't get what's meant by "next" here. Maybe I'm being stupid, but I don't get what's meant by "next" here.
Lukas Tönne
commented
Was just an ad-hoc name: it's copying from the simulation state into the output parameter for the next iteration. I'll add some comments. Was just an ad-hoc name: it's copying from the simulation state into the output parameter for the next iteration. I'll add some comments.
Lukas Tönne
commented
Renamed the functions and added comments for clarity. Also the Renamed the functions and added comments for clarity. Also the `copy_simulation_state_to_output_param` function is now shared by input and output node, they both need to do this: input node when preparing the next iteration, output node when outputting the sim result.
|
||||
|
||||
} // namespace blender::nodes
|
||||
|
||||
namespace blender::nodes::node_geo_simulation_input_cc {
|
||||
|
||||
NODE_STORAGE_FUNCS(NodeGeometrySimulationInput);
|
||||
|
@ -65,10 +85,8 @@ class LazyFunctionForSimulationInputNode final : public LazyFunction {
|
|||
if (params.output_was_set(i + 1)) {
|
||||
continue;
|
||||
}
|
||||
LukasTonne marked this conversation as resolved
Outdated
Hans Goudey
commented
`int index` -> `const int index`
|
||||
GeometrySet *geometry = params.try_get_input_data_ptr_or_request<GeometrySet>(i);
|
||||
if (geometry != nullptr) {
|
||||
params.set_output(i + 1, std::move(*geometry));
|
||||
}
|
||||
copy_input_to_output_param(
|
||||
Hans Goudey
commented
use use `eNodeSocketDatatype` instead of `short`
|
||||
params, i, eNodeSocketDatatype(simulation_items_[i].socket_type));
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -77,10 +95,11 @@ class LazyFunctionForSimulationInputNode final : public LazyFunction {
|
|||
params.set_output(i + 1, GeometrySet());
|
||||
continue;
|
||||
}
|
||||
const bke::sim::SimulationStateItem *item = prev_zone_state->items[i].get();
|
||||
if (auto *geometry_item = dynamic_cast<const bke::sim::GeometrySimulationStateItem *>(
|
||||
item)) {
|
||||
params.set_output(i + 1, geometry_item->geometry());
|
||||
const bke::sim::SimulationStateItem *state_item = prev_zone_state->items[i].get();
|
||||
if (state_item != nullptr) {
|
||||
/* First output parameter is "Delta Time", state item parameters start at index 1. */
|
||||
copy_simulation_state_to_output_param(
|
||||
params, i + 1, eNodeSocketDatatype(simulation_items_[i].socket_type), *state_item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,31 +21,62 @@ std::string socket_identifier_for_simulation_item(const NodeSimulationItem &item
|
|||
return "Item_" + std::to_string(item.identifier);
|
||||
}
|
||||
|
||||
static std::unique_ptr<SocketDeclaration> socket_declaration_for_simulation_item(
|
||||
const NodeSimulationItem &item, const eNodeSocketInOut in_out, const int index)
|
||||
{
|
||||
BLI_assert(NOD_geometry_simulation_output_item_socket_type_supported(
|
||||
eNodeSocketDatatype(item.socket_type)));
|
||||
|
||||
std::unique_ptr<SocketDeclaration> decl;
|
||||
switch (eNodeSocketDatatype(item.socket_type)) {
|
||||
case SOCK_FLOAT:
|
||||
decl = std::make_unique<decl::Float>();
|
||||
decl->input_field_type = InputSocketFieldType::IsSupported;
|
||||
decl->output_field_dependency = OutputFieldDependency::ForPartiallyDependentField({index});
|
||||
break;
|
||||
case SOCK_VECTOR:
|
||||
decl = std::make_unique<decl::Vector>();
|
||||
decl->input_field_type = InputSocketFieldType::IsSupported;
|
||||
decl->output_field_dependency = OutputFieldDependency::ForPartiallyDependentField({index});
|
||||
break;
|
||||
case SOCK_RGBA:
|
||||
decl = std::make_unique<decl::Color>();
|
||||
decl->input_field_type = InputSocketFieldType::IsSupported;
|
||||
decl->output_field_dependency = OutputFieldDependency::ForPartiallyDependentField({index});
|
||||
break;
|
||||
case SOCK_BOOLEAN:
|
||||
decl = std::make_unique<decl::Bool>();
|
||||
decl->input_field_type = InputSocketFieldType::IsSupported;
|
||||
decl->output_field_dependency = OutputFieldDependency::ForPartiallyDependentField({index});
|
||||
break;
|
||||
case SOCK_INT:
|
||||
decl = std::make_unique<decl::Int>();
|
||||
decl->input_field_type = InputSocketFieldType::IsSupported;
|
||||
decl->output_field_dependency = OutputFieldDependency::ForPartiallyDependentField({index});
|
||||
break;
|
||||
case SOCK_STRING:
|
||||
decl = std::make_unique<decl::String>();
|
||||
break;
|
||||
case SOCK_GEOMETRY:
|
||||
decl = std::make_unique<decl::Geometry>();
|
||||
break;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
decl->name = item.name ? item.name : "";
|
||||
decl->identifier = socket_identifier_for_simulation_item(item);
|
||||
decl->in_out = in_out;
|
||||
return decl;
|
||||
}
|
||||
|
||||
void socket_declarations_for_simulation_items(const Span<NodeSimulationItem> items,
|
||||
NodeDeclaration &r_declaration)
|
||||
{
|
||||
for (const NodeSimulationItem &item : items) {
|
||||
switch (eNodeSocketDatatype(item.socket_type)) {
|
||||
case SOCK_GEOMETRY: {
|
||||
{
|
||||
std::unique_ptr<decl::Geometry> decl = std::make_unique<decl::Geometry>();
|
||||
decl->name = item.name ? item.name : "";
|
||||
decl->identifier = socket_identifier_for_simulation_item(item);
|
||||
decl->in_out = SOCK_IN;
|
||||
r_declaration.inputs.append(std::move(decl));
|
||||
}
|
||||
{
|
||||
std::unique_ptr<decl::Geometry> decl = std::make_unique<decl::Geometry>();
|
||||
decl->name = item.name ? item.name : "";
|
||||
decl->identifier = socket_identifier_for_simulation_item(item);
|
||||
decl->in_out = SOCK_OUT;
|
||||
r_declaration.outputs.append(std::move(decl));
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
for (const int i : items.index_range()) {
|
||||
const NodeSimulationItem &item = items[i];
|
||||
r_declaration.inputs.append(socket_declaration_for_simulation_item(item, SOCK_IN, i));
|
||||
r_declaration.outputs.append(socket_declaration_for_simulation_item(item, SOCK_OUT, i));
|
||||
}
|
||||
r_declaration.inputs.append(decl::create_extend_declaration(SOCK_IN));
|
||||
r_declaration.outputs.append(decl::create_extend_declaration(SOCK_OUT));
|
||||
|
@ -73,14 +104,56 @@ static bool simulation_items_unique_name_check(void *arg, const char *name)
|
|||
return false;
|
||||
}
|
||||
|
||||
const CPPType &get_simulation_item_cpp_type(const eNodeSocketDatatype socket_type)
|
||||
{
|
||||
const char *socket_idname = nodeStaticSocketType(socket_type, 0);
|
||||
const bNodeSocketType *typeinfo = nodeSocketTypeFind(socket_idname);
|
||||
BLI_assert(typeinfo);
|
||||
LukasTonne marked this conversation as resolved
Outdated
Jacques Lucke
commented
functional style cast, few more cases below functional style cast, few more cases below
|
||||
BLI_assert(typeinfo->geometry_nodes_cpp_type);
|
||||
return *typeinfo->geometry_nodes_cpp_type;
|
||||
}
|
||||
|
||||
const CPPType &get_simulation_item_cpp_type(const NodeSimulationItem &item)
|
||||
{
|
||||
switch (item.socket_type) {
|
||||
case SOCK_GEOMETRY:
|
||||
return CPPType::get<GeometrySet>();
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
return CPPType::get<GeometrySet>();
|
||||
return get_simulation_item_cpp_type(eNodeSocketDatatype(item.socket_type));
|
||||
}
|
||||
|
||||
/** Create a simulation state item from parameter input data. */
|
||||
static std::unique_ptr<bke::sim::SimulationStateItem> make_simulation_state_item(
|
||||
const eNodeSocketDatatype socket_type, void *input_data)
|
||||
{
|
||||
if (socket_type == SOCK_GEOMETRY) {
|
||||
GeometrySet *input_geometry = static_cast<GeometrySet *>(input_data);
|
||||
input_geometry->ensure_owns_direct_data();
|
||||
return std::make_unique<bke::sim::GeometrySimulationStateItem>(*input_geometry);
|
||||
}
|
||||
else {
|
||||
/* TODO: Implement for non-geometry state items. */
|
||||
GeometrySet geometry;
|
||||
return std::make_unique<bke::sim::GeometrySimulationStateItem>(geometry);
|
||||
}
|
||||
}
|
||||
|
||||
/** Copy the current simulation state to the node output parameter. */
|
||||
void copy_simulation_state_to_output_param(lf::Params ¶ms,
|
||||
const int index,
|
||||
Hans Goudey
commented
I'd hope this could use the socket type's existing I'd hope this could use the socket type's existing `geometry_nodes_cpp_type` callback.
Lukas Tönne
commented
Yeah that works too. I just wanted to keep it simple, considering we have to do a bunch of type switches elsewhere anyway. Have to get the type Yeah that works too. I just wanted to keep it simple, considering we have to do a bunch of type switches elsewhere anyway. Have to get the type `idname` from the `eNodeSocketDatatype`, then get the `bNodeSocketType` from the `idname`, then get the CPPType.
|
||||
const eNodeSocketDatatype socket_type,
|
||||
const bke::sim::SimulationStateItem &state_item)
|
||||
{
|
||||
const CPPType &cpptype = get_simulation_item_cpp_type(socket_type);
|
||||
|
||||
if (socket_type == SOCK_GEOMETRY) {
|
||||
const bke::sim::GeometrySimulationStateItem &geo_state_item =
|
||||
static_cast<const bke::sim::GeometrySimulationStateItem &>(state_item);
|
||||
params.set_output(index, geo_state_item.geometry());
|
||||
}
|
||||
else {
|
||||
/* TODO: Implement for non-geometry state items. */
|
||||
if (const void *src = cpptype.default_value()) {
|
||||
void *dst = params.get_output_data_ptr(index);
|
||||
cpptype.copy_construct(src, dst);
|
||||
params.output_set(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -168,14 +241,16 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction {
|
|||
|
||||
bool all_available = true;
|
||||
for (const int i : simulation_items_.index_range()) {
|
||||
GeometrySet *input_geometry = params.try_get_input_data_ptr_or_request<GeometrySet>(i);
|
||||
if (input_geometry == nullptr) {
|
||||
const NodeSimulationItem &item = simulation_items_[i];
|
||||
|
||||
void *input_data = params.try_get_input_data_ptr_or_request(i);
|
||||
if (input_data == nullptr) {
|
||||
all_available = false;
|
||||
continue;
|
||||
}
|
||||
input_geometry->ensure_owns_direct_data();
|
||||
new_zone_state.items[i] = std::make_unique<bke::sim::GeometrySimulationStateItem>(
|
||||
std::move(*input_geometry));
|
||||
|
||||
new_zone_state.items[i] = make_simulation_state_item(eNodeSocketDatatype(item.socket_type),
|
||||
input_data);
|
||||
}
|
||||
|
||||
if (all_available) {
|
||||
|
@ -186,17 +261,17 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction {
|
|||
void output_cached_state(lf::Params ¶ms, const bke::sim::SimulationZoneState &state) const
|
||||
{
|
||||
for (const int i : simulation_items_.index_range()) {
|
||||
const NodeSimulationItem &item = simulation_items_[i];
|
||||
|
||||
if (i >= state.items.size()) {
|
||||
continue;
|
||||
}
|
||||
const bke::sim::SimulationStateItem *item = state.items[i].get();
|
||||
if (item == nullptr) {
|
||||
const bke::sim::SimulationStateItem *state_item = state.items[i].get();
|
||||
if (state_item == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (auto *geometry_item = dynamic_cast<const bke::sim::GeometrySimulationStateItem *>(
|
||||
item)) {
|
||||
params.set_output(i, geometry_item->geometry());
|
||||
}
|
||||
copy_simulation_state_to_output_param(
|
||||
params, i, eNodeSocketDatatype(item.socket_type), *state_item);
|
||||
}
|
||||
params.set_default_remaining_outputs();
|
||||
}
|
||||
|
@ -369,7 +444,14 @@ blender::IndexRange NodeGeometrySimulationOutput::items_range() const
|
|||
bool NOD_geometry_simulation_output_item_socket_type_supported(
|
||||
const eNodeSocketDatatype socket_type)
|
||||
{
|
||||
return ELEM(socket_type, SOCK_GEOMETRY);
|
||||
return ELEM(socket_type,
|
||||
SOCK_FLOAT,
|
||||
SOCK_VECTOR,
|
||||
SOCK_RGBA,
|
||||
SOCK_BOOLEAN,
|
||||
SOCK_INT,
|
||||
SOCK_STRING,
|
||||
SOCK_GEOMETRY);
|
||||
}
|
||||
|
||||
bNode *NOD_geometry_simulation_output_find_node_by_item(bNodeTree *ntree,
|
||||
|
@ -450,6 +532,11 @@ NodeSimulationItem *NOD_geometry_simulation_output_insert_item(NodeGeometrySimul
|
|||
const char *name,
|
||||
int index)
|
||||
{
|
||||
if (!NOD_geometry_simulation_output_item_socket_type_supported(
|
||||
eNodeSocketDatatype(socket_type))) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NodeSimulationItem *old_items = sim->items;
|
||||
sim->items = MEM_cnew_array<NodeSimulationItem>(sim->items_num + 1, __func__);
|
||||
for (const int i : blender::IndexRange(index)) {
|
||||
|
@ -474,9 +561,6 @@ NodeSimulationItem *NOD_geometry_simulation_output_insert_item(NodeGeometrySimul
|
|||
NodeSimulationItem *NOD_geometry_simulation_output_add_item_from_socket(
|
||||
NodeGeometrySimulationOutput *sim, const bNode * /*from_node*/, const bNodeSocket *from_sock)
|
||||
{
|
||||
if (from_sock->type != SOCK_GEOMETRY) {
|
||||
return nullptr;
|
||||
}
|
||||
return NOD_geometry_simulation_output_insert_item(
|
||||
sim, from_sock->type, from_sock->name, sim->items_num);
|
||||
}
|
||||
|
|
enum class FieldStateSyncResult