X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fvanishing-point.cpp;h=78ceec4671178a2bf4fb33535d7f0137eeab7af4;hb=fce046713c4cb905f38bf489cc4a73af425f3037;hp=5f40936cf5ca637da07e98c252eb15b223274a43;hpb=b2dfb5760b4ff97eabdaa0272ba7ed7f0dd8c6eb;p=inkscape.git diff --git a/src/vanishing-point.cpp b/src/vanishing-point.cpp index 5f40936cf..78ceec467 100644 --- a/src/vanishing-point.cpp +++ b/src/vanishing-point.cpp @@ -4,102 +4,768 @@ * Vanishing point for 3D perspectives * * Authors: + * bulia byak + * Johan Engelen * Maximilian Albert * - * Copyright (C) 2007 authors + * Copyright (C) 2005-2007 authors * * Released under GNU GPL, read the file 'COPYING' for more information */ +#include + #include "vanishing-point.h" +#include "desktop-handles.h" +#include "desktop.h" +#include "event-context.h" +#include "xml/repr.h" +#include "perspective-line.h" +#include "shape-editor.h" namespace Box3D { -// FIXME: We should always require to have both the point (for finite VPs) -// and the direction (for infinite VPs) set. Otherwise toggling -// shows very unexpected behaviour. -// Later on we can maybe infer the infinite direction from the finite point -// and a suitable center of the scene. How to go in the other direction? -VanishingPoint::VanishingPoint(NR::Point const &pt, NR::Point const &inf_dir, VPState st) - : NR::Point (pt), state (st), v_dir (inf_dir) {} +#define VP_KNOT_COLOR_NORMAL 0xffffff00 +#define VP_KNOT_COLOR_SELECTED 0x0000ff00 -VanishingPoint::VanishingPoint(NR::Point const &pt) - : NR::Point (pt), state (VP_FINITE), v_dir (0.0, 0.0) {} +#define VP_LINE_COLOR_FILL 0x0000ff7f +#define VP_LINE_COLOR_STROKE_X 0xff00007f +#define VP_LINE_COLOR_STROKE_Y 0x0000ff7f +#define VP_LINE_COLOR_STROKE_Z 0xffff007f -VanishingPoint::VanishingPoint(NR::Point const &pt, NR::Point const &direction) - : NR::Point (pt), state (VP_INFINITE), v_dir (direction) {} +// screen pixels between knots when they snap: +#define SNAP_DIST 5 -VanishingPoint::VanishingPoint(NR::Coord x, NR::Coord y) - : NR::Point(x, y), state(VP_FINITE), v_dir(0.0, 0.0) {} +// absolute distance between gradient points for them to become a single dragger when the drag is created: +#define MERGE_DIST 0.1 -VanishingPoint::VanishingPoint(NR::Coord dir_x, NR::Coord dir_y, VPState st) - : NR::Point(0.0, 0.0), state(st), v_dir(dir_x, dir_y) {} +// knot shapes corresponding to GrPointType enum +SPKnotShapeType vp_knot_shapes [] = { + SP_KNOT_SHAPE_SQUARE, // VP_FINITE + SP_KNOT_SHAPE_CIRCLE //VP_INFINITE +}; -VanishingPoint::VanishingPoint(NR::Coord x, NR::Coord y, NR::Coord dir_x, NR::Coord dir_y) - : NR::Point(x, y), state(VP_INFINITE), v_dir(dir_x, dir_y) {} +static void +vp_drag_sel_changed(Inkscape::Selection */*selection*/, gpointer data) +{ + VPDrag *drag = (VPDrag *) data; + drag->updateDraggers(); + drag->updateLines(); + drag->updateBoxReprs(); +} -VanishingPoint::VanishingPoint(VanishingPoint const &rhs) : NR::Point (rhs) +static void +vp_drag_sel_modified (Inkscape::Selection */*selection*/, guint /*flags*/, gpointer data) { - this->state = rhs.state; - //this->ref_pt = rhs.ref_pt; - this->v_dir = rhs.v_dir; + 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 (); } -bool VanishingPoint::operator== (VanishingPoint const &other) +static bool +have_VPs_of_same_perspective (VPDragger *dr1, VPDragger *dr2) { - // Should we compare the parent perspectives, too? Probably not. - if ((*this)[NR::X] == other[NR::X] && (*this)[NR::Y] == other[NR::Y] - && this->state == other.state && this->v_dir == other.v_dir) { - return true; + for (std::list::iterator i = dr1->vps.begin(); i != dr1->vps.end(); ++i) { + if (dr2->hasPerspective ((*i).get_perspective())) { + return true; + } } return false; } -bool VanishingPoint::is_finite() const +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 / inkscape_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 sel_vps = dragger->VPsOfSelectedBoxes(); + + std::list sel_boxes; + for (std::set::iterator vp = sel_vps.begin(); vp != sel_vps.end(); ++vp) { + // for each VP that has selected boxes: + Persp3D *old_persp = (*vp)->get_perspective(); + sel_boxes = (*vp)->selectedBoxes(sp_desktop_selection(inkscape_active_desktop())); + + // we create a new perspective ... + Persp3D *new_persp = persp3d_create_xml_element (dragger->parent->document, old_persp->perspective_impl); + + /* ... 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 bx_lst = persp3d_list_of_boxes(old_persp); + for (std::list::iterator i = bx_lst.begin(); i != bx_lst.end(); ++i) { + if (std::find(sel_boxes.begin(), sel_boxes.end(), *i) == 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 **/ + box3d_switch_perspectives(*i, old_persp, new_persp); + } + } + } + // FIXME: Do we need to create a new dragger as well? + dragger->updateZOrders (); + sp_document_done (sp_desktop_document (inkscape_active_desktop()), SP_VERB_CONTEXT_3DBOX, + _("Split vanishing points")); + return; + } + } + + if (!(state & GDK_SHIFT_MASK)) { + // without Shift; see if we need to snap to another dragger + for (GList *di = dragger->parent->draggers; di != NULL; di = di->next) { + VPDragger *d_new = (VPDragger *) di->data; + 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 (std::list::iterator j = dragger->vps.begin(); j != dragger->vps.end(); ++j) { + (*j).set_pos(d_new->point); + } + + // ... join lists of VPs ... + d_new->vps.merge(dragger->vps); + + // ... delete old dragger ... + drag->draggers = g_list_remove (drag->draggers, dragger); + delete dragger; + dragger = NULL; + + // ... 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. + + sp_document_done (sp_desktop_document (inkscape_active_desktop()), SP_VERB_CONTEXT_3DBOX, + _("Merge vanishing points")); + + return; + } + } + } + + + dragger->point = p; // FIXME: Brauchen wir dragger->point überhaupt? + + 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; +} + +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 (std::list::iterator i = dragger->vps.begin(); i != dragger->vps.end(); ++i) { + (*i).set_pos (knot->pos); + (*i).updateBoxReprs(); + (*i).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); + sp_document_done(dragger->parent->document, SP_VERB_CONTEXT_3DBOX, + _("3D box: Move vanishing point")); +} + +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 +VanishingPoint::selectedBoxes(Inkscape::Selection *sel) { + std::list sel_boxes; + for (GSList const* i = sel->itemList(); i != NULL; i = i->next) { + if (!SP_IS_BOX3D(i->data)) + continue; + SPBox3D *box = SP_BOX3D(i->data); + if (this->hasBox(box)) { + sel_boxes.push_back (box); + } + } + return sel_boxes; +} + +VPDragger::VPDragger(VPDrag *parent, Geom::Point p, VanishingPoint &vp) { - return this->state == VP_FINITE; + this->parent = parent; + + this->point = p; + this->point_original = p; + + this->dragging_started = false; + + if (vp.is_finite()) { + // create the knot + this->knot = sp_knot_new (inkscape_active_desktop(), NULL); + this->knot->setMode(SP_KNOT_MODE_XOR); + this->knot->setFill(VP_KNOT_COLOR_NORMAL, VP_KNOT_COLOR_NORMAL, VP_KNOT_COLOR_NORMAL); + this->knot->setStroke(0x000000ff, 0x000000ff, 0x000000ff); + sp_knot_update_ctrl(this->knot); + + // move knot to the given point + sp_knot_set_position (this->knot, this->point, SP_KNOT_STATE_NORMAL); + sp_knot_show (this->knot); + + // connect knot's signals + g_signal_connect (G_OBJECT (this->knot), "moved", G_CALLBACK (vp_knot_moved_handler), this); + g_signal_connect (G_OBJECT (this->knot), "grabbed", G_CALLBACK (vp_knot_grabbed_handler), this); + g_signal_connect (G_OBJECT (this->knot), "ungrabbed", G_CALLBACK (vp_knot_ungrabbed_handler), this); + + // add the initial VP (which may be NULL!) + this->addVP (vp); + } +} + +VPDragger::~VPDragger() +{ + // disconnect signals + g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_moved_handler), this); + g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_grabbed_handler), this); + g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_ungrabbed_handler), this); + /* unref should call destroy */ + g_object_unref (G_OBJECT (this->knot)); } -VPState VanishingPoint::toggle_parallel() +/** +Updates the statusbar tip of the dragger knot, based on its draggables + */ +void +VPDragger::updateTip () { - if (this->state == VP_FINITE) { - this->state = VP_INFINITE; + if (this->knot && this->knot->tip) { + g_free (this->knot->tip); + this->knot->tip = NULL; + } + + guint num = this->numberOfBoxes(); + if (this->vps.size() == 1) { + if (this->vps.front().is_finite()) { + this->knot->tip = g_strdup_printf (ngettext("Finite vanishing point shared by %d box", + "Finite vanishing point shared by %d boxes; drag with Shift 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("Infinite vanishing point shared by %d box", + "Infinite vanishing point shared by %d boxes; drag with Shift to separate selected box(es)", + num), + num); + } } else { - this->state = VP_FINITE; + int length = this->vps.size(); + char *desc1 = g_strdup_printf ("Collection of %d vanishing points ", length); + char *desc2 = g_strdup_printf (ngettext("shared by %d box; drag with Shift to separate selected box(es)", + "shared by %d boxes; drag with Shift to separate selected box(es)", + num), + num); + this->knot->tip = g_strconcat(desc1, desc2, NULL); + g_free (desc1); + g_free (desc2); + } +} + +/** + * 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::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 (std::list::iterator vp = vps.begin(); vp != vps.end(); ++vp) { + if ((*vp).hasBox(box)) { + return &(*vp); + } + } + return NULL; +} + +std::set +VPDragger::VPsOfSelectedBoxes() { + std::set 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_desktop_selection(inkscape_active_desktop()); + for (GSList const* i = sel->itemList(); i != NULL; i = i->next) { + if (!SP_IS_BOX3D(i->data)) + continue; + SPBox3D *box = SP_BOX3D(i->data); + vp = this->findVPWithBox(box); + if (vp) { + sel_vps.insert (vp); + } + } + return sel_vps; +} + +guint +VPDragger::numberOfBoxes () +{ + guint num = 0; + for (std::list::iterator vp = vps.begin(); vp != vps.end(); ++vp) { + num += (*vp).numberOfBoxes(); + } + return num; +} + +bool +VPDragger::hasPerspective (const Persp3D *persp) +{ + for (std::list::iterator i = vps.begin(); i != vps.end(); ++i) { + if (persp3d_perspectives_coincide(persp, (*i).get_perspective())) { + return true; + } + } + return false; +} + +void +VPDragger::mergePerspectives () +{ + Persp3D *persp1, *persp2; + for (std::list::iterator i = vps.begin(); i != vps.end(); ++i) { + persp1 = (*i).get_perspective(); + for (std::list::iterator j = i; j != vps.end(); ++j) { + persp2 = (*j).get_perspective(); + if (persp1 == persp2) { + /* don't merge a perspective with itself */ + continue; + } + if (persp3d_perspectives_coincide(persp1,persp2)) { + /* if perspectives coincide but are not the same, merge them */ + persp3d_absorb(persp1, persp2); + + this->parent->swap_perspectives_of_VPs(persp2, persp1); + + SP_OBJECT(persp2)->deleteObject(false); + } + } + } +} + +void +VPDragger::updateBoxDisplays () +{ + for (std::list::iterator i = this->vps.begin(); i != this->vps.end(); ++i) { + (*i).updateBoxDisplays(); + } +} + +void +VPDragger::updateVPs (Geom::Point const &pt) +{ + for (std::list::iterator i = this->vps.begin(); i != this->vps.end(); ++i) { + (*i).set_pos (pt); + } +} + +void +VPDragger::updateZOrders () +{ + for (std::list::iterator i = this->vps.begin(); i != this->vps.end(); ++i) { + persp3d_update_z_orders((*i).get_perspective()); + } +} + +void +VPDragger::printVPs() { + g_print ("VPDragger at position (%f, %f):\n", point[Geom::X], point[Geom::Y]); + for (std::list::iterator i = this->vps.begin(); i != this->vps.end(); ++i) { + g_print (" VP %s\n", (*i).axisString()); + } +} + +VPDrag::VPDrag (SPDocument *document) +{ + this->document = document; + this->selection = sp_desktop_selection(inkscape_active_desktop()); + + this->draggers = NULL; + this->lines = NULL; + 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 (GList *l = this->draggers; l != NULL; l = l->next) { + delete ((VPDragger *) l->data); + } + g_list_free (this->draggers); + this->draggers = NULL; + + for (GSList const *i = this->lines; i != NULL; i = i->next) { + gtk_object_destroy( GTK_OBJECT (i->data)); + } + g_slist_free (this->lines); + this->lines = NULL; +} + +/** + * Select the dragger that has the given VP. + */ +VPDragger * +VPDrag::getDraggerFor (VanishingPoint const &vp) +{ + for (GList const* i = this->draggers; i != NULL; i = i->next) { + VPDragger *dragger = (VPDragger *) i->data; + for (std::list::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 NULL; +} + +void +VPDrag::printDraggers () +{ + g_print ("=== VPDrag info: =================================\n"); + for (GList const* i = this->draggers; i != NULL; i = i->next) { + ((VPDragger *) i->data)->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 (GList const* i = this->draggers; i != NULL; i = i->next) { + delete ((VPDragger *) i->data); + } + g_list_free (this->draggers); + this->draggers = NULL; + + g_return_if_fail (this->selection != NULL); + + for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) { + SPItem *item = SP_ITEM(i->data); + if (!SP_IS_BOX3D (item)) continue; + SPBox3D *box = SP_BOX3D (item); + + VanishingPoint vp; + for (int i = 0; i < 3; ++i) { + vp.set(box3d_get_perspective(box), 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 (GSList const *i = this->lines; i != NULL; i = i->next) { + gtk_object_destroy( GTK_OBJECT (i->data)); } + g_slist_free (this->lines); + this->lines = NULL; - return this->state; + // do nothing if perspective lines are currently disabled + if (this->show_lines == 0) return; + + g_return_if_fail (this->selection != NULL); + + for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) { + if (!SP_IS_BOX3D(i->data)) continue; + SPBox3D *box = SP_BOX3D (i->data); + + this->drawLinesForFace (box, Proj::X); + this->drawLinesForFace (box, Proj::Y); + this->drawLinesForFace (box, Proj::Z); + } } -void VanishingPoint::draw(Box3D::Axis const axis) +void +VPDrag::updateBoxHandles () { + // FIXME: Is there a way to update the knots without accessing the + // (previously) statically linked function KnotHolder::update_knots? + + GSList *sel = (GSList *) selection->itemList(); + if (!sel) + return; // no selection + + if (g_slist_length (sel) > 1) { + // Currently we only show handles if a single box is selected + return; + } + + SPEventContext *ec = inkscape_active_event_context(); + g_assert (ec != NULL); + if (ec->shape_editor != NULL) { + ec->shape_editor->update_knotholder(); + } +} + +void +VPDrag::updateBoxReprs () +{ + for (GList *i = this->draggers; i != NULL; i = i->next) { + VPDragger *dragger = (VPDragger *) i->data; + for (std::list::iterator i = dragger->vps.begin(); i != dragger->vps.end(); ++i) { + (*i).updateBoxReprs(); + } + } +} + +void +VPDrag::updateBoxDisplays () +{ + for (GList *i = this->draggers; i != NULL; i = i->next) { + VPDragger *dragger = (VPDragger *) i->data; + for (std::list::iterator i = dragger->vps.begin(); i != dragger->vps.end(); ++i) { + (*i).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) +{ + guint color; switch (axis) { - case X: - if (state == VP_FINITE) - create_canvas_point(*this, 6.0, 0xff000000); - else - create_canvas_point(*this, 6.0, 0xffffff00); - break; - case Y: - if (state == VP_FINITE) - create_canvas_point(*this, 6.0, 0x0000ff00); - else - create_canvas_point(*this, 6.0, 0xffffff00); - break; - case Z: - if (state == VP_FINITE) - create_canvas_point(*this, 6.0, 0x00770000); - else - create_canvas_point(*this, 6.0, 0xffffff00); - break; - default: - g_assert_not_reached(); - break; - } -} - -} // namespace Box3D - + // TODO: Make color selectable by user + case Proj::X: color = VP_LINE_COLOR_STROKE_X; break; + case Proj::Y: color = VP_LINE_COLOR_STROKE_Y; break; + case Proj::Z: color = VP_LINE_COLOR_STROKE_Z; break; + default: g_assert_not_reached(); + } + + Geom::Point corner1, corner2, corner3, corner4; + box3d_corners_for_PLs (box, axis, corner1, corner2, corner3, corner4); + + g_return_if_fail (box3d_get_perspective(box)); + Proj::Pt2 vp = persp3d_get_VP (box3d_get_perspective(box), 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->addLine (corner1, pt, color); + this->addLine (corner2, pt, color); + } + if (this->front_or_rear_lines & 0x2) { + // draw 'rear' perspective lines + this->addLine (corner3, pt, color); + this->addLine (corner4, pt, color); + } + } else { + // draw perspective lines for infinite VPs + boost::optional pt1, pt2, pt3, pt4; + Persp3D *persp = box3d_get_perspective(box); + SPDesktop *desktop = inkscape_active_desktop (); // FIXME: Store the desktop in VPDrag + Box3D::PerspectiveLine pl (corner1, axis, persp); + pt1 = pl.intersection_with_viewbox(desktop); + + pl = Box3D::PerspectiveLine (corner2, axis, persp); + pt2 = pl.intersection_with_viewbox(desktop); + + pl = Box3D::PerspectiveLine (corner3, axis, persp); + pt3 = pl.intersection_with_viewbox(desktop); + + pl = Box3D::PerspectiveLine (corner4, axis, persp); + pt4 = pl.intersection_with_viewbox(desktop); + + if (!pt1 || !pt2 || !pt3 || !pt4) { + // some perspective lines s 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->addLine (corner1, *pt1, color); + this->addLine (corner2, *pt2, color); + } + if (this->front_or_rear_lines & 0x2) { + // draw 'rear' perspective lines + this->addLine (corner3, *pt3, color); + this->addLine (corner4, *pt4, color); + } + } +} + +/** + * 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 (GList *i = this->draggers; i != NULL; i = i->next) { + VPDragger *dragger = (VPDragger *) i->data; + 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 = g_list_append (this->draggers, 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 (GList *i = this->draggers; i != NULL; i = i->next) { + for (std::list::iterator j = ((VPDragger *) (i->data))->vps.begin(); + j != ((VPDragger *) (i->data))->vps.end(); ++j) { + if ((*j).get_perspective() == persp2) { + (*j).set_perspective(persp1); + } + } + } +} + +/** +Create a line from p1 to p2 and add it to the lines list + */ +void +VPDrag::addLine (Geom::Point p1, Geom::Point p2, guint32 rgba) +{ + SPCanvasItem *line = sp_canvas_item_new(sp_desktop_controls(inkscape_active_desktop()), SP_TYPE_CTRLLINE, NULL); + sp_ctrlline_set_coords(SP_CTRLLINE(line), p1, p2); + if (rgba != VP_LINE_COLOR_FILL) // fill is the default, so don't set color for it to speed up redraw + sp_ctrlline_set_rgba32 (SP_CTRLLINE(line), rgba); + sp_canvas_item_show (line); + this->lines = g_slist_append (this->lines, line); +} + +} // namespace Box3D + /* Local Variables: mode:c++