Raycast geometry node.
The //Raycast// node intersects rays from one geometry onto another. It computes hit points on the target mesh and returns normals, distances and any surface attribute specified by the user. A ray starts on each point of the input //Geometry//. Rays continue in the //Ray Direction// until they either hit the //Target Geometry// or reach the //Ray Length// limit. If the target is hit, the value of the //Is Hit// attribute in the output mesh will be true. //Hit Position//, //Hit Normal//, //Hit Distance// and //Hit Index// are the properties of the target mesh at the intersection point. In addition, a //Target Attribute// can be specified that is interpolated at the hit point and the result stored in //Hit Attribute//. Docs: D11620 Reviewed By: HooglyBoogly Differential Revision: https://developer.blender.org/D11619
This commit is contained in:
parent
577d6d3f87
commit
4b673ebb99
|
@ -516,6 +516,7 @@ geometry_node_categories = [
|
|||
NodeItem("GeometryNodeTransform"),
|
||||
NodeItem("GeometryNodeJoinGeometry"),
|
||||
NodeItem("GeometryNodeSeparateComponents"),
|
||||
NodeItem("GeometryNodeRaycast"),
|
||||
]),
|
||||
GeometryNodeCategory("GEO_INPUT", "Input", items=[
|
||||
NodeItem("GeometryNodeObjectInfo"),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
/** \} */
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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", "")
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
Loading…
Reference in New Issue