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"
23 namespace Box3D {
25 gint Perspective3D::counter = 0;
26 GSList * Perspective3D::perspectives = NULL;
27 Perspective3D * Perspective3D::current_perspective = NULL;
29 Perspective3D *
30 get_persp_of_box (const SP3DBox *box)
31 {
32 for (GSList *p = Perspective3D::perspectives; p != NULL; p = p->next) {
33 if (((Perspective3D *) p->data)->has_box (box))
34 return (Perspective3D *) p->data;
35 }
36 g_warning ("Stray 3D box!\n");
37 g_assert_not_reached();
38 }
40 Perspective3D *
41 get_persp_of_VP (const VanishingPoint *vp)
42 {
43 Perspective3D *persp;
44 for (GSList *p = Perspective3D::perspectives; p != NULL; p = p->next) {
45 persp = (Perspective3D *) p->data;
46 // we compare the pointers, not the position/state of the VPs; is this correct?
47 if (persp->get_vanishing_point (Box3D::X) == vp ||
48 persp->get_vanishing_point (Box3D::Y) == vp ||
49 persp->get_vanishing_point (Box3D::Z) == vp)
50 return persp;
51 }
53 g_warning ("Stray vanishing point!\n");
54 g_assert_not_reached();
55 }
57 /**
58 * Computes the intersection of the two perspective lines from pt1 and pt2 to the respective
59 * vanishing points in the given directions.
60 */
61 // FIXME: This has been moved to a virtual method inside PerspectiveLine; can probably be purged
62 NR::Point
63 perspective_intersection (NR::Point pt1, Box3D::Axis dir1, NR::Point pt2, Box3D::Axis dir2, Perspective3D *persp)
64 {
65 VanishingPoint const *vp1 = persp->get_vanishing_point(dir1);
66 VanishingPoint const *vp2 = persp->get_vanishing_point(dir2);
67 NR::Maybe<NR::Point> meet = Line(pt1, *vp1).intersect(Line(pt2, *vp2));
68 // FIXME: How to handle parallel lines (also depends on the type of the VPs)?
69 if (!meet) { meet = NR::Point (0.0, 0.0); }
70 return *meet;
71 }
73 /**
74 * Find the point on the perspective line from line_pt to the
75 * vanishing point in direction dir that is closest to ext_pt.
76 */
77 NR::Point
78 perspective_line_snap (NR::Point line_pt, Box3D::Axis dir, NR::Point ext_pt, Perspective3D *persp)
79 {
80 return PerspectiveLine(line_pt, dir, persp).closest_to(ext_pt);
81 }
83 Perspective3D::Perspective3D (VanishingPoint const &pt_x, VanishingPoint const &pt_y, VanishingPoint const &pt_z)
84 : boxes (NULL)
85 {
86 vp_x = new VanishingPoint (pt_x);
87 vp_y = new VanishingPoint (pt_y);
88 vp_z = new VanishingPoint (pt_z);
90 my_counter = Perspective3D::counter++;
91 }
93 Perspective3D::Perspective3D (Perspective3D &other)
94 : boxes (NULL) // Should we add an option to copy the list of boxes?
95 {
96 vp_x = new VanishingPoint (*other.vp_x);
97 vp_y = new VanishingPoint (*other.vp_y);
98 vp_z = new VanishingPoint (*other.vp_z);
100 my_counter = Perspective3D::counter++;
101 }
103 Perspective3D::~Perspective3D ()
104 {
105 Perspective3D::remove_perspective (this);
107 // Remove the VPs from their draggers
108 SPEventContext *ec = inkscape_active_event_context();
109 if (SP_IS_3DBOX_CONTEXT (ec)) {
110 SP3DBoxContext *bc = SP_3DBOX_CONTEXT (ec);
111 // we need to check if there are any draggers because the selection
112 // is temporarily empty during duplication of boxes, e.g.
113 if (bc->_vpdrag->draggers != NULL) {
114 /***
115 g_assert (bc->_vpdrag->getDraggerFor (*vp_x) != NULL);
116 g_assert (bc->_vpdrag->getDraggerFor (*vp_y) != NULL);
117 g_assert (bc->_vpdrag->getDraggerFor (*vp_z) != NULL);
118 bc->_vpdrag->getDraggerFor (*vp_x)->removeVP (vp_x);
119 bc->_vpdrag->getDraggerFor (*vp_y)->removeVP (vp_y);
120 bc->_vpdrag->getDraggerFor (*vp_z)->removeVP (vp_z);
121 ***/
122 // TODO: the temporary perspective created when building boxes is not linked to any dragger, hence
123 // we need to do the following checks. Maybe it would be better to not create a temporary
124 // perspective at all but simply compare the VPs manually in sp_3dbox_build.
125 VPDragger * dragger;
126 dragger = bc->_vpdrag->getDraggerFor (*vp_x);
127 if (dragger)
128 dragger->removeVP (vp_x);
129 dragger = bc->_vpdrag->getDraggerFor (*vp_y);
130 if (dragger)
131 dragger->removeVP (vp_y);
132 dragger = bc->_vpdrag->getDraggerFor (*vp_z);
133 if (dragger)
134 dragger->removeVP (vp_z);
135 }
136 }
138 delete vp_x;
139 delete vp_y;
140 delete vp_z;
142 g_slist_free (boxes);
143 }
145 bool
146 Perspective3D::operator==(Perspective3D const &other) const
147 {
148 // Two perspectives are equal iff their vanishing points coincide and have identical states
149 return (*vp_x == *other.vp_x && *vp_y == *other.vp_y && *vp_z == *other.vp_z);
150 }
152 bool
153 Perspective3D::has_vanishing_point (VanishingPoint *vp)
154 {
155 return (vp == vp_x || vp == vp_y || vp == vp_z);
156 }
158 VanishingPoint *
159 Perspective3D::get_vanishing_point (Box3D::Axis const dir)
160 {
161 switch (dir) {
162 case X:
163 return vp_x;
164 break;
165 case Y:
166 return vp_y;
167 break;
168 case Z:
169 return vp_z;
170 break;
171 case NONE:
172 g_warning ("Axis direction must be specified. As a workaround we return the VP in X direction.\n");
173 return vp_x;
174 break;
175 default:
176 g_warning ("Single axis direction needed to determine corresponding vanishing point.\n");
177 return get_vanishing_point (extract_first_axis_direction(dir));
178 break;
179 }
180 }
182 void
183 Perspective3D::set_vanishing_point (Box3D::Axis const dir, VanishingPoint const &pt)
184 {
185 switch (dir) {
186 case X:
187 (*vp_x) = pt;
188 break;
189 case Y:
190 (*vp_y) = pt;
191 break;
192 case Z:
193 (*vp_z) = pt;
194 break;
195 case NONE:
196 // no vanishing point to set
197 break;
198 }
199 }
201 Axis
202 Perspective3D::get_axis_of_VP (VanishingPoint *vp)
203 {
204 if (vp == vp_x) return X;
205 if (vp == vp_y) return Y;
206 if (vp == vp_z) return Z;
208 g_warning ("Vanishing point not present in the perspective.\n");
209 return NONE;
210 }
212 void
213 Perspective3D::set_vanishing_point (Box3D::Axis const dir, gdouble pt_x, gdouble pt_y, gdouble dir_x, gdouble dir_y, VPState st)
214 {
215 VanishingPoint *vp;
216 switch (dir) {
217 case X:
218 vp = vp_x;
219 break;
220 case Y:
221 vp = vp_y;
222 break;
223 case Z:
224 vp = vp_z;
225 break;
226 case NONE:
227 // no vanishing point to set
228 return;
229 }
231 vp->set_pos (pt_x, pt_y);
232 vp->v_dir = NR::Point (dir_x, dir_y);
233 vp->state = st;
234 }
236 void
237 Perspective3D::add_box (SP3DBox *box)
238 {
239 if (g_slist_find (this->boxes, box) != NULL) {
240 // Don't add the same box twice
241 g_warning ("Box already uses the current perspective. We don't add it again.\n");
242 return;
243 }
244 this->boxes = g_slist_append (this->boxes, box);
245 }
247 void
248 Perspective3D::remove_box (const SP3DBox *box)
249 {
250 if (!g_slist_find (this->boxes, box)) {
251 g_warning ("Could not find box that is to be removed in the current perspective.\n");
252 }
253 this->boxes = g_slist_remove (this->boxes, box);
254 }
256 bool
257 Perspective3D::has_box (const SP3DBox *box) const
258 {
259 return (g_slist_find (this->boxes, box) != NULL);
260 }
262 bool
263 Perspective3D::all_boxes_occur_in_list (GSList *boxes_to_do)
264 {
265 for (GSList *i = boxes; i != NULL; i = i->next) {
266 if (!g_slist_find (boxes_to_do, i->data)) {
267 return false;
268 }
269 }
270 return true;
271 }
273 GSList *
274 Perspective3D::boxes_occurring_in_list (GSList * list_of_boxes)
275 {
276 GSList * result = NULL;
277 for (GSList *i = list_of_boxes; i != NULL; i = i->next) {
278 if (this->has_box (SP_3DBOX (i->data))) {
279 result = g_slist_prepend (result, i->data);
280 }
281 }
282 // we reverse so as to retain the same order as in list_of_boxes
283 return g_slist_reverse (result);
284 }
286 /**
287 * Update the shape of a box after a handle was dragged or a VP was changed, according to the stored ratios.
288 */
289 void
290 Perspective3D::reshape_boxes (Box3D::Axis axes)
291 {
292 // TODO: Leave the "correct" corner fixed according to which face is supposed to be on front.
293 NR::Point new_pt;
294 VanishingPoint *vp;
295 for (const GSList *i = this->boxes; i != NULL; i = i->next) {
296 SP3DBox *box = SP_3DBOX (i->data);
297 if (axes & Box3D::X) {
298 vp = this->get_vanishing_point (Box3D::X);
299 if (vp->is_finite()) {
300 new_pt = vp->get_pos() + box->ratio_x * (box->corners[3] - vp->get_pos());
301 sp_3dbox_move_corner_in_XY_plane (box, 2, new_pt);
302 }
303 }
304 if (axes & Box3D::Y) {
305 vp = this->get_vanishing_point (Box3D::Y);
306 if (vp->is_finite()) {
307 new_pt = vp->get_pos() + box->ratio_y * (box->corners[0] - vp->get_pos());
308 sp_3dbox_move_corner_in_XY_plane (box, 2, new_pt);
309 }
310 }
311 if (axes & Box3D::Z) {
312 vp = this->get_vanishing_point (Box3D::Z);
313 if (vp->is_finite()) {
314 new_pt = vp->get_pos() + box->ratio_z * (box->corners[0] - vp->get_pos());
315 sp_3dbox_move_corner_in_Z_direction (box, 4, new_pt);
316 }
317 }
319 sp_3dbox_set_shape (box, true);
320 }
321 }
323 void
324 Perspective3D::toggle_boxes (Box3D::Axis axis)
325 {
326 get_vanishing_point (axis)->toggle_parallel();
327 for (GSList *i = this->boxes; i != NULL; i = i->next) {
328 sp_3dbox_reshape_after_VP_toggling (SP_3DBOX (i->data), axis);
329 }
330 update_box_reprs();
331 }
333 void
334 Perspective3D::update_box_reprs ()
335 {
336 for (GSList *i = this->boxes; i != NULL; i = i->next) {
337 SP_OBJECT(SP_3DBOX (i->data))->updateRepr(SP_OBJECT_WRITE_EXT);
338 }
339 }
341 void
342 Perspective3D::update_z_orders ()
343 {
344 for (GSList *i = this->boxes; i != NULL; i = i->next) {
345 sp_3dbox_set_z_orders (SP_3DBOX (i->data));
346 }
347 }
349 // swallow the list of boxes from the other perspective and delete it
350 void
351 Perspective3D::absorb (Perspective3D *other)
352 {
353 g_return_if_fail (*this == *other);
355 // FIXME: Is copying necessary? Is other->boxes invalidated when other is deleted below?
356 this->boxes = g_slist_concat (this->boxes, g_slist_copy (other->boxes));
358 // Should we delete the other perspective here or at the place from where absorb() is called?
359 delete other;
360 other = NULL;
361 }
363 // FIXME: We get compiler errors when we try to move the code from sp_3dbox_get_perspective_string to this function
364 /***
365 gchar *
366 Perspective3D::svg_string ()
367 {
368 }
369 ***/
371 void
372 Perspective3D::add_perspective (Box3D::Perspective3D * const persp)
373 {
374 // FIXME: Should we handle the case that the perspectives have equal VPs but are not identical?
375 // If so, we need to take care of relinking the boxes, etc.
376 if (persp == NULL || g_slist_find (Perspective3D::perspectives, persp)) return;
377 Perspective3D::perspectives = g_slist_prepend (Perspective3D::perspectives, persp);
378 }
380 void
381 Perspective3D::remove_perspective (Box3D::Perspective3D * const persp)
382 {
383 if (persp == NULL || !g_slist_find (Perspective3D::perspectives, persp)) return;
384 Perspective3D::perspectives = g_slist_remove (Perspective3D::perspectives, persp);
385 }
387 // find an existing perspective whose VPs are equal to those of persp
388 Box3D::Perspective3D *
389 Perspective3D::find_perspective (Box3D::Perspective3D * const persp)
390 {
391 for (GSList *p = Perspective3D::perspectives; p != NULL; p = p->next) {
392 if (*((Box3D::Perspective3D *) p->data) == *persp) {
393 return ((Box3D::Perspective3D *) p->data);
394 }
395 }
396 return NULL; // perspective was not found
397 }
399 void
400 Perspective3D::print_debugging_info ()
401 {
402 g_print ("====================================================\n");
403 for (GSList *i = Perspective3D::perspectives; i != NULL; i = i->next) {
404 Perspective3D *persp = (Perspective3D *) i->data;
405 g_print ("Perspective %d:\n", persp->my_counter);
407 VanishingPoint * vp = persp->get_vanishing_point(Box3D::X);
408 g_print (" VP X: (%f,%f) ", (*vp)[NR::X], (*vp)[NR::Y]);
409 g_print ((vp->is_finite()) ? "(finite)\n" : "(infinite)\n");
411 vp = persp->get_vanishing_point(Box3D::Y);
412 g_print (" VP Y: (%f,%f) ", (*vp)[NR::X], (*vp)[NR::Y]);
413 g_print ((vp->is_finite()) ? "(finite)\n" : "(infinite)\n");
415 vp = persp->get_vanishing_point(Box3D::Z);
416 g_print (" VP Z: (%f,%f) ", (*vp)[NR::X], (*vp)[NR::Y]);
417 g_print ((vp->is_finite()) ? "(finite)\n" : "(infinite)\n");
419 g_print ("\nBoxes: ");
420 if (persp->boxes == NULL) {
421 g_print ("none");
422 } else {
423 GSList *j;
424 for (j = persp->boxes; j != NULL; j = j->next) {
425 if (j->next == NULL) break;
426 g_print ("%d, ", SP_3DBOX (j->data)->my_counter);
427 }
428 if (j != NULL) {
429 g_print ("%d", SP_3DBOX (j->data)->my_counter);
430 }
431 }
432 }
433 g_print ("\n====================================================\n");
434 }
436 } // namespace Box3D
438 /*
439 Local Variables:
440 mode:c++
441 c-file-style:"stroustrup"
442 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
443 indent-tabs-mode:nil
444 fill-column:99
445 End:
446 */
447 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :