Blender  V3.3
node_geo_scale_elements.cc
Go to the documentation of this file.
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 
3 #include "BLI_array.hh"
4 #include "BLI_disjoint_set.hh"
5 #include "BLI_task.hh"
6 #include "BLI_vector.hh"
7 #include "BLI_vector_set.hh"
8 
9 #include "DNA_mesh_types.h"
10 #include "DNA_meshdata_types.h"
11 
12 #include "UI_interface.h"
13 #include "UI_resources.h"
14 
15 #include "BKE_mesh.h"
16 
17 #include "node_geometry_util.hh"
18 
20 
22 {
23  b.add_input<decl::Geometry>(N_("Geometry")).supported_type(GEO_COMPONENT_TYPE_MESH);
24  b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().supports_field();
25  b.add_input<decl::Float>(N_("Scale"), "Scale").default_value(1.0f).min(0.0f).supports_field();
26  b.add_input<decl::Vector>(N_("Center"))
27  .subtype(PROP_TRANSLATION)
28  .implicit_field()
29  .description(N_("Origin of the scaling for each element. If multiple elements are "
30  "connected, their center is averaged"));
31  b.add_input<decl::Vector>(N_("Axis"))
32  .default_value({1.0f, 0.0f, 0.0f})
33  .supports_field()
34  .description(N_("Direction in which to scale the element"))
35  .make_available([](bNode &node) { node.custom2 = GEO_NODE_SCALE_ELEMENTS_SINGLE_AXIS; });
36  b.add_output<decl::Geometry>(N_("Geometry"));
37 };
38 
39 static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
40 {
41  uiItemR(layout, ptr, "domain", 0, "", ICON_NONE);
42  uiItemR(layout, ptr, "scale_mode", 0, "", ICON_NONE);
43 }
44 
46 {
47  node->custom1 = ATTR_DOMAIN_FACE;
49 }
50 
52 {
53  bNodeSocket *geometry_socket = static_cast<bNodeSocket *>(node->inputs.first);
54  bNodeSocket *selection_socket = geometry_socket->next;
55  bNodeSocket *scale_float_socket = selection_socket->next;
56  bNodeSocket *center_socket = scale_float_socket->next;
57  bNodeSocket *axis_socket = center_socket->next;
58 
60  node->custom2);
61  const bool use_single_axis = mode == GEO_NODE_SCALE_ELEMENTS_SINGLE_AXIS;
62 
63  nodeSetSocketAvailability(ntree, axis_socket, use_single_axis);
64 }
65 
70 };
71 
76 };
77 
83 };
84 
90 };
91 
95 struct ElementIsland {
96  /* Either face or edge indices. */
98 };
99 
101  const float3 &center,
102  const float scale)
103 {
104  const float3 diff = position - center;
105  const float3 scaled_diff = scale * diff;
106  const float3 new_position = center + scaled_diff;
107  return new_position;
108 }
109 
111  const float3 &axis,
112  const float scale)
113 {
114  /* Scale along x axis. The other axis need to be orthogonal, but their specific value does not
115  * matter. */
116  const float3 x_axis = math::normalize(axis);
117  float3 y_axis = math::cross(x_axis, float3(0.0f, 0.0f, 1.0f));
118  if (math::is_zero(y_axis)) {
119  y_axis = math::cross(x_axis, float3(0.0f, 1.0f, 0.0f));
120  }
121  y_axis = math::normalize(y_axis);
122  const float3 z_axis = math::cross(x_axis, y_axis);
123 
125 
126  /* Move scaling center to the origin. */
127  sub_v3_v3(transform.values[3], center);
128 
129  /* `base_change` and `base_change_inv` are used to rotate space so that scaling along the
130  * provided axis is the same as scaling along the x axis. */
131  float4x4 base_change = float4x4::identity();
132  copy_v3_v3(base_change.values[0], x_axis);
133  copy_v3_v3(base_change.values[1], y_axis);
134  copy_v3_v3(base_change.values[2], z_axis);
135 
136  /* Can invert by transposing, because the matrix is orthonormal. */
137  float4x4 base_change_inv = base_change.transposed();
138 
139  float4x4 scale_transform = float4x4::identity();
140  scale_transform.values[0][0] = scale;
141 
142  transform = base_change * scale_transform * base_change_inv * transform;
143 
144  /* Move scaling center back to where it was. */
145  add_v3_v3(transform.values[3], center);
146 
147  return transform;
148 }
149 
151  FunctionRef<void(const Mesh &mesh, int element_index, VectorSet<int> &r_vertex_indices)>;
152 
154  const Span<ElementIsland> islands,
155  const UniformScaleParams &params,
156  const GetVertexIndicesFn get_vertex_indices)
157 {
158  threading::parallel_for(islands.index_range(), 256, [&](const IndexRange range) {
159  for (const int island_index : range) {
160  const ElementIsland &island = islands[island_index];
161 
162  float scale = 0.0f;
163  float3 center = {0.0f, 0.0f, 0.0f};
164 
165  VectorSet<int> vertex_indices;
166  for (const int poly_index : island.element_indices) {
167  get_vertex_indices(mesh, poly_index, vertex_indices);
168  center += params.centers[poly_index];
169  scale += params.scales[poly_index];
170  }
171 
172  /* Divide by number of elements to get the average. */
173  const float f = 1.0f / island.element_indices.size();
174  scale *= f;
175  center *= f;
176 
177  for (const int vert_index : vertex_indices) {
178  MVert &vert = mesh.mvert[vert_index];
179  const float3 old_position = vert.co;
180  const float3 new_position = transform_with_uniform_scale(old_position, center, scale);
181  copy_v3_v3(vert.co, new_position);
182  }
183  }
184  });
185 
187 }
188 
190  const Span<ElementIsland> islands,
191  const AxisScaleParams &params,
192  const GetVertexIndicesFn get_vertex_indices)
193 {
194  threading::parallel_for(islands.index_range(), 256, [&](const IndexRange range) {
195  for (const int island_index : range) {
196  const ElementIsland &island = islands[island_index];
197 
198  float scale = 0.0f;
199  float3 center = {0.0f, 0.0f, 0.0f};
200  float3 axis = {0.0f, 0.0f, 0.0f};
201 
202  VectorSet<int> vertex_indices;
203  for (const int poly_index : island.element_indices) {
204  get_vertex_indices(mesh, poly_index, vertex_indices);
205  center += params.centers[poly_index];
206  scale += params.scales[poly_index];
207  axis += params.axis_vectors[poly_index];
208  }
209 
210  /* Divide by number of elements to get the average. */
211  const float f = 1.0f / island.element_indices.size();
212  scale *= f;
213  center *= f;
214  axis *= f;
215 
216  if (math::is_zero(axis)) {
217  axis = float3(1.0f, 0.0f, 0.0f);
218  }
219 
220  const float4x4 transform = create_single_axis_transform(center, axis, scale);
221  for (const int vert_index : vertex_indices) {
222  MVert &vert = mesh.mvert[vert_index];
223  const float3 old_position = vert.co;
224  const float3 new_position = transform * old_position;
225  copy_v3_v3(vert.co, new_position);
226  }
227  }
228  });
229 
231 }
232 
233 static Vector<ElementIsland> prepare_face_islands(const Mesh &mesh, const IndexMask face_selection)
234 {
235  /* Use the disjoint set data structure to determine which vertices have to be scaled together. */
236  DisjointSet disjoint_set(mesh.totvert);
237  for (const int poly_index : face_selection) {
238  const MPoly &poly = mesh.mpoly[poly_index];
239  const Span<MLoop> poly_loops{mesh.mloop + poly.loopstart, poly.totloop};
240  for (const int loop_index : IndexRange(poly.totloop - 1)) {
241  const int v1 = poly_loops[loop_index].v;
242  const int v2 = poly_loops[loop_index + 1].v;
243  disjoint_set.join(v1, v2);
244  }
245  disjoint_set.join(poly_loops.first().v, poly_loops.last().v);
246  }
247 
248  VectorSet<int> island_ids;
249  Vector<ElementIsland> islands;
250  /* There are at most as many islands as there are selected faces. */
251  islands.reserve(face_selection.size());
252 
253  /* Gather all of the face indices in each island into separate vectors. */
254  for (const int poly_index : face_selection) {
255  const MPoly &poly = mesh.mpoly[poly_index];
256  const Span<MLoop> poly_loops{mesh.mloop + poly.loopstart, poly.totloop};
257  const int island_id = disjoint_set.find_root(poly_loops[0].v);
258  const int island_index = island_ids.index_of_or_add(island_id);
259  if (island_index == islands.size()) {
260  islands.append_as();
261  }
262  ElementIsland &island = islands[island_index];
263  island.element_indices.append(poly_index);
264  }
265 
266  return islands;
267 }
268 
269 static void get_face_vertices(const Mesh &mesh, int face_index, VectorSet<int> &r_vertex_indices)
270 {
271  const MPoly &poly = mesh.mpoly[face_index];
272  const Span<MLoop> poly_loops{mesh.mloop + poly.loopstart, poly.totloop};
273  for (const MLoop &loop : poly_loops) {
274  r_vertex_indices.add(loop.v);
275  }
276 }
277 
279  const AxisScaleFields &fields)
280 {
282  evaluator.set_selection(fields.selection);
283  evaluator.add(fields.scale, &out.scales);
284  evaluator.add(fields.center, &out.centers);
285  evaluator.add(fields.axis, &out.axis_vectors);
286  evaluator.evaluate();
287  out.selection = evaluator.get_evaluated_selection_as_mask();
288  return out;
289 }
290 
291 static void scale_faces_on_axis(MeshComponent &mesh_component, const AxisScaleFields &fields)
292 {
293  Mesh &mesh = *mesh_component.get_for_write();
294  mesh.mvert = static_cast<MVert *>(
296 
297  GeometryComponentFieldContext field_context{mesh_component, ATTR_DOMAIN_FACE};
298  FieldEvaluator evaluator{field_context, mesh.totpoly};
300 
303 }
304 
306  const UniformScaleFields &fields)
307 {
309  evaluator.set_selection(fields.selection);
310  evaluator.add(fields.scale, &out.scales);
311  evaluator.add(fields.center, &out.centers);
312  evaluator.evaluate();
313  out.selection = evaluator.get_evaluated_selection_as_mask();
314  return out;
315 }
316 
317 static void scale_faces_uniformly(MeshComponent &mesh_component, const UniformScaleFields &fields)
318 {
319  Mesh &mesh = *mesh_component.get_for_write();
320  mesh.mvert = static_cast<MVert *>(
322 
323  GeometryComponentFieldContext field_context{mesh_component, ATTR_DOMAIN_FACE};
324  FieldEvaluator evaluator{field_context, mesh.totpoly};
326 
329 }
330 
331 static Vector<ElementIsland> prepare_edge_islands(const Mesh &mesh, const IndexMask edge_selection)
332 {
333  /* Use the disjoint set data structure to determine which vertices have to be scaled together. */
334  DisjointSet disjoint_set(mesh.totvert);
335  for (const int edge_index : edge_selection) {
336  const MEdge &edge = mesh.medge[edge_index];
337  disjoint_set.join(edge.v1, edge.v2);
338  }
339 
340  VectorSet<int> island_ids;
341  Vector<ElementIsland> islands;
342  /* There are at most as many islands as there are selected edges. */
343  islands.reserve(edge_selection.size());
344 
345  /* Gather all of the edge indices in each island into separate vectors. */
346  for (const int edge_index : edge_selection) {
347  const MEdge &edge = mesh.medge[edge_index];
348  const int island_id = disjoint_set.find_root(edge.v1);
349  const int island_index = island_ids.index_of_or_add(island_id);
350  if (island_index == islands.size()) {
351  islands.append_as();
352  }
353  ElementIsland &island = islands[island_index];
354  island.element_indices.append(edge_index);
355  }
356 
357  return islands;
358 }
359 
360 static void get_edge_vertices(const Mesh &mesh, int edge_index, VectorSet<int> &r_vertex_indices)
361 {
362  const MEdge &edge = mesh.medge[edge_index];
363  r_vertex_indices.add(edge.v1);
364  r_vertex_indices.add(edge.v2);
365 }
366 
367 static void scale_edges_uniformly(MeshComponent &mesh_component, const UniformScaleFields &fields)
368 {
369  Mesh &mesh = *mesh_component.get_for_write();
370  mesh.mvert = static_cast<MVert *>(
372 
373  GeometryComponentFieldContext field_context{mesh_component, ATTR_DOMAIN_EDGE};
374  FieldEvaluator evaluator{field_context, mesh.totedge};
376 
379 }
380 
381 static void scale_edges_on_axis(MeshComponent &mesh_component, const AxisScaleFields &fields)
382 {
383  Mesh &mesh = *mesh_component.get_for_write();
384  mesh.mvert = static_cast<MVert *>(
386 
387  GeometryComponentFieldContext field_context{mesh_component, ATTR_DOMAIN_EDGE};
388  FieldEvaluator evaluator{field_context, mesh.totedge};
390 
393 }
394 
396 {
397  const bNode &node = params.node();
398  const eAttrDomain domain = static_cast<eAttrDomain>(node.custom1);
399  const GeometryNodeScaleElementsMode scale_mode = static_cast<GeometryNodeScaleElementsMode>(
400  node.custom2);
401 
402  GeometrySet geometry = params.extract_input<GeometrySet>("Geometry");
403 
404  Field<bool> selection_field = params.get_input<Field<bool>>("Selection");
405  Field<float> scale_field = params.get_input<Field<float>>("Scale");
406  Field<float3> center_field = params.get_input<Field<float3>>("Center");
407  Field<float3> axis_field;
408  if (scale_mode == GEO_NODE_SCALE_ELEMENTS_SINGLE_AXIS) {
409  axis_field = params.get_input<Field<float3>>("Axis");
410  }
411 
412  geometry.modify_geometry_sets([&](GeometrySet &geometry) {
413  if (!geometry.has_mesh()) {
414  return;
415  }
416  MeshComponent &mesh_component = geometry.get_component_for_write<MeshComponent>();
417  switch (domain) {
418  case ATTR_DOMAIN_FACE: {
419  switch (scale_mode) {
421  scale_faces_uniformly(mesh_component, {selection_field, scale_field, center_field});
422  break;
423  }
425  scale_faces_on_axis(mesh_component,
426  {selection_field, scale_field, center_field, axis_field});
427  break;
428  }
429  }
430  break;
431  }
432  case ATTR_DOMAIN_EDGE: {
433  switch (scale_mode) {
435  scale_edges_uniformly(mesh_component, {selection_field, scale_field, center_field});
436  break;
437  }
439  scale_edges_on_axis(mesh_component,
440  {selection_field, scale_field, center_field, axis_field});
441  break;
442  }
443  }
444  break;
445  }
446  default:
448  break;
449  }
450  });
451 
452  params.set_output("Geometry", std::move(geometry));
453 }
454 
455 } // namespace blender::nodes::node_geo_scale_elements_cc
456 
458 {
459  namespace file_ns = blender::nodes::node_geo_scale_elements_cc;
460 
461  static bNodeType ntype;
462 
469  nodeRegisterType(&ntype);
470 }
eAttrDomain
Definition: BKE_attribute.h:25
@ ATTR_DOMAIN_FACE
Definition: BKE_attribute.h:29
@ ATTR_DOMAIN_EDGE
Definition: BKE_attribute.h:28
void * CustomData_duplicate_referenced_layer(struct CustomData *data, int type, int totelem)
Definition: customdata.cc:2976
@ GEO_COMPONENT_TYPE_MESH
void BKE_mesh_tag_coords_changed(struct Mesh *mesh)
void nodeSetSocketAvailability(struct bNodeTree *ntree, struct bNodeSocket *sock, bool is_available)
Definition: node.cc:3664
#define NODE_CLASS_GEOMETRY
Definition: BKE_node.h:359
#define GEO_NODE_SCALE_ELEMENTS
Definition: BKE_node.h:1492
void nodeRegisterType(struct bNodeType *ntype)
Definition: node.cc:1357
#define BLI_assert_unreachable()
Definition: BLI_assert.h:93
MINLINE void sub_v3_v3(float r[3], const float a[3])
MINLINE void copy_v3_v3(float r[3], const float a[3])
MINLINE void add_v3_v3(float r[3], const float a[3])
#define UNUSED(x)
@ CD_MVERT
GeometryNodeScaleElementsMode
@ GEO_NODE_SCALE_ELEMENTS_SINGLE_AXIS
@ GEO_NODE_SCALE_ELEMENTS_UNIFORM
NSNotificationCenter * center
_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 const void *lists _GL_VOID_RET _GL_VOID const GLdouble *equation _GL_VOID_RET _GL_VOID GLdouble GLdouble blue _GL_VOID_RET _GL_VOID GLfloat GLfloat blue _GL_VOID_RET _GL_VOID GLint GLint blue _GL_VOID_RET _GL_VOID GLshort GLshort blue _GL_VOID_RET _GL_VOID GLubyte GLubyte blue _GL_VOID_RET _GL_VOID GLuint GLuint blue _GL_VOID_RET _GL_VOID GLushort GLushort blue _GL_VOID_RET _GL_VOID GLbyte GLbyte GLbyte alpha _GL_VOID_RET _GL_VOID GLdouble GLdouble GLdouble alpha _GL_VOID_RET _GL_VOID GLfloat GLfloat GLfloat alpha _GL_VOID_RET _GL_VOID GLint GLint GLint alpha _GL_VOID_RET _GL_VOID GLshort GLshort GLshort alpha _GL_VOID_RET _GL_VOID GLubyte GLubyte GLubyte alpha _GL_VOID_RET _GL_VOID GLuint GLuint GLuint alpha _GL_VOID_RET _GL_VOID GLushort GLushort GLushort alpha _GL_VOID_RET _GL_VOID GLenum mode _GL_VOID_RET _GL_VOID GLint GLsizei GLsizei GLenum type _GL_VOID_RET _GL_VOID GLsizei GLenum GLenum const void *pixels _GL_VOID_RET _GL_VOID const void *pointer _GL_VOID_RET _GL_VOID GLdouble v _GL_VOID_RET _GL_VOID GLfloat v _GL_VOID_RET _GL_VOID GLint GLint i2 _GL_VOID_RET _GL_VOID GLint j _GL_VOID_RET _GL_VOID GLfloat param _GL_VOID_RET _GL_VOID GLint param _GL_VOID_RET _GL_VOID GLdouble GLdouble GLdouble GLdouble GLdouble zFar _GL_VOID_RET _GL_UINT GLdouble *equation _GL_VOID_RET _GL_VOID GLenum GLint *params _GL_VOID_RET _GL_VOID GLenum GLfloat *v _GL_VOID_RET _GL_VOID GLenum GLfloat *params _GL_VOID_RET _GL_VOID GLfloat *values _GL_VOID_RET _GL_VOID GLushort *values _GL_VOID_RET _GL_VOID GLenum GLfloat *params _GL_VOID_RET _GL_VOID GLenum GLdouble *params _GL_VOID_RET _GL_VOID GLenum GLint *params _GL_VOID_RET _GL_VOID GLsizei const void *pointer _GL_VOID_RET _GL_VOID GLsizei const void *pointer _GL_VOID_RET _GL_BOOL GLfloat param _GL_VOID_RET _GL_VOID GLint param _GL_VOID_RET _GL_VOID GLenum GLfloat param _GL_VOID_RET _GL_VOID GLenum GLint param _GL_VOID_RET _GL_VOID GLushort pattern _GL_VOID_RET _GL_VOID GLdouble GLdouble GLint GLint const GLdouble *points _GL_VOID_RET _GL_VOID GLdouble GLdouble GLint GLint GLdouble v1
@ PROP_TRANSLATION
Definition: RNA_types.h:154
void uiItemR(uiLayout *layout, struct PointerRNA *ptr, const char *propname, int flag, const char *name, int icon)
ATTR_WARN_UNUSED_RESULT const BMVert * v2
ATTR_WARN_UNUSED_RESULT const BMVert * v
SIMD_FORCE_INLINE btVector3 transform(const btVector3 &point) const
int64_t find_root(int64_t x)
void join(int64_t x, int64_t y)
int64_t size() const
constexpr IndexRange index_range() const
Definition: BLI_span.hh:401
bool add(const Key &key)
int64_t index_of_or_add(const Key &key)
int64_t size() const
Definition: BLI_vector.hh:694
void append(const T &value)
Definition: BLI_vector.hh:433
void reserve(const int64_t min_capacity)
Definition: BLI_vector.hh:340
void append_as(ForwardValue &&...value)
Definition: BLI_vector.hh:442
IndexMask get_evaluated_selection_as_mask()
Definition: field.cc:797
void set_selection(Field< bool > selection)
Definition: FN_field.hh:366
int add(GField field, GVArray *varray_ptr)
Definition: field.cc:731
OperationNode * node
SyclQueue void void size_t num_bytes void
void * tree
bNodeTree * ntree
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
IMETHOD Vector diff(const Vector &a, const Vector &b, double dt=1)
vec_base< T, 3 > cross(const vec_base< T, 3 > &a, const vec_base< T, 3 > &b)
vec_base< T, Size > normalize(const vec_base< T, Size > &v)
bool is_zero(const T &a)
static Vector< ElementIsland > prepare_face_islands(const Mesh &mesh, const IndexMask face_selection)
static void scale_edges_on_axis(MeshComponent &mesh_component, const AxisScaleFields &fields)
static Vector< ElementIsland > prepare_edge_islands(const Mesh &mesh, const IndexMask edge_selection)
static void node_init(bNodeTree *UNUSED(tree), bNode *node)
static void scale_faces_on_axis(MeshComponent &mesh_component, const AxisScaleFields &fields)
static float3 transform_with_uniform_scale(const float3 &position, const float3 &center, const float scale)
static void get_edge_vertices(const Mesh &mesh, int edge_index, VectorSet< int > &r_vertex_indices)
static void scale_vertex_islands_on_axis(Mesh &mesh, const Span< ElementIsland > islands, const AxisScaleParams &params, const GetVertexIndicesFn get_vertex_indices)
static AxisScaleParams evaluate_axis_scale_fields(FieldEvaluator &evaluator, const AxisScaleFields &fields)
static void node_declare(NodeDeclarationBuilder &b)
static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
static void scale_faces_uniformly(MeshComponent &mesh_component, const UniformScaleFields &fields)
static UniformScaleParams evaluate_uniform_scale_fields(FieldEvaluator &evaluator, const UniformScaleFields &fields)
static float4x4 create_single_axis_transform(const float3 &center, const float3 &axis, const float scale)
static void scale_vertex_islands_uniformly(Mesh &mesh, const Span< ElementIsland > islands, const UniformScaleParams &params, const GetVertexIndicesFn get_vertex_indices)
static void node_geo_exec(GeoNodeExecParams params)
static void node_update(bNodeTree *ntree, bNode *node)
static void scale_edges_uniformly(MeshComponent &mesh_component, const UniformScaleFields &fields)
static void get_face_vertices(const Mesh &mesh, int face_index, VectorSet< int > &r_vertex_indices)
void parallel_for(IndexRange range, int64_t grain_size, const Function &function)
Definition: BLI_task.hh:51
vec_base< float, 3 > float3
static const pxr::TfToken out("out", pxr::TfToken::Immortal)
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_scale_elements()
void geo_node_type_base(bNodeType *ntype, int type, const char *name, short nclass)
GeometryComponent & get_component_for_write(GeometryComponentType component_type)
void modify_geometry_sets(ForeachSubGeometryCallback callback)
bool has_mesh() const
struct MEdge * medge
CustomData vdata
struct MVert * mvert
int totedge
int totvert
struct MLoop * mloop
int totpoly
struct MPoly * mpoly
struct bNodeSocket * next
Defines a node type.
Definition: BKE_node.h:226
NodeGeometryExecFunction geometry_node_execute
Definition: BKE_node.h:316
void(* updatefunc)(struct bNodeTree *ntree, struct bNode *node)
Definition: BKE_node.h:265
void(* draw_buttons)(struct uiLayout *, struct bContext *C, struct PointerRNA *ptr)
Definition: BKE_node.h:244
NodeDeclareFunction declare
Definition: BKE_node.h:324
void(* initfunc)(struct bNodeTree *ntree, struct bNode *node)
Definition: BKE_node.h:270
float values[4][4]
Definition: BLI_float4x4.hh:13
static float4x4 identity()
Definition: BLI_float4x4.hh:80
float4x4 transposed() const
#define N_(msgid)
PointerRNA * ptr
Definition: wm_files.c:3480