Blender V4.3
node_geo_raycast.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 "DNA_mesh_types.h"
6
8#include "BKE_bvhutils.hh"
9#include "BKE_mesh_sample.hh"
10
11#include "NOD_rna_define.hh"
13
14#include "UI_interface.hh"
15#include "UI_resources.hh"
16
17#include "RNA_enum_types.hh"
18
19#include "node_geometry_util.hh"
20
22
24
26
28{
29 const bNode *node = b.node_or_null();
30
31 b.add_input<decl::Geometry>("Target Geometry")
32 .only_realized_data()
33 .supported_type(GeometryComponent::Type::Mesh);
34 if (node != nullptr) {
35 const eCustomDataType data_type = eCustomDataType(node_storage(*node).data_type);
36 /* TODO: Field interfacing depends on the offset of the next declarations! */
37 b.add_input(data_type, "Attribute").hide_value().field_on_all();
38 }
39
40 b.add_input<decl::Vector>("Source Position").implicit_field(implicit_field_inputs::position);
41 b.add_input<decl::Vector>("Ray Direction").default_value({0.0f, 0.0f, -1.0f}).supports_field();
42 b.add_input<decl::Float>("Ray Length")
43 .default_value(100.0f)
44 .min(0.0f)
46 .supports_field();
47
48 b.add_output<decl::Bool>("Is Hit").dependent_field({2, 3, 4});
49 b.add_output<decl::Vector>("Hit Position").dependent_field({2, 3, 4});
50 b.add_output<decl::Vector>("Hit Normal").dependent_field({2, 3, 4});
51 b.add_output<decl::Float>("Hit Distance").dependent_field({2, 3, 4});
52
53 if (node != nullptr) {
54 const eCustomDataType data_type = eCustomDataType(node_storage(*node).data_type);
55 b.add_output(data_type, "Attribute").dependent_field({2, 3, 4});
56 }
57}
58
59static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
60{
61 uiItemR(layout, ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
62 uiItemR(layout, ptr, "mapping", UI_ITEM_NONE, "", ICON_NONE);
63}
64
65static void node_init(bNodeTree * /*tree*/, bNode *node)
66{
67 NodeGeometryRaycast *data = MEM_cnew<NodeGeometryRaycast>(__func__);
68 data->mapping = GEO_NODE_RAYCAST_INTERPOLATED;
69 data->data_type = CD_PROP_FLOAT;
70 node->storage = data;
71}
72
74{
75 const NodeDeclaration &declaration = *params.node_type().static_declaration;
78
79 const std::optional<eCustomDataType> type = bke::socket_type_to_custom_data_type(
80 eNodeSocketDatatype(params.other_socket().type));
81 if (type && *type != CD_PROP_STRING) {
82 /* The input and output sockets have the same name. */
83 params.add_item(IFACE_("Attribute"), [type](LinkSearchOpParams &params) {
84 bNode &node = params.add_node("GeometryNodeRaycast");
85 node_storage(node).data_type = *type;
86 params.update_and_connect_available_socket(node, "Attribute");
87 });
88 }
89}
90
91static void raycast_to_mesh(const IndexMask &mask,
92 const Mesh &mesh,
93 const VArray<float3> &ray_origins,
94 const VArray<float3> &ray_directions,
95 const VArray<float> &ray_lengths,
96 const MutableSpan<bool> r_hit,
97 const MutableSpan<int> r_hit_indices,
98 const MutableSpan<float3> r_hit_positions,
99 const MutableSpan<float3> r_hit_normals,
100 const MutableSpan<float> r_hit_distances)
101{
102 BVHTreeFromMesh tree_data;
104 BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&tree_data); });
105
106 if (tree_data.tree == nullptr) {
107 return;
108 }
109 /* We shouldn't be rebuilding the BVH tree when calling this function in parallel. */
110 BLI_assert(tree_data.cached);
111
112 mask.foreach_index([&](const int i) {
113 const float ray_length = ray_lengths[i];
114 const float3 ray_origin = ray_origins[i];
115 const float3 ray_direction = ray_directions[i];
116
117 BVHTreeRayHit hit;
118 hit.index = -1;
119 hit.dist = ray_length;
120 if (BLI_bvhtree_ray_cast(tree_data.tree,
121 ray_origin,
122 ray_direction,
123 0.0f,
124 &hit,
125 tree_data.raycast_callback,
126 &tree_data) != -1)
127 {
128 if (!r_hit.is_empty()) {
129 r_hit[i] = hit.index >= 0;
130 }
131 if (!r_hit_indices.is_empty()) {
132 /* The caller must be able to handle invalid indices anyway, so don't clamp this value. */
133 r_hit_indices[i] = hit.index;
134 }
135 if (!r_hit_positions.is_empty()) {
136 r_hit_positions[i] = hit.co;
137 }
138 if (!r_hit_normals.is_empty()) {
139 r_hit_normals[i] = hit.no;
140 }
141 if (!r_hit_distances.is_empty()) {
142 r_hit_distances[i] = hit.dist;
143 }
144 }
145 else {
146 if (!r_hit.is_empty()) {
147 r_hit[i] = false;
148 }
149 if (!r_hit_indices.is_empty()) {
150 r_hit_indices[i] = -1;
151 }
152 if (!r_hit_positions.is_empty()) {
153 r_hit_positions[i] = float3(0.0f, 0.0f, 0.0f);
154 }
155 if (!r_hit_normals.is_empty()) {
156 r_hit_normals[i] = float3(0.0f, 0.0f, 0.0f);
157 }
158 if (!r_hit_distances.is_empty()) {
159 r_hit_distances[i] = ray_length;
160 }
161 }
162 });
163}
164
166 private:
167 GeometrySet target_;
168
169 public:
170 RaycastFunction(GeometrySet target) : target_(std::move(target))
171 {
172 target_.ensure_owns_direct_data();
173 static const mf::Signature signature = []() {
174 mf::Signature signature;
175 mf::SignatureBuilder builder{"Raycast", signature};
176 builder.single_input<float3>("Source Position");
177 builder.single_input<float3>("Ray Direction");
178 builder.single_input<float>("Ray Length");
179 builder.single_output<bool>("Is Hit", mf::ParamFlag::SupportsUnusedOutput);
180 builder.single_output<float3>("Hit Position", mf::ParamFlag::SupportsUnusedOutput);
181 builder.single_output<float3>("Hit Normal", mf::ParamFlag::SupportsUnusedOutput);
182 builder.single_output<float>("Distance", mf::ParamFlag::SupportsUnusedOutput);
183 builder.single_output<int>("Triangle Index", mf::ParamFlag::SupportsUnusedOutput);
184 return signature;
185 }();
186 this->set_signature(&signature);
187 }
188
189 void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
190 {
191 BLI_assert(target_.has_mesh());
192 const Mesh &mesh = *target_.get_mesh();
193
194 raycast_to_mesh(mask,
195 mesh,
196 params.readonly_single_input<float3>(0, "Source Position"),
197 params.readonly_single_input<float3>(1, "Ray Direction"),
198 params.readonly_single_input<float>(2, "Ray Length"),
199 params.uninitialized_single_output_if_required<bool>(3, "Is Hit"),
200 params.uninitialized_single_output_if_required<int>(7, "Triangle Index"),
201 params.uninitialized_single_output_if_required<float3>(4, "Hit Position"),
202 params.uninitialized_single_output_if_required<float3>(5, "Hit Normal"),
203 params.uninitialized_single_output_if_required<float>(6, "Distance"));
204 }
205};
206
208{
209 GeometrySet target = params.extract_input<GeometrySet>("Target Geometry");
210 const NodeGeometryRaycast &storage = node_storage(params.node());
211 const GeometryNodeRaycastMapMode mapping = GeometryNodeRaycastMapMode(storage.mapping);
212
213 if (target.is_empty()) {
214 params.set_default_remaining_outputs();
215 return;
216 }
217
218 if (!target.has_mesh()) {
219 params.set_default_remaining_outputs();
220 return;
221 }
222
223 if (target.get_mesh()->faces_num == 0) {
224 params.error_message_add(NodeWarningType::Error, TIP_("The target mesh must have faces"));
225 params.set_default_remaining_outputs();
226 return;
227 }
228
229 static auto normalize_fn = mf::build::SI1_SO<float3, float3>(
230 "Normalize",
231 [](const float3 &v) { return math::normalize(v); },
232 mf::build::exec_presets::AllSpanOrSingle());
233 auto direction_op = FieldOperation::Create(
234 normalize_fn, {params.extract_input<Field<float3>>("Ray Direction")});
235
236 auto op = FieldOperation::Create(std::make_unique<RaycastFunction>(target),
237 {params.extract_input<Field<float3>>("Source Position"),
238 Field<float3>(direction_op),
239 params.extract_input<Field<float>>("Ray Length")});
240
241 Field<float3> hit_position(op, 1);
242 params.set_output("Is Hit", Field<bool>(op, 0));
243 params.set_output("Hit Position", hit_position);
244 params.set_output("Hit Normal", Field<float3>(op, 2));
245 params.set_output("Hit Distance", Field<float>(op, 3));
246
247 if (!params.output_is_required("Attribute")) {
248 return;
249 }
250
251 GField field = params.extract_input<GField>("Attribute");
252 Field<int> triangle_index(op, 4);
253 Field<float3> bary_weights;
254 switch (mapping) {
256 bary_weights = Field<float3>(FieldOperation::Create(
257 std::make_shared<bke::mesh_surface_sample::BaryWeightFromPositionFn>(target),
258 {hit_position, triangle_index}));
259 break;
261 bary_weights = Field<float3>(FieldOperation::Create(
262 std::make_shared<bke::mesh_surface_sample::CornerBaryWeightFromPositionFn>(target),
263 {hit_position, triangle_index}));
264 break;
265 }
266 auto sample_op = FieldOperation::Create(
267 std::make_shared<bke::mesh_surface_sample::BaryWeightSampleFn>(std::move(target),
268 std::move(field)),
269 {triangle_index, bary_weights});
270 params.set_output("Attribute", GField(sample_op));
271}
272
273static void node_rna(StructRNA *srna)
274{
275 static EnumPropertyItem mapping_items[] = {
277 "INTERPOLATED",
278 0,
279 "Interpolated",
280 "Interpolate the attribute from the corners of the hit face"},
282 "NEAREST",
283 0,
284 "Nearest",
285 "Use the attribute value of the closest mesh element"},
286 {0, nullptr, 0, nullptr, nullptr},
287 };
288
290 "mapping",
291 "Mapping",
292 "Mapping from the target geometry to hit points",
293 mapping_items,
297 "data_type",
298 "Data Type",
299 "Type of data stored in attribute",
303 enums::attribute_type_type_with_socket_fn);
304}
305
306static void node_register()
307{
308 static blender::bke::bNodeType ntype;
309
310 geo_node_type_base(&ntype, GEO_NODE_RAYCAST, "Raycast", NODE_CLASS_GEOMETRY);
311 bke::node_type_size_preset(&ntype, bke::eNodeSizePreset::Middle);
312 ntype.initfunc = node_init;
314 &ntype, "NodeGeometryRaycast", node_free_standard_storage, node_copy_standard_storage);
315 ntype.declare = node_declare;
320
321 node_rna(ntype.rna_ext.srna);
322}
323NOD_REGISTER_NODE(node_register)
324
325} // namespace blender::nodes::node_geo_raycast_cc
void free_bvhtree_from_mesh(BVHTreeFromMesh *data)
Definition bvhutils.cc:1160
BVHTree * BKE_bvhtree_from_mesh_get(BVHTreeFromMesh *data, const Mesh *mesh, BVHCacheType bvh_cache_type, int tree_type)
Definition bvhutils.cc:899
@ BVHTREE_FROM_CORNER_TRIS
#define NODE_STORAGE_FUNCS(StorageT)
Definition BKE_node.hh:1799
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:418
#define BLI_assert(a)
Definition BLI_assert.h:50
int BLI_bvhtree_ray_cast(const BVHTree *tree, const float co[3], const float dir[3], float radius, BVHTreeRayHit *hit, BVHTree_RayCastCallback callback, void *userdata)
#define BLI_SCOPED_DEFER(function_to_defer)
#define TIP_(msgid)
#define IFACE_(msgid)
@ CD_PROP_FLOAT
@ CD_PROP_STRING
eNodeSocketDatatype
GeometryNodeRaycastMapMode
@ GEO_NODE_RAYCAST_NEAREST
@ GEO_NODE_RAYCAST_INTERPOLATED
#define NOD_REGISTER_NODE(REGISTER_FUNC)
#define NOD_storage_enum_accessors(member)
@ PROP_DISTANCE
Definition RNA_types.hh:159
#define UI_ITEM_NONE
void uiItemR(uiLayout *layout, PointerRNA *ptr, const char *propname, eUI_Item_Flag flag, const char *name, int icon)
ATTR_WARN_UNUSED_RESULT const BMVert * v
constexpr bool is_empty() const
Definition BLI_span.hh:510
Vector< SocketDeclaration * > inputs
Vector< SocketDeclaration * > outputs
void call(const IndexMask &mask, mf::Params params, mf::Context) const override
local_group_size(16, 16) .push_constant(Type b
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void node_type_storage(bNodeType *ntype, const char *storagename, void(*freefunc)(bNode *node), void(*copyfunc)(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node))
Definition node.cc:4632
void node_register_type(bNodeType *ntype)
Definition node.cc:1708
std::optional< eCustomDataType > socket_type_to_custom_data_type(eNodeSocketDatatype type)
Definition node.cc:4379
MatBase< T, NumCol, NumRow > normalize(const MatBase< T, NumCol, NumRow > &a)
void position(const bNode &, void *r_value)
static void node_geo_exec(GeoNodeExecParams params)
static void node_init(bNodeTree *, bNode *node)
static void node_rna(StructRNA *srna)
static void node_layout(uiLayout *layout, bContext *, PointerRNA *ptr)
static void raycast_to_mesh(const IndexMask &mask, const Mesh &mesh, const VArray< float3 > &ray_origins, const VArray< float3 > &ray_directions, const VArray< float > &ray_lengths, const MutableSpan< bool > r_hit, const MutableSpan< int > r_hit_indices, const MutableSpan< float3 > r_hit_positions, const MutableSpan< float3 > r_hit_normals, const MutableSpan< float > r_hit_distances)
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
static void node_declare(NodeDeclarationBuilder &b)
void search_link_ops_for_declarations(GatherLinkSearchOpParams &params, Span< SocketDeclaration * > declarations)
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 geo_node_type_base(blender::bke::bNodeType *ntype, int type, const char *name, short nclass)
void node_free_standard_storage(bNode *node)
Definition node_util.cc:46
void node_copy_standard_storage(bNodeTree *, bNode *dest_node, const bNode *src_node)
Definition node_util.cc:58
const EnumPropertyItem rna_enum_attribute_type_items[]
BVHTree_RayCastCallback raycast_callback
StructRNA * srna
Definition RNA_types.hh:780
const Mesh * get_mesh() const
Defines a node type.
Definition BKE_node.hh:218
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:267
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:339
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:238
NodeGatherSocketLinkOperationsFunction gather_link_search_ops
Definition BKE_node.hh:363
NodeDeclareFunction declare
Definition BKE_node.hh:347
PointerRNA * ptr
Definition wm_files.cc:4126