Blender V5.0
node_geo_input_shortest_edge_paths.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include <queue>
6
7#include "BLI_array_utils.hh"
9#include "BLI_task.hh"
10
11#include "BKE_mesh.hh"
12#include "BKE_mesh_mapping.hh"
13
14#include "node_geometry_util.hh"
15
17
19{
20 b.add_input<decl::Bool>("End Vertex").default_value(false).hide_value().supports_field();
21 b.add_input<decl::Float>("Edge Cost").default_value(1.0f).hide_value().supports_field();
22 b.add_output<decl::Int>("Next Vertex Index").field_source().reference_pass_all();
23 b.add_output<decl::Float>("Total Cost").field_source().reference_pass_all();
24}
25
26using VertPriority = std::pair<float, int>;
27
28static void shortest_paths(const Mesh &mesh,
29 const GroupedSpan<int> vert_to_edge,
30 const IndexMask end_selection,
31 const VArray<float> &input_cost,
32 MutableSpan<int> r_next_index,
33 MutableSpan<float> r_cost)
34{
35 const Span<int2> edges = mesh.edges();
36 Array<bool> visited(mesh.verts_num, false);
37
38 std::priority_queue<VertPriority, std::vector<VertPriority>, std::greater<>> queue;
39
40 end_selection.foreach_index([&](const int start_vert_i) {
41 r_cost[start_vert_i] = 0.0f;
42 queue.emplace(0.0f, start_vert_i);
43 });
44
45 /* Though it uses more memory, calculating the adjacent vertex
46 * across each edge beforehand is noticeably faster. */
47 Array<int> other_vertex(vert_to_edge.data.size());
48 threading::parallel_for(vert_to_edge.index_range(), 2048, [&](const IndexRange range) {
49 for (const int vert_i : range) {
50 for (const int edge_i : vert_to_edge.offsets[vert_i]) {
51 other_vertex[edge_i] = bke::mesh::edge_other_vert(edges[vert_to_edge.data[edge_i]],
52 vert_i);
53 }
54 }
55 });
56
57 while (!queue.empty()) {
58 const float cost_i = queue.top().first;
59 const int vert_i = queue.top().second;
60 queue.pop();
61 if (visited[vert_i]) {
62 continue;
63 }
64 visited[vert_i] = true;
65 for (const int index : vert_to_edge.offsets[vert_i]) {
66 const int edge_i = vert_to_edge.data[index];
67 const int neighbor_vert_i = other_vertex[index];
68 if (visited[neighbor_vert_i]) {
69 continue;
70 }
71 const float edge_cost = std::max(0.0f, input_cost[edge_i]);
72 const float new_neighbor_cost = cost_i + edge_cost;
73 if (new_neighbor_cost < r_cost[neighbor_vert_i]) {
74 r_cost[neighbor_vert_i] = new_neighbor_cost;
75 r_next_index[neighbor_vert_i] = vert_i;
76 queue.emplace(new_neighbor_cost, neighbor_vert_i);
77 }
78 }
79 }
80}
81
83 private:
84 Field<bool> end_selection_;
85 Field<float> cost_;
86
87 public:
89 : bke::MeshFieldInput(CPPType::get<int>(), "Shortest Edge Paths Next Vertex Field"),
90 end_selection_(end_selection),
91 cost_(cost)
92 {
94 }
95
97 const AttrDomain domain,
98 const IndexMask & /*mask*/) const final
99 {
100 const bke::MeshFieldContext edge_context{mesh, AttrDomain::Edge};
101 fn::FieldEvaluator edge_evaluator{edge_context, mesh.edges_num};
102 edge_evaluator.add(cost_);
103 edge_evaluator.evaluate();
104 const VArray<float> input_cost = edge_evaluator.get_evaluated<float>(0);
105
106 const bke::MeshFieldContext point_context{mesh, AttrDomain::Point};
107 fn::FieldEvaluator point_evaluator{point_context, mesh.verts_num};
108 point_evaluator.add(end_selection_);
109 point_evaluator.evaluate();
110 const IndexMask end_selection = point_evaluator.get_evaluated_as_mask(0);
111
112 Array<int> next_index(mesh.verts_num, -1);
113 Array<float> cost(mesh.verts_num, FLT_MAX);
114
115 if (end_selection.is_empty()) {
117 return mesh.attributes().adapt_domain<int>(
118 VArray<int>::from_container(std::move(next_index)), AttrDomain::Point, domain);
119 }
120
121 const Span<int2> edges = mesh.edges();
122 Array<int> vert_to_edge_offset_data;
123 Array<int> vert_to_edge_indices;
125 edges, mesh.verts_num, vert_to_edge_offset_data, vert_to_edge_indices);
126 shortest_paths(mesh, vert_to_edge, end_selection, input_cost, next_index, cost);
127
128 threading::parallel_for(next_index.index_range(), 1024, [&](const IndexRange range) {
129 for (const int i : range) {
130 if (next_index[i] == -1) {
131 next_index[i] = i;
132 }
133 }
134 });
135 return mesh.attributes().adapt_domain<int>(
136 VArray<int>::from_container(std::move(next_index)), AttrDomain::Point, domain);
137 }
138
139 void for_each_field_input_recursive(FunctionRef<void(const FieldInput &)> fn) const override
140 {
141 end_selection_.node().for_each_field_input_recursive(fn);
142 cost_.node().for_each_field_input_recursive(fn);
143 }
144
145 uint64_t hash() const override
146 {
147 return get_default_hash(end_selection_, cost_);
148 }
149
150 bool is_equal_to(const fn::FieldNode &other) const override
151 {
152 if (const ShortestEdgePathsNextVertFieldInput *other_field =
153 dynamic_cast<const ShortestEdgePathsNextVertFieldInput *>(&other))
154 {
155 return other_field->end_selection_ == end_selection_ && other_field->cost_ == cost_;
156 }
157 return false;
158 }
159
160 std::optional<AttrDomain> preferred_domain(const Mesh & /*mesh*/) const override
161 {
162 return AttrDomain::Point;
163 }
164};
165
167 private:
168 Field<bool> end_selection_;
169 Field<float> cost_;
170
171 public:
173 : bke::MeshFieldInput(CPPType::get<float>(), "Shortest Edge Paths Cost Field"),
174 end_selection_(end_selection),
175 cost_(cost)
176 {
178 }
179
181 const AttrDomain domain,
182 const IndexMask & /*mask*/) const final
183 {
184 const bke::MeshFieldContext edge_context{mesh, AttrDomain::Edge};
185 fn::FieldEvaluator edge_evaluator{edge_context, mesh.edges_num};
186 edge_evaluator.add(cost_);
187 edge_evaluator.evaluate();
188 const VArray<float> input_cost = edge_evaluator.get_evaluated<float>(0);
189
190 const bke::MeshFieldContext point_context{mesh, AttrDomain::Point};
191 fn::FieldEvaluator point_evaluator{point_context, mesh.verts_num};
192 point_evaluator.add(end_selection_);
193 point_evaluator.evaluate();
194 const IndexMask end_selection = point_evaluator.get_evaluated_as_mask(0);
195
196 if (end_selection.is_empty()) {
197 return mesh.attributes().adapt_domain<float>(
198 VArray<float>::from_single(0.0f, mesh.verts_num), AttrDomain::Point, domain);
199 }
200
201 Array<int> next_index(mesh.verts_num, -1);
202 Array<float> cost(mesh.verts_num, FLT_MAX);
203
204 const Span<int2> edges = mesh.edges();
205 Array<int> vert_to_edge_offset_data;
206 Array<int> vert_to_edge_indices;
208 edges, mesh.verts_num, vert_to_edge_offset_data, vert_to_edge_indices);
209 shortest_paths(mesh, vert_to_edge, end_selection, input_cost, next_index, cost);
210
211 threading::parallel_for(cost.index_range(), 1024, [&](const IndexRange range) {
212 for (const int i : range) {
213 if (cost[i] == FLT_MAX) {
214 cost[i] = 0;
215 }
216 }
217 });
218 return mesh.attributes().adapt_domain<float>(
219 VArray<float>::from_container(std::move(cost)), AttrDomain::Point, domain);
220 }
221
222 void for_each_field_input_recursive(FunctionRef<void(const FieldInput &)> fn) const override
223 {
224 end_selection_.node().for_each_field_input_recursive(fn);
225 cost_.node().for_each_field_input_recursive(fn);
226 }
227
228 uint64_t hash() const override
229 {
230 return get_default_hash(end_selection_, cost_);
231 }
232
233 bool is_equal_to(const fn::FieldNode &other) const override
234 {
235 if (const ShortestEdgePathsCostFieldInput *other_field =
236 dynamic_cast<const ShortestEdgePathsCostFieldInput *>(&other))
237 {
238 return other_field->end_selection_ == end_selection_ && other_field->cost_ == cost_;
239 }
240 return false;
241 }
242
243 std::optional<AttrDomain> preferred_domain(const Mesh & /*mesh*/) const override
244 {
245 return AttrDomain::Point;
246 }
247};
248
250{
251 Field<bool> end_selection = params.extract_input<Field<bool>>("End Vertex");
252 Field<float> cost = params.extract_input<Field<float>>("Edge Cost");
253
254 Field<int> next_vert_field{
255 std::make_shared<ShortestEdgePathsNextVertFieldInput>(end_selection, cost)};
256 Field<float> cost_field{std::make_shared<ShortestEdgePathsCostFieldInput>(end_selection, cost)};
257 params.set_output("Next Vertex Index", std::move(next_vert_field));
258 params.set_output("Total Cost", std::move(cost_field));
259}
260
261static void node_register()
262{
263 static blender::bke::bNodeType ntype;
264
266 &ntype, "GeometryNodeInputShortestEdgePaths", GEO_NODE_INPUT_SHORTEST_EDGE_PATHS);
267 ntype.ui_name = "Shortest Edge Paths";
268 ntype.ui_description =
269 "Find the shortest paths along mesh edges to selected end vertices, with customizable cost "
270 "per edge";
271 ntype.enum_name_legacy = "SHORTEST_EDGE_PATHS";
272 ntype.nclass = NODE_CLASS_INPUT;
273 ntype.declare = node_declare;
276}
277NOD_REGISTER_NODE(node_register)
278
279} // namespace blender::nodes::node_geo_input_shortest_edge_paths_cc
#define NODE_CLASS_INPUT
Definition BKE_node.hh:447
#define GEO_NODE_INPUT_SHORTEST_EDGE_PATHS
#define final(a, b, c)
Definition BLI_hash.h:19
#define NOD_REGISTER_NODE(REGISTER_FUNC)
unsigned long long int uint64_t
static VArray from_single(T value, const int64_t size)
static VArray from_container(ContainerT container)
IndexRange index_range() const
Definition BLI_array.hh:360
FieldInput(const CPPType &type, std::string debug_name="")
Definition field.cc:677
int add(GField field, GVArray *varray_ptr)
Definition field.cc:751
IndexMask get_evaluated_as_mask(int field_index)
Definition field.cc:804
const GVArray & get_evaluated(const int field_index) const
Definition FN_field.hh:448
void foreach_index(Fn &&fn) const
GVArray get_varray_for_context(const Mesh &mesh, const AttrDomain domain, const IndexMask &) const final
GVArray get_varray_for_context(const Mesh &mesh, const AttrDomain domain, const IndexMask &) const final
nullptr float
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void fill_index_range(MutableSpan< T > span, const T start=0)
GroupedSpan< int > build_vert_to_edge_map(Span< int2 > edges, int verts_num, Array< int > &r_offsets, Array< int > &r_indices)
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
static void shortest_paths(const Mesh &mesh, const GroupedSpan< int > vert_to_edge, const IndexMask end_selection, const VArray< float > &input_cost, MutableSpan< int > r_next_index, MutableSpan< float > r_cost)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:93
uint64_t get_default_hash(const T &v, const Args &...args)
Definition BLI_hash.hh:233
void geo_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
#define FLT_MAX
Definition stdcycles.h:14
int verts_num
Defines a node type.
Definition BKE_node.hh:238
std::string ui_description
Definition BKE_node.hh:244
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:354
const char * enum_name_legacy
Definition BKE_node.hh:247
NodeDeclareFunction declare
Definition BKE_node.hh:362