Code

(Un-)Snapping of VPs by Shift-dragging; this makes it possible to separate perspectiv...
authorcilix42 <cilix42@users.sourceforge.net>
Mon, 6 Aug 2007 07:49:54 +0000 (07:49 +0000)
committercilix42 <cilix42@users.sourceforge.net>
Mon, 6 Aug 2007 07:49:54 +0000 (07:49 +0000)
src/axis-manip.cpp
src/axis-manip.h
src/perspective3d.cpp
src/perspective3d.h
src/vanishing-point.cpp
src/vanishing-point.h

index 094da7dde8634909ff660a084f97a0b42e61b601..c2edbfb89d06da593b8bb2b55b3d189887149e26 100644 (file)
@@ -19,6 +19,13 @@ Axis axes[3]   = { X,  Y,  Z };
 Axis planes[3] = { XY, XZ, YZ };
 FrontOrRear face_positions [2] = { FRONT, REAR };
 
+std::pair <Axis, Axis>
+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 
  
 /*
index 7ad716046286d9474d633af5925735748498a2a6..7461678d5321f2dd4814e64eb799b02c09dd3958 100644 (file)
@@ -113,6 +113,8 @@ inline gchar * string_from_axes (Box3D::Axis axes) {
     return pstring->str;
 }
 
+std::pair <Axis, Axis> get_remaining_axes (Axis axis);
+
 } // namespace Box3D
 
 #endif /* !SEEN_AXIS_MANIP_H */
index e5b1f62126f25983d026002cc3a8e5d3fefc3bbc..2137449373735c8a62dfec0f67fb6b95b37692f1 100644 (file)
@@ -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 *
index c3e07b23b3c0b357ee83d69de6e46c8b48444e35..4134b3fc1ffc59d970fa7914a26f6510efbc6bf8 100644 (file)
@@ -14,6 +14,7 @@
 
 #include "vanishing-point.h"
 #include "svg/stringstream.h"
+#include <glib.h>
 
 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;
index edf769c176388f9c6164836167639bc3fda1ad44..fa77d755fcef9959d29cdd5f7d35a8ea1e0084e1 100644 (file)
@@ -4,13 +4,17 @@
  * 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>
  *
- * Copyright (C) 2007 authors
+ * Copyright (C) 2005-2007 authors
  *
  * Released under GNU GPL, read the file 'COPYING' for more information
  */
 
+#include <glibmm/i18n.h>
+
 #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<Axis, Axis> 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
index 58b3b142727c8fd93b8f1b8b28d8547e5879e818..76299541ab7e7a11fc57b60183a595867542ed56 100644 (file)
@@ -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;