16 #include <unordered_map>
17 #include <unordered_set>
27 #define WANT_DEBUG_IMAGE 0
34 namespace visualization {
39 static const Eigen::Vector4f kBackgroundColor = {1.0f, 1.0f, 1.0f, 1.0f};
40 static const std::string kSelectablePointsName =
"__selectable_points";
44 static const unsigned int kMeshIndex = 0x00fffffe;
45 static const unsigned int kMaxPickableIndex = 0x00fffffd;
47 inline bool IsValidIndex(uint32_t idx) {
return (idx <= kMaxPickableIndex); }
49 Eigen::Vector3d CalcIndexColor(uint32_t idx) {
50 const double red = double((idx & 0x00ff0000) >> 16) / 255.0;
51 const double green = double((idx & 0x0000ff00) >> 8) / 255.0;
52 const double blue = double((idx & 0x000000ff)) / 255.0;
56 Eigen::Vector3d SetColorForIndex(uint32_t idx) {
57 return CalcIndexColor(std::min(kMaxPickableIndex, idx));
60 uint32_t GetIndexForColor(geometry::Image *
image,
int x,
int y) {
61 uint8_t *
rgb =
image->PointerAt<uint8_t>(
x,
y, 0);
62 const unsigned int red = (
static_cast<unsigned int>(
rgb[0]) << 16);
63 const unsigned int green = (
static_cast<unsigned int>(
rgb[1]) << 8);
64 const unsigned int blue = (
static_cast<unsigned int>(
rgb[2]));
77 Obj(
const std::string &n,
size_t start)
78 :
name(n), start_index(start) {};
82 void Clear() { objects_.clear(); }
85 void Add(
const std::string &
name,
size_t start_index) {
86 if (!objects_.empty() && objects_.back().start_index >= start_index) {
88 "start_index {} must be larger than all previously added "
90 start_index, objects_.back().start_index);
92 objects_.emplace_back(
name, start_index);
93 if (objects_[0].start_index != 0) {
95 "The first object's start_index must be 0, but got {}.",
96 objects_[0].start_index);
101 if (objects_.size() == 1) {
104 auto next = std::upper_bound(objects_.begin(), objects_.end(),
105 index, [](
size_t value,
const Obj &o) {
106 return value < o.start_index;
108 if (
next == objects_.end()) {
111 if (
next == objects_.end()) {
112 return objects_.back();
122 std::vector<Obj> objects_;
131 std::make_shared<rendering::CloudViewerScene>(scene->
GetRenderer());
133 picking_scene_->SetDownsampleThreshold(SIZE_MAX);
134 picking_scene_->SetBackground(kBackgroundColor);
136 picking_scene_->GetView()->ConfigureForColorPicking();
143 if (!points_.empty()) {
145 picking_scene_->GetScene()->OverrideMaterial(kSelectablePointsName,
151 const std::vector<SceneWidget::PickableGeometry> &geometry) {
155 picking_scene_->ClearGeometry();
161 for (
auto &pg : geometry) {
162 lookup_->
Add(pg.name, points_.size());
164 auto cloud =
dynamic_cast<const ccPointCloud *
>(pg.geometry);
167 auto mesh =
dynamic_cast<const ccMesh *
>(pg.geometry);
172 std::vector<Eigen::Vector3d> temp_points = cloud->getEigenPoints();
173 points_.insert(points_.end(), temp_points.begin(),
176 std::vector<Eigen::Vector3d> temp_points = mesh->getEigenVertices();
177 points_.insert(points_.end(), temp_points.begin(),
179 }
else if (tcloud || tmesh) {
180 const auto &tpoints = (tcloud ? tcloud->GetPoints()
181 : tmesh->GetVertexPositions());
182 const size_t n = tpoints.NumElements();
183 float *pts = (
float *)tpoints.GetDataPtr();
184 points_.reserve(points_.size() + n);
185 for (
size_t i = 0; i < n; i += 3) {
186 points_.emplace_back(
double(pts[i]), double(pts[i + 1]),
197 auto mesh_color = CalcIndexColor(kMeshIndex);
199 mat.
shader =
"unlitSolidColor";
201 float(mesh_color.z()), 1.0f};
204 picking_scene_->AddGeometry(pg.name, mesh, mat);
207 "PickPointsInteractor::SetPickableGeometry(): "
208 "CloudViewerScene cannot add a "
209 "t::geometry::TriangleMesh, "
210 "so points on the back side of the mesh '{}', will be "
218 if (points_.size() > kMaxPickableIndex) {
220 "Can only select from a maximumum of {} points; given {}",
221 kMaxPickableIndex, points_.size());
224 if (!points_.empty()) {
225 auto cloud = std::make_shared<ccPointCloud>(points_);
226 cloud->reserveTheRGBTable();
227 for (
size_t i = 0; i < cloud->size(); ++i) {
228 cloud->addEigenColor(SetColorForIndex(uint32_t(i)));
232 picking_scene_->AddGeometry(kSelectablePointsName, cloud.get(), mat);
233 picking_scene_->GetScene()->GeometryShadows(kSelectablePointsName,
241 return matrix_logic_;
246 const std::map<std::string,
247 std::vector<std::pair<size_t, Eigen::Vector3d>>>
254 std::function<
void(
const std::vector<Eigen::Vector2i> &)> on_ui) {
255 on_ui_changed_ = on_ui;
259 std::function<
void()> on_poly_pick) {
260 on_started_poly_pick_ = on_poly_pick;
266 if (pending_.empty() || pending_.back().keymods == 0) {
268 if (on_ui_changed_) {
272 pending_.back().polygon.push_back(
gui::Point(
e.x,
e.y));
273 if (on_started_poly_pick_) {
274 on_started_poly_pick_();
276 if (on_ui_changed_) {
277 std::vector<Eigen::Vector2i> lines;
278 auto &
polygon = pending_.back().polygon;
279 for (
size_t i = 1; i <
polygon.size(); ++i) {
282 lines.push_back({p0.x, p0.y});
283 lines.push_back({p1.x, p1.y});
287 on_ui_changed_(lines);
308 auto *view = picking_scene_->GetView();
309 view->SetViewport(0, 0,
312 view->GetCamera()->CopyFrom(camera_);
313 picking_scene_->GetRenderer().RenderToImage(
314 view, picking_scene_->GetScene(),
315 [
this](std::shared_ptr<geometry::Image> img) {
317 std::cout <<
"[debug] Writing pick image to "
318 <<
"/tmp/debug.png" << std::endl;
319 io::WriteImage(
"/tmp/debug.png", *img);
321 this->OnPickImageDone(img);
329 while (!pending_.empty()) {
332 if (on_ui_changed_) {
340 mat.
shader =
"unlitPolygonOffset";
349 std::shared_ptr<geometry::Image> img) {
355 if (on_ui_changed_) {
359 std::map<std::string, std::vector<std::pair<size_t, Eigen::Vector3d>>>
361 while (!pending_.empty()) {
362 PickInfo &info = pending_.back();
363 auto *img = pick_image_.get();
365 if (info.polygon.size() == 1) {
366 const int x0 = info.polygon[0].x;
367 const int y0 = info.polygon[0].y;
371 std::unordered_map<unsigned int, Score> candidates;
372 auto clicked_idx = GetIndexForColor(img, x0, y0);
377 if (clicked_idx == kMeshIndex || clicked_idx >= points_.size()) {
379 radius = 5 * point_size_;
385 radius = 2 * point_size_;
387 for (
int y = y0 - radius;
y < y0 + radius; ++
y) {
388 for (
int x = x0 - radius;
x < x0 + radius; ++
x) {
389 unsigned int idx = GetIndexForColor(img,
x,
y);
390 if (IsValidIndex(idx) && idx < points_.size()) {
391 float dist = std::sqrt(
float((
x - x0) * (
x - x0) +
392 (
y - y0) * (
y - y0)));
393 candidates[idx].score += radius - dist;
397 if (!candidates.empty()) {
402 float best_score = -1e30f;
403 unsigned int best_idx = (
unsigned int)-1;
404 for (
auto &idx_score : candidates) {
405 if (idx_score.second.score > best_score) {
406 best_score = idx_score.second.score;
407 best_idx = idx_score.first;
411 size_t obj_idx = best_idx - o.start_index;
412 indices[o.name].push_back(std::pair<size_t, Eigen::Vector3d>(
413 obj_idx, points_[best_idx]));
425 std::unordered_set<unsigned int> raw_indices;
428 int minY = 1000000, maxY = -1000000;
429 for (
auto &p : info.polygon) {
430 minY = std::min(minY, p.y);
431 maxY = std::max(maxY, p.y);
434 info.polygon.push_back(info.polygon[0]);
436 const double kInf = 1e18;
437 std::vector<double> m, b;
438 m.reserve(info.polygon.size() - 1);
439 b.reserve(info.polygon.size() - 1);
440 for (
size_t i = 1; i < info.polygon.size(); ++i) {
441 int m_denom = info.polygon[i].x - info.polygon[i - 1].x;
447 m.push_back(
double(info.polygon[i].y - info.polygon[i - 1].y) /
449 if (m.back() == 0.0) {
450 b.push_back(info.polygon[i].y);
452 b.push_back(info.polygon[i].y -
453 m.back() * info.polygon[i].x);
457 std::vector<bool> is_vert_corner(info.polygon.size(),
false);
458 for (
size_t i = 0; i < info.polygon.size() - 1; ++i) {
461 prev = info.polygon.size() - 2;
464 int lastY = info.polygon[prev].y;
465 int thisY = info.polygon[i].y;
466 int nextY = info.polygon[
next].y;
467 if ((thisY > lastY && thisY > nextY) ||
468 (thisY < lastY && thisY < nextY)) {
469 is_vert_corner[i] =
true;
472 is_vert_corner.back() = is_vert_corner[0];
473 std::unordered_set<int> intersectionsX;
474 std::vector<int> sortedX;
475 intersectionsX.reserve(32);
477 for (
int y = minY;
y <= maxY; ++
y) {
478 for (
size_t i = 0; i < m.size(); ++i) {
479 if ((
y < info.polygon[i].y &&
y < info.polygon[i + 1].y) ||
480 (
y > info.polygon[i].y &&
y > info.polygon[i + 1].y)) {
484 intersectionsX.insert({info.polygon[i].x});
485 intersectionsX.insert({info.polygon[i + 1].x});
486 }
else if (m[i] == kInf) {
487 bool is_corner = (
y == info.polygon[i].y);
488 intersectionsX.insert({info.polygon[i].x});
490 intersectionsX.insert({info.polygon[i].x});
493 double x = (double(
y) - b[i]) / m[i];
495 (
y == info.polygon[i].y &&
496 std::abs(
x -
double(info.polygon[i].x)) < 0.5);
498 (
y == info.polygon[i + 1].y &&
499 std::abs(
x -
double(info.polygon[i + 1].x)) <
501 if ((is_corner0 && is_vert_corner[i]) ||
502 (is_corner1 && is_vert_corner[i + 1])) {
506 intersectionsX.insert(
int(std::round(
x)));
510 for (
auto x : intersectionsX) {
511 sortedX.push_back(
x);
513 std::sort(sortedX.begin(), sortedX.end());
518 if (sortedX.size() % 2 == 1) {
520 for (
size_t i = 0; i < info.polygon.size() - 1; ++i) {
521 s <<
"(" << info.polygon[i].x <<
", "
522 << info.polygon[i].y <<
") ";
525 "Internal error: Odd number of points for row "
526 "segments (should be even).");
530 for (
size_t i = 0; i < sortedX.size(); ++i) {
531 s << sortedX[i] <<
" ";
537 sortedX.push_back(sortedX.back());
541 for (
size_t i = 0; i < sortedX.size(); i += 2) {
542 int startX = sortedX[i];
543 int endX = sortedX[i + 1];
544 for (
int x = startX;
x <= endX; ++
x) {
545 unsigned int idx = GetIndexForColor(img,
x,
y);
546 if (IsValidIndex(idx) && idx < points_.size()) {
547 raw_indices.insert(idx);
551 intersectionsX.clear();
555 for (
auto idx : raw_indices) {
557 size_t obj_idx = idx - o.start_index;
558 indices[o.name].push_back(std::pair<size_t, Eigen::Vector3d>(
559 obj_idx, points_[idx]));
565 if (on_picked_ && !indices.empty()) {
566 on_picked_(indices, info.keymods);
std::shared_ptr< core::Tensor > image
boost::geometry::model::polygon< point_xy > polygon
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
A point cloud contains a list of 3D points.
A triangle mesh contains vertices and triangles.
void SetOnUIChanged(std::function< void(const std::vector< Eigen::Vector2i > &)>)
void SetOnStartedPolygonPicking(std::function< void()> on_poly_pick)
Calls the provided function when polygon picking is initiated.
void Mouse(const MouseEvent &e) override
void SetPickableGeometry(const std::vector< SceneWidget::PickableGeometry > &geometry)
rendering::MaterialRecord MakeMaterial()
virtual ~PickPointsInteractor()
rendering::MatrixInteractorLogic & GetMatrixInteractor() override
void Key(const KeyEvent &e) override
PickPointsInteractor(rendering::CloudViewerScene *scene, rendering::Camera *camera)
void OnPickImageDone(std::shared_ptr< geometry::Image > img)
void SetPointSize(int px)
void SetOnPointsPicked(std::function< void(const std::map< std::string, std::vector< std::pair< size_t, Eigen::Vector3d >>> &, int)> f)
void Add(const std::string &name, size_t start_index)
const Obj & ObjectForIndex(size_t index)
Renderer & GetRenderer() const
int GetViewHeight() const
Generic file read and write utility for python interface.
constexpr Rgb red(MAX, 0, 0)
constexpr Rgb blue(0, 0, MAX)
constexpr Rgb green(0, MAX, 0)
Eigen::Vector4f base_color