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