Code

Rewrite of z-order code for 3D boxes, first stage (hopefully this is finally the...
[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);
247 static Inkscape::XML::Node *sp_3dbox_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
249     SP3DBox *box = SP_3DBOX(object);
250     // FIXME: How to handle other contexts???
251     // FIXME: Is tools_isactive(..) more recommended to check for the current context/tool?
252     if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
253         return repr;
255     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
256         Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
257         repr = xml_doc->createElement("svg:g");
258         repr->setAttribute("sodipodi:type", "inkscape:3dbox");
259         /* Hook paths to the faces of the box */
260         for (int i = 0; i < 6; ++i) {
261             box->faces[i]->hook_path_to_3dbox();
262         }
263     }
265     for (int i = 0; i < 6; ++i) {
266         box->faces[i]->set_path_repr();
267     }
269     if (flags & SP_OBJECT_WRITE_EXT) {
270         gchar *str;
271         str = sp_3dbox_get_corner_coords_string (box, 2);
272         repr->setAttribute("inkscape:box3dcornerA", str);
274         str = sp_3dbox_get_corner_coords_string (box, 1);
275         repr->setAttribute("inkscape:box3dcornerB", str);
277         str = sp_3dbox_get_corner_coords_string (box, 5);
278         repr->setAttribute("inkscape:box3dcornerC", str);
280         str = sp_3dbox_get_perspective_string (box);
281         repr->setAttribute("inkscape:perspective", str);
282         sp_3dbox_set_ratios (box);
284         g_free ((void *) str);
286         /* store center and construction-corners for later use during center-dragging */
287         NR::Maybe<NR::Point> cen = sp_3dbox_get_center (box);
288         if (cen) {
289             box->old_center = *cen;
290         }
291         box->old_corner2 = box->corners[2];
292         box->old_corner1 = box->corners[1];
293         box->old_corner0 = box->corners[0];
294         box->old_corner3 = box->corners[3];
295         box->old_corner5 = box->corners[5];
296         box->old_corner7 = box->corners[7];
297     }
299     if (((SPObjectClass *) (parent_class))->write) {
300         ((SPObjectClass *) (parent_class))->write(object, repr, flags);
301     }
303     return repr;
306 static gchar *
307 sp_3dbox_description(SPItem *item)
309     g_return_val_if_fail(SP_IS_3DBOX(item), NULL);
311     return g_strdup(_("<b>3D Box</b>"));
314 void sp_3dbox_set_ratios (SP3DBox *box, Box3D::Axis axes)
316     Box3D::Perspective3D *persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
317     NR::Point pt;
319     if (axes & Box3D::X) {
320         pt = persp->get_vanishing_point (Box3D::X)->get_pos();
321         box->ratio_x = NR::L2 (pt - box->corners[2]) / NR::L2 (pt - box->corners[3]);
322     }
324     if (axes & Box3D::Y) {
325         pt = persp->get_vanishing_point (Box3D::Y)->get_pos();
326         box->ratio_y = NR::L2 (pt - box->corners[2]) / NR::L2 (pt - box->corners[0]);
327     }
329     if (axes & Box3D::Z) {
330         pt = persp->get_vanishing_point (Box3D::Z)->get_pos();
331         box->ratio_z = NR::L2 (pt - box->corners[4]) / NR::L2 (pt - box->corners[0]);
332     }
335 void
336 sp_3dbox_switch_front_face (SP3DBox *box, Box3D::Axis axis)
338     if (SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point (axis)->is_finite()) {
339         box->front_bits = box->front_bits ^ axis;
340     }
344 void
345 sp_3dbox_position_set (SP3DBoxContext &bc)
347     SP3DBox *box3d = SP_3DBOX(bc.item);
349     sp_3dbox_set_shape(box3d);
351     // FIXME: Why does the following call not automatically update the children
352     //        of box3d (which is an SPGroup, which should do this)?
353     //SP_OBJECT(box3d)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
355     /**
356     SP_OBJECT(box3d->path_face1)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
357     SP_OBJECT(box3d->path_face2)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
358     SP_OBJECT(box3d->path_face3)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
359     SP_OBJECT(box3d->path_face4)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
360     SP_OBJECT(box3d->path_face5)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
361     SP_OBJECT(box3d->path_face6)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
362     ***/
365 static void
366 sp_3dbox_set_shape_from_points (SP3DBox *box, NR::Point const &cornerA, NR::Point const &cornerB, NR::Point const &cornerC)
368     sp_3dbox_recompute_corners (box, cornerA, cornerB, cornerC);
370     // FIXME: How to handle other contexts???
371     // FIXME: Is tools_isactive(..) more recommended to check for the current context/tool?
372     if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
373         return;
374     SP3DBoxContext *bc = SP_3DBOX_CONTEXT(inkscape_active_event_context());
376     if (bc->extruded) {
377         box->faces[0]->set_corners (box->corners[0], box->corners[4], box->corners[6], box->corners[2]);
378         box->faces[1]->set_corners (box->corners[1], box->corners[5], box->corners[7], box->corners[3]);
379         box->faces[2]->set_corners (box->corners[0], box->corners[1], box->corners[5], box->corners[4]);
380         box->faces[3]->set_corners (box->corners[2], box->corners[3], box->corners[7], box->corners[6]);
381         box->faces[5]->set_corners (box->corners[4], box->corners[5], box->corners[7], box->corners[6]);
382     }
383     box->faces[4]->set_corners (box->corners[0], box->corners[1], box->corners[3], box->corners[2]);
385     sp_3dbox_update_curves (box);
388 void
389 // FIXME: Note that this is _not_ the virtual set_shape() method inherited from SPShape,
390 //        since SP3DBox is inherited from SPGroup. The following method is "artificially"
391 //        called from sp_3dbox_update().
392 //sp_3dbox_set_shape(SPShape *shape)
393 sp_3dbox_set_shape(SP3DBox *box, bool use_previous_corners)
395     if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
396         return;
397     SP3DBoxContext *bc = SP_3DBOX_CONTEXT(inkscape_active_event_context());
399     if (!use_previous_corners) {
400         sp_3dbox_set_shape_from_points (box, bc->drag_origin, bc->drag_ptB, bc->drag_ptC);
401     } else {
402         sp_3dbox_set_shape_from_points (box, box->corners[2], box->corners[1], box->corners[5]);
403     }
407 void sp_3dbox_recompute_corners (SP3DBox *box, NR::Point const A, NR::Point const B, NR::Point const C)
409     sp_3dbox_move_corner_in_XY_plane (box, 2, A);
410     sp_3dbox_move_corner_in_XY_plane (box, 1, B);
411     sp_3dbox_move_corner_in_Z_direction (box, 5, C);
414 inline static double
415 normalized_angle (double angle) {
416     if (angle < -M_PI) {
417         return angle + 2*M_PI;
418     } else if (angle > M_PI) {
419         return angle - 2*M_PI;
420     }
421     return angle;
424 static gdouble
425 sp_3dbox_corner_angle_to_VP (SP3DBox *box, Box3D::Axis axis, guint extreme_corner)
427     Box3D::VanishingPoint *vp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point (axis);
428     NR::Point dir;
430     if (vp->is_finite()) {
431         dir = NR::unit_vector (vp->get_pos() - box->corners[extreme_corner]);
432     } else {
433         dir = NR::unit_vector (vp->v_dir);
434     }
436     return atan2 (dir[NR::Y], dir[NR::X]);
440 bool sp_3dbox_recompute_z_orders (SP3DBox *box)
442     guint new_z_orders[6];
444     // TODO: Determine the front corner depending on the distance from VPs and/or the user presets
445     guint front_corner = sp_3dbox_get_front_corner_id (box);
447     gdouble dir_1x = sp_3dbox_corner_angle_to_VP (box, Box3D::X, front_corner);
448     gdouble dir_3x = sp_3dbox_corner_angle_to_VP (box, Box3D::X, front_corner ^ Box3D::Y);
450     gdouble dir_1y = sp_3dbox_corner_angle_to_VP (box, Box3D::Y, front_corner);
451     //gdouble dir_0y = sp_3dbox_corner_angle_to_VP (box, Box3D::Y, front_corner ^ Box3D::X);
453     gdouble dir_1z = sp_3dbox_corner_angle_to_VP (box, Box3D::Z, front_corner);
454     gdouble dir_3z = sp_3dbox_corner_angle_to_VP (box, Box3D::Z, front_corner ^ Box3D::Y);
456     // Still not perfect, but only fails in some rather degenerate cases.
457     // I suspect that there is a more elegant model, though. :)
458     new_z_orders[0] = Box3D::face_containing_corner (Box3D::XY, front_corner);
459     if (normalized_angle (dir_1y - dir_1z) > 0) {
460         new_z_orders[1] = Box3D::face_containing_corner (Box3D::YZ, front_corner);
461         if (normalized_angle (dir_1x - dir_1z) > 0) {
462             new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner ^ Box3D::Y);
463         } else {
464             new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
465         }
466     } else {
467         if (normalized_angle (dir_3x - dir_3z) > 0) {
468             new_z_orders[1] = Box3D::face_containing_corner (Box3D::XZ, front_corner ^ Box3D::Y);
469             new_z_orders[2] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
470         } else {
471             if (normalized_angle (dir_1x - dir_1z) > 0) {
472                 new_z_orders[1] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
473                 new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
474             } else {
475                 new_z_orders[1] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
476                 new_z_orders[2] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
477             }
478         }
479     }
481     new_z_orders[3] = Box3D::opposite_face (new_z_orders[2]);
482     new_z_orders[4] = Box3D::opposite_face (new_z_orders[1]);
483     new_z_orders[5] = Box3D::opposite_face (new_z_orders[0]);
485     /* We only need to look for changes among the topmost three faces because the order
486        of the other ones is just inverted. */
487     if ((box->z_orders[0] != new_z_orders[0]) ||
488         (box->z_orders[1] != new_z_orders[1]) ||
489         (box->z_orders[2] != new_z_orders[2]))
490     {
491         for (int i = 0; i < 6; ++i) {
492             box->z_orders[i] = new_z_orders[i];
493         }
494         return true;
495     }
497     return false;
500 // convenience
501 static bool sp_3dbox_is_subset_or_superset (std::vector<gint> const &list1, std::vector<gint> const &list2)
503     return (std::includes (list1.begin(), list1.end(), list2.begin(), list2.end()) ||
504             std::includes (list2.begin(), list2.end(), list1.begin(), list1.end()));
507 static bool sp_3dbox_differ_by_opposite_faces (std::vector<gint> const &list1, std::vector<gint> const &list2)
509     std::vector<gint> diff1;
510     std::vector<gint> diff2;
511     std::set_difference (list1.begin(), list1.end(), list2.begin(), list2.end(),
512                          std::insert_iterator<std::vector<gint> >(diff1, diff1.begin()));
513     std::set_difference (list2.begin(), list2.end(), list1.begin(), list1.end(),
514                          std::insert_iterator<std::vector<gint> >(diff2, diff2.begin()));
516     if (diff1.size() == 3 || diff1.size() != diff2.size())
517         return false;
519     for (guint i = 0; i < diff1.size(); ++i) {
520         if (std::find (diff2.begin(), diff2.end(), Box3D::opposite_face (diff1[i])) == diff2.end()) {
521             return false;
522         }
523     }
524     return true;
527 static gint
528 sp_3dbox_face_containing_diagonal_corners (guint corner1, guint corner2)
530     Box3D::Axis plane = (Box3D::Axis) (corner1 ^ corner2);
531     if (!Box3D::is_plane (plane)) {
532         g_warning ("Corners %d and %d should span a plane.\n", corner1, corner2);
533         return 0;
534     }
536     return Box3D::face_containing_corner (plane, corner1);
539 static std::vector<gint> sp_3dbox_adjacent_faces_of_edge (guint corner1, guint corner2) {
540     std::vector<gint> adj_faces;
541     Box3D::Axis edge = (Box3D::Axis) (corner1 ^ corner2);
542     if (!Box3D::is_single_axis_direction (edge)) {
543         return adj_faces;
544     }
546     Box3D::Axis plane = Box3D::orth_plane_or_axis (edge);
547     Box3D::Axis axis1 = Box3D::extract_first_axis_direction (plane);
548     Box3D::Axis axis2 = Box3D::extract_second_axis_direction (plane);
549     adj_faces.push_back (Box3D::face_containing_corner ((Box3D::Axis) (edge ^ axis1), corner1));
550     adj_faces.push_back (Box3D::face_containing_corner ((Box3D::Axis) (edge ^ axis2), corner1));
551     return adj_faces;
554 static std::vector<gint> sp_3dbox_faces_meeting_in_corner (guint corner) {
555     std::vector<gint> faces;
556     for (int i = 0; i < 3; ++i) {
557         faces.push_back (sp_3dbox_face_containing_diagonal_corners (corner, corner ^ Box3D::planes[i]));
558     }
559     return faces;
562 static void sp_3dbox_remaining_faces (std::vector<gint> const &faces, std::vector<gint> &rem_faces)
564     rem_faces.clear();
565     for (gint i = 0; i < 6; ++i) {
566         if (std::find (faces.begin(), faces.end(), i) == faces.end()) {
567             rem_faces.push_back (i);
568         }
569     }
572 /*
573  * Given two adjacent edges (\a c2,\a c1) and (\a c2, \a c3) of \a box (with common corner \a c2),
574  * check whether both lie on the convex hull of the point configuration given by \a box's corners.
575  */
576 static bool
577 sp_3dbox_is_border_edge_pair (SP3DBox *box, guint const c1, guint const c2, guint const c3)
579     Box3D::Axis edge21 = (Box3D::Axis) (c2 ^ c1);
580     Box3D::Axis edge23 = (Box3D::Axis) (c2 ^ c3);
581     Box3D::Axis rear_axis = Box3D::orth_plane_or_axis ((Box3D::Axis) (edge21 ^ edge23));
582  
583     NR::Point corner2 = box->corners[c2];
584     NR::Point dir21 = box->corners[c1] - corner2;
585     NR::Point dir23 = box->corners[c3] - corner2;
587     if (!Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ edge21 ^ edge23] - corner2) ||
588         !Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ rear_axis] - corner2) ||
589         !Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ rear_axis ^ edge21] - corner2) ||
590         !Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ rear_axis ^ edge21 ^ edge23] - corner2) ||
591         !Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ rear_axis ^ edge23] - corner2)) {
592         // corner triple c1, c2, c3 doesn't bound the convex hull
593         return false;
594     }
595     // corner triple c1, c2, c3 bounds the convex hull
596     return true;    
599 /*
600  * Test whether there are any adjacent corners of \a corner (i.e., connected with it along one of the axes)
601  * such that the corresponding edges bound the convex hull of the box (as a point configuration in the plane)
602  * If this is the case, return the corresponding two adjacent corners; otherwise return (-1, -1).
603  */
604 static Box3D::Axis
605 sp_3dbox_axis_pair_bounding_convex_hull (SP3DBox *box, guint corner)
606  {
607     guint adj1 = corner ^ Box3D::X;
608     guint adj2 = corner ^ Box3D::Y;
609     guint adj3 = corner ^ Box3D::Z;
611     if (sp_3dbox_is_border_edge_pair (box, adj1, corner, adj2)) {
612         return Box3D::XY;
613     }
614     if (sp_3dbox_is_border_edge_pair (box, adj1, corner, adj3)) {
615         return Box3D::XZ;
616     }
617     if (sp_3dbox_is_border_edge_pair (box, adj2, corner, adj3)) {
618         return Box3D::YZ;
619     }
620     return Box3D::NONE;
623 // inside_hull is modified 'in place' by the following function
624 static void sp_3dbox_corner_configuration (SP3DBox *box, std::vector<gint> &on_hull, std::vector<gint> &inside_hull)
626     for (int i = 0; i < 8; ++i) {
627         Box3D::Axis bounding_edges = sp_3dbox_axis_pair_bounding_convex_hull (box, i);
628         if (bounding_edges != Box3D::NONE) {
629             on_hull.push_back (i);
630         } else {
631             inside_hull.push_back (i);
632         }
633     }
636 /* returns true if there was a change in the z-orders (which triggers an update of the repr) */
637 static bool sp_3dbox_recompute_z_orders_by_corner_configuration (SP3DBox *box)
639     guint new_z_orders[6];
640     Box3D::Axis front_rear_axis = Box3D::Z;
642     Box3D::Axis axis1 = Box3D::get_remaining_axes (front_rear_axis).first;
643     Box3D::Axis axis2 = Box3D::get_remaining_axes (front_rear_axis).second;
644     Box3D::Axis front_plane = Box3D::orth_plane_or_axis (front_rear_axis);
646     std::vector<gint> on_hull;
647     std::vector<gint> inside_hull;
648     std::vector<gint> visible_faces;
650     sp_3dbox_corner_configuration (box, on_hull, inside_hull);
652     switch (on_hull.size()) {
653         case 4:
654             {
655                 // the following works because on_hull is sorted
656                 gint front_face = sp_3dbox_face_containing_diagonal_corners (on_hull[0], on_hull[3]);
657                 visible_faces.push_back (front_face);
658             }
659             break;
661         case 6:
662         {
663             guint c1 = inside_hull[0] ^ Box3D::XYZ;
664             guint c2 = inside_hull[1] ^ Box3D::XYZ;
665             Box3D::Axis edge = (Box3D::Axis) (c1 ^ c2);
666             if (Box3D::is_single_axis_direction (edge)) {
667                 visible_faces = sp_3dbox_adjacent_faces_of_edge (c1, c2);
668             } else if (c1 == c2 ^ Box3D::XYZ) {
669                 guint c_cmp = sp_3dbox_get_corner_id_along_edge (box, 0, front_rear_axis, Box3D::FRONT);
670                 guint visible_front_corner = (((c_cmp & front_rear_axis) == (c1 & front_rear_axis)) ? c1 : c2);
671                 visible_faces = sp_3dbox_faces_meeting_in_corner (visible_front_corner);
672             } else {
673                 g_print ("Warning: Unhandled case. Current z-orders remain unchanged.\n");
674                 return false;
675             }
676             break;
677         }
679         default:
680             g_print ("Warning: Unhandled case. Current z-orders are not changed.\n");
681             return false;
682     }
684     // check for weird corner configurations that cannot be handled by the above code
685     if (std::find (visible_faces.begin(), visible_faces.end(), -1) != visible_faces.end()) {
686         g_warning ("Theoretically impossible corner configuration\n");
687         return false;
688     }
690     // sort the list of visible faces for later use (although it may be already sorted anyway)
691     std::sort (visible_faces.begin(), visible_faces.end());
693     std::vector<gint> invisible_faces;
694     sp_3dbox_remaining_faces (visible_faces, invisible_faces);
697     if (!sp_3dbox_is_subset_or_superset (visible_faces, box->currently_visible_faces) &&
698         !sp_3dbox_differ_by_opposite_faces (visible_faces, box->currently_visible_faces)) {
699         std::swap (visible_faces, invisible_faces);
700         if (!sp_3dbox_is_subset_or_superset (visible_faces, box->currently_visible_faces) &&
701             !sp_3dbox_differ_by_opposite_faces (visible_faces, box->currently_visible_faces)) {
702             // FIXME: Hopefully this case is only caused by rounding errors or something similar;
703             //        does it need further investigation?
704             g_warning ("Can't find out which faces are visible and which aren't ...\n");
705             return false;
706         }
707     }
709     box->currently_visible_faces = visible_faces;
711     // set new z-orders according to the visible/invisible faces
712     guint vis_size = visible_faces.size();
713     for (guint i = 0; i < vis_size; ++i) {
714         new_z_orders[i] = visible_faces[i];
715     }
716     for (guint i = 0; i < invisible_faces.size(); ++i) {
717         new_z_orders[vis_size + i] = invisible_faces[i];
718     }
720     // test whether any z-orders actually changed and indicate this in the return status
721     for (int i = 0; i < 6; ++i) {
722         if (box->z_orders[i] != new_z_orders[i]) {
723             // we update the z-orders starting from the index where the change occurs
724             for (int j = i; j < 6; ++j) {
725                 box->z_orders[j] = new_z_orders[j];
726             }
727             return true;
728         }
729     }
730     return false;
733 // FIXME: Can we unify this and the next function for setting the z-orders?
734 void sp_3dbox_set_z_orders_in_the_first_place (SP3DBox *box)
736     // For efficiency reasons, we only set the new z-orders if something really changed
737     if (sp_3dbox_recompute_z_orders (box)) {
738         box->faces[box->z_orders[0]]->lower_to_bottom ();
739         box->faces[box->z_orders[1]]->lower_to_bottom ();
740         box->faces[box->z_orders[2]]->lower_to_bottom ();
741         box->faces[box->z_orders[3]]->lower_to_bottom ();
742         box->faces[box->z_orders[4]]->lower_to_bottom ();
743         box->faces[box->z_orders[5]]->lower_to_bottom ();
744     }
747 void sp_3dbox_set_z_orders_later_on (SP3DBox *box)
749     // For efficiency reasons, we only set the new z-orders if something really changed
750     if (sp_3dbox_recompute_z_orders_by_corner_configuration (box)) {
751         box->faces[box->z_orders[0]]->lower_to_bottom ();
752         box->faces[box->z_orders[1]]->lower_to_bottom ();
753         box->faces[box->z_orders[2]]->lower_to_bottom ();
754         box->faces[box->z_orders[3]]->lower_to_bottom ();
755         box->faces[box->z_orders[4]]->lower_to_bottom ();
756         box->faces[box->z_orders[5]]->lower_to_bottom ();
757     }
760 void
761 sp_3dbox_update_curves (SP3DBox *box) {
762     for (int i = 0; i < 6; ++i) {
763         if (box->faces[i]) box->faces[i]->set_curve();
764     }
767 /**
768  * In some situations (e.g., after cloning boxes, undo & redo, or reading boxes from a file) there are
769  * paths already present in the document which correspond to the faces of newly created boxes, but their
770  * 'path' members don't link to them yet. The following function corrects this if necessary.
771  */
772 void
773 sp_3dbox_link_to_existing_paths (SP3DBox *box, Inkscape::XML::Node *repr) {
774     // TODO: We should probably destroy the existing paths and recreate them because we don't know
775     //       precisely which path corresponds to which face. Does this make a difference?
776     //       In sp_3dbox_write we write the correct paths anyway, don't we? But we could get into
777     //       trouble at a later stage when we only write single faces for degenerate boxes.
779     SPDocument *document = SP_OBJECT_DOCUMENT(box);
780     guint face_id = 0;
782     for (Inkscape::XML::Node *i = sp_repr_children(repr); i != NULL; i = sp_repr_next(i)) {
783         if (face_id > 5) {
784             g_warning ("SVG representation of 3D boxes must contain 6 paths or less.\n");
785             break;
786         }
788         SPObject *face_object = document->getObjectByRepr((Inkscape::XML::Node *) i);
789         if (!SP_IS_PATH(face_object)) {
790             g_warning ("SVG representation of 3D boxes should only contain paths.\n");
791             continue;
792         }
793         // TODO: Currently we don't check whether all paths are being linked to different faces.
794         //       This is no problem with valid SVG files. It may lead to crashes, however,
795         //       in case a file is corrupt (e.g., two or more faces have identical descriptions).
796         gint id = Box3DFace::descr_to_id (i->attribute ("inkscape:box3dface"));
797         box->faces[id]->hook_path_to_3dbox(SP_PATH(face_object));
798         ++face_id;
799     }
800     if (face_id < 6) {
801         //g_warning ("SVG representation of 3D boxes should contain exactly 6 paths (degenerate boxes are not yet supported).\n");
802         // TODO: Check whether it is safe to add the remaining paths to the box and do so in case it is.
803         //       (But we also land here for newly created boxes where we shouldn't add any paths because
804         //       This is done in sp_3dbox_write later on.
805     }
808 void
809 sp_3dbox_move_corner_in_XY_plane (SP3DBox *box, guint id, NR::Point pt, Box3D::Axis axes)
811     Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
813     NR::Point A (box->corners[id ^ Box3D::XY]);
814     if (Box3D::is_single_axis_direction (axes)) {
815         pt = Box3D::PerspectiveLine (box->corners[id], axes, persp).closest_to(pt);
816     }
818     /* set the 'front' corners */
819     box->corners[id] = pt;
821     Box3D::PerspectiveLine pl_one (A, Box3D::Y, persp);
822     Box3D::PerspectiveLine pl_two (pt, Box3D::X, persp);
823     box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
825     pl_one = Box3D::PerspectiveLine (A, Box3D::X, persp);
826     pl_two = Box3D::PerspectiveLine (pt, Box3D::Y, persp);
827     box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
829     /* set the 'rear' corners */
830     NR::Point B (box->corners[id ^ Box3D::XYZ]);
832     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Z, persp);
833     pl_two = Box3D::PerspectiveLine (B, Box3D::Y, persp);
834     box->corners[id ^ Box3D::XZ] = pl_one.meet(pl_two);
836     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XZ], Box3D::X, persp);
837     pl_two = Box3D::PerspectiveLine (pt, Box3D::Z, persp);
838     box->corners[id ^ Box3D::Z] = pl_one.meet(pl_two);
840     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::Z], Box3D::Y, persp);
841     pl_two = Box3D::PerspectiveLine (B, Box3D::X, persp);
842     box->corners[id ^ Box3D::YZ] = pl_one.meet(pl_two);
843     
846 void
847 sp_3dbox_move_corner_in_Z_direction (SP3DBox *box, guint id, NR::Point pt, bool constrained)
849     if (!constrained) sp_3dbox_move_corner_in_XY_plane (box, id, pt, Box3D::XY);
851     Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
853     /* set the four corners of the face containing corners[id] */
854     box->corners[id] = Box3D::PerspectiveLine (box->corners[id], Box3D::Z, persp).closest_to(pt);
856     Box3D::PerspectiveLine pl_one (box->corners[id], Box3D::X, persp);
857     Box3D::PerspectiveLine pl_two (box->corners[id ^ Box3D::XZ], Box3D::Z, persp);
858     box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
860     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Y, persp);
861     pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XYZ], Box3D::Z, persp);
862     box->corners[id ^ Box3D::XY] = pl_one.meet(pl_two);
864     pl_one = Box3D::PerspectiveLine (box->corners[id], Box3D::Y, persp);
865     pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::YZ], Box3D::Z, persp);
866     box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
869 static void
870 sp_3dbox_reshape_edge_after_VP_toggling (SP3DBox *box, const guint corner, const Box3D::Axis axis, Box3D::Perspective3D *persp)
872     /* Hmm, perhaps we should simply use one of the corners as the pivot point.
873        But this way we minimize the amount of reshaping.
874        On second thought, we need to find a way to ensure that all boxes sharing the same
875        perspective are updated consistently _as a group_. That is, they should also retain
876        their relative positions towards each other. */
877     NR::Maybe<NR::Point> pt = sp_3dbox_get_midpoint_between_corners (box, corner, corner ^ axis);
878     g_return_if_fail (pt);
880     Box3D::Axis axis2 = ((axis == Box3D::Y) ? Box3D::X : Box3D::Y);
882     Box3D::PerspectiveLine line1 (box->corners[corner], axis2, persp);
883     Box3D::PerspectiveLine line2 (box->corners[corner ^ axis], axis2, persp);
885     Box3D::PerspectiveLine line3 (*pt, axis, persp);
887     NR::Point new_corner1 = line1.meet (line3);
888     NR::Point new_corner2 = line2.meet (line3);
890     box->corners[corner] = new_corner1;
891     box->corners[corner ^ axis] = new_corner2;
894 void
895 sp_3dbox_reshape_after_VP_toggling (SP3DBox *box, Box3D::Axis axis)
897     Box3D::Perspective3D *persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
898     std::pair<Box3D::Axis, Box3D::Axis> dirs = Box3D::get_remaining_axes (axis);
900     sp_3dbox_reshape_edge_after_VP_toggling (box, 0, axis, persp);
901     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first, axis, persp);
902     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first ^ dirs.second, axis, persp);
903     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.second, axis, persp);
906 NR::Maybe<NR::Point>
907 sp_3dbox_get_center (SP3DBox *box)
909     return sp_3dbox_get_midpoint_between_corners (box, 0, 7);
912 NR::Point
913 sp_3dbox_get_midpoint_in_axis_direction (NR::Point const &C, NR::Point const &D, Box3D::Axis axis, Box3D::Perspective3D *persp)
915     Box3D::PerspectiveLine pl (D, axis, persp);
916     return pl.pt_with_given_cross_ratio (C, D, -1.0);
919 // TODO: The following function can probably be rewritten in a much more elegant and robust way
920 //        by using projective coordinates for all points and using the cross ratio.
921 NR::Maybe<NR::Point>
922 sp_3dbox_get_midpoint_between_corners (SP3DBox *box, guint id_corner1, guint id_corner2)
924     Box3D::Axis corner_axes = (Box3D::Axis) (id_corner1 ^ id_corner2);
926     // Is all this sufficiently precise also for degenerate cases?
927     if (sp_3dbox_corners_are_adjacent (id_corner1, id_corner2)) {
928         Box3D::Axis orth_dir = get_perpendicular_axis_direction (corner_axes);
930         Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2 ^ orth_dir]);
931         Box3D::Line diag2 (box->corners[id_corner1 ^ orth_dir], box->corners[id_corner2]);
932         NR::Maybe<NR::Point> adjacent_face_center = diag1.intersect(diag2);
934         if (!adjacent_face_center) return NR::Nothing();
936         Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
938         Box3D::PerspectiveLine pl (*adjacent_face_center, orth_dir, persp);
939         return pl.intersect(Box3D::PerspectiveLine(box->corners[id_corner1], corner_axes, persp));
940     } else {
941         Box3D::Axis dir = Box3D::extract_first_axis_direction (corner_axes);
942         Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2]);
943         Box3D::Line diag2 (box->corners[id_corner1 ^ dir], box->corners[id_corner2 ^ dir]);
944         return diag1.intersect(diag2);
945     }
948 static gchar *
949 sp_3dbox_get_corner_coords_string (SP3DBox *box, guint id)
951     id = id % 8;
952     Inkscape::SVGOStringStream os;
953     os << box->corners[id][NR::X] << "," << box->corners[id][NR::Y];
954     return g_strdup(os.str().c_str());
957 static std::pair<gdouble, gdouble>
958 sp_3dbox_get_coord_pair_from_string (const gchar *coords)
960     gchar **coordpair = g_strsplit( coords, ",", 0);
961     // We might as well rely on g_ascii_strtod to convert the NULL pointer to 0.0,
962     // but we include the following test anyway
963     if (coordpair[0] == NULL || coordpair[1] == NULL) {
964         g_strfreev (coordpair);
965         g_warning ("Coordinate conversion failed.\n");
966         return std::make_pair(0.0, 0.0);
967     }
969     gdouble coord1 = g_ascii_strtod(coordpair[0], NULL);
970     gdouble coord2 = g_ascii_strtod(coordpair[1], NULL);
971     g_strfreev (coordpair);
973     return std::make_pair(coord1, coord2);
976 static gchar *
977 sp_3dbox_get_perspective_string (SP3DBox *box)
979     
980     return sp_3dbox_get_svg_descr_of_persp (SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box));
982   
983 gchar *
984 sp_3dbox_get_svg_descr_of_persp (Box3D::Perspective3D *persp)
986     // FIXME: We should move this code to perspective3d.cpp, but this yields compiler errors. Why?
987     Inkscape::SVGOStringStream os;
989     Box3D::VanishingPoint vp = *(persp->get_vanishing_point (Box3D::X));
990     os << vp[NR::X] << "," << vp[NR::Y] << ",";
991     os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
992     if (vp.is_finite()) {
993         os << "finite,";
994     } else {
995         os << "infinite,";
996     }
998     vp = *(persp->get_vanishing_point (Box3D::Y));
999     os << vp[NR::X] << "," << vp[NR::Y] << ",";
1000     os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
1001     if (vp.is_finite()) {
1002         os << "finite,";
1003     } else {
1004         os << "infinite,";
1005     }
1007     vp = *(persp->get_vanishing_point (Box3D::Z));
1008     os << vp[NR::X] << "," << vp[NR::Y] << ",";
1009     os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
1010     if (vp.is_finite()) {
1011         os << "finite";
1012     } else {
1013         os << "infinite";
1014     }
1016     return g_strdup(os.str().c_str());
1019 // auxiliary function
1020 static std::pair<NR::Point, NR::Point>
1021 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)
1023     double cr1 = Box3D::cross_ratio (*persp->get_vanishing_point (axis), M0, M, A);
1024     double cr2 = Box3D::cross_ratio (*persp->get_vanishing_point (axis), M, B, M0);
1025     if (fabs (cr1 - 1) < Box3D::epsilon) {
1026         // FIXME: cr == 1 is a degenerate case; how should we deal with it?
1027         return std::make_pair (NR::Point (0,0), NR::Point (0,0));
1028     }
1029     Box3D::PerspectiveLine pl (M0, axis, persp);
1030     NR::Point B_new = pl.pt_with_given_cross_ratio (M0, M, cr1 / (cr1 - 1));
1031     NR::Point A_new = pl.pt_with_given_cross_ratio (M0, M, 1 - cr2);
1032     return std::make_pair (A_new, B_new);
1035 void sp_3dbox_recompute_Z_corners_from_new_center (SP3DBox *box, NR::Point const new_center)
1037     // TODO: Clean this function up
1039     Box3D::Perspective3D *persp = sp_desktop_document (inkscape_active_desktop())->get_persp_of_box (box);
1040     NR::Point old_center = box->old_center;
1042     Box3D::PerspectiveLine aux_line1 (old_center, Box3D::Z, persp);
1043     Box3D::PerspectiveLine aux_line2 (new_center, Box3D::Y, persp);
1044     NR::Point Z1 = aux_line1.meet (aux_line2);
1046     NR::Point A0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner2, box->old_corner0, Box3D::Y, persp));
1047     NR::Point B0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner7, box->old_corner5, Box3D::Y, persp));
1048     Box3D::PerspectiveLine aux_line3 (A0, Box3D::X, persp);
1049     Box3D::PerspectiveLine aux_line4 (B0, Box3D::X, persp);
1051     NR::Point C0 = aux_line3.meet (aux_line1);
1052     NR::Point D0 = aux_line4.meet (aux_line1);
1054     std::pair<NR::Point, NR::Point> new_midpts = sp_3dbox_new_midpoints (persp, Box3D::Z, old_center, Z1, C0, D0);
1055     NR::Point C1 (new_midpts.first);
1056     NR::Point D1 (new_midpts.second);
1057     Box3D::PerspectiveLine aux_line5 (C1, Box3D::X, persp);
1058     Box3D::PerspectiveLine aux_line6 (D1, Box3D::X, persp);
1060     Box3D::PerspectiveLine aux_line7 (A0, Box3D::Z, persp);
1061     Box3D::PerspectiveLine aux_line8 (B0, Box3D::Z, persp);
1063     NR::Point A1 = aux_line5.meet (aux_line7);
1064     NR::Point B1 = aux_line6.meet (aux_line8);
1066     Box3D::PerspectiveLine aux_line9  (box->old_corner2, Box3D::Z, persp);
1067     Box3D::PerspectiveLine aux_line10 (box->old_corner5, Box3D::Z, persp);
1069     Box3D::PerspectiveLine aux_line11 (A1, Box3D::Y, persp);
1070     Box3D::PerspectiveLine aux_line12 (B1, Box3D::Y, persp);
1072     NR::Point new_corner2 = aux_line9.meet (aux_line11);
1073     NR::Point new_corner5 = aux_line10.meet (aux_line12);
1075     Box3D::PerspectiveLine aux_line13 (A1, Box3D::X, persp);
1076     NR::Point E1 = aux_line13.meet (aux_line8);
1077     Box3D::PerspectiveLine aux_line14 (E1, Box3D::Y, persp);
1079     NR::Point new_corner1 = aux_line10.meet (aux_line14);
1081     sp_3dbox_set_shape_from_points (box, new_corner2, new_corner1, new_corner5);
1084 void sp_3dbox_recompute_XY_corners_from_new_center (SP3DBox *box, NR::Point const new_center)
1086     // TODO: Clean this function up
1088     Box3D::Perspective3D *persp = sp_desktop_document (inkscape_active_desktop())->get_persp_of_box (box);
1089     NR::Point old_center = box->old_center;
1091     NR::Point A0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner2, box->old_corner0, Box3D::Y, persp));
1092     NR::Point B0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner1, box->old_corner3, Box3D::Y, persp));
1094     /* we first move the box along the X-axis ... */
1095     Box3D::PerspectiveLine aux_line1 (old_center, Box3D::X, persp);
1096     Box3D::PerspectiveLine aux_line2 (new_center, Box3D::Y, persp);
1097     NR::Point Z1 = aux_line1.meet (aux_line2);
1099     Box3D::PerspectiveLine ref_line (B0, Box3D::X, persp);
1100     Box3D::PerspectiveLine pline2 (old_center, Box3D::Z, persp);
1101     Box3D::PerspectiveLine pline3 (Z1, Box3D::Z, persp);
1102     NR::Point M0 = ref_line.meet (pline2);
1103     NR::Point M1 = ref_line.meet (pline3);
1105     std::pair<NR::Point, NR::Point> new_midpts = sp_3dbox_new_midpoints (persp, Box3D::X, M0, M1, A0, B0);
1106     NR::Point A1 (new_midpts.first);
1107     NR::Point B1 (new_midpts.second);
1109     /* ... and then along the Y-axis */
1110     Box3D::PerspectiveLine pline4 (box->old_corner1, Box3D::X, persp);
1111     Box3D::PerspectiveLine pline5 (box->old_corner3, Box3D::X, persp);
1112     Box3D::PerspectiveLine aux_line3 (M1, Box3D::Y, persp);
1113     NR::Point C1 = aux_line3.meet (pline4);
1114     NR::Point D1 = aux_line3.meet (pline5);
1116     Box3D::PerspectiveLine aux_line4 (new_center, Box3D::Z, persp);
1117     NR::Point M2 = aux_line4.meet (aux_line3);
1119     std::pair<NR::Point, NR::Point> other_new_midpts = sp_3dbox_new_midpoints (persp, Box3D::Y, M1, M2, C1, D1);
1120     NR::Point C2 (other_new_midpts.first);
1121     NR::Point D2 (other_new_midpts.second);
1123     Box3D::PerspectiveLine plXC (C2, Box3D::X, persp);
1124     Box3D::PerspectiveLine plXD (D2, Box3D::X, persp);
1125     Box3D::PerspectiveLine plYA (A1, Box3D::Y, persp);
1126     Box3D::PerspectiveLine plYB (B1, Box3D::Y, persp);
1128     NR::Point new_corner2 (plXD.meet (plYA));
1129     NR::Point new_corner1 (plXC.meet (plYB));
1131     NR::Point tmp_corner1 (pline4.meet (plYB));
1132     Box3D::PerspectiveLine pline6 (box->old_corner5, Box3D::X, persp);
1133     Box3D::PerspectiveLine pline7 (tmp_corner1, Box3D::Z, persp);
1134     NR::Point tmp_corner5 (pline6.meet (pline7));
1136     Box3D::PerspectiveLine pline8 (tmp_corner5, Box3D::Y, persp);
1137     Box3D::PerspectiveLine pline9 (new_corner1, Box3D::Z, persp);
1138     NR::Point new_corner5 (pline8.meet (pline9));
1140     sp_3dbox_set_shape_from_points (box, new_corner2, new_corner1, new_corner5);
1143 void sp_3dbox_update_perspective_lines()
1145     SPEventContext *ec = inkscape_active_event_context();
1146     if (!SP_IS_3DBOX_CONTEXT (ec))
1147         return;
1149     SP_3DBOX_CONTEXT (ec)->_vpdrag->updateLines();
1152 /*
1153  * Manipulates corner1 through corner4 to contain the indices of the corners
1154  * from which the perspective lines in the direction of 'axis' emerge
1155  */
1156 void sp_3dbox_corners_for_perspective_lines (const SP3DBox * box, Box3D::Axis axis, 
1157                                              NR::Point &corner1, NR::Point &corner2, NR::Point &corner3, NR::Point &corner4)
1159     // along which axis to switch when takint
1160     Box3D::Axis switch_axis;
1161     if (axis == Box3D::X || axis == Box3D::Y) {
1162         switch_axis = (box->front_bits & axis) ? Box3D::Z : Box3D::NONE;
1163     } else {
1164         switch_axis = (box->front_bits & axis) ? Box3D::X : Box3D::NONE;
1165     }
1167     switch (axis) {
1168         case Box3D::X:
1169             corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
1170             corner2 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
1171             corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
1172             corner4 = sp_3dbox_get_corner_along_edge (box, 6 ^ switch_axis, axis, Box3D::REAR);
1173             break;
1174         case Box3D::Y:
1175             corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
1176             corner2 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
1177             corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
1178             corner4 = sp_3dbox_get_corner_along_edge (box, 5 ^ switch_axis, axis, Box3D::REAR);
1179             break;
1180         case Box3D::Z:
1181             corner1 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
1182             corner2 = sp_3dbox_get_corner_along_edge (box, 3 ^ switch_axis, axis, Box3D::REAR);
1183             corner3 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
1184             corner4 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
1185             break;
1186         default:
1187             // do nothing
1188             break;
1189     }            
1192 /**
1193  * Returns the id of the corner on the edge along 'axis' and passing through 'corner' that
1194  * lies on the front/rear face in this direction.
1195  */
1196 guint
1197 sp_3dbox_get_corner_id_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
1199     guint result;
1200     guint other_corner = corner ^ axis;
1201     Box3D::VanishingPoint *vp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point(axis);
1202     if (vp->is_finite()) {
1203         result = (  NR::L2 (vp->get_pos() - box->corners[corner])
1204                   < NR::L2 (vp->get_pos() - box->corners[other_corner]) ? other_corner : corner);
1205     } else {
1206         // clear the axis bit and switch to the appropriate corner along axis, depending on the value of front_bits
1207         result = ((corner & (0xF ^ axis)) ^ (box->front_bits & axis));
1208     }
1210     if (rel_pos == Box3D::FRONT) {
1211         return result;
1212     } else {
1213         return result ^ axis;
1214     }
1217 NR::Point
1218 sp_3dbox_get_corner_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
1220     return box->corners[sp_3dbox_get_corner_id_along_edge (box, corner, axis, rel_pos)];
1223 guint
1224 sp_3dbox_get_front_corner_id (const SP3DBox *box)
1226     guint front_corner = 1; // this could in fact be any corner, but we choose the one that is normally in front
1227     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::X, Box3D::FRONT);
1228     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Y, Box3D::FRONT);
1229     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Z, Box3D::FRONT);
1230     return front_corner;
1233 // auxiliary functions
1234 static void
1235 sp_3dbox_update_corner_with_value_from_svg (SPObject *object, guint corner_id, const gchar *value)
1237     if (value == NULL) return;
1238     SP3DBox *box = SP_3DBOX(object);
1240     std::pair<gdouble, gdouble> coord_pair = sp_3dbox_get_coord_pair_from_string (value);
1241     box->corners[corner_id] = NR::Point (coord_pair.first, coord_pair.second);
1242     sp_3dbox_recompute_corners (box, box->corners[2], box->corners[1], box->corners[5]);
1243     object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1246 static void
1247 sp_3dbox_update_perspective (Box3D::Perspective3D *persp, const gchar *value)
1249     // WARNING! This function changes the perspective associated to 'box'. Since there may be
1250     // many other boxes linked to the same perspective, their perspective is also changed.
1251     // If this behaviour is not desired in all cases, we need a different function.
1252     if (value == NULL) return;
1254     gchar **vps = g_strsplit( value, ",", 0);
1255     for (int i = 0; i < 15; ++i) {
1256         if (vps[i] == NULL) {
1257             g_warning ("Malformed svg attribute 'perspective'\n");
1258             return;
1259         }
1260     }
1262     persp->set_vanishing_point (Box3D::X, g_ascii_strtod (vps[0], NULL), g_ascii_strtod (vps[1], NULL),
1263                                           g_ascii_strtod (vps[2], NULL), g_ascii_strtod (vps[3], NULL),
1264                                           strcmp (vps[4], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
1265     persp->set_vanishing_point (Box3D::Y, g_ascii_strtod (vps[5], NULL), g_ascii_strtod (vps[6], NULL),
1266                                           g_ascii_strtod (vps[7], NULL), g_ascii_strtod (vps[8], NULL),
1267                                           strcmp (vps[9], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
1268     persp->set_vanishing_point (Box3D::Z, g_ascii_strtod (vps[10], NULL), g_ascii_strtod (vps[11], NULL),
1269                                           g_ascii_strtod (vps[12], NULL), g_ascii_strtod (vps[13], NULL),
1270                                           strcmp (vps[14], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
1272     // update the other boxes linked to the same perspective
1273     persp->reshape_boxes (Box3D::XYZ);
1276 /*
1277   Local Variables:
1278   mode:c++
1279   c-file-style:"stroustrup"
1280   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1281   indent-tabs-mode:nil
1282   fill-column:99
1283   End:
1284 */
1285 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :