1 #define __VANISHING_POINT_C__
3 /*
4 * Vanishing point for 3D perspectives
5 *
6 * Authors:
7 * bulia byak <buliabyak@users.sf.net>
8 * Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
9 * Maximilian Albert <Anhalter42@gmx.de>
10 *
11 * Copyright (C) 2005-2007 authors
12 *
13 * Released under GNU GPL, read the file 'COPYING' for more information
14 */
16 #include <glibmm/i18n.h>
18 #include "vanishing-point.h"
19 #include "desktop-handles.h"
20 #include "box3d.h"
22 #include "knotholder.h" // FIXME: can we avoid direct access to knotholder_update_knots?
24 namespace Box3D {
26 #define VP_KNOT_COLOR_NORMAL 0xffffff00
27 #define VP_KNOT_COLOR_SELECTED 0x0000ff00
29 #define VP_LINE_COLOR_FILL 0x0000ff7f
30 #define VP_LINE_COLOR_STROKE_X 0xff00007f
31 #define VP_LINE_COLOR_STROKE_Y 0x0000ff7f
32 #define VP_LINE_COLOR_STROKE_Z 0xffff007f
34 // screen pixels between knots when they snap:
35 #define SNAP_DIST 5
37 // absolute distance between gradient points for them to become a single dragger when the drag is created:
38 #define MERGE_DIST 0.1
40 // knot shapes corresponding to GrPointType enum
41 SPKnotShapeType vp_knot_shapes [] = {
42 SP_KNOT_SHAPE_SQUARE, // VP_FINITE
43 SP_KNOT_SHAPE_CIRCLE //VP_INFINITE
44 };
46 // FIXME: We should always require to have both the point (for finite VPs)
47 // and the direction (for infinite VPs) set. Otherwise toggling
48 // shows very unexpected behaviour.
49 // Later on we can maybe infer the infinite direction from the finite point
50 // and a suitable center of the scene. How to go in the other direction?
51 VanishingPoint::VanishingPoint(NR::Point const &pt, NR::Point const &inf_dir, VPState st)
52 : NR::Point (pt), state (st), v_dir (inf_dir) {}
54 VanishingPoint::VanishingPoint(NR::Point const &pt)
55 : NR::Point (pt), state (VP_FINITE), v_dir (0.0, 0.0) {}
57 VanishingPoint::VanishingPoint(NR::Point const &pt, NR::Point const &direction)
58 : NR::Point (pt), state (VP_INFINITE), v_dir (direction) {}
60 VanishingPoint::VanishingPoint(NR::Coord x, NR::Coord y)
61 : NR::Point(x, y), state(VP_FINITE), v_dir(0.0, 0.0) {}
63 VanishingPoint::VanishingPoint(NR::Coord dir_x, NR::Coord dir_y, VPState st)
64 : NR::Point(0.0, 0.0), state(st), v_dir(dir_x, dir_y) {}
66 VanishingPoint::VanishingPoint(NR::Coord x, NR::Coord y, NR::Coord dir_x, NR::Coord dir_y)
67 : NR::Point(x, y), state(VP_INFINITE), v_dir(dir_x, dir_y) {}
69 VanishingPoint::VanishingPoint(VanishingPoint const &rhs) : NR::Point (rhs)
70 {
71 this->state = rhs.state;
72 //this->ref_pt = rhs.ref_pt;
73 this->v_dir = rhs.v_dir;
74 }
76 VanishingPoint::~VanishingPoint () {}
78 bool VanishingPoint::operator== (VanishingPoint const &other)
79 {
80 // Should we compare the parent perspectives, too? Probably not.
81 if ((*this)[NR::X] == other[NR::X] && (*this)[NR::Y] == other[NR::Y]
82 && this->state == other.state && this->v_dir == other.v_dir) {
83 return true;
84 }
85 return false;
86 }
88 bool VanishingPoint::is_finite() const
89 {
90 return this->state == VP_FINITE;
91 }
93 VPState VanishingPoint::toggle_parallel()
94 {
95 if (this->state == VP_FINITE) {
96 this->state = VP_INFINITE;
97 } else {
98 this->state = VP_FINITE;
99 }
101 return this->state;
102 }
104 void VanishingPoint::draw(Box3D::Axis const axis)
105 {
106 switch (axis) {
107 case X:
108 if (state == VP_FINITE)
109 create_canvas_point(*this, 6.0, 0xff000000);
110 else
111 create_canvas_point(*this, 6.0, 0xffffff00);
112 break;
113 case Y:
114 if (state == VP_FINITE)
115 create_canvas_point(*this, 6.0, 0x0000ff00);
116 else
117 create_canvas_point(*this, 6.0, 0xffffff00);
118 break;
119 case Z:
120 if (state == VP_FINITE)
121 create_canvas_point(*this, 6.0, 0x00770000);
122 else
123 create_canvas_point(*this, 6.0, 0xffffff00);
124 break;
125 default:
126 g_assert_not_reached();
127 break;
128 }
129 }
131 static void
132 vp_drag_sel_changed(Inkscape::Selection *selection, gpointer data)
133 {
134 VPDrag *drag = (VPDrag *) data;
135 drag->updateDraggers ();
136 drag->updateLines ();
137 }
139 static void
140 vp_drag_sel_modified (Inkscape::Selection *selection, guint flags, gpointer data)
141 {
142 VPDrag *drag = (VPDrag *) data;
143 /***
144 if (drag->local_change) {
145 drag->local_change = false;
146 } else {
147 drag->updateDraggers ();
148 }
149 ***/
150 drag->updateLines ();
151 }
153 // auxiliary function
154 static GSList *
155 eliminate_remaining_boxes_of_persp_starting_from_list_position (GSList *boxes_to_do, const SP3DBox *start_box, const Perspective3D *persp)
156 {
157 GSList *i = g_slist_find (boxes_to_do, start_box);
158 g_return_val_if_fail (i != NULL, boxes_to_do);
160 SP3DBox *box;
161 GSList *successor;
163 i = i->next;
164 while (i != NULL) {
165 successor = i->next;
166 box = SP_3DBOX (i->data);
167 if (persp->has_box (box)) {
168 boxes_to_do = g_slist_remove (boxes_to_do, box);
169 }
170 i = successor;
171 }
173 return boxes_to_do;
174 }
176 static bool
177 have_VPs_of_same_perspective (VPDragger *dr1, VPDragger *dr2)
178 {
179 Perspective3D *persp;
180 for (GSList *i = dr1->vps; i != NULL; i = i->next) {
181 persp = dr1->parent->document->get_persp_of_VP ((VanishingPoint *) i->data);
182 if (dr2->hasPerspective (persp)) {
183 return true;
184 }
185 }
186 return false;
187 }
189 static void
190 vp_knot_moved_handler (SPKnot *knot, NR::Point const *ppointer, guint state, gpointer data)
191 {
192 VPDragger *dragger = (VPDragger *) data;
193 VPDrag *drag = dragger->parent;
195 NR::Point p = *ppointer;
197 // FIXME: take from prefs
198 double snap_dist = SNAP_DIST / inkscape_active_desktop()->current_zoom();
200 if (!(state & GDK_SHIFT_MASK)) {
201 // without Shift; see if we need to snap to another dragger
202 for (GList *di = dragger->parent->draggers; di != NULL; di = di->next) {
203 VPDragger *d_new = (VPDragger *) di->data;
204 if ((d_new != dragger) && (NR::L2 (d_new->point - p) < snap_dist)) {
205 if (have_VPs_of_same_perspective (dragger, d_new)) {
206 // this would result in degenerate boxes, which we disallow for the time being
207 continue;
208 }
210 // update positions ...
211 for (GSList *j = dragger->vps; j != NULL; j = j->next) {
212 ((VanishingPoint *) j->data)->set_pos (d_new->point);
213 }
214 // ... join lists of VPs ...
215 // FIXME: Do we have to copy the second list (i.e, is it invalidated when dragger is deleted below)?
216 d_new->vps = g_slist_concat (d_new->vps, g_slist_copy (dragger->vps));
218 // ... delete old dragger ...
219 drag->draggers = g_list_remove (drag->draggers, dragger);
220 delete dragger;
221 dragger = NULL;
223 // ... and merge any duplicate perspectives
224 d_new->mergePerspectives();
226 // TODO: Update the new merged dragger
227 //d_new->updateKnotShape ();
228 d_new->updateTip ();
230 d_new->reshapeBoxes (d_new->point, Box3D::XYZ);
231 d_new->updateBoxReprs ();
232 d_new->updateZOrders ();
234 drag->updateLines ();
236 // TODO: Undo machinery; this doesn't work yet because perspectives must be created and
237 // deleted according to changes in the svg representation, not based on any user input
238 // as is currently the case.
240 //sp_document_done (sp_desktop_document (drag->desktop), SP_VERB_CONTEXT_3DBOX,
241 // _("Merge vanishing points"));
243 return;
244 }
245 }
246 }
248 dragger->point = p;
250 dragger->reshapeBoxes (p, Box3D::XYZ);
251 dragger->updateBoxReprs ();
252 dragger->updateZOrders ();
254 drag->updateLines ();
256 //drag->local_change = false;
257 }
259 /***
260 static void
261 vp_knot_clicked_handler(SPKnot *knot, guint state, gpointer data)
262 {
263 VPDragger *dragger = (VPDragger *) data;
264 }
265 ***/
267 void
268 vp_knot_grabbed_handler (SPKnot *knot, unsigned int state, gpointer data)
269 {
270 VPDragger *dragger = (VPDragger *) data;
271 VPDrag *drag = dragger->parent;
273 //sp_canvas_force_full_redraw_after_interruptions(dragger->parent->desktop->canvas, 5);
275 if ((state & GDK_SHIFT_MASK) && !drag->hasEmptySelection()) { // FIXME: Is the second check necessary?
277 if (drag->allBoxesAreSelected (dragger)) {
278 // if all of the boxes linked to dragger are selected, we don't need to split it
279 return;
280 }
282 // we are Shift-dragging; unsnap if we carry more than one VP
284 // FIXME: Should we distinguish between the following cases:
285 // 1) there are several VPs in a dragger
286 // 2) there is only a single VP but several boxes linked to it
287 // ?
288 // Or should we simply unlink all selected boxes? Currently we do the latter.
289 if (dragger->numberOfBoxes() > 1) {
290 // create a new dragger
291 VPDragger *dr_new = new VPDragger (drag, dragger->point, NULL);
292 drag->draggers = g_list_prepend (drag->draggers, dr_new);
294 // move all the VPs from dragger to dr_new
295 dr_new->vps = dragger->vps;
296 dragger->vps = NULL;
298 /* now we move all selected boxes back to the current dragger (splitting perspectives
299 if they also have unselected boxes) so that they are further reshaped during dragging */
301 GSList *boxes_to_do = drag->selectedBoxesWithVPinDragger (dr_new);
303 for (GSList *i = boxes_to_do; i != NULL; i = i->next) {
304 SP3DBox *box = SP_3DBOX (i->data);
305 Perspective3D *persp = drag->document->get_persp_of_box (box);
306 VanishingPoint *vp = dr_new->getVPofPerspective (persp);
307 if (vp == NULL) {
308 g_warning ("VP is NULL. We should be okay, though.\n");
309 }
310 if (persp->all_boxes_occur_in_list (boxes_to_do)) {
311 // if all boxes of persp are selected, we can simply move the VP from dr_new back to dragger
312 dr_new->removeVP (vp);
313 dragger->addVP (vp);
315 // some cleaning up for efficiency
316 boxes_to_do = eliminate_remaining_boxes_of_persp_starting_from_list_position (boxes_to_do, box, persp);
317 } else {
318 /* otherwise the unselected boxes need to stay linked to dr_new; thus we
319 create a new perspective and link the VPs to the correct draggers */
320 Perspective3D *persp_new = new Perspective3D (*persp);
321 drag->document->add_perspective (persp_new);
323 Axis vp_axis = persp->get_axis_of_VP (vp);
324 dragger->addVP (persp_new->get_vanishing_point (vp_axis));
325 std::pair<Axis, Axis> rem_axes = get_remaining_axes (vp_axis);
326 drag->addDragger (persp->get_vanishing_point (rem_axes.first));
327 drag->addDragger (persp->get_vanishing_point (rem_axes.second));
329 // now we move the selected boxes from persp to persp_new
330 GSList * selected_boxes_of_perspective = persp->boxes_occurring_in_list (boxes_to_do);
331 for (GSList *j = selected_boxes_of_perspective; j != NULL; j = j->next) {
332 persp->remove_box (SP_3DBOX (j->data));
333 persp_new->add_box (SP_3DBOX (j->data));
334 }
336 // cleaning up
337 boxes_to_do = eliminate_remaining_boxes_of_persp_starting_from_list_position (boxes_to_do, box, persp);
338 }
339 }
341 // TODO: Something is still wrong with updating the boxes' representations after snapping
342 //dr_new->updateBoxReprs ();
344 dragger->updateTip();
345 dr_new->updateTip();
346 }
347 }
348 }
350 static void
351 vp_knot_ungrabbed_handler (SPKnot *knot, guint state, gpointer data)
352 {
353 VPDragger *dragger = (VPDragger *) data;
355 //sp_canvas_end_forced_full_redraws(dragger->parent->desktop->canvas);
357 dragger->point_original = dragger->point = knot->pos;
359 /***
360 VanishingPoint *vp;
361 for (GSList *i = dragger->vps; i != NULL; i = i->next) {
362 vp = (VanishingPoint *) i->data;
363 vp->set_pos (knot->pos);
364 }
365 ***/
367 dragger->parent->updateDraggers ();
368 dragger->updateBoxReprs ();
370 // TODO: Update box's paths and svg representation
372 // TODO: Undo machinery!!
373 }
375 VPDragger::VPDragger(VPDrag *parent, NR::Point p, VanishingPoint *vp)
376 {
377 this->vps = NULL;
379 this->parent = parent;
381 this->point = p;
382 this->point_original = p;
384 // create the knot
385 this->knot = sp_knot_new (inkscape_active_desktop(), NULL);
386 this->knot->setMode(SP_KNOT_MODE_XOR);
387 this->knot->setFill(VP_KNOT_COLOR_NORMAL, VP_KNOT_COLOR_NORMAL, VP_KNOT_COLOR_NORMAL);
388 this->knot->setStroke(0x000000ff, 0x000000ff, 0x000000ff);
389 sp_knot_update_ctrl(this->knot);
391 // move knot to the given point
392 sp_knot_set_position (this->knot, &this->point, SP_KNOT_STATE_NORMAL);
393 sp_knot_show (this->knot);
395 // connect knot's signals
396 g_signal_connect (G_OBJECT (this->knot), "moved", G_CALLBACK (vp_knot_moved_handler), this);
397 /***
398 g_signal_connect (G_OBJECT (this->knot), "clicked", G_CALLBACK (vp_knot_clicked_handler), this);
399 ***/
400 g_signal_connect (G_OBJECT (this->knot), "grabbed", G_CALLBACK (vp_knot_grabbed_handler), this);
401 g_signal_connect (G_OBJECT (this->knot), "ungrabbed", G_CALLBACK (vp_knot_ungrabbed_handler), this);
402 /***
403 g_signal_connect (G_OBJECT (this->knot), "doubleclicked", G_CALLBACK (vp_knot_doubleclicked_handler), this);
404 ***/
406 // add the initial VP (which may be NULL!)
407 this->addVP (vp);
408 //updateKnotShape();
409 }
411 VPDragger::~VPDragger()
412 {
413 // unselect if it was selected
414 //this->parent->setDeselected(this);
416 // disconnect signals
417 g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_moved_handler), this);
418 /***
419 g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_clicked_handler), this);
420 ***/
421 g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_grabbed_handler), this);
422 g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_ungrabbed_handler), this);
423 /***
424 g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_doubleclicked_handler), this);
425 ***/
427 /* unref should call destroy */
428 g_object_unref (G_OBJECT (this->knot));
430 g_slist_free (this->vps);
431 this->vps = NULL;
432 }
434 /**
435 Updates the statusbar tip of the dragger knot, based on its draggables
436 */
437 void
438 VPDragger::updateTip ()
439 {
440 if (this->knot && this->knot->tip) {
441 g_free (this->knot->tip);
442 this->knot->tip = NULL;
443 }
445 guint num = this->numberOfBoxes();
446 if (g_slist_length (this->vps) == 1) {
447 VanishingPoint *vp = (VanishingPoint *) this->vps->data;
448 switch (vp->state) {
449 case VP_FINITE:
450 this->knot->tip = g_strdup_printf (ngettext("<b>Finite</b> vanishing point shared by <b>%d</b> box",
451 "<b>Finite</b> vanishing point shared by <b>%d</b> boxes; drag with <b>Shift</b> to separate selected box(es)",
452 num),
453 num);
454 break;
455 case VP_INFINITE:
456 // This won't make sense any more when infinite VPs are not shown on the canvas,
457 // but currently we update the status message anyway
458 this->knot->tip = g_strdup_printf (ngettext("<b>Infinite</b> vanishing point shared by <b>%d</b> box",
459 "<b>Infinite</b> vanishing point shared by <b>%d</b> boxes; drag with <b>Shift</b> to separate selected box(es)",
460 num),
461 num);
462 break;
463 }
464 } else {
465 int length = g_slist_length (this->vps);
466 char *desc1 = g_strdup_printf ("Collection of <b>%d</b> vanishing points ", length);
467 char *desc2 = g_strdup_printf (ngettext("shared by <b>%d</b> box; drag with <b>Shift</b> to separate selected box(es)",
468 "shared by <b>%d</b> boxes; drag with <b>Shift</b> to separate selected box(es)",
469 num),
470 num);
471 this->knot->tip = g_strconcat(desc1, desc2, NULL);
472 g_free (desc1);
473 g_free (desc2);
474 }
475 }
477 /**
478 * Adds a vanishing point to the dragger (also updates the position)
479 */
480 void
481 VPDragger::addVP (VanishingPoint *vp)
482 {
483 if (vp == NULL) {
484 return;
485 }
486 if (g_slist_find (this->vps, vp)) {
487 // don't add the same VP twice
488 return;
489 }
491 vp->set_pos (this->point);
492 this->vps = g_slist_prepend (this->vps, vp);
494 this->updateTip();
495 }
497 void
498 VPDragger::removeVP (VanishingPoint *vp)
499 {
500 if (vp == NULL) {
501 g_print ("NULL vanishing point will not be removed.\n");
502 return;
503 }
504 g_assert (this->vps != NULL);
505 this->vps = g_slist_remove (this->vps, vp);
507 this->updateTip();
508 }
510 // returns the VP contained in the dragger that belongs to persp
511 VanishingPoint *
512 VPDragger::getVPofPerspective (Perspective3D *persp)
513 {
514 for (GSList *i = vps; i != NULL; i = i->next) {
515 if (persp->has_vanishing_point ((VanishingPoint *) i->data)) {
516 return ((VanishingPoint *) i->data);
517 }
518 }
519 return NULL;
520 }
522 bool
523 VPDragger::hasBox(const SP3DBox *box)
524 {
525 for (GSList *i = this->vps; i != NULL; i = i->next) {
526 if (parent->document->get_persp_of_VP ((VanishingPoint *) i->data)->has_box (box)) return true;
527 }
528 return false;
529 }
531 guint
532 VPDragger::numberOfBoxes ()
533 {
534 guint num = 0;
535 for (GSList *i = this->vps; i != NULL; i = i->next) {
536 num += parent->document->get_persp_of_VP ((VanishingPoint *) i->data)->number_of_boxes ();
537 }
538 return num;
539 }
541 bool
542 VPDragger::hasPerspective (const Perspective3D *persp)
543 {
544 for (GSList *i = this->vps; i != NULL; i = i->next) {
545 if (*persp == *parent->document->get_persp_of_VP ((VanishingPoint *) i->data)) {
546 return true;
547 }
548 }
549 return false;
550 }
552 void
553 VPDragger::mergePerspectives ()
554 {
555 Perspective3D *persp1, *persp2;
556 GSList * successor = NULL;
557 for (GSList *i = this->vps; i != NULL; i = i->next) {
558 persp1 = parent->document->get_persp_of_VP ((VanishingPoint *) i->data);
559 for (GSList *j = i->next; j != NULL; j = successor) {
560 // if the perspective is deleted, the VP is invalidated, too, so we must store its successor beforehand
561 successor = j->next;
562 persp2 = parent->document->get_persp_of_VP ((VanishingPoint *) j->data);
563 if (*persp1 == *persp2) {
564 persp1->absorb (persp2); // persp2 is deleted; hopefully this doesn't screw up the list of vanishing points and thus the loops
565 }
566 }
567 }
568 }
570 void
571 VPDragger::reshapeBoxes (NR::Point const &p, Box3D::Axis axes)
572 {
573 Perspective3D *persp;
574 for (GSList const* i = this->vps; i != NULL; i = i->next) {
575 VanishingPoint *vp = (VanishingPoint *) i->data;
576 // TODO: We can extract the VP directly from the box's perspective. Is that vanishing point identical to 'vp'?
577 // Or is there duplicated information? If so, remove it and simplify the whole construction!
578 vp->set_pos(p);
579 persp = parent->document->get_persp_of_VP (vp);
580 Box3D::Axis axis = persp->get_axis_of_VP (vp);
581 parent->document->get_persp_of_VP (vp)->reshape_boxes (axis); // FIXME: we should only update the direction of the VP
582 }
583 parent->updateBoxHandles();
584 }
586 void
587 VPDragger::updateBoxReprs ()
588 {
589 for (GSList *i = this->vps; i != NULL; i = i->next) {
590 parent->document->get_persp_of_VP ((VanishingPoint *) i->data)->update_box_reprs ();
591 }
592 }
594 void
595 VPDragger::updateZOrders ()
596 {
597 for (GSList *i = this->vps; i != NULL; i = i->next) {
598 parent->document->get_persp_of_VP ((VanishingPoint *) i->data)->update_z_orders ();
599 }
600 }
602 VPDrag::VPDrag (SPDocument *document)
603 {
604 this->document = document;
605 this->selection = sp_desktop_selection(inkscape_active_desktop());
607 this->draggers = NULL;
608 this->lines = NULL;
609 this->show_lines = true;
610 this->front_or_rear_lines = 0x1;
612 //this->selected = NULL;
613 this->local_change = false;
615 this->sel_changed_connection = this->selection->connectChanged(
616 sigc::bind (
617 sigc::ptr_fun(&vp_drag_sel_changed),
618 (gpointer)this )
620 );
621 this->sel_modified_connection = this->selection->connectModified(
622 sigc::bind(
623 sigc::ptr_fun(&vp_drag_sel_modified),
624 (gpointer)this )
625 );
627 this->updateDraggers ();
628 this->updateLines ();
629 }
631 VPDrag::~VPDrag()
632 {
633 this->sel_changed_connection.disconnect();
634 this->sel_modified_connection.disconnect();
636 for (GList *l = this->draggers; l != NULL; l = l->next) {
637 delete ((VPDragger *) l->data);
638 }
639 g_list_free (this->draggers);
640 this->draggers = NULL;
642 for (GSList const *i = this->lines; i != NULL; i = i->next) {
643 gtk_object_destroy( GTK_OBJECT (i->data));
644 }
645 g_slist_free (this->lines);
646 this->lines = NULL;
647 }
649 /**
650 * Select the dragger that has the given VP.
651 */
652 VPDragger *
653 VPDrag::getDraggerFor (VanishingPoint const &vp)
654 {
655 for (GList const* i = this->draggers; i != NULL; i = i->next) {
656 VPDragger *dragger = (VPDragger *) i->data;
657 for (GSList const* j = dragger->vps; j != NULL; j = j->next) {
658 VanishingPoint *vp2 = (VanishingPoint *) j->data;
659 g_assert (vp2 != NULL);
661 // TODO: Should we compare the pointers or the VPs themselves!?!?!?!
662 //if ((*vp2) == vp) {
663 if (vp2 == &vp) {
664 return (dragger);
665 }
666 }
667 }
668 return NULL;
669 }
671 /**
672 * Regenerates the draggers list from the current selection; is called when selection is changed or modified
673 */
674 void
675 VPDrag::updateDraggers ()
676 {
677 /***
678 while (selected) {
679 selected = g_list_remove(selected, selected->data);
680 }
681 ***/
682 // delete old draggers
683 for (GList const* i = this->draggers; i != NULL; i = i->next) {
684 delete ((VPDragger *) i->data);
685 }
686 g_list_free (this->draggers);
687 this->draggers = NULL;
689 g_return_if_fail (this->selection != NULL);
691 for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
692 SPItem *item = SP_ITEM(i->data);
693 //SPStyle *style = SP_OBJECT_STYLE (item);
695 if (!SP_IS_3DBOX (item)) continue;
696 SP3DBox *box = SP_3DBOX (item);
698 Box3D::Perspective3D *persp = document->get_persp_of_box (box);
699 addDragger (persp->get_vanishing_point(Box3D::X));
700 addDragger (persp->get_vanishing_point(Box3D::Y));
701 addDragger (persp->get_vanishing_point(Box3D::Z));
702 }
703 }
705 /**
706 Regenerates the lines list from the current selection; is called on each move
707 of a dragger, so that lines are always in sync with the actual perspective
708 */
709 void
710 VPDrag::updateLines ()
711 {
712 // delete old lines
713 for (GSList const *i = this->lines; i != NULL; i = i->next) {
714 gtk_object_destroy( GTK_OBJECT (i->data));
715 }
716 g_slist_free (this->lines);
717 this->lines = NULL;
719 // do nothing if perspective lines are currently disabled
720 if (this->show_lines == 0) return;
722 g_return_if_fail (this->selection != NULL);
724 for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
725 if (!SP_IS_3DBOX(i->data)) continue;
726 SP3DBox *box = SP_3DBOX (i->data);
728 this->drawLinesForFace (box, Box3D::X);
729 this->drawLinesForFace (box, Box3D::Y);
730 this->drawLinesForFace (box, Box3D::Z);
731 }
732 }
734 void
735 VPDrag::updateBoxHandles ()
736 {
737 // FIXME: Is there a way to update the knots without accessing the
738 // statically linked function knotholder_update_knots?
740 GSList *sel = (GSList *) selection->itemList();
741 if (g_slist_length (sel) > 1) {
742 // Currently we only show handles if a single box is selected
743 return;
744 }
746 if (!SP_IS_3DBOX (sel->data))
747 return;
749 SPEventContext *ec = inkscape_active_event_context();
750 g_assert (ec != NULL);
751 if (ec->shape_knot_holder != NULL) {
752 knotholder_update_knots(ec->shape_knot_holder, (SPItem *) sel->data);
753 }
754 }
756 /**
757 * Depending on the value of all_lines, draw the front and/or rear perspective lines starting from the given corners.
758 */
759 void
760 VPDrag::drawLinesForFace (const SP3DBox *box, Box3D::Axis axis) //, guint corner1, guint corner2, guint corner3, guint corner4)
761 {
762 guint color;
763 switch (axis) {
764 // TODO: Make color selectable by user
765 case Box3D::X: color = VP_LINE_COLOR_STROKE_X; break;
766 case Box3D::Y: color = VP_LINE_COLOR_STROKE_Y; break;
767 case Box3D::Z: color = VP_LINE_COLOR_STROKE_Z; break;
768 default: g_assert_not_reached();
769 }
771 NR::Point corner1, corner2, corner3, corner4;
772 sp_3dbox_corners_for_perspective_lines (box, axis, corner1, corner2, corner3, corner4);
774 VanishingPoint *vp = document->get_persp_of_box (box)->get_vanishing_point (axis);
775 if (vp->is_finite()) {
776 NR::Point pt = vp->get_pos();
777 if (this->front_or_rear_lines & 0x1) {
778 // draw 'front' perspective lines
779 this->addLine (corner1, pt, color);
780 this->addLine (corner2, pt, color);
781 }
782 if (this->front_or_rear_lines & 0x2) {
783 // draw 'rear' perspective lines
784 this->addLine (corner3, pt, color);
785 this->addLine (corner4, pt, color);
786 }
787 } else {
788 // TODO: Draw infinite PLs
789 //g_warning ("Perspective lines for infinite vanishing points are not supported yet.\n");
790 }
792 }
794 /**
795 * Returns true if all boxes that are linked to a VP in the dragger are selected
796 */
797 bool
798 VPDrag::allBoxesAreSelected (VPDragger *dragger) {
799 GSList *selected_boxes = (GSList *) dragger->parent->selection->itemList();
800 for (GSList *i = dragger->vps; i != NULL; i = i->next) {
801 if (!document->get_persp_of_VP ((VanishingPoint *) i->data)->all_boxes_occur_in_list (selected_boxes)) {
802 return false;
803 }
804 }
805 return true;
806 }
808 GSList *
809 VPDrag::selectedBoxesWithVPinDragger (VPDragger *dragger)
810 {
811 GSList *sel_boxes = g_slist_copy ((GSList *) dragger->parent->selection->itemList());
812 for (GSList const *i = sel_boxes; i != NULL; i = i->next) {
813 SP3DBox *box = SP_3DBOX (i->data);
814 if (!dragger->hasBox (box)) {
815 sel_boxes = g_slist_remove (sel_boxes, box);
816 }
817 }
818 return sel_boxes;
819 }
822 /**
823 * If there already exists a dragger within MERGE_DIST of p, add the VP to it;
824 * otherwise create new dragger and add it to draggers list
825 */
826 void
827 VPDrag::addDragger (VanishingPoint *vp)
828 {
829 if (vp == NULL) {
830 g_print ("Warning: The VP in addDragger is already NULL. Aborting.\n)");
831 g_assert (vp != NULL);
832 }
833 NR::Point p = vp->get_pos();
835 for (GList *i = this->draggers; i != NULL; i = i->next) {
836 VPDragger *dragger = (VPDragger *) i->data;
837 if (NR::L2 (dragger->point - p) < MERGE_DIST) {
838 // distance is small, merge this draggable into dragger, no need to create new dragger
839 dragger->addVP (vp);
840 //dragger->updateKnotShape();
841 return;
842 }
843 }
845 VPDragger *new_dragger = new VPDragger(this, p, vp);
846 // fixme: draggers should be added AFTER the last one: this way tabbing through them will be from begin to end.
847 this->draggers = g_list_append (this->draggers, new_dragger);
848 }
850 /**
851 Create a line from p1 to p2 and add it to the lines list
852 */
853 void
854 VPDrag::addLine (NR::Point p1, NR::Point p2, guint32 rgba)
855 {
856 SPCanvasItem *line = sp_canvas_item_new(sp_desktop_controls(inkscape_active_desktop()), SP_TYPE_CTRLLINE, NULL);
857 sp_ctrlline_set_coords(SP_CTRLLINE(line), p1, p2);
858 if (rgba != VP_LINE_COLOR_FILL) // fill is the default, so don't set color for it to speed up redraw
859 sp_ctrlline_set_rgba32 (SP_CTRLLINE(line), rgba);
860 sp_canvas_item_show (line);
861 this->lines = g_slist_append (this->lines, line);
862 }
864 } // namespace Box3D
866 /*
867 Local Variables:
868 mode:c++
869 c-file-style:"stroustrup"
870 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
871 indent-tabs-mode:nil
872 fill-column:99
873 End:
874 */
875 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :