WIP: Test Embree alternative to Blender BVH trees #108148
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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<blender::bvh::BVHTree> bvh_embree_cache;
|
||||
|
||||
/** Cache of non-manifold boundary data for Shrink-wrap Target Project. */
|
||||
ShrinkwrapBoundaryData *shrinkwrap_data = nullptr;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
/** \} */
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <embree3/rtcore.h>
|
||||
# include <embree3/rtcore_geometry.h>
|
||||
# include <embree3/rtcore_ray.h>
|
||||
# include <embree3/rtcore_scene.h>
|
||||
|
||||
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<float3> 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<float3> positions,
|
||||
Span<int> corner_verts,
|
||||
Span<MLoopTri> 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<unsigned *>(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<MLoopTri>(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 */
|
|
@ -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<int> {};
|
||||
|
||||
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<float3> positions = mesh->vert_positions_for_write();
|
||||
MutableSpan<int> poly_offsets = mesh->poly_offsets_for_write();
|
||||
MutableSpan<int> 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<int> poly_offsets,
|
||||
MutableSpan<int> corner_verts,
|
||||
MutableSpan<float3> 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<float3> positions = mesh->vert_positions_for_write();
|
||||
MutableSpan<int> poly_offsets = mesh->poly_offsets_for_write();
|
||||
MutableSpan<int> 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
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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<MLoopTri> 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<int> Mesh::looptri_polys() const
|
||||
{
|
||||
using namespace blender;
|
||||
|
|
|
@ -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<blender::float3> vert_normals() const;
|
||||
|
||||
const blender::bvh::BVHTree &bvh_tree() const;
|
||||
#endif
|
||||
} Mesh;
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<float3> &ray_origins,
|
||||
const VArray<float3> &ray_directions,
|
||||
|
@ -127,75 +129,131 @@ static void raycast_to_mesh(IndexMask mask,
|
|||
const MutableSpan<float3> r_hit_normals,
|
||||
const MutableSpan<float> 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<float3>(0, "Source Position"),
|
||||
params.readonly_single_input<float3>(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<GeometrySet>("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<Field<float3>>("Ray Direction")});
|
||||
|
||||
auto op = FieldOperation::Create(std::make_unique<RaycastFunction>(target),
|
||||
auto op = FieldOperation::Create(std::make_unique<RaycastFunction>(bvh_type, target),
|
||||
{params.extract_input<Field<float3>>("Source Position"),
|
||||
Field<float3>(direction_op),
|
||||
params.extract_input<Field<float>>("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);
|
||||
|
|
Loading…
Reference in New Issue