Lazy collider (#1473)
* lazy collider * format * address comments * avoid std::shared_ptr<T>::unique() * try to fix maybe uninitialized warning * remove unused recorder wrapper
This commit is contained in:
@@ -21,6 +21,7 @@ set(
|
||||
edge_op.cpp
|
||||
face_op.cpp
|
||||
impl.cpp
|
||||
lazy_collider.cpp
|
||||
manifold.cpp
|
||||
polygon.cpp
|
||||
properties.cpp
|
||||
@@ -43,6 +44,7 @@ set(
|
||||
hashtable.h
|
||||
impl.h
|
||||
iters.h
|
||||
lazy_collider.h
|
||||
mesh_fixes.h
|
||||
parallel.h
|
||||
quickhull.h
|
||||
|
||||
+2
-3
@@ -371,7 +371,7 @@ Intersections Intersect12_(const Manifold::Impl& inP,
|
||||
a.vertPos_[a.halfedge_[i].endVert])
|
||||
: Box();
|
||||
};
|
||||
b.collider_.Collisions<false>(f, a.halfedge_.size(), recorder);
|
||||
b.collider_->Collisions<false>(recorder, f, a.halfedge_.size());
|
||||
|
||||
Intersections result = recorder.get();
|
||||
auto& p1q2 = result.p1q2;
|
||||
@@ -454,8 +454,7 @@ Vec<int> Winding03_(const Manifold::Impl& inP, const Manifold::Impl& inQ,
|
||||
};
|
||||
auto recorder = MakeSimpleRecorder(recorderf);
|
||||
auto f = [&](int i) { return a.vertPos_[verts[i]]; };
|
||||
b.collider_.Collisions<false, decltype(f), decltype(recorder)>(
|
||||
f, verts.size(), recorder);
|
||||
b.collider_->Collisions<false>(recorder, f, verts.size());
|
||||
// flood fill
|
||||
for_each(autoPolicy(w03.size()), countAt(0), countAt(w03.size()),
|
||||
[&](size_t i) {
|
||||
|
||||
+34
-47
@@ -157,17 +157,21 @@ struct CreateRadixTree {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename F, const bool selfCollision, typename Recorder>
|
||||
template <typename F, const bool selfCollision, const bool hasTransform,
|
||||
typename Recorder>
|
||||
struct FindCollision {
|
||||
F& f;
|
||||
VecView<const Box> nodeBBox_;
|
||||
VecView<const std::pair<int, int>> internalChildren_;
|
||||
Recorder& recorder;
|
||||
mat3x4 transform;
|
||||
|
||||
using Local = typename Recorder::Local;
|
||||
|
||||
inline int RecordCollision(int node, const int queryIdx, Local& local) {
|
||||
bool overlaps = nodeBBox_[node].DoesOverlap(f(queryIdx));
|
||||
auto box = nodeBBox_[node];
|
||||
if (hasTransform) box = box.Transform(transform);
|
||||
bool overlaps = box.DoesOverlap(f(queryIdx));
|
||||
if (overlaps && IsLeaf(node)) {
|
||||
int leafIdx = Node2Leaf(node);
|
||||
if (!selfCollision || leafIdx != queryIdx) {
|
||||
@@ -224,11 +228,6 @@ struct BuildInternalBoxes {
|
||||
}
|
||||
};
|
||||
|
||||
struct TransformBox {
|
||||
const mat3x4 transform;
|
||||
void operator()(Box& box) { box = box.Transform(transform); }
|
||||
};
|
||||
|
||||
constexpr inline uint32_t SpreadBits3(uint32_t v) {
|
||||
v = 0xFF0000FFu & (v * 0x00010001u);
|
||||
v = 0x0F00F00Fu & (v * 0x00000101u);
|
||||
@@ -264,6 +263,7 @@ class Collider {
|
||||
ZoneScoped;
|
||||
DEBUG_ASSERT(leafBB.size() == leafMorton.size(), userErr,
|
||||
"vectors must be the same length");
|
||||
if (leafBB.size() == 0) return;
|
||||
int num_nodes = 2 * leafBB.size() - 1;
|
||||
// assign and allocate members
|
||||
nodeBBox_.resize_nofill(num_nodes);
|
||||
@@ -276,24 +276,6 @@ class Collider {
|
||||
UpdateBoxes(leafBB);
|
||||
}
|
||||
|
||||
bool Transform(mat3x4 transform) {
|
||||
ZoneScoped;
|
||||
bool axisAligned = true;
|
||||
for (int row : {0, 1, 2}) {
|
||||
int count = 0;
|
||||
for (int col : {0, 1, 2}) {
|
||||
if (transform[col][row] == 0.0) ++count;
|
||||
}
|
||||
if (count != 2) axisAligned = false;
|
||||
}
|
||||
if (axisAligned) {
|
||||
for_each(autoPolicy(nodeBBox_.size(), 1e5), nodeBBox_.begin(),
|
||||
nodeBBox_.end(),
|
||||
[transform](Box& box) { box = box.Transform(transform); });
|
||||
}
|
||||
return axisAligned;
|
||||
}
|
||||
|
||||
void UpdateBoxes(const VecView<const Box>& leafBB) {
|
||||
ZoneScoped;
|
||||
DEBUG_ASSERT(leafBB.size() == NumLeaves(), userErr,
|
||||
@@ -309,6 +291,29 @@ class Collider {
|
||||
{nodeBBox_, counter, nodeParent_, internalChildren_}));
|
||||
}
|
||||
|
||||
template <const bool selfCollision = false, typename F, typename Recorder>
|
||||
void Collisions(Recorder& recorder, std::optional<mat3x4> transform, F f,
|
||||
int n, bool parallel = true) const {
|
||||
ZoneScoped;
|
||||
using collider_internal::FindCollision;
|
||||
if (internalChildren_.empty()) return;
|
||||
if (transform) {
|
||||
for_each_n(
|
||||
parallel ? autoPolicy(n, collider_internal::kSequentialThreshold)
|
||||
: ExecutionPolicy::Seq,
|
||||
countAt(0), n,
|
||||
FindCollision<decltype(f), selfCollision, true, Recorder>{
|
||||
f, nodeBBox_, internalChildren_, recorder, transform.value()});
|
||||
} else {
|
||||
for_each_n(parallel
|
||||
? autoPolicy(n, collider_internal::kSequentialThreshold)
|
||||
: ExecutionPolicy::Seq,
|
||||
countAt(0), n,
|
||||
FindCollision<decltype(f), selfCollision, false, Recorder>{
|
||||
f, nodeBBox_, internalChildren_, recorder, la::identity});
|
||||
}
|
||||
}
|
||||
|
||||
// This function iterates over queriesIn and calls recorder.record(queryIdx,
|
||||
// leafIdx, local) for each collision it found.
|
||||
// If selfCollisionl is true, it will skip the case where queryIdx == leafIdx.
|
||||
@@ -319,30 +324,12 @@ class Collider {
|
||||
//
|
||||
// If thread local storage is not needed, use SimpleRecorder.
|
||||
template <const bool selfCollision = false, typename T, typename Recorder>
|
||||
void Collisions(const VecView<const T>& queriesIn, Recorder& recorder,
|
||||
void Collisions(Recorder& recorder, std::optional<mat3x4> transform,
|
||||
const VecView<const T>& queriesIn,
|
||||
bool parallel = true) const {
|
||||
ZoneScoped;
|
||||
using collider_internal::FindCollision;
|
||||
if (internalChildren_.empty()) return;
|
||||
auto f = [queriesIn](const int i) { return queriesIn[i]; };
|
||||
for_each_n(parallel ? autoPolicy(queriesIn.size(),
|
||||
collider_internal::kSequentialThreshold)
|
||||
: ExecutionPolicy::Seq,
|
||||
countAt(0), queriesIn.size(),
|
||||
FindCollision<decltype(f), selfCollision, Recorder>{
|
||||
f, nodeBBox_, internalChildren_, recorder});
|
||||
}
|
||||
|
||||
template <const bool selfCollision = false, typename F, typename Recorder>
|
||||
void Collisions(F f, int n, Recorder& recorder, bool parallel = true) const {
|
||||
ZoneScoped;
|
||||
using collider_internal::FindCollision;
|
||||
if (internalChildren_.empty()) return;
|
||||
for_each_n(parallel ? autoPolicy(n, collider_internal::kSequentialThreshold)
|
||||
: ExecutionPolicy::Seq,
|
||||
countAt(0), n,
|
||||
FindCollision<decltype(f), selfCollision, Recorder>{
|
||||
f, nodeBBox_, internalChildren_, recorder});
|
||||
Collisions<selfCollision>(recorder, transform, f, queriesIn.size(),
|
||||
parallel);
|
||||
}
|
||||
|
||||
static uint32_t MortonCode(vec3 position, Box bBox) {
|
||||
|
||||
+1
-1
@@ -274,7 +274,7 @@ Polygons Manifold::Impl::Slice(double height) const {
|
||||
};
|
||||
|
||||
auto recorder = MakeSimpleRecorder(recordCollision);
|
||||
collider_.Collisions<false>(query.cview(), recorder, false);
|
||||
collider_->Collisions<false>(recorder, query.cview(), false);
|
||||
|
||||
Polygons polys;
|
||||
while (!tris.empty()) {
|
||||
|
||||
+14
-18
@@ -572,18 +572,6 @@ void Manifold::Impl::CreateHalfedges(const Vec<ivec3>& triProp,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a full recalculation of the face bounding boxes, including updating
|
||||
* the collider, but does not resort the faces.
|
||||
*/
|
||||
void Manifold::Impl::Update() {
|
||||
CalculateBBox();
|
||||
Vec<Box> faceBox;
|
||||
Vec<uint32_t> faceMorton;
|
||||
GetFaceBoxMorton(faceBox, faceMorton);
|
||||
collider_.UpdateBoxes(faceBox);
|
||||
}
|
||||
|
||||
void Manifold::Impl::MakeEmpty(Error status) {
|
||||
bBox_ = Box();
|
||||
vertPos_.clear();
|
||||
@@ -592,6 +580,7 @@ void Manifold::Impl::MakeEmpty(Error status) {
|
||||
faceNormal_.clear();
|
||||
halfedgeTangent_.clear();
|
||||
meshRelation_ = MeshRelationD();
|
||||
collider_ = std::make_shared<LazyCollider>(LazyCollider::Empty());
|
||||
status_ = status;
|
||||
}
|
||||
|
||||
@@ -608,7 +597,7 @@ void Manifold::Impl::WarpBatch(std::function<void(VecView<vec3>)> warpFunc) {
|
||||
MakeEmpty(Error::NonFiniteVertex);
|
||||
return;
|
||||
}
|
||||
Update();
|
||||
CalculateBBox();
|
||||
faceNormal_.clear(); // force recalculation of triNormal
|
||||
SetEpsilon();
|
||||
Finish();
|
||||
@@ -629,7 +618,6 @@ Manifold::Impl Manifold::Impl::Transform(const mat3x4& transform_) const {
|
||||
result.MakeEmpty(Error::NonFiniteVertex);
|
||||
return result;
|
||||
}
|
||||
result.collider_ = collider_;
|
||||
result.meshRelation_ = meshRelation_;
|
||||
result.epsilon_ = epsilon_;
|
||||
result.tolerance_ = tolerance_;
|
||||
@@ -669,15 +657,23 @@ Manifold::Impl Manifold::Impl::Transform(const mat3x4& transform_) const {
|
||||
FlipTris({result.halfedge_}));
|
||||
}
|
||||
|
||||
// This optimization does a cheap collider update if the transform is
|
||||
// axis-aligned.
|
||||
if (!result.collider_.Transform(transform_)) result.Update();
|
||||
|
||||
result.CalculateBBox();
|
||||
// Scale epsilon by the norm of the 3x3 portion of the transform.
|
||||
result.epsilon_ *= SpectralNorm(mat3(transform_));
|
||||
// Maximum of inherited epsilon loss and translational epsilon loss.
|
||||
result.SetEpsilon(result.epsilon_);
|
||||
|
||||
if (LazyCollider::IsAxisAligned(transform_)) {
|
||||
result.collider_ = std::make_shared<LazyCollider>(collider_, transform_);
|
||||
} else if (!result.IsEmpty()) {
|
||||
Vec<Box> faceBox;
|
||||
Vec<uint32_t> faceMorton;
|
||||
result.GetFaceBoxMorton(faceBox, faceMorton);
|
||||
result.collider_ =
|
||||
std::make_shared<LazyCollider>(collider_, std::move(faceBox));
|
||||
} else {
|
||||
result.collider_ = std::make_shared<LazyCollider>(LazyCollider::Empty());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
+3
-4
@@ -15,7 +15,7 @@
|
||||
#pragma once
|
||||
#include <map>
|
||||
|
||||
#include "collider.h"
|
||||
#include "lazy_collider.h"
|
||||
#include "manifold/common.h"
|
||||
#include "manifold/manifold.h"
|
||||
#include "shared.h"
|
||||
@@ -54,12 +54,12 @@ struct Manifold::Impl {
|
||||
Vec<vec3> faceNormal_;
|
||||
Vec<vec4> halfedgeTangent_;
|
||||
MeshRelationD meshRelation_;
|
||||
Collider collider_;
|
||||
std::shared_ptr<LazyCollider> collider_ = std::make_shared<LazyCollider>();
|
||||
|
||||
static std::atomic<uint32_t> meshIDCounter_;
|
||||
static uint32_t ReserveIDs(uint32_t);
|
||||
|
||||
Impl() {}
|
||||
Impl() : collider_{std::make_shared<LazyCollider>(LazyCollider::Empty())} {};
|
||||
enum class Shape { Tetrahedron, Cube, Octahedron };
|
||||
Impl(Shape, const mat3x4 = la::identity);
|
||||
|
||||
@@ -300,7 +300,6 @@ struct Manifold::Impl {
|
||||
void CalculateNormals();
|
||||
void IncrementMeshIDs();
|
||||
|
||||
void Update();
|
||||
void MakeEmpty(Error status);
|
||||
void Warp(std::function<void(vec3&)> warpFunc);
|
||||
void WarpBatch(std::function<void(VecView<vec3>)> warpFunc);
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
// Copyright 2025 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "lazy_collider.h"
|
||||
|
||||
namespace manifold {
|
||||
LazyCollider::LazyCollider() = default;
|
||||
|
||||
LazyCollider::LazyCollider(LeafData&& leafData) : leafData_{leafData} {}
|
||||
|
||||
LazyCollider::LazyCollider(std::shared_ptr<const LazyCollider> base,
|
||||
const mat3x4& transform) {
|
||||
DEBUG_ASSERT(base != nullptr, logicErr, "invalid base ptr");
|
||||
mat3x4 composed = transform;
|
||||
// attempt to unwrap one level, if the base is also a transformed collider
|
||||
// we do not unwrap if the base has updatedLeafBox
|
||||
std::lock_guard<std::mutex> lock(base->mutex_);
|
||||
if (base->base_ && !base->base_->updatedLeafBox.has_value()) {
|
||||
composed = composed * Mat4(base->base_->transform);
|
||||
base = base->base_->base;
|
||||
}
|
||||
base_ = Base{base, std::nullopt, composed};
|
||||
}
|
||||
|
||||
LazyCollider::LazyCollider(std::shared_ptr<const LazyCollider> base,
|
||||
std::optional<Vec<Box>> updatedLeafBox) {
|
||||
DEBUG_ASSERT(base != nullptr, logicErr, "invalid base ptr");
|
||||
// attempt to unwrap base
|
||||
// there are two cases:
|
||||
// 1. base contains updatedLeafBox, which means that its points to the root
|
||||
// 2. base is a transformed node, which either points to the root or points to
|
||||
// a node with updatedLeafBox, which is case 1
|
||||
// For case 2, the max depth is 2, and in any case we ignore transforms
|
||||
std::lock_guard<std::mutex> lock(base->mutex_);
|
||||
if (base->base_) base = base->base_->base;
|
||||
if (base->base_) base = base->base_->base;
|
||||
base_ = Base{base, std::move(updatedLeafBox), la::identity};
|
||||
}
|
||||
|
||||
LazyCollider::LazyCollider(const LazyCollider& other) {
|
||||
std::lock_guard<std::mutex> lock(other.mutex_);
|
||||
built_ = other.built_;
|
||||
leafData_ = other.leafData_;
|
||||
base_ = other.base_;
|
||||
}
|
||||
|
||||
LazyCollider::LazyCollider(LazyCollider&& other) noexcept {
|
||||
std::lock_guard<std::mutex> lock(other.mutex_);
|
||||
built_ = std::move(other.built_);
|
||||
leafData_ = std::move(other.leafData_);
|
||||
base_ = std::move(other.base_);
|
||||
}
|
||||
|
||||
LazyCollider& LazyCollider::operator=(const LazyCollider& other) {
|
||||
if (this == &other) return *this;
|
||||
std::scoped_lock lock(mutex_, other.mutex_);
|
||||
built_ = other.built_;
|
||||
leafData_ = other.leafData_;
|
||||
base_ = other.base_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
LazyCollider& LazyCollider::operator=(LazyCollider&& other) noexcept {
|
||||
if (this == &other) return *this;
|
||||
std::scoped_lock lock(mutex_, other.mutex_);
|
||||
built_ = std::move(other.built_);
|
||||
leafData_ = std::move(other.leafData_);
|
||||
base_ = std::move(other.base_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool LazyCollider::IsBuilt() const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return built_.has_value();
|
||||
}
|
||||
|
||||
bool LazyCollider::IsAxisAligned(const mat3x4& transform) {
|
||||
for (int row : {0, 1, 2}) {
|
||||
int count = 0;
|
||||
for (int col : {0, 1, 2}) {
|
||||
if (transform[col][row] == 0.0) ++count;
|
||||
}
|
||||
if (count != 2) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const LazyCollider::Built& LazyCollider::EnsureBuilt() const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (built_) return built_.value();
|
||||
|
||||
if (base_) {
|
||||
const Built& baseBuilt = base_->base->EnsureBuilt();
|
||||
// no need to propagate transform from built, we already computed the
|
||||
// composed transform in the constructor
|
||||
built_ = {baseBuilt.collider, base_->transform};
|
||||
if (base_->updatedLeafBox) {
|
||||
auto leafBox = std::move(base_->updatedLeafBox.value());
|
||||
base_ = std::nullopt;
|
||||
// need a unique collider, it may be possible that the base reference to
|
||||
// the collider was dropped when we reset the base_ pointer
|
||||
if (built_->collider.use_count() != 1)
|
||||
built_->collider = std::make_shared<Collider>(*built_->collider);
|
||||
built_->collider->UpdateBoxes(leafBox);
|
||||
// note that after updating the boxes, we don't need transform
|
||||
built_->transform = std::nullopt;
|
||||
}
|
||||
base_ = std::nullopt;
|
||||
} else {
|
||||
DEBUG_ASSERT(leafData_.has_value(), logicErr, "uninitialized collider");
|
||||
built_ = {
|
||||
std::make_shared<Collider>(leafData_->leafBox, leafData_->leafMorton),
|
||||
std::nullopt};
|
||||
leafData_ = std::nullopt;
|
||||
}
|
||||
return built_.value();
|
||||
}
|
||||
|
||||
} // namespace manifold
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright 2025 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include "collider.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
class LazyCollider {
|
||||
public:
|
||||
struct LeafData {
|
||||
Vec<Box> leafBox;
|
||||
Vec<uint32_t> leafMorton;
|
||||
};
|
||||
|
||||
struct Built {
|
||||
std::shared_ptr<Collider> collider;
|
||||
std::optional<mat3x4> transform;
|
||||
};
|
||||
|
||||
struct Base {
|
||||
std::shared_ptr<const LazyCollider> base;
|
||||
std::optional<Vec<Box>> updatedLeafBox;
|
||||
mat3x4 transform;
|
||||
};
|
||||
|
||||
static LazyCollider Empty() { return LazyCollider(LeafData{{}, {}}); }
|
||||
LazyCollider();
|
||||
LazyCollider(LeafData&& leafData);
|
||||
LazyCollider(std::shared_ptr<const LazyCollider> base,
|
||||
const mat3x4& transform);
|
||||
LazyCollider(std::shared_ptr<const LazyCollider> base,
|
||||
std::optional<Vec<Box>> updatedLeafBox);
|
||||
LazyCollider(const LazyCollider& other);
|
||||
LazyCollider(LazyCollider&& other) noexcept;
|
||||
LazyCollider& operator=(const LazyCollider& other);
|
||||
LazyCollider& operator=(LazyCollider&& other) noexcept;
|
||||
~LazyCollider() = default;
|
||||
|
||||
bool IsBuilt() const;
|
||||
|
||||
template <const bool selfCollision = false, typename Recorder,
|
||||
typename... Args>
|
||||
void Collisions(Recorder& recorder, Args... args) const {
|
||||
const Built& built = EnsureBuilt();
|
||||
built.collider->Collisions<false>(recorder, built.transform, args...);
|
||||
}
|
||||
|
||||
static bool IsAxisAligned(const mat3x4& transform);
|
||||
|
||||
private:
|
||||
const Built& EnsureBuilt() const;
|
||||
|
||||
mutable std::mutex mutex_;
|
||||
|
||||
mutable std::optional<Built> built_;
|
||||
mutable std::optional<LeafData> leafData_;
|
||||
mutable std::optional<Base> base_;
|
||||
};
|
||||
|
||||
} // namespace manifold
|
||||
+2
-3
@@ -204,7 +204,7 @@ bool Manifold::Impl::IsSelfIntersecting() const {
|
||||
};
|
||||
|
||||
auto recorder = MakeSimpleRecorder(f);
|
||||
collider_.Collisions<true>(faceBox.cview(), recorder);
|
||||
collider_->Collisions<true>(recorder, faceBox.cview());
|
||||
|
||||
return intersecting.load();
|
||||
}
|
||||
@@ -416,8 +416,7 @@ double Manifold::Impl::MinGap(const Manifold::Impl& other,
|
||||
});
|
||||
|
||||
MinDistanceRecorder recorder{*this, other};
|
||||
collider_.Collisions<false, Box, MinDistanceRecorder>(faceBoxOther.cview(),
|
||||
recorder, false);
|
||||
collider_->Collisions<false>(recorder, faceBoxOther.cview(), false);
|
||||
double minDistanceSquared =
|
||||
std::min(recorder.get(), searchLength * searchLength);
|
||||
return sqrt(minDistanceSquared);
|
||||
|
||||
+13
-8
@@ -159,7 +159,7 @@ bool MergeMeshGLP(MeshGLP<Precision, I>& mesh) {
|
||||
return uf.unite(openVerts[a], openVerts[b]);
|
||||
};
|
||||
auto recorder = MakeSimpleRecorder(f);
|
||||
collider.Collisions<true>(vertBox.cview(), recorder, false);
|
||||
collider.Collisions<true>(recorder, std::nullopt, vertBox.cview(), false);
|
||||
|
||||
for (size_t i = 0; i < mesh.mergeFromVert.size(); ++i) {
|
||||
uf.unite(static_cast<int>(mesh.mergeFromVert[i]),
|
||||
@@ -188,7 +188,10 @@ namespace manifold {
|
||||
* and halfedges flagged for removal (NaN verts and -1 halfedges).
|
||||
*/
|
||||
void Manifold::Impl::Finish() {
|
||||
if (halfedge_.size() == 0) return;
|
||||
if (halfedge_.size() == 0) {
|
||||
collider_ = std::make_shared<LazyCollider>(LazyCollider::Empty());
|
||||
return;
|
||||
}
|
||||
|
||||
CalculateBBox();
|
||||
SetEpsilon(epsilon_);
|
||||
@@ -199,11 +202,14 @@ void Manifold::Impl::Finish() {
|
||||
}
|
||||
|
||||
SortVerts();
|
||||
Vec<Box> faceBox;
|
||||
Vec<uint32_t> faceMorton;
|
||||
GetFaceBoxMorton(faceBox, faceMorton);
|
||||
SortFaces(faceBox, faceMorton);
|
||||
if (halfedge_.size() == 0) return;
|
||||
LazyCollider::LeafData leafData;
|
||||
GetFaceBoxMorton(leafData.leafBox, leafData.leafMorton);
|
||||
SortFaces(leafData.leafBox, leafData.leafMorton);
|
||||
if (halfedge_.size() == 0) {
|
||||
collider_ = std::make_shared<LazyCollider>(LazyCollider::Empty());
|
||||
return;
|
||||
}
|
||||
collider_ = std::make_shared<LazyCollider>(std::move(leafData));
|
||||
CompactProps();
|
||||
|
||||
DEBUG_ASSERT(halfedge_.size() % 6 == 0, topologyErr,
|
||||
@@ -247,7 +253,6 @@ void Manifold::Impl::Finish() {
|
||||
"faceNormal size = " + std::to_string(faceNormal_.size()) +
|
||||
", NumTri = " + std::to_string(NumTri()));
|
||||
CalculateNormals();
|
||||
collider_ = Collider(faceBox, faceMorton);
|
||||
|
||||
DEBUG_ASSERT(Is2Manifold(), logicErr, "mesh is not 2-manifold!");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user