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