Code

1942ea3d66494438522e0eebb575b3f4cf37282e
[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_reshape_after_VP_rotation (SP3DBox *box, Box3D::Axis axis)
804     Box3D::Perspective3D *persp = inkscape_active_document()->get_persp_of_box (box);
805     Box3D::VanishingPoint *vp = persp->get_vanishing_point (axis);
807     guint c1 = (axis == Box3D::Z) ? 1 : sp_3dbox_get_front_corner_id (box); // hack
808     guint c2 = c1 ^ axis;
809     NR::Point v = box->corners[c1] - box->corners[c2];
810     double dist = NR::L2 (v) * ((NR::dot (v, vp->v_dir) < 0) ? 1 : -1); // "directed" distance
812     Box3D::PerspectiveLine pline (box->corners[c1], axis, persp);
813     NR::Point pt = pline.point_from_lambda (dist);
815     sp_3dbox_move_corner_in_Z_direction (box, c2, pt, axis == Box3D::Z);
818 void
819 sp_3dbox_move_corner_in_XY_plane (SP3DBox *box, guint id, NR::Point pt, Box3D::Axis axes)
821     Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
823     NR::Point A (box->corners[id ^ Box3D::XY]);
824     if (Box3D::is_single_axis_direction (axes)) {
825         pt = Box3D::PerspectiveLine (box->corners[id], axes, persp).closest_to(pt);
826     }
828     /* set the 'front' corners */
829     box->corners[id] = pt;
831     Box3D::PerspectiveLine pl_one (A, Box3D::Y, persp);
832     Box3D::PerspectiveLine pl_two (pt, Box3D::X, persp);
833     box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
835     pl_one = Box3D::PerspectiveLine (A, Box3D::X, persp);
836     pl_two = Box3D::PerspectiveLine (pt, Box3D::Y, persp);
837     box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
839     /* set the 'rear' corners */
840     NR::Point B (box->corners[id ^ Box3D::XYZ]);
842     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Z, persp);
843     pl_two = Box3D::PerspectiveLine (B, Box3D::Y, persp);
844     box->corners[id ^ Box3D::XZ] = pl_one.meet(pl_two);
846     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XZ], Box3D::X, persp);
847     pl_two = Box3D::PerspectiveLine (pt, Box3D::Z, persp);
848     box->corners[id ^ Box3D::Z] = pl_one.meet(pl_two);
850     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::Z], Box3D::Y, persp);
851     pl_two = Box3D::PerspectiveLine (B, Box3D::X, persp);
852     box->corners[id ^ Box3D::YZ] = pl_one.meet(pl_two);
853     
856 void
857 sp_3dbox_move_corner_in_Z_direction (SP3DBox *box, guint id, NR::Point pt, bool constrained)
859     if (!constrained) sp_3dbox_move_corner_in_XY_plane (box, id, pt, Box3D::XY);
861     Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
863     /* set the four corners of the face containing corners[id] */
864     box->corners[id] = Box3D::PerspectiveLine (box->corners[id], Box3D::Z, persp).closest_to(pt);
866     Box3D::PerspectiveLine pl_one (box->corners[id], Box3D::X, persp);
867     Box3D::PerspectiveLine pl_two (box->corners[id ^ Box3D::XZ], Box3D::Z, persp);
868     box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
870     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Y, persp);
871     pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XYZ], Box3D::Z, persp);
872     box->corners[id ^ Box3D::XY] = pl_one.meet(pl_two);
874     pl_one = Box3D::PerspectiveLine (box->corners[id], Box3D::Y, persp);
875     pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::YZ], Box3D::Z, persp);
876     box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
879 static void
880 sp_3dbox_reshape_edge_after_VP_toggling (SP3DBox *box, const guint corner, const Box3D::Axis axis, Box3D::Perspective3D *persp)
882     /* Hmm, perhaps we should simply use one of the corners as the pivot point.
883        But this way we minimize the amount of reshaping.
884        On second thought, we need to find a way to ensure that all boxes sharing the same
885        perspective are updated consistently _as a group_. That is, they should also retain
886        their relative positions towards each other. */
887     NR::Maybe<NR::Point> pt = sp_3dbox_get_midpoint_between_corners (box, corner, corner ^ axis);
888     g_return_if_fail (pt);
890     Box3D::Axis axis2 = ((axis == Box3D::Y) ? Box3D::X : Box3D::Y);
892     Box3D::PerspectiveLine line1 (box->corners[corner], axis2, persp);
893     Box3D::PerspectiveLine line2 (box->corners[corner ^ axis], axis2, persp);
895     Box3D::PerspectiveLine line3 (*pt, axis, persp);
897     NR::Point new_corner1 = line1.meet (line3);
898     NR::Point new_corner2 = line2.meet (line3);
900     box->corners[corner] = new_corner1;
901     box->corners[corner ^ axis] = new_corner2;
904 void
905 sp_3dbox_reshape_after_VP_toggling (SP3DBox *box, Box3D::Axis axis)
907     Box3D::Perspective3D *persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
908     std::pair<Box3D::Axis, Box3D::Axis> dirs = Box3D::get_remaining_axes (axis);
910     sp_3dbox_reshape_edge_after_VP_toggling (box, 0, axis, persp);
911     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first, axis, persp);
912     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first ^ dirs.second, axis, persp);
913     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.second, axis, persp);
916 NR::Maybe<NR::Point>
917 sp_3dbox_get_center (SP3DBox *box)
919     return sp_3dbox_get_midpoint_between_corners (box, 0, 7);
922 NR::Point
923 sp_3dbox_get_midpoint_in_axis_direction (NR::Point const &C, NR::Point const &D, Box3D::Axis axis, Box3D::Perspective3D *persp)
925     Box3D::PerspectiveLine pl (D, axis, persp);
926     return pl.pt_with_given_cross_ratio (C, D, -1.0);
929 // TODO: The following function can probably be rewritten in a much more elegant and robust way
930 //        by using projective coordinates for all points and using the cross ratio.
931 NR::Maybe<NR::Point>
932 sp_3dbox_get_midpoint_between_corners (SP3DBox *box, guint id_corner1, guint id_corner2)
934     Box3D::Axis corner_axes = (Box3D::Axis) (id_corner1 ^ id_corner2);
936     // Is all this sufficiently precise also for degenerate cases?
937     if (sp_3dbox_corners_are_adjacent (id_corner1, id_corner2)) {
938         Box3D::Axis orth_dir = get_perpendicular_axis_direction (corner_axes);
940         Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2 ^ orth_dir]);
941         Box3D::Line diag2 (box->corners[id_corner1 ^ orth_dir], box->corners[id_corner2]);
942         NR::Maybe<NR::Point> adjacent_face_center = diag1.intersect(diag2);
944         if (!adjacent_face_center) return NR::Nothing();
946         Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
948         Box3D::PerspectiveLine pl (*adjacent_face_center, orth_dir, persp);
949         return pl.intersect(Box3D::PerspectiveLine(box->corners[id_corner1], corner_axes, persp));
950     } else {
951         Box3D::Axis dir = Box3D::extract_first_axis_direction (corner_axes);
952         Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2]);
953         Box3D::Line diag2 (box->corners[id_corner1 ^ dir], box->corners[id_corner2 ^ dir]);
954         return diag1.intersect(diag2);
955     }
958 static gchar *
959 sp_3dbox_get_corner_coords_string (SP3DBox *box, guint id)
961     id = id % 8;
962     Inkscape::SVGOStringStream os;
963     os << box->corners[id][NR::X] << "," << box->corners[id][NR::Y];
964     return g_strdup(os.str().c_str());
967 static std::pair<gdouble, gdouble>
968 sp_3dbox_get_coord_pair_from_string (const gchar *coords)
970     gchar **coordpair = g_strsplit( coords, ",", 0);
971     // We might as well rely on g_ascii_strtod to convert the NULL pointer to 0.0,
972     // but we include the following test anyway
973     if (coordpair[0] == NULL || coordpair[1] == NULL) {
974         g_strfreev (coordpair);
975         g_warning ("Coordinate conversion failed.\n");
976         return std::make_pair(0.0, 0.0);
977     }
979     gdouble coord1 = g_ascii_strtod(coordpair[0], NULL);
980     gdouble coord2 = g_ascii_strtod(coordpair[1], NULL);
981     g_strfreev (coordpair);
983     return std::make_pair(coord1, coord2);
986 static gchar *
987 sp_3dbox_get_perspective_string (SP3DBox *box)
989     
990     return sp_3dbox_get_svg_descr_of_persp (SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box));
992   
993 gchar *
994 sp_3dbox_get_svg_descr_of_persp (Box3D::Perspective3D *persp)
996     // FIXME: We should move this code to perspective3d.cpp, but this yields compiler errors. Why?
997     Inkscape::SVGOStringStream os;
999     Box3D::VanishingPoint vp = *(persp->get_vanishing_point (Box3D::X));
1000     os << vp[NR::X] << "," << vp[NR::Y] << ",";
1001     os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
1002     if (vp.is_finite()) {
1003         os << "finite,";
1004     } else {
1005         os << "infinite,";
1006     }
1008     vp = *(persp->get_vanishing_point (Box3D::Y));
1009     os << vp[NR::X] << "," << vp[NR::Y] << ",";
1010     os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
1011     if (vp.is_finite()) {
1012         os << "finite,";
1013     } else {
1014         os << "infinite,";
1015     }
1017     vp = *(persp->get_vanishing_point (Box3D::Z));
1018     os << vp[NR::X] << "," << vp[NR::Y] << ",";
1019     os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
1020     if (vp.is_finite()) {
1021         os << "finite";
1022     } else {
1023         os << "infinite";
1024     }
1026     return g_strdup(os.str().c_str());
1029 // auxiliary function
1030 static std::pair<NR::Point, NR::Point>
1031 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)
1033     double cr1 = Box3D::cross_ratio (*persp->get_vanishing_point (axis), M0, M, A);
1034     double cr2 = Box3D::cross_ratio (*persp->get_vanishing_point (axis), M, B, M0);
1035     if (fabs (cr1 - 1) < Box3D::epsilon) {
1036         // FIXME: cr == 1 is a degenerate case; how should we deal with it?
1037         return std::make_pair (NR::Point (0,0), NR::Point (0,0));
1038     }
1039     if (cr1 == NR_HUGE) {
1040         return std::make_pair (A, B);
1041     }
1042     Box3D::PerspectiveLine pl (M0, axis, persp);
1043     NR::Point B_new = pl.pt_with_given_cross_ratio (M0, M, cr1 / (cr1 - 1));
1044     NR::Point A_new = pl.pt_with_given_cross_ratio (M0, M, 1 - cr2);
1045     return std::make_pair (A_new, B_new);
1048 void sp_3dbox_recompute_Z_corners_from_new_center (SP3DBox *box, NR::Point const new_center)
1050     // TODO: Clean this function up
1052     Box3D::Perspective3D *persp = sp_desktop_document (inkscape_active_desktop())->get_persp_of_box (box);
1053     NR::Point old_center = box->old_center;
1055     Box3D::PerspectiveLine aux_line1 (old_center, Box3D::Z, persp);
1056     Box3D::PerspectiveLine aux_line2 (new_center, Box3D::Y, persp);
1057     NR::Point Z1 = aux_line1.meet (aux_line2);
1059     NR::Point A0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner2, box->old_corner0, Box3D::Y, persp));
1060     NR::Point B0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner7, box->old_corner5, Box3D::Y, persp));
1061     Box3D::PerspectiveLine aux_line3 (A0, Box3D::X, persp);
1062     Box3D::PerspectiveLine aux_line4 (B0, Box3D::X, persp);
1064     NR::Point C0 = aux_line3.meet (aux_line1);
1065     NR::Point D0 = aux_line4.meet (aux_line1);
1067     std::pair<NR::Point, NR::Point> new_midpts = sp_3dbox_new_midpoints (persp, Box3D::Z, old_center, Z1, C0, D0);
1068     NR::Point C1 (new_midpts.first);
1069     NR::Point D1 (new_midpts.second);
1070     Box3D::PerspectiveLine aux_line5 (C1, Box3D::X, persp);
1071     Box3D::PerspectiveLine aux_line6 (D1, Box3D::X, persp);
1073     Box3D::PerspectiveLine aux_line7 (A0, Box3D::Z, persp);
1074     Box3D::PerspectiveLine aux_line8 (B0, Box3D::Z, persp);
1076     NR::Point A1 = aux_line5.meet (aux_line7);
1077     NR::Point B1 = aux_line6.meet (aux_line8);
1079     Box3D::PerspectiveLine aux_line9  (box->old_corner2, Box3D::Z, persp);
1080     Box3D::PerspectiveLine aux_line10 (box->old_corner5, Box3D::Z, persp);
1082     Box3D::PerspectiveLine aux_line11 (A1, Box3D::Y, persp);
1083     Box3D::PerspectiveLine aux_line12 (B1, Box3D::Y, persp);
1085     NR::Point new_corner2 = aux_line9.meet (aux_line11);
1086     NR::Point new_corner5 = aux_line10.meet (aux_line12);
1088     Box3D::PerspectiveLine aux_line13 (A1, Box3D::X, persp);
1089     NR::Point E1 = aux_line13.meet (aux_line8);
1090     Box3D::PerspectiveLine aux_line14 (E1, Box3D::Y, persp);
1092     NR::Point new_corner1 = aux_line10.meet (aux_line14);
1094     sp_3dbox_set_shape_from_points (box, new_corner2, new_corner1, new_corner5);
1097 void sp_3dbox_recompute_XY_corners_from_new_center (SP3DBox *box, NR::Point const new_center)
1099     // TODO: Clean this function up
1101     Box3D::Perspective3D *persp = sp_desktop_document (inkscape_active_desktop())->get_persp_of_box (box);
1102     NR::Point old_center = box->old_center;
1104     NR::Point A0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner2, box->old_corner0, Box3D::Y, persp));
1105     NR::Point B0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner1, box->old_corner3, Box3D::Y, persp));
1107     /* we first move the box along the X-axis ... */
1108     Box3D::PerspectiveLine aux_line1 (old_center, Box3D::X, persp);
1109     Box3D::PerspectiveLine aux_line2 (new_center, Box3D::Y, persp);
1110     NR::Point Z1 = aux_line1.meet (aux_line2);
1112     Box3D::PerspectiveLine ref_line (B0, Box3D::X, persp);
1113     Box3D::PerspectiveLine pline2 (old_center, Box3D::Z, persp);
1114     Box3D::PerspectiveLine pline3 (Z1, Box3D::Z, persp);
1115     NR::Point M0 = ref_line.meet (pline2);
1116     NR::Point M1 = ref_line.meet (pline3);
1118     std::pair<NR::Point, NR::Point> new_midpts = sp_3dbox_new_midpoints (persp, Box3D::X, M0, M1, A0, B0);
1119     NR::Point A1 (new_midpts.first);
1120     NR::Point B1 (new_midpts.second);
1122     /* ... and then along the Y-axis */
1123     Box3D::PerspectiveLine pline4 (box->old_corner1, Box3D::X, persp);
1124     Box3D::PerspectiveLine pline5 (box->old_corner3, Box3D::X, persp);
1125     Box3D::PerspectiveLine aux_line3 (M1, Box3D::Y, persp);
1126     NR::Point C1 = aux_line3.meet (pline4);
1127     NR::Point D1 = aux_line3.meet (pline5);
1129     Box3D::PerspectiveLine aux_line4 (new_center, Box3D::Z, persp);
1130     NR::Point M2 = aux_line4.meet (aux_line3);
1132     std::pair<NR::Point, NR::Point> other_new_midpts = sp_3dbox_new_midpoints (persp, Box3D::Y, M1, M2, C1, D1);
1133     NR::Point C2 (other_new_midpts.first);
1134     NR::Point D2 (other_new_midpts.second);
1136     Box3D::PerspectiveLine plXC (C2, Box3D::X, persp);
1137     Box3D::PerspectiveLine plXD (D2, Box3D::X, persp);
1138     Box3D::PerspectiveLine plYA (A1, Box3D::Y, persp);
1139     Box3D::PerspectiveLine plYB (B1, Box3D::Y, persp);
1141     NR::Point new_corner2 (plXD.meet (plYA));
1142     NR::Point new_corner1 (plXC.meet (plYB));
1144     NR::Point tmp_corner1 (pline4.meet (plYB));
1145     Box3D::PerspectiveLine pline6 (box->old_corner5, Box3D::X, persp);
1146     Box3D::PerspectiveLine pline7 (tmp_corner1, Box3D::Z, persp);
1147     NR::Point tmp_corner5 (pline6.meet (pline7));
1149     Box3D::PerspectiveLine pline8 (tmp_corner5, Box3D::Y, persp);
1150     Box3D::PerspectiveLine pline9 (new_corner1, Box3D::Z, persp);
1151     NR::Point new_corner5 (pline8.meet (pline9));
1153     sp_3dbox_set_shape_from_points (box, new_corner2, new_corner1, new_corner5);
1156 void sp_3dbox_update_perspective_lines()
1158     SPEventContext *ec = inkscape_active_event_context();
1159     if (!SP_IS_3DBOX_CONTEXT (ec))
1160         return;
1162     SP_3DBOX_CONTEXT (ec)->_vpdrag->updateLines();
1165 /*
1166  * Manipulates corner1 through corner4 to contain the indices of the corners
1167  * from which the perspective lines in the direction of 'axis' emerge
1168  */
1169 void sp_3dbox_corners_for_perspective_lines (const SP3DBox * box, Box3D::Axis axis, 
1170                                              NR::Point &corner1, NR::Point &corner2, NR::Point &corner3, NR::Point &corner4)
1172     // along which axis to switch when takint
1173     Box3D::Axis switch_axis;
1174     if (axis == Box3D::X || axis == Box3D::Y) {
1175         switch_axis = (box->front_bits & axis) ? Box3D::Z : Box3D::NONE;
1176     } else {
1177         switch_axis = (box->front_bits & axis) ? Box3D::X : Box3D::NONE;
1178     }
1180     switch (axis) {
1181         case Box3D::X:
1182             corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
1183             corner2 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
1184             corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
1185             corner4 = sp_3dbox_get_corner_along_edge (box, 6 ^ switch_axis, axis, Box3D::REAR);
1186             break;
1187         case Box3D::Y:
1188             corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
1189             corner2 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
1190             corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
1191             corner4 = sp_3dbox_get_corner_along_edge (box, 5 ^ switch_axis, axis, Box3D::REAR);
1192             break;
1193         case Box3D::Z:
1194             corner1 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
1195             corner2 = sp_3dbox_get_corner_along_edge (box, 3 ^ switch_axis, axis, Box3D::REAR);
1196             corner3 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
1197             corner4 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
1198             break;
1199         default:
1200             // do nothing
1201             break;
1202     }            
1205 /**
1206  * Returns the id of the corner on the edge along 'axis' and passing through 'corner' that
1207  * lies on the front/rear face in this direction.
1208  */
1209 guint
1210 sp_3dbox_get_corner_id_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
1212     guint result;
1213     guint other_corner = corner ^ axis;
1214     Box3D::VanishingPoint *vp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point(axis);
1215     if (vp->is_finite()) {
1216         result = (  NR::L2 (vp->get_pos() - box->corners[corner])
1217                   < NR::L2 (vp->get_pos() - box->corners[other_corner]) ? other_corner : corner);
1218     } else {
1219         // clear the axis bit and switch to the appropriate corner along axis, depending on the value of front_bits
1220         result = ((corner & (0xF ^ axis)) ^ (box->front_bits & axis));
1221     }
1223     if (rel_pos == Box3D::FRONT) {
1224         return result;
1225     } else {
1226         return result ^ axis;
1227     }
1230 NR::Point
1231 sp_3dbox_get_corner_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
1233     return box->corners[sp_3dbox_get_corner_id_along_edge (box, corner, axis, rel_pos)];
1236 guint
1237 sp_3dbox_get_front_corner_id (const SP3DBox *box)
1239     guint front_corner = 1; // this could in fact be any corner, but we choose the one that is normally in front
1240     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::X, Box3D::FRONT);
1241     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Y, Box3D::FRONT);
1242     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Z, Box3D::FRONT);
1243     return front_corner;
1246 // auxiliary functions
1247 static void
1248 sp_3dbox_update_corner_with_value_from_svg (SPObject *object, guint corner_id, const gchar *value)
1250     if (value == NULL) return;
1251     SP3DBox *box = SP_3DBOX(object);
1253     std::pair<gdouble, gdouble> coord_pair = sp_3dbox_get_coord_pair_from_string (value);
1254     box->corners[corner_id] = NR::Point (coord_pair.first, coord_pair.second);
1255     sp_3dbox_recompute_corners (box, box->corners[2], box->corners[1], box->corners[5]);
1256     object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1259 static void
1260 sp_3dbox_update_perspective (Box3D::Perspective3D *persp, const gchar *value)
1262     // WARNING! This function changes the perspective associated to 'box'. Since there may be
1263     // many other boxes linked to the same perspective, their perspective is also changed.
1264     // If this behaviour is not desired in all cases, we need a different function.
1265     if (value == NULL) return;
1267     gchar **vps = g_strsplit( value, ",", 0);
1268     for (int i = 0; i < 15; ++i) {
1269         if (vps[i] == NULL) {
1270             g_warning ("Malformed svg attribute 'perspective'\n");
1271             return;
1272         }
1273     }
1275     persp->set_vanishing_point (Box3D::X, g_ascii_strtod (vps[0], NULL), g_ascii_strtod (vps[1], NULL),
1276                                           g_ascii_strtod (vps[2], NULL), g_ascii_strtod (vps[3], NULL),
1277                                           strcmp (vps[4], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
1278     persp->set_vanishing_point (Box3D::Y, g_ascii_strtod (vps[5], NULL), g_ascii_strtod (vps[6], NULL),
1279                                           g_ascii_strtod (vps[7], NULL), g_ascii_strtod (vps[8], NULL),
1280                                           strcmp (vps[9], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
1281     persp->set_vanishing_point (Box3D::Z, g_ascii_strtod (vps[10], NULL), g_ascii_strtod (vps[11], NULL),
1282                                           g_ascii_strtod (vps[12], NULL), g_ascii_strtod (vps[13], NULL),
1283                                           strcmp (vps[14], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
1285     // update the other boxes linked to the same perspective
1286     persp->reshape_boxes (Box3D::XYZ);
1289 /*
1290   Local Variables:
1291   mode:c++
1292   c-file-style:"stroustrup"
1293   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1294   indent-tabs-mode:nil
1295   fill-column:99
1296   End:
1297 */
1298 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :