Changeset View
Changeset View
Standalone View
Standalone View
source/blender/nodes/geometry/nodes/node_geo_raycast.cc
- This file was added.
| /* | |||||
| * 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" | |||||
| namespace blender::nodes { | |||||
| static void geo_node_raycast_declare(NodeDeclarationBuilder &b) | |||||
| { | |||||
| b.add_input<decl::Vector>("Source Position").implicit_field(); | |||||
| b.add_input<decl::Geometry>("Target Geometry"); | |||||
| 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(); | |||||
| /* FOR TARGET ATTRIBUTE IF IMPLEMENTED*/ | |||||
| // b.add_output<decl::String>("Target Attribute"); | |||||
| // b.add_output<decl::String>("Hit Attribute"); | |||||
| } | |||||
| static void geo_node_raycast_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) | |||||
JacquesLucke: These dependent fields need to specify what inputs they depend on. | |||||
| { | |||||
| uiLayoutSetPropSep(layout, true); | |||||
| uiLayoutSetPropDecorate(layout, false); | |||||
| uiItemR(layout, ptr, "mapping", 0, IFACE_("Mapping"), 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; | |||||
| node->storage = data; | |||||
| } | |||||
| 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) | |||||
| { | |||||
| 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, &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) { | |||||
| 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 void raycast_from_points(IndexMask mask, | |||||
| const VArray<float3> &ray_origins, | |||||
| const VArray<float3> &ray_directions, | |||||
| const VArray<float> &ray_lengths, | |||||
| const GeometrySet &target_geometry, | |||||
| /*UNUSED FOR NOW MAY NEED FOR TARGET ATTRIBUTE NODE*/ | |||||
| const GeometryNodeRaycastMapMode map_mode, | |||||
| MutableSpan<bool> r_is_hits, | |||||
| MutableSpan<float3> r_hit_positions, | |||||
| MutableSpan<float3> r_hit_normals, | |||||
| MutableSpan<float> r_hit_distances) | |||||
| { | |||||
| const MeshComponent *src_mesh_component = | |||||
| target_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; | |||||
| } | |||||
| Array<int> hit_indices; | |||||
| Array<float3> hit_positions_internal; | |||||
| raycast_to_mesh(mask, | |||||
| *src_mesh, | |||||
| ray_origins, | |||||
| ray_directions, | |||||
| ray_lengths, | |||||
| r_is_hits, | |||||
| hit_indices, | |||||
| r_hit_positions, | |||||
| r_hit_normals, | |||||
| r_hit_distances); | |||||
| /* THIS IS THE ORIGINAL CODE FOR TARGET ATTRIBUTE */ | |||||
| // const AttributeDomain result_domain = ATTR_DOMAIN_POINT; | |||||
| // /* 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(); | |||||
| // } | |||||
Not Done Inline Actionstypos (evaluate), space before Eventually JacquesLucke: typos (`evaluate`), space before `Eventually` | |||||
| // } | |||||
| } | |||||
| class RaycastFunction : public fn::MultiFunction { | |||||
| private: | |||||
| GeometrySet target_; | |||||
| GeometryNodeRaycastMapMode mode_; | |||||
| public: | |||||
| RaycastFunction(GeometrySet target, GeometryNodeRaycastMapMode mode) | |||||
| : target_(std::move(target)), mode_((GeometryNodeRaycastMapMode)mode) | |||||
| { | |||||
| static fn::MFSignature signature = create_signature(); | |||||
| this->set_signature(&signature); | |||||
| } | |||||
| static 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"); | |||||
| return signature.build(); | |||||
Done Inline ActionsJacquesLucke: The mask can't be ignored. This results in a crash in the following setup.
{F10618187}
I… | |||||
| } | |||||
| void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override | |||||
| { | |||||
| const VArray<float3> &in_positions = params.readonly_single_input<float3>(0, | |||||
| "Source Position"); | |||||
| const VArray<float3> &in_directions = params.readonly_single_input<float3>(1, "Ray Direction"); | |||||
| const VArray<float> &in_lengths = params.readonly_single_input<float>(2, "Ray Length"); | |||||
| MutableSpan<bool> is_hits = params.uninitialized_single_output<bool>(3, "Is Hit"); | |||||
| MutableSpan<float3> hit_positions = params.uninitialized_single_output<float3>(4, | |||||
| "Hit Position"); | |||||
| MutableSpan<float3> hit_normals = params.uninitialized_single_output<float3>(5, "Hit Normal"); | |||||
| MutableSpan<float> hit_distances = params.uninitialized_single_output<float>(6, "Distance"); | |||||
| raycast_from_points(mask, | |||||
| in_positions, | |||||
| in_directions, | |||||
| in_lengths, | |||||
| target_, | |||||
| mode_, | |||||
| is_hits, | |||||
| hit_positions, | |||||
| hit_normals, | |||||
| hit_distances); | |||||
| } | |||||
| }; | |||||
| static void geo_node_raycast_exec(GeoNodeExecParams params) | |||||
| { | |||||
| GeometrySet target_geometry_set = params.extract_input<GeometrySet>("Target Geometry"); | |||||
| target_geometry_set = bke::geometry_set_realize_instances(target_geometry_set); | |||||
| if (!target_geometry_set.has_mesh() && !target_geometry_set.has_pointcloud() && | |||||
| !target_geometry_set.has_curve()) { | |||||
| params.set_output("Is Hit", fn::make_constant_field<bool>(false)); | |||||
Done Inline Actionsmissing dot JacquesLucke: missing dot | |||||
| 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)); | |||||
| return; | |||||
| } | |||||
Done Inline ActionsLooks like this relies on the method implemented in another patch. JacquesLucke: Looks like this relies on the method implemented in another patch. | |||||
| 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"); | |||||
| const NodeGeometryRaycast &storage = *(const NodeGeometryRaycast *)params.node().storage; | |||||
| auto raycast_fn = std::make_unique<RaycastFunction>(std::move(target_geometry_set), | |||||
| (GeometryNodeRaycastMapMode)storage.mapping); | |||||
| auto raycast_op = std::make_shared<FieldOperation>(FieldOperation( | |||||
| std::move(raycast_fn), | |||||
| {std::move(position_field), std::move(direction_field), std::move(length_field)})); | |||||
| params.set_output("Is Hit", Field<bool>(raycast_op, 0)); | |||||
| params.set_output("Hit Position", Field<float3>(raycast_op, 1)); | |||||
| params.set_output("Hit Normal", Field<float3>(raycast_op, 2)); | |||||
| params.set_output("Hit Distance", Field<float>(raycast_op, 3)); | |||||
| } | |||||
| } // namespace blender::nodes | |||||
Done Inline ActionsYou have to pass the ray direction/length fields as input of the operation as well. JacquesLucke: You have to pass the ray direction/length fields as input of the operation as well. | |||||
Done Inline Actions
Thank you, this comment made me realize that the inputs and outputs mentioned in the call() function are for the operation, not the node. This makes things much clearer to me! guitargeek: > You have to pass the ray direction/length fields as input of the operation as well.
Thank… | |||||
| 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_LARGE); | |||||
| node_type_init(&ntype, blender::nodes::geo_node_raycast_init); | |||||
| 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); | |||||
| } | |||||

These dependent fields need to specify what inputs they depend on.