Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
## Main
- Added copy and move semantics for `t::geometry::RaycastingScene` in C++ and Python: copy/move constructors and assignments are now supported. Python `copy.deepcopy()` is supported for `RaycastingScene`. (issue #7014)(PR #7437)
- Upgrade stdgpu third-party library to commit d7c07d0.
- Fix performance for non-contiguous NumPy array conversion in pybind vector converters. This change removes restrictive `py::array::c_style` flags and adds a runtime contiguity check, improving Pandas-to-Open3D conversion speed by up to ~50×. (issue #5250)(PR #7343).
- Corrected documentation for Link Open3D in C++ projects (broken links).
Expand Down
144 changes: 140 additions & 4 deletions cpp/open3d/t/geometry/RaycastingScene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -361,12 +361,17 @@ struct RaycastingScene::Impl {
RTCDevice device_;
// Vector for storing some information about the added geometry.
std::vector<GeometryPtr> geometry_ptrs_;
// (num_vertices, num_triangles) per geometry, for copy support.
std::vector<std::pair<size_t, size_t>> geometry_sizes_;
core::Device tensor_device_; // cpu or sycl

bool devprop_join_commit;

virtual ~Impl() = default;

/// Returns a deep copy of this implementation (new device and scene).
virtual std::unique_ptr<Impl> Clone() const = 0;

void CommitScene() {
if (!scene_committed_) {
if (devprop_join_commit) {
Expand Down Expand Up @@ -818,6 +823,59 @@ struct RaycastingScene::SYCLImpl : public RaycastingScene::Impl {
void CopyArray(int* src, uint32_t* dst, size_t num_elements) override {
queue_.memcpy(dst, src, num_elements * sizeof(uint32_t)).wait();
}

std::unique_ptr<Impl> Clone() const override {
auto copy = std::make_unique<SYCLImpl>();
copy->InitializeDevice();
copy->tensor_device_ = tensor_device_;
rtcSetDeviceErrorFunction(copy->device_, ErrorFunction, NULL);
copy->scene_ = rtcNewScene(copy->device_);
rtcSetSceneFlags(copy->scene_,
RTC_SCENE_FLAG_ROBUST |
RTC_SCENE_FLAG_FILTER_FUNCTION_IN_ARGUMENTS);
copy->devprop_join_commit = rtcGetDeviceProperty(
copy->device_, RTC_DEVICE_PROPERTY_JOIN_COMMIT_SUPPORTED);
copy->scene_committed_ = false;
copy->geometry_sizes_ = geometry_sizes_;

SYCLImpl* dst = copy.get();

for (size_t geom_id = 0; geom_id < geometry_ptrs_.size(); ++geom_id) {
const size_t num_vertices = geometry_sizes_[geom_id].first;
const size_t num_triangles = geometry_sizes_[geom_id].second;
const size_t vb_bytes = 3 * sizeof(float) * num_vertices;
const size_t ib_bytes = 3 * sizeof(uint32_t) * num_triangles;

RTCGeometry src_geom = rtcGetGeometry(scene_, geom_id);
const void* vb_src = rtcGetGeometryBufferData(
src_geom, RTC_BUFFER_TYPE_VERTEX, 0);
const void* ib_src = rtcGetGeometryBufferData(
src_geom, RTC_BUFFER_TYPE_INDEX, 0);

RTCGeometry geom =
rtcNewGeometry(dst->device_, RTC_GEOMETRY_TYPE_TRIANGLE);
void* vertex_buffer = rtcSetNewGeometryBuffer(
geom, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT3,
3 * sizeof(float), num_vertices);
void* index_buffer = rtcSetNewGeometryBuffer(
geom, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT3,
3 * sizeof(uint32_t), num_triangles);

std::memcpy(vertex_buffer, vb_src, vb_bytes);
std::memcpy(index_buffer, ib_src, ib_bytes);

rtcSetGeometryEnableFilterFunctionFromArguments(geom, true);
rtcCommitGeometry(geom);
rtcAttachGeometry(dst->scene_, geom);
rtcReleaseGeometry(geom);

GeometryPtr geometry_ptr = {RTC_GEOMETRY_TYPE_TRIANGLE,
static_cast<const void*>(vertex_buffer),
static_cast<const void*>(index_buffer)};
dst->geometry_ptrs_.push_back(geometry_ptr);
}
return copy;
}
};
#endif

Expand Down Expand Up @@ -1192,6 +1250,61 @@ struct RaycastingScene::CPUImpl : public RaycastingScene::Impl {
void CopyArray(int* src, uint32_t* dst, size_t num_elements) override {
std::copy(src, src + num_elements, dst);
}

std::unique_ptr<Impl> Clone() const override {
auto copy = std::make_unique<CPUImpl>();
copy->device_ = rtcNewDevice(NULL);
rtcSetDeviceErrorFunction(copy->device_, ErrorFunction, NULL);
copy->scene_ = rtcNewScene(copy->device_);
rtcSetSceneFlags(copy->scene_,
RTC_SCENE_FLAG_ROBUST |
RTC_SCENE_FLAG_FILTER_FUNCTION_IN_ARGUMENTS);
copy->devprop_join_commit = rtcGetDeviceProperty(
copy->device_, RTC_DEVICE_PROPERTY_JOIN_COMMIT_SUPPORTED);
copy->scene_committed_ = false;
copy->tensor_device_ = tensor_device_;
copy->geometry_sizes_ = geometry_sizes_;

for (size_t geom_id = 0; geom_id < geometry_ptrs_.size(); ++geom_id) {
const size_t num_vertices = geometry_sizes_[geom_id].first;
const size_t num_triangles = geometry_sizes_[geom_id].second;

RTCGeometry src_geom = rtcGetGeometry(scene_, geom_id);
const float* vb_src =
reinterpret_cast<const float*>(rtcGetGeometryBufferData(
src_geom, RTC_BUFFER_TYPE_VERTEX, 0));
const uint32_t* ib_src =
reinterpret_cast<const uint32_t*>(rtcGetGeometryBufferData(
src_geom, RTC_BUFFER_TYPE_INDEX, 0));

RTCGeometry geom =
rtcNewGeometry(copy->device_, RTC_GEOMETRY_TYPE_TRIANGLE);
float* vertex_buffer =
reinterpret_cast<float*>(rtcSetNewGeometryBuffer(
geom, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT3,
3 * sizeof(float), num_vertices));
uint32_t* index_buffer =
reinterpret_cast<uint32_t*>(rtcSetNewGeometryBuffer(
geom, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT3,
3 * sizeof(uint32_t), num_triangles));

std::memcpy(vertex_buffer, vb_src,
3 * sizeof(float) * num_vertices);
std::memcpy(index_buffer, ib_src,
3 * sizeof(uint32_t) * num_triangles);

rtcSetGeometryEnableFilterFunctionFromArguments(geom, true);
rtcCommitGeometry(geom);
rtcAttachGeometry(copy->scene_, geom);
rtcReleaseGeometry(geom);

GeometryPtr geometry_ptr = {RTC_GEOMETRY_TYPE_TRIANGLE,
static_cast<const void*>(vertex_buffer),
static_cast<const void*>(index_buffer)};
copy->geometry_ptrs_.push_back(geometry_ptr);
}
return copy;
}
};

RaycastingScene::RaycastingScene(int64_t nthreads, const core::Device& device) {
Expand Down Expand Up @@ -1230,8 +1343,30 @@ RaycastingScene::RaycastingScene(int64_t nthreads, const core::Device& device) {
}

RaycastingScene::~RaycastingScene() {
rtcReleaseScene(impl_->scene_);
rtcReleaseDevice(impl_->device_);
if (impl_) {
rtcReleaseScene(impl_->scene_);
rtcReleaseDevice(impl_->device_);
}
}

RaycastingScene::RaycastingScene(const RaycastingScene& other)
: impl_(other.impl_->Clone()) {}

RaycastingScene& RaycastingScene::operator=(const RaycastingScene& other) {
if (this != &other) {
impl_ = other.impl_->Clone();
}
return *this;
}

RaycastingScene::RaycastingScene(RaycastingScene&& other) noexcept
: impl_(std::move(other.impl_)) {}

RaycastingScene& RaycastingScene::operator=(RaycastingScene&& other) noexcept {
if (this != &other) {
impl_ = std::move(other.impl_);
}
return *this;
}

uint32_t RaycastingScene::AddTriangles(const core::Tensor& vertex_positions,
Expand Down Expand Up @@ -1301,9 +1436,10 @@ uint32_t RaycastingScene::AddTriangles(const core::Tensor& vertex_positions,
rtcReleaseGeometry(geom);

GeometryPtr geometry_ptr = {RTC_GEOMETRY_TYPE_TRIANGLE,
(const void*)vertex_buffer,
(const void*)index_buffer};
static_cast<const void*>(vertex_buffer),
static_cast<const void*>(index_buffer)};
impl_->geometry_ptrs_.push_back(geometry_ptr);
impl_->geometry_sizes_.push_back({num_vertices, num_triangles});
return geom_id;
}

Expand Down
14 changes: 14 additions & 0 deletions cpp/open3d/t/geometry/RaycastingScene.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,20 @@ class RaycastingScene {

~RaycastingScene();

/// \brief Copy constructor.
/// Creates a new object as a copy of an existing one.
RaycastingScene(const RaycastingScene &other);

/// \brief Copy assignment operator.
/// Assigns the contents of an existing object to this object.
RaycastingScene &operator=(const RaycastingScene &other);

/// \brief Move constructor.
RaycastingScene(RaycastingScene &&other) noexcept;

/// \brief Move assignment operator.
RaycastingScene &operator=(RaycastingScene &&other) noexcept;

/// \brief Add a triangle mesh to the scene.
/// \param vertex_positions Vertices as Tensor of dim {N,3} and dtype float.
/// \param triangle_indices Triangles as Tensor of dim {M,3} and dtype
Expand Down
12 changes: 12 additions & 0 deletions cpp/pybind/t/geometry/raycasting_scene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,18 @@ Create a RaycastingScene.
device (open3d.core.Device): The device to use. Currently CPU and SYCL devices are supported.
)doc");

raycasting_scene.def(py::init<const RaycastingScene&>(), "other"_a, R"doc(
Create a RaycastingScene as a copy of another scene (copy constructor).

Args:
other (open3d.t.geometry.RaycastingScene): The scene to copy.
)doc");

raycasting_scene.def("__deepcopy__",
[](const RaycastingScene& self, py::dict /* memo */) {
return RaycastingScene(self);
});

raycasting_scene.def(
"add_triangles",
py::overload_cast<const core::Tensor&, const core::Tensor&>(
Expand Down
127 changes: 127 additions & 0 deletions python/test/t/geometry/test_raycasting_scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,3 +460,130 @@ def test_sphere_wrong_occupancy():
# we should get the same result with more samples
occupancy_3samples = scene.compute_occupancy(query_points, nsamples=3)
np.testing.assert_equal(occupancy_3samples.numpy(), expected)


# --- Tests for copy constructor and copy semantics. ---


def _make_scene_with_triangle(device):
"""Create a RaycastingScene with a single triangle for copy tests."""
vertices = o3d.core.Tensor([[0, 0, 0], [1, 0, 0], [1, 1, 0]],
dtype=o3d.core.float32,
device=device)
triangles = o3d.core.Tensor([[0, 1, 2]],
dtype=o3d.core.uint32,
device=device)
scene = o3d.t.geometry.RaycastingScene(device=device)
scene.add_triangles(vertices, triangles)
return scene


@pytest.mark.parametrize("device",
list_devices(enable_cuda=False, enable_sycl=True))
def test_copy_constructor_empty_scene(device):
scene = o3d.t.geometry.RaycastingScene(device=device)
scene_copy = o3d.t.geometry.RaycastingScene(scene)
rays = o3d.core.Tensor([[0, 0, 1, 0, 0, -1]],
dtype=o3d.core.float32,
device=device)
ans_orig = scene.cast_rays(rays)
ans_copy = scene_copy.cast_rays(rays)
invalid_id = o3d.t.geometry.RaycastingScene.INVALID_ID
assert ans_orig["geometry_ids"][0].cpu().item() == invalid_id
assert ans_copy["geometry_ids"][0].cpu().item() == invalid_id
assert np.isinf(ans_orig["t_hit"][0].item())
assert np.isinf(ans_copy["t_hit"][0].item())


@pytest.mark.parametrize("device",
list_devices(enable_cuda=False, enable_sycl=True))
def test_copy_constructor_scene_with_geometry(device):
"""Copy of a scene with geometry yields same cast_rays and compute_* results (tests clone)."""
scene = _make_scene_with_triangle(device)
scene_copy = o3d.t.geometry.RaycastingScene(scene)

rays = o3d.core.Tensor(
[[0.2, 0.1, 1, 0, 0, -1], [10, 10, 10, 1, 0, 0]],
dtype=o3d.core.float32,
device=device,
)
ans_orig = scene.cast_rays(rays)
ans_copy = scene_copy.cast_rays(rays)

np.testing.assert_allclose(ans_orig["t_hit"].numpy(),
ans_copy["t_hit"].numpy())
np.testing.assert_array_equal(ans_orig["geometry_ids"].numpy(),
ans_copy["geometry_ids"].numpy())

query = o3d.core.Tensor([[0.2, 0.1, 1], [10, 10, 10]],
dtype=o3d.core.float32,
device=device)
closest_orig = scene.compute_closest_points(query)
closest_copy = scene_copy.compute_closest_points(query)
np.testing.assert_allclose(closest_orig["points"].numpy(),
closest_copy["points"].numpy())
np.testing.assert_array_equal(closest_orig["geometry_ids"].numpy(),
closest_copy["geometry_ids"].numpy())

dist_orig = scene.compute_distance(query)
dist_copy = scene_copy.compute_distance(query)
np.testing.assert_allclose(dist_orig.numpy(), dist_copy.numpy())


@pytest.mark.parametrize("device",
list_devices(enable_cuda=False, enable_sycl=True))
def test_copy_independence(device):
"""Copy is independent: adding geometry to one does not affect the other."""
scene = _make_scene_with_triangle(device)
scene_copy = o3d.t.geometry.RaycastingScene(scene)

# Add another triangle only to the copy
v2 = o3d.core.Tensor([[0, 0, 1], [1, 0, 1], [0, 1, 1]],
dtype=o3d.core.float32,
device=device)
t2 = o3d.core.Tensor([[0, 1, 2]], dtype=o3d.core.uint32, device=device)
scene_copy.add_triangles(v2, t2)

invalid_id = o3d.t.geometry.RaycastingScene.INVALID_ID
# Ray hitting first triangle: both should hit
rays_one = o3d.core.Tensor(
[[0.2, 0.1, 0.5, 0, 0, -1]],
dtype=o3d.core.float32,
device=device,
)
ans_orig = scene.cast_rays(rays_one)
ans_copy = scene_copy.cast_rays(rays_one)
assert ans_orig["geometry_ids"][0].cpu().item() != invalid_id
assert ans_copy["geometry_ids"][0].cpu().item() != invalid_id

# Ray that would hit second triangle (only in copy): miss in original, hit in copy
rays_two = o3d.core.Tensor(
[[0.2, 0.1, 0.5, 0, 0, 1]],
dtype=o3d.core.float32,
device=device,
)
ans_orig2 = scene.cast_rays(rays_two)
ans_copy2 = scene_copy.cast_rays(rays_two)
assert ans_orig2["geometry_ids"][0].cpu().item() == invalid_id
assert ans_copy2["geometry_ids"][0].cpu().item() != invalid_id


@pytest.mark.parametrize("device",
list_devices(enable_cuda=False, enable_sycl=True))
def test_copy_module_deepcopy(device):
"""copy.deepcopy(scene) works and produces an independent copy."""
import copy as copy_module
scene = _make_scene_with_triangle(device)
scene_copy = copy_module.deepcopy(scene)

rays = o3d.core.Tensor(
[[0.2, 0.1, 1, 0, 0, -1]],
dtype=o3d.core.float32,
device=device,
)
ans_orig = scene.cast_rays(rays)
ans_copy = scene_copy.cast_rays(rays)
np.testing.assert_allclose(ans_orig["t_hit"].numpy(),
ans_copy["t_hit"].numpy())
assert ans_orig["geometry_ids"][0].cpu().item(
) == ans_copy["geometry_ids"][0].cpu().item()
Loading