Blender V5.0
node_geo_grid_advect.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2025 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "NOD_rna_define.hh"
7
8#include "RNA_enum_types.hh"
9
10#include "node_geometry_util.hh"
11
13#include "UI_resources.hh"
14
15#include "BKE_volume_grid.hh"
16#include "BKE_volume_openvdb.hh"
17
18#ifdef WITH_OPENVDB
19# include "openvdb/tools/VolumeAdvect.h"
20#endif
21
23
32
33enum class LimiterType : int8_t {
34 None = 0,
35 Clamp = 1,
36 Revert = 2,
37};
38
41 "SEMI",
42 0,
43 N_("Semi-Lagrangian"),
44 N_("1st order semi-Lagrangian integration. Fast but least accurate, suitable for simple "
45 "advection")},
47 "MID",
48 0,
49 N_("Midpoint"),
50 N_("2nd order midpoint integration. Good balance between speed and accuracy for most cases")},
52 "RK3",
53 0,
54 N_("Runge-Kutta 3"),
55 N_("3rd order Runge-Kutta integration. Higher accuracy at moderate computational cost")},
57 "RK4",
58 0,
59 N_("Runge-Kutta 4"),
60 N_("4th order Runge-Kutta integration. Highest accuracy single-step method but slower")},
62 "MAC",
63 0,
64 N_("MacCormack"),
65 N_("MacCormack scheme with implicit diffusion control. Reduces numerical dissipation while "
66 "maintaining stability")},
68 "BFECC",
69 0,
70 N_("BFECC"),
71 N_("Back and Forth Error Compensation and Correction. Advanced scheme that minimizes "
72 "dissipation and diffusion")},
73 {0, nullptr, 0, nullptr, nullptr},
74};
75
78 "NONE",
79 0,
80 N_("None"),
81 N_("No limiting applied. Fastest but may produce artifacts in high-order schemes")},
83 "CLAMP",
84 0,
85 N_("Clamp"),
86 N_("Clamp values to the range of the original neighborhood. Prevents overshooting and "
87 "undershooting")},
89 "REVERT",
90 0,
91 N_("Revert"),
92 N_("Revert to 1st order integration when clamping would be applied. More conservative than "
93 "clamping")},
94 {0, nullptr, 0, nullptr, nullptr},
95};
96
98{
99 b.use_custom_socket_order();
100 b.allow_any_socket_order();
101 b.add_default_layout();
102
103 const bNode *node = b.node_or_null();
104 if (!node) {
105 return;
106 }
107
108 const eNodeSocketDatatype data_type = eNodeSocketDatatype(node->custom1);
109 b.add_input(data_type, "Grid")
110 .hide_value()
111 .structure_type(StructureType::Grid)
112 .is_default_link_socket();
113 b.add_output(data_type, "Grid").structure_type(StructureType::Grid).align_with_previous();
114 b.add_input<decl::Vector>("Velocity").hide_value().structure_type(StructureType::Grid);
115 b.add_input<decl::Float>("Time Step")
116 .subtype(PROP_TIME_ABSOLUTE)
117 .default_value(1.0f)
118 .description("Time step for advection in seconds");
119 b.add_input<decl::Menu>("Integration Scheme")
120 .static_items(integration_scheme_items)
122 .optional_label()
123 .description("Numerical integration method for advection");
124 b.add_input<decl::Menu>("Limiter")
125 .static_items(limiter_type_items)
127 .optional_label()
128 .description("Limiting strategy to prevent numerical artifacts");
129}
130
131static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
132{
133 layout->prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
134}
135
136static std::optional<eNodeSocketDatatype> node_type_for_socket_type(const bNodeSocket &socket)
137{
138 switch (socket.type) {
139 case SOCK_FLOAT:
140 return SOCK_FLOAT;
141 case SOCK_INT:
142 return SOCK_INT;
143 case SOCK_VECTOR:
144 case SOCK_RGBA:
145 return SOCK_VECTOR;
146 default:
147 return std::nullopt;
148 }
149}
150
152{
153 const std::optional<eNodeSocketDatatype> data_type = node_type_for_socket_type(
154 params.other_socket());
155 if (!data_type) {
156 return;
157 }
158 if (params.in_out() == SOCK_IN) {
159 if (params.node_tree().typeinfo->validate_link(eNodeSocketDatatype(params.other_socket().type),
161 {
162 params.add_item(IFACE_("Velocity"), [](LinkSearchOpParams &params) {
163 bNode &node = params.add_node("GeometryNodeGridAdvect");
164 params.update_and_connect_available_socket(node, "Velocity");
165 });
166 }
167 if (params.node_tree().typeinfo->validate_link(eNodeSocketDatatype(params.other_socket().type),
168 SOCK_FLOAT))
169 {
170 params.add_item(IFACE_("Time Step"), [](LinkSearchOpParams &params) {
171 bNode &node = params.add_node("GeometryNodeGridAdvect");
172 params.update_and_connect_available_socket(node, "Time Step");
173 });
174 }
175 }
176 params.add_item(IFACE_("Grid"), [data_type](LinkSearchOpParams &params) {
177 bNode &node = params.add_node("GeometryNodeGridAdvect");
178 node.custom1 = *data_type;
179 params.update_and_connect_available_socket(node, "Grid");
180 });
181}
182
183#ifdef WITH_OPENVDB
184static openvdb::tools::Scheme::SemiLagrangian to_openvdb_scheme(const IntegrationScheme scheme)
185{
186 switch (scheme) {
188 return openvdb::tools::Scheme::SEMI;
190 return openvdb::tools::Scheme::MID;
192 return openvdb::tools::Scheme::RK3;
194 return openvdb::tools::Scheme::RK4;
196 return openvdb::tools::Scheme::MAC;
198 return openvdb::tools::Scheme::BFECC;
199 }
200 return openvdb::tools::Scheme::SEMI;
201}
202
203static openvdb::tools::Scheme::Limiter to_openvdb_limiter(const LimiterType limiter)
204{
205 switch (limiter) {
207 return openvdb::tools::Scheme::NO_LIMITER;
209 return openvdb::tools::Scheme::CLAMP;
211 return openvdb::tools::Scheme::REVERT;
212 }
213 return openvdb::tools::Scheme::NO_LIMITER;
214}
215
216template<typename GridType, typename SamplerType = openvdb::tools::Sampler<1>>
217static typename GridType::Ptr advect_grid(const GridType &grid,
218 const openvdb::Vec3SGrid &velocity_grid,
219 const float time_step,
220 const IntegrationScheme scheme,
221 const LimiterType limiter)
222{
223 openvdb::tools::VolumeAdvection<openvdb::Vec3SGrid, false> advection(velocity_grid);
224
225 advection.setIntegrator(to_openvdb_scheme(scheme));
226 advection.setLimiter(to_openvdb_limiter(limiter));
227 return advection.template advect<GridType, SamplerType>(grid, time_step);
228}
229#endif
230
232{
233#ifdef WITH_OPENVDB
234 bke::GVolumeGrid grid = params.extract_input<bke::GVolumeGrid>("Grid");
235 if (!grid) {
236 params.set_default_remaining_outputs();
237 return;
238 }
239
240 const bke::VolumeGrid<float3> velocity_grid = params.extract_input<bke::VolumeGrid<float3>>(
241 "Velocity");
242 if (!velocity_grid) {
243 params.set_output("Grid", std::move(grid));
244 return;
245 }
246
247 const float time_step = params.extract_input<float>("Time Step");
248 const IntegrationScheme scheme = params.extract_input<IntegrationScheme>("Integration Scheme");
249 const LimiterType limiter = params.extract_input<LimiterType>("Limiter");
250
251 bke::VolumeTreeAccessToken tree_token;
252 bke::VolumeTreeAccessToken velocity_token;
253 const openvdb::GridBase &grid_base = grid->grid(tree_token);
254 const openvdb::Vec3SGrid &velocity_vdb_grid = velocity_grid.grid(velocity_token);
255
256 /* OpenVDB's advection requires uniform voxel scale on the grid being advected
257 but not for the velocity grid being sampled */
258 if (!grid_base.hasUniformVoxels()) {
259 params.error_message_add(
261 TIP_("The input grid must have a uniform voxel scale to be advected."));
262 params.set_output("Grid", std::move(grid));
263 return;
264 }
265
266 const VolumeGridType grid_type = grid->grid_type();
267
268 BKE_volume_grid_type_to_static_type(grid_type, [&](auto grid_type_tag) {
269 using GridType = typename decltype(grid_type_tag)::type;
270 if constexpr (std::is_same_v<GridType, openvdb::FloatGrid> ||
271 std::is_same_v<GridType, openvdb::Int32Grid> ||
272 std::is_same_v<GridType, openvdb::Vec3fGrid>)
273 {
274 typename GridType::Ptr result = advect_grid(
275 static_cast<const GridType &>(grid->grid(tree_token)),
276 velocity_vdb_grid,
277 time_step,
278 scheme,
279 limiter);
280 params.set_output("Grid", bke::GVolumeGrid(std::move(result)));
281 }
282 else {
283 params.error_message_add(NodeWarningType::Error, "Unsupported grid type for advection");
284 params.set_default_remaining_outputs();
285 }
286 });
287#else
289#endif
290}
291
292static void node_init(bNodeTree * /*tree*/, bNode *node)
293{
294 node->custom1 = SOCK_FLOAT;
295}
296
297/* Only float, int, and vector grids are supported for advection */
299 PointerRNA * /*ptr*/,
300 PropertyRNA * /*prop*/,
301 bool *r_free)
302{
303 *r_free = true;
305 [](const EnumPropertyItem &item) -> bool {
307 return ELEM(type, SOCK_FLOAT, SOCK_INT, SOCK_VECTOR);
308 });
309}
310
311static void node_rna(StructRNA *srna)
312{
314 "data_type",
315 "Data Type",
316 "Node socket data type",
321}
322
324 const bNode &node,
325 const bNodeSocket &output_socket)
326{
327 return node.input_by_identifier(output_socket.identifier);
328}
329
330static void node_register()
331{
332 static blender::bke::bNodeType ntype;
333 geo_node_type_base(&ntype, "GeometryNodeGridAdvect");
334 ntype.ui_name = "Advect Grid";
335 ntype.ui_description =
336 "Move grid values through a velocity field using numerical integration. Supports multiple "
337 "integration schemes for different accuracy and performance trade-offs";
339 ntype.declare = node_declare;
341 ntype.initfunc = node_init;
346 node_rna(ntype.rna_ext.srna);
347}
349
350} // namespace blender::nodes::node_geo_grid_advect_cc
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:461
VolumeGridType
#define ELEM(...)
#define TIP_(msgid)
#define IFACE_(msgid)
@ SOCK_IN
eNodeSocketDatatype
@ SOCK_INT
@ SOCK_VECTOR
@ SOCK_FLOAT
@ SOCK_RGBA
static double Clamp(const double x, const double min, const double max)
Definition IK_Math.h:30
#define NOD_REGISTER_NODE(REGISTER_FUNC)
#define NOD_inline_enum_accessors(member)
@ PROP_TIME_ABSOLUTE
Definition RNA_types.hh:254
#define UI_ITEM_NONE
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
static const bNodeSocket * node_internally_linked_input(const bNodeTree &, const bNode &node, const bNodeSocket &output_socket)
static const EnumPropertyItem * advect_grid_socket_type_filter(bContext *, PointerRNA *, PropertyRNA *, bool *r_free)
static const EnumPropertyItem integration_scheme_items[]
static void node_declare(NodeDeclarationBuilder &b)
static void node_geo_exec(GeoNodeExecParams params)
static std::optional< eNodeSocketDatatype > node_type_for_socket_type(const bNodeSocket &socket)
static void node_init(bNodeTree *, bNode *node)
static const EnumPropertyItem limiter_type_items[]
static void node_gather_link_search_ops(GatherLinkSearchOpParams &params)
static void node_layout(uiLayout *layout, bContext *, PointerRNA *ptr)
PropertyRNA * RNA_def_node_enum(StructRNA *srna, const char *identifier, const char *ui_name, const char *ui_description, const EnumPropertyItem *static_items, const EnumRNAAccessors accessors, std::optional< int > default_value, const EnumPropertyItemFunc item_func, const bool allow_animation)
void node_geo_exec_with_missing_openvdb(GeoNodeExecParams &params)
const EnumPropertyItem * enum_items_filter(const EnumPropertyItem *original_item_array, FunctionRef< bool(const EnumPropertyItem &item)> fn)
void geo_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
const EnumPropertyItem rna_enum_node_socket_data_type_items[]
StructRNA * srna
char identifier[64]
int16_t custom1
Defines a node type.
Definition BKE_node.hh:238
NodeInternallyLinkedInputFunction internally_linked_input
Definition BKE_node.hh:384
std::string ui_description
Definition BKE_node.hh:244
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:289
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:354
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:259
NodeGatherSocketLinkOperationsFunction gather_link_search_ops
Definition BKE_node.hh:378
NodeDeclareFunction declare
Definition BKE_node.hh:362
void prop(PointerRNA *ptr, PropertyRNA *prop, int index, int value, eUI_Item_Flag flag, std::optional< blender::StringRef > name_opt, int icon, std::optional< blender::StringRef > placeholder=std::nullopt)
#define N_(msgid)
PointerRNA * ptr
Definition wm_files.cc:4238