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;
100 }
102 void VanishingPoint::draw(Box3D::Axis const axis)
103 {
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 }
127 }
129 static void
130 vp_drag_sel_changed(Inkscape::Selection *selection, gpointer data)
131 {
132 VPDrag *drag = (VPDrag *) data;
133 drag->updateDraggers ();
134 //drag->updateLines ();
135 }
137 static void
138 vp_drag_sel_modified (Inkscape::Selection *selection, guint flags, gpointer data)
139 {
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 ();
149 }
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)
154 {
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;
172 }
174 static bool
175 have_VPs_of_same_perspective (VPDragger *dr1, VPDragger *dr2)
176 {
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;
185 }
187 static void
188 vp_knot_moved_handler (SPKnot *knot, NR::Point const *ppointer, guint state, gpointer data)
189 {
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();
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;
251 }
253 /***
254 static void
255 vp_knot_clicked_handler(SPKnot *knot, guint state, gpointer data)
256 {
257 VPDragger *dragger = (VPDragger *) data;
258 }
259 ***/
261 void
262 vp_knot_grabbed_handler (SPKnot *knot, unsigned int state, gpointer data)
263 {
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);
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
341 }
343 static void
344 vp_knot_ungrabbed_handler (SPKnot *knot, guint state, gpointer data)
345 {
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!!
366 }
368 VPDragger::VPDragger(VPDrag *parent, NR::Point p, VanishingPoint *vp)
369 {
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();
402 }
404 VPDragger::~VPDragger()
405 {
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;
425 }
427 /**
428 * Adds a vanishing point to the dragger (also updates the position)
429 */
430 void
431 VPDragger::addVP (VanishingPoint *vp)
432 {
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();
445 }
447 void
448 VPDragger::removeVP (VanishingPoint *vp)
449 {
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();
458 }
460 // returns the VP contained in the dragger that belongs to persp
461 VanishingPoint *
462 VPDragger::getVPofPerspective (Perspective3D *persp)
463 {
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;
470 }
472 bool
473 VPDragger::hasBox(const SP3DBox *box)
474 {
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;
479 }
481 guint
482 VPDragger::numberOfBoxes ()
483 {
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;
489 }
491 bool
492 VPDragger::hasPerspective (const Perspective3D *persp)
493 {
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;
500 }
502 void
503 VPDragger::mergePerspectives ()
504 {
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 }
518 }
520 void
521 VPDragger::reshapeBoxes (NR::Point const &p, Box3D::Axis axes)
522 {
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 }
533 }
535 void
536 VPDragger::updateBoxReprs ()
537 {
538 for (GSList *i = this->vps; i != NULL; i = i->next) {
539 Box3D::get_persp_of_VP ((VanishingPoint *) i->data)->update_box_reprs ();
540 }
541 }
543 VPDrag::VPDrag (SPDesktop *desktop)
544 {
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 ();
563 }
565 VPDrag::~VPDrag()
566 {
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;
575 }
577 /**
578 * Select the dragger that has the given VP.
579 */
580 VPDragger *
581 VPDrag::getDraggerFor (VanishingPoint const &vp)
582 {
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;
597 }
599 /**
600 * Regenerates the draggers list from the current selection; is called when selection is changed or modified
601 */
602 void
603 VPDrag::updateDraggers ()
604 {
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 }
637 }
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;
652 }
654 GSList *
655 VPDrag::selectedBoxesWithVPinDragger (VPDragger *dragger)
656 {
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;
665 }
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)
674 {
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);
694 }
696 } // namespace Box3D
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 :