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 //g_print ("Number of boxes in dragger: %d\n", dragger->numberOfBoxes());
98 if (dragger->numberOfBoxes() > 1) { // FIXME: Don't do anything if *all* boxes of a VP are selected
99 //g_print ("We need to split the VPDragger\n");
100 std::set<VanishingPoint*, less_ptr> sel_vps = dragger->VPsOfSelectedBoxes();
101 /**
102 g_print ("===== VPs of selected boxes: ===========================\n");
103 for (std::set<VanishingPoint*, less_ptr>::iterator i = sel_vps.begin(); i != sel_vps.end(); ++i) {
104 (*i)->printPt();
105 }
106 g_print ("========================================================\n");
107 **/
109 std::list<SPBox3D *> sel_boxes;
110 for (std::set<VanishingPoint*, less_ptr>::iterator vp = sel_vps.begin(); vp != sel_vps.end(); ++vp) {
111 // for each VP that has selected boxes:
112 Persp3D *old_persp = (*vp)->get_perspective();
113 sel_boxes = (*vp)->selectedBoxes(sp_desktop_selection(inkscape_active_desktop()));
115 // we create a new perspective ...
116 Persp3D *new_persp = persp3d_create_xml_element (dragger->parent->document, old_persp);
118 /* ... unlink the boxes from the old one and
119 FIXME: We need to unlink the _un_selected boxes of each VP so that
120 the correct boxes are kept with the VP being moved */
121 std::list<SPBox3D *> bx_lst = persp3d_list_of_boxes(old_persp);
122 for (std::list<SPBox3D *>::iterator i = bx_lst.begin(); i != bx_lst.end(); ++i) {
123 //g_print ("Iterating over box #%d\n", (*i)->my_counter);
124 if (std::find(sel_boxes.begin(), sel_boxes.end(), *i) == sel_boxes.end()) {
125 /* if a box in the VP is unselected, move it to the
126 newly created perspective so that it doesn't get dragged **/
127 //g_print (" switching box #%d to new perspective.\n", (*i)->my_counter);
128 persp3d_remove_box (old_persp, *i);
129 persp3d_add_box (new_persp, *i);
130 gchar *href = g_strdup_printf("#%s", SP_OBJECT_REPR(new_persp)->attribute("id"));
131 SP_OBJECT_REPR(*i)->setAttribute("inkscape:perspectiveID", href);
132 g_free(href);
133 }
134 }
135 }
136 // FIXME: Do we need to create a new dragger as well?
137 dragger->updateZOrders ();
138 sp_document_done (sp_desktop_document (inkscape_active_desktop()), SP_VERB_CONTEXT_3DBOX,
139 _("Split vanishing points"));
140 return;
141 }
142 }
144 if (!(state & GDK_SHIFT_MASK)) {
145 // without Shift; see if we need to snap to another dragger
146 for (GList *di = dragger->parent->draggers; di != NULL; di = di->next) {
147 VPDragger *d_new = (VPDragger *) di->data;
148 if ((d_new != dragger) && (NR::L2 (d_new->point - p) < snap_dist)) {
149 if (have_VPs_of_same_perspective (dragger, d_new)) {
150 // this would result in degenerate boxes, which we disallow for the time being
151 continue;
152 }
154 // update positions ... (this is needed so that the perspectives are detected as identical)
155 // FIXME: This is called a bit too often, isn't it?
156 for (std::list<VanishingPoint>::iterator j = dragger->vps.begin(); j != dragger->vps.end(); ++j) {
157 (*j).set_pos(d_new->point);
158 }
160 // ... join lists of VPs ...
161 d_new->vps.merge(dragger->vps);
163 // ... delete old dragger ...
164 drag->draggers = g_list_remove (drag->draggers, dragger);
165 delete dragger;
166 dragger = NULL;
168 // ... and merge any duplicate perspectives
169 d_new->mergePerspectives();
171 // TODO: Update the new merged dragger
172 //d_new->updateKnotShape ();
173 d_new->updateTip();
175 d_new->parent->updateBoxDisplays (); // FIXME: Only update boxes in current dragger!
176 d_new->updateZOrders ();
178 drag->updateLines ();
180 // TODO: Undo machinery; this doesn't work yet because perspectives must be created and
181 // deleted according to changes in the svg representation, not based on any user input
182 // as is currently the case.
184 sp_document_done (sp_desktop_document (inkscape_active_desktop()), SP_VERB_CONTEXT_3DBOX,
185 _("Merge vanishing points"));
187 return;
188 }
189 }
190 }
193 dragger->point = p; // FIXME: Brauchen wir dragger->point überhaupt?
195 dragger->updateVPs(p);
196 dragger->updateBoxDisplays();
197 dragger->parent->updateBoxHandles (); // FIXME: Only update the handles of boxes on this dragger (not on all)
198 dragger->updateZOrders();
200 drag->updateLines();
202 dragger->dragging_started = true;
203 }
205 /* helpful for debugging */
206 static void
207 vp_knot_clicked_handler(SPKnot *knot, guint state, gpointer data)
208 {
209 VPDragger *dragger = (VPDragger *) data;
210 g_print ("\nVPDragger contains the following VPs: ");
211 for (std::list<VanishingPoint>::iterator i = dragger->vps.begin(); i != dragger->vps.end(); ++i) {
212 g_print("%d (%d) ", (*i).my_counter, (*i).get_perspective()->my_counter);
213 }
214 g_print("\n");
215 }
217 void
218 vp_knot_grabbed_handler (SPKnot *knot, unsigned int state, gpointer data)
219 {
220 VPDragger *dragger = (VPDragger *) data;
221 VPDrag *drag = dragger->parent;
223 drag->dragging = true;
224 }
226 static void
227 vp_knot_ungrabbed_handler (SPKnot *knot, guint state, gpointer data)
228 {
229 VPDragger *dragger = (VPDragger *) data;
231 //sp_canvas_end_forced_full_redraws(dragger->parent->desktop->canvas);
233 dragger->point_original = dragger->point = knot->pos;
235 dragger->dragging_started = false;
237 for (std::list<VanishingPoint>::iterator i = dragger->vps.begin(); i != dragger->vps.end(); ++i) {
238 (*i).set_pos (knot->pos);
239 (*i).updateBoxReprs();
240 (*i).updatePerspRepr();
241 }
243 dragger->parent->updateDraggers ();
244 //dragger->updateBoxReprs ();
245 dragger->parent->updateLines ();
246 dragger->parent->updateBoxHandles ();
248 // TODO: Update box's paths and svg representation
250 dragger->parent->dragging = false;
252 // TODO: Undo machinery!!
253 g_return_if_fail (dragger->parent);
254 g_return_if_fail (dragger->parent->document);
255 sp_document_done(dragger->parent->document, SP_VERB_CONTEXT_3DBOX,
256 _("3D box: Move vanishing point"));
257 }
259 unsigned int VanishingPoint::global_counter = 0;
261 // FIXME: Rename to something more meaningful!
262 void
263 VanishingPoint::set_pos(Proj::Pt2 const &pt) {
264 g_return_if_fail (_persp);
265 _persp->tmat.set_image_pt (_axis, pt);
266 }
268 std::list<SPBox3D *>
269 VanishingPoint::selectedBoxes(Inkscape::Selection *sel) {
270 std::list<SPBox3D *> sel_boxes;
271 for (GSList const* i = sel->itemList(); i != NULL; i = i->next) {
272 if (!SP_IS_BOX3D(i->data))
273 continue;
274 SPBox3D *box = SP_BOX3D(i->data);
275 if (this->hasBox(box)) {
276 sel_boxes.push_back (box);
277 }
278 }
279 return sel_boxes;
280 }
282 VPDragger::VPDragger(VPDrag *parent, NR::Point p, VanishingPoint &vp)
283 {
284 //this->vps = NULL;
286 this->parent = parent;
288 this->point = p;
289 this->point_original = p;
291 this->dragging_started = false;
293 if (vp.is_finite()) {
294 // create the knot
295 this->knot = sp_knot_new (inkscape_active_desktop(), NULL);
296 this->knot->setMode(SP_KNOT_MODE_XOR);
297 this->knot->setFill(VP_KNOT_COLOR_NORMAL, VP_KNOT_COLOR_NORMAL, VP_KNOT_COLOR_NORMAL);
298 this->knot->setStroke(0x000000ff, 0x000000ff, 0x000000ff);
299 sp_knot_update_ctrl(this->knot);
301 // move knot to the given point
302 sp_knot_set_position (this->knot, &this->point, SP_KNOT_STATE_NORMAL);
303 sp_knot_show (this->knot);
305 // connect knot's signals
306 g_signal_connect (G_OBJECT (this->knot), "moved", G_CALLBACK (vp_knot_moved_handler), this);
307 g_signal_connect (G_OBJECT (this->knot), "clicked", G_CALLBACK (vp_knot_clicked_handler), this);
308 g_signal_connect (G_OBJECT (this->knot), "grabbed", G_CALLBACK (vp_knot_grabbed_handler), this);
309 g_signal_connect (G_OBJECT (this->knot), "ungrabbed", G_CALLBACK (vp_knot_ungrabbed_handler), this);
310 /***
311 g_signal_connect (G_OBJECT (this->knot), "doubleclicked", G_CALLBACK (vp_knot_doubleclicked_handler), this);
312 ***/
314 // add the initial VP (which may be NULL!)
315 this->addVP (vp);
316 //updateKnotShape();
317 }
318 }
320 VPDragger::~VPDragger()
321 {
322 // unselect if it was selected
323 //this->parent->setDeselected(this);
325 // disconnect signals
326 g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_moved_handler), this);
327 g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_clicked_handler), this);
328 g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_grabbed_handler), this);
329 g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_ungrabbed_handler), this);
330 /***
331 g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_doubleclicked_handler), this);
332 ***/
334 /* unref should call destroy */
335 g_object_unref (G_OBJECT (this->knot));
337 //g_slist_free (this->vps);
338 //this->vps = NULL;
339 }
341 /**
342 Updates the statusbar tip of the dragger knot, based on its draggables
343 */
344 void
345 VPDragger::updateTip ()
346 {
347 if (this->knot && this->knot->tip) {
348 g_free (this->knot->tip);
349 this->knot->tip = NULL;
350 }
352 guint num = this->numberOfBoxes();
353 if (this->vps.size() == 1) {
354 if (this->vps.front().is_finite()) {
355 this->knot->tip = g_strdup_printf (ngettext("<b>Finite</b> vanishing point shared by <b>%d</b> box",
356 "<b>Finite</b> vanishing point shared by <b>%d</b> boxes; drag with <b>Shift</b> to separate selected box(es)",
357 num),
358 num);
359 } else {
360 // This won't make sense any more when infinite VPs are not shown on the canvas,
361 // but currently we update the status message anyway
362 this->knot->tip = g_strdup_printf (ngettext("<b>Infinite</b> vanishing point shared by <b>%d</b> box",
363 "<b>Infinite</b> vanishing point shared by <b>%d</b> boxes; drag with <b>Shift</b> to separate selected box(es)",
364 num),
365 num);
366 }
367 } else {
368 int length = this->vps.size();
369 char *desc1 = g_strdup_printf ("Collection of <b>%d</b> vanishing points ", length);
370 char *desc2 = g_strdup_printf (ngettext("shared by <b>%d</b> box; drag with <b>Shift</b> to separate selected box(es)",
371 "shared by <b>%d</b> boxes; drag with <b>Shift</b> to separate selected box(es)",
372 num),
373 num);
374 this->knot->tip = g_strconcat(desc1, desc2, NULL);
375 g_free (desc1);
376 g_free (desc2);
377 }
378 }
380 /**
381 * Adds a vanishing point to the dragger (also updates the position if necessary);
382 * the perspective is stored separately, too, for efficiency in updating boxes.
383 */
384 void
385 VPDragger::addVP (VanishingPoint &vp, bool update_pos)
386 {
387 //if (!vp.is_finite() || g_slist_find (this->vps, vp)) {
388 if (!vp.is_finite() || std::find (vps.begin(), vps.end(), vp) != vps.end()) {
389 // don't add infinite VPs; don't add the same VP twice
390 return;
391 }
393 if (update_pos) {
394 vp.set_pos (this->point);
395 }
396 //this->vps = g_slist_prepend (this->vps, vp);
397 this->vps.push_front (vp);
398 //this->persps.include (vp.get_perspective());
400 this->updateTip();
401 }
403 void
404 VPDragger::removeVP (VanishingPoint const &vp)
405 {
406 std::list<VanishingPoint>::iterator i = std::find (this->vps.begin(), this->vps.end(), vp);
407 if (i != this->vps.end()) {
408 this->vps.erase (i);
409 }
410 this->updateTip();
411 }
413 VanishingPoint *
414 VPDragger::findVPWithBox (SPBox3D *box) {
415 for (std::list<VanishingPoint>::iterator vp = vps.begin(); vp != vps.end(); ++vp) {
416 if ((*vp).hasBox(box)) {
417 return &(*vp);
418 }
419 }
420 return NULL;
421 }
423 std::set<VanishingPoint*, less_ptr>
424 VPDragger::VPsOfSelectedBoxes() {
425 std::set<VanishingPoint*, less_ptr> sel_vps;
426 VanishingPoint *vp;
427 // FIXME: Should we take the selection from the parent VPDrag? I guess it shouldn't make a difference.
428 Inkscape::Selection *sel = sp_desktop_selection(inkscape_active_desktop());
429 for (GSList const* i = sel->itemList(); i != NULL; i = i->next) {
430 if (!SP_IS_BOX3D(i->data))
431 continue;
432 SPBox3D *box = SP_BOX3D(i->data);
433 vp = this->findVPWithBox(box);
434 if (vp) {
435 sel_vps.insert (vp);
436 }
437 }
438 return sel_vps;
439 }
441 guint
442 VPDragger::numberOfBoxes ()
443 {
444 guint num = 0;
445 for (std::list<VanishingPoint>::iterator vp = vps.begin(); vp != vps.end(); ++vp) {
446 num += (*vp).numberOfBoxes();
447 }
448 return num;
449 }
451 bool
452 VPDragger::hasPerspective (const Persp3D *persp)
453 {
454 for (std::list<VanishingPoint>::iterator i = vps.begin(); i != vps.end(); ++i) {
455 if (persp3d_perspectives_coincide(persp, (*i).get_perspective())) {
456 return true;
457 }
458 }
459 return false;
460 }
462 void
463 VPDragger::mergePerspectives ()
464 {
465 Persp3D *persp1, *persp2;
466 for (std::list<VanishingPoint>::iterator i = vps.begin(); i != vps.end(); ++i) {
467 persp1 = (*i).get_perspective();
468 for (std::list<VanishingPoint>::iterator j = i; j != vps.end(); ++j) {
469 persp2 = (*j).get_perspective();
470 if (persp1 == persp2) {
471 /* don't merge a perspective with itself */
472 continue;
473 }
474 if (persp3d_perspectives_coincide(persp1,persp2)) {
475 /* if perspectives coincide but are not the same, merge them */
476 persp3d_absorb(persp1, persp2);
478 this->parent->swap_perspectives_of_VPs(persp2, persp1);
480 SP_OBJECT(persp2)->deleteObject(false);
481 }
482 }
483 }
484 }
486 void
487 VPDragger::updateBoxDisplays ()
488 {
489 for (std::list<VanishingPoint>::iterator i = this->vps.begin(); i != this->vps.end(); ++i) {
490 (*i).updateBoxDisplays();
491 }
492 }
494 void
495 VPDragger::updateVPs (NR::Point const &pt)
496 {
497 for (std::list<VanishingPoint>::iterator i = this->vps.begin(); i != this->vps.end(); ++i) {
498 (*i).set_pos (pt);
499 }
500 }
502 void
503 VPDragger::updateZOrders ()
504 {
505 for (std::list<VanishingPoint>::iterator i = this->vps.begin(); i != this->vps.end(); ++i) {
506 persp3d_update_z_orders((*i).get_perspective());
507 }
508 }
510 void
511 VPDragger::printVPs() {
512 g_print ("VPDragger at position (%f, %f):\n", point[NR::X], point[NR::Y]);
513 for (std::list<VanishingPoint>::iterator i = this->vps.begin(); i != this->vps.end(); ++i) {
514 g_print (" VP %s\n", (*i).axisString());
515 }
516 }
518 VPDrag::VPDrag (SPDocument *document)
519 {
520 this->document = document;
521 this->selection = sp_desktop_selection(inkscape_active_desktop());
523 this->draggers = NULL;
524 this->lines = NULL;
525 this->show_lines = true;
526 this->front_or_rear_lines = 0x1;
528 //this->selected = NULL;
529 this->dragging = false;
531 this->sel_changed_connection = this->selection->connectChanged(
532 sigc::bind (
533 sigc::ptr_fun(&vp_drag_sel_changed),
534 (gpointer)this )
536 );
537 this->sel_modified_connection = this->selection->connectModified(
538 sigc::bind(
539 sigc::ptr_fun(&vp_drag_sel_modified),
540 (gpointer)this )
541 );
543 this->updateDraggers ();
544 this->updateLines ();
545 }
547 VPDrag::~VPDrag()
548 {
549 this->sel_changed_connection.disconnect();
550 this->sel_modified_connection.disconnect();
552 for (GList *l = this->draggers; l != NULL; l = l->next) {
553 delete ((VPDragger *) l->data);
554 }
555 g_list_free (this->draggers);
556 this->draggers = NULL;
558 for (GSList const *i = this->lines; i != NULL; i = i->next) {
559 gtk_object_destroy( GTK_OBJECT (i->data));
560 }
561 g_slist_free (this->lines);
562 this->lines = NULL;
563 }
565 /**
566 * Select the dragger that has the given VP.
567 */
568 VPDragger *
569 VPDrag::getDraggerFor (VanishingPoint const &vp)
570 {
571 for (GList const* i = this->draggers; i != NULL; i = i->next) {
572 VPDragger *dragger = (VPDragger *) i->data;
573 for (std::list<VanishingPoint>::iterator j = dragger->vps.begin(); j != dragger->vps.end(); ++j) {
574 // TODO: Should we compare the pointers or the VPs themselves!?!?!?!
575 if (*j == vp) {
576 return (dragger);
577 }
578 }
579 }
580 return NULL;
581 }
583 void
584 VPDrag::printDraggers ()
585 {
586 g_print ("=== VPDrag info: =================================\n");
587 for (GList const* i = this->draggers; i != NULL; i = i->next) {
588 ((VPDragger *) i->data)->printVPs();
589 g_print ("========\n");
590 }
591 g_print ("=================================================\n");
592 }
594 /**
595 * Regenerates the draggers list from the current selection; is called when selection is changed or modified
596 */
597 void
598 VPDrag::updateDraggers ()
599 {
600 if (this->dragging)
601 return;
602 // delete old draggers
603 for (GList const* i = this->draggers; i != NULL; i = i->next) {
604 delete ((VPDragger *) i->data);
605 }
606 g_list_free (this->draggers);
607 this->draggers = NULL;
609 g_return_if_fail (this->selection != NULL);
611 for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
612 SPItem *item = SP_ITEM(i->data);
613 if (!SP_IS_BOX3D (item)) continue;
614 SPBox3D *box = SP_BOX3D (item);
616 VanishingPoint vp;
617 for (int i = 0; i < 3; ++i) {
618 vp.set (box->persp_ref->getObject(), Proj::axes[i]);
619 addDragger (vp);
620 }
621 }
622 }
624 /**
625 Regenerates the lines list from the current selection; is called on each move
626 of a dragger, so that lines are always in sync with the actual perspective
627 */
628 void
629 VPDrag::updateLines ()
630 {
631 // delete old lines
632 for (GSList const *i = this->lines; i != NULL; i = i->next) {
633 gtk_object_destroy( GTK_OBJECT (i->data));
634 }
635 g_slist_free (this->lines);
636 this->lines = NULL;
638 // do nothing if perspective lines are currently disabled
639 if (this->show_lines == 0) return;
641 g_return_if_fail (this->selection != NULL);
643 for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
644 if (!SP_IS_BOX3D(i->data)) continue;
645 SPBox3D *box = SP_BOX3D (i->data);
647 this->drawLinesForFace (box, Proj::X);
648 this->drawLinesForFace (box, Proj::Y);
649 this->drawLinesForFace (box, Proj::Z);
650 }
651 }
653 void
654 VPDrag::updateBoxHandles ()
655 {
656 // FIXME: Is there a way to update the knots without accessing the
657 // (previously) statically linked function knotholder_update_knots?
659 GSList *sel = (GSList *) selection->itemList();
660 if (!sel)
661 return; // no selection
663 if (g_slist_length (sel) > 1) {
664 // Currently we only show handles if a single box is selected
665 return;
666 }
668 SPEventContext *ec = inkscape_active_event_context();
669 g_assert (ec != NULL);
670 if (ec->shape_knot_holder != NULL) {
671 knotholder_update_knots(ec->shape_knot_holder, (SPItem *) sel->data);
672 }
673 }
675 void
676 VPDrag::updateBoxReprs ()
677 {
678 for (GList *i = this->draggers; i != NULL; i = i->next) {
679 VPDragger *dragger = (VPDragger *) i->data;
680 for (std::list<VanishingPoint>::iterator i = dragger->vps.begin(); i != dragger->vps.end(); ++i) {
681 (*i).updateBoxReprs();
682 }
683 }
684 }
686 void
687 VPDrag::updateBoxDisplays ()
688 {
689 for (GList *i = this->draggers; i != NULL; i = i->next) {
690 VPDragger *dragger = (VPDragger *) i->data;
691 for (std::list<VanishingPoint>::iterator i = dragger->vps.begin(); i != dragger->vps.end(); ++i) {
692 (*i).updateBoxDisplays();
693 }
694 }
695 }
698 /**
699 * Depending on the value of all_lines, draw the front and/or rear perspective lines starting from the given corners.
700 */
701 void
702 VPDrag::drawLinesForFace (const SPBox3D *box, Proj::Axis axis) //, guint corner1, guint corner2, guint corner3, guint corner4)
703 {
704 guint color;
705 switch (axis) {
706 // TODO: Make color selectable by user
707 case Proj::X: color = VP_LINE_COLOR_STROKE_X; break;
708 case Proj::Y: color = VP_LINE_COLOR_STROKE_Y; break;
709 case Proj::Z: color = VP_LINE_COLOR_STROKE_Z; break;
710 default: g_assert_not_reached();
711 }
713 NR::Point corner1, corner2, corner3, corner4;
714 box3d_corners_for_PLs (box, axis, corner1, corner2, corner3, corner4);
716 g_return_if_fail (box->persp_ref->getObject());
717 Proj::Pt2 vp = persp3d_get_VP (box->persp_ref->getObject(), axis);
718 if (vp.is_finite()) {
719 // draw perspective lines for finite VPs
720 NR::Point pt = vp.affine();
721 if (this->front_or_rear_lines & 0x1) {
722 // draw 'front' perspective lines
723 this->addLine (corner1, pt, color);
724 this->addLine (corner2, pt, color);
725 }
726 if (this->front_or_rear_lines & 0x2) {
727 // draw 'rear' perspective lines
728 this->addLine (corner3, pt, color);
729 this->addLine (corner4, pt, color);
730 }
731 } else {
732 // draw perspective lines for infinite VPs
733 NR::Maybe<NR::Point> pt1, pt2, pt3, pt4;
734 Persp3D *persp = box->persp_ref->getObject();
735 SPDesktop *desktop = inkscape_active_desktop (); // FIXME: Store the desktop in VPDrag
736 Box3D::PerspectiveLine pl (corner1, axis, persp);
737 pt1 = pl.intersection_with_viewbox(desktop);
739 pl = Box3D::PerspectiveLine (corner2, axis, persp);
740 pt2 = pl.intersection_with_viewbox(desktop);
742 pl = Box3D::PerspectiveLine (corner3, axis, persp);
743 pt3 = pl.intersection_with_viewbox(desktop);
745 pl = Box3D::PerspectiveLine (corner4, axis, persp);
746 pt4 = pl.intersection_with_viewbox(desktop);
748 if (!pt1 || !pt2 || !pt3 || !pt4) {
749 // some perspective lines s are outside the canvas; currently we don't draw any of them
750 return;
751 }
752 if (this->front_or_rear_lines & 0x1) {
753 // draw 'front' perspective lines
754 this->addLine (corner1, *pt1, color);
755 this->addLine (corner2, *pt2, color);
756 }
757 if (this->front_or_rear_lines & 0x2) {
758 // draw 'rear' perspective lines
759 this->addLine (corner3, *pt3, color);
760 this->addLine (corner4, *pt4, color);
761 }
762 }
763 }
765 /**
766 * If there already exists a dragger within MERGE_DIST of p, add the VP to it;
767 * otherwise create new dragger and add it to draggers list
768 * We also store the corresponding perspective in case it is not already present.
769 */
770 void
771 VPDrag::addDragger (VanishingPoint &vp)
772 {
773 if (!vp.is_finite()) {
774 // don't create draggers for infinite vanishing points
775 return;
776 }
777 NR::Point p = vp.get_pos();
779 for (GList *i = this->draggers; i != NULL; i = i->next) {
780 VPDragger *dragger = (VPDragger *) i->data;
781 if (NR::L2 (dragger->point - p) < MERGE_DIST) {
782 // distance is small, merge this draggable into dragger, no need to create new dragger
783 dragger->addVP (vp);
784 //dragger->updateKnotShape();
785 return;
786 }
787 }
789 VPDragger *new_dragger = new VPDragger(this, p, vp);
790 // fixme: draggers should be added AFTER the last one: this way tabbing through them will be from begin to end.
791 this->draggers = g_list_append (this->draggers, new_dragger);
792 }
794 void
795 VPDrag::swap_perspectives_of_VPs(Persp3D *persp2, Persp3D *persp1)
796 {
797 // iterate over all VP in all draggers and replace persp2 with persp1
798 for (GList *i = this->draggers; i != NULL; i = i->next) {
799 for (std::list<VanishingPoint>::iterator j = ((VPDragger *) (i->data))->vps.begin();
800 j != ((VPDragger *) (i->data))->vps.end(); ++j) {
801 if ((*j).get_perspective() == persp2) {
802 (*j).set_perspective(persp1);
803 }
804 }
805 }
806 }
808 /**
809 Create a line from p1 to p2 and add it to the lines list
810 */
811 void
812 VPDrag::addLine (NR::Point p1, NR::Point p2, guint32 rgba)
813 {
814 SPCanvasItem *line = sp_canvas_item_new(sp_desktop_controls(inkscape_active_desktop()), SP_TYPE_CTRLLINE, NULL);
815 sp_ctrlline_set_coords(SP_CTRLLINE(line), p1, p2);
816 if (rgba != VP_LINE_COLOR_FILL) // fill is the default, so don't set color for it to speed up redraw
817 sp_ctrlline_set_rgba32 (SP_CTRLLINE(line), rgba);
818 sp_canvas_item_show (line);
819 this->lines = g_slist_append (this->lines, line);
820 }
822 } // namespace Box3D
824 /*
825 Local Variables:
826 mode:c++
827 c-file-style:"stroustrup"
828 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
829 indent-tabs-mode:nil
830 fill-column:99
831 End:
832 */
833 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :