diff options
-rw-r--r-- | release/scripts/startup/nodeitems_builtins.py | 1 | ||||
-rw-r--r-- | source/blender/blenkernel/BKE_mesh_sample.hh | 3 | ||||
-rw-r--r-- | source/blender/blenkernel/BKE_node.h | 1 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/mesh_sample.cc | 33 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/node.cc | 2 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_node_types.h | 6 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_nodetree.c | 35 | ||||
-rw-r--r-- | source/blender/nodes/CMakeLists.txt | 1 | ||||
-rw-r--r-- | source/blender/nodes/NOD_geometry.h | 1 | ||||
-rw-r--r-- | source/blender/nodes/NOD_static_types.h | 3 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_transfer.cc | 3 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/legacy/node_geo_raycast.cc | 5 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_raycast.cc | 456 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc | 4 |
14 files changed, 523 insertions, 31 deletions
diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py index 66c1ab0358a..05f2e900841 100644 --- a/release/scripts/startup/nodeitems_builtins.py +++ b/release/scripts/startup/nodeitems_builtins.py @@ -678,6 +678,7 @@ geometry_node_categories = [ NodeItem("GeometryNodeLegacyDeleteGeometry", poll=geometry_nodes_legacy_poll), NodeItem("GeometryNodeLegacyRaycast", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeRaycast"), NodeItem("GeometryNodeProximity"), NodeItem("GeometryNodeBoundBox"), NodeItem("GeometryNodeConvexHull"), diff --git a/source/blender/blenkernel/BKE_mesh_sample.hh b/source/blender/blenkernel/BKE_mesh_sample.hh index 4845876751b..17f8e766724 100644 --- a/source/blender/blenkernel/BKE_mesh_sample.hh +++ b/source/blender/blenkernel/BKE_mesh_sample.hh @@ -75,6 +75,7 @@ enum class eAttributeMapMode { class MeshAttributeInterpolator { private: const Mesh *mesh_; + const IndexMask mask_; const Span<float3> positions_; const Span<int> looptri_indices_; @@ -83,13 +84,13 @@ class MeshAttributeInterpolator { public: MeshAttributeInterpolator(const Mesh *mesh, + const IndexMask mask, const Span<float3> positions, const Span<int> looptri_indices); void sample_data(const GVArray &src, const AttributeDomain domain, const eAttributeMapMode mode, - const IndexMask mask, const GMutableSpan dst); void sample_attribute(const ReadAttributeLookup &src_attribute, diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 65e54be7db4..07ad317dd30 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -1544,6 +1544,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_TRANSFER_ATTRIBUTE 1125 #define GEO_NODE_SUBDIVISION_SURFACE 1126 #define GEO_NODE_CURVE_ENDPOINT_SELECTION 1127 +#define GEO_NODE_RAYCAST 1128 /** \} */ diff --git a/source/blender/blenkernel/intern/mesh_sample.cc b/source/blender/blenkernel/intern/mesh_sample.cc index 9842b3aff31..2274d34f0f1 100644 --- a/source/blender/blenkernel/intern/mesh_sample.cc +++ b/source/blender/blenkernel/intern/mesh_sample.cc @@ -60,8 +60,6 @@ void sample_point_attribute(const Mesh &mesh, const IndexMask mask, const GMutableSpan data_out) { - BLI_assert(data_out.size() == looptri_indices.size()); - BLI_assert(data_out.size() == bary_coords.size()); BLI_assert(data_in.size() == mesh.totvert); BLI_assert(data_in.type() == data_out.type()); @@ -109,8 +107,6 @@ void sample_corner_attribute(const Mesh &mesh, const IndexMask mask, const GMutableSpan data_out) { - BLI_assert(data_out.size() == looptri_indices.size()); - BLI_assert(data_out.size() == bary_coords.size()); BLI_assert(data_in.size() == mesh.totloop); BLI_assert(data_in.type() == data_out.type()); @@ -146,7 +142,6 @@ void sample_face_attribute(const Mesh &mesh, const IndexMask mask, const GMutableSpan data_out) { - BLI_assert(data_out.size() == looptri_indices.size()); BLI_assert(data_in.size() == mesh.totpoly); BLI_assert(data_in.type() == data_out.type()); @@ -158,9 +153,10 @@ void sample_face_attribute(const Mesh &mesh, } MeshAttributeInterpolator::MeshAttributeInterpolator(const Mesh *mesh, + const IndexMask mask, const Span<float3> positions, const Span<int> looptri_indices) - : mesh_(mesh), positions_(positions), looptri_indices_(looptri_indices) + : mesh_(mesh), mask_(mask), positions_(positions), looptri_indices_(looptri_indices) { BLI_assert(positions.size() == looptri_indices.size()); } @@ -168,15 +164,15 @@ MeshAttributeInterpolator::MeshAttributeInterpolator(const Mesh *mesh, Span<float3> MeshAttributeInterpolator::ensure_barycentric_coords() { if (!bary_coords_.is_empty()) { - BLI_assert(bary_coords_.size() == positions_.size()); + BLI_assert(bary_coords_.size() >= mask_.min_array_size()); return bary_coords_; } - bary_coords_.reinitialize(positions_.size()); + bary_coords_.reinitialize(mask_.min_array_size()); const Span<MLoopTri> looptris{BKE_mesh_runtime_looptri_ensure(mesh_), BKE_mesh_runtime_looptri_len(mesh_)}; - for (const int i : bary_coords_.index_range()) { + for (const int i : mask_) { const int looptri_index = looptri_indices_[i]; const MLoopTri &looptri = looptris[looptri_index]; @@ -196,15 +192,15 @@ Span<float3> MeshAttributeInterpolator::ensure_barycentric_coords() Span<float3> MeshAttributeInterpolator::ensure_nearest_weights() { if (!nearest_weights_.is_empty()) { - BLI_assert(nearest_weights_.size() == positions_.size()); + BLI_assert(nearest_weights_.size() >= mask_.min_array_size()); return nearest_weights_; } - nearest_weights_.reinitialize(positions_.size()); + nearest_weights_.reinitialize(mask_.min_array_size()); const Span<MLoopTri> looptris{BKE_mesh_runtime_looptri_ensure(mesh_), BKE_mesh_runtime_looptri_len(mesh_)}; - for (const int i : nearest_weights_.index_range()) { + for (const int i : mask_) { const int looptri_index = looptri_indices_[i]; const MLoopTri &looptri = looptris[looptri_index]; @@ -224,7 +220,6 @@ Span<float3> MeshAttributeInterpolator::ensure_nearest_weights() void MeshAttributeInterpolator::sample_data(const GVArray &src, const AttributeDomain domain, const eAttributeMapMode mode, - const IndexMask mask, const GMutableSpan dst) { if (src.is_empty() || dst.is_empty()) { @@ -247,15 +242,15 @@ void MeshAttributeInterpolator::sample_data(const GVArray &src, /* Interpolate the source attributes on the surface. */ switch (domain) { case ATTR_DOMAIN_POINT: { - sample_point_attribute(*mesh_, looptri_indices_, weights, src, mask, dst); + sample_point_attribute(*mesh_, looptri_indices_, weights, src, mask_, dst); break; } case ATTR_DOMAIN_FACE: { - sample_face_attribute(*mesh_, looptri_indices_, src, mask, dst); + sample_face_attribute(*mesh_, looptri_indices_, src, mask_, dst); break; } case ATTR_DOMAIN_CORNER: { - sample_corner_attribute(*mesh_, looptri_indices_, weights, src, mask, dst); + sample_corner_attribute(*mesh_, looptri_indices_, weights, src, mask_, dst); break; } case ATTR_DOMAIN_EDGE: { @@ -274,11 +269,7 @@ void MeshAttributeInterpolator::sample_attribute(const ReadAttributeLookup &src_ eAttributeMapMode mode) { if (src_attribute && dst_attribute) { - this->sample_data(*src_attribute.varray, - src_attribute.domain, - mode, - IndexMask(dst_attribute->size()), - dst_attribute.as_span()); + this->sample_data(*src_attribute.varray, src_attribute.domain, mode, dst_attribute.as_span()); } } diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index c5fb9030847..ce8f941b0b6 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -5751,6 +5751,7 @@ static void registerGeometryNodes() register_node_type_geo_legacy_curve_subdivide(); register_node_type_geo_legacy_edge_split(); register_node_type_geo_legacy_subdivision_surface(); + register_node_type_geo_legacy_raycast(); register_node_type_geo_align_rotation_to_vector(); register_node_type_geo_attribute_capture(); @@ -5814,7 +5815,6 @@ static void registerGeometryNodes() register_node_type_geo_instance_on_points(); register_node_type_geo_is_viewport(); register_node_type_geo_join_geometry(); - register_node_type_geo_material_replace(); register_node_type_geo_material_selection(); register_node_type_geo_mesh_primitive_circle(); register_node_type_geo_mesh_primitive_cone(); diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index dd749a9dc60..e6d02923dfe 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -1526,9 +1526,13 @@ typedef struct NodeGeometryRaycast { /* GeometryNodeRaycastMapMode. */ uint8_t mapping; + /* CustomDataType. */ + int8_t data_type; + + /* Deprecated input types in new Raycast node. Can be removed when legacy nodes are no longer + * supported. */ uint8_t input_type_ray_direction; uint8_t input_type_ray_length; - char _pad[1]; } NodeGeometryRaycast; typedef struct NodeGeometryCurveFill { diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index 9571d889f6d..dfefc774b3d 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -10716,7 +10716,7 @@ static void def_geo_input_material(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } -static void def_geo_raycast(StructRNA *srna) +static void def_geo_legacy_raycast(StructRNA *srna) { static EnumPropertyItem mapping_items[] = { {GEO_NODE_RAYCAST_INTERPOLATED, @@ -10752,6 +10752,39 @@ static void def_geo_raycast(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } +static void def_geo_raycast(StructRNA *srna) +{ + static EnumPropertyItem mapping_items[] = { + {GEO_NODE_RAYCAST_INTERPOLATED, + "INTERPOLATED", + 0, + "Interpolated", + "Interpolate the attribute from the corners of the hit face"}, + {GEO_NODE_RAYCAST_NEAREST, + "NEAREST", + 0, + "Nearest", + "Use the attribute value of the closest mesh element"}, + {0, NULL, 0, NULL, NULL}, + }; + + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeGeometryRaycast", "storage"); + + prop = RNA_def_property(srna, "mapping", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, mapping_items); + RNA_def_property_ui_text(prop, "Mapping", "Mapping from the target geometry to hit points"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + + prop = RNA_def_property(srna, "data_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_enum_attribute_type_items); + RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_GeometryNodeAttributeFill_type_itemf"); + RNA_def_property_enum_default(prop, CD_PROP_FLOAT); + RNA_def_property_ui_text(prop, "Data Type", "Type of data stored in attribute"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_GeometryNode_socket_update"); +} + static void def_geo_curve_fill(StructRNA *srna) { static const EnumPropertyItem mode_items[] = { diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index 35cd1c8c5a9..4d006342e72 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -255,6 +255,7 @@ set(SRC geometry/nodes/node_geo_points_to_vertices.cc geometry/nodes/node_geo_points_to_volume.cc geometry/nodes/node_geo_proximity.cc + geometry/nodes/node_geo_raycast.cc geometry/nodes/node_geo_realize_instances.cc geometry/nodes/node_geo_rotate_instances.cc geometry/nodes/node_geo_scale_instances.cc diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index 3ca8c0d02d7..4d75303363c 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -45,6 +45,7 @@ void register_node_type_geo_legacy_select_by_handle_type(void); void register_node_type_geo_legacy_curve_subdivide(void); void register_node_type_geo_legacy_edge_split(void); void register_node_type_geo_legacy_subdivision_surface(void); +void register_node_type_geo_legacy_raycast(void); void register_node_type_geo_align_rotation_to_vector(void); void register_node_type_geo_attribute_capture(void); diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 9a361c3a3f1..c20a9545968 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -315,7 +315,7 @@ DefNode(GeometryNode, GEO_NODE_LEGACY_POINT_SCALE, def_geo_point_scale, "LEGACY_ DefNode(GeometryNode, GEO_NODE_LEGACY_POINT_SEPARATE, 0, "LEGACY_POINT_SEPARATE", LegacyPointSeparate, "Point Separate", "") DefNode(GeometryNode, GEO_NODE_LEGACY_POINT_TRANSLATE, def_geo_point_translate, "LEGACY_POINT_TRANSLATE", LegacyPointTranslate, "Point Translate", "") DefNode(GeometryNode, GEO_NODE_LEGACY_POINTS_TO_VOLUME, def_geo_legacy_points_to_volume, "LEGACY_POINTS_TO_VOLUME", LegacyPointsToVolume, "Points to Volume", "") -DefNode(GeometryNode, GEO_NODE_LEGACY_RAYCAST, def_geo_raycast, "LEGACY_RAYCAST", LegacyRaycast, "Raycast", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_RAYCAST, def_geo_legacy_raycast, "LEGACY_RAYCAST", LegacyRaycast, "Raycast", "") DefNode(GeometryNode, GEO_NODE_LEGACY_SELECT_BY_MATERIAL, 0, "LEGACY_SELECT_BY_MATERIAL", LegacySelectByMaterial, "Select by Material", "") DefNode(GeometryNode, GEO_NODE_LEGACY_SUBDIVISION_SURFACE, def_geo_subdivision_surface, "LEGACY_SUBDIVISION_SURFACE", LegacySubdivisionSurface, "Subdivision Surface", "") @@ -383,6 +383,7 @@ DefNode(GeometryNode, GEO_NODE_OBJECT_INFO, def_geo_object_info, "OBJECT_INFO", DefNode(GeometryNode, GEO_NODE_POINTS_TO_VERTICES, 0, "POINTS_TO_VERTICES", PointsToVertices, "Points to Vertices", "") DefNode(GeometryNode, GEO_NODE_POINTS_TO_VOLUME, def_geo_points_to_volume, "POINTS_TO_VOLUME", PointsToVolume, "Points to Volume", "") DefNode(GeometryNode, GEO_NODE_PROXIMITY, def_geo_proximity, "PROXIMITY", Proximity, "Geometry Proximity", "") +DefNode(GeometryNode, GEO_NODE_RAYCAST, def_geo_raycast, "RAYCAST", Raycast, "Raycast", "") DefNode(GeometryNode, GEO_NODE_REALIZE_INSTANCES, 0, "REALIZE_INSTANCES", RealizeInstances, "Realize Instances", "") DefNode(GeometryNode, GEO_NODE_ROTATE_INSTANCES, 0, "ROTATE_INSTANCES", RotateInstances, "Rotate Instances", "") DefNode(GeometryNode, GEO_NODE_SCALE_INSTANCES, 0, "SCALE_INSTANCES", ScaleInstances, "Scale Instances", "") diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_transfer.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_transfer.cc index 6bd39bc9145..d7a66dac3ad 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_transfer.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_transfer.cc @@ -284,7 +284,8 @@ static void transfer_attribute_nearest_face_interpolated(const GeometrySet &src_ Array<float3> positions(tot_samples); get_closest_mesh_looptris(*mesh, dst_positions, looptri_indices, {}, positions); - bke::mesh_surface_sample::MeshAttributeInterpolator interp(mesh, positions, looptri_indices); + bke::mesh_surface_sample::MeshAttributeInterpolator interp( + mesh, IndexMask(tot_samples), positions, looptri_indices); interp.sample_attribute( src_attribute, dst_attribute, bke::mesh_surface_sample::eAttributeMapMode::INTERPOLATED); diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_raycast.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_raycast.cc index 401a478f04c..6641e622362 100644 --- a/source/blender/nodes/geometry/nodes/legacy/node_geo_raycast.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_raycast.cc @@ -250,7 +250,8 @@ static void raycast_from_points(const GeoNodeExecParams ¶ms, hit_distance_attribute.save(); /* Custom interpolated attributes */ - bke::mesh_surface_sample::MeshAttributeInterpolator interp(src_mesh, hit_positions, hit_indices); + bke::mesh_surface_sample::MeshAttributeInterpolator interp( + src_mesh, IndexMask(ray_origins.size()), hit_positions, hit_indices); for (const int i : hit_attribute_names.index_range()) { const std::optional<AttributeMetaData> meta_data = src_mesh_component->attribute_get_meta_data( hit_attribute_names[i]); @@ -304,7 +305,7 @@ static void geo_node_raycast_exec(GeoNodeExecParams params) } // namespace blender::nodes -void register_node_type_geo_raycast() +void register_node_type_geo_legacy_raycast() { static bNodeType ntype; diff --git a/source/blender/nodes/geometry/nodes/node_geo_raycast.cc b/source/blender/nodes/geometry/nodes/node_geo_raycast.cc new file mode 100644 index 00000000000..e5ed5c02090 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_raycast.cc @@ -0,0 +1,456 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "DNA_mesh_types.h" + +#include "BKE_attribute_math.hh" +#include "BKE_bvhutils.h" +#include "BKE_mesh_sample.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +using namespace blender::bke::mesh_surface_sample; + +namespace blender::nodes { + +static void geo_node_raycast_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Target Geometry"); + + b.add_input<decl::Vector>("Attribute").hide_value().supports_field(); + b.add_input<decl::Float>("Attribute", "Attribute_001").hide_value().supports_field(); + b.add_input<decl::Color>("Attribute", "Attribute_002").hide_value().supports_field(); + b.add_input<decl::Bool>("Attribute", "Attribute_003").hide_value().supports_field(); + b.add_input<decl::Int>("Attribute", "Attribute_004").hide_value().supports_field(); + + b.add_input<decl::Vector>("Source Position").implicit_field(); + b.add_input<decl::Vector>("Ray Direction").default_value({0.0f, 0.0f, 1.0f}).supports_field(); + b.add_input<decl::Float>("Ray Length") + .default_value(100.0f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .supports_field(); + + b.add_output<decl::Bool>("Is Hit").dependent_field(); + b.add_output<decl::Vector>("Hit Position").dependent_field(); + b.add_output<decl::Vector>("Hit Normal").dependent_field(); + b.add_output<decl::Float>("Hit Distance").dependent_field(); + + b.add_output<decl::Vector>("Attribute").dependent_field({1, 2, 3, 4, 5, 6}); + b.add_output<decl::Float>("Attribute", "Attribute_001").dependent_field({1, 2, 3, 4, 5, 6}); + b.add_output<decl::Color>("Attribute", "Attribute_002").dependent_field({1, 2, 3, 4, 5, 6}); + b.add_output<decl::Bool>("Attribute", "Attribute_003").dependent_field({1, 2, 3, 4, 5, 6}); + b.add_output<decl::Int>("Attribute", "Attribute_004").dependent_field({1, 2, 3, 4, 5, 6}); +} + +static void geo_node_raycast_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); + uiItemR(layout, ptr, "mapping", 0, "", ICON_NONE); +} + +static void geo_node_raycast_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryRaycast *data = (NodeGeometryRaycast *)MEM_callocN(sizeof(NodeGeometryRaycast), + __func__); + data->mapping = GEO_NODE_RAYCAST_INTERPOLATED; + data->data_type = CD_PROP_FLOAT; + node->storage = data; +} + +static void geo_node_raycast_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + const NodeGeometryRaycast &data = *(const NodeGeometryRaycast *)node->storage; + const CustomDataType data_type = static_cast<CustomDataType>(data.data_type); + + bNodeSocket *socket_vector = (bNodeSocket *)BLI_findlink(&node->inputs, 1); + bNodeSocket *socket_float = socket_vector->next; + bNodeSocket *socket_color4f = socket_float->next; + bNodeSocket *socket_boolean = socket_color4f->next; + bNodeSocket *socket_int32 = socket_boolean->next; + + nodeSetSocketAvailability(socket_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(socket_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(socket_color4f, data_type == CD_PROP_COLOR); + nodeSetSocketAvailability(socket_boolean, data_type == CD_PROP_BOOL); + nodeSetSocketAvailability(socket_int32, data_type == CD_PROP_INT32); + + bNodeSocket *out_socket_vector = (bNodeSocket *)BLI_findlink(&node->outputs, 4); + bNodeSocket *out_socket_float = out_socket_vector->next; + bNodeSocket *out_socket_color4f = out_socket_float->next; + bNodeSocket *out_socket_boolean = out_socket_color4f->next; + bNodeSocket *out_socket_int32 = out_socket_boolean->next; + + nodeSetSocketAvailability(out_socket_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(out_socket_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(out_socket_color4f, data_type == CD_PROP_COLOR); + nodeSetSocketAvailability(out_socket_boolean, data_type == CD_PROP_BOOL); + nodeSetSocketAvailability(out_socket_int32, data_type == CD_PROP_INT32); +} + +static eAttributeMapMode get_map_mode(GeometryNodeRaycastMapMode map_mode) +{ + switch (map_mode) { + case GEO_NODE_RAYCAST_INTERPOLATED: + return eAttributeMapMode::INTERPOLATED; + default: + case GEO_NODE_RAYCAST_NEAREST: + return eAttributeMapMode::NEAREST; + } +} + +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) +{ + BVHTreeFromMesh tree_data; + BKE_bvhtree_from_mesh_get(&tree_data, &mesh, BVHTREE_FROM_LOOPTRI, 4); + if (tree_data.tree == nullptr) { + free_bvhtree_from_mesh(&tree_data); + return; + } + + for (const int i : mask) { + const float ray_length = ray_lengths[i]; + const float3 ray_origin = ray_origins[i]; + const float3 ray_direction = ray_directions[i].normalized(); + + BVHTreeRayHit hit; + hit.index = -1; + hit.dist = ray_length; + if (BLI_bvhtree_ray_cast(tree_data.tree, + ray_origin, + ray_direction, + 0.0f, + &hit, + tree_data.raycast_callback, + &tree_data) != -1) { + hit_count++; + if (!r_hit.is_empty()) { + r_hit[i] = hit.index >= 0; + } + if (!r_hit_indices.is_empty()) { + /* The caller must be able to handle invalid indices anyway, so don't clamp this value. */ + r_hit_indices[i] = hit.index; + } + if (!r_hit_positions.is_empty()) { + r_hit_positions[i] = hit.co; + } + if (!r_hit_normals.is_empty()) { + r_hit_normals[i] = hit.no; + } + if (!r_hit_distances.is_empty()) { + r_hit_distances[i] = hit.dist; + } + } + else { + if (!r_hit.is_empty()) { + r_hit[i] = false; + } + if (!r_hit_indices.is_empty()) { + r_hit_indices[i] = -1; + } + if (!r_hit_positions.is_empty()) { + r_hit_positions[i] = float3(0.0f, 0.0f, 0.0f); + } + if (!r_hit_normals.is_empty()) { + r_hit_normals[i] = float3(0.0f, 0.0f, 0.0f); + } + if (!r_hit_distances.is_empty()) { + r_hit_distances[i] = ray_length; + } + } + } + + /* We shouldn't be rebuilding the BVH tree when calling this function in parallel. */ + BLI_assert(tree_data.cached); + free_bvhtree_from_mesh(&tree_data); +} + +class RaycastFunction : public fn::MultiFunction { + private: + GeometrySet target_; + GeometryNodeRaycastMapMode mapping_; + + /** The field for data evaluated on the target geometry. */ + std::optional<GeometryComponentFieldContext> target_context_; + std::unique_ptr<FieldEvaluator> target_evaluator_; + const GVArray *target_data_ = nullptr; + + /* Always evaluate the target domain data on the point domain. Eventually this could be + * exposed as an option or determined automatically from the field inputs in order to avoid + * losing information if the target field is on a different domain. */ + const AttributeDomain domain_ = ATTR_DOMAIN_POINT; + + fn::MFSignature signature_; + + public: + RaycastFunction(GeometrySet target, GField src_field, GeometryNodeRaycastMapMode mapping) + : target_(std::move(target)), mapping_((GeometryNodeRaycastMapMode)mapping) + { + target_.ensure_owns_direct_data(); + this->evaluate_target_field(std::move(src_field)); + signature_ = create_signature(); + this->set_signature(&signature_); + } + + fn::MFSignature create_signature() + { + blender::fn::MFSignatureBuilder signature{"Geometry Proximity"}; + signature.single_input<float3>("Source Position"); + signature.single_input<float3>("Ray Direction"); + signature.single_input<float>("Ray Length"); + signature.single_output<bool>("Is Hit"); + signature.single_output<float3>("Hit Position"); + signature.single_output<float3>("Hit Normal"); + signature.single_output<float>("Distance"); + if (target_data_) { + signature.single_output("Attribute", target_data_->type()); + } + return signature.build(); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + /* Hit positions are always necessary for retrieving the attribute from the target if that + * output is required, so always retrieve a span from the evaluator in that case (it's + * expected that the evaluator is more likely to have a spare buffer that could be used). */ + MutableSpan<float3> hit_positions = + (target_data_) ? params.uninitialized_single_output<float3>(4, "Hit Position") : + params.uninitialized_single_output_if_required<float3>(4, "Hit Position"); + + Array<int> hit_indices; + if (target_data_) { + hit_indices.reinitialize(mask.min_array_size()); + } + + BLI_assert(target_.has_mesh()); + const Mesh &mesh = *target_.get_mesh_for_read(); + + int hit_count = 0; + raycast_to_mesh(mask, + mesh, + params.readonly_single_input<float3>(0, "Source Position"), + params.readonly_single_input<float3>(1, "Ray Direction"), + params.readonly_single_input<float>(2, "Ray Length"), + params.uninitialized_single_output_if_required<bool>(3, "Is Hit"), + hit_indices, + hit_positions, + params.uninitialized_single_output_if_required<float3>(5, "Hit Normal"), + params.uninitialized_single_output_if_required<float>(6, "Distance"), + hit_count); + + IndexMask hit_mask; + Vector<int64_t> hit_mask_indices; + if (hit_count < mask.size()) { + /* Not all rays hit the target. Create a corrected mask to avoid transferring attribute data + * to invalid indices. An alternative would be handling -1 indices in a separate case in + * #MeshAttributeInterpolator, but since it already has an IndexMask in its constructor, it's + * simpler to use that. */ + hit_mask_indices.reserve(hit_count); + for (const int64_t i : mask) { + if (hit_indices[i] != -1) { + hit_mask_indices.append(i); + } + hit_mask = IndexMask(hit_mask_indices); + } + } + else { + hit_mask = mask; + } + + if (target_data_) { + GMutableSpan result = params.uninitialized_single_output_if_required(7, "Attribute"); + if (!result.is_empty()) { + MeshAttributeInterpolator interp(&mesh, hit_mask, hit_positions, hit_indices); + result.type().fill_assign_indices(result.type().default_value(), result.data(), mask); + interp.sample_data(*target_data_, domain_, get_map_mode(mapping_), result); + } + } + } + + private: + void evaluate_target_field(GField src_field) + { + if (!src_field) { + return; + } + const MeshComponent &mesh_component = *target_.get_component_for_read<MeshComponent>(); + target_context_.emplace(GeometryComponentFieldContext{mesh_component, domain_}); + const int domain_size = mesh_component.attribute_domain_size(domain_); + target_evaluator_ = std::make_unique<FieldEvaluator>(*target_context_, domain_size); + target_evaluator_->add(std::move(src_field)); + target_evaluator_->evaluate(); + target_data_ = &target_evaluator_->get_evaluated(0); + } +}; + +static GField get_input_attribute_field(GeoNodeExecParams ¶ms, const CustomDataType data_type) +{ + switch (data_type) { + case CD_PROP_FLOAT: + if (params.output_is_required("Attribute_001")) { + return params.extract_input<Field<float>>("Attribute_001"); + } + break; + case CD_PROP_FLOAT3: + if (params.output_is_required("Attribute")) { + return params.extract_input<Field<float3>>("Attribute"); + } + break; + case CD_PROP_COLOR: + if (params.output_is_required("Attribute_002")) { + return params.extract_input<Field<ColorGeometry4f>>("Attribute_002"); + } + break; + case CD_PROP_BOOL: + if (params.output_is_required("Attribute_003")) { + return params.extract_input<Field<bool>>("Attribute_003"); + } + break; + case CD_PROP_INT32: + if (params.output_is_required("Attribute_004")) { + return params.extract_input<Field<int>>("Attribute_004"); + } + break; + default: + BLI_assert_unreachable(); + } + return {}; +} + +static void output_attribute_field(GeoNodeExecParams ¶ms, GField field) +{ + switch (bke::cpp_type_to_custom_data_type(field.cpp_type())) { + case CD_PROP_FLOAT: { + params.set_output("Attribute_001", Field<float>(field)); + break; + } + case CD_PROP_FLOAT3: { + params.set_output("Attribute", Field<float3>(field)); + break; + } + case CD_PROP_COLOR: { + params.set_output("Attribute_002", Field<ColorGeometry4f>(field)); + break; + } + case CD_PROP_BOOL: { + params.set_output("Attribute_003", Field<bool>(field)); + break; + } + case CD_PROP_INT32: { + params.set_output("Attribute_004", Field<int>(field)); + break; + } + default: + break; + } +} + +static void geo_node_raycast_exec(GeoNodeExecParams params) +{ + GeometrySet target = params.extract_input<GeometrySet>("Target Geometry"); + const NodeGeometryRaycast &data = *(const NodeGeometryRaycast *)params.node().storage; + const GeometryNodeRaycastMapMode mapping = static_cast<GeometryNodeRaycastMapMode>(data.mapping); + const CustomDataType data_type = static_cast<CustomDataType>(data.data_type); + + auto return_default = [&]() { + params.set_output("Is Hit", fn::make_constant_field<bool>(false)); + params.set_output("Hit Position", fn::make_constant_field<float3>({0.0f, 0.0f, 0.0f})); + params.set_output("Hit Normal", fn::make_constant_field<float3>({0.0f, 0.0f, 0.0f})); + params.set_output("Hit Distance", fn::make_constant_field<float>(0.0f)); + attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + using T = decltype(dummy); + output_attribute_field(params, fn::make_constant_field<T>(T())); + }); + }; + + if (target.has_instances()) { + if (target.has_realized_data()) { + params.error_message_add( + NodeWarningType::Info, + TIP_("Only realized geometry is supported, instances will not be used")); + } + else { + params.error_message_add(NodeWarningType::Error, + TIP_("Target target must contain realized data")); + return return_default(); + } + } + + if (target.is_empty()) { + return return_default(); + } + + if (!target.has_mesh()) { + params.error_message_add(NodeWarningType::Error, + TIP_("The target geometry must contain a mesh")); + return return_default(); + } + + if (target.get_mesh_for_read()->totpoly == 0) { + params.error_message_add(NodeWarningType::Error, TIP_("The target mesh must have faces")); + return return_default(); + } + + GField field = get_input_attribute_field(params, data_type); + const bool do_attribute_transfer = bool(field); + Field<float3> position_field = params.extract_input<Field<float3>>("Source Position"); + Field<float3> direction_field = params.extract_input<Field<float3>>("Ray Direction"); + Field<float> length_field = params.extract_input<Field<float>>("Ray Length"); + + auto fn = std::make_unique<RaycastFunction>(std::move(target), std::move(field), mapping); + auto op = std::make_shared<FieldOperation>(FieldOperation( + std::move(fn), + {std::move(position_field), std::move(direction_field), std::move(length_field)})); + + params.set_output("Is Hit", Field<bool>(op, 0)); + params.set_output("Hit Position", Field<float3>(op, 1)); + params.set_output("Hit Normal", Field<float3>(op, 2)); + params.set_output("Hit Distance", Field<float>(op, 3)); + if (do_attribute_transfer) { + output_attribute_field(params, GField(op, 4)); + } +} + +} // namespace blender::nodes + +void register_node_type_geo_raycast() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_RAYCAST, "Raycast", NODE_CLASS_GEOMETRY, 0); + node_type_size_preset(&ntype, NODE_SIZE_MIDDLE); + node_type_init(&ntype, blender::nodes::geo_node_raycast_init); + node_type_update(&ntype, blender::nodes::geo_node_raycast_update); + node_type_storage( + &ntype, "NodeGeometryRaycast", node_free_standard_storage, node_copy_standard_storage); + ntype.declare = blender::nodes::geo_node_raycast_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_raycast_exec; + ntype.draw_buttons = blender::nodes::geo_node_raycast_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc b/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc index 63792304726..4bf856698d9 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc @@ -409,8 +409,8 @@ class NearestInterpolatedTransferFunction : public fn::MultiFunction { Array<float3> sampled_positions(mask.min_array_size()); get_closest_mesh_looptris(mesh, positions, mask, looptri_indices, {}, sampled_positions); - MeshAttributeInterpolator interp(&mesh, sampled_positions, looptri_indices); - interp.sample_data(*target_data_, domain_, eAttributeMapMode::INTERPOLATED, mask, dst); + MeshAttributeInterpolator interp(&mesh, mask, sampled_positions, looptri_indices); + interp.sample_data(*target_data_, domain_, eAttributeMapMode::INTERPOLATED, dst); } private: |