summaryrefslogtreecommitdiffstats
path: root/src/vanishing-point.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
commitcca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch)
tree146f39ded1c938019e1ed42d30923c2ac9e86789 /src/vanishing-point.cpp
parentInitial commit. (diff)
downloadinkscape-12fc8abae6d434cac7670a59ed3a67301cc2eb10.tar.xz
inkscape-12fc8abae6d434cac7670a59ed3a67301cc2eb10.zip
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/vanishing-point.cpp')
-rw-r--r--src/vanishing-point.cpp763
1 files changed, 763 insertions, 0 deletions
diff --git a/src/vanishing-point.cpp b/src/vanishing-point.cpp
new file mode 100644
index 0000000..0427ddf
--- /dev/null
+++ b/src/vanishing-point.cpp
@@ -0,0 +1,763 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Vanishing point for 3D perspectives
+ *
+ * Authors:
+ * bulia byak <buliabyak@users.sf.net>
+ * Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
+ * Maximilian Albert <Anhalter42@gmx.de>
+ * Abhishek Sharma
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2005-2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glibmm/i18n.h>
+
+#include "vanishing-point.h"
+
+#include "desktop.h"
+#include "document-undo.h"
+#include "perspective-line.h"
+#include "snap.h"
+
+#include "display/control/canvas-item-curve.h"
+
+#include "object/sp-namedview.h"
+#include "object/box3d.h"
+
+#include "ui/icon-names.h"
+#include "ui/knot/knot.h"
+#include "ui/shape-editor.h"
+#include "ui/tools/tool-base.h"
+
+using Inkscape::DocumentUndo;
+
+namespace Box3D {
+
+#define VP_KNOT_COLOR_NORMAL 0xffffff00
+#define VP_KNOT_COLOR_SELECTED 0x0000ff00
+
+// screen pixels between knots when they snap:
+#define SNAP_DIST 5
+
+// absolute distance between gradient points for them to become a single dragger when the drag is created:
+#define MERGE_DIST 0.1
+
+// knot shapes corresponding to GrPointType enum
+Inkscape::CanvasItemCtrlShape vp_knot_shapes[] = {
+ Inkscape::CANVAS_ITEM_CTRL_SHAPE_SQUARE, // VP_FINITE
+ Inkscape::CANVAS_ITEM_CTRL_SHAPE_CIRCLE // VP_INFINITE
+};
+
+static void vp_drag_sel_changed(Inkscape::Selection * /*selection*/, gpointer data)
+{
+ VPDrag *drag = (VPDrag *)data;
+ drag->updateDraggers();
+ drag->updateLines();
+ drag->updateBoxReprs();
+}
+
+static void vp_drag_sel_modified(Inkscape::Selection * /*selection*/, guint /*flags*/, gpointer data)
+{
+ VPDrag *drag = (VPDrag *)data;
+ drag->updateLines();
+ // drag->updateBoxReprs();
+ drag->updateBoxHandles(); // FIXME: Only update the handles of boxes on this dragger (not on all)
+ drag->updateDraggers();
+}
+
+static bool have_VPs_of_same_perspective(VPDragger *dr1, VPDragger *dr2)
+{
+ for (auto & vp : dr1->vps) {
+ if (dr2->hasPerspective(vp.get_perspective())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static void vp_knot_moved_handler(SPKnot *knot, Geom::Point const &ppointer, guint state, gpointer data)
+{
+ VPDragger *dragger = (VPDragger *)data;
+ VPDrag *drag = dragger->parent;
+
+ Geom::Point p = ppointer;
+
+ // FIXME: take from prefs
+ double snap_dist = SNAP_DIST / SP_ACTIVE_DESKTOP->current_zoom();
+
+ /*
+ * We use dragging_started to indicate if we have already checked for the need to split Draggers up.
+ * This only has the purpose of avoiding costly checks in the routine below.
+ */
+ if (!dragger->dragging_started && (state & GDK_SHIFT_MASK)) {
+ /* with Shift; if there is more than one box linked to this VP
+ we need to split it and create a new perspective */
+ if (dragger->numberOfBoxes() > 1) { // FIXME: Don't do anything if *all* boxes of a VP are selected
+ std::set<VanishingPoint *> sel_vps = dragger->VPsOfSelectedBoxes();
+
+ std::list<SPBox3D *> sel_boxes;
+ for (auto sel_vp : sel_vps) {
+ // for each VP that has selected boxes:
+ Persp3D *old_persp = sel_vp->get_perspective();
+ sel_boxes = sel_vp->selectedBoxes(SP_ACTIVE_DESKTOP->getSelection());
+
+ // we create a new perspective ...
+ Persp3D *new_persp = Persp3D::create_xml_element(dragger->parent->document);
+
+ /* ... unlink the boxes from the old one and
+ FIXME: We need to unlink the _un_selected boxes of each VP so that
+ the correct boxes are kept with the VP being moved */
+ std::list<SPBox3D *> bx_lst = old_persp->list_of_boxes();
+ for (auto & box : bx_lst) {
+ if (std::find(sel_boxes.begin(), sel_boxes.end(), box) == sel_boxes.end()) {
+ /* if a box in the VP is unselected, move it to the
+ newly created perspective so that it doesn't get dragged **/
+ box->switch_perspectives(old_persp, new_persp);
+ }
+ }
+ }
+ // FIXME: Do we need to create a new dragger as well?
+ dragger->updateZOrders();
+ DocumentUndo::done(SP_ACTIVE_DESKTOP->getDocument(), _("Split vanishing points"), INKSCAPE_ICON("draw-cuboid"));
+ return;
+ }
+ }
+
+ if (!(state & GDK_SHIFT_MASK)) {
+ // without Shift; see if we need to snap to another dragger
+ for (std::vector<VPDragger *>::const_iterator di = dragger->parent->draggers.begin();
+ di != dragger->parent->draggers.end(); ++di) {
+ VPDragger *d_new = *di;
+ if ((d_new != dragger) && (Geom::L2(d_new->point - p) < snap_dist)) {
+ if (have_VPs_of_same_perspective(dragger, d_new)) {
+ // this would result in degenerate boxes, which we disallow for the time being
+ continue;
+ }
+
+ // update positions ... (this is needed so that the perspectives are detected as identical)
+ // FIXME: This is called a bit too often, isn't it?
+ for (auto & vp : dragger->vps) {
+ vp.set_pos(d_new->point);
+ }
+
+ // ... join lists of VPs ...
+ d_new->vps.merge(dragger->vps);
+
+ // ... delete old dragger ...
+ drag->draggers.erase(std::remove(drag->draggers.begin(), drag->draggers.end(), dragger),
+ drag->draggers.end());
+ delete dragger;
+ dragger = nullptr;
+
+ // ... and merge any duplicate perspectives
+ d_new->mergePerspectives();
+
+ // TODO: Update the new merged dragger
+ d_new->updateTip();
+
+ d_new->parent->updateBoxDisplays(); // FIXME: Only update boxes in current dragger!
+ d_new->updateZOrders();
+
+ drag->updateLines();
+
+ // TODO: Undo machinery; this doesn't work yet because perspectives must be created and
+ // deleted according to changes in the svg representation, not based on any user input
+ // as is currently the case.
+
+ DocumentUndo::done(SP_ACTIVE_DESKTOP->getDocument(), _("Merge vanishing points"), INKSCAPE_ICON("draw-cuboid"));
+
+ return;
+ }
+ }
+ }
+
+ // We didn't hit the return statement above, so we didn't snap to another dragger. Therefore we'll now try a regular
+ // snap
+ // Regardless of the status of the SHIFT key, we will try to snap; Here SHIFT does not disable snapping, as the
+ // shift key
+ // has a different purpose in this context (see above)
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ SnapManager &m = desktop->namedview->snap_manager;
+ m.setup(desktop);
+ Inkscape::SnappedPoint s = m.freeSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_OTHER_HANDLE));
+ m.unSetup();
+ if (s.getSnapped()) {
+ p = s.getPoint();
+ knot->moveto(p);
+ }
+
+ dragger->point = p; // FIXME: Is dragger->point being used at all?
+
+ dragger->updateVPs(p);
+ dragger->updateBoxDisplays();
+ dragger->parent->updateBoxHandles(); // FIXME: Only update the handles of boxes on this dragger (not on all)
+ dragger->updateZOrders();
+
+ drag->updateLines();
+
+ dragger->dragging_started = true;
+}
+
+static void vp_knot_grabbed_handler(SPKnot * /*knot*/, unsigned int /*state*/, gpointer data)
+{
+ VPDragger *dragger = (VPDragger *)data;
+ VPDrag *drag = dragger->parent;
+
+ drag->dragging = true;
+}
+
+static void vp_knot_ungrabbed_handler(SPKnot *knot, guint /*state*/, gpointer data)
+{
+ VPDragger *dragger = (VPDragger *)data;
+
+ dragger->point_original = dragger->point = knot->pos;
+
+ dragger->dragging_started = false;
+
+ for (auto & vp : dragger->vps) {
+ vp.set_pos(knot->pos);
+ vp.updateBoxReprs();
+ vp.updatePerspRepr();
+ }
+
+ dragger->parent->updateDraggers();
+ dragger->parent->updateLines();
+ dragger->parent->updateBoxHandles();
+
+ // TODO: Update box's paths and svg representation
+
+ dragger->parent->dragging = false;
+
+ // TODO: Undo machinery!!
+ g_return_if_fail(dragger->parent);
+ g_return_if_fail(dragger->parent->document);
+ DocumentUndo::done(dragger->parent->document, _("3D box: Move vanishing point"), INKSCAPE_ICON("draw-cuboid"));
+}
+
+unsigned int VanishingPoint::global_counter = 0;
+
+// FIXME: Rename to something more meaningful!
+void VanishingPoint::set_pos(Proj::Pt2 const &pt)
+{
+ g_return_if_fail(_persp);
+ _persp->perspective_impl->tmat.set_image_pt(_axis, pt);
+}
+
+std::list<SPBox3D *> VanishingPoint::selectedBoxes(Inkscape::Selection *sel)
+{
+ std::list<SPBox3D *> sel_boxes;
+ auto itemlist = sel->items();
+ for (auto i = itemlist.begin(); i != itemlist.end(); ++i) {
+ SPItem *item = *i;
+ SPBox3D *box = dynamic_cast<SPBox3D *>(item);
+ if (box && this->hasBox(box)) {
+ sel_boxes.push_back(box);
+ }
+ }
+ return sel_boxes;
+}
+
+VPDragger::VPDragger(VPDrag *parent, Geom::Point p, VanishingPoint &vp)
+ : parent(parent)
+ , knot(nullptr)
+ , point(p)
+ , point_original(p)
+ , dragging_started(false)
+ , vps()
+{
+ if (vp.is_finite()) {
+ // create the knot
+ this->knot = new SPKnot(SP_ACTIVE_DESKTOP, "", Inkscape::CANVAS_ITEM_CTRL_TYPE_ANCHOR, "CanvasItemCtrl:VPDragger");
+ this->knot->setFill(VP_KNOT_COLOR_NORMAL, VP_KNOT_COLOR_NORMAL, VP_KNOT_COLOR_NORMAL, VP_KNOT_COLOR_NORMAL);
+ this->knot->setStroke(0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff);
+ this->knot->updateCtrl();
+
+ // move knot to the given point
+ this->knot->setPosition(this->point, SP_KNOT_STATE_NORMAL);
+ this->knot->show();
+
+ // connect knot's signals
+ this->_moved_connection =
+ this->knot->moved_signal.connect(sigc::bind(sigc::ptr_fun(vp_knot_moved_handler), this));
+ this->_grabbed_connection =
+ this->knot->grabbed_signal.connect(sigc::bind(sigc::ptr_fun(vp_knot_grabbed_handler), this));
+ this->_ungrabbed_connection =
+ this->knot->ungrabbed_signal.connect(sigc::bind(sigc::ptr_fun(vp_knot_ungrabbed_handler), this));
+
+ // add the initial VP (which may be NULL!)
+ this->addVP(vp);
+ }
+}
+
+VPDragger::~VPDragger()
+{
+ // disconnect signals
+ this->_moved_connection.disconnect();
+ this->_grabbed_connection.disconnect();
+ this->_ungrabbed_connection.disconnect();
+
+ /* unref should call destroy */
+ knot_unref(this->knot);
+}
+
+/**
+Updates the statusbar tip of the dragger knot, based on its draggables
+ */
+void VPDragger::updateTip()
+{
+ if (this->knot && this->knot->tip) {
+ g_free(this->knot->tip);
+ this->knot->tip = nullptr;
+ }
+
+ guint num = this->numberOfBoxes();
+ if (this->vps.size() == 1) {
+ if (this->vps.front().is_finite()) {
+ this->knot->tip = g_strdup_printf(ngettext("<b>Finite</b> vanishing point shared by <b>%d</b> box",
+ "<b>Finite</b> vanishing point shared by <b>%d</b> boxes; drag "
+ "with <b>Shift</b> to separate selected box(es)",
+ num),
+ num);
+ }
+ else {
+ // This won't make sense any more when infinite VPs are not shown on the canvas,
+ // but currently we update the status message anyway
+ this->knot->tip = g_strdup_printf(ngettext("<b>Infinite</b> vanishing point shared by the box",
+ "<b>Infinite</b> vanishing point shared by <b>%d</b> boxes; "
+ "drag with <b>Shift</b> to separate selected box(es)",
+ num),
+ num);
+ }
+ }
+ else {
+ int length = this->vps.size();
+ char const *tmpl = ngettext("Collection of <b>%d</b> vanishing points shared by the box; "
+ "drag with <b>Shift</b> to separate",
+ "Collection of <b>%d</b> vanishing points shared by <b>%d</b> boxes; "
+ "drag with <b>Shift</b> to separate",
+ num);
+ this->knot->tip = g_strdup_printf(tmpl, length, num);
+ }
+}
+
+/**
+ * Adds a vanishing point to the dragger (also updates the position if necessary);
+ * the perspective is stored separately, too, for efficiency in updating boxes.
+ */
+void VPDragger::addVP(VanishingPoint &vp, bool update_pos)
+{
+ if (!vp.is_finite() || std::find(vps.begin(), vps.end(), vp) != vps.end()) {
+ // don't add infinite VPs; don't add the same VP twice
+ return;
+ }
+
+ if (update_pos) {
+ vp.set_pos(this->point);
+ }
+ this->vps.push_front(vp);
+
+ this->updateTip();
+}
+
+void VPDragger::removeVP(VanishingPoint const &vp)
+{
+ std::list<VanishingPoint>::iterator i = std::find(this->vps.begin(), this->vps.end(), vp);
+ if (i != this->vps.end()) {
+ this->vps.erase(i);
+ }
+ this->updateTip();
+}
+
+VanishingPoint *VPDragger::findVPWithBox(SPBox3D *box)
+{
+ for (auto & vp : vps) {
+ if (vp.hasBox(box)) {
+ return &vp;
+ }
+ }
+ return nullptr;
+}
+
+std::set<VanishingPoint *> VPDragger::VPsOfSelectedBoxes()
+{
+ std::set<VanishingPoint *> sel_vps;
+ VanishingPoint *vp;
+ // FIXME: Should we take the selection from the parent VPDrag? I guess it shouldn't make a difference.
+ Inkscape::Selection *sel = SP_ACTIVE_DESKTOP->getSelection();
+ auto itemlist = sel->items();
+ for (auto i = itemlist.begin(); i != itemlist.end(); ++i) {
+ SPItem *item = *i;
+ SPBox3D *box = dynamic_cast<SPBox3D *>(item);
+ if (box) {
+ vp = this->findVPWithBox(box);
+ if (vp) {
+ sel_vps.insert(vp);
+ }
+ }
+ }
+ return sel_vps;
+}
+
+guint VPDragger::numberOfBoxes()
+{
+ guint num = 0;
+ for (auto & vp : vps) {
+ num += vp.numberOfBoxes();
+ }
+ return num;
+}
+
+bool VPDragger::hasPerspective(const Persp3D *persp)
+{
+ for (auto & vp : vps) {
+ if (persp->perspectives_coincide(vp.get_perspective())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void VPDragger::mergePerspectives()
+{
+ Persp3D *persp1, *persp2;
+ for (std::list<VanishingPoint>::iterator i = vps.begin(); i != vps.end(); ++i) {
+ persp1 = (*i).get_perspective();
+ for (std::list<VanishingPoint>::iterator j = i; j != vps.end(); ++j) {
+ persp2 = (*j).get_perspective();
+ if (persp1 == persp2) {
+ /* don't merge a perspective with itself */
+ continue;
+ }
+ if (persp1->perspectives_coincide(persp2)) {
+ /* if perspectives coincide but are not the same, merge them */
+ persp1->absorb(persp2);
+
+ this->parent->swap_perspectives_of_VPs(persp2, persp1);
+
+ persp2->deleteObject(false);
+ }
+ }
+ }
+}
+
+void VPDragger::updateBoxDisplays()
+{
+ for (auto & vp : this->vps) {
+ vp.updateBoxDisplays();
+ }
+}
+
+void VPDragger::updateVPs(Geom::Point const &pt)
+{
+ for (auto & vp : this->vps) {
+ vp.set_pos(pt);
+ }
+}
+
+void VPDragger::updateZOrders()
+{
+ for (auto & vp : this->vps) {
+ vp.get_perspective()->update_z_orders();
+ }
+}
+
+void VPDragger::printVPs()
+{
+ g_print("VPDragger at position (%f, %f):\n", point[Geom::X], point[Geom::Y]);
+ for (auto & vp : this->vps) {
+ g_print(" VP %s\n", vp.axisString());
+ }
+}
+
+VPDrag::VPDrag(SPDocument *document)
+{
+ this->document = document;
+ this->selection = SP_ACTIVE_DESKTOP->getSelection();
+
+ this->show_lines = true;
+ this->front_or_rear_lines = 0x1;
+
+ this->dragging = false;
+
+ this->sel_changed_connection =
+ this->selection->connectChanged(sigc::bind(sigc::ptr_fun(&vp_drag_sel_changed), (gpointer) this)
+
+ );
+ this->sel_modified_connection =
+ this->selection->connectModified(sigc::bind(sigc::ptr_fun(&vp_drag_sel_modified), (gpointer) this));
+
+ this->updateDraggers();
+ this->updateLines();
+}
+
+VPDrag::~VPDrag()
+{
+ this->sel_changed_connection.disconnect();
+ this->sel_modified_connection.disconnect();
+
+ for (auto dragger : this->draggers) {
+ delete dragger;
+ }
+ this->draggers.clear();
+
+ for (auto item_curve : item_curves) {
+ delete item_curve;
+ }
+ item_curves.clear();
+}
+
+/**
+ * Select the dragger that has the given VP.
+ */
+VPDragger *VPDrag::getDraggerFor(VanishingPoint const &vp)
+{
+ for (auto dragger : this->draggers) {
+ for (std::list<VanishingPoint>::iterator j = dragger->vps.begin(); j != dragger->vps.end(); ++j) {
+ // TODO: Should we compare the pointers or the VPs themselves!?!?!?!
+ if (*j == vp) {
+ return (dragger);
+ }
+ }
+ }
+ return nullptr;
+}
+
+void VPDrag::printDraggers()
+{
+ g_print("=== VPDrag info: =================================\n");
+ for (auto dragger : this->draggers) {
+ dragger->printVPs();
+ g_print("========\n");
+ }
+ g_print("=================================================\n");
+}
+
+/**
+ * Regenerates the draggers list from the current selection; is called when selection is changed or modified
+ */
+void VPDrag::updateDraggers()
+{
+ if (this->dragging)
+ return;
+ // delete old draggers
+ for (auto dragger : this->draggers) {
+ delete dragger;
+ }
+ this->draggers.clear();
+
+ g_return_if_fail(this->selection != nullptr);
+
+ auto itemlist = this->selection->items();
+ for (auto i = itemlist.begin(); i != itemlist.end(); ++i) {
+ SPItem *item = *i;
+ SPBox3D *box = dynamic_cast<SPBox3D *>(item);
+ if (box) {
+ VanishingPoint vp;
+ for (int i = 0; i < 3; ++i) {
+ vp.set(box->get_perspective(), Proj::axes[i]);
+ addDragger(vp);
+ }
+ }
+ }
+}
+
+/**
+Regenerates the lines list from the current selection; is called on each move
+of a dragger, so that lines are always in sync with the actual perspective
+*/
+void VPDrag::updateLines()
+{
+ // Delete old lines
+ for (auto curve : item_curves) {
+ delete curve;
+ }
+ item_curves.clear();
+
+ // do nothing if perspective lines are currently disabled
+ if (this->show_lines == 0)
+ return;
+
+ g_return_if_fail(this->selection != nullptr);
+
+ auto itemlist = this->selection->items();
+ for (auto i = itemlist.begin(); i != itemlist.end(); ++i) {
+ SPItem *item = *i;
+ SPBox3D *box = dynamic_cast<SPBox3D *>(item);
+ if (box) {
+ this->drawLinesForFace(box, Proj::X);
+ this->drawLinesForFace(box, Proj::Y);
+ this->drawLinesForFace(box, Proj::Z);
+ }
+ }
+}
+
+void VPDrag::updateBoxHandles()
+{
+ // FIXME: Is there a way to update the knots without accessing the
+ // (previously) statically linked function KnotHolder::update_knots?
+
+ auto sel = selection->items();
+ if (sel.empty())
+ return; // no selection
+
+ if (boost::distance(sel) > 1) {
+ // Currently we only show handles if a single box is selected
+ return;
+ }
+
+ Inkscape::UI::Tools::ToolBase *ec = SP_ACTIVE_DESKTOP->getEventContext();
+ g_assert(ec != nullptr);
+ if (ec->shape_editor != nullptr) {
+ ec->shape_editor->update_knotholder();
+ }
+}
+
+void VPDrag::updateBoxReprs()
+{
+ for (auto dragger : this->draggers) {
+ for (auto & vp : dragger->vps) {
+ vp.updateBoxReprs();
+ }
+ }
+}
+
+void VPDrag::updateBoxDisplays()
+{
+ for (auto dragger : this->draggers) {
+ for (auto & vp : dragger->vps) {
+ vp.updateBoxDisplays();
+ }
+ }
+}
+
+
+/**
+ * Depending on the value of all_lines, draw the front and/or rear perspective lines starting from the given corners.
+ */
+void VPDrag::drawLinesForFace(const SPBox3D *box,
+ Proj::Axis axis) //, guint corner1, guint corner2, guint corner3, guint corner4)
+{
+ Inkscape::CanvasItemColor type = Inkscape::CANVAS_ITEM_PRIMARY;
+ switch (axis) {
+ // TODO: Make color selectable by user
+ case Proj::X:
+ type = Inkscape::CANVAS_ITEM_SECONDARY;
+ break;
+ case Proj::Y:
+ type = Inkscape::CANVAS_ITEM_PRIMARY;
+ break;
+ case Proj::Z:
+ type = Inkscape::CANVAS_ITEM_TERTIARY;;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ const size_t NUM_CORNERS = 4;
+ Geom::Point corners[NUM_CORNERS];
+ box->corners_for_PLs(axis, corners[0], corners[1], corners[2], corners[3]);
+
+ g_return_if_fail(box->get_perspective());
+ Proj::Pt2 vp = box->get_perspective()->get_VP(axis);
+ if (vp.is_finite()) {
+ // draw perspective lines for finite VPs
+ Geom::Point pt = vp.affine();
+ if (this->front_or_rear_lines & 0x1) {
+ // draw 'front' perspective lines
+ this->addCurve(corners[0], pt, type);
+ this->addCurve(corners[1], pt, type);
+ }
+ if (this->front_or_rear_lines & 0x2) {
+ // draw 'rear' perspective lines
+ this->addCurve(corners[2], pt, type);
+ this->addCurve(corners[3], pt, type);
+ }
+ }
+ else {
+ // draw perspective lines for infinite VPs
+ std::optional<Geom::Point> pts[NUM_CORNERS];
+ Persp3D *persp = box->get_perspective();
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP; // FIXME: Store the desktop in VPDrag
+
+ for (size_t i = 0; i < NUM_CORNERS; i++) {
+ Box3D::PerspectiveLine pl(corners[i], axis, persp);
+ if (!(pts[i] = pl.intersection_with_viewbox(desktop))) {
+ // some perspective lines are outside the canvas; currently we don't draw any of them
+ return;
+ }
+ }
+ if (this->front_or_rear_lines & 0x1) {
+ // draw 'front' perspective lines
+ this->addCurve(corners[0], *pts[0], type);
+ this->addCurve(corners[1], *pts[1], type);
+ }
+ if (this->front_or_rear_lines & 0x2) {
+ // draw 'rear' perspective lines
+ this->addCurve(corners[2], *pts[2], type);
+ this->addCurve(corners[3], *pts[3], type);
+ }
+ }
+}
+
+/**
+ * If there already exists a dragger within MERGE_DIST of p, add the VP to it;
+ * otherwise create new dragger and add it to draggers list
+ * We also store the corresponding perspective in case it is not already present.
+ */
+void VPDrag::addDragger(VanishingPoint &vp)
+{
+ if (!vp.is_finite()) {
+ // don't create draggers for infinite vanishing points
+ return;
+ }
+ Geom::Point p = vp.get_pos();
+
+ for (auto dragger : this->draggers) {
+ if (Geom::L2(dragger->point - p) < MERGE_DIST) {
+ // distance is small, merge this draggable into dragger, no need to create new dragger
+ dragger->addVP(vp);
+ return;
+ }
+ }
+
+ VPDragger *new_dragger = new VPDragger(this, p, vp);
+ // fixme: draggers should be added AFTER the last one: this way tabbing through them will be from begin to end.
+ this->draggers.push_back(new_dragger);
+}
+
+void VPDrag::swap_perspectives_of_VPs(Persp3D *persp2, Persp3D *persp1)
+{
+ // iterate over all VP in all draggers and replace persp2 with persp1
+ for (auto dragger : this->draggers) {
+ for (auto & vp : dragger->vps) {
+ if (vp.get_perspective() == persp2) {
+ vp.set_perspective(persp1);
+ }
+ }
+ }
+}
+
+void VPDrag::addCurve(Geom::Point const &p1, Geom::Point const &p2, Inkscape::CanvasItemColor color)
+{
+ auto item_curve = new Inkscape::CanvasItemCurve(SP_ACTIVE_DESKTOP->getCanvasControls(), p1, p2);
+ item_curve->set_name("3DBoxCurve");
+ item_curve->set_stroke(color);
+ item_curves.push_back(item_curve);
+}
+
+} // namespace Box3D
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :