Blender  V3.3
node_geo_raycast.cc
Go to the documentation of this file.
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 
3 #include "DNA_mesh_types.h"
4 
5 #include "BKE_attribute_math.hh"
6 #include "BKE_bvhutils.h"
7 #include "BKE_mesh_sample.hh"
8 
9 #include "UI_interface.h"
10 #include "UI_resources.h"
11 
13 
14 #include "node_geometry_util.hh"
15 
17 
18 using namespace blender::bke::mesh_surface_sample;
19 
21 
23 {
24  b.add_input<decl::Geometry>(N_("Target Geometry"))
25  .only_realized_data()
26  .supported_type(GEO_COMPONENT_TYPE_MESH);
27 
28  b.add_input<decl::Vector>(N_("Attribute")).hide_value().supports_field();
29  b.add_input<decl::Float>(N_("Attribute"), "Attribute_001").hide_value().supports_field();
30  b.add_input<decl::Color>(N_("Attribute"), "Attribute_002").hide_value().supports_field();
31  b.add_input<decl::Bool>(N_("Attribute"), "Attribute_003").hide_value().supports_field();
32  b.add_input<decl::Int>(N_("Attribute"), "Attribute_004").hide_value().supports_field();
33 
34  b.add_input<decl::Vector>(N_("Source Position")).implicit_field();
35  b.add_input<decl::Vector>(N_("Ray Direction"))
36  .default_value({0.0f, 0.0f, -1.0f})
37  .supports_field();
38  b.add_input<decl::Float>(N_("Ray Length"))
39  .default_value(100.0f)
40  .min(0.0f)
41  .subtype(PROP_DISTANCE)
42  .supports_field();
43 
44  b.add_output<decl::Bool>(N_("Is Hit")).dependent_field();
45  b.add_output<decl::Vector>(N_("Hit Position")).dependent_field();
46  b.add_output<decl::Vector>(N_("Hit Normal")).dependent_field();
47  b.add_output<decl::Float>(N_("Hit Distance")).dependent_field();
48 
49  b.add_output<decl::Vector>(N_("Attribute")).dependent_field({1, 2, 3, 4, 5, 6});
50  b.add_output<decl::Float>(N_("Attribute"), "Attribute_001").dependent_field({1, 2, 3, 4, 5, 6});
51  b.add_output<decl::Color>(N_("Attribute"), "Attribute_002").dependent_field({1, 2, 3, 4, 5, 6});
52  b.add_output<decl::Bool>(N_("Attribute"), "Attribute_003").dependent_field({1, 2, 3, 4, 5, 6});
53  b.add_output<decl::Int>(N_("Attribute"), "Attribute_004").dependent_field({1, 2, 3, 4, 5, 6});
54 }
55 
56 static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
57 {
58  uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE);
59  uiItemR(layout, ptr, "mapping", 0, "", ICON_NONE);
60 }
61 
63 {
64  NodeGeometryRaycast *data = MEM_cnew<NodeGeometryRaycast>(__func__);
66  data->data_type = CD_PROP_FLOAT;
67  node->storage = data;
68 }
69 
71 {
72  const NodeGeometryRaycast &storage = node_storage(*node);
73  const eCustomDataType data_type = static_cast<eCustomDataType>(storage.data_type);
74 
75  bNodeSocket *socket_vector = (bNodeSocket *)BLI_findlink(&node->inputs, 1);
76  bNodeSocket *socket_float = socket_vector->next;
77  bNodeSocket *socket_color4f = socket_float->next;
78  bNodeSocket *socket_boolean = socket_color4f->next;
79  bNodeSocket *socket_int32 = socket_boolean->next;
80 
81  nodeSetSocketAvailability(ntree, socket_vector, data_type == CD_PROP_FLOAT3);
82  nodeSetSocketAvailability(ntree, socket_float, data_type == CD_PROP_FLOAT);
83  nodeSetSocketAvailability(ntree, socket_color4f, data_type == CD_PROP_COLOR);
84  nodeSetSocketAvailability(ntree, socket_boolean, data_type == CD_PROP_BOOL);
85  nodeSetSocketAvailability(ntree, socket_int32, data_type == CD_PROP_INT32);
86 
87  bNodeSocket *out_socket_vector = (bNodeSocket *)BLI_findlink(&node->outputs, 4);
88  bNodeSocket *out_socket_float = out_socket_vector->next;
89  bNodeSocket *out_socket_color4f = out_socket_float->next;
90  bNodeSocket *out_socket_boolean = out_socket_color4f->next;
91  bNodeSocket *out_socket_int32 = out_socket_boolean->next;
92 
93  nodeSetSocketAvailability(ntree, out_socket_vector, data_type == CD_PROP_FLOAT3);
94  nodeSetSocketAvailability(ntree, out_socket_float, data_type == CD_PROP_FLOAT);
95  nodeSetSocketAvailability(ntree, out_socket_color4f, data_type == CD_PROP_COLOR);
96  nodeSetSocketAvailability(ntree, out_socket_boolean, data_type == CD_PROP_BOOL);
97  nodeSetSocketAvailability(ntree, out_socket_int32, data_type == CD_PROP_INT32);
98 }
99 
101 {
102  const NodeDeclaration &declaration = *params.node_type().fixed_declaration;
103  search_link_ops_for_declarations(params, declaration.inputs().take_front(1));
104  search_link_ops_for_declarations(params, declaration.inputs().take_back(3));
105  search_link_ops_for_declarations(params, declaration.outputs().take_front(4));
106 
107  const std::optional<eCustomDataType> type = node_data_type_to_custom_data_type(
108  (eNodeSocketDatatype)params.other_socket().type);
109  if (type && *type != CD_PROP_STRING) {
110  /* The input and output sockets have the same name. */
111  params.add_item(IFACE_("Attribute"), [type](LinkSearchOpParams &params) {
112  bNode &node = params.add_node("GeometryNodeRaycast");
113  node_storage(node).data_type = *type;
114  params.update_and_connect_available_socket(node, "Attribute");
115  });
116  }
117 }
118 
120 {
121  switch (map_mode) {
123  return eAttributeMapMode::INTERPOLATED;
124  default:
126  return eAttributeMapMode::NEAREST;
127  }
128 }
129 
131  const Mesh &mesh,
132  const VArray<float3> &ray_origins,
133  const VArray<float3> &ray_directions,
134  const VArray<float> &ray_lengths,
135  const MutableSpan<bool> r_hit,
136  const MutableSpan<int> r_hit_indices,
137  const MutableSpan<float3> r_hit_positions,
138  const MutableSpan<float3> r_hit_normals,
139  const MutableSpan<float> r_hit_distances,
140  int &hit_count)
141 {
142  BVHTreeFromMesh tree_data;
144  BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&tree_data); });
145 
146  if (tree_data.tree == nullptr) {
147  return;
148  }
149  /* We shouldn't be rebuilding the BVH tree when calling this function in parallel. */
150  BLI_assert(tree_data.cached);
151 
152  for (const int i : mask) {
153  const float ray_length = ray_lengths[i];
154  const float3 ray_origin = ray_origins[i];
155  const float3 ray_direction = math::normalize(ray_directions[i]);
156 
157  BVHTreeRayHit hit;
158  hit.index = -1;
159  hit.dist = ray_length;
160  if (BLI_bvhtree_ray_cast(tree_data.tree,
161  ray_origin,
162  ray_direction,
163  0.0f,
164  &hit,
165  tree_data.raycast_callback,
166  &tree_data) != -1) {
167  hit_count++;
168  if (!r_hit.is_empty()) {
169  r_hit[i] = hit.index >= 0;
170  }
171  if (!r_hit_indices.is_empty()) {
172  /* The caller must be able to handle invalid indices anyway, so don't clamp this value. */
173  r_hit_indices[i] = hit.index;
174  }
175  if (!r_hit_positions.is_empty()) {
176  r_hit_positions[i] = hit.co;
177  }
178  if (!r_hit_normals.is_empty()) {
179  r_hit_normals[i] = hit.no;
180  }
181  if (!r_hit_distances.is_empty()) {
182  r_hit_distances[i] = hit.dist;
183  }
184  }
185  else {
186  if (!r_hit.is_empty()) {
187  r_hit[i] = false;
188  }
189  if (!r_hit_indices.is_empty()) {
190  r_hit_indices[i] = -1;
191  }
192  if (!r_hit_positions.is_empty()) {
193  r_hit_positions[i] = float3(0.0f, 0.0f, 0.0f);
194  }
195  if (!r_hit_normals.is_empty()) {
196  r_hit_normals[i] = float3(0.0f, 0.0f, 0.0f);
197  }
198  if (!r_hit_distances.is_empty()) {
199  r_hit_distances[i] = ray_length;
200  }
201  }
202  }
203 }
204 
206  private:
207  GeometrySet target_;
209 
211  std::optional<GeometryComponentFieldContext> target_context_;
212  std::unique_ptr<FieldEvaluator> target_evaluator_;
213  const GVArray *target_data_ = nullptr;
214 
215  /* Always evaluate the target domain data on the face corner domain because it contains the most
216  * information. Eventually this could be exposed as an option or determined automatically from
217  * the field inputs for better performance. */
218  const eAttrDomain domain_ = ATTR_DOMAIN_CORNER;
219 
220  fn::MFSignature signature_;
221 
222  public:
224  : target_(std::move(target)), mapping_((GeometryNodeRaycastMapMode)mapping)
225  {
226  target_.ensure_owns_direct_data();
227  this->evaluate_target_field(std::move(src_field));
228  signature_ = create_signature();
229  this->set_signature(&signature_);
230  }
231 
233  {
234  blender::fn::MFSignatureBuilder signature{"Geometry Proximity"};
235  signature.single_input<float3>("Source Position");
236  signature.single_input<float3>("Ray Direction");
237  signature.single_input<float>("Ray Length");
238  signature.single_output<bool>("Is Hit");
239  signature.single_output<float3>("Hit Position");
240  signature.single_output<float3>("Hit Normal");
241  signature.single_output<float>("Distance");
242  if (target_data_) {
243  signature.single_output("Attribute", target_data_->type());
244  }
245  return signature.build();
246  }
247 
249  {
250  /* Hit positions are always necessary for retrieving the attribute from the target if that
251  * output is required, so always retrieve a span from the evaluator in that case (it's
252  * expected that the evaluator is more likely to have a spare buffer that could be used). */
253  MutableSpan<float3> hit_positions =
254  (target_data_) ? params.uninitialized_single_output<float3>(4, "Hit Position") :
255  params.uninitialized_single_output_if_required<float3>(4, "Hit Position");
256 
257  Array<int> hit_indices;
258  if (target_data_) {
259  hit_indices.reinitialize(mask.min_array_size());
260  }
261 
262  BLI_assert(target_.has_mesh());
263  const Mesh &mesh = *target_.get_mesh_for_read();
264 
265  int hit_count = 0;
267  mesh,
268  params.readonly_single_input<float3>(0, "Source Position"),
269  params.readonly_single_input<float3>(1, "Ray Direction"),
270  params.readonly_single_input<float>(2, "Ray Length"),
271  params.uninitialized_single_output_if_required<bool>(3, "Is Hit"),
272  hit_indices,
273  hit_positions,
274  params.uninitialized_single_output_if_required<float3>(5, "Hit Normal"),
275  params.uninitialized_single_output_if_required<float>(6, "Distance"),
276  hit_count);
277 
278  if (target_data_) {
279  IndexMask hit_mask;
280  Vector<int64_t> hit_mask_indices;
281  if (hit_count < mask.size()) {
282  /* Not all rays hit the target. Create a corrected mask to avoid transferring attribute
283  * data to invalid indices. An alternative would be handling -1 indices in a separate case
284  * in #MeshAttributeInterpolator, but since it already has an IndexMask in its constructor,
285  * it's simpler to use that. */
286  hit_mask_indices.reserve(hit_count);
287  for (const int64_t i : mask) {
288  if (hit_indices[i] != -1) {
289  hit_mask_indices.append(i);
290  }
291  hit_mask = IndexMask(hit_mask_indices);
292  }
293  }
294  else {
295  hit_mask = mask;
296  }
297 
298  GMutableSpan result = params.uninitialized_single_output_if_required(7, "Attribute");
299  if (!result.is_empty()) {
300  MeshAttributeInterpolator interp(&mesh, hit_mask, hit_positions, hit_indices);
301  result.type().value_initialize_indices(result.data(), mask);
302  interp.sample_data(*target_data_, domain_, get_map_mode(mapping_), result);
303  }
304  }
305  }
306 
307  private:
308  void evaluate_target_field(GField src_field)
309  {
310  if (!src_field) {
311  return;
312  }
313  const MeshComponent &mesh_component = *target_.get_component_for_read<MeshComponent>();
314  target_context_.emplace(GeometryComponentFieldContext{mesh_component, domain_});
315  const int domain_size = mesh_component.attribute_domain_size(domain_);
316  target_evaluator_ = std::make_unique<FieldEvaluator>(*target_context_, domain_size);
317  target_evaluator_->add(std::move(src_field));
318  target_evaluator_->evaluate();
319  target_data_ = &target_evaluator_->get_evaluated(0);
320  }
321 };
322 
324 {
325  switch (data_type) {
326  case CD_PROP_FLOAT:
327  if (params.output_is_required("Attribute_001")) {
328  return params.extract_input<Field<float>>("Attribute_001");
329  }
330  break;
331  case CD_PROP_FLOAT3:
332  if (params.output_is_required("Attribute")) {
333  return params.extract_input<Field<float3>>("Attribute");
334  }
335  break;
336  case CD_PROP_COLOR:
337  if (params.output_is_required("Attribute_002")) {
338  return params.extract_input<Field<ColorGeometry4f>>("Attribute_002");
339  }
340  break;
341  case CD_PROP_BOOL:
342  if (params.output_is_required("Attribute_003")) {
343  return params.extract_input<Field<bool>>("Attribute_003");
344  }
345  break;
346  case CD_PROP_INT32:
347  if (params.output_is_required("Attribute_004")) {
348  return params.extract_input<Field<int>>("Attribute_004");
349  }
350  break;
351  default:
353  }
354  return {};
355 }
356 
358 {
359  switch (bke::cpp_type_to_custom_data_type(field.cpp_type())) {
360  case CD_PROP_FLOAT: {
361  params.set_output("Attribute_001", Field<float>(field));
362  break;
363  }
364  case CD_PROP_FLOAT3: {
365  params.set_output("Attribute", Field<float3>(field));
366  break;
367  }
368  case CD_PROP_COLOR: {
369  params.set_output("Attribute_002", Field<ColorGeometry4f>(field));
370  break;
371  }
372  case CD_PROP_BOOL: {
373  params.set_output("Attribute_003", Field<bool>(field));
374  break;
375  }
376  case CD_PROP_INT32: {
377  params.set_output("Attribute_004", Field<int>(field));
378  break;
379  }
380  default:
381  break;
382  }
383 }
384 
386 {
387  GeometrySet target = params.extract_input<GeometrySet>("Target Geometry");
388  const NodeGeometryRaycast &storage = node_storage(params.node());
389  const GeometryNodeRaycastMapMode mapping = (GeometryNodeRaycastMapMode)storage.mapping;
390  const eCustomDataType data_type = static_cast<eCustomDataType>(storage.data_type);
391 
392  if (target.is_empty()) {
393  params.set_default_remaining_outputs();
394  return;
395  }
396 
397  if (!target.has_mesh()) {
398  params.set_default_remaining_outputs();
399  return;
400  }
401 
402  if (target.get_mesh_for_read()->totpoly == 0) {
403  params.error_message_add(NodeWarningType::Error, TIP_("The target mesh must have faces"));
404  params.set_default_remaining_outputs();
405  return;
406  }
407 
408  GField field = get_input_attribute_field(params, data_type);
409  const bool do_attribute_transfer = bool(field);
410  Field<float3> position_field = params.extract_input<Field<float3>>("Source Position");
411  Field<float3> direction_field = params.extract_input<Field<float3>>("Ray Direction");
412  Field<float> length_field = params.extract_input<Field<float>>("Ray Length");
413 
414  auto fn = std::make_unique<RaycastFunction>(std::move(target), std::move(field), mapping);
415  auto op = std::make_shared<FieldOperation>(FieldOperation(
416  std::move(fn),
417  {std::move(position_field), std::move(direction_field), std::move(length_field)}));
418 
419  params.set_output("Is Hit", Field<bool>(op, 0));
420  params.set_output("Hit Position", Field<float3>(op, 1));
421  params.set_output("Hit Normal", Field<float3>(op, 2));
422  params.set_output("Hit Distance", Field<float>(op, 3));
423  if (do_attribute_transfer) {
425  }
426 }
427 
428 } // namespace blender::nodes::node_geo_raycast_cc
429 
431 {
432  namespace file_ns = blender::nodes::node_geo_raycast_cc;
433 
434  static bNodeType ntype;
435 
441  &ntype, "NodeGeometryRaycast", node_free_standard_storage, node_copy_standard_storage);
446  nodeRegisterType(&ntype);
447 }
eAttrDomain
Definition: BKE_attribute.h:25
@ ATTR_DOMAIN_CORNER
Definition: BKE_attribute.h:30
void free_bvhtree_from_mesh(struct BVHTreeFromMesh *data)
Definition: bvhutils.cc:1410
@ BVHTREE_FROM_LOOPTRI
Definition: BKE_bvhutils.h:73
BVHTree * BKE_bvhtree_from_mesh_get(struct BVHTreeFromMesh *data, const struct Mesh *mesh, BVHCacheType bvh_cache_type, int tree_type)
Definition: bvhutils.cc:1213
@ GEO_COMPONENT_TYPE_MESH
void node_type_update(struct bNodeType *ntype, void(*updatefunc)(struct bNodeTree *ntree, struct bNode *node))
Definition: node.cc:4443
#define NODE_STORAGE_FUNCS(StorageT)
Definition: BKE_node.h:1563
void nodeSetSocketAvailability(struct bNodeTree *ntree, struct bNodeSocket *sock, bool is_available)
Definition: node.cc:3664
void node_type_init(struct bNodeType *ntype, void(*initfunc)(struct bNodeTree *ntree, struct bNode *node))
Definition: node.cc:4390
void node_type_size_preset(struct bNodeType *ntype, eNodeSizePreset size)
Definition: node.cc:4408
#define NODE_CLASS_GEOMETRY
Definition: BKE_node.h:359
#define GEO_NODE_RAYCAST
Definition: BKE_node.h:1470
void node_type_storage(struct bNodeType *ntype, const char *storagename, void(*freefunc)(struct bNode *node), void(*copyfunc)(struct bNodeTree *dest_ntree, struct bNode *dest_node, const struct bNode *src_node))
Definition: node.cc:4426
void nodeRegisterType(struct bNodeType *ntype)
Definition: node.cc:1357
@ NODE_SIZE_MIDDLE
Definition: BKE_node.h:366
#define BLI_assert_unreachable()
Definition: BLI_assert.h:93
#define BLI_assert(a)
Definition: BLI_assert.h:46
int BLI_bvhtree_ray_cast(BVHTree *tree, const float co[3], const float dir[3], float radius, BVHTreeRayHit *hit, BVHTree_RayCastCallback callback, void *userdata)
Definition: BLI_kdopbvh.c:1942
void * BLI_findlink(const struct ListBase *listbase, int number) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
#define BLI_SCOPED_DEFER(function_to_defer)
#define UNUSED(x)
#define TIP_(msgid)
#define IFACE_(msgid)
eCustomDataType
@ CD_PROP_FLOAT
@ CD_PROP_FLOAT3
@ CD_PROP_COLOR
@ CD_PROP_INT32
@ CD_PROP_BOOL
@ CD_PROP_STRING
eNodeSocketDatatype
GeometryNodeRaycastMapMode
@ GEO_NODE_RAYCAST_NEAREST
@ GEO_NODE_RAYCAST_INTERPOLATED
_GL_VOID GLfloat value _GL_VOID_RET _GL_VOID const GLuint GLboolean *residences _GL_BOOL_RET _GL_VOID GLsizei GLfloat GLfloat GLfloat GLfloat const GLubyte *bitmap _GL_VOID_RET _GL_VOID GLenum type
@ PROP_DISTANCE
Definition: RNA_types.h:149
void uiItemR(uiLayout *layout, struct PointerRNA *ptr, const char *propname, int flag, const char *name, int icon)
int attribute_domain_size(eAttrDomain domain) const
Definition: geometry_set.cc:63
void reinitialize(const int64_t new_size)
Definition: BLI_array.hh:387
const CPPType & type() const
constexpr bool is_empty() const
Definition: BLI_span.hh:519
void append(const T &value)
Definition: BLI_vector.hh:433
void reserve(const int64_t min_capacity)
Definition: BLI_vector.hh:340
const CPPType & cpp_type() const
Definition: FN_field.hh:122
Span< SocketDeclarationPtr > outputs() const
Span< SocketDeclarationPtr > inputs() const
void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override
RaycastFunction(GeometrySet target, GField src_field, GeometryNodeRaycastMapMode mapping)
OperationNode * node
void * tree
bNodeTree * ntree
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
ccl_device_inline float2 interp(const float2 &a, const float2 &b, float t)
Definition: math_float2.h:232
ccl_device_inline float4 mask(const int4 &mask, const float4 &a)
Definition: math_float4.h:513
eCustomDataType cpp_type_to_custom_data_type(const blender::CPPType &type)
Definition: customdata.cc:5337
vec_base< T, Size > normalize(const vec_base< T, Size > &v)
static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
static void node_geo_exec(GeoNodeExecParams params)
static void output_attribute_field(GeoNodeExecParams &params, GField field)
static void node_init(bNodeTree *UNUSED(tree), bNode *node)
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
static void raycast_to_mesh(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, int &hit_count)
static GField get_input_attribute_field(GeoNodeExecParams &params, const eCustomDataType data_type)
static eAttributeMapMode get_map_mode(GeometryNodeRaycastMapMode map_mode)
static void node_update(bNodeTree *ntree, bNode *node)
static void node_declare(NodeDeclarationBuilder &b)
std::optional< eCustomDataType > node_data_type_to_custom_data_type(const eNodeSocketDatatype type)
void search_link_ops_for_declarations(GatherLinkSearchOpParams &params, Span< SocketDeclarationPtr > declarations)
vec_base< float, 3 > float3
static const pxr::TfToken b("b", pxr::TfToken::Immortal)
static void node_init(const struct bContext *C, bNodeTree *ntree, bNode *node)
Definition: node.cc:1082
void register_node_type_geo_raycast()
void geo_node_type_base(bNodeType *ntype, int type, const char *name, short nclass)
void node_copy_standard_storage(bNodeTree *UNUSED(dest_ntree), bNode *dest_node, const bNode *src_node)
Definition: node_util.c:55
void node_free_standard_storage(bNode *node)
Definition: node_util.c:43
__int64 int64_t
Definition: stdint.h:89
BVHTree_RayCastCallback raycast_callback
Definition: BKE_bvhutils.h:54
struct BVHTree * tree
Definition: BKE_bvhutils.h:50
float co[3]
Definition: BLI_kdopbvh.h:68
float no[3]
Definition: BLI_kdopbvh.h:70
void ensure_owns_direct_data()
const GeometryComponent * get_component_for_read(GeometryComponentType component_type) const
const Mesh * get_mesh_for_read() const
bool is_empty() const
bool has_mesh() const
int totpoly
struct bNodeSocket * next
Defines a node type.
Definition: BKE_node.h:226
NodeGeometryExecFunction geometry_node_execute
Definition: BKE_node.h:316
NodeGatherSocketLinkOperationsFunction gather_link_search_ops
Definition: BKE_node.h:335
void(* draw_buttons)(struct uiLayout *, struct bContext *C, struct PointerRNA *ptr)
Definition: BKE_node.h:244
NodeDeclareFunction declare
Definition: BKE_node.h:324
#define N_(msgid)
PointerRNA * ptr
Definition: wm_files.c:3480