diff options
author | Hans Goudey <h.goudey@me.com> | 2022-01-11 01:41:05 +0300 |
---|---|---|
committer | Hans Goudey <h.goudey@me.com> | 2022-01-11 01:41:05 +0300 |
commit | f4af21038d82bba525dec4bfd291c43504607acf (patch) | |
tree | e8ffe16b4059d1a269cf497b1475a826afb39f80 /source | |
parent | fe82b8d1e8425c2e9ba30f85de20f85de274dd99 (diff) |
Geometry Nodes: Move normal field input to be usable elsewhere
This commit moves the normal field input to `BKE_geometry_set.hh`
from the node file so that normals can be used as an implicit input to
other nodes.
Differential Revision: https://developer.blender.org/D13779
Diffstat (limited to 'source')
5 files changed, 285 insertions, 239 deletions
diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh index a429aecd828..acb89637f20 100644 --- a/source/blender/blenkernel/BKE_geometry_set.hh +++ b/source/blender/blenkernel/BKE_geometry_set.hh @@ -1078,6 +1078,30 @@ class IDAttributeFieldInput : public GeometryFieldInput { bool is_equal_to(const fn::FieldNode &other) const override; }; +VArray<float3> curve_normals_varray(const CurveComponent &component, const AttributeDomain domain); + +VArray<float3> mesh_normals_varray(const MeshComponent &mesh_component, + const Mesh &mesh, + const IndexMask mask, + const AttributeDomain domain); + +class NormalFieldInput : public GeometryFieldInput { + public: + NormalFieldInput() : GeometryFieldInput(CPPType::get<float3>()) + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask mask) const override; + + std::string socket_inspection_name() const override; + + uint64_t hash() const override; + bool is_equal_to(const fn::FieldNode &other) const override; +}; + class AnonymousAttributeFieldInput : public GeometryFieldInput { private: /** diff --git a/source/blender/blenkernel/intern/geometry_component_curve.cc b/source/blender/blenkernel/intern/geometry_component_curve.cc index 1e24b29038d..16edbc36f9c 100644 --- a/source/blender/blenkernel/intern/geometry_component_curve.cc +++ b/source/blender/blenkernel/intern/geometry_component_curve.cc @@ -14,6 +14,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include "BLI_task.hh" + #include "DNA_ID_enums.h" #include "DNA_curve_types.h" @@ -389,6 +391,98 @@ static const CurveEval *get_curve_from_component_for_read(const GeometryComponen namespace blender::bke { /* -------------------------------------------------------------------- */ +/** \name Curve Normals Access + * \{ */ + +static void calculate_bezier_normals(const BezierSpline &spline, MutableSpan<float3> normals) +{ + Span<int> offsets = spline.control_point_offsets(); + Span<float3> evaluated_normals = spline.evaluated_normals(); + for (const int i : IndexRange(spline.size())) { + normals[i] = evaluated_normals[offsets[i]]; + } +} + +static void calculate_poly_normals(const PolySpline &spline, MutableSpan<float3> normals) +{ + normals.copy_from(spline.evaluated_normals()); +} + +/** + * Because NURBS control points are not necessarily on the path, the normal at the control points + * is not well defined, so create a temporary poly spline to find the normals. This requires extra + * copying currently, but may be more efficient in the future if attributes have some form of CoW. + */ +static void calculate_nurbs_normals(const NURBSpline &spline, MutableSpan<float3> normals) +{ + PolySpline poly_spline; + poly_spline.resize(spline.size()); + poly_spline.positions().copy_from(spline.positions()); + poly_spline.tilts().copy_from(spline.tilts()); + normals.copy_from(poly_spline.evaluated_normals()); +} + +static Array<float3> curve_normal_point_domain(const CurveEval &curve) +{ + Span<SplinePtr> splines = curve.splines(); + Array<int> offsets = curve.control_point_offsets(); + const int total_size = offsets.last(); + Array<float3> normals(total_size); + + threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { + for (const int i : range) { + const Spline &spline = *splines[i]; + MutableSpan spline_normals{normals.as_mutable_span().slice(offsets[i], spline.size())}; + switch (splines[i]->type()) { + case Spline::Type::Bezier: + calculate_bezier_normals(static_cast<const BezierSpline &>(spline), spline_normals); + break; + case Spline::Type::Poly: + calculate_poly_normals(static_cast<const PolySpline &>(spline), spline_normals); + break; + case Spline::Type::NURBS: + calculate_nurbs_normals(static_cast<const NURBSpline &>(spline), spline_normals); + break; + } + } + }); + return normals; +} + +VArray<float3> curve_normals_varray(const CurveComponent &component, const AttributeDomain domain) +{ + const CurveEval *curve = component.get_for_read(); + if (curve == nullptr) { + return nullptr; + } + + if (domain == ATTR_DOMAIN_POINT) { + const Span<SplinePtr> splines = curve->splines(); + + /* Use a reference to evaluated normals if possible to avoid an allocation and a copy. + * This is only possible when there is only one poly spline. */ + if (splines.size() == 1 && splines.first()->type() == Spline::Type::Poly) { + const PolySpline &spline = static_cast<PolySpline &>(*splines.first()); + return VArray<float3>::ForSpan(spline.evaluated_normals()); + } + + Array<float3> normals = curve_normal_point_domain(*curve); + return VArray<float3>::ForContainer(std::move(normals)); + } + + if (domain == ATTR_DOMAIN_CURVE) { + Array<float3> point_normals = curve_normal_point_domain(*curve); + VArray<float3> varray = VArray<float3>::ForContainer(std::move(point_normals)); + return component.attribute_try_adapt_domain<float3>( + std::move(varray), ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE); + } + + return nullptr; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Builtin Spline Attributes * * Attributes with a value for every spline, stored contiguously or in every spline separately. diff --git a/source/blender/blenkernel/intern/geometry_component_mesh.cc b/source/blender/blenkernel/intern/geometry_component_mesh.cc index cc15e6d7b84..2b4238d26bb 100644 --- a/source/blender/blenkernel/intern/geometry_component_mesh.cc +++ b/source/blender/blenkernel/intern/geometry_component_mesh.cc @@ -123,6 +123,128 @@ void MeshComponent::ensure_owns_direct_data() /** \} */ /* -------------------------------------------------------------------- */ +/** \name Mesh Normals Field Input + * \{ */ + +namespace blender::bke { + +static VArray<float3> mesh_face_normals(const Mesh &mesh, + const Span<MVert> verts, + const Span<MPoly> polys, + const Span<MLoop> loops, + const IndexMask mask) +{ + /* Use existing normals to avoid unnecessarily recalculating them, if possible. */ + if (!(mesh.runtime.cd_dirty_poly & CD_MASK_NORMAL) && + CustomData_has_layer(&mesh.pdata, CD_NORMAL)) { + const void *data = CustomData_get_layer(&mesh.pdata, CD_NORMAL); + + return VArray<float3>::ForSpan({(const float3 *)data, polys.size()}); + } + + auto normal_fn = [verts, polys, loops](const int i) -> float3 { + float3 normal; + const MPoly &poly = polys[i]; + BKE_mesh_calc_poly_normal(&poly, &loops[poly.loopstart], verts.data(), normal); + return normal; + }; + + return VArray<float3>::ForFunc(mask.min_array_size(), normal_fn); +} + +static VArray<float3> mesh_vertex_normals(const Mesh &mesh, + const Span<MVert> verts, + const Span<MPoly> polys, + const Span<MLoop> loops, + const IndexMask mask) +{ + /* Use existing normals to avoid unnecessarily recalculating them, if possible. */ + if (!(mesh.runtime.cd_dirty_vert & CD_MASK_NORMAL) && + CustomData_has_layer(&mesh.vdata, CD_NORMAL)) { + const void *data = CustomData_get_layer(&mesh.pdata, CD_NORMAL); + + return VArray<float3>::ForSpan({(const float3 *)data, mesh.totvert}); + } + + /* If the normals are dirty, they must be recalculated for the output of this node's field + * source. Ideally vertex normals could be calculated lazily on a const mesh, but that's not + * possible at the moment, so we take ownership of the results. Sadly we must also create a copy + * of MVert to use the mesh normals API. This can be improved by adding mutex-protected lazy + * calculation of normals on meshes. + * + * Use mask.min_array_size() to avoid calculating a final chunk of data if possible. */ + Array<MVert> temp_verts(verts); + Array<float3> normals(verts.size()); /* Use full size for accumulation from faces. */ + BKE_mesh_calc_normals_poly_and_vertex(temp_verts.data(), + mask.min_array_size(), + loops.data(), + loops.size(), + polys.data(), + polys.size(), + nullptr, + (float(*)[3])normals.data()); + + return VArray<float3>::ForContainer(std::move(normals)); +} + +VArray<float3> mesh_normals_varray(const MeshComponent &mesh_component, + const Mesh &mesh, + const IndexMask mask, + const AttributeDomain domain) +{ + Span<MVert> verts{mesh.mvert, mesh.totvert}; + Span<MEdge> edges{mesh.medge, mesh.totedge}; + Span<MPoly> polys{mesh.mpoly, mesh.totpoly}; + Span<MLoop> loops{mesh.mloop, mesh.totloop}; + + switch (domain) { + case ATTR_DOMAIN_FACE: { + return mesh_face_normals(mesh, verts, polys, loops, mask); + } + case ATTR_DOMAIN_POINT: { + return mesh_vertex_normals(mesh, verts, polys, loops, mask); + } + case ATTR_DOMAIN_EDGE: { + /* In this case, start with vertex normals and convert to the edge domain, since the + * conversion from edges to vertices is very simple. Use the full mask since the edges + * might use the vertex normal from any index. */ + GVArray vert_normals = mesh_vertex_normals( + mesh, verts, polys, loops, IndexRange(verts.size())); + Span<float3> vert_normals_span = vert_normals.get_internal_span().typed<float3>(); + Array<float3> edge_normals(mask.min_array_size()); + + /* Use "manual" domain interpolation instead of the GeometryComponent API to avoid + * calculating unnecessary values and to allow normalizing the result much more simply. */ + for (const int i : mask) { + const MEdge &edge = edges[i]; + edge_normals[i] = float3::interpolate( + vert_normals_span[edge.v1], vert_normals_span[edge.v2], 0.5f) + .normalized(); + } + + return VArray<float3>::ForContainer(std::move(edge_normals)); + } + case ATTR_DOMAIN_CORNER: { + /* The normals on corners are just the mesh's face normals, so start with the face normal + * array and copy the face normal for each of its corners. */ + VArray<float3> face_normals = mesh_face_normals( + mesh, verts, polys, loops, IndexRange(polys.size())); + + /* In this case using the mesh component's generic domain interpolation is fine, the data + * will still be normalized, since the face normal is just copied to every corner. */ + return mesh_component.attribute_try_adapt_domain<float3>( + std::move(face_normals), ATTR_DOMAIN_FACE, ATTR_DOMAIN_CORNER); + } + default: + return {}; + } +} + +} // namespace blender::bke + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Attribute Access * \{ */ diff --git a/source/blender/blenkernel/intern/geometry_set.cc b/source/blender/blenkernel/intern/geometry_set.cc index 06e0e78745b..c1e386c626b 100644 --- a/source/blender/blenkernel/intern/geometry_set.cc +++ b/source/blender/blenkernel/intern/geometry_set.cc @@ -17,6 +17,8 @@ #include "BLI_map.hh" #include "BLI_task.hh" +#include "BLT_translation.h" + #include "BKE_attribute.h" #include "BKE_attribute_access.hh" #include "BKE_geometry_set.hh" @@ -568,6 +570,48 @@ void GeometrySet::modify_geometry_sets(ForeachSubGeometryCallback callback) /** \} */ /* -------------------------------------------------------------------- */ +/** \name Mesh and Curve Normals Field Input + * \{ */ + +namespace blender::bke { + +GVArray NormalFieldInput::get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask mask) const +{ + if (component.type() == GEO_COMPONENT_TYPE_MESH) { + const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); + if (const Mesh *mesh = mesh_component.get_for_read()) { + return mesh_normals_varray(mesh_component, *mesh, mask, domain); + } + } + else if (component.type() == GEO_COMPONENT_TYPE_CURVE) { + const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); + return curve_normals_varray(curve_component, domain); + } + return {}; +} + +std::string NormalFieldInput::socket_inspection_name() const +{ + return TIP_("Normal"); +} + +uint64_t NormalFieldInput::hash() const +{ + return 213980475983; +} + +bool NormalFieldInput::is_equal_to(const fn::FieldNode &other) const +{ + return dynamic_cast<const NormalFieldInput *>(&other) != nullptr; +} + +} // namespace blender::bke + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name C API * \{ */ diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc b/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc index db5f30fb6c9..120ae0e9bd1 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc @@ -31,247 +31,9 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_output<decl::Vector>(N_("Normal")).field_source(); } -static VArray<float3> mesh_face_normals(const Mesh &mesh, - const Span<MVert> verts, - const Span<MPoly> polys, - const Span<MLoop> loops, - const IndexMask mask) -{ - /* Use existing normals to avoid unnecessarily recalculating them, if possible. */ - if (!(mesh.runtime.cd_dirty_poly & CD_MASK_NORMAL) && - CustomData_has_layer(&mesh.pdata, CD_NORMAL)) { - const void *data = CustomData_get_layer(&mesh.pdata, CD_NORMAL); - - return VArray<float3>::ForSpan({(const float3 *)data, polys.size()}); - } - - auto normal_fn = [verts, polys, loops](const int i) -> float3 { - float3 normal; - const MPoly &poly = polys[i]; - BKE_mesh_calc_poly_normal(&poly, &loops[poly.loopstart], verts.data(), normal); - return normal; - }; - - return VArray<float3>::ForFunc(mask.min_array_size(), normal_fn); -} - -static VArray<float3> mesh_vertex_normals(const Mesh &mesh, - const Span<MVert> verts, - const Span<MPoly> polys, - const Span<MLoop> loops, - const IndexMask mask) -{ - /* Use existing normals to avoid unnecessarily recalculating them, if possible. */ - if (!(mesh.runtime.cd_dirty_vert & CD_MASK_NORMAL) && - CustomData_has_layer(&mesh.vdata, CD_NORMAL)) { - const void *data = CustomData_get_layer(&mesh.pdata, CD_NORMAL); - - return VArray<float3>::ForSpan({(const float3 *)data, mesh.totvert}); - } - - /* If the normals are dirty, they must be recalculated for the output of this node's field - * source. Ideally vertex normals could be calculated lazily on a const mesh, but that's not - * possible at the moment, so we take ownership of the results. Sadly we must also create a copy - * of MVert to use the mesh normals API. This can be improved by adding mutex-protected lazy - * calculation of normals on meshes. - * - * Use mask.min_array_size() to avoid calculating a final chunk of data if possible. */ - Array<MVert> temp_verts(verts); - Array<float3> normals(verts.size()); /* Use full size for accumulation from faces. */ - BKE_mesh_calc_normals_poly_and_vertex(temp_verts.data(), - mask.min_array_size(), - loops.data(), - loops.size(), - polys.data(), - polys.size(), - nullptr, - (float(*)[3])normals.data()); - - return VArray<float3>::ForContainer(std::move(normals)); -} - -static VArray<float3> construct_mesh_normals_gvarray(const MeshComponent &mesh_component, - const Mesh &mesh, - const IndexMask mask, - const AttributeDomain domain) -{ - Span<MVert> verts{mesh.mvert, mesh.totvert}; - Span<MEdge> edges{mesh.medge, mesh.totedge}; - Span<MPoly> polys{mesh.mpoly, mesh.totpoly}; - Span<MLoop> loops{mesh.mloop, mesh.totloop}; - - switch (domain) { - case ATTR_DOMAIN_FACE: { - return mesh_face_normals(mesh, verts, polys, loops, mask); - } - case ATTR_DOMAIN_POINT: { - return mesh_vertex_normals(mesh, verts, polys, loops, mask); - } - case ATTR_DOMAIN_EDGE: { - /* In this case, start with vertex normals and convert to the edge domain, since the - * conversion from edges to vertices is very simple. Use the full mask since the edges - * might use the vertex normal from any index. */ - GVArray vert_normals = mesh_vertex_normals( - mesh, verts, polys, loops, IndexRange(verts.size())); - Span<float3> vert_normals_span = vert_normals.get_internal_span().typed<float3>(); - Array<float3> edge_normals(mask.min_array_size()); - - /* Use "manual" domain interpolation instead of the GeometryComponent API to avoid - * calculating unnecessary values and to allow normalizing the result much more simply. */ - for (const int i : mask) { - const MEdge &edge = edges[i]; - edge_normals[i] = float3::interpolate( - vert_normals_span[edge.v1], vert_normals_span[edge.v2], 0.5f) - .normalized(); - } - - return VArray<float3>::ForContainer(std::move(edge_normals)); - } - case ATTR_DOMAIN_CORNER: { - /* The normals on corners are just the mesh's face normals, so start with the face normal - * array and copy the face normal for each of its corners. */ - VArray<float3> face_normals = mesh_face_normals( - mesh, verts, polys, loops, IndexRange(polys.size())); - - /* In this case using the mesh component's generic domain interpolation is fine, the data - * will still be normalized, since the face normal is just copied to every corner. */ - return mesh_component.attribute_try_adapt_domain<float3>( - std::move(face_normals), ATTR_DOMAIN_FACE, ATTR_DOMAIN_CORNER); - } - default: - return {}; - } -} - -static void calculate_bezier_normals(const BezierSpline &spline, MutableSpan<float3> normals) -{ - Span<int> offsets = spline.control_point_offsets(); - Span<float3> evaluated_normals = spline.evaluated_normals(); - for (const int i : IndexRange(spline.size())) { - normals[i] = evaluated_normals[offsets[i]]; - } -} - -static void calculate_poly_normals(const PolySpline &spline, MutableSpan<float3> normals) -{ - normals.copy_from(spline.evaluated_normals()); -} - -/** - * Because NURBS control points are not necessarily on the path, the normal at the control points - * is not well defined, so create a temporary poly spline to find the normals. This requires extra - * copying currently, but may be more efficient in the future if attributes have some form of CoW. - */ -static void calculate_nurbs_normals(const NURBSpline &spline, MutableSpan<float3> normals) -{ - PolySpline poly_spline; - poly_spline.resize(spline.size()); - poly_spline.positions().copy_from(spline.positions()); - poly_spline.tilts().copy_from(spline.tilts()); - normals.copy_from(poly_spline.evaluated_normals()); -} - -static Array<float3> curve_normal_point_domain(const CurveEval &curve) -{ - Span<SplinePtr> splines = curve.splines(); - Array<int> offsets = curve.control_point_offsets(); - const int total_size = offsets.last(); - Array<float3> normals(total_size); - - threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { - for (const int i : range) { - const Spline &spline = *splines[i]; - MutableSpan spline_normals{normals.as_mutable_span().slice(offsets[i], spline.size())}; - switch (splines[i]->type()) { - case Spline::Type::Bezier: - calculate_bezier_normals(static_cast<const BezierSpline &>(spline), spline_normals); - break; - case Spline::Type::Poly: - calculate_poly_normals(static_cast<const PolySpline &>(spline), spline_normals); - break; - case Spline::Type::NURBS: - calculate_nurbs_normals(static_cast<const NURBSpline &>(spline), spline_normals); - break; - } - } - }); - return normals; -} - -static VArray<float3> construct_curve_normal_gvarray(const CurveComponent &component, - const AttributeDomain domain) -{ - const CurveEval *curve = component.get_for_read(); - if (curve == nullptr) { - return nullptr; - } - - if (domain == ATTR_DOMAIN_POINT) { - const Span<SplinePtr> splines = curve->splines(); - - /* Use a reference to evaluated normals if possible to avoid an allocation and a copy. - * This is only possible when there is only one poly spline. */ - if (splines.size() == 1 && splines.first()->type() == Spline::Type::Poly) { - const PolySpline &spline = static_cast<PolySpline &>(*splines.first()); - return VArray<float3>::ForSpan(spline.evaluated_normals()); - } - - Array<float3> normals = curve_normal_point_domain(*curve); - return VArray<float3>::ForContainer(std::move(normals)); - } - - if (domain == ATTR_DOMAIN_CURVE) { - Array<float3> point_normals = curve_normal_point_domain(*curve); - VArray<float3> varray = VArray<float3>::ForContainer(std::move(point_normals)); - return component.attribute_try_adapt_domain<float3>( - std::move(varray), ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE); - } - - return nullptr; -} - -class NormalFieldInput final : public GeometryFieldInput { - public: - NormalFieldInput() : GeometryFieldInput(CPPType::get<float3>(), "Normal node") - { - category_ = Category::Generated; - } - - GVArray get_varray_for_context(const GeometryComponent &component, - const AttributeDomain domain, - IndexMask mask) const final - { - if (component.type() == GEO_COMPONENT_TYPE_MESH) { - const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); - const Mesh *mesh = mesh_component.get_for_read(); - if (mesh == nullptr) { - return {}; - } - - return construct_mesh_normals_gvarray(mesh_component, *mesh, mask, domain); - } - if (component.type() == GEO_COMPONENT_TYPE_CURVE) { - const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); - return construct_curve_normal_gvarray(curve_component, domain); - } - return {}; - } - - uint64_t hash() const override - { - /* Some random constant hash. */ - return 669605641; - } - - bool is_equal_to(const fn::FieldNode &other) const override - { - return dynamic_cast<const NormalFieldInput *>(&other) != nullptr; - } -}; - static void node_geo_exec(GeoNodeExecParams params) { - Field<float3> normal_field{std::make_shared<NormalFieldInput>()}; + Field<float3> normal_field{std::make_shared<bke::NormalFieldInput>()}; params.set_output("Normal", std::move(normal_field)); } |