489e88dfc859d46786ec28cb4872b229b2f90fca
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);
125 }
127 bool
128 Perspective3D::operator==(Perspective3D const &other) const
129 {
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);
132 }
134 bool
135 Perspective3D::has_vanishing_point (VanishingPoint *vp)
136 {
137 return (vp == vp_x || vp == vp_y || vp == vp_z);
138 }
140 VanishingPoint *
141 Perspective3D::get_vanishing_point (Box3D::Axis const dir)
142 {
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 }
162 }
164 void
165 Perspective3D::set_vanishing_point (Box3D::Axis const dir, VanishingPoint const &pt)
166 {
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 }
181 }
183 void
184 Perspective3D::set_infinite_direction (Box3D::Axis axis, NR::Point const dir)
185 {
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);
199 for (GSList *i = this->boxes; i != NULL; i = i->next) {
200 sp_3dbox_reshape_after_VP_rotation (SP_3DBOX (i->data), axis);
201 sp_3dbox_set_z_orders_later_on (SP_3DBOX (i->data));
202 }
203 update_box_reprs();
204 }
206 void
207 Perspective3D::rotate (Box3D::Axis const axis, double const angle, bool const alt_pressed)
208 {
209 Box3D::VanishingPoint *vp = get_vanishing_point (axis);
210 if (!vp->is_finite()) {
211 //double add_value = angle;
212 double a = NR::atan2 (vp->v_dir) * 180/M_PI;
213 a += alt_pressed ? 0.5 * ((angle > 0 ) - (angle < 0)) : angle; // the r.h.s. yields +/-0.5 or angle
214 a *= M_PI/180;
215 this->set_infinite_direction (axis, NR::Point (cos (a), sin (a)));
216 }
217 }
219 Axis
220 Perspective3D::get_axis_of_VP (VanishingPoint *vp)
221 {
222 if (vp == vp_x) return X;
223 if (vp == vp_y) return Y;
224 if (vp == vp_z) return Z;
226 g_warning ("Vanishing point not present in the perspective.\n");
227 return NONE;
228 }
230 void
231 Perspective3D::set_vanishing_point (Box3D::Axis const dir, gdouble pt_x, gdouble pt_y, gdouble dir_x, gdouble dir_y, VPState st)
232 {
233 VanishingPoint *vp;
234 switch (dir) {
235 case X:
236 vp = vp_x;
237 break;
238 case Y:
239 vp = vp_y;
240 break;
241 case Z:
242 vp = vp_z;
243 break;
244 default:
245 // no vanishing point to set
246 return;
247 }
249 vp->set_pos (pt_x, pt_y);
250 vp->v_dir = NR::Point (dir_x, dir_y);
251 vp->state = st;
252 }
254 void
255 Perspective3D::add_box (SP3DBox *box)
256 {
257 if (g_slist_find (this->boxes, box) != NULL) {
258 // Don't add the same box twice
259 g_warning ("Box already uses the current perspective. We don't add it again.\n");
260 return;
261 }
262 this->boxes = g_slist_append (this->boxes, box);
263 }
265 void
266 Perspective3D::remove_box (const SP3DBox *box)
267 {
268 if (!g_slist_find (this->boxes, box)) {
269 g_warning ("Could not find box that is to be removed in the current perspective.\n");
270 }
271 this->boxes = g_slist_remove (this->boxes, box);
272 }
274 bool
275 Perspective3D::has_box (const SP3DBox *box) const
276 {
277 return (g_slist_find (this->boxes, box) != NULL);
278 }
280 bool
281 Perspective3D::all_boxes_occur_in_list (GSList *boxes_to_do)
282 {
283 for (GSList *i = boxes; i != NULL; i = i->next) {
284 if (!g_slist_find (boxes_to_do, i->data)) {
285 return false;
286 }
287 }
288 return true;
289 }
291 GSList *
292 Perspective3D::boxes_occurring_in_list (GSList * list_of_boxes)
293 {
294 GSList * result = NULL;
295 for (GSList *i = list_of_boxes; i != NULL; i = i->next) {
296 if (this->has_box (SP_3DBOX (i->data))) {
297 result = g_slist_prepend (result, i->data);
298 }
299 }
300 // we reverse so as to retain the same order as in list_of_boxes
301 return g_slist_reverse (result);
302 }
304 /**
305 * Update the shape of a box after a handle was dragged or a VP was changed, according to the stored ratios.
306 */
307 void
308 Perspective3D::reshape_boxes (Box3D::Axis axes)
309 {
310 // TODO: Leave the "correct" corner fixed according to which face is supposed to be on front.
311 NR::Point new_pt;
312 VanishingPoint *vp;
313 for (const GSList *i = this->boxes; i != NULL; i = i->next) {
314 SP3DBox *box = SP_3DBOX (i->data);
315 if (axes & Box3D::X) {
316 vp = this->get_vanishing_point (Box3D::X);
317 if (vp->is_finite()) {
318 new_pt = vp->get_pos() + box->ratio_x * (box->corners[3] - vp->get_pos());
319 sp_3dbox_move_corner_in_XY_plane (box, 2, new_pt);
320 }
321 }
322 if (axes & Box3D::Y) {
323 vp = this->get_vanishing_point (Box3D::Y);
324 if (vp->is_finite()) {
325 new_pt = vp->get_pos() + box->ratio_y * (box->corners[0] - vp->get_pos());
326 sp_3dbox_move_corner_in_XY_plane (box, 2, new_pt);
327 }
328 }
329 if (axes & Box3D::Z) {
330 vp = this->get_vanishing_point (Box3D::Z);
331 if (vp->is_finite()) {
332 new_pt = vp->get_pos() + box->ratio_z * (box->corners[0] - vp->get_pos());
333 sp_3dbox_move_corner_in_Z_direction (box, 4, new_pt);
334 }
335 }
337 sp_3dbox_set_shape (box, true);
338 }
339 }
341 void
342 Perspective3D::toggle_boxes (Box3D::Axis axis)
343 {
344 get_vanishing_point (axis)->toggle_parallel();
345 for (GSList *i = this->boxes; i != NULL; i = i->next) {
346 sp_3dbox_reshape_after_VP_toggling (SP_3DBOX (i->data), axis);
347 }
348 update_box_reprs();
350 SP3DBoxContext *bc = SP_3DBOX_CONTEXT (inkscape_active_event_context());
351 bc->_vpdrag->updateDraggers ();
352 }
354 void
355 Perspective3D::update_box_reprs ()
356 {
357 for (GSList *i = this->boxes; i != NULL; i = i->next) {
358 SP_OBJECT(SP_3DBOX (i->data))->updateRepr(SP_OBJECT_WRITE_EXT);
359 }
360 }
362 void
363 Perspective3D::update_z_orders ()
364 {
365 for (GSList *i = this->boxes; i != NULL; i = i->next) {
366 sp_3dbox_set_z_orders_later_on (SP_3DBOX (i->data));
367 }
368 }
370 /* the direction from a point pt towards the specified vanishing point of the perspective */
371 NR::Point
372 Perspective3D::direction (NR::Point pt, Box3D::Axis axis)
373 {
374 Box3D::VanishingPoint *vp = this->get_vanishing_point (axis);
375 if (!vp->is_finite()) {
376 return vp->v_dir;
377 }
378 return (vp->get_pos() - pt);
379 }
381 // swallow the list of boxes from the other perspective and delete it
382 void
383 Perspective3D::absorb (Perspective3D *other)
384 {
385 g_return_if_fail (*this == *other);
387 // FIXME: Is copying necessary? Is other->boxes invalidated when other is deleted below?
388 this->boxes = g_slist_concat (this->boxes, g_slist_copy (other->boxes));
390 // Should we delete the other perspective here or at the place from where absorb() is called?
391 delete other;
392 other = NULL;
393 }
395 // FIXME: We get compiler errors when we try to move the code from sp_3dbox_get_perspective_string to this function
396 /***
397 gchar *
398 Perspective3D::svg_string ()
399 {
400 }
401 ***/
403 void
404 Perspective3D::print_debugging_info ()
405 {
406 g_print ("====================================================\n");
407 for (GSList *i = sp_desktop_document (inkscape_active_desktop())->perspectives; i != NULL; i = i->next) {
408 Perspective3D *persp = (Perspective3D *) i->data;
409 g_print ("Perspective %d:\n", persp->my_counter);
411 VanishingPoint * vp = persp->get_vanishing_point(Box3D::X);
412 g_print (" VP X: (%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::Y);
416 g_print (" VP Y: (%f,%f) ", (*vp)[NR::X], (*vp)[NR::Y]);
417 g_print ((vp->is_finite()) ? "(finite)\n" : "(infinite)\n");
419 vp = persp->get_vanishing_point(Box3D::Z);
420 g_print (" VP Z: (%f,%f) ", (*vp)[NR::X], (*vp)[NR::Y]);
421 g_print ((vp->is_finite()) ? "(finite)\n" : "(infinite)\n");
423 g_print ("\nBoxes: ");
424 if (persp->boxes == NULL) {
425 g_print ("none");
426 } else {
427 GSList *j;
428 for (j = persp->boxes; j != NULL; j = j->next) {
429 if (j->next == NULL) break;
430 g_print ("%d, ", SP_3DBOX (j->data)->my_counter);
431 }
432 if (j != NULL) {
433 g_print ("%d", SP_3DBOX (j->data)->my_counter);
434 }
435 g_print ("\n");
436 }
437 g_print ("\n");
438 }
439 g_print ("====================================================\n");
440 }
442 } // namespace Box3D
444 /*
445 Local Variables:
446 mode:c++
447 c-file-style:"stroustrup"
448 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
449 indent-tabs-mode:nil
450 fill-column:99
451 End:
452 */
453 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :