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";
200 mat.
base_color = {float(mesh_color.x()), float(mesh_color.y()),
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) {
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
__host__ __device__ int2 abs(int2 v)
static double dist(double x1, double y1, double x2, double y2)
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