33#include <Eigen/Geometry>
48 constexpr std::size_t MinPolygonSize = 3;
50 const std::vector<std::string> VertexContextMenu{
"Insert point after",
51 "Insert point before",
54 const std::vector<std::string> EdgeContextMenu{
"Cut edge here"};
58 describe(
const viz::InteractionFeedback& interaction)
65 description +=
interaction.isTransformBegin() ?
"begin " :
"";
66 description +=
interaction.isTransformDuring() ?
"during " :
"";
67 description +=
interaction.isTransformEnd() ?
"end" :
"";
71 return description +
" on `" +
interaction.element() +
"`";
75 to3D(
const Eigen::Vector2f& point,
float z)
77 return Eigen::Vector3f{point.x(), point.y(), z};
81 centroid(
const std::vector<Eigen::Vector2f>& polygon)
83 Eigen::Vector2f sum = Eigen::Vector2f::Zero();
84 for (
const auto& point : polygon)
88 return sum /
static_cast<float>(std::max<std::size_t>(polygon.size(), 1));
92 std::vector<Eigen::Vector2f>
93 applyRigidMotion(
const std::vector<Eigen::Vector2f>& polygon,
94 const Eigen::Vector2f& pivot,
96 const Eigen::Vector2f& translation)
98 const Eigen::Rotation2Df rotation(
angle);
100 std::vector<Eigen::Vector2f> result;
101 result.reserve(polygon.size());
102 for (
const auto& point : polygon)
104 result.push_back(pivot + rotation * (point - pivot) + translation);
111 arviz(
std::move(arviz)), parameters(parameters)
118 pendingTransform.reset();
122 const std::vector<EditableRoom>&
131 roomChangedCallback = std::move(callback);
137 return pendingTransform.has_value();
143 if (handleLayer.data_.name.empty())
153 if (interaction.layer() != handleLayer.
data_.name)
158 handleInteraction(interaction, stage);
170 not interaction.isTransformEnd();
177 ARMARX_INFO <<
"Interaction: " << describe(interaction);
180 const auto it = handleByElement.find(interaction.element());
181 if (it == handleByElement.end())
183 ARMARX_VERBOSE <<
"Interaction with unknown element `" << interaction.element() <<
"`.";
187 const Handle handle = it->second;
189 switch (interaction.type())
193 const Eigen::Matrix4f
transform = interaction.transformation();
194 const Eigen::Vector3f translation =
transform.block<3, 1>(0, 3);
197 if (handle.type == Handle::Type::Move)
199 const Eigen::Matrix3f rotation =
transform.block<3, 3>(0, 0);
200 const float angle = std::atan2(rotation(1, 0), rotation(0, 0));
202 pendingTransform = PendingTransform{.element = interaction.element(),
205 .translation = translation.head<2>()};
212 .position = handlePosition(handle) + translation.head<2>()};
217 applyPendingTransform(stage);
230 if (pendingTransform.has_value() and
231 pendingTransform->element ==
interaction.element())
233 applyPendingTransform(stage);
240 handleContextMenu(handle,
interaction.chosenContextMenuEntry(), stage);
251 RoomEditor::applyPendingTransform(viz::StagedCommit& stage)
253 if (not pendingTransform.has_value())
258 const PendingTransform pending = *pendingTransform;
259 pendingTransform.reset();
261 EditableRoom& editable = rooms_.at(pending.handle.roomIndex);
262 std::vector<Eigen::Vector2f>& polygon = editable.room.polygon;
264 if (pending.handle.type == Handle::Type::Move)
266 polygon = applyRigidMotion(
267 polygon, centroid(polygon), pending.rotation, pending.translation);
269 ARMARX_INFO <<
"Moved room `" << editable.room.name <<
"` by ["
270 << pending.translation.x() <<
", " << pending.translation.y()
271 <<
"] and rotated it by " << pending.rotation <<
" rad.";
273 notifyRoomChanged(pending.handle.roomIndex);
278 if (pending.handle.pointIndex >= polygon.size())
280 ARMARX_WARNING <<
"The edited polygon changed underneath the interaction. "
281 "Discarding the transformation.";
286 if (pending.handle.type == Handle::Type::Vertex)
288 polygon.at(pending.handle.pointIndex) = pending.position;
290 ARMARX_INFO <<
"Moved point " << pending.handle.pointIndex <<
" of room `"
291 << editable.room.name <<
"` to [" << pending.position.x() <<
", "
292 << pending.position.y() <<
"].";
296 polygon.insert(polygon.begin() +
297 static_cast<std::ptrdiff_t
>(pending.handle.pointIndex) + 1,
300 ARMARX_INFO <<
"Cut edge " << pending.handle.pointIndex <<
" of room `"
301 << editable.room.name <<
"` at [" << pending.position.x() <<
", "
302 << pending.position.y() <<
"].";
305 notifyRoomChanged(pending.handle.roomIndex);
310 RoomEditor::handleContextMenu(
const Handle& handle,
int entry, viz::StagedCommit& stage)
313 pendingTransform.reset();
315 EditableRoom& editable = rooms_.at(handle.roomIndex);
316 std::vector<Eigen::Vector2f>& polygon = editable.room.polygon;
318 if (handle.pointIndex >= polygon.size())
320 ARMARX_WARNING <<
"The edited polygon changed underneath the interaction. "
321 "Ignoring the context menu action.";
325 const auto insertAt = [&polygon](std::size_t
index,
const Eigen::Vector2f& point)
326 { polygon.insert(polygon.begin() +
static_cast<std::ptrdiff_t
>(
index), point); };
328 if (handle.type == Handle::Type::Edge)
331 const Eigen::Vector2f midpoint = handlePosition(handle);
332 insertAt(handle.pointIndex + 1, midpoint);
334 ARMARX_INFO <<
"Cut edge " << handle.pointIndex <<
" of room `" << editable.room.name
339 const std::size_t i = handle.pointIndex;
340 const std::size_t
n = polygon.size();
342 switch (
static_cast<ContextMenuEntry
>(entry))
344 case ContextMenuEntry::InsertPointAfter:
346 const Eigen::Vector2f newPoint =
347 0.5F * (polygon.at(i) + polygon.at((i + 1) % n));
348 insertAt(i + 1, newPoint);
350 ARMARX_INFO <<
"Inserted a point after point " << i <<
" of room `"
351 << editable.room.name <<
"`.";
355 case ContextMenuEntry::InsertPointBefore:
357 const Eigen::Vector2f newPoint =
358 0.5F * (polygon.at(i) + polygon.at((i + n - 1) % n));
359 insertAt(i, newPoint);
361 ARMARX_INFO <<
"Inserted a point before point " << i <<
" of room `"
362 << editable.room.name <<
"`.";
366 case ContextMenuEntry::DeletePoint:
368 if (n <= MinPolygonSize)
370 ARMARX_WARNING <<
"Refusing to delete point " << i <<
" of room `"
371 << editable.room.name <<
"`: a polygon needs at least "
372 << MinPolygonSize <<
" points.";
376 polygon.erase(polygon.begin() +
static_cast<std::ptrdiff_t
>(i));
378 ARMARX_INFO <<
"Deleted point " << i <<
" of room `" << editable.room.name
391 notifyRoomChanged(handle.roomIndex);
398 handleByElement.clear();
399 handleLayer = arviz.layer(parameters.layerPrefix +
"_Handles");
401 for (std::size_t roomIndex = 0; roomIndex < rooms_.size(); ++roomIndex)
404 const std::vector<Eigen::Vector2f>& polygon = editable.
room.
polygon;
406 const float z = editable.
room.
zFloor + parameters.handleZOffset;
408 for (std::size_t i = 0; i < polygon.size(); ++i)
411 .type = Handle::Type::Vertex, .roomIndex = roomIndex, .pointIndex = i};
412 const std::string name = elementName(handle);
413 handleByElement[name] = handle;
416 .position(to3D(polygon.at(i), z))
417 .
radius(parameters.vertexHandleRadius)
420 .translation(viz::AXES_XY)
421 .contextMenu(VertexContextMenu)));
425 if (polygon.size() >= 2)
427 for (std::size_t i = 0; i < polygon.size(); ++i)
430 .type = Handle::Type::Edge, .roomIndex = roomIndex, .pointIndex = i};
431 const std::string name = elementName(handle);
432 handleByElement[name] = handle;
435 .position(to3D(handlePosition(handle), z))
436 .size(parameters.edgeHandleSize)
437 .
color(viz::Color::gray(160))
439 .translation(viz::AXES_XY)
440 .contextMenu(EdgeContextMenu)));
445 if (polygon.size() >= MinPolygonSize)
448 .type = Handle::Type::Move, .roomIndex = roomIndex, .pointIndex = 0};
449 const std::string name = elementName(handle);
450 handleByElement[name] = handle;
453 .position(to3D(handlePosition(handle), z))
454 .radius(parameters.moveHandleRadius)
455 .
height(parameters.moveHandleHeight)
456 .
color(viz::Color::gray(200))
458 .translation(viz::AXES_XY)
459 .rotation(viz::AXES_Z)));
463 stage.
add(handleLayer);
473 for (std::size_t roomIndex = 0; roomIndex < rooms_.size(); ++roomIndex)
476 const std::vector<Eigen::Vector2f> polygon = previewPolygon(roomIndex);
478 if (polygon.size() < 2)
487 for (
const auto& point : polygon)
489 outline.addPoint(to3D(point, z));
492 outline.addPoint(to3D(polygon.front(), z));
493 outline.colorGlasbeyLUT(roomIndex).width(parameters.
lineWidth);
494 roomLayer.
add(outline);
496 if (polygon.size() >= MinPolygonSize)
499 for (
const auto& point : polygon)
501 area.addPoint(to3D(point, z));
503 area.colorGlasbeyLUT(roomIndex, 60);
504 area.lineColorGlasbeyLUT(roomIndex);
511 .
position(to3D(centroid(polygon), z))
515 stage.
add(roomLayer);
518 std::vector<Eigen::Vector2f>
519 RoomEditor::previewPolygon(std::size_t roomIndex)
const
521 std::vector<Eigen::Vector2f> polygon = rooms_.at(roomIndex).room.polygon;
523 if (not pendingTransform.has_value() or pendingTransform->handle.roomIndex != roomIndex)
528 const Handle& handle = pendingTransform->handle;
530 if (handle.type == Handle::Type::Move)
532 return applyRigidMotion(polygon,
534 pendingTransform->rotation,
535 pendingTransform->translation);
538 if (handle.pointIndex >= polygon.size())
543 if (handle.type == Handle::Type::Vertex)
545 polygon.at(handle.pointIndex) = pendingTransform->position;
549 polygon.insert(polygon.begin() +
static_cast<std::ptrdiff_t
>(handle.pointIndex) + 1,
550 pendingTransform->position);
557 RoomEditor::handlePosition(
const Handle& handle)
const
559 const std::vector<Eigen::Vector2f>& polygon = rooms_.at(handle.roomIndex).room.polygon;
561 if (handle.type == Handle::Type::Move)
563 return centroid(polygon);
566 if (handle.type == Handle::Type::Vertex)
568 return polygon.at(handle.pointIndex);
571 const std::size_t next = (handle.pointIndex + 1) % polygon.size();
572 return 0.5F * (polygon.at(handle.pointIndex) + polygon.at(next));
576 RoomEditor::elementName(
const Handle& handle)
const
578 const EditableRoom& editable = rooms_.at(handle.roomIndex);
581 if (handle.type == Handle::Type::Move)
583 return prefix +
"/move";
586 const std::string suffix = handle.type == Handle::Type::Vertex ?
"/vertex_" :
"/edge_";
588 return prefix + suffix + std::to_string(handle.pointIndex);
592 RoomEditor::notifyRoomChanged(std::size_t roomIndex)
const
594 if (roomChangedCallback)
596 roomChangedCallback(rooms_.at(roomIndex));
bool isInteracting() const
True while the user is dragging a handle. Reloading should be paused meanwhile.
void update(const viz::CommitResult &result, viz::StagedCommit &stage)
Processes the interaction feedback of the previous commit and stages everything that has to be (re-)d...
const std::vector< EditableRoom > & rooms() const
void setRooms(const std::vector< EditableRoom > &rooms)
Replaces the edited rooms, e.g.
void setRoomChangedCallback(RoomChangedCallback callback)
std::function< void(const EditableRoom &)> RoomChangedCallback
Called whenever a room was modified by the user.
RoomEditor(viz::Client arviz, const Parameters ¶meters)
void redrawAll(viz::StagedCommit &stage)
Stages a full redraw of all layers (rooms and handles).
virtual Layer layer(std::string const &name) const
DerivedT & enable(InteractionDescription const &interactionDescription)
DerivedT & colorGlasbeyLUT(std::size_t id, int alpha=255)
DerivedT & color(Color color)
DerivedT & position(float x, float y, float z)
#define ARMARX_INFO
The normal logging level.
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
#define ARMARX_VERBOSE
The logging level for verbose information.
std::vector< Eigen::Vector3f > to3D(const std::vector< Eigen::Vector2f > &v)
InteractionDescription interaction()
@ Transform
The element was transformed (translated or rotated).
@ ContextMenuChosen
A context menu entry was chosen.
@ Deselect
An element was deselected.
const char * toString(InteractionFeedbackType type)
auto transform(const Container< InputT, Alloc > &in, OutputT(*func)(InputT const &)) -> Container< OutputT, typename std::allocator_traits< Alloc >::template rebind_alloc< OutputT > >
Convenience function (with less typing) to transform a container of type InputT into the same contain...
constexpr auto n() noexcept
double angle(const Point &a, const Point &b, const Point &c)
std::vector< Eigen::Vector2f > polygon
A room together with the provider segment (i.e. the navigation graph) it belongs to.
std::string providerSegmentName
float lineWidth
Width of the polygon outline [mm].
float labelScale
Scale of the room name labels.
std::string layerPrefix
Prefix of the ArViz layers created by the editor.
InteractionFeedbackRange interactions() const
Cylinder & height(float h)
Self & rotation(AxesFlags const &axes=AXES_XYZ)
data::InteractionDescription data_
void add(ElementT const &element)
A staged commit prepares multiple layers to be committed.
void requestInteraction(Layer const &layer)
Request interaction feedback for a particular layer.
void add(Layer const &layer)
Stage a layer to be committed later via client.apply(*this)