diff options
-rw-r--r-- | release/scripts/startup/nodeitems_builtins.py | 1 | ||||
-rw-r--r-- | source/blender/blenkernel/BKE_mesh_sample.hh | 42 | ||||
-rw-r--r-- | source/blender/blenkernel/BKE_node.h | 1 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/mesh_sample.cc | 119 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/node.cc | 1 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_node_types.h | 14 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_nodetree.c | 36 | ||||
-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 | 1 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc | 82 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_raycast.cc | 321 |
12 files changed, 545 insertions, 75 deletions
diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py index e27073bca9b..e7d991622e8 100644 --- a/release/scripts/startup/nodeitems_builtins.py +++ b/release/scripts/startup/nodeitems_builtins.py @@ -516,6 +516,7 @@ geometry_node_categories = [ NodeItem("GeometryNodeTransform"), NodeItem("GeometryNodeJoinGeometry"), NodeItem("GeometryNodeSeparateComponents"), + NodeItem("GeometryNodeRaycast"), ]), GeometryNodeCategory("GEO_INPUT", "Input", items=[ NodeItem("GeometryNodeObjectInfo"), diff --git a/source/blender/blenkernel/BKE_mesh_sample.hh b/source/blender/blenkernel/BKE_mesh_sample.hh index f504650e349..c40d2e947e3 100644 --- a/source/blender/blenkernel/BKE_mesh_sample.hh +++ b/source/blender/blenkernel/BKE_mesh_sample.hh @@ -28,6 +28,11 @@ struct Mesh; +namespace blender::bke { +struct ReadAttributeLookup; +class OutputAttribute; +} // namespace blender::bke + namespace blender::bke::mesh_surface_sample { using fn::CPPType; @@ -35,6 +40,8 @@ using fn::GMutableSpan; using fn::GSpan; using fn::GVArray; +Span<MLoopTri> get_mesh_looptris(const Mesh &mesh); + void sample_point_attribute(const Mesh &mesh, Span<int> looptri_indices, Span<float3> bary_coords, @@ -52,4 +59,39 @@ void sample_face_attribute(const Mesh &mesh, const GVArray &data_in, GMutableSpan data_out); +enum class eAttributeMapMode { + INTERPOLATED, + NEAREST, +}; + +/** + * A utility class that performs attribute interpolation from a source mesh. + * + * The interpolator is only valid as long as the mesh is valid. + * Barycentric weights are needed when interpolating point or corner domain attributes, + * these are computed lazily when needed and re-used. + */ +class MeshAttributeInterpolator { + private: + const Mesh *mesh_; + const Span<float3> positions_; + const Span<int> looptri_indices_; + + Array<float3> bary_coords_; + Array<float3> nearest_weights_; + + public: + MeshAttributeInterpolator(const Mesh *mesh, + const Span<float3> positions, + const Span<int> looptri_indices); + + void sample_attribute(const ReadAttributeLookup &src_attribute, + OutputAttribute &dst_attribute, + eAttributeMapMode mode); + + protected: + Span<float3> ensure_barycentric_coords(); + Span<float3> ensure_nearest_weights(); +}; + } // namespace blender::bke::mesh_surface_sample diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index c7ed12452a5..a0f6be6b3e9 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -1438,6 +1438,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_CURVE_REVERSE 1058 #define GEO_NODE_SEPARATE_COMPONENTS 1059 #define GEO_NODE_CURVE_SUBDIVIDE 1060 +#define GEO_NODE_RAYCAST 1061 /** \} */ diff --git a/source/blender/blenkernel/intern/mesh_sample.cc b/source/blender/blenkernel/intern/mesh_sample.cc index 91c9951ae89..7bc73762506 100644 --- a/source/blender/blenkernel/intern/mesh_sample.cc +++ b/source/blender/blenkernel/intern/mesh_sample.cc @@ -14,6 +14,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include "BKE_attribute_access.hh" #include "BKE_attribute_math.hh" #include "BKE_mesh_runtime.h" #include "BKE_mesh_sample.hh" @@ -23,7 +24,7 @@ namespace blender::bke::mesh_surface_sample { -static Span<MLoopTri> get_mesh_looptris(const Mesh &mesh) +Span<MLoopTri> get_mesh_looptris(const Mesh &mesh) { /* This only updates a cache and can be considered to be logically const. */ const MLoopTri *looptris = BKE_mesh_runtime_looptri_ensure(const_cast<Mesh *>(&mesh)); @@ -155,4 +156,120 @@ void sample_face_attribute(const Mesh &mesh, }); } +MeshAttributeInterpolator::MeshAttributeInterpolator(const Mesh *mesh, + const Span<float3> positions, + const Span<int> looptri_indices) + : mesh_(mesh), positions_(positions), looptri_indices_(looptri_indices) +{ + BLI_assert(positions.size() == looptri_indices.size()); +} + +Span<float3> MeshAttributeInterpolator::ensure_barycentric_coords() +{ + if (!bary_coords_.is_empty()) { + BLI_assert(bary_coords_.size() == positions_.size()); + return bary_coords_; + } + bary_coords_.reinitialize(positions_.size()); + + Span<MLoopTri> looptris = get_mesh_looptris(*mesh_); + + for (const int i : bary_coords_.index_range()) { + const int looptri_index = looptri_indices_[i]; + const MLoopTri &looptri = looptris[looptri_index]; + + const int v0_index = mesh_->mloop[looptri.tri[0]].v; + const int v1_index = mesh_->mloop[looptri.tri[1]].v; + const int v2_index = mesh_->mloop[looptri.tri[2]].v; + + interp_weights_tri_v3(bary_coords_[i], + mesh_->mvert[v0_index].co, + mesh_->mvert[v1_index].co, + mesh_->mvert[v2_index].co, + positions_[i]); + } + return bary_coords_; +} + +Span<float3> MeshAttributeInterpolator::ensure_nearest_weights() +{ + if (!nearest_weights_.is_empty()) { + BLI_assert(nearest_weights_.size() == positions_.size()); + return nearest_weights_; + } + nearest_weights_.reinitialize(positions_.size()); + + Span<MLoopTri> looptris = get_mesh_looptris(*mesh_); + + for (const int i : nearest_weights_.index_range()) { + const int looptri_index = looptri_indices_[i]; + const MLoopTri &looptri = looptris[looptri_index]; + + const int v0_index = mesh_->mloop[looptri.tri[0]].v; + const int v1_index = mesh_->mloop[looptri.tri[1]].v; + const int v2_index = mesh_->mloop[looptri.tri[2]].v; + + const float d0 = len_squared_v3v3(positions_[i], mesh_->mvert[v0_index].co); + const float d1 = len_squared_v3v3(positions_[i], mesh_->mvert[v1_index].co); + const float d2 = len_squared_v3v3(positions_[i], mesh_->mvert[v2_index].co); + + nearest_weights_[i] = MIN3_PAIR(d0, d1, d2, float3(1, 0, 0), float3(0, 1, 0), float3(0, 0, 1)); + } + return nearest_weights_; +} + +void MeshAttributeInterpolator::sample_attribute(const ReadAttributeLookup &src_attribute, + OutputAttribute &dst_attribute, + eAttributeMapMode mode) +{ + if (!src_attribute || !dst_attribute) { + return; + } + const GVArray &src_varray = *src_attribute.varray; + GMutableSpan dst_span = dst_attribute.as_span(); + if (src_varray.is_empty() || dst_span.is_empty()) { + return; + } + + /* Compute barycentric coordinates only when they are needed. */ + Span<float3> weights; + if (ELEM(src_attribute.domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CORNER)) { + switch (mode) { + case eAttributeMapMode::INTERPOLATED: + weights = ensure_barycentric_coords(); + break; + case eAttributeMapMode::NEAREST: + weights = ensure_nearest_weights(); + break; + } + } + + /* Interpolate the source attributes on the surface. */ + switch (src_attribute.domain) { + case ATTR_DOMAIN_POINT: { + sample_point_attribute( + *mesh_, looptri_indices_, weights, src_varray, dst_span); + break; + } + case ATTR_DOMAIN_FACE: { + sample_face_attribute( + *mesh_, looptri_indices_, src_varray, dst_span); + break; + } + case ATTR_DOMAIN_CORNER: { + sample_corner_attribute( + *mesh_, looptri_indices_, weights, src_varray, dst_span); + break; + } + case ATTR_DOMAIN_EDGE: { + /* Not yet supported. */ + break; + } + default: { + BLI_assert_unreachable(); + break; + } + } +} + } // namespace blender::bke::mesh_surface_sample diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 3e89be37d2f..db18cecb5d3 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -5083,6 +5083,7 @@ static void registerGeometryNodes() register_node_type_geo_point_separate(); register_node_type_geo_point_translate(); register_node_type_geo_points_to_volume(); + register_node_type_geo_raycast(); register_node_type_geo_sample_texture(); register_node_type_geo_select_by_material(); register_node_type_geo_separate_components(); diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 2a5b59f03b1..12625d3408d 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -1379,6 +1379,15 @@ typedef struct NodeGeometryAttributeTransfer { uint8_t mapping; } NodeGeometryAttributeTransfer; +typedef struct NodeGeometryRaycast { + /* GeometryNodeRaycastMapMode. */ + uint8_t mapping; + + uint8_t input_type_ray_direction; + uint8_t input_type_ray_length; + char _pad[1]; +} NodeGeometryRaycast; + /* script node mode */ #define NODE_SCRIPT_INTERNAL 0 #define NODE_SCRIPT_EXTERNAL 1 @@ -1891,6 +1900,11 @@ typedef enum GeometryNodeAttributeTransferMapMode { GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST = 1, } GeometryNodeAttributeTransferMapMode; +typedef enum GeometryNodeRaycastMapMode { + GEO_NODE_RAYCAST_INTERPOLATED = 0, + GEO_NODE_RAYCAST_NEAREST = 1, +} GeometryNodeRaycastMapMode; + #ifdef __cplusplus } #endif diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index b96400cda91..c26e9e883d6 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -9950,6 +9950,42 @@ 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 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, "input_type_ray_direction", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_node_geometry_attribute_input_type_items_vector); + RNA_def_property_ui_text(prop, "Input Type Ray Direction", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); + + prop = RNA_def_property(srna, "input_type_ray_length", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_node_geometry_attribute_input_type_items_float); + RNA_def_property_ui_text(prop, "Input Type Ray Length", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); +} + /* -------------------------------------------------------------------------- */ static void rna_def_shader_node(BlenderRNA *brna) diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index d5ed8317bbb..39f26737620 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -193,6 +193,7 @@ set(SRC geometry/nodes/node_geo_point_separate.cc geometry/nodes/node_geo_point_translate.cc geometry/nodes/node_geo_points_to_volume.cc + geometry/nodes/node_geo_raycast.cc geometry/nodes/node_geo_select_by_material.cc geometry/nodes/node_geo_separate_components.cc geometry/nodes/node_geo_subdivide.cc diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index 5bbdf57f1fa..fddaaf6e640 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -81,6 +81,7 @@ void register_node_type_geo_point_scale(void); void register_node_type_geo_point_separate(void); void register_node_type_geo_point_translate(void); void register_node_type_geo_points_to_volume(void); +void register_node_type_geo_raycast(void); void register_node_type_geo_sample_texture(void); void register_node_type_geo_select_by_material(void); void register_node_type_geo_separate_components(void); diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 5e66b72cadb..d79fa7bffb3 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -320,6 +320,7 @@ DefNode(GeometryNode, GEO_NODE_POINT_SCALE, def_geo_point_scale, "POINT_SCALE", DefNode(GeometryNode, GEO_NODE_POINT_SEPARATE, 0, "POINT_SEPARATE", PointSeparate, "Point Separate", "") DefNode(GeometryNode, GEO_NODE_POINT_TRANSLATE, def_geo_point_translate, "POINT_TRANSLATE", PointTranslate, "Point Translate", "") DefNode(GeometryNode, GEO_NODE_POINTS_TO_VOLUME, def_geo_points_to_volume, "POINTS_TO_VOLUME", PointsToVolume, "Points to Volume", "") +DefNode(GeometryNode, GEO_NODE_RAYCAST, def_geo_raycast, "RAYCAST", Raycast, "Raycast", "") DefNode(GeometryNode, GEO_NODE_SELECT_BY_MATERIAL, 0, "SELECT_BY_MATERIAL", SelectByMaterial, "Select by Material", "") DefNode(GeometryNode, GEO_NODE_SEPARATE_COMPONENTS, 0, "SEPARATE_COMPONENTS", SeparateComponents, "Separate Components", "") DefNode(GeometryNode, GEO_NODE_SUBDIVIDE, 0, "SUBDIVIDE", Subdivide, "Subdivide", "") diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc index 4b677dc5c82..d1114713672 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc @@ -102,14 +102,6 @@ static void get_result_domain_and_data_type(const GeometrySet &src_geometry, } } -static Span<MLoopTri> get_mesh_looptris(const Mesh &mesh) -{ - /* This only updates a cache and can be considered to be logically const. */ - const MLoopTri *looptris = BKE_mesh_runtime_looptri_ensure(const_cast<Mesh *>(&mesh)); - const int looptris_len = BKE_mesh_runtime_looptri_len(&mesh); - return {looptris, looptris_len}; -} - static void get_closest_in_bvhtree(BVHTreeFromMesh &tree_data, const VArray<float3> &positions, const MutableSpan<int> r_indices, @@ -212,7 +204,7 @@ static void get_closest_mesh_polygons(const Mesh &mesh, Array<int> looptri_indices(positions.size()); get_closest_mesh_looptris(mesh, positions, looptri_indices, r_distances_sq, r_positions); - Span<MLoopTri> looptris = get_mesh_looptris(mesh); + Span<MLoopTri> looptris = bke::mesh_surface_sample::get_mesh_looptris(mesh); for (const int i : positions.index_range()) { const MLoopTri &looptri = looptris[looptri_indices[i]]; r_poly_indices[i] = looptri.poly; @@ -262,32 +254,6 @@ static void get_closest_mesh_corners(const Mesh &mesh, } } -static void get_barycentric_coords(const Mesh &mesh, - const Span<int> looptri_indices, - const Span<float3> positions, - const MutableSpan<float3> r_bary_coords) -{ - BLI_assert(r_bary_coords.size() == positions.size()); - BLI_assert(r_bary_coords.size() == looptri_indices.size()); - - Span<MLoopTri> looptris = get_mesh_looptris(mesh); - - for (const int i : r_bary_coords.index_range()) { - const int looptri_index = looptri_indices[i]; - const MLoopTri &looptri = looptris[looptri_index]; - - const int v0_index = mesh.mloop[looptri.tri[0]].v; - const int v1_index = mesh.mloop[looptri.tri[1]].v; - const int v2_index = mesh.mloop[looptri.tri[2]].v; - - interp_weights_tri_v3(r_bary_coords[i], - mesh.mvert[v0_index].co, - mesh.mvert[v1_index].co, - mesh.mvert[v2_index].co, - positions[i]); - } -} - static void transfer_attribute_nearest_face_interpolated(const GeometrySet &src_geometry, GeometryComponent &dst_component, const VArray<float3> &dst_positions, @@ -308,8 +274,11 @@ static void transfer_attribute_nearest_face_interpolated(const GeometrySet &src_ if (mesh->totpoly == 0) { return; } + ReadAttributeLookup src_attribute = component->attribute_try_get_for_read(src_name, data_type); - if (!src_attribute) { + OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( + dst_name, dst_domain, data_type); + if (!src_attribute || !dst_attribute) { return; } @@ -318,45 +287,10 @@ 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); - OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( - dst_name, dst_domain, data_type); - if (!dst_attribute) { - return; - } - GMutableSpan dst_span = dst_attribute.as_span(); - Array<float3> bary_coords; + bke::mesh_surface_sample::MeshAttributeInterpolator interp(mesh, positions, looptri_indices); + interp.sample_attribute( + src_attribute, dst_attribute, bke::mesh_surface_sample::eAttributeMapMode::INTERPOLATED); - /* Compute barycentric coordinates only when they are needed. */ - if (src_attribute.domain != ATTR_DOMAIN_FACE) { - bary_coords.reinitialize(tot_samples); - get_barycentric_coords(*mesh, looptri_indices, positions, bary_coords); - } - /* Interpolate the source attribute on the surface. */ - switch (src_attribute.domain) { - case ATTR_DOMAIN_POINT: { - bke::mesh_surface_sample::sample_point_attribute( - *mesh, looptri_indices, bary_coords, *src_attribute.varray, dst_span); - break; - } - case ATTR_DOMAIN_FACE: { - bke::mesh_surface_sample::sample_face_attribute( - *mesh, looptri_indices, *src_attribute.varray, dst_span); - break; - } - case ATTR_DOMAIN_CORNER: { - bke::mesh_surface_sample::sample_corner_attribute( - *mesh, looptri_indices, bary_coords, *src_attribute.varray, dst_span); - break; - } - case ATTR_DOMAIN_EDGE: { - /* Not yet supported. */ - break; - } - default: { - BLI_assert_unreachable(); - break; - } - } dst_attribute.save(); } 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..3a4492d24d6 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_raycast.cc @@ -0,0 +1,321 @@ +/* + * 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_bvhutils.h" +#include "BKE_mesh_sample.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_raycast_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_GEOMETRY, N_("Target Geometry")}, + {SOCK_STRING, N_("Ray Direction")}, + {SOCK_VECTOR, N_("Ray Direction"), 0.0, 0.0, 1.0, 0.0, -FLT_MAX, FLT_MAX}, + {SOCK_STRING, N_("Ray Length")}, + {SOCK_FLOAT, N_("Ray Length"), 100.0, 0.0, 0.0, 0.0, 0.0f, FLT_MAX, PROP_DISTANCE}, + {SOCK_STRING, N_("Target Attribute")}, + {SOCK_STRING, N_("Is Hit")}, + {SOCK_STRING, N_("Hit Position")}, + {SOCK_STRING, N_("Hit Normal")}, + {SOCK_STRING, N_("Hit Distance")}, + {SOCK_STRING, N_("Hit Attribute")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_raycast_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static void geo_node_raycast_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiItemR(layout, ptr, "mapping", 0, IFACE_("Mapping"), ICON_NONE); + uiItemR(layout, ptr, "input_type_ray_direction", 0, IFACE_("Ray Direction"), ICON_NONE); + uiItemR(layout, ptr, "input_type_ray_length", 0, IFACE_("Ray Length"), ICON_NONE); +} + +static void geo_node_raycast_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryRaycast *data = (NodeGeometryRaycast *)MEM_callocN(sizeof(NodeGeometryRaycast), + __func__); + data->input_type_ray_direction = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; + data->input_type_ray_length = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; + node->storage = data; +} + +static void geo_node_raycast_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryRaycast *node_storage = (NodeGeometryRaycast *)node->storage; + blender::nodes::update_attribute_input_socket_availabilities( + *node, + "Ray Direction", + (GeometryNodeAttributeInputMode)node_storage->input_type_ray_direction); + blender::nodes::update_attribute_input_socket_availabilities( + *node, "Ray Length", (GeometryNodeAttributeInputMode)node_storage->input_type_ray_length); +} + +namespace blender::nodes { + +static void raycast_to_mesh(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) +{ + BLI_assert(ray_origins.size() == ray_directions.size()); + BLI_assert(ray_origins.size() == ray_lengths.size()); + BLI_assert(ray_origins.size() == r_hit.size() || r_hit.is_empty()); + BLI_assert(ray_origins.size() == r_hit_indices.size() || r_hit_indices.is_empty()); + BLI_assert(ray_origins.size() == r_hit_positions.size() || r_hit_positions.is_empty()); + BLI_assert(ray_origins.size() == r_hit_normals.size() || r_hit_normals.is_empty()); + BLI_assert(ray_origins.size() == r_hit_distances.size() || r_hit_distances.is_empty()); + + BVHTreeFromMesh tree_data; + BKE_bvhtree_from_mesh_get(&tree_data, const_cast<Mesh *>(mesh), BVHTREE_FROM_LOOPTRI, 4); + + if (tree_data.tree != NULL) { + for (const int i : ray_origins.index_range()) { + 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) { + if (!r_hit.is_empty()) { + r_hit[i] = hit.index >= 0; + } + if (!r_hit_indices.is_empty()) { + /* Index should always be a valid looptri index, use 0 when hit failed. */ + r_hit_indices[i] = max_ii(hit.index, 0); + } + 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] = 0; + } + 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; + } + } + } + + free_bvhtree_from_mesh(&tree_data); + } +} + +static bke::mesh_surface_sample::eAttributeMapMode get_map_mode( + GeometryNodeRaycastMapMode map_mode) +{ + switch (map_mode) { + case GEO_NODE_RAYCAST_INTERPOLATED: + return bke::mesh_surface_sample::eAttributeMapMode::INTERPOLATED; + default: + case GEO_NODE_RAYCAST_NEAREST: + return bke::mesh_surface_sample::eAttributeMapMode::NEAREST; + } +} + +static void raycast_from_points(const GeoNodeExecParams ¶ms, + const GeometrySet &src_geometry, + GeometryComponent &dst_component, + const StringRef hit_name, + const StringRef hit_position_name, + const StringRef hit_normal_name, + const StringRef hit_distance_name, + const Span<std::string> hit_attribute_names, + const Span<std::string> hit_attribute_output_names) +{ + BLI_assert(hit_attribute_names.size() == hit_attribute_output_names.size()); + + const MeshComponent *src_mesh_component = src_geometry.get_component_for_read<MeshComponent>(); + if (src_mesh_component == nullptr) { + return; + } + const Mesh *src_mesh = src_mesh_component->get_for_read(); + if (src_mesh == nullptr) { + return; + } + if (src_mesh->totpoly == 0) { + return; + } + + const NodeGeometryRaycast &storage = *(const NodeGeometryRaycast *)params.node().storage; + bke::mesh_surface_sample::eAttributeMapMode map_mode = get_map_mode( + (GeometryNodeRaycastMapMode)storage.mapping); + const AttributeDomain result_domain = ATTR_DOMAIN_POINT; + + GVArray_Typed<float3> ray_origins = dst_component.attribute_get_for_read<float3>( + "position", result_domain, {0, 0, 0}); + GVArray_Typed<float3> ray_directions = params.get_input_attribute<float3>( + "Ray Direction", dst_component, result_domain, {0, 0, 0}); + GVArray_Typed<float> ray_lengths = params.get_input_attribute<float>( + "Ray Length", dst_component, result_domain, 0); + + OutputAttribute_Typed<bool> hit_attribute = + dst_component.attribute_try_get_for_output_only<bool>(hit_name, result_domain); + OutputAttribute_Typed<float3> hit_position_attribute = + dst_component.attribute_try_get_for_output_only<float3>(hit_position_name, result_domain); + OutputAttribute_Typed<float3> hit_normal_attribute = + dst_component.attribute_try_get_for_output_only<float3>(hit_normal_name, result_domain); + OutputAttribute_Typed<float> hit_distance_attribute = + dst_component.attribute_try_get_for_output_only<float>(hit_distance_name, result_domain); + + /* Positions and looptri indices are always needed for interpolation, + * so create temporary arrays if no output attribute is given. + */ + Array<int> hit_indices; + Array<float3> hit_positions_internal; + if (!hit_attribute_names.is_empty()) { + hit_indices.reinitialize(ray_origins->size()); + + if (!hit_position_attribute) { + hit_positions_internal.reinitialize(ray_origins->size()); + } + } + const MutableSpan<bool> is_hit = hit_attribute ? hit_attribute.as_span() : MutableSpan<bool>(); + const MutableSpan<float3> hit_positions = hit_position_attribute ? + hit_position_attribute.as_span() : + hit_positions_internal; + const MutableSpan<float3> hit_normals = hit_normal_attribute ? hit_normal_attribute.as_span() : + MutableSpan<float3>(); + const MutableSpan<float> hit_distances = hit_distance_attribute ? + hit_distance_attribute.as_span() : + MutableSpan<float>(); + + raycast_to_mesh(src_mesh, + ray_origins, + ray_directions, + ray_lengths, + is_hit, + hit_indices, + hit_positions, + hit_normals, + hit_distances); + + hit_attribute.save(); + hit_position_attribute.save(); + hit_normal_attribute.save(); + hit_distance_attribute.save(); + + /* Custom interpolated attributes */ + bke::mesh_surface_sample::MeshAttributeInterpolator interp(src_mesh, 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]); + if (meta_data) { + ReadAttributeLookup hit_attribute = src_mesh_component->attribute_try_get_for_read( + hit_attribute_names[i]); + OutputAttribute hit_attribute_output = dst_component.attribute_try_get_for_output_only( + hit_attribute_output_names[i], result_domain, meta_data->data_type); + + interp.sample_attribute(hit_attribute, hit_attribute_output, map_mode); + + hit_attribute_output.save(); + } + } +} + +static void geo_node_raycast_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + GeometrySet cast_geometry_set = params.extract_input<GeometrySet>("Target Geometry"); + + const std::string hit_name = params.extract_input<std::string>("Is Hit"); + const std::string hit_position_name = params.extract_input<std::string>("Hit Position"); + const std::string hit_normal_name = params.extract_input<std::string>("Hit Normal"); + const std::string hit_distance_name = params.extract_input<std::string>("Hit Distance"); + + const Array<std::string> hit_attribute_names = { + params.extract_input<std::string>("Target Attribute")}; + const Array<std::string> hit_attribute_output_names = { + params.extract_input<std::string>("Hit Attribute")}; + + geometry_set = bke::geometry_set_realize_instances(geometry_set); + cast_geometry_set = bke::geometry_set_realize_instances(cast_geometry_set); + + static const Array<GeometryComponentType> SupportedTypes = { + GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_CURVE}; + for (GeometryComponentType geo_type : SupportedTypes) { + if (geometry_set.has(geo_type)) { + raycast_from_points(params, + cast_geometry_set, + geometry_set.get_component_for_write(geo_type), + hit_name, + hit_position_name, + hit_normal_name, + hit_distance_name, + hit_attribute_names, + hit_attribute_output_names); + } + } + + params.set_output("Geometry", geometry_set); +} + +} // 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_socket_templates(&ntype, geo_node_raycast_in, geo_node_raycast_out); + node_type_size_preset(&ntype, NODE_SIZE_LARGE); + node_type_init(&ntype, geo_node_raycast_init); + node_type_update(&ntype, geo_node_raycast_update); + node_type_storage( + &ntype, "NodeGeometryRaycast", node_free_standard_storage, node_copy_standard_storage); + ntype.geometry_node_execute = blender::nodes::geo_node_raycast_exec; + ntype.draw_buttons = geo_node_raycast_layout; + nodeRegisterType(&ntype); +} |