Code

make win32 compile using libxslt
[inkscape.git] / src / box3d.cpp
1 #define __SP_3DBOX_C__
3 /*
4  * SVG <box3d> implementation
5  *
6  * Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   bulia byak <buliabyak@users.sf.net>
9  *   Maximilian Albert <Anhalter42@gmx.de>
10  *
11  * Copyright (C) 2007      Authors
12  * Copyright (C) 1999-2002 Lauris Kaplinski
13  * Copyright (C) 2000-2001 Ximian, Inc.
14  *
15  * Released under GNU GPL, read the file 'COPYING' for more information
16  */
18 #include <glibmm/i18n.h>
19 #include "attributes.h"
20 #include "svg/stringstream.h"
21 #include "box3d.h"
22 #include "desktop-handles.h"
24 static void sp_3dbox_class_init(SP3DBoxClass *klass);
25 static void sp_3dbox_init(SP3DBox *box3d);
27 static void sp_3dbox_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
28 static void sp_3dbox_release (SPObject *object);
29 static void sp_3dbox_set(SPObject *object, unsigned int key, const gchar *value);
30 static void sp_3dbox_update(SPObject *object, SPCtx *ctx, guint flags);
31 static Inkscape::XML::Node *sp_3dbox_write(SPObject *object, Inkscape::XML::Node *repr, guint flags);
33 static gchar *sp_3dbox_description(SPItem *item);
35 //static void sp_3dbox_set_shape(SPShape *shape);
36 //static void sp_3dbox_set_shape(SP3DBox *box3d);
38 static void sp_3dbox_update_corner_with_value_from_svg (SPObject *object, guint corner_id, const gchar *value);
39 static void sp_3dbox_update_perspective (Box3D::Perspective3D *persp, const gchar *value);
40 static gchar * sp_3dbox_get_corner_coords_string (SP3DBox *box, guint id);
41 static std::pair<gdouble, gdouble> sp_3dbox_get_coord_pair_from_string (const gchar *);
42 static gchar * sp_3dbox_get_perspective_string (SP3DBox *box);
44 static SPGroupClass *parent_class;
46 static gint counter = 0;
48 GType
49 sp_3dbox_get_type(void)
50 {
51     static GType type = 0;
53     if (!type) {
54         GTypeInfo info = {
55             sizeof(SP3DBoxClass),
56             NULL,   /* base_init */
57             NULL,   /* base_finalize */
58             (GClassInitFunc) sp_3dbox_class_init,
59             NULL,   /* class_finalize */
60             NULL,   /* class_data */
61             sizeof(SP3DBox),
62             16,     /* n_preallocs */
63             (GInstanceInitFunc) sp_3dbox_init,
64             NULL,   /* value_table */
65         };
66         type = g_type_register_static(SP_TYPE_GROUP, "SP3DBox", &info, (GTypeFlags) 0);
67     }
69     return type;
70 }
72 static void
73 sp_3dbox_class_init(SP3DBoxClass *klass)
74 {
75     SPObjectClass *sp_object_class = (SPObjectClass *) klass;
76     SPItemClass *item_class = (SPItemClass *) klass;
78     parent_class = (SPGroupClass *) g_type_class_ref(SP_TYPE_GROUP);
80     sp_object_class->build = sp_3dbox_build;
81     sp_object_class->set = sp_3dbox_set;
82     sp_object_class->write = sp_3dbox_write;
83     sp_object_class->update = sp_3dbox_update;
84     sp_object_class->release = sp_3dbox_release;
86     item_class->description = sp_3dbox_description;
87 }
89 static void
90 sp_3dbox_init(SP3DBox *box)
91 {
92     for (int i = 0; i < 8; ++i) box->corners[i] = NR::Point(0,0);
93     for (int i = 0; i < 6; ++i) box->faces[i] = NULL;
94 }
96 static void
97 sp_3dbox_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
98 {
99     if (((SPObjectClass *) (parent_class))->build) {
100         ((SPObjectClass *) (parent_class))->build(object, document, repr);
101     }
103     SP3DBox *box = SP_3DBOX (object);
105     box->my_counter = counter++;
107     /* we initialize the z-orders to zero so that they are updated during dragging */
108     for (int i = 0; i < 6; ++i) {
109         box->z_orders[i] = 0;
110     }
112     box->front_bits = 0x0;
114     
115     if (repr->attribute ("inkscape:perspective") == NULL) {
116         // we are creating a new box; link it to the current perspective
117         document->current_perspective->add_box (box);
118     } else {
119         // create a new perspective that we can compare with existing ones
120         Box3D::Perspective3D *persp = new Box3D::Perspective3D (Box3D::VanishingPoint (0,0),
121                                                                 Box3D::VanishingPoint (0,0),
122                                                                 Box3D::VanishingPoint (0,0),
123                                                                 document);
124         sp_3dbox_update_perspective (persp, repr->attribute ("inkscape:perspective"));
125         Box3D::Perspective3D *comp =  document->find_perspective (persp);
126         if (comp == NULL) {
127             // perspective doesn't exist yet
128             document->add_perspective (persp);
129             persp->add_box (box);
130         } else {
131             // link the box to the existing perspective and delete the temporary one
132             comp->add_box (box);
133             delete persp;
134             //g_assert (Box3D::get_persp_of_box (box) == comp);
136             // FIXME: If the paths of the box's faces do not correspond to the svg representation of the perspective
137             //        the box is shown with a "wrong" initial shape that is only corrected after dragging.
138             //        Should we "repair" this by updating the paths at the end of sp_3dbox_build()?
139             //        Maybe it would be better to simply destroy and rebuild them in sp_3dbox_link_to_existing_paths().
140         }
141     }
143     sp_object_read_attr(object, "inkscape:box3dcornerA");
144     sp_object_read_attr(object, "inkscape:box3dcornerB");
145     sp_object_read_attr(object, "inkscape:box3dcornerC");
147     // TODO: We create all faces in the beginning, but only the non-degenerate ones
148     //       should be written to the svg representation later in sp_3dbox_write.
149     Box3D::Axis cur_plane, axis, dir1, dir2;
150     Box3D::FrontOrRear cur_pos;
151     for (int i = 0; i < 3; ++i) {
152         for (int j = 0; j < 2; ++j) {
153             cur_plane = Box3D::planes[i];
154             cur_pos = Box3D::face_positions[j];
155             // FIXME: The following code could theoretically be moved to
156             //        the constructor of Box3DFace (but see the comment there).
157             axis = (cur_pos == Box3D::FRONT ? Box3D::NONE : Box3D::third_axis_direction (cur_plane));
158             dir1 = extract_first_axis_direction (cur_plane);
159             dir2 = extract_second_axis_direction (cur_plane);
160             
161             box->faces[Box3D::face_to_int(cur_plane ^ cur_pos)] =
162                 new Box3DFace (box, box->corners[axis], box->corners[axis ^ dir1],
163                                     box->corners[axis ^ dir1 ^ dir2], box->corners[axis ^ dir2],
164                                     cur_plane, cur_pos);
165         }
166     }
168     // Check whether the paths of the faces of the box need to be linked to existing paths in the
169     // document (e.g., after a 'redo' operation or after opening a file) and do so if necessary.
170     sp_3dbox_link_to_existing_paths (box, repr);
172     sp_3dbox_set_ratios (box, Box3D::XYZ);
174     // Store the center (if it already exists) and certain corners for later use during center-dragging
175     NR::Maybe<NR::Point> cen = sp_3dbox_get_center (box);
176     if (cen) {
177         box->old_center = *cen;
178     }
179     box->old_corner2 = box->corners[2];
180     box->old_corner1 = box->corners[1];
181     box->old_corner0 = box->corners[0];
182     box->old_corner3 = box->corners[3];
183     box->old_corner5 = box->corners[5];
184     box->old_corner7 = box->corners[7];
187 static void
188 sp_3dbox_release (SPObject *object)
190         SP3DBox *box = SP_3DBOX(object);
191         for (int i = 0; i < 6; ++i) {
192             if (box->faces[i]) {
193                 delete box->faces[i]; // FIXME: Anything else to do? Do we need to clean up the face first?
194             }
195         }
197         // FIXME: We do not duplicate perspectives if they are the same for several boxes.
198         //        Thus, don't delete the perspective when deleting a box but rather unlink the box from it.
199         SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->remove_box (box);
201         if (((SPObjectClass *) parent_class)->release) {
202           ((SPObjectClass *) parent_class)->release (object);
203         }
206 static void sp_3dbox_set(SPObject *object, unsigned int key, const gchar *value)
208     switch (key) {
209         case SP_ATTR_INKSCAPE_3DBOX_CORNER_A:
210             sp_3dbox_update_corner_with_value_from_svg (object, 2, value);
211             break;
212         case SP_ATTR_INKSCAPE_3DBOX_CORNER_B:
213             sp_3dbox_update_corner_with_value_from_svg (object, 1, value);
214             break;
215         case SP_ATTR_INKSCAPE_3DBOX_CORNER_C:
216             sp_3dbox_update_corner_with_value_from_svg (object, 5, value);
217             break;
218         case SP_ATTR_INKSCAPE_3DBOX_PERSPECTIVE:
219         {
220             SP3DBox *box = SP_3DBOX (object);
221             sp_3dbox_update_perspective (SP_OBJECT_DOCUMENT (object)->get_persp_of_box (box), value);
222             break;
223         }
224         default:
225             if (((SPObjectClass *) (parent_class))->set) {
226                 ((SPObjectClass *) (parent_class))->set(object, key, value);
227             }
228             break;
229     }
232 static void
233 sp_3dbox_update(SPObject *object, SPCtx *ctx, guint flags)
235     if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
236         SP3DBox *box = SP_3DBOX(object);
237         Inkscape::XML::Node *repr = SP_OBJECT_REPR(object);
238         sp_3dbox_link_to_existing_paths (box, repr);
239         SPEventContext *ec = inkscape_active_event_context();
240         if (SP_IS_3DBOX_CONTEXT (ec)) {
241             SP_3DBOX_CONTEXT (ec)->_vpdrag->updateDraggers();
242             // FIXME: Should we update the corners here, too? Maybe this is the reason why the handles
243             //        are off after an undo/redo! On the other hand, if we do so we get warnings about
244             //        updates occuring while other updats are in progress ...
245         }
246     }
248     /* Invoke parent method */
249     if (((SPObjectClass *) (parent_class))->update)
250         ((SPObjectClass *) (parent_class))->update(object, ctx, flags);
253 static Inkscape::XML::Node *sp_3dbox_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
255     SP3DBox *box = SP_3DBOX(object);
256     // FIXME: How to handle other contexts???
257     // FIXME: Is tools_isactive(..) more recommended to check for the current context/tool?
258     if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
259         return repr;
261     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
262         Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
263         repr = xml_doc->createElement("svg:g");
264         repr->setAttribute("sodipodi:type", "inkscape:3dbox");
265         /* Hook paths to the faces of the box */
266         for (int i = 0; i < 6; ++i) {
267             box->faces[i]->hook_path_to_3dbox();
268         }
269     }
271     for (int i = 0; i < 6; ++i) {
272         box->faces[i]->set_path_repr();
273     }
275     if (flags & SP_OBJECT_WRITE_EXT) {
276         gchar *str;
277         str = sp_3dbox_get_corner_coords_string (box, 2);
278         repr->setAttribute("inkscape:box3dcornerA", str);
280         str = sp_3dbox_get_corner_coords_string (box, 1);
281         repr->setAttribute("inkscape:box3dcornerB", str);
283         str = sp_3dbox_get_corner_coords_string (box, 5);
284         repr->setAttribute("inkscape:box3dcornerC", str);
286         str = sp_3dbox_get_perspective_string (box);
287         repr->setAttribute("inkscape:perspective", str);
288         sp_3dbox_set_ratios (box);
290         g_free ((void *) str);
292         /* store center and construction-corners for later use during center-dragging */
293         NR::Maybe<NR::Point> cen = sp_3dbox_get_center (box);
294         if (cen) {
295             box->old_center = *cen;
296         }
297         box->old_corner2 = box->corners[2];
298         box->old_corner1 = box->corners[1];
299         box->old_corner0 = box->corners[0];
300         box->old_corner3 = box->corners[3];
301         box->old_corner5 = box->corners[5];
302         box->old_corner7 = box->corners[7];
303     }
305     if (((SPObjectClass *) (parent_class))->write) {
306         ((SPObjectClass *) (parent_class))->write(object, repr, flags);
307     }
309     return repr;
312 static gchar *
313 sp_3dbox_description(SPItem *item)
315     g_return_val_if_fail(SP_IS_3DBOX(item), NULL);
317     return g_strdup(_("<b>3D Box</b>"));
320 void sp_3dbox_set_ratios (SP3DBox *box, Box3D::Axis axes)
322     Box3D::Perspective3D *persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
323     NR::Point pt;
325     if (axes & Box3D::X) {
326         pt = persp->get_vanishing_point (Box3D::X)->get_pos();
327         box->ratio_x = NR::L2 (pt - box->corners[2]) / NR::L2 (pt - box->corners[3]);
328     }
330     if (axes & Box3D::Y) {
331         pt = persp->get_vanishing_point (Box3D::Y)->get_pos();
332         box->ratio_y = NR::L2 (pt - box->corners[2]) / NR::L2 (pt - box->corners[0]);
333     }
335     if (axes & Box3D::Z) {
336         pt = persp->get_vanishing_point (Box3D::Z)->get_pos();
337         box->ratio_z = NR::L2 (pt - box->corners[4]) / NR::L2 (pt - box->corners[0]);
338     }
341 void
342 sp_3dbox_switch_front_face (SP3DBox *box, Box3D::Axis axis)
344     if (SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point (axis)->is_finite()) {
345         box->front_bits = box->front_bits ^ axis;
346     }
350 void
351 sp_3dbox_position_set (SP3DBoxContext &bc)
353     SP3DBox *box3d = SP_3DBOX(bc.item);
355     sp_3dbox_set_shape(box3d);
357     // FIXME: Why does the following call not automatically update the children
358     //        of box3d (which is an SPGroup, which should do this)?
359     //SP_OBJECT(box3d)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
361     /**
362     SP_OBJECT(box3d->path_face1)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
363     SP_OBJECT(box3d->path_face2)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
364     SP_OBJECT(box3d->path_face3)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
365     SP_OBJECT(box3d->path_face4)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
366     SP_OBJECT(box3d->path_face5)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
367     SP_OBJECT(box3d->path_face6)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
368     ***/
371 static void
372 sp_3dbox_set_shape_from_points (SP3DBox *box, NR::Point const &cornerA, NR::Point const &cornerB, NR::Point const &cornerC)
374     sp_3dbox_recompute_corners (box, cornerA, cornerB, cornerC);
376     // FIXME: How to handle other contexts???
377     // FIXME: Is tools_isactive(..) more recommended to check for the current context/tool?
378     if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
379         return;
380     SP3DBoxContext *bc = SP_3DBOX_CONTEXT(inkscape_active_event_context());
382     if (bc->extruded) {
383         box->faces[0]->set_corners (box->corners[0], box->corners[4], box->corners[6], box->corners[2]);
384         box->faces[1]->set_corners (box->corners[1], box->corners[5], box->corners[7], box->corners[3]);
385         box->faces[2]->set_corners (box->corners[0], box->corners[1], box->corners[5], box->corners[4]);
386         box->faces[3]->set_corners (box->corners[2], box->corners[3], box->corners[7], box->corners[6]);
387         box->faces[5]->set_corners (box->corners[4], box->corners[5], box->corners[7], box->corners[6]);
388     }
389     box->faces[4]->set_corners (box->corners[0], box->corners[1], box->corners[3], box->corners[2]);
391     sp_3dbox_update_curves (box);
394 void
395 // FIXME: Note that this is _not_ the virtual set_shape() method inherited from SPShape,
396 //        since SP3DBox is inherited from SPGroup. The following method is "artificially"
397 //        called from sp_3dbox_update().
398 //sp_3dbox_set_shape(SPShape *shape)
399 sp_3dbox_set_shape(SP3DBox *box, bool use_previous_corners)
401     if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
402         return;
403     SP3DBoxContext *bc = SP_3DBOX_CONTEXT(inkscape_active_event_context());
405     if (!use_previous_corners) {
406         sp_3dbox_set_shape_from_points (box, bc->drag_origin, bc->drag_ptB, bc->drag_ptC);
407     } else {
408         sp_3dbox_set_shape_from_points (box, box->corners[2], box->corners[1], box->corners[5]);
409     }
413 void sp_3dbox_recompute_corners (SP3DBox *box, NR::Point const A, NR::Point const B, NR::Point const C)
415     sp_3dbox_move_corner_in_XY_plane (box, 2, A);
416     sp_3dbox_move_corner_in_XY_plane (box, 1, B);
417     sp_3dbox_move_corner_in_Z_direction (box, 5, C);
420 inline static double
421 normalized_angle (double angle) {
422     if (angle < -M_PI) {
423         return angle + 2*M_PI;
424     } else if (angle > M_PI) {
425         return angle - 2*M_PI;
426     }
427     return angle;
430 static gdouble
431 sp_3dbox_corner_angle_to_VP (SP3DBox *box, Box3D::Axis axis, guint extreme_corner)
433     Box3D::VanishingPoint *vp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point (axis);
434     NR::Point dir;
436     if (vp->is_finite()) {
437         dir = NR::unit_vector (vp->get_pos() - box->corners[extreme_corner]);
438     } else {
439         dir = NR::unit_vector (vp->v_dir);
440     }
442     return atan2 (dir[NR::Y], dir[NR::X]);
446 bool sp_3dbox_recompute_z_orders (SP3DBox *box)
448     gint new_z_orders[6];
450     // TODO: Determine the front corner depending on the distance from VPs and/or the user presets
451     guint front_corner = sp_3dbox_get_front_corner_id (box);
453     gdouble dir_1x = sp_3dbox_corner_angle_to_VP (box, Box3D::X, front_corner);
454     gdouble dir_3x = sp_3dbox_corner_angle_to_VP (box, Box3D::X, front_corner ^ Box3D::Y);
456     gdouble dir_1y = sp_3dbox_corner_angle_to_VP (box, Box3D::Y, front_corner);
457     //gdouble dir_0y = sp_3dbox_corner_angle_to_VP (box, Box3D::Y, front_corner ^ Box3D::X);
459     gdouble dir_1z = sp_3dbox_corner_angle_to_VP (box, Box3D::Z, front_corner);
460     gdouble dir_3z = sp_3dbox_corner_angle_to_VP (box, Box3D::Z, front_corner ^ Box3D::Y);
462     // Still not perfect, but only fails in some rather degenerate cases.
463     // I suspect that there is a more elegant model, though. :)
464     new_z_orders[0] = Box3D::face_containing_corner (Box3D::XY, front_corner);
465     if (normalized_angle (dir_1y - dir_1z) > 0) {
466         new_z_orders[1] = Box3D::face_containing_corner (Box3D::YZ, front_corner);
467         if (normalized_angle (dir_1x - dir_1z) > 0) {
468             new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner ^ Box3D::Y);
469         } else {
470             new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
471         }
472     } else {
473         if (normalized_angle (dir_3x - dir_3z) > 0) {
474             new_z_orders[1] = Box3D::face_containing_corner (Box3D::XZ, front_corner ^ Box3D::Y);
475             new_z_orders[2] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
476         } else {
477             if (normalized_angle (dir_1x - dir_1z) > 0) {
478                 new_z_orders[1] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
479                 new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
480             } else {
481                 new_z_orders[1] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
482                 new_z_orders[2] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
483             }
484         }
485     }
487     new_z_orders[3] = Box3D::opposite_face (new_z_orders[2]);
488     new_z_orders[4] = Box3D::opposite_face (new_z_orders[1]);
489     new_z_orders[5] = Box3D::opposite_face (new_z_orders[0]);
491     /* We only need to look for changes among the topmost three faces because the order
492        of the other ones is just inverted. */
493     if ((box->z_orders[0] != new_z_orders[0]) ||
494         (box->z_orders[1] != new_z_orders[1]) ||
495         (box->z_orders[2] != new_z_orders[2]))
496     {
497         for (int i = 0; i < 6; ++i) {
498             box->z_orders[i] = new_z_orders[i];
499         }
500         return true;
501     }
503     return false;
506 // convenience
507 static bool sp_3dbox_is_subset_or_superset (std::vector<gint> const &list1, std::vector<gint> const &list2)
509     return (std::includes (list1.begin(), list1.end(), list2.begin(), list2.end()) ||
510             std::includes (list2.begin(), list2.end(), list1.begin(), list1.end()));
513 static bool sp_3dbox_differ_by_opposite_faces (std::vector<gint> const &list1, std::vector<gint> const &list2)
515     std::vector<gint> diff1;
516     std::vector<gint> diff2;
517     std::set_difference (list1.begin(), list1.end(), list2.begin(), list2.end(),
518                          std::insert_iterator<std::vector<gint> >(diff1, diff1.begin()));
519     std::set_difference (list2.begin(), list2.end(), list1.begin(), list1.end(),
520                          std::insert_iterator<std::vector<gint> >(diff2, diff2.begin()));
522     if (diff1.size() == 3 || diff1.size() != diff2.size())
523         return false;
525     for (guint i = 0; i < diff1.size(); ++i) {
526         if (std::find (diff2.begin(), diff2.end(), Box3D::opposite_face (diff1[i])) == diff2.end()) {
527             return false;
528         }
529     }
530     return true;
533 static gint
534 sp_3dbox_face_containing_diagonal_corners (guint corner1, guint corner2)
536     Box3D::Axis plane = (Box3D::Axis) (corner1 ^ corner2);
537     if (!Box3D::is_plane (plane)) {
538         g_warning ("Corners %d and %d should span a plane.\n", corner1, corner2);
539         return 0;
540     }
542     return Box3D::face_containing_corner (plane, corner1);
545 static std::vector<gint> sp_3dbox_adjacent_faces_of_edge (guint corner1, guint corner2) {
546     std::vector<gint> adj_faces;
547     Box3D::Axis edge = (Box3D::Axis) (corner1 ^ corner2);
548     if (!Box3D::is_single_axis_direction (edge)) {
549         return adj_faces;
550     }
552     Box3D::Axis plane = Box3D::orth_plane_or_axis (edge);
553     Box3D::Axis axis1 = Box3D::extract_first_axis_direction (plane);
554     Box3D::Axis axis2 = Box3D::extract_second_axis_direction (plane);
555     adj_faces.push_back (Box3D::face_containing_corner ((Box3D::Axis) (edge ^ axis1), corner1));
556     adj_faces.push_back (Box3D::face_containing_corner ((Box3D::Axis) (edge ^ axis2), corner1));
557     return adj_faces;
560 static std::vector<gint> sp_3dbox_faces_meeting_in_corner (guint corner) {
561     std::vector<gint> faces;
562     for (int i = 0; i < 3; ++i) {
563         faces.push_back (sp_3dbox_face_containing_diagonal_corners (corner, corner ^ Box3D::planes[i]));
564     }
565     return faces;
568 static void sp_3dbox_remaining_faces (std::vector<gint> const &faces, std::vector<gint> &rem_faces)
570     rem_faces.clear();
571     for (gint i = 0; i < 6; ++i) {
572         if (std::find (faces.begin(), faces.end(), i) == faces.end()) {
573             rem_faces.push_back (i);
574         }
575     }
578 /*
579  * Given two adjacent edges (\a c2,\a c1) and (\a c2, \a c3) of \a box (with common corner \a c2),
580  * check whether both lie on the convex hull of the point configuration given by \a box's corners.
581  */
582 static bool
583 sp_3dbox_is_border_edge_pair (SP3DBox *box, guint const c1, guint const c2, guint const c3)
585     Box3D::Axis edge21 = (Box3D::Axis) (c2 ^ c1);
586     Box3D::Axis edge23 = (Box3D::Axis) (c2 ^ c3);
587     Box3D::Axis rear_axis = Box3D::orth_plane_or_axis ((Box3D::Axis) (edge21 ^ edge23));
588  
589     NR::Point corner2 = box->corners[c2];
590     NR::Point dir21 = box->corners[c1] - corner2;
591     NR::Point dir23 = box->corners[c3] - corner2;
593     if (!Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ edge21 ^ edge23] - corner2) ||
594         !Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ rear_axis] - corner2) ||
595         !Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ rear_axis ^ edge21] - corner2) ||
596         !Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ rear_axis ^ edge21 ^ edge23] - corner2) ||
597         !Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ rear_axis ^ edge23] - corner2)) {
598         // corner triple c1, c2, c3 doesn't bound the convex hull
599         return false;
600     }
601     // corner triple c1, c2, c3 bounds the convex hull
602     return true;    
605 /*
606  * Test whether there are any adjacent corners of \a corner (i.e., connected with it along one of the axes)
607  * such that the corresponding edges bound the convex hull of the box (as a point configuration in the plane)
608  * If this is the case, return the corresponding two adjacent corners; otherwise return (-1, -1).
609  */
610 static Box3D::Axis
611 sp_3dbox_axis_pair_bounding_convex_hull (SP3DBox *box, guint corner)
612  {
613     guint adj1 = corner ^ Box3D::X;
614     guint adj2 = corner ^ Box3D::Y;
615     guint adj3 = corner ^ Box3D::Z;
617     if (sp_3dbox_is_border_edge_pair (box, adj1, corner, adj2)) {
618         return Box3D::XY;
619     }
620     if (sp_3dbox_is_border_edge_pair (box, adj1, corner, adj3)) {
621         return Box3D::XZ;
622     }
623     if (sp_3dbox_is_border_edge_pair (box, adj2, corner, adj3)) {
624         return Box3D::YZ;
625     }
626     return Box3D::NONE;
629 // inside_hull is modified 'in place' by the following function
630 static void sp_3dbox_corner_configuration (SP3DBox *box, std::vector<gint> &on_hull, std::vector<gint> &inside_hull)
632     for (int i = 0; i < 8; ++i) {
633         Box3D::Axis bounding_edges = sp_3dbox_axis_pair_bounding_convex_hull (box, i);
634         if (bounding_edges != Box3D::NONE) {
635             on_hull.push_back (i);
636         } else {
637             inside_hull.push_back (i);
638         }
639     }
642 /* returns true if there was a change in the z-orders (which triggers an update of the repr) */
643 static bool sp_3dbox_recompute_z_orders_by_corner_configuration (SP3DBox *box)
645     gint new_z_orders[6];
646     Box3D::Axis front_rear_axis = Box3D::Z;
648     std::vector<gint> on_hull;
649     std::vector<gint> inside_hull;
650     std::vector<gint> visible_faces;
652     sp_3dbox_corner_configuration (box, on_hull, inside_hull);
654     switch (on_hull.size()) {
655         case 4:
656             {
657                 // the following works because on_hull is sorted
658                 gint front_face = sp_3dbox_face_containing_diagonal_corners (on_hull[0], on_hull[3]);
659                 visible_faces.push_back (front_face);
660             }
661             break;
663         case 6:
664         {
665             guint c1 = inside_hull[0] ^ Box3D::XYZ;
666             guint c2 = inside_hull[1] ^ Box3D::XYZ;
667             Box3D::Axis edge = (Box3D::Axis) (c1 ^ c2);
668             if (Box3D::is_single_axis_direction (edge)) {
669                 visible_faces = sp_3dbox_adjacent_faces_of_edge (c1, c2);
670             } else if (c1 == c2 ^ Box3D::XYZ) {
671                 guint c_cmp = sp_3dbox_get_corner_id_along_edge (box, 0, front_rear_axis, Box3D::FRONT);
672                 guint visible_front_corner = (((c_cmp & front_rear_axis) == (c1 & front_rear_axis)) ? c1 : c2);
673                 visible_faces = sp_3dbox_faces_meeting_in_corner (visible_front_corner);
674             } else {
675                 /* Under what conditions do we end up here? Can we safely ignore this case? */
676                 return false;
677             }
678             break;
679         }
681         default:
682             /* Under what conditions do we end up here? Can we safely ignore this case? */
683             return false;
684     }
686     /* catch weird corner configurations; these should be theoretically impossible, but maybe
687        occur in (almost) degenerate cases due to rounding errors, for example */
688     if (std::find (visible_faces.begin(), visible_faces.end(), -1) != visible_faces.end()) {
689         return false;
690     }
692     /* sort the list of visible faces for later use (although it may be already sorted anyway) */
693     std::sort (visible_faces.begin(), visible_faces.end());
695     std::vector<gint> invisible_faces;
696     sp_3dbox_remaining_faces (visible_faces, invisible_faces);
699     if (!sp_3dbox_is_subset_or_superset (visible_faces, box->currently_visible_faces) &&
700         !sp_3dbox_differ_by_opposite_faces (visible_faces, box->currently_visible_faces)) {
701         std::swap (visible_faces, invisible_faces);
702         if (!sp_3dbox_is_subset_or_superset (visible_faces, box->currently_visible_faces) &&
703             !sp_3dbox_differ_by_opposite_faces (visible_faces, box->currently_visible_faces)) {
704             /* Hopefully this case is only caused by rounding errors or something similar;
705                does it need further investigation? */
706             return false;
707         }
708     }
710     box->currently_visible_faces = visible_faces;
712     // set new z-orders according to the visible/invisible faces
713     guint vis_size = visible_faces.size();
714     for (guint i = 0; i < vis_size; ++i) {
715         new_z_orders[i] = visible_faces[i];
716     }
717     for (guint i = 0; i < invisible_faces.size(); ++i) {
718         new_z_orders[vis_size + i] = invisible_faces[i];
719     }
721     // test whether any z-orders actually changed and indicate this in the return status
722     for (int i = 0; i < 6; ++i) {
723         if (box->z_orders[i] != new_z_orders[i]) {
724             // we update the z-orders starting from the index where the change occurs
725             for (int j = i; j < 6; ++j) {
726                 box->z_orders[j] = new_z_orders[j];
727             }
728             return true;
729         }
730     }
731     return false;
734 // FIXME: Can we unify this and the next function for setting the z-orders?
735 void sp_3dbox_set_z_orders_in_the_first_place (SP3DBox *box)
737     // For efficiency reasons, we only set the new z-orders if something really changed
738     if (sp_3dbox_recompute_z_orders (box)) {
739         box->faces[box->z_orders[0]]->lower_to_bottom ();
740         box->faces[box->z_orders[1]]->lower_to_bottom ();
741         box->faces[box->z_orders[2]]->lower_to_bottom ();
742         box->faces[box->z_orders[3]]->lower_to_bottom ();
743         box->faces[box->z_orders[4]]->lower_to_bottom ();
744         box->faces[box->z_orders[5]]->lower_to_bottom ();
745     }
748 void sp_3dbox_set_z_orders_later_on (SP3DBox *box)
750     // For efficiency reasons, we only set the new z-orders if something really changed
751     if (sp_3dbox_recompute_z_orders_by_corner_configuration (box)) {
752         box->faces[box->z_orders[0]]->lower_to_bottom ();
753         box->faces[box->z_orders[1]]->lower_to_bottom ();
754         box->faces[box->z_orders[2]]->lower_to_bottom ();
755         box->faces[box->z_orders[3]]->lower_to_bottom ();
756         box->faces[box->z_orders[4]]->lower_to_bottom ();
757         box->faces[box->z_orders[5]]->lower_to_bottom ();
758     }
761 void
762 sp_3dbox_update_curves (SP3DBox *box) {
763     for (int i = 0; i < 6; ++i) {
764         if (box->faces[i]) box->faces[i]->set_curve();
765     }
768 /**
769  * In some situations (e.g., after cloning boxes, undo & redo, or reading boxes from a file) there are
770  * paths already present in the document which correspond to the faces of newly created boxes, but their
771  * 'path' members don't link to them yet. The following function corrects this if necessary.
772  */
773 void
774 sp_3dbox_link_to_existing_paths (SP3DBox *box, Inkscape::XML::Node *repr) {
775     // TODO: We should probably destroy the existing paths and recreate them because we don't know
776     //       precisely which path corresponds to which face. Does this make a difference?
777     //       In sp_3dbox_write we write the correct paths anyway, don't we? But we could get into
778     //       trouble at a later stage when we only write single faces for degenerate boxes.
780     SPDocument *document = SP_OBJECT_DOCUMENT(box);
781     guint face_id = 0;
783     for (Inkscape::XML::Node *i = sp_repr_children(repr); i != NULL; i = sp_repr_next(i)) {
784         if (face_id > 5) {
785             g_warning ("SVG representation of 3D boxes must contain 6 paths or less.\n");
786             break;
787         }
789         SPObject *face_object = document->getObjectByRepr((Inkscape::XML::Node *) i);
790         if (!SP_IS_PATH(face_object)) {
791             g_warning ("SVG representation of 3D boxes should only contain paths.\n");
792             continue;
793         }
794         // TODO: Currently we don't check whether all paths are being linked to different faces.
795         //       This is no problem with valid SVG files. It may lead to crashes, however,
796         //       in case a file is corrupt (e.g., two or more faces have identical descriptions).
797         gint id = Box3DFace::descr_to_id (i->attribute ("inkscape:box3dface"));
798         box->faces[id]->hook_path_to_3dbox(SP_PATH(face_object));
799         ++face_id;
800     }
801     if (face_id < 6) {
802         //g_warning ("SVG representation of 3D boxes should contain exactly 6 paths (degenerate boxes are not yet supported).\n");
803         // TODO: Check whether it is safe to add the remaining paths to the box and do so in case it is.
804         //       (But we also land here for newly created boxes where we shouldn't add any paths because
805         //       This is done in sp_3dbox_write later on.
806     }
809 void
810 sp_3dbox_reshape_after_VP_rotation (SP3DBox *box, Box3D::Axis axis)
812     Box3D::Perspective3D *persp = inkscape_active_document()->get_persp_of_box (box);
813     Box3D::VanishingPoint *vp = persp->get_vanishing_point (axis);
815     guint c1 = (axis == Box3D::Z) ? 1 : sp_3dbox_get_front_corner_id (box); // hack
816     guint c2 = c1 ^ axis;
817     NR::Point v = box->corners[c1] - box->corners[c2];
818     double dist = NR::L2 (v) * ((NR::dot (v, vp->v_dir) < 0) ? 1 : -1); // "directed" distance
820     Box3D::PerspectiveLine pline (box->corners[c1], axis, persp);
821     NR::Point pt = pline.point_from_lambda (dist);
823     sp_3dbox_move_corner_in_Z_direction (box, c2, pt, axis == Box3D::Z);
826 void
827 sp_3dbox_move_corner_in_XY_plane (SP3DBox *box, guint id, NR::Point pt, Box3D::Axis axes)
829     Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
831     NR::Point A (box->corners[id ^ Box3D::XY]);
832     if (Box3D::is_single_axis_direction (axes)) {
833         pt = Box3D::PerspectiveLine (box->corners[id], axes, persp).closest_to(pt);
834     }
836     /* set the 'front' corners */
837     box->corners[id] = pt;
839     Box3D::PerspectiveLine pl_one (A, Box3D::Y, persp);
840     Box3D::PerspectiveLine pl_two (pt, Box3D::X, persp);
841     box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
843     pl_one = Box3D::PerspectiveLine (A, Box3D::X, persp);
844     pl_two = Box3D::PerspectiveLine (pt, Box3D::Y, persp);
845     box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
847     /* set the 'rear' corners */
848     NR::Point B (box->corners[id ^ Box3D::XYZ]);
850     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Z, persp);
851     pl_two = Box3D::PerspectiveLine (B, Box3D::Y, persp);
852     box->corners[id ^ Box3D::XZ] = pl_one.meet(pl_two);
854     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XZ], Box3D::X, persp);
855     pl_two = Box3D::PerspectiveLine (pt, Box3D::Z, persp);
856     box->corners[id ^ Box3D::Z] = pl_one.meet(pl_two);
858     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::Z], Box3D::Y, persp);
859     pl_two = Box3D::PerspectiveLine (B, Box3D::X, persp);
860     box->corners[id ^ Box3D::YZ] = pl_one.meet(pl_two);
861     
864 void
865 sp_3dbox_move_corner_in_Z_direction (SP3DBox *box, guint id, NR::Point pt, bool constrained)
867     if (!constrained) sp_3dbox_move_corner_in_XY_plane (box, id, pt, Box3D::XY);
869     Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
871     /* set the four corners of the face containing corners[id] */
872     box->corners[id] = Box3D::PerspectiveLine (box->corners[id], Box3D::Z, persp).closest_to(pt);
874     Box3D::PerspectiveLine pl_one (box->corners[id], Box3D::X, persp);
875     Box3D::PerspectiveLine pl_two (box->corners[id ^ Box3D::XZ], Box3D::Z, persp);
876     box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
878     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Y, persp);
879     pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XYZ], Box3D::Z, persp);
880     box->corners[id ^ Box3D::XY] = pl_one.meet(pl_two);
882     pl_one = Box3D::PerspectiveLine (box->corners[id], Box3D::Y, persp);
883     pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::YZ], Box3D::Z, persp);
884     box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
887 static void
888 sp_3dbox_reshape_edge_after_VP_toggling (SP3DBox *box, const guint corner, const Box3D::Axis axis, Box3D::Perspective3D *persp)
890     /* Hmm, perhaps we should simply use one of the corners as the pivot point.
891        But this way we minimize the amount of reshaping.
892        On second thought, we need to find a way to ensure that all boxes sharing the same
893        perspective are updated consistently _as a group_. That is, they should also retain
894        their relative positions towards each other. */
895     NR::Maybe<NR::Point> pt = sp_3dbox_get_midpoint_between_corners (box, corner, corner ^ axis);
896     g_return_if_fail (pt);
898     Box3D::Axis axis2 = ((axis == Box3D::Y) ? Box3D::X : Box3D::Y);
900     Box3D::PerspectiveLine line1 (box->corners[corner], axis2, persp);
901     Box3D::PerspectiveLine line2 (box->corners[corner ^ axis], axis2, persp);
903     Box3D::PerspectiveLine line3 (*pt, axis, persp);
905     NR::Point new_corner1 = line1.meet (line3);
906     NR::Point new_corner2 = line2.meet (line3);
908     box->corners[corner] = new_corner1;
909     box->corners[corner ^ axis] = new_corner2;
912 void
913 sp_3dbox_reshape_after_VP_toggling (SP3DBox *box, Box3D::Axis axis)
915     Box3D::Perspective3D *persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
916     std::pair<Box3D::Axis, Box3D::Axis> dirs = Box3D::get_remaining_axes (axis);
918     sp_3dbox_reshape_edge_after_VP_toggling (box, 0, axis, persp);
919     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first, axis, persp);
920     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first ^ dirs.second, axis, persp);
921     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.second, axis, persp);
924 NR::Maybe<NR::Point>
925 sp_3dbox_get_center (SP3DBox *box)
927     return sp_3dbox_get_midpoint_between_corners (box, 0, 7);
930 NR::Point
931 sp_3dbox_get_midpoint_in_axis_direction (NR::Point const &C, NR::Point const &D, Box3D::Axis axis, Box3D::Perspective3D *persp)
933     Box3D::PerspectiveLine pl (D, axis, persp);
934     return pl.pt_with_given_cross_ratio (C, D, -1.0);
937 // TODO: The following function can probably be rewritten in a much more elegant and robust way
938 //        by using projective coordinates for all points and using the cross ratio.
939 NR::Maybe<NR::Point>
940 sp_3dbox_get_midpoint_between_corners (SP3DBox *box, guint id_corner1, guint id_corner2)
942     Box3D::Axis corner_axes = (Box3D::Axis) (id_corner1 ^ id_corner2);
944     // Is all this sufficiently precise also for degenerate cases?
945     if (sp_3dbox_corners_are_adjacent (id_corner1, id_corner2)) {
946         Box3D::Axis orth_dir = get_perpendicular_axis_direction (corner_axes);
948         Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2 ^ orth_dir]);
949         Box3D::Line diag2 (box->corners[id_corner1 ^ orth_dir], box->corners[id_corner2]);
950         NR::Maybe<NR::Point> adjacent_face_center = diag1.intersect(diag2);
952         if (!adjacent_face_center) return NR::Nothing();
954         Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
956         Box3D::PerspectiveLine pl (*adjacent_face_center, orth_dir, persp);
957         return pl.intersect(Box3D::PerspectiveLine(box->corners[id_corner1], corner_axes, persp));
958     } else {
959         Box3D::Axis dir = Box3D::extract_first_axis_direction (corner_axes);
960         Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2]);
961         Box3D::Line diag2 (box->corners[id_corner1 ^ dir], box->corners[id_corner2 ^ dir]);
962         return diag1.intersect(diag2);
963     }
966 static gchar *
967 sp_3dbox_get_corner_coords_string (SP3DBox *box, guint id)
969     id = id % 8;
970     Inkscape::SVGOStringStream os;
971     os << box->corners[id][NR::X] << "," << box->corners[id][NR::Y];
972     return g_strdup(os.str().c_str());
975 static std::pair<gdouble, gdouble>
976 sp_3dbox_get_coord_pair_from_string (const gchar *coords)
978     gchar **coordpair = g_strsplit( coords, ",", 0);
979     // We might as well rely on g_ascii_strtod to convert the NULL pointer to 0.0,
980     // but we include the following test anyway
981     if (coordpair[0] == NULL || coordpair[1] == NULL) {
982         g_strfreev (coordpair);
983         g_warning ("Coordinate conversion failed.\n");
984         return std::make_pair(0.0, 0.0);
985     }
987     gdouble coord1 = g_ascii_strtod(coordpair[0], NULL);
988     gdouble coord2 = g_ascii_strtod(coordpair[1], NULL);
989     g_strfreev (coordpair);
991     return std::make_pair(coord1, coord2);
994 static gchar *
995 sp_3dbox_get_perspective_string (SP3DBox *box)
997     
998     return sp_3dbox_get_svg_descr_of_persp (SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box));
1000   
1001 gchar *
1002 sp_3dbox_get_svg_descr_of_persp (Box3D::Perspective3D *persp)
1004     // FIXME: We should move this code to perspective3d.cpp, but this yields compiler errors. Why?
1005     Inkscape::SVGOStringStream os;
1007     Box3D::VanishingPoint vp = *(persp->get_vanishing_point (Box3D::X));
1008     os << vp[NR::X] << "," << vp[NR::Y] << ",";
1009     os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
1010     if (vp.is_finite()) {
1011         os << "finite,";
1012     } else {
1013         os << "infinite,";
1014     }
1016     vp = *(persp->get_vanishing_point (Box3D::Y));
1017     os << vp[NR::X] << "," << vp[NR::Y] << ",";
1018     os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
1019     if (vp.is_finite()) {
1020         os << "finite,";
1021     } else {
1022         os << "infinite,";
1023     }
1025     vp = *(persp->get_vanishing_point (Box3D::Z));
1026     os << vp[NR::X] << "," << vp[NR::Y] << ",";
1027     os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
1028     if (vp.is_finite()) {
1029         os << "finite";
1030     } else {
1031         os << "infinite";
1032     }
1034     return g_strdup(os.str().c_str());
1037 // auxiliary function
1038 static std::pair<NR::Point, NR::Point>
1039 sp_3dbox_new_midpoints (Box3D::Perspective3D *persp, Box3D::Axis axis, NR::Point const &M0, NR::Point const &M, NR::Point const &A, NR::Point const &B)
1041     double cr1 = Box3D::cross_ratio (*persp->get_vanishing_point (axis), M0, M, A);
1042     double cr2 = Box3D::cross_ratio (*persp->get_vanishing_point (axis), M, B, M0);
1043     if (fabs (cr1 - 1) < Box3D::epsilon) {
1044         // FIXME: cr == 1 is a degenerate case; how should we deal with it?
1045         return std::make_pair (NR::Point (0,0), NR::Point (0,0));
1046     }
1047     if (cr1 == NR_HUGE) {
1048         return std::make_pair (A, B);
1049     }
1050     Box3D::PerspectiveLine pl (M0, axis, persp);
1051     NR::Point B_new = pl.pt_with_given_cross_ratio (M0, M, cr1 / (cr1 - 1));
1052     NR::Point A_new = pl.pt_with_given_cross_ratio (M0, M, 1 - cr2);
1053     return std::make_pair (A_new, B_new);
1056 void sp_3dbox_recompute_Z_corners_from_new_center (SP3DBox *box, NR::Point const new_center)
1058     // TODO: Clean this function up
1060     Box3D::Perspective3D *persp = sp_desktop_document (inkscape_active_desktop())->get_persp_of_box (box);
1061     NR::Point old_center = box->old_center;
1063     Box3D::PerspectiveLine aux_line1 (old_center, Box3D::Z, persp);
1064     Box3D::PerspectiveLine aux_line2 (new_center, Box3D::Y, persp);
1065     NR::Point Z1 = aux_line1.meet (aux_line2);
1067     NR::Point A0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner2, box->old_corner0, Box3D::Y, persp));
1068     NR::Point B0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner7, box->old_corner5, Box3D::Y, persp));
1069     Box3D::PerspectiveLine aux_line3 (A0, Box3D::X, persp);
1070     Box3D::PerspectiveLine aux_line4 (B0, Box3D::X, persp);
1072     NR::Point C0 = aux_line3.meet (aux_line1);
1073     NR::Point D0 = aux_line4.meet (aux_line1);
1075     std::pair<NR::Point, NR::Point> new_midpts = sp_3dbox_new_midpoints (persp, Box3D::Z, old_center, Z1, C0, D0);
1076     NR::Point C1 (new_midpts.first);
1077     NR::Point D1 (new_midpts.second);
1078     Box3D::PerspectiveLine aux_line5 (C1, Box3D::X, persp);
1079     Box3D::PerspectiveLine aux_line6 (D1, Box3D::X, persp);
1081     Box3D::PerspectiveLine aux_line7 (A0, Box3D::Z, persp);
1082     Box3D::PerspectiveLine aux_line8 (B0, Box3D::Z, persp);
1084     NR::Point A1 = aux_line5.meet (aux_line7);
1085     NR::Point B1 = aux_line6.meet (aux_line8);
1087     Box3D::PerspectiveLine aux_line9  (box->old_corner2, Box3D::Z, persp);
1088     Box3D::PerspectiveLine aux_line10 (box->old_corner5, Box3D::Z, persp);
1090     Box3D::PerspectiveLine aux_line11 (A1, Box3D::Y, persp);
1091     Box3D::PerspectiveLine aux_line12 (B1, Box3D::Y, persp);
1093     NR::Point new_corner2 = aux_line9.meet (aux_line11);
1094     NR::Point new_corner5 = aux_line10.meet (aux_line12);
1096     Box3D::PerspectiveLine aux_line13 (A1, Box3D::X, persp);
1097     NR::Point E1 = aux_line13.meet (aux_line8);
1098     Box3D::PerspectiveLine aux_line14 (E1, Box3D::Y, persp);
1100     NR::Point new_corner1 = aux_line10.meet (aux_line14);
1102     sp_3dbox_set_shape_from_points (box, new_corner2, new_corner1, new_corner5);
1105 void sp_3dbox_recompute_XY_corners_from_new_center (SP3DBox *box, NR::Point const new_center)
1107     // TODO: Clean this function up
1109     Box3D::Perspective3D *persp = sp_desktop_document (inkscape_active_desktop())->get_persp_of_box (box);
1110     NR::Point old_center = box->old_center;
1112     NR::Point A0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner2, box->old_corner0, Box3D::Y, persp));
1113     NR::Point B0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner1, box->old_corner3, Box3D::Y, persp));
1115     /* we first move the box along the X-axis ... */
1116     Box3D::PerspectiveLine aux_line1 (old_center, Box3D::X, persp);
1117     Box3D::PerspectiveLine aux_line2 (new_center, Box3D::Y, persp);
1118     NR::Point Z1 = aux_line1.meet (aux_line2);
1120     Box3D::PerspectiveLine ref_line (B0, Box3D::X, persp);
1121     Box3D::PerspectiveLine pline2 (old_center, Box3D::Z, persp);
1122     Box3D::PerspectiveLine pline3 (Z1, Box3D::Z, persp);
1123     NR::Point M0 = ref_line.meet (pline2);
1124     NR::Point M1 = ref_line.meet (pline3);
1126     std::pair<NR::Point, NR::Point> new_midpts = sp_3dbox_new_midpoints (persp, Box3D::X, M0, M1, A0, B0);
1127     NR::Point A1 (new_midpts.first);
1128     NR::Point B1 (new_midpts.second);
1130     /* ... and then along the Y-axis */
1131     Box3D::PerspectiveLine pline4 (box->old_corner1, Box3D::X, persp);
1132     Box3D::PerspectiveLine pline5 (box->old_corner3, Box3D::X, persp);
1133     Box3D::PerspectiveLine aux_line3 (M1, Box3D::Y, persp);
1134     NR::Point C1 = aux_line3.meet (pline4);
1135     NR::Point D1 = aux_line3.meet (pline5);
1137     Box3D::PerspectiveLine aux_line4 (new_center, Box3D::Z, persp);
1138     NR::Point M2 = aux_line4.meet (aux_line3);
1140     std::pair<NR::Point, NR::Point> other_new_midpts = sp_3dbox_new_midpoints (persp, Box3D::Y, M1, M2, C1, D1);
1141     NR::Point C2 (other_new_midpts.first);
1142     NR::Point D2 (other_new_midpts.second);
1144     Box3D::PerspectiveLine plXC (C2, Box3D::X, persp);
1145     Box3D::PerspectiveLine plXD (D2, Box3D::X, persp);
1146     Box3D::PerspectiveLine plYA (A1, Box3D::Y, persp);
1147     Box3D::PerspectiveLine plYB (B1, Box3D::Y, persp);
1149     NR::Point new_corner2 (plXD.meet (plYA));
1150     NR::Point new_corner1 (plXC.meet (plYB));
1152     NR::Point tmp_corner1 (pline4.meet (plYB));
1153     Box3D::PerspectiveLine pline6 (box->old_corner5, Box3D::X, persp);
1154     Box3D::PerspectiveLine pline7 (tmp_corner1, Box3D::Z, persp);
1155     NR::Point tmp_corner5 (pline6.meet (pline7));
1157     Box3D::PerspectiveLine pline8 (tmp_corner5, Box3D::Y, persp);
1158     Box3D::PerspectiveLine pline9 (new_corner1, Box3D::Z, persp);
1159     NR::Point new_corner5 (pline8.meet (pline9));
1161     sp_3dbox_set_shape_from_points (box, new_corner2, new_corner1, new_corner5);
1164 void sp_3dbox_update_perspective_lines()
1166     SPEventContext *ec = inkscape_active_event_context();
1167     if (!SP_IS_3DBOX_CONTEXT (ec))
1168         return;
1170     SP_3DBOX_CONTEXT (ec)->_vpdrag->updateLines();
1173 /*
1174  * Manipulates corner1 through corner4 to contain the indices of the corners
1175  * from which the perspective lines in the direction of 'axis' emerge
1176  */
1177 void sp_3dbox_corners_for_perspective_lines (const SP3DBox * box, Box3D::Axis axis, 
1178                                              NR::Point &corner1, NR::Point &corner2, NR::Point &corner3, NR::Point &corner4)
1180     // along which axis to switch when takint
1181     Box3D::Axis switch_axis;
1182     if (axis == Box3D::X || axis == Box3D::Y) {
1183         switch_axis = (box->front_bits & axis) ? Box3D::Z : Box3D::NONE;
1184     } else {
1185         switch_axis = (box->front_bits & axis) ? Box3D::X : Box3D::NONE;
1186     }
1188     switch (axis) {
1189         case Box3D::X:
1190             corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
1191             corner2 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
1192             corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
1193             corner4 = sp_3dbox_get_corner_along_edge (box, 6 ^ switch_axis, axis, Box3D::REAR);
1194             break;
1195         case Box3D::Y:
1196             corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
1197             corner2 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
1198             corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
1199             corner4 = sp_3dbox_get_corner_along_edge (box, 5 ^ switch_axis, axis, Box3D::REAR);
1200             break;
1201         case Box3D::Z:
1202             corner1 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
1203             corner2 = sp_3dbox_get_corner_along_edge (box, 3 ^ switch_axis, axis, Box3D::REAR);
1204             corner3 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
1205             corner4 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
1206             break;
1207         default:
1208             // do nothing
1209             break;
1210     }            
1213 /**
1214  * Returns the id of the corner on the edge along 'axis' and passing through 'corner' that
1215  * lies on the front/rear face in this direction.
1216  */
1217 guint
1218 sp_3dbox_get_corner_id_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
1220     guint result;
1221     guint other_corner = corner ^ axis;
1222     Box3D::VanishingPoint *vp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point(axis);
1223     if (vp->is_finite()) {
1224         result = (  NR::L2 (vp->get_pos() - box->corners[corner])
1225                   < NR::L2 (vp->get_pos() - box->corners[other_corner]) ? other_corner : corner);
1226     } else {
1227         // clear the axis bit and switch to the appropriate corner along axis, depending on the value of front_bits
1228         result = ((corner & (0xF ^ axis)) ^ (box->front_bits & axis));
1229     }
1231     if (rel_pos == Box3D::FRONT) {
1232         return result;
1233     } else {
1234         return result ^ axis;
1235     }
1238 NR::Point
1239 sp_3dbox_get_corner_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
1241     return box->corners[sp_3dbox_get_corner_id_along_edge (box, corner, axis, rel_pos)];
1244 guint
1245 sp_3dbox_get_front_corner_id (const SP3DBox *box)
1247     guint front_corner = 1; // this could in fact be any corner, but we choose the one that is normally in front
1248     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::X, Box3D::FRONT);
1249     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Y, Box3D::FRONT);
1250     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Z, Box3D::FRONT);
1251     return front_corner;
1254 // auxiliary functions
1255 static void
1256 sp_3dbox_update_corner_with_value_from_svg (SPObject *object, guint corner_id, const gchar *value)
1258     if (value == NULL) return;
1259     SP3DBox *box = SP_3DBOX(object);
1261     std::pair<gdouble, gdouble> coord_pair = sp_3dbox_get_coord_pair_from_string (value);
1262     box->corners[corner_id] = NR::Point (coord_pair.first, coord_pair.second);
1263     sp_3dbox_recompute_corners (box, box->corners[2], box->corners[1], box->corners[5]);
1264     object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1267 static void
1268 sp_3dbox_update_perspective (Box3D::Perspective3D *persp, const gchar *value)
1270     // WARNING! This function changes the perspective associated to 'box'. Since there may be
1271     // many other boxes linked to the same perspective, their perspective is also changed.
1272     // If this behaviour is not desired in all cases, we need a different function.
1273     if (value == NULL) return;
1275     gchar **vps = g_strsplit( value, ",", 0);
1276     for (int i = 0; i < 15; ++i) {
1277         if (vps[i] == NULL) {
1278             g_warning ("Malformed svg attribute 'perspective'\n");
1279             return;
1280         }
1281     }
1283     persp->set_vanishing_point (Box3D::X, g_ascii_strtod (vps[0], NULL), g_ascii_strtod (vps[1], NULL),
1284                                           g_ascii_strtod (vps[2], NULL), g_ascii_strtod (vps[3], NULL),
1285                                           strcmp (vps[4], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
1286     persp->set_vanishing_point (Box3D::Y, g_ascii_strtod (vps[5], NULL), g_ascii_strtod (vps[6], NULL),
1287                                           g_ascii_strtod (vps[7], NULL), g_ascii_strtod (vps[8], NULL),
1288                                           strcmp (vps[9], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
1289     persp->set_vanishing_point (Box3D::Z, g_ascii_strtod (vps[10], NULL), g_ascii_strtod (vps[11], NULL),
1290                                           g_ascii_strtod (vps[12], NULL), g_ascii_strtod (vps[13], NULL),
1291                                           strcmp (vps[14], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
1293     // update the other boxes linked to the same perspective
1294     persp->reshape_boxes (Box3D::XYZ);
1297 /*
1298   Local Variables:
1299   mode:c++
1300   c-file-style:"stroustrup"
1301   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1302   indent-tabs-mode:nil
1303   fill-column:99
1304   End:
1305 */
1306 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :