Code

Infrastructure to set direction of infinite VPs (now adjustable by some shortcuts...
[inkscape.git] / src / perspective3d.cpp
1 #define __PERSPECTIVE3D_C__
3 /*
4  * Class modelling a 3D perspective
5  *
6  * Authors:
7  *   Maximilian Albert <Anhalter42@gmx.de>
8  *
9  * Copyright (C) 2007 authors
10  *
11  * Released under GNU GPL, read the file 'COPYING' for more information
12  */
14 #include "box3d.h"
15 #include "box3d-context.h"
16 #include "perspective-line.h"
17 #include <iostream>
18 #include "perspective3d.h"
19 #include "desktop-handles.h"
21 // can probably be removed later
22 #include "inkscape.h"
24 namespace Box3D {
26 gint Perspective3D::counter = 0;
28 /**
29  * Computes the intersection of the two perspective lines from pt1 and pt2 to the respective
30  * vanishing points in the given directions.
31  */
32 // FIXME: This has been moved to a virtual method inside PerspectiveLine; can probably be purged
33 NR::Point
34 perspective_intersection (NR::Point pt1, Box3D::Axis dir1, NR::Point pt2, Box3D::Axis dir2, Perspective3D *persp)
35 {
36     VanishingPoint const *vp1 = persp->get_vanishing_point(dir1);
37     VanishingPoint const *vp2 = persp->get_vanishing_point(dir2);
38     NR::Maybe<NR::Point> meet = Line(pt1, *vp1).intersect(Line(pt2, *vp2));
39     // FIXME: How to handle parallel lines (also depends on the type of the VPs)?
40     if (!meet) { meet = NR::Point (0.0, 0.0); }
41     return *meet;
42 }
44 /**
45  * Find the point on the perspective line from line_pt to the
46  * vanishing point in direction dir that is closest to ext_pt.
47  */
48 NR::Point
49 perspective_line_snap (NR::Point line_pt, Box3D::Axis dir, NR::Point ext_pt, Perspective3D *persp)
50 {
51     return PerspectiveLine(line_pt, dir, persp).closest_to(ext_pt);
52 }  
54 Perspective3D::Perspective3D (VanishingPoint const &pt_x, VanishingPoint const &pt_y, VanishingPoint const &pt_z, SPDocument *doc)
55     : boxes (NULL),
56       document (doc)
57 {
58     vp_x = new VanishingPoint (pt_x);
59     vp_y = new VanishingPoint (pt_y);
60     vp_z = new VanishingPoint (pt_z);
62     my_counter = Perspective3D::counter++;
64     if (document == NULL) {
65         g_warning ("What to do now?\n");
66     }
67 }
69 Perspective3D::Perspective3D (Perspective3D &other)
70     : boxes (NULL) // Should we add an option to copy the list of boxes?
71 {
72     vp_x = new VanishingPoint (*other.vp_x);
73     vp_y = new VanishingPoint (*other.vp_y);
74     vp_z = new VanishingPoint (*other.vp_z);
76     my_counter = Perspective3D::counter++;
78     document = other.document;
79 }
81 Perspective3D::~Perspective3D ()
82 {
83     if (document) {
84         document->remove_perspective (this);
85     } else {
86         g_warning ("No document found!\n");
87     }
89     // Remove the VPs from their draggers
90     SPEventContext *ec = inkscape_active_event_context();
91     if (SP_IS_3DBOX_CONTEXT (ec)) {
92         SP3DBoxContext *bc = SP_3DBOX_CONTEXT (ec);
93         // we need to check if there are any draggers because the selection
94         // is temporarily empty during duplication of boxes, e.g.
95         if (bc->_vpdrag->draggers != NULL) {
96             /***
97             g_assert (bc->_vpdrag->getDraggerFor (*vp_x) != NULL);
98             g_assert (bc->_vpdrag->getDraggerFor (*vp_y) != NULL);
99             g_assert (bc->_vpdrag->getDraggerFor (*vp_z) != NULL);
100             bc->_vpdrag->getDraggerFor (*vp_x)->removeVP (vp_x);
101             bc->_vpdrag->getDraggerFor (*vp_y)->removeVP (vp_y);
102             bc->_vpdrag->getDraggerFor (*vp_z)->removeVP (vp_z);
103             ***/
104             // TODO: the temporary perspective created when building boxes is not linked to any dragger, hence
105             //       we need to do the following checks. Maybe it would be better to not create a temporary
106             //       perspective at all but simply compare the VPs manually in sp_3dbox_build.
107             VPDragger * dragger;
108             dragger = bc->_vpdrag->getDraggerFor (*vp_x);
109             if (dragger)
110                 dragger->removeVP (vp_x);
111             dragger = bc->_vpdrag->getDraggerFor (*vp_y);
112             if (dragger)
113                 dragger->removeVP (vp_y);
114             dragger = bc->_vpdrag->getDraggerFor (*vp_z);
115             if (dragger)
116                 dragger->removeVP (vp_z);
117         }
118     }
120     delete vp_x;
121     delete vp_y;
122     delete vp_z;
124     g_slist_free (boxes);
127 bool
128 Perspective3D::operator==(Perspective3D const &other) const
130     // Two perspectives are equal iff their vanishing points coincide and have identical states
131     return (*vp_x == *other.vp_x && *vp_y == *other.vp_y && *vp_z == *other.vp_z);
134 bool
135 Perspective3D::has_vanishing_point (VanishingPoint *vp)
137     return (vp == vp_x || vp == vp_y || vp == vp_z);
140 VanishingPoint *
141 Perspective3D::get_vanishing_point (Box3D::Axis const dir)
143     switch (dir) {
144         case X:
145             return vp_x;
146             break;
147         case Y:
148             return vp_y;
149             break;
150         case Z:
151             return vp_z;
152             break;
153         case NONE:
154             g_warning ("Axis direction must be specified. As a workaround we return the VP in X direction.\n");
155             return vp_x;
156             break;
157         default:
158             g_warning ("Single axis direction needed to determine corresponding vanishing point.\n");
159             return get_vanishing_point (extract_first_axis_direction(dir));
160             break;
161     }
164 void
165 Perspective3D::set_vanishing_point (Box3D::Axis const dir, VanishingPoint const &pt)
167     switch (dir) {
168         case X:
169             (*vp_x) = pt;
170             break;
171         case Y:
172             (*vp_y) = pt;
173             break;
174         case Z:
175             (*vp_z) = pt;
176             break;
177         default:
178             // no vanishing point to set
179             break;
180     }
183 void
184 Perspective3D::set_infinite_direction (Box3D::Axis axis, NR::Point const dir)
186     Box3D::Axis axis1 = Box3D::get_remaining_axes (axis).first;
187     Box3D::Axis axis2 = Box3D::get_remaining_axes (axis).second;
188     Box3D::VanishingPoint *vp1 = get_vanishing_point (axis1);
189     Box3D::VanishingPoint *vp2 = get_vanishing_point (axis2);
190     if (fabs (Box3D::determinant (vp1->v_dir, dir)) < Box3D::epsilon ||
191         fabs (Box3D::determinant (vp2->v_dir, dir)) < Box3D::epsilon) {
192         // This is an ad-hoc correction; we should fix this more thoroughly
193         double a = NR::atan2 (dir) + 0.01;
194         this->set_infinite_direction (axis, NR::Point (cos (a), sin (a))); // we call this function again in case there is another conflict (which is unlikely, but possible)
195         return;
196     }
198     get_vanishing_point (axis)->set_infinite_direction (dir);
201 void
202 Perspective3D::rotate (Box3D::Axis const axis, double const angle)
204     Box3D::VanishingPoint *vp = get_vanishing_point (axis);
205     if (!vp->is_finite()) {
206         double a = NR::atan2 (vp->v_dir) * 180/M_PI;
207         a += angle;
208         a *= M_PI/180;
209         this->set_infinite_direction (axis, NR::Point (cos (a), sin (a)));
210         for (GSList *i = this->boxes; i != NULL; i = i->next) {
211             sp_3dbox_reshape_after_VP_rotation (SP_3DBOX (i->data), axis);
212             sp_3dbox_set_z_orders_later_on (SP_3DBOX (i->data));
213         }
214         update_box_reprs();
215     }
218 Axis
219 Perspective3D::get_axis_of_VP (VanishingPoint *vp)
221     if (vp == vp_x) return X;
222     if (vp == vp_y) return Y;
223     if (vp == vp_z) return Z;
225     g_warning ("Vanishing point not present in the perspective.\n");
226     return NONE;
229 void
230 Perspective3D::set_vanishing_point (Box3D::Axis const dir, gdouble pt_x, gdouble pt_y, gdouble dir_x, gdouble dir_y, VPState st)
232     VanishingPoint *vp;
233     switch (dir) {
234         case X:
235             vp = vp_x;
236             break;
237         case Y:
238             vp = vp_y;
239             break;
240         case Z:
241             vp = vp_z;
242             break;
243         default:
244             // no vanishing point to set
245             return;
246     }
248     vp->set_pos (pt_x, pt_y);
249     vp->v_dir = NR::Point (dir_x, dir_y);
250     vp->state = st;
253 void
254 Perspective3D::add_box (SP3DBox *box)
256     if (g_slist_find (this->boxes, box) != NULL) {
257         // Don't add the same box twice
258         g_warning ("Box already uses the current perspective. We don't add it again.\n");
259         return;
260     }
261     this->boxes = g_slist_append (this->boxes, box);
264 void
265 Perspective3D::remove_box (const SP3DBox *box)
267     if (!g_slist_find (this->boxes, box)) {
268         g_warning ("Could not find box that is to be removed in the current perspective.\n");
269     }
270     this->boxes = g_slist_remove (this->boxes, box);
273 bool
274 Perspective3D::has_box (const SP3DBox *box) const
276     return (g_slist_find (this->boxes, box) != NULL);
279 bool
280 Perspective3D::all_boxes_occur_in_list (GSList *boxes_to_do)
282     for (GSList *i = boxes; i != NULL; i = i->next) {
283         if (!g_slist_find (boxes_to_do, i->data)) {
284             return false;
285         }
286     }
287     return true;
290 GSList *
291 Perspective3D::boxes_occurring_in_list (GSList * list_of_boxes)
293     GSList * result = NULL;
294     for (GSList *i = list_of_boxes; i != NULL; i = i->next) {
295         if (this->has_box (SP_3DBOX (i->data))) {
296             result = g_slist_prepend (result, i->data);
297         }
298     }
299     // we reverse so as to retain the same order as in list_of_boxes
300     return g_slist_reverse (result);
303 /**
304  * Update the shape of a box after a handle was dragged or a VP was changed, according to the stored ratios.
305  */
306 void
307 Perspective3D::reshape_boxes (Box3D::Axis axes)
309     // TODO: Leave the "correct" corner fixed according to which face is supposed to be on front.
310     NR::Point new_pt;
311     VanishingPoint *vp;
312     for (const GSList *i = this->boxes; i != NULL; i = i->next) {
313         SP3DBox *box = SP_3DBOX (i->data);
314         if (axes & Box3D::X) {
315             vp = this->get_vanishing_point (Box3D::X);
316             if (vp->is_finite()) {
317                 new_pt = vp->get_pos() + box->ratio_x * (box->corners[3] - vp->get_pos());
318                 sp_3dbox_move_corner_in_XY_plane (box, 2, new_pt);
319             }
320         }
321         if (axes & Box3D::Y) {
322             vp = this->get_vanishing_point (Box3D::Y);
323             if (vp->is_finite()) {
324                 new_pt = vp->get_pos() + box->ratio_y * (box->corners[0] - vp->get_pos());
325                 sp_3dbox_move_corner_in_XY_plane (box, 2, new_pt);
326             }
327         }
328         if (axes & Box3D::Z) {
329             vp = this->get_vanishing_point (Box3D::Z);
330             if (vp->is_finite()) {
331                 new_pt = vp->get_pos() + box->ratio_z * (box->corners[0] - vp->get_pos());
332                 sp_3dbox_move_corner_in_Z_direction (box, 4, new_pt);
333             }
334         }                
336         sp_3dbox_set_shape (box, true);
337     }
340 void
341 Perspective3D::toggle_boxes (Box3D::Axis axis)
343     get_vanishing_point (axis)->toggle_parallel();
344     for (GSList *i = this->boxes; i != NULL; i = i->next) {
345         sp_3dbox_reshape_after_VP_toggling (SP_3DBOX (i->data), axis);
346     }
347     update_box_reprs();
349     SP3DBoxContext *bc = SP_3DBOX_CONTEXT (inkscape_active_event_context());
350     bc->_vpdrag->updateDraggers ();
353 void
354 Perspective3D::update_box_reprs ()
356     for (GSList *i = this->boxes; i != NULL; i = i->next) {
357         SP_OBJECT(SP_3DBOX (i->data))->updateRepr(SP_OBJECT_WRITE_EXT);
358     }
361 void
362 Perspective3D::update_z_orders ()
364     for (GSList *i = this->boxes; i != NULL; i = i->next) {
365         sp_3dbox_set_z_orders_later_on (SP_3DBOX (i->data));
366     }
369 /* the direction from a point pt towards the specified vanishing point of the perspective */
370 NR::Point
371 Perspective3D::direction (NR::Point pt, Box3D::Axis axis)
373     Box3D::VanishingPoint *vp = this->get_vanishing_point (axis);
374     if (!vp->is_finite()) {
375         return vp->v_dir;
376     }
377     return (vp->get_pos() - pt);
380 // swallow the list of boxes from the other perspective and delete it
381 void
382 Perspective3D::absorb (Perspective3D *other)
384     g_return_if_fail (*this == *other);
386     // FIXME: Is copying necessary? Is other->boxes invalidated when other is deleted below?
387     this->boxes = g_slist_concat (this->boxes, g_slist_copy (other->boxes));
389     // Should we delete the other perspective here or at the place from where absorb() is called?
390     delete other;
391     other = NULL;
394 // FIXME: We get compiler errors when we try to move the code from sp_3dbox_get_perspective_string to this function
395 /***
396 gchar *
397 Perspective3D::svg_string ()
400 ***/
402 void
403 Perspective3D::print_debugging_info ()
405     g_print ("====================================================\n");
406     for (GSList *i = sp_desktop_document (inkscape_active_desktop())->perspectives; i != NULL; i = i->next) {
407         Perspective3D *persp = (Perspective3D *) i->data;
408         g_print ("Perspective %d:\n", persp->my_counter);
410         VanishingPoint * vp = persp->get_vanishing_point(Box3D::X);
411         g_print ("   VP X: (%f,%f) ", (*vp)[NR::X], (*vp)[NR::Y]);
412         g_print ((vp->is_finite()) ? "(finite)\n" : "(infinite)\n");
414         vp = persp->get_vanishing_point(Box3D::Y);
415         g_print ("   VP Y: (%f,%f) ", (*vp)[NR::X], (*vp)[NR::Y]);
416         g_print ((vp->is_finite()) ? "(finite)\n" : "(infinite)\n");
418         vp = persp->get_vanishing_point(Box3D::Z);
419         g_print ("   VP Z: (%f,%f) ", (*vp)[NR::X], (*vp)[NR::Y]);
420         g_print ((vp->is_finite()) ? "(finite)\n" : "(infinite)\n");
422         g_print ("\nBoxes: ");
423         if (persp->boxes == NULL) {
424             g_print ("none");
425         } else {
426             GSList *j;
427             for (j = persp->boxes; j != NULL; j = j->next) {
428                 if (j->next == NULL) break;
429                 g_print ("%d, ", SP_3DBOX (j->data)->my_counter);
430             }
431             if (j != NULL) {
432                 g_print ("%d", SP_3DBOX (j->data)->my_counter);
433             }
434             g_print ("\n");
435         }
436         g_print ("\n");
437     }
438     g_print ("====================================================\n");
441 } // namespace Box3D 
442  
443 /*
444   Local Variables:
445   mode:c++
446   c-file-style:"stroustrup"
447   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
448   indent-tabs-mode:nil
449   fill-column:99
450   End:
451 */
452 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :