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++;
102 }
104 Perspective3D::~Perspective3D ()
105 {
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);
144 }
146 bool
147 Perspective3D::operator==(Perspective3D const &other) const
148 {
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);
151 }
153 bool
154 Perspective3D::has_vanishing_point (VanishingPoint *vp)
155 {
156 return (vp == vp_x || vp == vp_y || vp == vp_z);
157 }
159 VanishingPoint *
160 Perspective3D::get_vanishing_point (Box3D::Axis const dir)
161 {
162 switch (dir) {
163 case X:
164 return vp_x;
165 break;
166 case Y:
167 return vp_y;
168 break;
169 case Z:
170 return vp_z;
171 break;
172 case NONE:
173 g_warning ("Axis direction must be specified. As a workaround we return the VP in X direction.\n");
174 return vp_x;
175 break;
176 default:
177 g_warning ("Single axis direction needed to determine corresponding vanishing point.\n");
178 return get_vanishing_point (extract_first_axis_direction(dir));
179 break;
180 }
181 }
183 void
184 Perspective3D::set_vanishing_point (Box3D::Axis const dir, VanishingPoint const &pt)
185 {
186 switch (dir) {
187 case X:
188 (*vp_x) = pt;
189 break;
190 case Y:
191 (*vp_y) = pt;
192 break;
193 case Z:
194 (*vp_z) = pt;
195 break;
196 case NONE:
197 // no vanishing point to set
198 break;
199 }
200 }
202 Axis
203 Perspective3D::get_axis_of_VP (VanishingPoint *vp)
204 {
205 if (vp == vp_x) return X;
206 if (vp == vp_y) return Y;
207 if (vp == vp_z) return Z;
209 g_warning ("Vanishing point not present in the perspective.\n");
210 return NONE;
211 }
213 void
214 Perspective3D::set_vanishing_point (Box3D::Axis const dir, gdouble pt_x, gdouble pt_y, gdouble dir_x, gdouble dir_y, VPState st)
215 {
216 VanishingPoint *vp;
217 switch (dir) {
218 case X:
219 vp = vp_x;
220 break;
221 case Y:
222 vp = vp_y;
223 break;
224 case Z:
225 vp = vp_z;
226 break;
227 case NONE:
228 // no vanishing point to set
229 return;
230 }
232 vp->set_pos (pt_x, pt_y);
233 vp->v_dir = NR::Point (dir_x, dir_y);
234 vp->state = st;
235 }
237 void
238 Perspective3D::add_box (SP3DBox *box)
239 {
240 if (g_slist_find (this->boxes, box) != NULL) {
241 // Don't add the same box twice
242 g_warning ("Box already uses the current perspective. We don't add it again.\n");
243 return;
244 }
245 this->boxes = g_slist_append (this->boxes, box);
246 }
248 void
249 Perspective3D::remove_box (const SP3DBox *box)
250 {
251 if (!g_slist_find (this->boxes, box)) {
252 g_warning ("Could not find box that is to be removed in the current perspective.\n");
253 }
254 this->boxes = g_slist_remove (this->boxes, box);
255 }
257 bool
258 Perspective3D::has_box (const SP3DBox *box) const
259 {
260 return (g_slist_find (this->boxes, box) != NULL);
261 }
263 bool
264 Perspective3D::all_boxes_occur_in_list (GSList *boxes_to_do)
265 {
266 for (GSList *i = boxes; i != NULL; i = i->next) {
267 if (!g_slist_find (boxes_to_do, i->data)) {
268 return false;
269 }
270 }
271 return true;
272 }
274 GSList *
275 Perspective3D::boxes_occurring_in_list (GSList * list_of_boxes)
276 {
277 GSList * result = NULL;
278 for (GSList *i = list_of_boxes; i != NULL; i = i->next) {
279 if (this->has_box (SP_3DBOX (i->data))) {
280 result = g_slist_prepend (result, i->data);
281 }
282 }
283 // we reverse so as to retain the same order as in list_of_boxes
284 return g_slist_reverse (result);
285 }
287 /**
288 * Update the shape of a box after a handle was dragged or a VP was changed, according to the stored ratios.
289 */
290 void
291 Perspective3D::reshape_boxes (Box3D::Axis axes)
292 {
293 // TODO: Leave the "correct" corner fixed according to which face is supposed to be on front.
294 NR::Point new_pt;
295 VanishingPoint *vp;
296 for (const GSList *i = this->boxes; i != NULL; i = i->next) {
297 SP3DBox *box = SP_3DBOX (i->data);
298 if (axes & Box3D::X) {
299 vp = this->get_vanishing_point (Box3D::X);
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 if (axes & Box3D::Y) {
304 vp = this->get_vanishing_point (Box3D::Y);
305 new_pt = vp->get_pos() + box->ratio_y * (box->corners[0] - vp->get_pos());
306 sp_3dbox_move_corner_in_XY_plane (box, 2, new_pt);
307 }
308 if (axes & Box3D::Z) {
309 vp = this->get_vanishing_point (Box3D::Z);
310 new_pt = vp->get_pos() + box->ratio_z * (box->corners[0] - vp->get_pos());
311 sp_3dbox_move_corner_in_Z_direction (box, 4, new_pt);
312 }
314 sp_3dbox_set_shape (box, true);
315 // FIXME: Is there a way update the knots without accessing the
316 // statically linked function knotholder_update_knots?
317 SPEventContext *ec = inkscape_active_event_context();
318 g_assert (ec != NULL);
319 if (ec->shape_knot_holder != NULL) {
320 knotholder_update_knots(ec->shape_knot_holder, (SPItem *) box);
321 }
322 }
323 }
325 void
326 Perspective3D::update_box_reprs ()
327 {
328 for (GSList *i = this->boxes; i != NULL; i = i->next) {
329 SP_OBJECT(SP_3DBOX (i->data))->updateRepr(SP_OBJECT_WRITE_EXT);
330 }
331 }
333 // swallow the list of boxes from the other perspective and delete it
334 void
335 Perspective3D::absorb (Perspective3D *other)
336 {
337 g_return_if_fail (*this == *other);
339 // FIXME: Is copying necessary? Is other->boxes invalidated when other is deleted below?
340 this->boxes = g_slist_concat (this->boxes, g_slist_copy (other->boxes));
342 // Should we delete the other perspective here or at the place from where absorb() is called?
343 delete other;
344 other = NULL;
345 }
347 // FIXME: We get compiler errors when we try to move the code from sp_3dbox_get_perspective_string to this function
348 /***
349 gchar *
350 Perspective3D::svg_string ()
351 {
352 }
353 ***/
355 void
356 Perspective3D::add_perspective (Box3D::Perspective3D * const persp)
357 {
358 // FIXME: Should we handle the case that the perspectives have equal VPs but are not identical?
359 // If so, we need to take care of relinking the boxes, etc.
360 if (persp == NULL || g_slist_find (Perspective3D::perspectives, persp)) return;
361 Perspective3D::perspectives = g_slist_prepend (Perspective3D::perspectives, persp);
362 }
364 void
365 Perspective3D::remove_perspective (Box3D::Perspective3D * const persp)
366 {
367 if (persp == NULL || !g_slist_find (Perspective3D::perspectives, persp)) return;
368 Perspective3D::perspectives = g_slist_remove (Perspective3D::perspectives, persp);
369 }
371 // find an existing perspective whose VPs are equal to those of persp
372 Box3D::Perspective3D *
373 Perspective3D::find_perspective (Box3D::Perspective3D * const persp)
374 {
375 for (GSList *p = Perspective3D::perspectives; p != NULL; p = p->next) {
376 if (*((Box3D::Perspective3D *) p->data) == *persp) {
377 return ((Box3D::Perspective3D *) p->data);
378 }
379 }
380 return NULL; // perspective was not found
381 }
383 void
384 Perspective3D::print_debugging_info ()
385 {
386 g_print ("====================================================\n");
387 for (GSList *i = Perspective3D::perspectives; i != NULL; i = i->next) {
388 Perspective3D *persp = (Perspective3D *) i->data;
389 g_print ("Perspective %d:\n", persp->my_counter);
391 VanishingPoint * vp = persp->get_vanishing_point(Box3D::X);
392 g_print (" VP X: (%f,%f) ", (*vp)[NR::X], (*vp)[NR::Y]);
393 g_print ((vp->is_finite()) ? "(finite)\n" : "(infinite)\n");
395 vp = persp->get_vanishing_point(Box3D::Y);
396 g_print (" VP Y: (%f,%f) ", (*vp)[NR::X], (*vp)[NR::Y]);
397 g_print ((vp->is_finite()) ? "(finite)\n" : "(infinite)\n");
399 vp = persp->get_vanishing_point(Box3D::Z);
400 g_print (" VP Z: (%f,%f) ", (*vp)[NR::X], (*vp)[NR::Y]);
401 g_print ((vp->is_finite()) ? "(finite)\n" : "(infinite)\n");
403 g_print ("\nBoxes: ");
404 if (persp->boxes == NULL) {
405 g_print ("none");
406 } else {
407 GSList *j;
408 for (j = persp->boxes; j != NULL; j = j->next) {
409 if (j->next == NULL) break;
410 g_print ("%d, ", SP_3DBOX (j->data)->my_counter);
411 }
412 if (j != NULL) {
413 g_print ("%d", SP_3DBOX (j->data)->my_counter);
414 }
415 }
416 }
417 g_print ("\n====================================================\n");
418 }
420 } // namespace Box3D
422 /*
423 Local Variables:
424 mode:c++
425 c-file-style:"stroustrup"
426 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
427 indent-tabs-mode:nil
428 fill-column:99
429 End:
430 */
431 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :