Code

e5b1f62126f25983d026002cc3a8e5d3fefc3bbc
[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"
20 // can probably be removed later
21 #include "inkscape.h"
22 #include "knotholder.h"
24 namespace Box3D {
26 gint Perspective3D::counter = 0;
27 GSList * Perspective3D::perspectives = NULL;
28 Perspective3D * Perspective3D::current_perspective = NULL;
30 Perspective3D *
31 get_persp_of_box (const SP3DBox *box)
32 {
33     for (GSList *p = Perspective3D::perspectives; p != NULL; p = p->next) {
34         if (((Perspective3D *) p->data)->has_box (box))
35             return (Perspective3D *) p->data;
36     }
37     g_warning ("Stray 3D box!\n");
38     g_assert_not_reached();
39 }
41 Perspective3D *
42 get_persp_of_VP (const VanishingPoint *vp)
43 {
44     Perspective3D *persp;
45     for (GSList *p = Perspective3D::perspectives; p != NULL; p = p->next) {
46         persp = (Perspective3D *) p->data;
47         // we compare the pointers, not the position/state of the VPs; is this correct?
48         if (persp->get_vanishing_point (Box3D::X) == vp ||
49             persp->get_vanishing_point (Box3D::Y) == vp ||
50             persp->get_vanishing_point (Box3D::Z) == vp)
51             return persp;
52     }
54     g_warning ("Stray vanishing point!\n");
55     g_assert_not_reached();
56 }
58 /**
59  * Computes the intersection of the two perspective lines from pt1 and pt2 to the respective
60  * vanishing points in the given directions.
61  */
62 // FIXME: This has been moved to a virtual method inside PerspectiveLine; can probably be purged
63 NR::Point
64 perspective_intersection (NR::Point pt1, Box3D::Axis dir1, NR::Point pt2, Box3D::Axis dir2, Perspective3D *persp)
65 {
66     VanishingPoint const *vp1 = persp->get_vanishing_point(dir1);
67     VanishingPoint const *vp2 = persp->get_vanishing_point(dir2);
68     NR::Maybe<NR::Point> meet = Line(pt1, *vp1).intersect(Line(pt2, *vp2));
69     // FIXME: How to handle parallel lines (also depends on the type of the VPs)?
70     if (!meet) { meet = NR::Point (0.0, 0.0); }
71     return *meet;
72 }
74 /**
75  * Find the point on the perspective line from line_pt to the
76  * vanishing point in direction dir that is closest to ext_pt.
77  */
78 NR::Point
79 perspective_line_snap (NR::Point line_pt, Box3D::Axis dir, NR::Point ext_pt, Perspective3D *persp)
80 {
81     return PerspectiveLine(line_pt, dir, persp).closest_to(ext_pt);
82 }  
84 Perspective3D::Perspective3D (VanishingPoint const &pt_x, VanishingPoint const &pt_y, VanishingPoint const &pt_z)
85     : boxes (NULL)
86 {
87     vp_x = new VanishingPoint (pt_x);
88     vp_y = new VanishingPoint (pt_y);
89     vp_z = new VanishingPoint (pt_z);
91     my_counter = Perspective3D::counter++;
92 }
94 Perspective3D::Perspective3D (Perspective3D &other)
95     : boxes (NULL) // Should we add an option to copy the list of boxes?
96 {
97     vp_x = new VanishingPoint (*other.vp_x);
98     vp_y = new VanishingPoint (*other.vp_y);
99     vp_z = new VanishingPoint (*other.vp_z);
101     my_counter = Perspective3D::counter++;
104 Perspective3D::~Perspective3D ()
106     Perspective3D::remove_perspective (this);
108     // Remove the VPs from their draggers
109     SPEventContext *ec = inkscape_active_event_context();
110     if (SP_IS_3DBOX_CONTEXT (ec)) {
111         SP3DBoxContext *bc = SP_3DBOX_CONTEXT (ec);
112         // we need to check if there are any draggers because the selection
113         // is temporarily empty during duplication of boxes, e.g.
114         if (bc->_vpdrag->draggers != NULL) {
115             /***
116             g_assert (bc->_vpdrag->getDraggerFor (*vp_x) != NULL);
117             g_assert (bc->_vpdrag->getDraggerFor (*vp_y) != NULL);
118             g_assert (bc->_vpdrag->getDraggerFor (*vp_z) != NULL);
119             bc->_vpdrag->getDraggerFor (*vp_x)->removeVP (vp_x);
120             bc->_vpdrag->getDraggerFor (*vp_y)->removeVP (vp_y);
121             bc->_vpdrag->getDraggerFor (*vp_z)->removeVP (vp_z);
122             ***/
123             // TODO: the temporary perspective created when building boxes is not linked to any dragger, hence
124             //       we need to do the following checks. Maybe it would be better to not create a temporary
125             //       perspective at all but simply compare the VPs manually in sp_3dbox_build.
126             VPDragger * dragger;
127             dragger = bc->_vpdrag->getDraggerFor (*vp_x);
128             if (dragger)
129                 dragger->removeVP (vp_x);
130             dragger = bc->_vpdrag->getDraggerFor (*vp_y);
131             if (dragger)
132                 dragger->removeVP (vp_y);
133             dragger = bc->_vpdrag->getDraggerFor (*vp_z);
134             if (dragger)
135                 dragger->removeVP (vp_z);
136         }
137     }
139     delete vp_x;
140     delete vp_y;
141     delete vp_z;
143     g_slist_free (boxes);
146 bool
147 Perspective3D::operator==(Perspective3D const &other)
149     // Two perspectives are equal iff their vanishing points coincide and have identical states
150     return (*vp_x == *other.vp_x && *vp_y == *other.vp_y && *vp_z == *other.vp_z);
153 VanishingPoint *
154 Perspective3D::get_vanishing_point (Box3D::Axis const dir)
156     switch (dir) {
157         case X:
158             return vp_x;
159             break;
160         case Y:
161             return vp_y;
162             break;
163         case Z:
164             return vp_z;
165             break;
166         case NONE:
167             g_warning ("Axis direction must be specified. As a workaround we return the VP in X direction.\n");
168             return vp_x;
169             break;
170         default:
171             g_warning ("Single axis direction needed to determine corresponding vanishing point.\n");
172             return get_vanishing_point (extract_first_axis_direction(dir));
173             break;
174     }
177 void
178 Perspective3D::set_vanishing_point (Box3D::Axis const dir, VanishingPoint const &pt)
180     switch (dir) {
181         case X:
182             (*vp_x) = pt;
183             break;
184         case Y:
185             (*vp_y) = pt;
186             break;
187         case Z:
188             (*vp_z) = pt;
189             break;
190         case NONE:
191             // no vanishing point to set
192             break;
193     }
196 Axis
197 Perspective3D::get_axis_of_VP (VanishingPoint *vp)
199     if (vp == vp_x) return X;
200     if (vp == vp_y) return Y;
201     if (vp == vp_z) return Z;
203     g_warning ("Vanishing point not present in the perspective.\n");
204     return NONE;
207 void
208 Perspective3D::set_vanishing_point (Box3D::Axis const dir, gdouble pt_x, gdouble pt_y, gdouble dir_x, gdouble dir_y, VPState st)
210     VanishingPoint *vp;
211     switch (dir) {
212         case X:
213             vp = vp_x;
214             break;
215         case Y:
216             vp = vp_y;
217             break;
218         case Z:
219             vp = vp_z;
220             break;
221         case NONE:
222             // no vanishing point to set
223             return;
224     }
226     vp->set_pos (pt_x, pt_y);
227     vp->v_dir = NR::Point (dir_x, dir_y);
228     vp->state = st;
231 void
232 Perspective3D::add_box (SP3DBox *box)
234     if (g_slist_find (this->boxes, box) != NULL) {
235         // Don't add the same box twice
236         g_warning ("Box already uses the current perspective. We don't add it again.\n");
237         return;
238     }
239     this->boxes = g_slist_append (this->boxes, box);
242 void
243 Perspective3D::remove_box (const SP3DBox *box)
245     if (!g_slist_find (this->boxes, box)) {
246         g_warning ("Could not find box that is to be removed in the current perspective.\n");
247     }
248     this->boxes = g_slist_remove (this->boxes, box);
251 bool
252 Perspective3D::has_box (const SP3DBox *box)
254     return (g_slist_find (this->boxes, box) != NULL);
257 /**
258  * Update the shape of a box after a handle was dragged or a VP was changed, according to the stored ratios.
259  */
260 void
261 Perspective3D::reshape_boxes (Box3D::Axis axes)
263     // TODO: Leave the "correct" corner fixed according to which face is supposed to be on front.
264     NR::Point new_pt;
265     VanishingPoint *vp;
266     for (const GSList *i = this->boxes; i != NULL; i = i->next) {
267         SP3DBox *box = SP_3DBOX (i->data);
268         if (axes & Box3D::X) {
269             vp = this->get_vanishing_point (Box3D::X);
270             new_pt = vp->get_pos() + box->ratio_x * (box->corners[3] - vp->get_pos());
271             sp_3dbox_move_corner_in_XY_plane (box, 2, new_pt);
272         }
273         if (axes & Box3D::Y) {
274             vp = this->get_vanishing_point (Box3D::Y);
275             new_pt = vp->get_pos() + box->ratio_y * (box->corners[0] - vp->get_pos());
276             sp_3dbox_move_corner_in_XY_plane (box, 2, new_pt);
277         }
278         if (axes & Box3D::Z) {
279             vp = this->get_vanishing_point (Box3D::Z);
280             new_pt = vp->get_pos() + box->ratio_z * (box->corners[0] - vp->get_pos());
281             sp_3dbox_move_corner_in_Z_direction (box, 4, new_pt);
282         }                
284         sp_3dbox_set_shape (box, true);
285         // FIXME: Is there a way update the knots without accessing the
286         //        statically linked function knotholder_update_knots?
287         SPEventContext *ec = inkscape_active_event_context();
288         g_assert (ec != NULL);
289         if (ec->shape_knot_holder != NULL) {
290             knotholder_update_knots(ec->shape_knot_holder, (SPItem *) box);
291         }
292     }
295 void
296 Perspective3D::update_box_reprs ()
298     for (GSList *i = this->boxes; i != NULL; i = i->next) {
299         SP_OBJECT(SP_3DBOX (i->data))->updateRepr(SP_OBJECT_WRITE_EXT);
300     }
303 // FIXME: We get compiler errors when we try to move the code from sp_3dbox_get_perspective_string to this function
304 /***
305 gchar *
306 Perspective3D::svg_string ()
309 ***/
311 void
312 Perspective3D::add_perspective (Box3D::Perspective3D * const persp)
314     // FIXME: Should we handle the case that the perspectives have equal VPs but are not identical?
315     //        If so, we need to take care of relinking the boxes, etc.
316     if (persp == NULL || g_slist_find (Perspective3D::perspectives, persp)) return;
317     Perspective3D::perspectives = g_slist_prepend (Perspective3D::perspectives, persp);
320 void
321 Perspective3D::remove_perspective (Box3D::Perspective3D * const persp)
323     if (persp == NULL || !g_slist_find (Perspective3D::perspectives, persp)) return;
324     Perspective3D::perspectives = g_slist_remove (Perspective3D::perspectives, persp);
327 // find an existing perspective whose VPs are equal to those of persp
328 Box3D::Perspective3D *
329 Perspective3D::find_perspective (Box3D::Perspective3D * const persp)
331     for (GSList *p = Perspective3D::perspectives; p != NULL; p = p->next) {
332         if (*((Box3D::Perspective3D *) p->data) == *persp) {
333             return ((Box3D::Perspective3D *) p->data);
334         }
335     }
336     return NULL; // perspective was not found
339 void
340 Perspective3D::print_debugging_info ()
342     g_print ("====================================================\n");
343     for (GSList *i = Perspective3D::perspectives; i != NULL; i = i->next) {
344         Perspective3D *persp = (Perspective3D *) i->data;
345         g_print ("Perspective %d:\n", persp->my_counter);
347         VanishingPoint * vp = persp->get_vanishing_point(Box3D::X);
348         g_print ("   VP X: (%f,%f) ", (*vp)[NR::X], (*vp)[NR::Y]);
349         g_print ((vp->is_finite()) ? "(finite)\n" : "(infinite)\n");
351         vp = persp->get_vanishing_point(Box3D::Y);
352         g_print ("   VP Y: (%f,%f) ", (*vp)[NR::X], (*vp)[NR::Y]);
353         g_print ((vp->is_finite()) ? "(finite)\n" : "(infinite)\n");
355         vp = persp->get_vanishing_point(Box3D::Z);
356         g_print ("   VP Z: (%f,%f) ", (*vp)[NR::X], (*vp)[NR::Y]);
357         g_print ((vp->is_finite()) ? "(finite)\n" : "(infinite)\n");
359         g_print ("\nBoxes: ");
360         if (persp->boxes == NULL) {
361             g_print ("none");
362         } else {
363             GSList *j;
364             for (j = persp->boxes; j != NULL; j = j->next) {
365                 if (j->next == NULL) break;
366                 g_print ("%d, ", SP_3DBOX (j->data)->my_counter);
367             }
368             if (j != NULL) {
369                 g_print ("%d", SP_3DBOX (j->data)->my_counter);
370             }
371         }
372     }
373     g_print ("\n====================================================\n");
376 } // namespace Box3D 
377  
378 /*
379   Local Variables:
380   mode:c++
381   c-file-style:"stroustrup"
382   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
383   indent-tabs-mode:nil
384   fill-column:99
385   End:
386 */
387 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :