1 /*
2 * Vanishing point for 3D perspectives
3 *
4 * Authors:
5 * bulia byak <buliabyak@users.sf.net>
6 * Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
7 * Maximilian Albert <Anhalter42@gmx.de>
8 * Abhishek Sharma
9 *
10 * Copyright (C) 2005-2007 authors
11 *
12 * Released under GNU GPL, read the file 'COPYING' for more information
13 */
15 #include <glibmm/i18n.h>
17 #include "vanishing-point.h"
18 #include "desktop-handles.h"
19 #include "desktop.h"
20 #include "event-context.h"
21 #include "xml/repr.h"
22 #include "perspective-line.h"
23 #include "shape-editor.h"
24 #include "snap.h"
25 #include "sp-namedview.h"
27 using Inkscape::DocumentUndo;
29 namespace Box3D {
31 #define VP_KNOT_COLOR_NORMAL 0xffffff00
32 #define VP_KNOT_COLOR_SELECTED 0x0000ff00
34 #define VP_LINE_COLOR_FILL 0x0000ff7f
35 #define VP_LINE_COLOR_STROKE_X 0xff00007f
36 #define VP_LINE_COLOR_STROKE_Y 0x0000ff7f
37 #define VP_LINE_COLOR_STROKE_Z 0xffff007f
39 // screen pixels between knots when they snap:
40 #define SNAP_DIST 5
42 // absolute distance between gradient points for them to become a single dragger when the drag is created:
43 #define MERGE_DIST 0.1
45 // knot shapes corresponding to GrPointType enum
46 SPKnotShapeType vp_knot_shapes [] = {
47 SP_KNOT_SHAPE_SQUARE, // VP_FINITE
48 SP_KNOT_SHAPE_CIRCLE //VP_INFINITE
49 };
51 static void
52 vp_drag_sel_changed(Inkscape::Selection */*selection*/, gpointer data)
53 {
54 VPDrag *drag = (VPDrag *) data;
55 drag->updateDraggers();
56 drag->updateLines();
57 drag->updateBoxReprs();
58 }
60 static void
61 vp_drag_sel_modified (Inkscape::Selection */*selection*/, guint /*flags*/, gpointer data)
62 {
63 VPDrag *drag = (VPDrag *) data;
64 drag->updateLines ();
65 //drag->updateBoxReprs();
66 drag->updateBoxHandles (); // FIXME: Only update the handles of boxes on this dragger (not on all)
67 drag->updateDraggers ();
68 }
70 static bool
71 have_VPs_of_same_perspective (VPDragger *dr1, VPDragger *dr2)
72 {
73 for (std::list<VanishingPoint>::iterator i = dr1->vps.begin(); i != dr1->vps.end(); ++i) {
74 if (dr2->hasPerspective ((*i).get_perspective())) {
75 return true;
76 }
77 }
78 return false;
79 }
81 static void
82 vp_knot_moved_handler (SPKnot *knot, Geom::Point const *ppointer, guint state, gpointer data)
83 {
84 VPDragger *dragger = (VPDragger *) data;
85 VPDrag *drag = dragger->parent;
87 Geom::Point p = *ppointer;
89 // FIXME: take from prefs
90 double snap_dist = SNAP_DIST / inkscape_active_desktop()->current_zoom();
92 /*
93 * We use dragging_started to indicate if we have already checked for the need to split Draggers up.
94 * This only has the purpose of avoiding costly checks in the routine below.
95 */
96 if (!dragger->dragging_started && (state & GDK_SHIFT_MASK)) {
97 /* with Shift; if there is more than one box linked to this VP
98 we need to split it and create a new perspective */
99 if (dragger->numberOfBoxes() > 1) { // FIXME: Don't do anything if *all* boxes of a VP are selected
100 std::set<VanishingPoint*, less_ptr> sel_vps = dragger->VPsOfSelectedBoxes();
102 std::list<SPBox3D *> sel_boxes;
103 for (std::set<VanishingPoint*, less_ptr>::iterator vp = sel_vps.begin(); vp != sel_vps.end(); ++vp) {
104 // for each VP that has selected boxes:
105 Persp3D *old_persp = (*vp)->get_perspective();
106 sel_boxes = (*vp)->selectedBoxes(sp_desktop_selection(inkscape_active_desktop()));
108 // we create a new perspective ...
109 Persp3D *new_persp = persp3d_create_xml_element (dragger->parent->document, old_persp->perspective_impl);
111 /* ... unlink the boxes from the old one and
112 FIXME: We need to unlink the _un_selected boxes of each VP so that
113 the correct boxes are kept with the VP being moved */
114 std::list<SPBox3D *> bx_lst = persp3d_list_of_boxes(old_persp);
115 for (std::list<SPBox3D *>::iterator i = bx_lst.begin(); i != bx_lst.end(); ++i) {
116 if (std::find(sel_boxes.begin(), sel_boxes.end(), *i) == sel_boxes.end()) {
117 /* if a box in the VP is unselected, move it to the
118 newly created perspective so that it doesn't get dragged **/
119 box3d_switch_perspectives(*i, old_persp, new_persp);
120 }
121 }
122 }
123 // FIXME: Do we need to create a new dragger as well?
124 dragger->updateZOrders ();
125 DocumentUndo::done(sp_desktop_document (inkscape_active_desktop()), SP_VERB_CONTEXT_3DBOX,
126 _("Split vanishing points"));
127 return;
128 }
129 }
131 if (!(state & GDK_SHIFT_MASK)) {
132 // without Shift; see if we need to snap to another dragger
133 for (GList *di = dragger->parent->draggers; di != NULL; di = di->next) {
134 VPDragger *d_new = (VPDragger *) di->data;
135 if ((d_new != dragger) && (Geom::L2 (d_new->point - p) < snap_dist)) {
136 if (have_VPs_of_same_perspective (dragger, d_new)) {
137 // this would result in degenerate boxes, which we disallow for the time being
138 continue;
139 }
141 // update positions ... (this is needed so that the perspectives are detected as identical)
142 // FIXME: This is called a bit too often, isn't it?
143 for (std::list<VanishingPoint>::iterator j = dragger->vps.begin(); j != dragger->vps.end(); ++j) {
144 (*j).set_pos(d_new->point);
145 }
147 // ... join lists of VPs ...
148 d_new->vps.merge(dragger->vps);
150 // ... delete old dragger ...
151 drag->draggers = g_list_remove (drag->draggers, dragger);
152 delete dragger;
153 dragger = NULL;
155 // ... and merge any duplicate perspectives
156 d_new->mergePerspectives();
158 // TODO: Update the new merged dragger
159 d_new->updateTip();
161 d_new->parent->updateBoxDisplays (); // FIXME: Only update boxes in current dragger!
162 d_new->updateZOrders ();
164 drag->updateLines ();
166 // TODO: Undo machinery; this doesn't work yet because perspectives must be created and
167 // deleted according to changes in the svg representation, not based on any user input
168 // as is currently the case.
170 DocumentUndo::done(sp_desktop_document (inkscape_active_desktop()), SP_VERB_CONTEXT_3DBOX,
171 _("Merge vanishing points"));
173 return;
174 }
175 }
177 // We didn't snap to another dragger, so we'll try a regular snap
178 SPDesktop *desktop = inkscape_active_desktop();
179 SnapManager &m = desktop->namedview->snap_manager;
180 m.setup(desktop);
181 Inkscape::SnappedPoint s = m.freeSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_OTHER_HANDLE));
182 m.unSetup();
183 if (s.getSnapped()) {
184 p = s.getPoint();
185 sp_knot_moveto(knot, p);
186 }
187 }
189 dragger->point = p; // FIXME: Is dragger->point being used at all?
191 dragger->updateVPs(p);
192 dragger->updateBoxDisplays();
193 dragger->parent->updateBoxHandles (); // FIXME: Only update the handles of boxes on this dragger (not on all)
194 dragger->updateZOrders();
196 drag->updateLines();
198 dragger->dragging_started = true;
199 }
201 void
202 vp_knot_grabbed_handler (SPKnot */*knot*/, unsigned int /*state*/, gpointer data)
203 {
204 VPDragger *dragger = (VPDragger *) data;
205 VPDrag *drag = dragger->parent;
207 drag->dragging = true;
208 }
210 static void
211 vp_knot_ungrabbed_handler (SPKnot *knot, guint /*state*/, gpointer data)
212 {
213 VPDragger *dragger = (VPDragger *) data;
215 dragger->point_original = dragger->point = knot->pos;
217 dragger->dragging_started = false;
219 for (std::list<VanishingPoint>::iterator i = dragger->vps.begin(); i != dragger->vps.end(); ++i) {
220 (*i).set_pos (knot->pos);
221 (*i).updateBoxReprs();
222 (*i).updatePerspRepr();
223 }
225 dragger->parent->updateDraggers ();
226 dragger->parent->updateLines ();
227 dragger->parent->updateBoxHandles ();
229 // TODO: Update box's paths and svg representation
231 dragger->parent->dragging = false;
233 // TODO: Undo machinery!!
234 g_return_if_fail (dragger->parent);
235 g_return_if_fail (dragger->parent->document);
236 DocumentUndo::done(dragger->parent->document, SP_VERB_CONTEXT_3DBOX,
237 _("3D box: Move vanishing point"));
238 }
240 unsigned int VanishingPoint::global_counter = 0;
242 // FIXME: Rename to something more meaningful!
243 void
244 VanishingPoint::set_pos(Proj::Pt2 const &pt) {
245 g_return_if_fail (_persp);
246 _persp->perspective_impl->tmat.set_image_pt (_axis, pt);
247 }
249 std::list<SPBox3D *>
250 VanishingPoint::selectedBoxes(Inkscape::Selection *sel) {
251 std::list<SPBox3D *> sel_boxes;
252 for (GSList const* i = sel->itemList(); i != NULL; i = i->next) {
253 if (!SP_IS_BOX3D(i->data))
254 continue;
255 SPBox3D *box = SP_BOX3D(i->data);
256 if (this->hasBox(box)) {
257 sel_boxes.push_back (box);
258 }
259 }
260 return sel_boxes;
261 }
263 VPDragger::VPDragger(VPDrag *parent, Geom::Point p, VanishingPoint &vp)
264 {
265 this->parent = parent;
267 this->point = p;
268 this->point_original = p;
270 this->dragging_started = false;
272 if (vp.is_finite()) {
273 // create the knot
274 this->knot = sp_knot_new (inkscape_active_desktop(), NULL);
275 this->knot->setMode(SP_KNOT_MODE_XOR);
276 this->knot->setFill(VP_KNOT_COLOR_NORMAL, VP_KNOT_COLOR_NORMAL, VP_KNOT_COLOR_NORMAL);
277 this->knot->setStroke(0x000000ff, 0x000000ff, 0x000000ff);
278 sp_knot_update_ctrl(this->knot);
280 // move knot to the given point
281 sp_knot_set_position (this->knot, this->point, SP_KNOT_STATE_NORMAL);
282 sp_knot_show (this->knot);
284 // connect knot's signals
285 g_signal_connect (G_OBJECT (this->knot), "moved", G_CALLBACK (vp_knot_moved_handler), this);
286 g_signal_connect (G_OBJECT (this->knot), "grabbed", G_CALLBACK (vp_knot_grabbed_handler), this);
287 g_signal_connect (G_OBJECT (this->knot), "ungrabbed", G_CALLBACK (vp_knot_ungrabbed_handler), this);
289 // add the initial VP (which may be NULL!)
290 this->addVP (vp);
291 }
292 }
294 VPDragger::~VPDragger()
295 {
296 // disconnect signals
297 g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_moved_handler), this);
298 g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_grabbed_handler), this);
299 g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_ungrabbed_handler), this);
300 /* unref should call destroy */
301 g_object_unref (G_OBJECT (this->knot));
302 }
304 /**
305 Updates the statusbar tip of the dragger knot, based on its draggables
306 */
307 void
308 VPDragger::updateTip ()
309 {
310 if (this->knot && this->knot->tip) {
311 g_free (this->knot->tip);
312 this->knot->tip = NULL;
313 }
315 guint num = this->numberOfBoxes();
316 if (this->vps.size() == 1) {
317 if (this->vps.front().is_finite()) {
318 this->knot->tip = g_strdup_printf (ngettext("<b>Finite</b> vanishing point shared by <b>%d</b> box",
319 "<b>Finite</b> vanishing point shared by <b>%d</b> boxes; drag with <b>Shift</b> to separate selected box(es)",
320 num),
321 num);
322 } else {
323 // This won't make sense any more when infinite VPs are not shown on the canvas,
324 // but currently we update the status message anyway
325 this->knot->tip = g_strdup_printf (ngettext("<b>Infinite</b> vanishing point shared by <b>%d</b> box",
326 "<b>Infinite</b> vanishing point shared by <b>%d</b> boxes; drag with <b>Shift</b> to separate selected box(es)",
327 num),
328 num);
329 }
330 } else {
331 int length = this->vps.size();
332 char *desc1 = g_strdup_printf ("Collection of <b>%d</b> vanishing points ", length);
333 char *desc2 = g_strdup_printf (ngettext("shared by <b>%d</b> box; drag with <b>Shift</b> to separate selected box(es)",
334 "shared by <b>%d</b> boxes; drag with <b>Shift</b> to separate selected box(es)",
335 num),
336 num);
337 this->knot->tip = g_strconcat(desc1, desc2, NULL);
338 g_free (desc1);
339 g_free (desc2);
340 }
341 }
343 /**
344 * Adds a vanishing point to the dragger (also updates the position if necessary);
345 * the perspective is stored separately, too, for efficiency in updating boxes.
346 */
347 void
348 VPDragger::addVP (VanishingPoint &vp, bool update_pos)
349 {
350 if (!vp.is_finite() || std::find (vps.begin(), vps.end(), vp) != vps.end()) {
351 // don't add infinite VPs; don't add the same VP twice
352 return;
353 }
355 if (update_pos) {
356 vp.set_pos (this->point);
357 }
358 this->vps.push_front (vp);
360 this->updateTip();
361 }
363 void
364 VPDragger::removeVP (VanishingPoint const &vp)
365 {
366 std::list<VanishingPoint>::iterator i = std::find (this->vps.begin(), this->vps.end(), vp);
367 if (i != this->vps.end()) {
368 this->vps.erase (i);
369 }
370 this->updateTip();
371 }
373 VanishingPoint *
374 VPDragger::findVPWithBox (SPBox3D *box) {
375 for (std::list<VanishingPoint>::iterator vp = vps.begin(); vp != vps.end(); ++vp) {
376 if ((*vp).hasBox(box)) {
377 return &(*vp);
378 }
379 }
380 return NULL;
381 }
383 std::set<VanishingPoint*, less_ptr>
384 VPDragger::VPsOfSelectedBoxes() {
385 std::set<VanishingPoint*, less_ptr> sel_vps;
386 VanishingPoint *vp;
387 // FIXME: Should we take the selection from the parent VPDrag? I guess it shouldn't make a difference.
388 Inkscape::Selection *sel = sp_desktop_selection(inkscape_active_desktop());
389 for (GSList const* i = sel->itemList(); i != NULL; i = i->next) {
390 if (!SP_IS_BOX3D(i->data))
391 continue;
392 SPBox3D *box = SP_BOX3D(i->data);
393 vp = this->findVPWithBox(box);
394 if (vp) {
395 sel_vps.insert (vp);
396 }
397 }
398 return sel_vps;
399 }
401 guint
402 VPDragger::numberOfBoxes ()
403 {
404 guint num = 0;
405 for (std::list<VanishingPoint>::iterator vp = vps.begin(); vp != vps.end(); ++vp) {
406 num += (*vp).numberOfBoxes();
407 }
408 return num;
409 }
411 bool
412 VPDragger::hasPerspective (const Persp3D *persp)
413 {
414 for (std::list<VanishingPoint>::iterator i = vps.begin(); i != vps.end(); ++i) {
415 if (persp3d_perspectives_coincide(persp, (*i).get_perspective())) {
416 return true;
417 }
418 }
419 return false;
420 }
422 void
423 VPDragger::mergePerspectives ()
424 {
425 Persp3D *persp1, *persp2;
426 for (std::list<VanishingPoint>::iterator i = vps.begin(); i != vps.end(); ++i) {
427 persp1 = (*i).get_perspective();
428 for (std::list<VanishingPoint>::iterator j = i; j != vps.end(); ++j) {
429 persp2 = (*j).get_perspective();
430 if (persp1 == persp2) {
431 /* don't merge a perspective with itself */
432 continue;
433 }
434 if (persp3d_perspectives_coincide(persp1,persp2)) {
435 /* if perspectives coincide but are not the same, merge them */
436 persp3d_absorb(persp1, persp2);
438 this->parent->swap_perspectives_of_VPs(persp2, persp1);
440 SP_OBJECT(persp2)->deleteObject(false);
441 }
442 }
443 }
444 }
446 void
447 VPDragger::updateBoxDisplays ()
448 {
449 for (std::list<VanishingPoint>::iterator i = this->vps.begin(); i != this->vps.end(); ++i) {
450 (*i).updateBoxDisplays();
451 }
452 }
454 void
455 VPDragger::updateVPs (Geom::Point const &pt)
456 {
457 for (std::list<VanishingPoint>::iterator i = this->vps.begin(); i != this->vps.end(); ++i) {
458 (*i).set_pos (pt);
459 }
460 }
462 void
463 VPDragger::updateZOrders ()
464 {
465 for (std::list<VanishingPoint>::iterator i = this->vps.begin(); i != this->vps.end(); ++i) {
466 persp3d_update_z_orders((*i).get_perspective());
467 }
468 }
470 void
471 VPDragger::printVPs() {
472 g_print ("VPDragger at position (%f, %f):\n", point[Geom::X], point[Geom::Y]);
473 for (std::list<VanishingPoint>::iterator i = this->vps.begin(); i != this->vps.end(); ++i) {
474 g_print (" VP %s\n", (*i).axisString());
475 }
476 }
478 VPDrag::VPDrag (SPDocument *document)
479 {
480 this->document = document;
481 this->selection = sp_desktop_selection(inkscape_active_desktop());
483 this->draggers = NULL;
484 this->lines = NULL;
485 this->show_lines = true;
486 this->front_or_rear_lines = 0x1;
488 this->dragging = false;
490 this->sel_changed_connection = this->selection->connectChanged(
491 sigc::bind (
492 sigc::ptr_fun(&vp_drag_sel_changed),
493 (gpointer)this )
495 );
496 this->sel_modified_connection = this->selection->connectModified(
497 sigc::bind(
498 sigc::ptr_fun(&vp_drag_sel_modified),
499 (gpointer)this )
500 );
502 this->updateDraggers ();
503 this->updateLines ();
504 }
506 VPDrag::~VPDrag()
507 {
508 this->sel_changed_connection.disconnect();
509 this->sel_modified_connection.disconnect();
511 for (GList *l = this->draggers; l != NULL; l = l->next) {
512 delete ((VPDragger *) l->data);
513 }
514 g_list_free (this->draggers);
515 this->draggers = NULL;
517 for (GSList const *i = this->lines; i != NULL; i = i->next) {
518 gtk_object_destroy( GTK_OBJECT (i->data));
519 }
520 g_slist_free (this->lines);
521 this->lines = NULL;
522 }
524 /**
525 * Select the dragger that has the given VP.
526 */
527 VPDragger *
528 VPDrag::getDraggerFor (VanishingPoint const &vp)
529 {
530 for (GList const* i = this->draggers; i != NULL; i = i->next) {
531 VPDragger *dragger = (VPDragger *) i->data;
532 for (std::list<VanishingPoint>::iterator j = dragger->vps.begin(); j != dragger->vps.end(); ++j) {
533 // TODO: Should we compare the pointers or the VPs themselves!?!?!?!
534 if (*j == vp) {
535 return (dragger);
536 }
537 }
538 }
539 return NULL;
540 }
542 void
543 VPDrag::printDraggers ()
544 {
545 g_print ("=== VPDrag info: =================================\n");
546 for (GList const* i = this->draggers; i != NULL; i = i->next) {
547 ((VPDragger *) i->data)->printVPs();
548 g_print ("========\n");
549 }
550 g_print ("=================================================\n");
551 }
553 /**
554 * Regenerates the draggers list from the current selection; is called when selection is changed or modified
555 */
556 void
557 VPDrag::updateDraggers ()
558 {
559 if (this->dragging)
560 return;
561 // delete old draggers
562 for (GList const* i = this->draggers; i != NULL; i = i->next) {
563 delete ((VPDragger *) i->data);
564 }
565 g_list_free (this->draggers);
566 this->draggers = NULL;
568 g_return_if_fail (this->selection != NULL);
570 for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
571 SPItem *item = SP_ITEM(i->data);
572 if (!SP_IS_BOX3D (item)) continue;
573 SPBox3D *box = SP_BOX3D (item);
575 VanishingPoint vp;
576 for (int i = 0; i < 3; ++i) {
577 vp.set(box3d_get_perspective(box), Proj::axes[i]);
578 addDragger (vp);
579 }
580 }
581 }
583 /**
584 Regenerates the lines list from the current selection; is called on each move
585 of a dragger, so that lines are always in sync with the actual perspective
586 */
587 void
588 VPDrag::updateLines ()
589 {
590 // delete old lines
591 for (GSList const *i = this->lines; i != NULL; i = i->next) {
592 gtk_object_destroy( GTK_OBJECT (i->data));
593 }
594 g_slist_free (this->lines);
595 this->lines = NULL;
597 // do nothing if perspective lines are currently disabled
598 if (this->show_lines == 0) return;
600 g_return_if_fail (this->selection != NULL);
602 for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
603 if (!SP_IS_BOX3D(i->data)) continue;
604 SPBox3D *box = SP_BOX3D (i->data);
606 this->drawLinesForFace (box, Proj::X);
607 this->drawLinesForFace (box, Proj::Y);
608 this->drawLinesForFace (box, Proj::Z);
609 }
610 }
612 void
613 VPDrag::updateBoxHandles ()
614 {
615 // FIXME: Is there a way to update the knots without accessing the
616 // (previously) statically linked function KnotHolder::update_knots?
618 GSList *sel = (GSList *) selection->itemList();
619 if (!sel)
620 return; // no selection
622 if (g_slist_length (sel) > 1) {
623 // Currently we only show handles if a single box is selected
624 return;
625 }
627 SPEventContext *ec = inkscape_active_event_context();
628 g_assert (ec != NULL);
629 if (ec->shape_editor != NULL) {
630 ec->shape_editor->update_knotholder();
631 }
632 }
634 void
635 VPDrag::updateBoxReprs ()
636 {
637 for (GList *i = this->draggers; i != NULL; i = i->next) {
638 VPDragger *dragger = (VPDragger *) i->data;
639 for (std::list<VanishingPoint>::iterator i = dragger->vps.begin(); i != dragger->vps.end(); ++i) {
640 (*i).updateBoxReprs();
641 }
642 }
643 }
645 void
646 VPDrag::updateBoxDisplays ()
647 {
648 for (GList *i = this->draggers; i != NULL; i = i->next) {
649 VPDragger *dragger = (VPDragger *) i->data;
650 for (std::list<VanishingPoint>::iterator i = dragger->vps.begin(); i != dragger->vps.end(); ++i) {
651 (*i).updateBoxDisplays();
652 }
653 }
654 }
657 /**
658 * Depending on the value of all_lines, draw the front and/or rear perspective lines starting from the given corners.
659 */
660 void
661 VPDrag::drawLinesForFace (const SPBox3D *box, Proj::Axis axis) //, guint corner1, guint corner2, guint corner3, guint corner4)
662 {
663 guint color;
664 switch (axis) {
665 // TODO: Make color selectable by user
666 case Proj::X: color = VP_LINE_COLOR_STROKE_X; break;
667 case Proj::Y: color = VP_LINE_COLOR_STROKE_Y; break;
668 case Proj::Z: color = VP_LINE_COLOR_STROKE_Z; break;
669 default: g_assert_not_reached();
670 }
672 Geom::Point corner1, corner2, corner3, corner4;
673 box3d_corners_for_PLs (box, axis, corner1, corner2, corner3, corner4);
675 g_return_if_fail (box3d_get_perspective(box));
676 Proj::Pt2 vp = persp3d_get_VP (box3d_get_perspective(box), axis);
677 if (vp.is_finite()) {
678 // draw perspective lines for finite VPs
679 Geom::Point pt = vp.affine();
680 if (this->front_or_rear_lines & 0x1) {
681 // draw 'front' perspective lines
682 this->addLine (corner1, pt, color);
683 this->addLine (corner2, pt, color);
684 }
685 if (this->front_or_rear_lines & 0x2) {
686 // draw 'rear' perspective lines
687 this->addLine (corner3, pt, color);
688 this->addLine (corner4, pt, color);
689 }
690 } else {
691 // draw perspective lines for infinite VPs
692 boost::optional<Geom::Point> pt1, pt2, pt3, pt4;
693 Persp3D *persp = box3d_get_perspective(box);
694 SPDesktop *desktop = inkscape_active_desktop (); // FIXME: Store the desktop in VPDrag
695 Box3D::PerspectiveLine pl (corner1, axis, persp);
696 pt1 = pl.intersection_with_viewbox(desktop);
698 pl = Box3D::PerspectiveLine (corner2, axis, persp);
699 pt2 = pl.intersection_with_viewbox(desktop);
701 pl = Box3D::PerspectiveLine (corner3, axis, persp);
702 pt3 = pl.intersection_with_viewbox(desktop);
704 pl = Box3D::PerspectiveLine (corner4, axis, persp);
705 pt4 = pl.intersection_with_viewbox(desktop);
707 if (!pt1 || !pt2 || !pt3 || !pt4) {
708 // some perspective lines s are outside the canvas; currently we don't draw any of them
709 return;
710 }
711 if (this->front_or_rear_lines & 0x1) {
712 // draw 'front' perspective lines
713 this->addLine (corner1, *pt1, color);
714 this->addLine (corner2, *pt2, color);
715 }
716 if (this->front_or_rear_lines & 0x2) {
717 // draw 'rear' perspective lines
718 this->addLine (corner3, *pt3, color);
719 this->addLine (corner4, *pt4, color);
720 }
721 }
722 }
724 /**
725 * If there already exists a dragger within MERGE_DIST of p, add the VP to it;
726 * otherwise create new dragger and add it to draggers list
727 * We also store the corresponding perspective in case it is not already present.
728 */
729 void
730 VPDrag::addDragger (VanishingPoint &vp)
731 {
732 if (!vp.is_finite()) {
733 // don't create draggers for infinite vanishing points
734 return;
735 }
736 Geom::Point p = vp.get_pos();
738 for (GList *i = this->draggers; i != NULL; i = i->next) {
739 VPDragger *dragger = (VPDragger *) i->data;
740 if (Geom::L2 (dragger->point - p) < MERGE_DIST) {
741 // distance is small, merge this draggable into dragger, no need to create new dragger
742 dragger->addVP (vp);
743 return;
744 }
745 }
747 VPDragger *new_dragger = new VPDragger(this, p, vp);
748 // fixme: draggers should be added AFTER the last one: this way tabbing through them will be from begin to end.
749 this->draggers = g_list_append (this->draggers, new_dragger);
750 }
752 void
753 VPDrag::swap_perspectives_of_VPs(Persp3D *persp2, Persp3D *persp1)
754 {
755 // iterate over all VP in all draggers and replace persp2 with persp1
756 for (GList *i = this->draggers; i != NULL; i = i->next) {
757 for (std::list<VanishingPoint>::iterator j = ((VPDragger *) (i->data))->vps.begin();
758 j != ((VPDragger *) (i->data))->vps.end(); ++j) {
759 if ((*j).get_perspective() == persp2) {
760 (*j).set_perspective(persp1);
761 }
762 }
763 }
764 }
766 /**
767 Create a line from p1 to p2 and add it to the lines list
768 */
769 void
770 VPDrag::addLine (Geom::Point p1, Geom::Point p2, guint32 rgba)
771 {
772 SPCanvasItem *line = sp_canvas_item_new(sp_desktop_controls(inkscape_active_desktop()), SP_TYPE_CTRLLINE, NULL);
773 sp_ctrlline_set_coords(SP_CTRLLINE(line), p1, p2);
774 if (rgba != VP_LINE_COLOR_FILL) // fill is the default, so don't set color for it to speed up redraw
775 sp_ctrlline_set_rgba32 (SP_CTRLLINE(line), rgba);
776 sp_canvas_item_show (line);
777 this->lines = g_slist_append (this->lines, line);
778 }
780 } // namespace Box3D
782 /*
783 Local Variables:
784 mode:c++
785 c-file-style:"stroustrup"
786 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
787 indent-tabs-mode:nil
788 fill-column:99
789 End:
790 */
791 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :