Code

0cf0743dbc6568f4218e1cb9aae1400be3635c29
[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         sp_3dbox_link_to_existing_paths (box, SP_OBJECT_REPR(object));
238     }
240     /* Invoke parent method */
241     if (((SPObjectClass *) (parent_class))->update)
242         ((SPObjectClass *) (parent_class))->update(object, ctx, flags);
245 static Inkscape::XML::Node *sp_3dbox_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
247     SP3DBox *box = SP_3DBOX(object);
248     // FIXME: How to handle other contexts???
249     // FIXME: Is tools_isactive(..) more recommended to check for the current context/tool?
250     if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
251         return repr;
253     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
254         Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
255         repr = xml_doc->createElement("svg:g");
256         repr->setAttribute("sodipodi:type", "inkscape:3dbox");
257         /* Hook paths to the faces of the box */
258         for (int i = 0; i < 6; ++i) {
259             box->faces[i]->hook_path_to_3dbox();
260         }
261     }
263     for (int i = 0; i < 6; ++i) {
264         box->faces[i]->set_path_repr();
265     }
267     if (flags & SP_OBJECT_WRITE_EXT) {
268         gchar *str;
269         str = sp_3dbox_get_corner_coords_string (box, 2);
270         repr->setAttribute("inkscape:box3dcornerA", str);
272         str = sp_3dbox_get_corner_coords_string (box, 1);
273         repr->setAttribute("inkscape:box3dcornerB", str);
275         str = sp_3dbox_get_corner_coords_string (box, 5);
276         repr->setAttribute("inkscape:box3dcornerC", str);
278         str = sp_3dbox_get_perspective_string (box);
279         repr->setAttribute("inkscape:perspective", str);
280         sp_3dbox_set_ratios (box);
282         g_free ((void *) str);
284         /* store center and construction-corners for later use during center-dragging */
285         NR::Maybe<NR::Point> cen = sp_3dbox_get_center (box);
286         if (cen) {
287             box->old_center = *cen;
288         }
289         box->old_corner2 = box->corners[2];
290         box->old_corner1 = box->corners[1];
291         box->old_corner0 = box->corners[0];
292         box->old_corner3 = box->corners[3];
293         box->old_corner5 = box->corners[5];
294         box->old_corner7 = box->corners[7];
295     }
297     if (((SPObjectClass *) (parent_class))->write) {
298         ((SPObjectClass *) (parent_class))->write(object, repr, flags);
299     }
301     return repr;
304 static gchar *
305 sp_3dbox_description(SPItem *item)
307     g_return_val_if_fail(SP_IS_3DBOX(item), NULL);
309     return g_strdup(_("<b>3D Box</b>"));
312 void sp_3dbox_set_ratios (SP3DBox *box, Box3D::Axis axes)
314     Box3D::Perspective3D *persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
315     NR::Point pt;
317     if (axes & Box3D::X) {
318         pt = persp->get_vanishing_point (Box3D::X)->get_pos();
319         box->ratio_x = NR::L2 (pt - box->corners[2]) / NR::L2 (pt - box->corners[3]);
320     }
322     if (axes & Box3D::Y) {
323         pt = persp->get_vanishing_point (Box3D::Y)->get_pos();
324         box->ratio_y = NR::L2 (pt - box->corners[2]) / NR::L2 (pt - box->corners[0]);
325     }
327     if (axes & Box3D::Z) {
328         pt = persp->get_vanishing_point (Box3D::Z)->get_pos();
329         box->ratio_z = NR::L2 (pt - box->corners[4]) / NR::L2 (pt - box->corners[0]);
330     }
333 void
334 sp_3dbox_switch_front_face (SP3DBox *box, Box3D::Axis axis)
336     if (SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point (axis)->is_finite()) {
337         box->front_bits = box->front_bits ^ axis;
338     }
342 void
343 sp_3dbox_position_set (SP3DBoxContext &bc)
345     SP3DBox *box3d = SP_3DBOX(bc.item);
347     sp_3dbox_set_shape(box3d);
349     // FIXME: Why does the following call not automatically update the children
350     //        of box3d (which is an SPGroup, which should do this)?
351     //SP_OBJECT(box3d)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
353     /**
354     SP_OBJECT(box3d->path_face1)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
355     SP_OBJECT(box3d->path_face2)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
356     SP_OBJECT(box3d->path_face3)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
357     SP_OBJECT(box3d->path_face4)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
358     SP_OBJECT(box3d->path_face5)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
359     SP_OBJECT(box3d->path_face6)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
360     ***/
363 static void
364 sp_3dbox_set_shape_from_points (SP3DBox *box, NR::Point const &cornerA, NR::Point const &cornerB, NR::Point const &cornerC)
366     sp_3dbox_recompute_corners (box, cornerA, cornerB, cornerC);
368     // FIXME: How to handle other contexts???
369     // FIXME: Is tools_isactive(..) more recommended to check for the current context/tool?
370     if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
371         return;
372     SP3DBoxContext *bc = SP_3DBOX_CONTEXT(inkscape_active_event_context());
374     if (bc->extruded) {
375         box->faces[0]->set_corners (box->corners[0], box->corners[4], box->corners[6], box->corners[2]);
376         box->faces[1]->set_corners (box->corners[1], box->corners[5], box->corners[7], box->corners[3]);
377         box->faces[2]->set_corners (box->corners[0], box->corners[1], box->corners[5], box->corners[4]);
378         box->faces[3]->set_corners (box->corners[2], box->corners[3], box->corners[7], box->corners[6]);
379         box->faces[5]->set_corners (box->corners[4], box->corners[5], box->corners[7], box->corners[6]);
380     }
381     box->faces[4]->set_corners (box->corners[0], box->corners[1], box->corners[3], box->corners[2]);
383     sp_3dbox_update_curves (box);
386 void
387 // FIXME: Note that this is _not_ the virtual set_shape() method inherited from SPShape,
388 //        since SP3DBox is inherited from SPGroup. The following method is "artificially"
389 //        called from sp_3dbox_update().
390 //sp_3dbox_set_shape(SPShape *shape)
391 sp_3dbox_set_shape(SP3DBox *box, bool use_previous_corners)
393     if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
394         return;
395     SP3DBoxContext *bc = SP_3DBOX_CONTEXT(inkscape_active_event_context());
397     if (!use_previous_corners) {
398         sp_3dbox_set_shape_from_points (box, bc->drag_origin, bc->drag_ptB, bc->drag_ptC);
399     } else {
400         sp_3dbox_set_shape_from_points (box, box->corners[2], box->corners[1], box->corners[5]);
401     }
405 void sp_3dbox_recompute_corners (SP3DBox *box, NR::Point const A, NR::Point const B, NR::Point const C)
407     sp_3dbox_move_corner_in_XY_plane (box, 2, A);
408     sp_3dbox_move_corner_in_XY_plane (box, 1, B);
409     sp_3dbox_move_corner_in_Z_direction (box, 5, C);
412 inline static double
413 normalized_angle (double angle) {
414     if (angle < -M_PI) {
415         return angle + 2*M_PI;
416     } else if (angle > M_PI) {
417         return angle - 2*M_PI;
418     }
419     return angle;
422 static gdouble
423 sp_3dbox_corner_angle_to_VP (SP3DBox *box, Box3D::Axis axis, guint extreme_corner)
425     Box3D::VanishingPoint *vp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point (axis);
426     NR::Point dir;
428     if (vp->is_finite()) {
429         dir = NR::unit_vector (vp->get_pos() - box->corners[extreme_corner]);
430     } else {
431         dir = NR::unit_vector (vp->v_dir);
432     }
434     return atan2 (dir[NR::Y], dir[NR::X]);
438 bool sp_3dbox_recompute_z_orders (SP3DBox *box)
440     gint new_z_orders[6];
442     // TODO: Determine the front corner depending on the distance from VPs and/or the user presets
443     guint front_corner = sp_3dbox_get_front_corner_id (box);
445     gdouble dir_1x = sp_3dbox_corner_angle_to_VP (box, Box3D::X, front_corner);
446     gdouble dir_3x = sp_3dbox_corner_angle_to_VP (box, Box3D::X, front_corner ^ Box3D::Y);
448     gdouble dir_1y = sp_3dbox_corner_angle_to_VP (box, Box3D::Y, front_corner);
449     //gdouble dir_0y = sp_3dbox_corner_angle_to_VP (box, Box3D::Y, front_corner ^ Box3D::X);
451     gdouble dir_1z = sp_3dbox_corner_angle_to_VP (box, Box3D::Z, front_corner);
452     gdouble dir_3z = sp_3dbox_corner_angle_to_VP (box, Box3D::Z, front_corner ^ Box3D::Y);
454     // Still not perfect, but only fails in some rather degenerate cases.
455     // I suspect that there is a more elegant model, though. :)
456     new_z_orders[0] = Box3D::face_containing_corner (Box3D::XY, front_corner);
457     if (normalized_angle (dir_1y - dir_1z) > 0) {
458         new_z_orders[1] = Box3D::face_containing_corner (Box3D::YZ, front_corner);
459         if (normalized_angle (dir_1x - dir_1z) > 0) {
460             new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner ^ Box3D::Y);
461         } else {
462             new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
463         }
464     } else {
465         if (normalized_angle (dir_3x - dir_3z) > 0) {
466             new_z_orders[1] = Box3D::face_containing_corner (Box3D::XZ, front_corner ^ Box3D::Y);
467             new_z_orders[2] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
468         } else {
469             if (normalized_angle (dir_1x - dir_1z) > 0) {
470                 new_z_orders[1] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
471                 new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
472             } else {
473                 new_z_orders[1] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
474                 new_z_orders[2] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
475             }
476         }
477     }
479     new_z_orders[3] = Box3D::opposite_face (new_z_orders[2]);
480     new_z_orders[4] = Box3D::opposite_face (new_z_orders[1]);
481     new_z_orders[5] = Box3D::opposite_face (new_z_orders[0]);
483     /* We only need to look for changes among the topmost three faces because the order
484        of the other ones is just inverted. */
485     if ((box->z_orders[0] != new_z_orders[0]) ||
486         (box->z_orders[1] != new_z_orders[1]) ||
487         (box->z_orders[2] != new_z_orders[2]))
488     {
489         for (int i = 0; i < 6; ++i) {
490             box->z_orders[i] = new_z_orders[i];
491         }
492         return true;
493     }
495     return false;
498 // convenience
499 static bool sp_3dbox_is_subset_or_superset (std::vector<gint> const &list1, std::vector<gint> const &list2)
501     return (std::includes (list1.begin(), list1.end(), list2.begin(), list2.end()) ||
502             std::includes (list2.begin(), list2.end(), list1.begin(), list1.end()));
505 static bool sp_3dbox_differ_by_opposite_faces (std::vector<gint> const &list1, std::vector<gint> const &list2)
507     std::vector<gint> diff1;
508     std::vector<gint> diff2;
509     std::set_difference (list1.begin(), list1.end(), list2.begin(), list2.end(),
510                          std::insert_iterator<std::vector<gint> >(diff1, diff1.begin()));
511     std::set_difference (list2.begin(), list2.end(), list1.begin(), list1.end(),
512                          std::insert_iterator<std::vector<gint> >(diff2, diff2.begin()));
514     if (diff1.size() == 3 || diff1.size() != diff2.size())
515         return false;
517     for (guint i = 0; i < diff1.size(); ++i) {
518         if (std::find (diff2.begin(), diff2.end(), Box3D::opposite_face (diff1[i])) == diff2.end()) {
519             return false;
520         }
521     }
522     return true;
525 static gint
526 sp_3dbox_face_containing_diagonal_corners (guint corner1, guint corner2)
528     Box3D::Axis plane = (Box3D::Axis) (corner1 ^ corner2);
529     if (!Box3D::is_plane (plane)) {
530         g_warning ("Corners %d and %d should span a plane.\n", corner1, corner2);
531         return 0;
532     }
534     return Box3D::face_containing_corner (plane, corner1);
537 static std::vector<gint> sp_3dbox_adjacent_faces_of_edge (guint corner1, guint corner2) {
538     std::vector<gint> adj_faces;
539     Box3D::Axis edge = (Box3D::Axis) (corner1 ^ corner2);
540     if (!Box3D::is_single_axis_direction (edge)) {
541         return adj_faces;
542     }
544     Box3D::Axis plane = Box3D::orth_plane_or_axis (edge);
545     Box3D::Axis axis1 = Box3D::extract_first_axis_direction (plane);
546     Box3D::Axis axis2 = Box3D::extract_second_axis_direction (plane);
547     adj_faces.push_back (Box3D::face_containing_corner ((Box3D::Axis) (edge ^ axis1), corner1));
548     adj_faces.push_back (Box3D::face_containing_corner ((Box3D::Axis) (edge ^ axis2), corner1));
549     return adj_faces;
552 static std::vector<gint> sp_3dbox_faces_meeting_in_corner (guint corner) {
553     std::vector<gint> faces;
554     for (int i = 0; i < 3; ++i) {
555         faces.push_back (sp_3dbox_face_containing_diagonal_corners (corner, corner ^ Box3D::planes[i]));
556     }
557     return faces;
560 static void sp_3dbox_remaining_faces (std::vector<gint> const &faces, std::vector<gint> &rem_faces)
562     rem_faces.clear();
563     for (gint i = 0; i < 6; ++i) {
564         if (std::find (faces.begin(), faces.end(), i) == faces.end()) {
565             rem_faces.push_back (i);
566         }
567     }
570 /*
571  * Given two adjacent edges (\a c2,\a c1) and (\a c2, \a c3) of \a box (with common corner \a c2),
572  * check whether both lie on the convex hull of the point configuration given by \a box's corners.
573  */
574 static bool
575 sp_3dbox_is_border_edge_pair (SP3DBox *box, guint const c1, guint const c2, guint const c3)
577     Box3D::Axis edge21 = (Box3D::Axis) (c2 ^ c1);
578     Box3D::Axis edge23 = (Box3D::Axis) (c2 ^ c3);
579     Box3D::Axis rear_axis = Box3D::orth_plane_or_axis ((Box3D::Axis) (edge21 ^ edge23));
580  
581     NR::Point corner2 = box->corners[c2];
582     NR::Point dir21 = box->corners[c1] - corner2;
583     NR::Point dir23 = box->corners[c3] - corner2;
585     if (!Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ edge21 ^ edge23] - corner2) ||
586         !Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ rear_axis] - corner2) ||
587         !Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ rear_axis ^ edge21] - corner2) ||
588         !Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ rear_axis ^ edge21 ^ edge23] - corner2) ||
589         !Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ rear_axis ^ edge23] - corner2)) {
590         // corner triple c1, c2, c3 doesn't bound the convex hull
591         return false;
592     }
593     // corner triple c1, c2, c3 bounds the convex hull
594     return true;    
597 /*
598  * Test whether there are any adjacent corners of \a corner (i.e., connected with it along one of the axes)
599  * such that the corresponding edges bound the convex hull of the box (as a point configuration in the plane)
600  * If this is the case, return the corresponding two adjacent corners; otherwise return (-1, -1).
601  */
602 static Box3D::Axis
603 sp_3dbox_axis_pair_bounding_convex_hull (SP3DBox *box, guint corner)
604  {
605     guint adj1 = corner ^ Box3D::X;
606     guint adj2 = corner ^ Box3D::Y;
607     guint adj3 = corner ^ Box3D::Z;
609     if (sp_3dbox_is_border_edge_pair (box, adj1, corner, adj2)) {
610         return Box3D::XY;
611     }
612     if (sp_3dbox_is_border_edge_pair (box, adj1, corner, adj3)) {
613         return Box3D::XZ;
614     }
615     if (sp_3dbox_is_border_edge_pair (box, adj2, corner, adj3)) {
616         return Box3D::YZ;
617     }
618     return Box3D::NONE;
621 // inside_hull is modified 'in place' by the following function
622 static void sp_3dbox_corner_configuration (SP3DBox *box, std::vector<gint> &on_hull, std::vector<gint> &inside_hull)
624     for (int i = 0; i < 8; ++i) {
625         Box3D::Axis bounding_edges = sp_3dbox_axis_pair_bounding_convex_hull (box, i);
626         if (bounding_edges != Box3D::NONE) {
627             on_hull.push_back (i);
628         } else {
629             inside_hull.push_back (i);
630         }
631     }
634 /* returns true if there was a change in the z-orders (which triggers an update of the repr) */
635 static bool sp_3dbox_recompute_z_orders_by_corner_configuration (SP3DBox *box)
637     gint new_z_orders[6];
638     Box3D::Axis front_rear_axis = Box3D::Z;
640     std::vector<gint> on_hull;
641     std::vector<gint> inside_hull;
642     std::vector<gint> visible_faces;
644     sp_3dbox_corner_configuration (box, on_hull, inside_hull);
646     switch (on_hull.size()) {
647         case 4:
648             {
649                 // the following works because on_hull is sorted
650                 gint front_face = sp_3dbox_face_containing_diagonal_corners (on_hull[0], on_hull[3]);
651                 visible_faces.push_back (front_face);
652             }
653             break;
655         case 6:
656         {
657             guint c1 = inside_hull[0] ^ Box3D::XYZ;
658             guint c2 = inside_hull[1] ^ Box3D::XYZ;
659             Box3D::Axis edge = (Box3D::Axis) (c1 ^ c2);
660             if (Box3D::is_single_axis_direction (edge)) {
661                 visible_faces = sp_3dbox_adjacent_faces_of_edge (c1, c2);
662             } else if (c1 == c2 ^ Box3D::XYZ) {
663                 guint c_cmp = sp_3dbox_get_corner_id_along_edge (box, 0, front_rear_axis, Box3D::FRONT);
664                 guint visible_front_corner = (((c_cmp & front_rear_axis) == (c1 & front_rear_axis)) ? c1 : c2);
665                 visible_faces = sp_3dbox_faces_meeting_in_corner (visible_front_corner);
666             } else {
667                 /* Under what conditions do we end up here? Can we safely ignore this case? */
668                 return false;
669             }
670             break;
671         }
673         default:
674             /* Under what conditions do we end up here? Can we safely ignore this case? */
675             return false;
676     }
678     /* catch weird corner configurations; these should be theoretically impossible, but maybe
679        occur in (almost) degenerate cases due to rounding errors, for example */
680     if (std::find (visible_faces.begin(), visible_faces.end(), -1) != visible_faces.end()) {
681         return false;
682     }
684     /* sort the list of visible faces for later use (although it may be already sorted anyway) */
685     std::sort (visible_faces.begin(), visible_faces.end());
687     std::vector<gint> invisible_faces;
688     sp_3dbox_remaining_faces (visible_faces, invisible_faces);
691     if (!sp_3dbox_is_subset_or_superset (visible_faces, box->currently_visible_faces) &&
692         !sp_3dbox_differ_by_opposite_faces (visible_faces, box->currently_visible_faces)) {
693         std::swap (visible_faces, invisible_faces);
694         if (!sp_3dbox_is_subset_or_superset (visible_faces, box->currently_visible_faces) &&
695             !sp_3dbox_differ_by_opposite_faces (visible_faces, box->currently_visible_faces)) {
696             /* Hopefully this case is only caused by rounding errors or something similar;
697                does it need further investigation? */
698             return false;
699         }
700     }
702     box->currently_visible_faces = visible_faces;
704     // set new z-orders according to the visible/invisible faces
705     guint vis_size = visible_faces.size();
706     for (guint i = 0; i < vis_size; ++i) {
707         new_z_orders[i] = visible_faces[i];
708     }
709     for (guint i = 0; i < invisible_faces.size(); ++i) {
710         new_z_orders[vis_size + i] = invisible_faces[i];
711     }
713     // test whether any z-orders actually changed and indicate this in the return status
714     for (int i = 0; i < 6; ++i) {
715         if (box->z_orders[i] != new_z_orders[i]) {
716             // we update the z-orders starting from the index where the change occurs
717             for (int j = i; j < 6; ++j) {
718                 box->z_orders[j] = new_z_orders[j];
719             }
720             return true;
721         }
722     }
723     return false;
726 // FIXME: Can we unify this and the next function for setting the z-orders?
727 void sp_3dbox_set_z_orders_in_the_first_place (SP3DBox *box)
729     // For efficiency reasons, we only set the new z-orders if something really changed
730     if (sp_3dbox_recompute_z_orders (box)) {
731         box->faces[box->z_orders[0]]->lower_to_bottom ();
732         box->faces[box->z_orders[1]]->lower_to_bottom ();
733         box->faces[box->z_orders[2]]->lower_to_bottom ();
734         box->faces[box->z_orders[3]]->lower_to_bottom ();
735         box->faces[box->z_orders[4]]->lower_to_bottom ();
736         box->faces[box->z_orders[5]]->lower_to_bottom ();
737     }
740 void sp_3dbox_set_z_orders_later_on (SP3DBox *box)
742     // For efficiency reasons, we only set the new z-orders if something really changed
743     if (sp_3dbox_recompute_z_orders_by_corner_configuration (box)) {
744         box->faces[box->z_orders[0]]->lower_to_bottom ();
745         box->faces[box->z_orders[1]]->lower_to_bottom ();
746         box->faces[box->z_orders[2]]->lower_to_bottom ();
747         box->faces[box->z_orders[3]]->lower_to_bottom ();
748         box->faces[box->z_orders[4]]->lower_to_bottom ();
749         box->faces[box->z_orders[5]]->lower_to_bottom ();
750     }
753 void
754 sp_3dbox_update_curves (SP3DBox *box) {
755     for (int i = 0; i < 6; ++i) {
756         if (box->faces[i]) box->faces[i]->set_curve();
757     }
760 /**
761  * In some situations (e.g., after cloning boxes, undo & redo, or reading boxes from a file) there are
762  * paths already present in the document which correspond to the faces of newly created boxes, but their
763  * 'path' members don't link to them yet. The following function corrects this if necessary.
764  */
765 void
766 sp_3dbox_link_to_existing_paths (SP3DBox *box, Inkscape::XML::Node *repr) {
767     // TODO: We should probably destroy the existing paths and recreate them because we don't know
768     //       precisely which path corresponds to which face. Does this make a difference?
769     //       In sp_3dbox_write we write the correct paths anyway, don't we? But we could get into
770     //       trouble at a later stage when we only write single faces for degenerate boxes.
772     SPDocument *document = SP_OBJECT_DOCUMENT(box);
773     guint face_id = 0;
775     for (Inkscape::XML::Node *i = sp_repr_children(repr); i != NULL; i = sp_repr_next(i)) {
776         if (face_id > 5) {
777             g_warning ("SVG representation of 3D boxes must contain 6 paths or less.\n");
778             break;
779         }
781         SPObject *face_object = document->getObjectByRepr((Inkscape::XML::Node *) i);
782         if (!SP_IS_PATH(face_object)) {
783             g_warning ("SVG representation of 3D boxes should only contain paths.\n");
784             continue;
785         }
786         // TODO: Currently we don't check whether all paths are being linked to different faces.
787         //       This is no problem with valid SVG files. It may lead to crashes, however,
788         //       in case a file is corrupt (e.g., two or more faces have identical descriptions).
789         gint id = Box3DFace::descr_to_id (i->attribute ("inkscape:box3dface"));
790         box->faces[id]->hook_path_to_3dbox(SP_PATH(face_object));
791         ++face_id;
792     }
793     if (face_id < 6) {
794         //g_warning ("SVG representation of 3D boxes should contain exactly 6 paths (degenerate boxes are not yet supported).\n");
795         // TODO: Check whether it is safe to add the remaining paths to the box and do so in case it is.
796         //       (But we also land here for newly created boxes where we shouldn't add any paths because
797         //       This is done in sp_3dbox_write later on.
798     }
801 void
802 sp_3dbox_move_corner_in_XY_plane (SP3DBox *box, guint id, NR::Point pt, Box3D::Axis axes)
804     Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
806     NR::Point A (box->corners[id ^ Box3D::XY]);
807     if (Box3D::is_single_axis_direction (axes)) {
808         pt = Box3D::PerspectiveLine (box->corners[id], axes, persp).closest_to(pt);
809     }
811     /* set the 'front' corners */
812     box->corners[id] = pt;
814     Box3D::PerspectiveLine pl_one (A, Box3D::Y, persp);
815     Box3D::PerspectiveLine pl_two (pt, Box3D::X, persp);
816     box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
818     pl_one = Box3D::PerspectiveLine (A, Box3D::X, persp);
819     pl_two = Box3D::PerspectiveLine (pt, Box3D::Y, persp);
820     box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
822     /* set the 'rear' corners */
823     NR::Point B (box->corners[id ^ Box3D::XYZ]);
825     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Z, persp);
826     pl_two = Box3D::PerspectiveLine (B, Box3D::Y, persp);
827     box->corners[id ^ Box3D::XZ] = pl_one.meet(pl_two);
829     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XZ], Box3D::X, persp);
830     pl_two = Box3D::PerspectiveLine (pt, Box3D::Z, persp);
831     box->corners[id ^ Box3D::Z] = pl_one.meet(pl_two);
833     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::Z], Box3D::Y, persp);
834     pl_two = Box3D::PerspectiveLine (B, Box3D::X, persp);
835     box->corners[id ^ Box3D::YZ] = pl_one.meet(pl_two);
836     
839 void
840 sp_3dbox_move_corner_in_Z_direction (SP3DBox *box, guint id, NR::Point pt, bool constrained)
842     if (!constrained) sp_3dbox_move_corner_in_XY_plane (box, id, pt, Box3D::XY);
844     Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
846     /* set the four corners of the face containing corners[id] */
847     box->corners[id] = Box3D::PerspectiveLine (box->corners[id], Box3D::Z, persp).closest_to(pt);
849     Box3D::PerspectiveLine pl_one (box->corners[id], Box3D::X, persp);
850     Box3D::PerspectiveLine pl_two (box->corners[id ^ Box3D::XZ], Box3D::Z, persp);
851     box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
853     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Y, persp);
854     pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XYZ], Box3D::Z, persp);
855     box->corners[id ^ Box3D::XY] = pl_one.meet(pl_two);
857     pl_one = Box3D::PerspectiveLine (box->corners[id], Box3D::Y, persp);
858     pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::YZ], Box3D::Z, persp);
859     box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
862 static void
863 sp_3dbox_reshape_edge_after_VP_toggling (SP3DBox *box, const guint corner, const Box3D::Axis axis, Box3D::Perspective3D *persp)
865     /* Hmm, perhaps we should simply use one of the corners as the pivot point.
866        But this way we minimize the amount of reshaping.
867        On second thought, we need to find a way to ensure that all boxes sharing the same
868        perspective are updated consistently _as a group_. That is, they should also retain
869        their relative positions towards each other. */
870     NR::Maybe<NR::Point> pt = sp_3dbox_get_midpoint_between_corners (box, corner, corner ^ axis);
871     g_return_if_fail (pt);
873     Box3D::Axis axis2 = ((axis == Box3D::Y) ? Box3D::X : Box3D::Y);
875     Box3D::PerspectiveLine line1 (box->corners[corner], axis2, persp);
876     Box3D::PerspectiveLine line2 (box->corners[corner ^ axis], axis2, persp);
878     Box3D::PerspectiveLine line3 (*pt, axis, persp);
880     NR::Point new_corner1 = line1.meet (line3);
881     NR::Point new_corner2 = line2.meet (line3);
883     box->corners[corner] = new_corner1;
884     box->corners[corner ^ axis] = new_corner2;
887 void
888 sp_3dbox_reshape_after_VP_toggling (SP3DBox *box, Box3D::Axis axis)
890     Box3D::Perspective3D *persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
891     std::pair<Box3D::Axis, Box3D::Axis> dirs = Box3D::get_remaining_axes (axis);
893     sp_3dbox_reshape_edge_after_VP_toggling (box, 0, axis, persp);
894     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first, axis, persp);
895     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first ^ dirs.second, axis, persp);
896     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.second, axis, persp);
899 NR::Maybe<NR::Point>
900 sp_3dbox_get_center (SP3DBox *box)
902     return sp_3dbox_get_midpoint_between_corners (box, 0, 7);
905 NR::Point
906 sp_3dbox_get_midpoint_in_axis_direction (NR::Point const &C, NR::Point const &D, Box3D::Axis axis, Box3D::Perspective3D *persp)
908     Box3D::PerspectiveLine pl (D, axis, persp);
909     return pl.pt_with_given_cross_ratio (C, D, -1.0);
912 // TODO: The following function can probably be rewritten in a much more elegant and robust way
913 //        by using projective coordinates for all points and using the cross ratio.
914 NR::Maybe<NR::Point>
915 sp_3dbox_get_midpoint_between_corners (SP3DBox *box, guint id_corner1, guint id_corner2)
917     Box3D::Axis corner_axes = (Box3D::Axis) (id_corner1 ^ id_corner2);
919     // Is all this sufficiently precise also for degenerate cases?
920     if (sp_3dbox_corners_are_adjacent (id_corner1, id_corner2)) {
921         Box3D::Axis orth_dir = get_perpendicular_axis_direction (corner_axes);
923         Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2 ^ orth_dir]);
924         Box3D::Line diag2 (box->corners[id_corner1 ^ orth_dir], box->corners[id_corner2]);
925         NR::Maybe<NR::Point> adjacent_face_center = diag1.intersect(diag2);
927         if (!adjacent_face_center) return NR::Nothing();
929         Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
931         Box3D::PerspectiveLine pl (*adjacent_face_center, orth_dir, persp);
932         return pl.intersect(Box3D::PerspectiveLine(box->corners[id_corner1], corner_axes, persp));
933     } else {
934         Box3D::Axis dir = Box3D::extract_first_axis_direction (corner_axes);
935         Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2]);
936         Box3D::Line diag2 (box->corners[id_corner1 ^ dir], box->corners[id_corner2 ^ dir]);
937         return diag1.intersect(diag2);
938     }
941 static gchar *
942 sp_3dbox_get_corner_coords_string (SP3DBox *box, guint id)
944     id = id % 8;
945     Inkscape::SVGOStringStream os;
946     os << box->corners[id][NR::X] << "," << box->corners[id][NR::Y];
947     return g_strdup(os.str().c_str());
950 static std::pair<gdouble, gdouble>
951 sp_3dbox_get_coord_pair_from_string (const gchar *coords)
953     gchar **coordpair = g_strsplit( coords, ",", 0);
954     // We might as well rely on g_ascii_strtod to convert the NULL pointer to 0.0,
955     // but we include the following test anyway
956     if (coordpair[0] == NULL || coordpair[1] == NULL) {
957         g_strfreev (coordpair);
958         g_warning ("Coordinate conversion failed.\n");
959         return std::make_pair(0.0, 0.0);
960     }
962     gdouble coord1 = g_ascii_strtod(coordpair[0], NULL);
963     gdouble coord2 = g_ascii_strtod(coordpair[1], NULL);
964     g_strfreev (coordpair);
966     return std::make_pair(coord1, coord2);
969 static gchar *
970 sp_3dbox_get_perspective_string (SP3DBox *box)
972     
973     return sp_3dbox_get_svg_descr_of_persp (SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box));
975   
976 gchar *
977 sp_3dbox_get_svg_descr_of_persp (Box3D::Perspective3D *persp)
979     // FIXME: We should move this code to perspective3d.cpp, but this yields compiler errors. Why?
980     Inkscape::SVGOStringStream os;
982     Box3D::VanishingPoint vp = *(persp->get_vanishing_point (Box3D::X));
983     os << vp[NR::X] << "," << vp[NR::Y] << ",";
984     os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
985     if (vp.is_finite()) {
986         os << "finite,";
987     } else {
988         os << "infinite,";
989     }
991     vp = *(persp->get_vanishing_point (Box3D::Y));
992     os << vp[NR::X] << "," << vp[NR::Y] << ",";
993     os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
994     if (vp.is_finite()) {
995         os << "finite,";
996     } else {
997         os << "infinite,";
998     }
1000     vp = *(persp->get_vanishing_point (Box3D::Z));
1001     os << vp[NR::X] << "," << vp[NR::Y] << ",";
1002     os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
1003     if (vp.is_finite()) {
1004         os << "finite";
1005     } else {
1006         os << "infinite";
1007     }
1009     return g_strdup(os.str().c_str());
1012 // auxiliary function
1013 static std::pair<NR::Point, NR::Point>
1014 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)
1016     double cr1 = Box3D::cross_ratio (*persp->get_vanishing_point (axis), M0, M, A);
1017     double cr2 = Box3D::cross_ratio (*persp->get_vanishing_point (axis), M, B, M0);
1018     if (fabs (cr1 - 1) < Box3D::epsilon) {
1019         // FIXME: cr == 1 is a degenerate case; how should we deal with it?
1020         return std::make_pair (NR::Point (0,0), NR::Point (0,0));
1021     }
1022     Box3D::PerspectiveLine pl (M0, axis, persp);
1023     NR::Point B_new = pl.pt_with_given_cross_ratio (M0, M, cr1 / (cr1 - 1));
1024     NR::Point A_new = pl.pt_with_given_cross_ratio (M0, M, 1 - cr2);
1025     return std::make_pair (A_new, B_new);
1028 void sp_3dbox_recompute_Z_corners_from_new_center (SP3DBox *box, NR::Point const new_center)
1030     // TODO: Clean this function up
1032     Box3D::Perspective3D *persp = sp_desktop_document (inkscape_active_desktop())->get_persp_of_box (box);
1033     NR::Point old_center = box->old_center;
1035     Box3D::PerspectiveLine aux_line1 (old_center, Box3D::Z, persp);
1036     Box3D::PerspectiveLine aux_line2 (new_center, Box3D::Y, persp);
1037     NR::Point Z1 = aux_line1.meet (aux_line2);
1039     NR::Point A0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner2, box->old_corner0, Box3D::Y, persp));
1040     NR::Point B0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner7, box->old_corner5, Box3D::Y, persp));
1041     Box3D::PerspectiveLine aux_line3 (A0, Box3D::X, persp);
1042     Box3D::PerspectiveLine aux_line4 (B0, Box3D::X, persp);
1044     NR::Point C0 = aux_line3.meet (aux_line1);
1045     NR::Point D0 = aux_line4.meet (aux_line1);
1047     std::pair<NR::Point, NR::Point> new_midpts = sp_3dbox_new_midpoints (persp, Box3D::Z, old_center, Z1, C0, D0);
1048     NR::Point C1 (new_midpts.first);
1049     NR::Point D1 (new_midpts.second);
1050     Box3D::PerspectiveLine aux_line5 (C1, Box3D::X, persp);
1051     Box3D::PerspectiveLine aux_line6 (D1, Box3D::X, persp);
1053     Box3D::PerspectiveLine aux_line7 (A0, Box3D::Z, persp);
1054     Box3D::PerspectiveLine aux_line8 (B0, Box3D::Z, persp);
1056     NR::Point A1 = aux_line5.meet (aux_line7);
1057     NR::Point B1 = aux_line6.meet (aux_line8);
1059     Box3D::PerspectiveLine aux_line9  (box->old_corner2, Box3D::Z, persp);
1060     Box3D::PerspectiveLine aux_line10 (box->old_corner5, Box3D::Z, persp);
1062     Box3D::PerspectiveLine aux_line11 (A1, Box3D::Y, persp);
1063     Box3D::PerspectiveLine aux_line12 (B1, Box3D::Y, persp);
1065     NR::Point new_corner2 = aux_line9.meet (aux_line11);
1066     NR::Point new_corner5 = aux_line10.meet (aux_line12);
1068     Box3D::PerspectiveLine aux_line13 (A1, Box3D::X, persp);
1069     NR::Point E1 = aux_line13.meet (aux_line8);
1070     Box3D::PerspectiveLine aux_line14 (E1, Box3D::Y, persp);
1072     NR::Point new_corner1 = aux_line10.meet (aux_line14);
1074     sp_3dbox_set_shape_from_points (box, new_corner2, new_corner1, new_corner5);
1077 void sp_3dbox_recompute_XY_corners_from_new_center (SP3DBox *box, NR::Point const new_center)
1079     // TODO: Clean this function up
1081     Box3D::Perspective3D *persp = sp_desktop_document (inkscape_active_desktop())->get_persp_of_box (box);
1082     NR::Point old_center = box->old_center;
1084     NR::Point A0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner2, box->old_corner0, Box3D::Y, persp));
1085     NR::Point B0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner1, box->old_corner3, Box3D::Y, persp));
1087     /* we first move the box along the X-axis ... */
1088     Box3D::PerspectiveLine aux_line1 (old_center, Box3D::X, persp);
1089     Box3D::PerspectiveLine aux_line2 (new_center, Box3D::Y, persp);
1090     NR::Point Z1 = aux_line1.meet (aux_line2);
1092     Box3D::PerspectiveLine ref_line (B0, Box3D::X, persp);
1093     Box3D::PerspectiveLine pline2 (old_center, Box3D::Z, persp);
1094     Box3D::PerspectiveLine pline3 (Z1, Box3D::Z, persp);
1095     NR::Point M0 = ref_line.meet (pline2);
1096     NR::Point M1 = ref_line.meet (pline3);
1098     std::pair<NR::Point, NR::Point> new_midpts = sp_3dbox_new_midpoints (persp, Box3D::X, M0, M1, A0, B0);
1099     NR::Point A1 (new_midpts.first);
1100     NR::Point B1 (new_midpts.second);
1102     /* ... and then along the Y-axis */
1103     Box3D::PerspectiveLine pline4 (box->old_corner1, Box3D::X, persp);
1104     Box3D::PerspectiveLine pline5 (box->old_corner3, Box3D::X, persp);
1105     Box3D::PerspectiveLine aux_line3 (M1, Box3D::Y, persp);
1106     NR::Point C1 = aux_line3.meet (pline4);
1107     NR::Point D1 = aux_line3.meet (pline5);
1109     Box3D::PerspectiveLine aux_line4 (new_center, Box3D::Z, persp);
1110     NR::Point M2 = aux_line4.meet (aux_line3);
1112     std::pair<NR::Point, NR::Point> other_new_midpts = sp_3dbox_new_midpoints (persp, Box3D::Y, M1, M2, C1, D1);
1113     NR::Point C2 (other_new_midpts.first);
1114     NR::Point D2 (other_new_midpts.second);
1116     Box3D::PerspectiveLine plXC (C2, Box3D::X, persp);
1117     Box3D::PerspectiveLine plXD (D2, Box3D::X, persp);
1118     Box3D::PerspectiveLine plYA (A1, Box3D::Y, persp);
1119     Box3D::PerspectiveLine plYB (B1, Box3D::Y, persp);
1121     NR::Point new_corner2 (plXD.meet (plYA));
1122     NR::Point new_corner1 (plXC.meet (plYB));
1124     NR::Point tmp_corner1 (pline4.meet (plYB));
1125     Box3D::PerspectiveLine pline6 (box->old_corner5, Box3D::X, persp);
1126     Box3D::PerspectiveLine pline7 (tmp_corner1, Box3D::Z, persp);
1127     NR::Point tmp_corner5 (pline6.meet (pline7));
1129     Box3D::PerspectiveLine pline8 (tmp_corner5, Box3D::Y, persp);
1130     Box3D::PerspectiveLine pline9 (new_corner1, Box3D::Z, persp);
1131     NR::Point new_corner5 (pline8.meet (pline9));
1133     sp_3dbox_set_shape_from_points (box, new_corner2, new_corner1, new_corner5);
1136 void sp_3dbox_update_perspective_lines()
1138     SPEventContext *ec = inkscape_active_event_context();
1139     if (!SP_IS_3DBOX_CONTEXT (ec))
1140         return;
1142     SP_3DBOX_CONTEXT (ec)->_vpdrag->updateLines();
1145 /*
1146  * Manipulates corner1 through corner4 to contain the indices of the corners
1147  * from which the perspective lines in the direction of 'axis' emerge
1148  */
1149 void sp_3dbox_corners_for_perspective_lines (const SP3DBox * box, Box3D::Axis axis, 
1150                                              NR::Point &corner1, NR::Point &corner2, NR::Point &corner3, NR::Point &corner4)
1152     // along which axis to switch when takint
1153     Box3D::Axis switch_axis;
1154     if (axis == Box3D::X || axis == Box3D::Y) {
1155         switch_axis = (box->front_bits & axis) ? Box3D::Z : Box3D::NONE;
1156     } else {
1157         switch_axis = (box->front_bits & axis) ? Box3D::X : Box3D::NONE;
1158     }
1160     switch (axis) {
1161         case Box3D::X:
1162             corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
1163             corner2 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
1164             corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
1165             corner4 = sp_3dbox_get_corner_along_edge (box, 6 ^ switch_axis, axis, Box3D::REAR);
1166             break;
1167         case Box3D::Y:
1168             corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
1169             corner2 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
1170             corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
1171             corner4 = sp_3dbox_get_corner_along_edge (box, 5 ^ switch_axis, axis, Box3D::REAR);
1172             break;
1173         case Box3D::Z:
1174             corner1 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
1175             corner2 = sp_3dbox_get_corner_along_edge (box, 3 ^ switch_axis, axis, Box3D::REAR);
1176             corner3 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
1177             corner4 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
1178             break;
1179         default:
1180             // do nothing
1181             break;
1182     }            
1185 /**
1186  * Returns the id of the corner on the edge along 'axis' and passing through 'corner' that
1187  * lies on the front/rear face in this direction.
1188  */
1189 guint
1190 sp_3dbox_get_corner_id_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
1192     guint result;
1193     guint other_corner = corner ^ axis;
1194     Box3D::VanishingPoint *vp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point(axis);
1195     if (vp->is_finite()) {
1196         result = (  NR::L2 (vp->get_pos() - box->corners[corner])
1197                   < NR::L2 (vp->get_pos() - box->corners[other_corner]) ? other_corner : corner);
1198     } else {
1199         // clear the axis bit and switch to the appropriate corner along axis, depending on the value of front_bits
1200         result = ((corner & (0xF ^ axis)) ^ (box->front_bits & axis));
1201     }
1203     if (rel_pos == Box3D::FRONT) {
1204         return result;
1205     } else {
1206         return result ^ axis;
1207     }
1210 NR::Point
1211 sp_3dbox_get_corner_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
1213     return box->corners[sp_3dbox_get_corner_id_along_edge (box, corner, axis, rel_pos)];
1216 guint
1217 sp_3dbox_get_front_corner_id (const SP3DBox *box)
1219     guint front_corner = 1; // this could in fact be any corner, but we choose the one that is normally in front
1220     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::X, Box3D::FRONT);
1221     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Y, Box3D::FRONT);
1222     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Z, Box3D::FRONT);
1223     return front_corner;
1226 // auxiliary functions
1227 static void
1228 sp_3dbox_update_corner_with_value_from_svg (SPObject *object, guint corner_id, const gchar *value)
1230     if (value == NULL) return;
1231     SP3DBox *box = SP_3DBOX(object);
1233     std::pair<gdouble, gdouble> coord_pair = sp_3dbox_get_coord_pair_from_string (value);
1234     box->corners[corner_id] = NR::Point (coord_pair.first, coord_pair.second);
1235     sp_3dbox_recompute_corners (box, box->corners[2], box->corners[1], box->corners[5]);
1236     object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1239 static void
1240 sp_3dbox_update_perspective (Box3D::Perspective3D *persp, const gchar *value)
1242     // WARNING! This function changes the perspective associated to 'box'. Since there may be
1243     // many other boxes linked to the same perspective, their perspective is also changed.
1244     // If this behaviour is not desired in all cases, we need a different function.
1245     if (value == NULL) return;
1247     gchar **vps = g_strsplit( value, ",", 0);
1248     for (int i = 0; i < 15; ++i) {
1249         if (vps[i] == NULL) {
1250             g_warning ("Malformed svg attribute 'perspective'\n");
1251             return;
1252         }
1253     }
1255     persp->set_vanishing_point (Box3D::X, g_ascii_strtod (vps[0], NULL), g_ascii_strtod (vps[1], NULL),
1256                                           g_ascii_strtod (vps[2], NULL), g_ascii_strtod (vps[3], NULL),
1257                                           strcmp (vps[4], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
1258     persp->set_vanishing_point (Box3D::Y, g_ascii_strtod (vps[5], NULL), g_ascii_strtod (vps[6], NULL),
1259                                           g_ascii_strtod (vps[7], NULL), g_ascii_strtod (vps[8], NULL),
1260                                           strcmp (vps[9], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
1261     persp->set_vanishing_point (Box3D::Z, g_ascii_strtod (vps[10], NULL), g_ascii_strtod (vps[11], NULL),
1262                                           g_ascii_strtod (vps[12], NULL), g_ascii_strtod (vps[13], NULL),
1263                                           strcmp (vps[14], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
1265     // update the other boxes linked to the same perspective
1266     persp->reshape_boxes (Box3D::XYZ);
1269 /*
1270   Local Variables:
1271   mode:c++
1272   c-file-style:"stroustrup"
1273   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1274   indent-tabs-mode:nil
1275   fill-column:99
1276   End:
1277 */
1278 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :