Code

(Un-)Snapping of VPs by Shift-dragging; this makes it possible to separate perspectiv...
[inkscape.git] / src / vanishing-point.cpp
1 #define __VANISHING_POINT_C__
3 /*
4  * Vanishing point for 3D perspectives
5  *
6  * Authors:
7  *   bulia byak <buliabyak@users.sf.net>
8  *   Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
9  *   Maximilian Albert <Anhalter42@gmx.de>
10  *
11  * Copyright (C) 2005-2007 authors
12  *
13  * Released under GNU GPL, read the file 'COPYING' for more information
14  */
16 #include <glibmm/i18n.h>
18 #include "vanishing-point.h"
19 #include "desktop-handles.h"
20 #include "box3d.h"
22 namespace Box3D {
24 #define VP_KNOT_COLOR_NORMAL 0xffffff00
25 #define VP_KNOT_COLOR_SELECTED 0x0000ff00
27 #define VP_LINE_COLOR_FILL 0x0000ff7f
28 #define VP_LINE_COLOR_STROKE_X 0xff00007f
29 #define VP_LINE_COLOR_STROKE_Y 0x0000ff7f
30 #define VP_LINE_COLOR_STROKE_Z 0xffff007f
32 // screen pixels between knots when they snap:
33 #define SNAP_DIST 5
35 // absolute distance between gradient points for them to become a single dragger when the drag is created:
36 #define MERGE_DIST 0.1
38 // knot shapes corresponding to GrPointType enum
39 SPKnotShapeType vp_knot_shapes [] = {
40         SP_KNOT_SHAPE_SQUARE, // VP_FINITE
41         SP_KNOT_SHAPE_CIRCLE  //VP_INFINITE
42 };
44 // FIXME: We should always require to have both the point (for finite VPs)
45 //        and the direction (for infinite VPs) set. Otherwise toggling 
46 //        shows very unexpected behaviour.
47 //        Later on we can maybe infer the infinite direction from the finite point
48 //        and a suitable center of the scene. How to go in the other direction?
49 VanishingPoint::VanishingPoint(NR::Point const &pt, NR::Point const &inf_dir, VPState st)
50                              : NR::Point (pt), state (st), v_dir (inf_dir) {}
52 VanishingPoint::VanishingPoint(NR::Point const &pt)
53                              : NR::Point (pt), state (VP_FINITE), v_dir (0.0, 0.0) {}
55 VanishingPoint::VanishingPoint(NR::Point const &pt, NR::Point const &direction)
56                              : NR::Point (pt), state (VP_INFINITE), v_dir (direction) {}
58 VanishingPoint::VanishingPoint(NR::Coord x, NR::Coord y)
59                              : NR::Point(x, y), state(VP_FINITE), v_dir(0.0, 0.0) {}
61 VanishingPoint::VanishingPoint(NR::Coord dir_x, NR::Coord dir_y, VPState st)
62                              : NR::Point(0.0, 0.0), state(st), v_dir(dir_x, dir_y) {}
64 VanishingPoint::VanishingPoint(NR::Coord x, NR::Coord y, NR::Coord dir_x, NR::Coord dir_y)
65                              : NR::Point(x, y), state(VP_INFINITE), v_dir(dir_x, dir_y) {}
67 VanishingPoint::VanishingPoint(VanishingPoint const &rhs) : NR::Point (rhs)
68 {
69     this->state = rhs.state;
70     //this->ref_pt = rhs.ref_pt;
71     this->v_dir = rhs.v_dir;
72 }
74 VanishingPoint::~VanishingPoint () {}
76 bool VanishingPoint::operator== (VanishingPoint const &other)
77 {
78     // Should we compare the parent perspectives, too? Probably not.
79     if ((*this)[NR::X] == other[NR::X] && (*this)[NR::Y] == other[NR::Y]
80         && this->state == other.state && this->v_dir == other.v_dir) {
81         return true;
82     }
83     return false;
84 }
86 bool VanishingPoint::is_finite() const
87 {
88     return this->state == VP_FINITE;
89 }
91 VPState VanishingPoint::toggle_parallel()
92 {
93     if (this->state == VP_FINITE) {
94         this->state = VP_INFINITE;
95     } else {
96         this->state = VP_FINITE;
97     }
99     return this->state;
102 void VanishingPoint::draw(Box3D::Axis const axis)
104     switch (axis) {
105         case X:
106             if (state == VP_FINITE)
107                 create_canvas_point(*this, 6.0, 0xff000000);
108             else
109                 create_canvas_point(*this, 6.0, 0xffffff00);
110             break;
111         case Y:
112             if (state == VP_FINITE)
113                 create_canvas_point(*this, 6.0, 0x0000ff00);
114             else
115                 create_canvas_point(*this, 6.0, 0xffffff00);
116             break;
117         case Z:
118             if (state == VP_FINITE)
119                 create_canvas_point(*this, 6.0, 0x00770000);
120             else
121                 create_canvas_point(*this, 6.0, 0xffffff00);
122             break;
123         default:
124             g_assert_not_reached();
125             break;
126     }
129 static void
130 vp_drag_sel_changed(Inkscape::Selection *selection, gpointer data)
132     VPDrag *drag = (VPDrag *) data;
133     drag->updateDraggers ();
134     //drag->updateLines ();
137 static void
138 vp_drag_sel_modified (Inkscape::Selection *selection, guint flags, gpointer data)
140     VPDrag *drag = (VPDrag *) data;
141     /***
142     if (drag->local_change) {
143         drag->local_change = false;
144     } else {
145         drag->updateDraggers ();
146     }
147     ***/
148     //drag->updateLines ();
151 // auxiliary function
152 static GSList *
153 eliminate_remaining_boxes_of_persp_starting_from_list_position (GSList *boxes_to_do, const SP3DBox *start_box, const Perspective3D *persp)
155     GSList *i = g_slist_find (boxes_to_do, start_box);
156     g_return_val_if_fail (i != NULL, boxes_to_do);
158     SP3DBox *box;
159     GSList *successor;
161     i = i->next;
162     while (i != NULL) {
163         successor = i->next;
164         box = SP_3DBOX (i->data);
165         if (persp->has_box (box)) {
166             boxes_to_do = g_slist_remove (boxes_to_do, box);
167         }
168         i = successor;
169     }
171     return boxes_to_do;
174 static bool
175 have_VPs_of_same_perspective (VPDragger *dr1, VPDragger *dr2)
177     Perspective3D *persp;
178     for (GSList *i = dr1->vps; i != NULL; i = i->next) {
179         persp = get_persp_of_VP ((VanishingPoint *) i->data);
180         if (dr2->hasPerspective (persp)) {
181             return true;
182         }
183     }
184     return false;
187 static void
188 vp_knot_moved_handler (SPKnot *knot, NR::Point const *ppointer, guint state, gpointer data)
190     VPDragger *dragger = (VPDragger *) data;
191     VPDrag *drag = dragger->parent;
193     NR::Point p = *ppointer;
195     // FIXME: take from prefs
196     double snap_dist = SNAP_DIST / drag->desktop->current_zoom();
198     if (!(state & GDK_SHIFT_MASK)) {
199         // without Shift; see if we need to snap to another dragger
200         for (GList *di = dragger->parent->draggers; di != NULL; di = di->next) {
201             VPDragger *d_new = (VPDragger *) di->data;
202             if ((d_new != dragger) && (NR::L2 (d_new->point - p) < snap_dist)) {
203                 if (have_VPs_of_same_perspective (dragger, d_new)) {
204                     // this would result in degenerate boxes, which we disallow for the time being
205                     continue;
206                 }
208                 // update positions ...
209                 for (GSList *j = dragger->vps; j != NULL; j = j->next) {
210                     ((VanishingPoint *) j->data)->set_pos (d_new->point);
211                 }
212                 // ... join lists of VPs ...
213                 // FIXME: Do we have to copy the second list (i.e, is it invalidated when dragger is deleted below)?
214                 d_new->vps = g_slist_concat (d_new->vps, g_slist_copy (dragger->vps));
216                 // ... delete old dragger ...
217                 drag->draggers = g_list_remove (drag->draggers, dragger);
218                 delete dragger;
219                 dragger = NULL;
221                 // ... and merge any duplicate perspectives
222                 d_new->mergePerspectives();
223                     
224                 // TODO: Update the new merged dragger
225                 //d_new->updateKnotShape ();
226                 //d_new->updateTip ();
228                 d_new->reshapeBoxes (p, Box3D::XYZ);
229                 d_new->updateBoxReprs ();
231                 // TODO: Undo machinery; this doesn't work yet because perspectives must be created and
232                 //       deleted according to changes in the svg representation, not based on any user input
233                 //       as is currently the case.
235                 //sp_document_done (sp_desktop_document (drag->desktop), SP_VERB_CONTEXT_3DBOX,
236                 //                  _("Merge vanishing points"));
238                 return;
239             }
240         }
241     }
243     dragger->point = p;
245     dragger->reshapeBoxes (p, Box3D::XYZ);
246     dragger->updateBoxReprs ();
248     //dragger->parent->updateLines ();
250     //drag->local_change = false;
253 /***
254 static void
255 vp_knot_clicked_handler(SPKnot *knot, guint state, gpointer data)
257     VPDragger *dragger = (VPDragger *) data;
259 ***/
261 void
262 vp_knot_grabbed_handler (SPKnot *knot, unsigned int state, gpointer data)
264     VPDragger *dragger = (VPDragger *) data;
265     VPDrag *drag = dragger->parent;
267     //sp_canvas_force_full_redraw_after_interruptions(dragger->parent->desktop->canvas, 5);
269     if ((state & GDK_SHIFT_MASK) && !drag->hasEmptySelection()) { // FIXME: Is the second check necessary?
271         if (drag->allBoxesAreSelected (dragger)) {
272             // if all of the boxes linked to dragger are selected, we don't need to split it
273             return;
274         }
276         // we are Shift-dragging; unsnap if we carry more than one VP
278         // FIXME: Should we distinguish between the following cases:
279         //        1) there are several VPs in a dragger
280         //        2) there is only a single VP but several boxes linked to it
281         //           ?
282         //        Or should we simply unlink all selected boxes? Currently we do the latter.
283         if (dragger->numberOfBoxes() > 1) {
284             // create a new dragger
285             VPDragger *dr_new = new VPDragger (drag, dragger->point, NULL);
286             drag->draggers = g_list_prepend (drag->draggers, dr_new);
288             // move all the VPs from dragger to dr_new
289             dr_new->vps = dragger->vps;
290             dragger->vps = NULL;
292             /* now we move all selected boxes back to the current dragger (splitting perspectives
293                if they also have unselected boxes) so that they are further reshaped during dragging */
295             GSList *boxes_to_do = drag->selectedBoxesWithVPinDragger (dr_new);
297             for (GSList *i = boxes_to_do; i != NULL; i = i->next) {
298                 SP3DBox *box = SP_3DBOX (i->data);
299                 Perspective3D *persp = get_persp_of_box (box);
300                 VanishingPoint *vp = dr_new->getVPofPerspective (persp);
301                 if (vp == NULL) {
302                     g_warning ("VP is NULL. We should be okay, though.\n");
303                 }
304                 if (persp->all_boxes_occur_in_list (boxes_to_do)) {
305                     // if all boxes of persp are selected, we can simply move the VP from dr_new back to dragger
306                     dr_new->removeVP (vp);
307                     dragger->addVP (vp);
308                     
309                     // some cleaning up for efficiency
310                     boxes_to_do = eliminate_remaining_boxes_of_persp_starting_from_list_position (boxes_to_do, box, persp);
311                 } else {
312                     /* otherwise the unselected boxes need to stay linked to dr_new; thus we
313                        create a new perspective and link the VPs to the correct draggers */
314                     Perspective3D *persp_new = new Perspective3D (*persp);
315                     Perspective3D::add_perspective (persp_new);
317                     Axis vp_axis = persp->get_axis_of_VP (vp);
318                     dragger->addVP (persp_new->get_vanishing_point (vp_axis));
319                     std::pair<Axis, Axis> rem_axes = get_remaining_axes (vp_axis);
320                     drag->addDragger (persp->get_vanishing_point (rem_axes.first));
321                     drag->addDragger (persp->get_vanishing_point (rem_axes.second));
323                     // now we move the selected boxes from persp to persp_new
324                     GSList * selected_boxes_of_perspective = persp->boxes_occurring_in_list (boxes_to_do);
325                     for (GSList *j = selected_boxes_of_perspective; j != NULL; j = j->next) {
326                         persp->remove_box (SP_3DBOX (j->data));
327                         persp_new->add_box (SP_3DBOX (j->data));
328                     }
330                     // cleaning up
331                     boxes_to_do = eliminate_remaining_boxes_of_persp_starting_from_list_position (boxes_to_do, box, persp);
332                 }
333             }
335             // TODO: Something is still wrong with updating the boxes' representations after snapping
336             //dr_new->updateBoxReprs ();
337         }
338     }
340     // TODO: Update the tips
343 static void
344 vp_knot_ungrabbed_handler (SPKnot *knot, guint state, gpointer data)
346     VPDragger *dragger = (VPDragger *) data;
348     //sp_canvas_end_forced_full_redraws(dragger->parent->desktop->canvas);
350     dragger->point_original = dragger->point = knot->pos;
352     /***
353     VanishingPoint *vp;
354     for (GSList *i = dragger->vps; i != NULL; i = i->next) {
355         vp = (VanishingPoint *) i->data;
356         vp->set_pos (knot->pos);
357     }
358     ***/
360     dragger->parent->updateDraggers ();
361     dragger->updateBoxReprs ();
363     // TODO: Update box's paths and svg representation
365     // TODO: Undo machinery!!
368 VPDragger::VPDragger(VPDrag *parent, NR::Point p, VanishingPoint *vp)
370     this->vps = NULL;
372     this->parent = parent;
374     this->point = p;
375     this->point_original = p;
377     // create the knot
378     this->knot = sp_knot_new (parent->desktop, NULL);
379     this->knot->setMode(SP_KNOT_MODE_XOR);
380     this->knot->setFill(VP_KNOT_COLOR_NORMAL, VP_KNOT_COLOR_NORMAL, VP_KNOT_COLOR_NORMAL);
381     this->knot->setStroke(0x000000ff, 0x000000ff, 0x000000ff);
382     sp_knot_update_ctrl(this->knot);
384     // move knot to the given point
385     sp_knot_set_position (this->knot, &this->point, SP_KNOT_STATE_NORMAL);
386     sp_knot_show (this->knot);
388     // connect knot's signals
389     g_signal_connect (G_OBJECT (this->knot), "moved", G_CALLBACK (vp_knot_moved_handler), this);
390     /***
391     g_signal_connect (G_OBJECT (this->knot), "clicked", G_CALLBACK (vp_knot_clicked_handler), this);
392     ***/
393     g_signal_connect (G_OBJECT (this->knot), "grabbed", G_CALLBACK (vp_knot_grabbed_handler), this);
394     g_signal_connect (G_OBJECT (this->knot), "ungrabbed", G_CALLBACK (vp_knot_ungrabbed_handler), this);
395     /***
396     g_signal_connect (G_OBJECT (this->knot), "doubleclicked", G_CALLBACK (vp_knot_doubleclicked_handler), this);
397     ***/
399     // add the initial VP (which may be NULL!)
400     this->addVP (vp);
401     //updateKnotShape();
404 VPDragger::~VPDragger()
406     // unselect if it was selected
407     //this->parent->setDeselected(this);
409     // disconnect signals
410     g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_moved_handler), this);
411     /***
412     g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_clicked_handler), this);
413     ***/
414     g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_grabbed_handler), this);
415     g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_ungrabbed_handler), this);
416     /***
417     g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_doubleclicked_handler), this);
418     ***/
420     /* unref should call destroy */
421     g_object_unref (G_OBJECT (this->knot));
423     g_slist_free (this->vps);
424     this->vps = NULL;
427 /**
428  * Adds a vanishing point to the dragger (also updates the position)
429  */
430 void
431 VPDragger::addVP (VanishingPoint *vp)
433     if (vp == NULL) {
434         return;
435     }
436     if (g_slist_find (this->vps, vp)) {
437         // don't add the same VP twice
438         return;
439     }
441     vp->set_pos (this->point);
442     this->vps = g_slist_prepend (this->vps, vp);
444     //this->updateTip();
447 void
448 VPDragger::removeVP (VanishingPoint *vp)
450     if (vp == NULL) {
451         g_print ("NULL vanishing point will not be removed.\n");
452         return;
453     }
454     g_assert (this->vps != NULL);
455     this->vps = g_slist_remove (this->vps, vp);
457     //this->updateTip();
460 // returns the VP contained in the dragger that belongs to persp
461 VanishingPoint *
462 VPDragger::getVPofPerspective (Perspective3D *persp)
464     for (GSList *i = vps; i != NULL; i = i->next) {
465         if (persp->has_vanishing_point ((VanishingPoint *) i->data)) {
466             return ((VanishingPoint *) i->data);
467         }
468     }
469     return NULL;
472 bool
473 VPDragger::hasBox(const SP3DBox *box)
475     for (GSList *i = this->vps; i != NULL; i = i->next) {
476         if (get_persp_of_VP ((VanishingPoint *) i->data)->has_box (box)) return true;
477     }
478     return false;
481 guint
482 VPDragger::numberOfBoxes ()
484     guint num = 0;
485     for (GSList *i = this->vps; i != NULL; i = i->next) {
486         num += get_persp_of_VP ((VanishingPoint *) i->data)->number_of_boxes ();
487     }
488     return num;
491 bool
492 VPDragger::hasPerspective (const Perspective3D *persp)
494     for (GSList *i = this->vps; i != NULL; i = i->next) {
495         if (*persp == *get_persp_of_VP ((VanishingPoint *) i->data)) {
496             return true;
497         }        
498     }
499     return false;
502 void
503 VPDragger::mergePerspectives ()
505     Perspective3D *persp1, *persp2;
506     GSList * successor = NULL;
507     for (GSList *i = this->vps; i != NULL; i = i->next) {
508         persp1 = get_persp_of_VP ((VanishingPoint *) i->data);
509         for (GSList *j = i->next; j != NULL; j = successor) {
510             // if the perspective is deleted, the VP is invalidated, too, so we must store its successor beforehand
511             successor = j->next;
512             persp2 = get_persp_of_VP ((VanishingPoint *) j->data);
513             if (*persp1 == *persp2) {
514                 persp1->absorb (persp2); // persp2 is deleted; hopefully this doesn't screw up the list of vanishing points and thus the loops
515             }
516         }
517     }
520 void
521 VPDragger::reshapeBoxes (NR::Point const &p, Box3D::Axis axes)
523     Perspective3D *persp;
524     for (GSList const* i = this->vps; i != NULL; i = i->next) {
525         VanishingPoint *vp = (VanishingPoint *) i->data;
526         // TODO: We can extract the VP directly from the box's perspective. Is that vanishing point identical to 'vp'?
527         //       Or is there duplicated information? If so, remove it and simplify the whole construction!
528         vp->set_pos(p);
529         persp = get_persp_of_VP (vp);
530         Box3D::Axis axis = persp->get_axis_of_VP (vp);
531         get_persp_of_VP (vp)->reshape_boxes (axis); // FIXME: we should only update the direction of the VP
532     }
535 void
536 VPDragger::updateBoxReprs ()
538     for (GSList *i = this->vps; i != NULL; i = i->next) {
539         Box3D::get_persp_of_VP ((VanishingPoint *) i->data)->update_box_reprs ();
540     }
543 VPDrag::VPDrag (SPDesktop *desktop)
545     this->desktop = desktop;
546     this->draggers = NULL;
547     this->selection = sp_desktop_selection(desktop);
549     this->sel_changed_connection = this->selection->connectChanged(
550         sigc::bind (
551             sigc::ptr_fun(&vp_drag_sel_changed),
552             (gpointer)this )
554         );
555     this->sel_modified_connection = this->selection->connectModified(
556         sigc::bind(
557             sigc::ptr_fun(&vp_drag_sel_modified),
558             (gpointer)this )
559         );
561     this->updateDraggers ();
562     //this->updateLines ();
565 VPDrag::~VPDrag()
567     this->sel_changed_connection.disconnect();
568     this->sel_modified_connection.disconnect();
570     for (GList *l = this->draggers; l != NULL; l = l->next) {
571         delete ((VPDragger *) l->data);
572     }
573     g_list_free (this->draggers);
574     this->draggers = NULL;
577 /**
578  * Select the dragger that has the given VP.
579  */
580 VPDragger *
581 VPDrag::getDraggerFor (VanishingPoint const &vp)
583     for (GList const* i = this->draggers; i != NULL; i = i->next) {
584         VPDragger *dragger = (VPDragger *) i->data;
585         for (GSList const* j = dragger->vps; j != NULL; j = j->next) {
586             VanishingPoint *vp2 = (VanishingPoint *) j->data;
587             g_assert (vp2 != NULL);
589             // TODO: Should we compare the pointers or the VPs themselves!?!?!?!
590             //if ((*vp2) == vp) {
591             if (vp2 == &vp) {
592                 return (dragger);
593             }
594         }
595     }
596     return NULL;
599 /**
600  * Regenerates the draggers list from the current selection; is called when selection is changed or modified
601  */
602 void
603 VPDrag::updateDraggers ()
605     /***
606     while (selected) {
607         selected = g_list_remove(selected, selected->data);
608     }
609     ***/
610     // delete old draggers
611     for (GList const* i = this->draggers; i != NULL; i = i->next) {
612         delete ((VPDragger *) i->data);
613     }
614     g_list_free (this->draggers);
615     this->draggers = NULL;
617     g_return_if_fail (this->selection != NULL);
619     for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
620         SPItem *item = SP_ITEM(i->data);
621         //SPStyle *style = SP_OBJECT_STYLE (item);
623         if (!SP_IS_3DBOX (item)) continue;
624         SP3DBox *box = SP_3DBOX (item);
626         // FIXME: Get the VPs from the selection!!!!
627         //addDragger (Box3D::Perspective3D::current_perspective->get_vanishing_point(Box3D::X));
628         //addDragger (Box3D::Perspective3D::current_perspective->get_vanishing_point(Box3D::Y));
629         //addDragger (Box3D::Perspective3D::current_perspective->get_vanishing_point(Box3D::Z));
631         //Box3D::Perspective3D *persp = box->perspective;
632         Box3D::Perspective3D *persp = Box3D::get_persp_of_box (box);
633         addDragger (persp->get_vanishing_point(Box3D::X));
634         addDragger (persp->get_vanishing_point(Box3D::Y));
635         addDragger (persp->get_vanishing_point(Box3D::Z));
636     }
640 /**
641  * Returns true if all boxes that are linked to a VP in the dragger are selected
642  */
643 bool
644 VPDrag::allBoxesAreSelected (VPDragger *dragger) {
645     GSList *selected_boxes = (GSList *) dragger->parent->selection->itemList();
646     for (GSList *i = dragger->vps; i != NULL; i = i->next) {
647         if (!get_persp_of_VP ((VanishingPoint *) i->data)->all_boxes_occur_in_list (selected_boxes)) {
648             return false;
649         }
650     }
651     return true;
654 GSList *
655 VPDrag::selectedBoxesWithVPinDragger (VPDragger *dragger)
657     GSList *sel_boxes = g_slist_copy ((GSList *) dragger->parent->selection->itemList());
658     for (GSList const *i = sel_boxes; i != NULL; i = i->next) {
659         SP3DBox *box = SP_3DBOX (i->data);
660         if (!dragger->hasBox (box)) {
661             sel_boxes = g_slist_remove (sel_boxes, box);
662         }
663     }
664     return sel_boxes;
668 /**
669  * If there already exists a dragger within MERGE_DIST of p, add the VP to it;
670  * otherwise create new dragger and add it to draggers list
671  */
672 void
673 VPDrag::addDragger (VanishingPoint *vp)
675     if (vp == NULL) {
676         g_print ("Warning: The VP in addDragger is already NULL. Aborting.\n)");
677         g_assert (vp != NULL);
678     }
679     NR::Point p = vp->get_pos();
681     for (GList *i = this->draggers; i != NULL; i = i->next) {
682         VPDragger *dragger = (VPDragger *) i->data;
683         if (NR::L2 (dragger->point - p) < MERGE_DIST) {
684             // distance is small, merge this draggable into dragger, no need to create new dragger
685             dragger->addVP (vp);
686             //dragger->updateKnotShape();
687             return;
688         }
689     }
691     VPDragger *new_dragger = new VPDragger(this, p, vp);
692     // fixme: draggers should be added AFTER the last one: this way tabbing through them will be from begin to end.
693     this->draggers = g_list_append (this->draggers, new_dragger);
696 } // namespace Box3D 
697  
698 /*
699   Local Variables:
700   mode:c++
701   c-file-style:"stroustrup"
702   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
703   indent-tabs-mode:nil
704   fill-column:99
705   End:
706 */
707 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :