diff --git a/CMakeLists.txt b/CMakeLists.txt index 312a073288f..f4f94fa6175 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -590,6 +590,7 @@ endif() option(WITH_BOOST "Enable features depending on boost" ON) option(WITH_TBB "Enable multithreading. TBB is also required for features such as Cycles, OpenVDB and USD" ON) +option(WITH_BVH_EMBREE "Enable Embree support for BVH trees" OFF) # TBB malloc is only supported on for windows currently if(WIN32) @@ -887,6 +888,7 @@ set_and_warn_dependency(WITH_BOOST WITH_INTERNATIONAL OFF) set_and_warn_dependency(WITH_BOOST WITH_OPENVDB OFF) set_and_warn_dependency(WITH_BOOST WITH_QUADRIFLOW OFF) set_and_warn_dependency(WITH_BOOST WITH_USD OFF) +set_and_warn_dependency(WITH_BOOST WITH_BVH_EMBREE OFF) if(WITH_CYCLES) set_and_warn_dependency(WITH_BOOST WITH_CYCLES_OSL OFF) set_and_warn_dependency(WITH_PUGIXML WITH_CYCLES_OSL OFF) @@ -1900,6 +1902,7 @@ if(FIRST_RUN) info_cfg_text("Build Options:") info_cfg_option(WITH_ALEMBIC) info_cfg_option(WITH_BULLET) + info_cfg_option(WITH_BVH_EMBREE) info_cfg_option(WITH_CLANG) info_cfg_option(WITH_CYCLES) info_cfg_option(WITH_FFTW3) diff --git a/scripts/startup/bl_ui/node_add_menu_geometry.py b/scripts/startup/bl_ui/node_add_menu_geometry.py index 73cdc4f3e8c..9efa3dfc6ce 100644 --- a/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -222,6 +222,7 @@ class NODE_MT_geometry_node_GEO_GEOMETRY_SAMPLE(Menu): node_add_menu.add_node_type(layout, "GeometryNodeProximity") node_add_menu.add_node_type(layout, "GeometryNodeIndexOfNearest") node_add_menu.add_node_type(layout, "GeometryNodeRaycast") + node_add_menu.add_node_type(layout, "GeometryNodeRaycastEmbree") node_add_menu.add_node_type(layout, "GeometryNodeSampleIndex") node_add_menu.add_node_type(layout, "GeometryNodeSampleNearest") node_add_menu.draw_assets_for_catalog(layout, self.bl_label) diff --git a/source/blender/blenkernel/BKE_bvh.hh b/source/blender/blenkernel/BKE_bvh.hh new file mode 100644 index 00000000000..3764d4cc806 --- /dev/null +++ b/source/blender/blenkernel/BKE_bvh.hh @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_math_vector_types.hh" + +struct Mesh; +struct RTCDeviceTy; +struct RTCSceneTy; + +namespace blender::bvh { + +struct BVHRay { + static constexpr unsigned int MASK_FULL = 0xffffffff; + static constexpr unsigned int FLAGS_NONE = 0; + + /* Coordinate of ray origin */ + float3 origin = float3(0.0f); + /* Start of ray segment relative to ray length */ + float dist_min = 0.0f; + + /* Ray direction */ + float3 direction = float3(1.0f, 0.0f, 0.0f); + + /* Time of this ray for motion blur */ + float time = 0.0f; + + /* End of ray segment relative to ray length (set to hit distance) */ + float dist_max = 1.0f; + + /* Ray mask */ + unsigned int mask = MASK_FULL; + /* Ray ID */ + unsigned int id = 0; + /* Ray flags */ + unsigned int flags = FLAGS_NONE; +}; + +struct BVHHit { + static constexpr unsigned int INVALID_INSTANCE_ID = 0xffffffff; + static constexpr int MAX_INSTANCE_LEVEL = 8; + + /* Geometry normal */ + float3 normal; + /* Barycentric UV of hit */ + float2 uv; + + /* Primitive ID */ + unsigned int primitive_id; + /* Geometry ID */ + unsigned int geometry_id; + /* Instance ID */ + unsigned int instance_id[MAX_INSTANCE_LEVEL]; +}; + +struct BVHRayHit { + BVHRay ray; + BVHHit hit; +}; + +class BVHTree { + private: + RTCDeviceTy *rtc_device = nullptr; + RTCSceneTy *rtc_scene = nullptr; + + public: + BVHTree(); + BVHTree(const BVHTree &) = delete; + ~BVHTree(); + + BVHTree &operator=(const BVHTree &) = delete; + + void free(); + + void build_single_mesh(const Mesh &mesh); + + bool ray_intersect1(const BVHRay &ray, BVHRayHit &r_hit) const; +}; + +} // namespace blender diff --git a/source/blender/blenkernel/BKE_mesh_sample.hh b/source/blender/blenkernel/BKE_mesh_sample.hh index 17e4ba0d83d..27ecbb5512f 100644 --- a/source/blender/blenkernel/BKE_mesh_sample.hh +++ b/source/blender/blenkernel/BKE_mesh_sample.hh @@ -16,6 +16,7 @@ #include "DNA_meshdata_types.h" #include "BKE_attribute.h" +#include "BKE_attribute_math.hh" #include "BKE_geometry_fields.hh" struct Mesh; diff --git a/source/blender/blenkernel/BKE_mesh_types.h b/source/blender/blenkernel/BKE_mesh_types.h index 7f84a8a8830..2a160a5075a 100644 --- a/source/blender/blenkernel/BKE_mesh_types.h +++ b/source/blender/blenkernel/BKE_mesh_types.h @@ -22,6 +22,8 @@ # include "DNA_meshdata_types.h" +# include "BKE_bvh.hh" + struct BVHCache; struct EditMeshData; struct Mesh; @@ -124,6 +126,9 @@ struct MeshRuntime { /** Cache for BVH trees generated for the mesh. Defined in 'BKE_bvhutil.c' */ BVHCache *bvh_cache = nullptr; + /** Cache for derived triangulation of the mesh, accessed with #Mesh::looptris(). */ + SharedCache bvh_embree_cache; + /** Cache of non-manifold boundary data for Shrink-wrap Target Project. */ ShrinkwrapBoundaryData *shrinkwrap_data = nullptr; diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 61fbaa67724..0226c98b22e 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -1307,6 +1307,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i #define GEO_NODE_SIMULATION_OUTPUT 2101 #define GEO_NODE_INPUT_SIGNED_DISTANCE 2102 #define GEO_NODE_SAMPLE_VOLUME 2103 +#define GEO_NODE_RAYCAST_EMBREE 2104 /** \} */ diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index c5731f35c56..eb3022202fa 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -86,6 +86,7 @@ set(SRC intern/boids.c intern/bpath.c intern/brush.cc + intern/bvh.cc intern/bvhutils.cc intern/cachefile.c intern/callbacks.c @@ -339,6 +340,7 @@ set(SRC BKE_boids.h BKE_bpath.h BKE_brush.h + BKE_bvh.hh BKE_bvhutils.h BKE_cachefile.h BKE_callbacks.h @@ -782,6 +784,16 @@ if(WITH_XR_OPENXR) add_definitions(-DWITH_XR_OPENXR) endif() +if(WITH_BVH_EMBREE) + add_definitions(-DWITH_BVH_EMBREE) + list(APPEND INC_SYS + ${EMBREE_INCLUDE_DIRS} + ) + list(APPEND LIB + ${EMBREE_LIBRARIES} + ) +endif() + if(WITH_TBB) add_definitions(-DWITH_TBB) @@ -821,6 +833,7 @@ if(WITH_GTESTS) intern/armature_test.cc intern/asset_metadata_test.cc intern/bpath_test.cc + intern/bvh_test.cc intern/cryptomatte_test.cc intern/curves_geometry_test.cc intern/fcurve_test.cc diff --git a/source/blender/blenkernel/intern/bvh.cc b/source/blender/blenkernel/intern/bvh.cc new file mode 100644 index 00000000000..5678e145ff0 --- /dev/null +++ b/source/blender/blenkernel/intern/bvh.cc @@ -0,0 +1,274 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_bvh.hh" + +#include "BLI_assert.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" + +#ifdef WITH_BVH_EMBREE + +# include +# include +# include +# include + +namespace blender::bvh { + +BVHTree::BVHTree() {} + +BVHTree::~BVHTree() +{ + free(); +} + +static void rtc_error_func(void *, enum RTCError, const char *str) {} + +static bool rtc_memory_monitor_func(void *userPtr, const ssize_t bytes, const bool) +{ + return true; +} + +static bool rtc_progress_func(void *user_ptr, const double n) +{ + return true; +} + +void BVHTree::free() +{ + rtcReleaseScene(rtc_scene); + rtc_scene = nullptr; + rtcReleaseDevice(rtc_device); + rtc_device = nullptr; +} + +namespace { + +struct BvhBuildContext { + RTCDevice device; + RTCScene scene; + RTCBuildQuality build_quality; +}; + +void set_tri_vertex_buffer(RTCGeometry geom_id, Span positions, const bool update) +{ + // const Attribute *attr_mP = NULL; + size_t num_motion_steps = 1; + // int t_mid = 0; + // if (mesh->has_motion_blur()) { + // attr_mP = mesh->attributes.find(ATTR_STD_MOTION_VERTEX_POSITION); + // if (attr_mP) { + // num_motion_steps = mesh->get_motion_steps(); + // t_mid = (num_motion_steps - 1) / 2; + // if (num_motion_steps > RTC_MAX_TIME_STEP_COUNT) { + // assert(0); + // num_motion_steps = RTC_MAX_TIME_STEP_COUNT; + // } + // } + // } + + const int num_verts = positions.size(); + for (int t = 0; t < num_motion_steps; ++t) { + const float3 *verts; + // if (t == t_mid) { + verts = positions.data(); + //} + // else { + // int t_ = (t > t_mid) ? (t - 1) : t; + // verts = &attr_mP->data_float3()[t_ * num_verts]; + //} + + float *rtc_verts = (update) ? + (float *)rtcGetGeometryBufferData(geom_id, RTC_BUFFER_TYPE_VERTEX, t) : + (float *)rtcSetNewGeometryBuffer(geom_id, + RTC_BUFFER_TYPE_VERTEX, + t, + RTC_FORMAT_FLOAT3, + sizeof(float) * 3, + num_verts); + + BLI_assert(rtc_verts); + if (rtc_verts) { + for (size_t j = 0; j < num_verts; ++j) { + rtc_verts[0] = verts[j].x; + rtc_verts[1] = verts[j].y; + rtc_verts[2] = verts[j].z; + rtc_verts += 3; + } + } + + if (update) { + rtcUpdateGeometryBuffer(geom_id, RTC_BUFFER_TYPE_VERTEX, t); + } + } +} + +void add_triangles(BvhBuildContext ctx, + int id, + Span positions, + Span corner_verts, + Span looptris) +{ + // size_t prim_offset = mesh->prim_offset; + + // const Attribute *attr_mP = NULL; + // size_t num_motion_steps = 1; + // if (mesh->has_motion_blur()) { + // attr_mP = mesh->attributes.find(ATTR_STD_MOTION_VERTEX_POSITION); + // if (attr_mP) { + // num_motion_steps = mesh->get_motion_steps(); + // } + // } + + // assert(num_motion_steps <= RTC_MAX_TIME_STEP_COUNT); + // num_motion_steps = min(num_motion_steps, (size_t)RTC_MAX_TIME_STEP_COUNT); + + RTCGeometry geom_id = rtcNewGeometry(ctx.device, RTC_GEOMETRY_TYPE_TRIANGLE); + rtcSetGeometryBuildQuality(geom_id, ctx.build_quality); + // rtcSetGeometryTimeStepCount(geom_id, num_motion_steps); + + unsigned *rtc_indices = static_cast(rtcSetNewGeometryBuffer( + geom_id, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT3, sizeof(int) * 3, looptris.size())); + BLI_assert(rtc_indices); + // if (!rtc_indices) { + // VLOG_WARNING << "Embree could not create new geometry buffer for mesh " << + // mesh->name.c_str() + // << ".\n"; + // return; + // } + for (size_t j = 0; j < looptris.size(); ++j) { + rtc_indices[0] = corner_verts[looptris[j].tri[0]]; + rtc_indices[1] = corner_verts[looptris[j].tri[1]]; + rtc_indices[2] = corner_verts[looptris[j].tri[2]]; + rtc_indices += 3; + } + + set_tri_vertex_buffer(geom_id, positions, false); + + // rtcSetGeometryUserData(geom_id, (void *)prim_offset); + // rtcSetGeometryOccludedFilterFunction(geom_id, kernel_embree_filter_occluded_func); + // rtcSetGeometryIntersectFilterFunction(geom_id, kernel_embree_filter_intersection_func); + // rtcSetGeometryMask(geom_id, 1); + + rtcCommitGeometry(geom_id); + rtcAttachGeometryByID(ctx.scene, geom_id, id); + rtcReleaseGeometry(geom_id); +} + +void add_mesh(BvhBuildContext ctx, int id, const Mesh &mesh) +{ + const MLoopTri *looptri = BKE_mesh_runtime_looptri_ensure(&mesh); + int looptri_len = BKE_mesh_runtime_looptri_len(&mesh); + if (looptri_len == 0) { + return; + } + + add_triangles( + ctx, id, mesh.vert_positions(), mesh.corner_verts(), Span(looptri, looptri_len)); +} + +} // namespace + +void BVHTree::build_single_mesh(const Mesh &mesh) +{ + rtc_device = rtcNewDevice("verbose=0"); + BLI_assert(rtc_device); + + rtcSetDeviceErrorFunction(rtc_device, rtc_error_func, nullptr); + rtcSetDeviceMemoryMonitorFunction(rtc_device, rtc_memory_monitor_func, nullptr); + + rtc_scene = rtcNewScene(rtc_device); + const RTCSceneFlags scene_flags = RTC_SCENE_FLAG_ROBUST; + rtcSetSceneFlags(rtc_scene, scene_flags); + RTCBuildQuality build_quality = RTC_BUILD_QUALITY_MEDIUM; + rtcSetSceneBuildQuality(rtc_scene, build_quality); + + BvhBuildContext ctx{rtc_device, rtc_scene, build_quality}; + + add_mesh(ctx, 0, mesh); + + rtcSetSceneProgressMonitorFunction(rtc_scene, rtc_progress_func, nullptr); + rtcCommitScene(rtc_scene); +} + +bool BVHTree::ray_intersect1(const BVHRay &ray, BVHRayHit &r_hit) const +{ + RTCIntersectContext rtc_ctx; + rtcInitIntersectContext(&rtc_ctx); + + RTCRayHit rtc_hit; + rtc_hit.ray.org_x = ray.origin.x; + rtc_hit.ray.org_y = ray.origin.y; + rtc_hit.ray.org_z = ray.origin.z; + rtc_hit.ray.dir_x = ray.direction.x; + rtc_hit.ray.dir_y = ray.direction.y; + rtc_hit.ray.dir_z = ray.direction.z; + rtc_hit.ray.tnear = ray.dist_min; + rtc_hit.ray.tfar = ray.dist_max; + rtc_hit.ray.time = ray.time; /* Motion blur time */ + rtc_hit.ray.mask = ray.mask; + rtc_hit.hit.geomID = RTC_INVALID_GEOMETRY_ID; + rtc_hit.hit.instID[0] = RTC_INVALID_GEOMETRY_ID; + rtcIntersect1(rtc_scene, &rtc_ctx, &rtc_hit); + + if (rtc_hit.hit.geomID == RTC_INVALID_GEOMETRY_ID || + rtc_hit.hit.primID == RTC_INVALID_GEOMETRY_ID) + { + return false; + } + + r_hit.ray.origin = float3(rtc_hit.ray.org_x, rtc_hit.ray.org_y, rtc_hit.ray.org_z); + r_hit.ray.dist_min = rtc_hit.ray.tnear; + r_hit.ray.direction = float3(rtc_hit.ray.dir_x, rtc_hit.ray.dir_y, rtc_hit.ray.dir_z); + r_hit.ray.time = rtc_hit.ray.time; + r_hit.ray.dist_max = rtc_hit.ray.tfar; + r_hit.ray.mask = rtc_hit.ray.mask; + r_hit.ray.id = rtc_hit.ray.id; + r_hit.ray.flags = rtc_hit.ray.flags; + + r_hit.hit.normal = float3(rtc_hit.hit.Ng_x, rtc_hit.hit.Ng_y, rtc_hit.hit.Ng_z); + r_hit.hit.uv = float2(rtc_hit.hit.u, rtc_hit.hit.v); + r_hit.hit.primitive_id = rtc_hit.hit.primID; + r_hit.hit.geometry_id = rtc_hit.hit.geomID; + static constexpr int MAX_INSTANCE_ID_COPY = std::min(BVHHit::MAX_INSTANCE_LEVEL, + RTC_MAX_INSTANCE_LEVEL_COUNT); + for (int i = 0; i < MAX_INSTANCE_ID_COPY; ++i) { + r_hit.hit.instance_id[i] = rtc_hit.hit.instID[i]; + } + for (int i = MAX_INSTANCE_ID_COPY; i < BVHHit::MAX_INSTANCE_LEVEL; ++i) { + r_hit.hit.instance_id[i] = BVHHit::INVALID_INSTANCE_ID; + } + + return true; +} + +} // namespace blender::bvh + +#else /* WITH_BVH_EMBREE */ + +namespace blender::bvh { + +BVHTree::BVHTree() {} + +BVHTree::~BVHTree() {} + +void BVHTree::free() {} + +void BVHTree::build_single_mesh(const Mesh &mesh) +{ + UNUSED_VARS(mesh); +} + +bool BVHTree::ray_intersect1(const BVHRay &ray, BVHRayHit &r_hit) const +{ + UNUSED_VARS(ray, r_hit); + return false; +} + +} // namespace blender::bvh + +#endif /* WITH_BVH_EMBREE */ diff --git a/source/blender/blenkernel/intern/bvh_test.cc b/source/blender/blenkernel/intern/bvh_test.cc new file mode 100644 index 00000000000..728adabd66e --- /dev/null +++ b/source/blender/blenkernel/intern/bvh_test.cc @@ -0,0 +1,577 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2020 Blender Foundation. */ +#include "testing/testing.h" + +#include "MEM_guardedalloc.h" + +#include "BKE_appdir.h" +#include "BKE_blender.h" +#include "BKE_bvh.hh" +#include "BKE_bvhutils.h" +#include "BKE_callbacks.h" +#include "BKE_global.h" +#include "BKE_idtype.h" +#include "BKE_lib_id.h" +#include "BKE_main.h" +#include "BKE_main_namemap.h" +#include "BKE_mesh.h" +#include "BKE_mesh.hh" +#include "BKE_modifier.h" +#include "BKE_node.h" + +#include "DEG_depsgraph.h" +#include "DNA_genfile.h" /* for DNA_sdna_current_init() */ +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" +#include "DNA_windowmanager_types.h" + +#include "BLI_fileops.h" +#include "BLI_math_base.hh" +#include "BLI_math_rotation.hh" +#include "BLI_path_util.h" +#include "BLI_rand.hh" +#include "BLI_string_ref.hh" +#include "BLI_utildefines.h" + +#include "IMB_imbuf.h" + +#include "PIL_time_utildefines.h" + +#include "BLO_writefile.h" + +#include "RNA_define.h" + +#include "GHOST_Path-api.hh" + +#include "CLG_log.h" + +#define DO_PERF_TESTS 1 +#define WRITE_DEBUG_MESH_FILES 0 + +namespace blender::bke::tests { + +/* Utility class that records a duration when going out of scope. */ +struct ScopedTimer { + private: + std::string name_; + double start_time_; + + public: + ScopedTimer(StringRef name) : name_(name), start_time_(PIL_check_seconds_timer()) {} + + ~ScopedTimer() + { + ::testing::Test::RecordProperty(name_, std::to_string(elapsed())); + } + + double elapsed() const + { + return PIL_check_seconds_timer() - start_time_; + } +}; + +class BVHPerfTest : public testing::Test { + public: + unsigned int num_rays_ = 1000000; + unsigned int raycast_seed_ = 121212; + + static void SetUpTestSuite() + { + BKE_idtype_init(); + // CLG_init(); + // BKE_callback_global_init(); + // RNA_init(); + // BKE_node_system_init(); + } + + static void TearDownTestSuite() + { + // RNA_exit(); + // BKE_node_system_exit(); + // BKE_callback_global_finalize(); + // CLG_exit(); + } + + void init_ray(RandomNumberGenerator &rng, float3 &r_origin, float3 &r_direction, float &r_length) + { + r_origin = float3(rng.get_float(), rng.get_float(), rng.get_float()) * 200.0f - 100.0f; + r_direction = rng.get_unit_float3(); + r_length = rng.get_float() * 200.0f; + } + + void test_raycasts_classic(const Mesh &mesh) + { + BVHTreeFromMesh tree_data; + + { + ScopedTimer timer("[Classic] BVH Build"); + BKE_bvhtree_from_mesh_get(&tree_data, &mesh, BVHTREE_FROM_LOOPTRI, 4); + } + BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&tree_data); }); + + if (tree_data.tree == nullptr) { + return; + } + + { + ScopedTimer timer("[Classic] Raycast"); + RandomNumberGenerator rng(raycast_seed_); + int hits = 0; + for (int i = 0; i < num_rays_; ++i) { + float3 ray_origin; + float3 ray_direction; + float ray_length; + init_ray(rng, ray_origin, ray_direction, ray_length); + + BVHTreeRayHit hit; + hit.index = -1; + hit.dist = ray_length; + bool is_hit = (BLI_bvhtree_ray_cast(tree_data.tree, + ray_origin, + ray_direction, + 0.0f, + &hit, + tree_data.raycast_callback, + &tree_data) != -1); + if (is_hit) { + ++hits; + } + } + RecordProperty("[Classic] Hits", hits); + } + } + + void test_raycasts_embree(const Mesh &mesh) + { + bvh::BVHTree tree; + + { + ScopedTimer timer("[Embree] BVH Build"); + tree.build_single_mesh(mesh); + } + + { + ScopedTimer timer("[Embree] Raycast 1"); + RandomNumberGenerator rng(raycast_seed_); + int hits = 0; + for (int i = 0; i < num_rays_; ++i) { + bvh::BVHRay ray; + init_ray(rng, ray.origin, ray.direction, ray.dist_max); + + bvh::BVHRayHit hit; + bool is_hit = tree.ray_intersect1(ray, hit); + if (is_hit) { + ++hits; + } + } + RecordProperty("[Embree] Hits", hits); + } + } + + void test_raycasts(const Mesh &mesh) + { + test_raycasts_classic(mesh); + test_raycasts_embree(mesh); + } +}; + +class BVHPerfParamTest : public BVHPerfTest, public testing::WithParamInterface {}; + +class BVHBlendFileTest : public testing::Test { + private: + std::string temp_library_path_; + + public: + static void SetUpTestSuite() + { + /* Minimal code to make loading a blendfile and constructing a depsgraph not crash, copied from + * main() in creator.c. */ + CLG_init(); + BLI_threadapi_init(); + + DNA_sdna_current_init(); + BKE_blender_globals_init(); + + BKE_idtype_init(); + BKE_appdir_init(); + IMB_init(); + BKE_modifier_init(); + DEG_register_node_types(); + RNA_init(); + BKE_node_system_init(); + BKE_callback_global_init(); + // BKE_vfont_builtin_register(datatoc_bfont_pfb, datatoc_bfont_pfb_size); + + G.background = true; + G.factory_startup = true; + + /* Allocate a dummy window manager. The real window manager will try and load Python scripts + * from the release directory, which it won't be able to find. */ + ASSERT_EQ(G.main->wm.first, nullptr); + G.main->wm.first = MEM_callocN(sizeof(wmWindowManager), __func__); + } + + static void TearDownTestSuite() + { + if (G.main->wm.first != nullptr) { + MEM_freeN(G.main->wm.first); + G.main->wm.first = nullptr; + } + + /* Copied from WM_exit_ex() in wm_init_exit.cc, and cherry-picked those lines that match the + * allocation/initialization done in SetUpTestCase(). */ + BKE_blender_free(); + RNA_exit(); + + DEG_free_node_types(); + GHOST_DisposeSystemPaths(); + DNA_sdna_current_free(); + BLI_threadapi_exit(); + + BKE_blender_atexit(); + + BKE_tempdir_session_purge(); + BKE_appdir_exit(); + CLG_exit(); + } + + void SetUp() override + { + temp_library_path_ = ""; + } + + void TearDown() override + { + if (!temp_library_path_.empty()) { + BLI_delete(temp_library_path_.c_str(), true, true); + temp_library_path_ = ""; + } + } + + /* Register a temporary path, which will be removed at the end of the test. + * The returned path ends in a slash. */ + std::string use_temp_path() + { + BKE_tempdir_init(""); + const std::string tempdir = BKE_tempdir_session(); + temp_library_path_ = tempdir + "test-temporary-path" + SEP_STR; + return temp_library_path_; + } + + std::string create_temp_path() + { + std::string path = use_temp_path(); + BLI_dir_create_recursive(path.c_str()); + return path; + } + + /* Register a persistent temporary path. The returned path ends in a slash. */ + std::string use_persistent_temp_path() + { + BKE_tempdir_init(""); + const std::string tempdir = BKE_tempdir_base(); + return tempdir + "test-persistent-path" + SEP_STR; + } + + std::string create_persistent_temp_path() + { + std::string path = use_persistent_temp_path(); + BLI_dir_create_recursive(path.c_str()); + return path; + } + + /* Warning: takes ownership of the mesh and frees it at the end! */ + void write_debug_mesh_file(Mesh *mesh, const char *filename) + { + Main *bmain = BKE_main_new(); + + Mesh *main_mesh = BKE_mesh_add(bmain, "BVHTest"); + BKE_mesh_nomain_to_mesh(mesh, main_mesh, nullptr); + + char filepath[FILE_MAX]; + BLI_strncpy(filepath, create_persistent_temp_path().c_str(), sizeof(filepath)); + BLI_path_append(filepath, sizeof(filepath), filename); + + BlendFileWriteParams write_params{}; + write_params.use_save_versions = 0; + BLO_write_file(bmain, filepath, 0, &write_params, nullptr); + std::cout << "BVH mesh file written to " << filepath << std::endl; + + BKE_main_free(bmain); + } +}; + +class MeshGenerator { + public: + int num_triangles_ = 100; + float size_ = 10.0f; + float scale_ = 1.0f; + + MeshGenerator(int num_triangles) : num_triangles_(num_triangles) + { + scale_ = size_ / powf(num_triangles_, 0.333333f); + } +}; + +class UniformAreaTrianglesGenerator : public MeshGenerator { + public: + unsigned int mesh_seed_ = 12345; + float triangle_area() const + { + const float avg_side_length = 0.5f * scale_; + return 0.5f * avg_side_length * avg_side_length; + } + /* Note: don't make this too large, or the rng filter below + * might reject a large proportion of candidate triangles and take a lot of time. + */ + float min_angle_ = DEG2RAD(20.0f); + float min_side_factor = 0.333f; + + UniformAreaTrianglesGenerator(int num_triangles) : MeshGenerator(num_triangles) {} + + Mesh *create_mesh() const + { + const int num_tris = num_triangles_; + const int num_verts = num_tris * 3; + const int num_corners = num_tris * 3; + Mesh *mesh = BKE_mesh_new_nomain(num_verts, 0, num_tris, num_corners); + MutableSpan positions = mesh->vert_positions_for_write(); + MutableSpan poly_offsets = mesh->poly_offsets_for_write(); + MutableSpan corner_verts = mesh->corner_verts_for_write(); + + RandomNumberGenerator rng(mesh_seed_); + for (int i = 0; i < num_tris; ++i) { + float3 p0 = (float3(rng.get_float(), rng.get_float(), rng.get_float()) - float3(0.5f)) * + size_; + float3 u, v; + float dot_uv; + do { + u = rng.get_unit_float3(); + v = rng.get_unit_float3(); + dot_uv = math::dot(u, v); + } while (math::abs(dot_uv) >= math::cos(min_angle_)); + const float side_scale = math::sqrt(2.0f * triangle_area() / + math::sqrt(1.0f - dot_uv * dot_uv)); + const float r = min_side_factor + (1.0f - min_side_factor) * rng.get_float(); + const float s = 1.0f / r; + float3 p1 = p0 + side_scale * r * u; + float3 p2 = p0 + side_scale * s * v; + + positions[i * 3 + 0] = p0; + positions[i * 3 + 1] = p1; + positions[i * 3 + 2] = p2; + + poly_offsets[i] = i * 3; + + corner_verts[i * 3 + 0] = i * 3 + 0; + corner_verts[i * 3 + 1] = i * 3 + 1; + corner_verts[i * 3 + 2] = i * 3 + 2; + } + poly_offsets[num_tris] = num_tris * 3; + BKE_mesh_calc_edges(mesh, false, false); + + return mesh; + } +}; + +class DonutCloudGenerator : MeshGenerator { + public: + unsigned int mesh_seed_ = 12345; + int min_segments_ = 4; + int max_segments_ = 128; + + DonutCloudGenerator(int num_triangles) : MeshGenerator(num_triangles) {} + + void get_donut_params(RandomNumberGenerator &rng, + int &r_major_segments, + int &r_minor_segments, + float &r_major_radius, + float &r_minor_radius, + float3 &r_center, + math::Quaternion &r_rotation) const + { + const float avg_major_radius_ = 4.0f; + r_major_radius = scale_ * avg_major_radius_ * (0.5f + 1.0f * rng.get_float()); + r_minor_radius = r_major_radius * (0.25f + 0.5f * rng.get_float()); + r_major_segments = min_segments_ + rng.get_int32(max_segments_ - min_segments_ + 1); + r_minor_segments = math::max((int)(r_major_segments * r_minor_radius / r_major_radius), + min_segments_); + r_center = (float3(rng.get_float(), rng.get_float(), rng.get_float()) - float3(0.5f)) * size_; + const float a0 = rng.get_float() * 2.0f * M_PI; + const float a1 = rng.get_float() * 2.0f * M_PI; + const float a2 = rng.get_float() * 2.0f * M_PI; + mul_qt_qtqt((float4 &)r_rotation, + float4(math::sin(a0), 0.0f, 0.0f, math::cos(a0)), + float4(0.0f, math::sin(a1), 0.0f, math::cos(a1))); + mul_qt_qtqt((float4 &)r_rotation, + (float4)r_rotation, + float4(0.0f, 0.0f, math::sin(a2), math::cos(a2))); + } + + void generate_donut(MutableSpan poly_offsets, + MutableSpan corner_verts, + MutableSpan positions, + int verts_start, + int corners_start, + int major_segments, + int minor_segments, + float major_radius, + float minor_radius, + const float3 ¢er, + const math::Quaternion &rotation) const + { + BLI_assert(poly_offsets.size() == major_segments * minor_segments + 1); + BLI_assert(corner_verts.size() == 4 * major_segments * minor_segments); + BLI_assert(positions.size() == major_segments * minor_segments); + + const float major_delta = M_PI * 2.0f / (float)major_segments; + const float minor_delta = M_PI * 2.0f / (float)minor_segments; + for (const int i : IndexRange(major_segments)) { + for (const int j : IndexRange(minor_segments)) { + const int index = i * minor_segments + j; + + poly_offsets[index] = corners_start + index * 4; + + const int vert00 = verts_start + index; + const int vert10 = j + 1 < minor_segments ? vert00 + 1 : vert00 - j; + const int vert01 = i + 1 < major_segments ? vert00 + minor_segments : + vert00 - i * minor_segments; + const int vert11 = j + 1 < minor_segments ? vert01 + 1 : vert01 - j; + // BLI_assert(vert00 < poly_start + polys.size()); + // BLI_assert(vert10 < poly_start + polys.size()); + // BLI_assert(vert01 < poly_start + polys.size()); + // BLI_assert(vert11 < poly_start + polys.size()); + corner_verts[index * 4 + 0] = vert00; + corner_verts[index * 4 + 1] = vert10; + corner_verts[index * 4 + 2] = vert11; + corner_verts[index * 4 + 3] = vert01; + + const float major_angle = i * major_delta; + const float minor_angle = j * minor_delta; + const float3 minor_pos = float3(math::cos(minor_angle), 0.0f, math::sin(minor_angle)) * + minor_radius + + float3(major_radius, 0.0f, 0.0f); + const float3 major_pos(minor_pos.x * math::cos(major_angle), + minor_pos.x * math::sin(major_angle), + minor_pos.z); + float3 pos = major_pos; + mul_qt_v3((float4)rotation, pos); + pos += center; + positions[index] = pos; + } + } + poly_offsets[major_segments * minor_segments] = corners_start + + major_segments * minor_segments * 4; + } + + Mesh *create_mesh() const + { + /* Dry run for counting triangles */ + RandomNumberGenerator rng(mesh_seed_); + int num_donuts = 0; + int num_polys = 0; + const int max_polys = num_triangles_ / 2; + while (true) { + float r_maj, r_min; + int seg_maj, seg_min; + float3 center; + math::Quaternion rotation; + get_donut_params(rng, seg_maj, seg_min, r_maj, r_min, center, rotation); + const int donut_polys = seg_min * seg_maj; + + if (num_polys + donut_polys >= max_polys) { + break; + } + ++num_donuts; + num_polys += donut_polys; + } + + /* Torus has same number of verts as polys, 4 loops per poly */ + Mesh *mesh = BKE_mesh_new_nomain(num_polys, 0, num_polys, num_polys * 4); + MutableSpan positions = mesh->vert_positions_for_write(); + MutableSpan poly_offsets = mesh->poly_offsets_for_write(); + MutableSpan corner_verts = mesh->corner_verts_for_write(); + + /* Reset RNG */ + rng.seed(mesh_seed_); + int polys_start = 0; + int corners_start = 0; + int verts_start = 0; + for (const int idonut : IndexRange(num_donuts)) { + float r_maj, r_min; + int seg_maj, seg_min; + float3 center; + math::Quaternion rotation; + get_donut_params(rng, seg_maj, seg_min, r_maj, r_min, center, rotation); + const int donut_polys = seg_min * seg_maj; + + generate_donut(poly_offsets.slice(polys_start, donut_polys + 1), + corner_verts.slice(corners_start, donut_polys * 4), + positions.slice(verts_start, donut_polys), + verts_start, + corners_start, + seg_maj, + seg_min, + r_maj, + r_min, + center, + rotation); + + polys_start += donut_polys; + corners_start += donut_polys * 4; + verts_start += donut_polys; + } + BKE_mesh_calc_edges(mesh, false, false); + + // BKE_mesh_validate(mesh, true, true); + + return mesh; + } +}; + +#if DO_PERF_TESTS +TEST_P(BVHPerfParamTest, raycast_uniform_area_triangles) +{ + UniformAreaTrianglesGenerator gen(GetParam()); + Mesh *mesh = gen.create_mesh(); + + test_raycasts(*mesh); + + BKE_id_free(nullptr, mesh); +} + +TEST_P(BVHPerfParamTest, raycast_donut_cloud) +{ + DonutCloudGenerator gen(GetParam()); + Mesh *mesh = gen.create_mesh(); + + test_raycasts(*mesh); + + BKE_id_free(nullptr, mesh); +} + +INSTANTIATE_TEST_SUITE_P( + MeshSizeTests, + BVHPerfParamTest, + testing::Values( + 100000, 200000, 300000, 400000, 500000, 1000000, 2000000, 3000000, 4000000, 5000000)); +#endif + +#if WRITE_DEBUG_MESH_FILES +TEST_F(BVHBlendFileTest, write_mesh_debug_files) +{ + { + UniformAreaTrianglesGenerator gen(10000); + Mesh *mesh = gen.create_mesh(); + write_debug_mesh_file(mesh, "triangle_soup.blend"); + } + { + DonutCloudGenerator gen(1000000); + Mesh *mesh = gen.create_mesh(); + write_debug_mesh_file(mesh, "donut_cloud.blend"); + } +} +#endif + +} // namespace blender::bke::tests diff --git a/source/blender/blenkernel/intern/mesh.cc b/source/blender/blenkernel/intern/mesh.cc index 71d8c147d87..05f044c00e9 100644 --- a/source/blender/blenkernel/intern/mesh.cc +++ b/source/blender/blenkernel/intern/mesh.cc @@ -136,6 +136,8 @@ static void mesh_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const int mesh_dst->runtime->looptris_cache = mesh_src->runtime->looptris_cache; mesh_dst->runtime->looptri_polys_cache = mesh_src->runtime->looptri_polys_cache; + mesh_dst->runtime->bvh_embree_cache = mesh_src->runtime->bvh_embree_cache; + /* Only do tessface if we have no polys. */ const bool do_tessface = ((mesh_src->totface != 0) && (mesh_src->totpoly == 0)); diff --git a/source/blender/blenkernel/intern/mesh_runtime.cc b/source/blender/blenkernel/intern/mesh_runtime.cc index 2573837f28d..47874f96eb2 100644 --- a/source/blender/blenkernel/intern/mesh_runtime.cc +++ b/source/blender/blenkernel/intern/mesh_runtime.cc @@ -17,6 +17,7 @@ #include "BLI_task.hh" #include "BLI_timeit.hh" +#include "BKE_bvh.hh" #include "BKE_bvhutils.h" #include "BKE_editmesh_cache.h" #include "BKE_lib_id.h" @@ -76,6 +77,7 @@ static void free_bvh_cache(MeshRuntime &mesh_runtime) bvhcache_free(mesh_runtime.bvh_cache); mesh_runtime.bvh_cache = nullptr; } + mesh_runtime.bvh_embree_cache.tag_dirty(); } static void reset_normals(MeshRuntime &mesh_runtime) @@ -221,6 +223,15 @@ blender::Span Mesh::looptris() const return this->runtime->looptris_cache.data(); } +const blender::bvh::BVHTree &Mesh::bvh_tree() const +{ + this->runtime->bvh_embree_cache.ensure([&](blender::bvh::BVHTree &r_data) { + r_data.build_single_mesh(*this); + }); + + return this->runtime->bvh_embree_cache.data(); +} + blender::Span Mesh::looptri_polys() const { using namespace blender; diff --git a/source/blender/makesdna/DNA_mesh_types.h b/source/blender/makesdna/DNA_mesh_types.h index 69a6fbdf471..5560890b9b0 100644 --- a/source/blender/makesdna/DNA_mesh_types.h +++ b/source/blender/makesdna/DNA_mesh_types.h @@ -30,6 +30,9 @@ class MutableAttributeAccessor; struct LooseVertCache; struct LooseEdgeCache; } // namespace bke +namespace bvh { +class BVHTree; +} // namespace bvh } // namespace blender using MeshRuntimeHandle = blender::bke::MeshRuntime; #else @@ -343,6 +346,8 @@ typedef struct Mesh { * surrounding each vertex and the normalized position for loose vertices. */ blender::Span vert_normals() const; + + const blender::bvh::BVHTree &bvh_tree() const; #endif } Mesh; diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index e24c2f4570b..17851f423c5 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -390,7 +390,9 @@ DefNode(GeometryNode, GEO_NODE_POINTS_TO_VOLUME, def_geo_points_to_volume, "POIN DefNode(GeometryNode, GEO_NODE_POINTS, 0, "POINTS", Points, "Points", "Generate a point cloud with positions and radii defined by fields") DefNode(GeometryNode, GEO_NODE_PROXIMITY, def_geo_proximity, "PROXIMITY", Proximity, "Geometry Proximity", "Compute the closest location on the target geometry") DefNode(GeometryNode, GEO_NODE_RAYCAST, def_geo_raycast, "RAYCAST", Raycast, "Raycast", "Cast rays from the context geometry onto a target geometry, and retrieve information from each hit point") +DefNode(GeometryNode, GEO_NODE_RAYCAST_EMBREE, def_geo_raycast, "RAYCAST_EMBREE", RaycastEmbree, "Raycast (Embree)", "Cast rays from the context geometry onto a target geometry, and retrieve information from each hit point") DefNode(GeometryNode, GEO_NODE_REALIZE_INSTANCES, 0, "REALIZE_INSTANCES", RealizeInstances, "Realize Instances", "Convert instances into real geometry data") + DefNode(GeometryNode, GEO_NODE_REMOVE_ATTRIBUTE, 0, "REMOVE_ATTRIBUTE", RemoveAttribute, "Remove Named Attribute", "Delete an attribute with a specified name from a geometry. Typically used to optimize performance") DefNode(GeometryNode, GEO_NODE_REPLACE_MATERIAL, 0, "REPLACE_MATERIAL", ReplaceMaterial, "Replace Material", "Swap one material with another") DefNode(GeometryNode, GEO_NODE_RESAMPLE_CURVE, def_geo_curve_resample, "RESAMPLE_CURVE", ResampleCurve, "Resample Curve", "Generate a poly spline for each input spline") diff --git a/source/blender/nodes/geometry/node_geometry_register.cc b/source/blender/nodes/geometry/node_geometry_register.cc index 45de3a4f5e1..1a77eb95ac9 100644 --- a/source/blender/nodes/geometry/node_geometry_register.cc +++ b/source/blender/nodes/geometry/node_geometry_register.cc @@ -130,6 +130,7 @@ void register_geometry_nodes() register_node_type_geo_points(); register_node_type_geo_proximity(); register_node_type_geo_raycast(); + register_node_type_geo_raycast_embree(); register_node_type_geo_realize_instances(); register_node_type_geo_remove_attribute(); register_node_type_geo_rotate_instances(); diff --git a/source/blender/nodes/geometry/node_geometry_register.hh b/source/blender/nodes/geometry/node_geometry_register.hh index 39efd11639c..5a9be714962 100644 --- a/source/blender/nodes/geometry/node_geometry_register.hh +++ b/source/blender/nodes/geometry/node_geometry_register.hh @@ -127,6 +127,7 @@ void register_node_type_geo_points_to_volume(); void register_node_type_geo_points(); void register_node_type_geo_proximity(); void register_node_type_geo_raycast(); +void register_node_type_geo_raycast_embree(); void register_node_type_geo_realize_instances(); void register_node_type_geo_remove_attribute(); void register_node_type_geo_rotate_instances(); diff --git a/source/blender/nodes/geometry/nodes/node_geo_raycast.cc b/source/blender/nodes/geometry/nodes/node_geo_raycast.cc index 5a5dd6ecf25..87d7373a864 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_raycast.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_raycast.cc @@ -1,11 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include "BLI_index_mask_ops.hh" - #include "DNA_mesh_types.h" -#include "BKE_attribute_math.hh" -#include "BKE_bvhutils.h" #include "BKE_mesh_sample.hh" #include "UI_interface.h" @@ -15,6 +11,9 @@ #include "node_geometry_util.hh" +#include "BKE_bvh.hh" +#include "BKE_bvhutils.h" + namespace blender::nodes::node_geo_raycast_cc { using namespace blender::bke::mesh_surface_sample; @@ -116,7 +115,10 @@ static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) } } -static void raycast_to_mesh(IndexMask mask, +enum BVHType { Classic, Embree }; + +static void raycast_to_mesh(BVHType bvh_type, + IndexMask mask, const Mesh &mesh, const VArray &ray_origins, const VArray &ray_directions, @@ -127,75 +129,131 @@ static void raycast_to_mesh(IndexMask mask, const MutableSpan r_hit_normals, const MutableSpan r_hit_distances) { - BVHTreeFromMesh tree_data; - BKE_bvhtree_from_mesh_get(&tree_data, &mesh, BVHTREE_FROM_LOOPTRI, 4); - BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&tree_data); }); + switch (bvh_type) { + case BVHType::Classic: { + BVHTreeFromMesh tree_data; + BKE_bvhtree_from_mesh_get(&tree_data, &mesh, BVHTREE_FROM_LOOPTRI, 4); + BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&tree_data); }); - if (tree_data.tree == nullptr) { - return; - } - /* We shouldn't be rebuilding the BVH tree when calling this function in parallel. */ - BLI_assert(tree_data.cached); + if (tree_data.tree == nullptr) { + return; + } + /* We shouldn't be rebuilding the BVH tree when calling this function in parallel. */ + BLI_assert(tree_data.cached); - 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]; + 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]; - 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; + 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()) { + /* 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; + } + } } - 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; + } break; + + case BVHType::Embree: { + const bvh::BVHTree &tree = mesh.bvh_tree(); + + for (const int i : mask) { + bvh::BVHRay ray; + ray.origin = ray_origins[i]; + ray.direction = ray_directions[i]; + ray.dist_max = ray_lengths[i]; + + bvh::BVHRayHit hit; + if (tree.ray_intersect1(ray, hit)) { + if (!r_hit.is_empty()) { + r_hit[i] = true; + } + 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.hit.primitive_id; + } + if (!r_hit_positions.is_empty()) { + r_hit_positions[i] = hit.ray.origin + hit.ray.direction * hit.ray.dist_max; + } + if (!r_hit_normals.is_empty()) { + r_hit_normals[i] = hit.hit.normal; + } + if (!r_hit_distances.is_empty()) { + r_hit_distances[i] = math::length(hit.ray.direction * hit.ray.dist_max); + } + } + 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_lengths[i]; + } + } } - 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; - } - } + } break; } } class RaycastFunction : public mf::MultiFunction { private: + BVHType bvh_type_; GeometrySet target_; public: - RaycastFunction(GeometrySet target) : target_(std::move(target)) + RaycastFunction(BVHType bvh_type, GeometrySet target) + : bvh_type_(bvh_type), target_(std::move(target)) { target_.ensure_owns_direct_data(); static const mf::Signature signature = []() { @@ -219,7 +277,8 @@ class RaycastFunction : public mf::MultiFunction { BLI_assert(target_.has_mesh()); const Mesh &mesh = *target_.get_mesh_for_read(); - raycast_to_mesh(mask, + raycast_to_mesh(bvh_type_, + mask, mesh, params.readonly_single_input(0, "Source Position"), params.readonly_single_input(1, "Ray Direction"), @@ -294,7 +353,7 @@ static void output_attribute_field(GeoNodeExecParams ¶ms, GField field) } } -static void node_geo_exec(GeoNodeExecParams params) +static void node_geo_exec(GeoNodeExecParams params, BVHType bvh_type) { GeometrySet target = params.extract_input("Target Geometry"); const NodeGeometryRaycast &storage = node_storage(params.node()); @@ -324,7 +383,7 @@ static void node_geo_exec(GeoNodeExecParams params) auto direction_op = FieldOperation::Create( normalize_fn, {params.extract_input>("Ray Direction")}); - auto op = FieldOperation::Create(std::make_unique(target), + auto op = FieldOperation::Create(std::make_unique(bvh_type, target), {params.extract_input>("Source Position"), Field(direction_op), params.extract_input>("Ray Length")}); @@ -357,6 +416,16 @@ static void node_geo_exec(GeoNodeExecParams params) } } +static void node_geo_exec_classic(GeoNodeExecParams params) +{ + node_geo_exec(params, BVHType::Classic); +} + +static void node_geo_exec_embree(GeoNodeExecParams params) +{ + node_geo_exec(params, BVHType::Embree); +} + } // namespace blender::nodes::node_geo_raycast_cc void register_node_type_geo_raycast() @@ -372,7 +441,26 @@ void register_node_type_geo_raycast() node_type_storage( &ntype, "NodeGeometryRaycast", node_free_standard_storage, node_copy_standard_storage); ntype.declare = file_ns::node_declare; - ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.geometry_node_execute = file_ns::node_geo_exec_classic; + ntype.draw_buttons = file_ns::node_layout; + ntype.gather_link_search_ops = file_ns::node_gather_link_searches; + nodeRegisterType(&ntype); +} + +void register_node_type_geo_raycast_embree() +{ + namespace file_ns = blender::nodes::node_geo_raycast_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_RAYCAST_EMBREE, "Raycast (Embree)", NODE_CLASS_GEOMETRY); + node_type_size_preset(&ntype, blender::bke::eNodeSizePreset::MIDDLE); + ntype.initfunc = file_ns::node_init; + ntype.updatefunc = file_ns::node_update; + node_type_storage( + &ntype, "NodeGeometryRaycast", node_free_standard_storage, node_copy_standard_storage); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec_embree; ntype.draw_buttons = file_ns::node_layout; ntype.gather_link_search_ops = file_ns::node_gather_link_searches; nodeRegisterType(&ntype);