From: cilix42 Date: Mon, 6 Aug 2007 07:49:54 +0000 (+0000) Subject: (Un-)Snapping of VPs by Shift-dragging; this makes it possible to separate perspectiv... X-Git-Url: https://git.tokkee.org/?a=commitdiff_plain;h=3616fc4f881e624b50093cef5f017751ddf51b07;p=inkscape.git (Un-)Snapping of VPs by Shift-dragging; this makes it possible to separate perspectives and thus create new ones interactively --- diff --git a/src/axis-manip.cpp b/src/axis-manip.cpp index 094da7dde..c2edbfb89 100644 --- a/src/axis-manip.cpp +++ b/src/axis-manip.cpp @@ -19,6 +19,13 @@ Axis axes[3] = { X, Y, Z }; Axis planes[3] = { XY, XZ, YZ }; FrontOrRear face_positions [2] = { FRONT, REAR }; +std::pair +get_remaining_axes (Axis axis) { + if (!is_single_axis_direction (axis)) return std::make_pair (NONE, NONE); + Axis plane = orth_plane (axis); + return std::make_pair (extract_first_axis_direction (plane), extract_second_axis_direction (plane)); +} + } // namespace Box3D /* diff --git a/src/axis-manip.h b/src/axis-manip.h index 7ad716046..7461678d5 100644 --- a/src/axis-manip.h +++ b/src/axis-manip.h @@ -113,6 +113,8 @@ inline gchar * string_from_axes (Box3D::Axis axes) { return pstring->str; } +std::pair get_remaining_axes (Axis axis); + } // namespace Box3D #endif /* !SEEN_AXIS_MANIP_H */ diff --git a/src/perspective3d.cpp b/src/perspective3d.cpp index e5b1f6212..213744937 100644 --- a/src/perspective3d.cpp +++ b/src/perspective3d.cpp @@ -144,12 +144,18 @@ Perspective3D::~Perspective3D () } bool -Perspective3D::operator==(Perspective3D const &other) +Perspective3D::operator==(Perspective3D const &other) const { // Two perspectives are equal iff their vanishing points coincide and have identical states return (*vp_x == *other.vp_x && *vp_y == *other.vp_y && *vp_z == *other.vp_z); } +bool +Perspective3D::has_vanishing_point (VanishingPoint *vp) +{ + return (vp == vp_x || vp == vp_y || vp == vp_z); +} + VanishingPoint * Perspective3D::get_vanishing_point (Box3D::Axis const dir) { @@ -249,11 +255,35 @@ Perspective3D::remove_box (const SP3DBox *box) } bool -Perspective3D::has_box (const SP3DBox *box) +Perspective3D::has_box (const SP3DBox *box) const { return (g_slist_find (this->boxes, box) != NULL); } +bool +Perspective3D::all_boxes_occur_in_list (GSList *boxes_to_do) +{ + for (GSList *i = boxes; i != NULL; i = i->next) { + if (!g_slist_find (boxes_to_do, i->data)) { + return false; + } + } + return true; +} + +GSList * +Perspective3D::boxes_occurring_in_list (GSList * list_of_boxes) +{ + GSList * result = NULL; + for (GSList *i = list_of_boxes; i != NULL; i = i->next) { + if (this->has_box (SP_3DBOX (i->data))) { + result = g_slist_prepend (result, i->data); + } + } + // we reverse so as to retain the same order as in list_of_boxes + return g_slist_reverse (result); +} + /** * Update the shape of a box after a handle was dragged or a VP was changed, according to the stored ratios. */ @@ -300,6 +330,20 @@ Perspective3D::update_box_reprs () } } +// swallow the list of boxes from the other perspective and delete it +void +Perspective3D::absorb (Perspective3D *other) +{ + g_return_if_fail (*this == *other); + + // FIXME: Is copying necessary? Is other->boxes invalidated when other is deleted below? + this->boxes = g_slist_concat (this->boxes, g_slist_copy (other->boxes)); + + // Should we delete the other perspective here or at the place from where absorb() is called? + delete other; + other = NULL; +} + // FIXME: We get compiler errors when we try to move the code from sp_3dbox_get_perspective_string to this function /*** gchar * diff --git a/src/perspective3d.h b/src/perspective3d.h index c3e07b23b..4134b3fc1 100644 --- a/src/perspective3d.h +++ b/src/perspective3d.h @@ -14,6 +14,7 @@ #include "vanishing-point.h" #include "svg/stringstream.h" +#include class SP3DBox; @@ -27,25 +28,37 @@ public: Perspective3D(Perspective3D &other); ~Perspective3D(); - bool operator== (Perspective3D const &other); + bool operator== (Perspective3D const &other) const; + bool has_vanishing_point (VanishingPoint *vp); VanishingPoint *get_vanishing_point (Box3D::Axis const dir); Axis get_axis_of_VP (VanishingPoint *vp); void set_vanishing_point (Box3D::Axis const dir, VanishingPoint const &pt); void set_vanishing_point (Box3D::Axis const dir, gdouble pt_x, gdouble pt_y, gdouble dir_x, gdouble dir_y, VPState st); void add_box (SP3DBox *box); void remove_box (const SP3DBox *box); - bool has_box (const SP3DBox *box); + bool has_box (const SP3DBox *box) const; + inline guint number_of_boxes () { return g_slist_length (boxes); } void reshape_boxes (Box3D::Axis axes); void update_box_reprs (); + /* convenience functions for interaction with dragging machinery: */ + bool all_boxes_occur_in_list (GSList *boxes_to_do); + GSList * boxes_occurring_in_list (GSList * list_of_boxes); + + void absorb (Perspective3D *other); // swallow the other perspective if both coincide + static gint counter; // for testing only gint my_counter; // for testing only static GSList * perspectives; // All existing 3D perspectives + // FIXME: Perspectives should be linked to the list of existing ones automatically in the constructor + // and removed in the destructor! static void add_perspective (Box3D::Perspective3D * const persp); static void remove_perspective (Box3D::Perspective3D * const persp); - static Box3D::Perspective3D * find_perspective (Box3D::Perspective3D * const persp); // find an existing perspective whose VPs are equal to those of persp + + /* find an existing perspective whose VPs are equal to those of persp */ + static Box3D::Perspective3D * find_perspective (Box3D::Perspective3D * const persp); static void print_debugging_info(); static Perspective3D * current_perspective; diff --git a/src/vanishing-point.cpp b/src/vanishing-point.cpp index edf769c17..fa77d755f 100644 --- a/src/vanishing-point.cpp +++ b/src/vanishing-point.cpp @@ -4,13 +4,17 @@ * 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 "box3d.h" @@ -144,38 +148,202 @@ vp_drag_sel_modified (Inkscape::Selection *selection, guint flags, gpointer data //drag->updateLines (); } +// auxiliary function +static GSList * +eliminate_remaining_boxes_of_persp_starting_from_list_position (GSList *boxes_to_do, const SP3DBox *start_box, const Perspective3D *persp) +{ + GSList *i = g_slist_find (boxes_to_do, start_box); + g_return_val_if_fail (i != NULL, boxes_to_do); + + SP3DBox *box; + GSList *successor; + + i = i->next; + while (i != NULL) { + successor = i->next; + box = SP_3DBOX (i->data); + if (persp->has_box (box)) { + boxes_to_do = g_slist_remove (boxes_to_do, box); + } + i = successor; + } + + return boxes_to_do; +} + +static bool +have_VPs_of_same_perspective (VPDragger *dr1, VPDragger *dr2) +{ + Perspective3D *persp; + for (GSList *i = dr1->vps; i != NULL; i = i->next) { + persp = get_persp_of_VP ((VanishingPoint *) i->data); + if (dr2->hasPerspective (persp)) { + return true; + } + } + return false; +} + static void vp_knot_moved_handler (SPKnot *knot, NR::Point const *ppointer, guint state, gpointer data) { - g_warning ("Please implement vp_knot_moved_handler.\n"); VPDragger *dragger = (VPDragger *) data; - //VPDrag *drag = dragger->parent; + VPDrag *drag = dragger->parent; NR::Point p = *ppointer; + // FIXME: take from prefs + double snap_dist = SNAP_DIST / drag->desktop->current_zoom(); + + 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) && (NR::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 ... + for (GSList *j = dragger->vps; j != NULL; j = j->next) { + ((VanishingPoint *) j->data)->set_pos (d_new->point); + } + // ... join lists of VPs ... + // FIXME: Do we have to copy the second list (i.e, is it invalidated when dragger is deleted below)? + d_new->vps = g_slist_concat (d_new->vps, g_slist_copy (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->updateKnotShape (); + //d_new->updateTip (); + + d_new->reshapeBoxes (p, Box3D::XYZ); + d_new->updateBoxReprs (); + + // 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 (drag->desktop), SP_VERB_CONTEXT_3DBOX, + // _("Merge vanishing points")); + + return; + } + } + } + dragger->point = p; dragger->reshapeBoxes (p, Box3D::XYZ); + dragger->updateBoxReprs (); + //dragger->parent->updateLines (); //drag->local_change = false; } +/*** static void +vp_knot_clicked_handler(SPKnot *knot, guint state, gpointer data) +{ + VPDragger *dragger = (VPDragger *) data; +} +***/ + +void vp_knot_grabbed_handler (SPKnot *knot, unsigned int state, gpointer data) { VPDragger *dragger = (VPDragger *) data; + VPDrag *drag = dragger->parent; //sp_canvas_force_full_redraw_after_interruptions(dragger->parent->desktop->canvas, 5); + + if ((state & GDK_SHIFT_MASK) && !drag->hasEmptySelection()) { // FIXME: Is the second check necessary? + + if (drag->allBoxesAreSelected (dragger)) { + // if all of the boxes linked to dragger are selected, we don't need to split it + return; + } + + // we are Shift-dragging; unsnap if we carry more than one VP + + // FIXME: Should we distinguish between the following cases: + // 1) there are several VPs in a dragger + // 2) there is only a single VP but several boxes linked to it + // ? + // Or should we simply unlink all selected boxes? Currently we do the latter. + if (dragger->numberOfBoxes() > 1) { + // create a new dragger + VPDragger *dr_new = new VPDragger (drag, dragger->point, NULL); + drag->draggers = g_list_prepend (drag->draggers, dr_new); + + // move all the VPs from dragger to dr_new + dr_new->vps = dragger->vps; + dragger->vps = NULL; + + /* now we move all selected boxes back to the current dragger (splitting perspectives + if they also have unselected boxes) so that they are further reshaped during dragging */ + + GSList *boxes_to_do = drag->selectedBoxesWithVPinDragger (dr_new); + + for (GSList *i = boxes_to_do; i != NULL; i = i->next) { + SP3DBox *box = SP_3DBOX (i->data); + Perspective3D *persp = get_persp_of_box (box); + VanishingPoint *vp = dr_new->getVPofPerspective (persp); + if (vp == NULL) { + g_warning ("VP is NULL. We should be okay, though.\n"); + } + if (persp->all_boxes_occur_in_list (boxes_to_do)) { + // if all boxes of persp are selected, we can simply move the VP from dr_new back to dragger + dr_new->removeVP (vp); + dragger->addVP (vp); + + // some cleaning up for efficiency + boxes_to_do = eliminate_remaining_boxes_of_persp_starting_from_list_position (boxes_to_do, box, persp); + } else { + /* otherwise the unselected boxes need to stay linked to dr_new; thus we + create a new perspective and link the VPs to the correct draggers */ + Perspective3D *persp_new = new Perspective3D (*persp); + Perspective3D::add_perspective (persp_new); + + Axis vp_axis = persp->get_axis_of_VP (vp); + dragger->addVP (persp_new->get_vanishing_point (vp_axis)); + std::pair rem_axes = get_remaining_axes (vp_axis); + drag->addDragger (persp->get_vanishing_point (rem_axes.first)); + drag->addDragger (persp->get_vanishing_point (rem_axes.second)); + + // now we move the selected boxes from persp to persp_new + GSList * selected_boxes_of_perspective = persp->boxes_occurring_in_list (boxes_to_do); + for (GSList *j = selected_boxes_of_perspective; j != NULL; j = j->next) { + persp->remove_box (SP_3DBOX (j->data)); + persp_new->add_box (SP_3DBOX (j->data)); + } + + // cleaning up + boxes_to_do = eliminate_remaining_boxes_of_persp_starting_from_list_position (boxes_to_do, box, persp); + } + } + + // TODO: Something is still wrong with updating the boxes' representations after snapping + //dr_new->updateBoxReprs (); + } + } + + // TODO: Update the tips } static void vp_knot_ungrabbed_handler (SPKnot *knot, guint state, gpointer data) { - g_warning ("Please fully implement vp_knot_ungrabbed_handler.\n"); - VPDragger *dragger = (VPDragger *) data; - //VPDrag *drag = dragger->parent; //sp_canvas_end_forced_full_redraws(dragger->parent->desktop->canvas); @@ -199,11 +367,6 @@ vp_knot_ungrabbed_handler (SPKnot *knot, guint state, gpointer data) VPDragger::VPDragger(VPDrag *parent, NR::Point p, VanishingPoint *vp) { - if (vp == NULL) { - g_print ("VP used to create the VPDragger is NULL. This can happen when shift-dragging knots.\n"); - g_print ("How to correctly handle this? Should we just ignore it, as we currently do?\n"); - //g_assert (vp != NULL); - } this->vps = NULL; this->parent = parent; @@ -268,9 +431,13 @@ void VPDragger::addVP (VanishingPoint *vp) { if (vp == NULL) { - g_print ("No VP present in addVP. We return without adding a new VP to the list.\n"); return; } + if (g_slist_find (this->vps, vp)) { + // don't add the same VP twice + return; + } + vp->set_pos (this->point); this->vps = g_slist_prepend (this->vps, vp); @@ -290,6 +457,66 @@ VPDragger::removeVP (VanishingPoint *vp) //this->updateTip(); } +// returns the VP contained in the dragger that belongs to persp +VanishingPoint * +VPDragger::getVPofPerspective (Perspective3D *persp) +{ + for (GSList *i = vps; i != NULL; i = i->next) { + if (persp->has_vanishing_point ((VanishingPoint *) i->data)) { + return ((VanishingPoint *) i->data); + } + } + return NULL; +} + +bool +VPDragger::hasBox(const SP3DBox *box) +{ + for (GSList *i = this->vps; i != NULL; i = i->next) { + if (get_persp_of_VP ((VanishingPoint *) i->data)->has_box (box)) return true; + } + return false; +} + +guint +VPDragger::numberOfBoxes () +{ + guint num = 0; + for (GSList *i = this->vps; i != NULL; i = i->next) { + num += get_persp_of_VP ((VanishingPoint *) i->data)->number_of_boxes (); + } + return num; +} + +bool +VPDragger::hasPerspective (const Perspective3D *persp) +{ + for (GSList *i = this->vps; i != NULL; i = i->next) { + if (*persp == *get_persp_of_VP ((VanishingPoint *) i->data)) { + return true; + } + } + return false; +} + +void +VPDragger::mergePerspectives () +{ + Perspective3D *persp1, *persp2; + GSList * successor = NULL; + for (GSList *i = this->vps; i != NULL; i = i->next) { + persp1 = get_persp_of_VP ((VanishingPoint *) i->data); + for (GSList *j = i->next; j != NULL; j = successor) { + // if the perspective is deleted, the VP is invalidated, too, so we must store its successor beforehand + successor = j->next; + persp2 = get_persp_of_VP ((VanishingPoint *) j->data); + if (*persp1 == *persp2) { + persp1->absorb (persp2); // persp2 is deleted; hopefully this doesn't screw up the list of vanishing points and thus the loops + } + } + } +} + void VPDragger::reshapeBoxes (NR::Point const &p, Box3D::Axis axes) { @@ -409,6 +636,35 @@ VPDrag::updateDraggers () } } + +/** + * Returns true if all boxes that are linked to a VP in the dragger are selected + */ +bool +VPDrag::allBoxesAreSelected (VPDragger *dragger) { + GSList *selected_boxes = (GSList *) dragger->parent->selection->itemList(); + for (GSList *i = dragger->vps; i != NULL; i = i->next) { + if (!get_persp_of_VP ((VanishingPoint *) i->data)->all_boxes_occur_in_list (selected_boxes)) { + return false; + } + } + return true; +} + +GSList * +VPDrag::selectedBoxesWithVPinDragger (VPDragger *dragger) +{ + GSList *sel_boxes = g_slist_copy ((GSList *) dragger->parent->selection->itemList()); + for (GSList const *i = sel_boxes; i != NULL; i = i->next) { + SP3DBox *box = SP_3DBOX (i->data); + if (!dragger->hasBox (box)) { + sel_boxes = g_slist_remove (sel_boxes, box); + } + } + return sel_boxes; +} + + /** * 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 diff --git a/src/vanishing-point.h b/src/vanishing-point.h index 58b3b1427..76299541a 100644 --- a/src/vanishing-point.h +++ b/src/vanishing-point.h @@ -19,6 +19,8 @@ #include "line-geometry.h" // TODO: Remove this include as soon as we don't need create_canvas_(point|line) any more. +class SP3DBox; + namespace Box3D { enum VPState { @@ -70,6 +72,7 @@ public: private: }; +class Perspective3D; class VPDrag; struct VPDragger { @@ -89,6 +92,14 @@ public: void addVP(VanishingPoint *vp); void removeVP(VanishingPoint *vp); + /* returns the VP of the dragger that belongs to the given perspective */ + VanishingPoint *getVPofPerspective (Perspective3D *persp); + + bool hasBox (const SP3DBox *box); + guint numberOfBoxes(); // the number of boxes linked to all VPs of the dragger + + bool hasPerspective (const Perspective3D *perps); + void mergePerspectives (); // remove duplicate perspectives void reshapeBoxes(NR::Point const &p, Box3D::Axis axes); void updateBoxReprs(); @@ -112,11 +123,18 @@ public: void updateDraggers (); //void updateLines (); + inline bool hasEmptySelection() { return this->selection->isEmpty(); } + bool allBoxesAreSelected (VPDragger *dragger); + GSList * selectedBoxesWithVPinDragger (VPDragger *dragger); + + // FIXME: Should this be private? (It's the case with the corresponding function in gradient-drag.h) + // But vp_knot_grabbed_handler + void addDragger (VanishingPoint *vp); + private: //void deselect_all(); //void addLine (NR::Point p1, NR::Point p2, guint32 rgba); - void addDragger (VanishingPoint *vp); Inkscape::Selection *selection; sigc::connection sel_changed_connection;