Code

Remember last used style for single 3D box faces (also fixes bug with wrongly assigne...
[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"
23 static void sp_3dbox_class_init(SP3DBoxClass *klass);
24 static void sp_3dbox_init(SP3DBox *box3d);
26 static void sp_3dbox_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
27 static void sp_3dbox_release (SPObject *object);
28 static void sp_3dbox_set(SPObject *object, unsigned int key, const gchar *value);
29 static void sp_3dbox_update(SPObject *object, SPCtx *ctx, guint flags);
30 static Inkscape::XML::Node *sp_3dbox_write(SPObject *object, Inkscape::XML::Node *repr, guint flags);
32 static gchar *sp_3dbox_description(SPItem *item);
34 //static void sp_3dbox_set_shape(SPShape *shape);
35 //static void sp_3dbox_set_shape(SP3DBox *box3d);
37 static void sp_3dbox_update_corner_with_value_from_svg (SPObject *object, guint corner_id, const gchar *value);
38 static void sp_3dbox_update_perspective (Box3D::Perspective3D *persp, const gchar *value);
39 static gchar * sp_3dbox_get_corner_coords_string (SP3DBox *box, guint id);
40 static std::pair<gdouble, gdouble> sp_3dbox_get_coord_pair_from_string (const gchar *);
41 static gchar * sp_3dbox_get_perspective_string (SP3DBox *box);
43 static SPGroupClass *parent_class;
45 static gint counter = 0;
47 GType
48 sp_3dbox_get_type(void)
49 {
50     static GType type = 0;
52     if (!type) {
53         GTypeInfo info = {
54             sizeof(SP3DBoxClass),
55             NULL,   /* base_init */
56             NULL,   /* base_finalize */
57             (GClassInitFunc) sp_3dbox_class_init,
58             NULL,   /* class_finalize */
59             NULL,   /* class_data */
60             sizeof(SP3DBox),
61             16,     /* n_preallocs */
62             (GInstanceInitFunc) sp_3dbox_init,
63             NULL,   /* value_table */
64         };
65         type = g_type_register_static(SP_TYPE_GROUP, "SP3DBox", &info, (GTypeFlags) 0);
66     }
68     return type;
69 }
71 static void
72 sp_3dbox_class_init(SP3DBoxClass *klass)
73 {
74     SPObjectClass *sp_object_class = (SPObjectClass *) klass;
75     SPItemClass *item_class = (SPItemClass *) klass;
77     parent_class = (SPGroupClass *) g_type_class_ref(SP_TYPE_GROUP);
79     sp_object_class->build = sp_3dbox_build;
80     sp_object_class->set = sp_3dbox_set;
81     sp_object_class->write = sp_3dbox_write;
82     sp_object_class->update = sp_3dbox_update;
83     sp_object_class->release = sp_3dbox_release;
85     item_class->description = sp_3dbox_description;
86 }
88 static void
89 sp_3dbox_init(SP3DBox *box)
90 {
91     for (int i = 0; i < 8; ++i) box->corners[i] = NR::Point(0,0);
92     for (int i = 0; i < 6; ++i) box->faces[i] = NULL;
93 }
95 static void
96 sp_3dbox_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
97 {
98     if (((SPObjectClass *) (parent_class))->build) {
99         ((SPObjectClass *) (parent_class))->build(object, document, repr);
100     }
102     SP3DBox *box = SP_3DBOX (object);
104     box->my_counter = counter++;
106     /* we initialize the z-orders to zero so that they are updated during dragging */
107     for (int i = 0; i < 6; ++i) {
108         box->z_orders[i] = 0;
109     }
111     box->front_bits = 0x0;
113     
114     if (repr->attribute ("inkscape:perspective") == NULL) {
115         // we are creating a new box; link it to the current perspective
116         document->current_perspective->add_box (box);
117     } else {
118         // create a new perspective that we can compare with existing ones
119         Box3D::Perspective3D *persp = new Box3D::Perspective3D (Box3D::VanishingPoint (0,0),
120                                                                 Box3D::VanishingPoint (0,0),
121                                                                 Box3D::VanishingPoint (0,0),
122                                                                 document);
123         sp_3dbox_update_perspective (persp, repr->attribute ("inkscape:perspective"));
124         Box3D::Perspective3D *comp =  document->find_perspective (persp);
125         if (comp == NULL) {
126             // perspective doesn't exist yet
127             document->add_perspective (persp);
128             persp->add_box (box);
129         } else {
130             // link the box to the existing perspective and delete the temporary one
131             comp->add_box (box);
132             delete persp;
133             //g_assert (Box3D::get_persp_of_box (box) == comp);
135             // FIXME: If the paths of the box's faces do not correspond to the svg representation of the perspective
136             //        the box is shown with a "wrong" initial shape that is only corrected after dragging.
137             //        Should we "repair" this by updating the paths at the end of sp_3dbox_build()?
138             //        Maybe it would be better to simply destroy and rebuild them in sp_3dbox_link_to_existing_paths().
139         }
140     }
142     sp_object_read_attr(object, "inkscape:box3dcornerA");
143     sp_object_read_attr(object, "inkscape:box3dcornerB");
144     sp_object_read_attr(object, "inkscape:box3dcornerC");
146     // TODO: We create all faces in the beginning, but only the non-degenerate ones
147     //       should be written to the svg representation later in sp_3dbox_write.
148     Box3D::Axis cur_plane, axis, dir1, dir2;
149     Box3D::FrontOrRear cur_pos;
150     for (int i = 0; i < 3; ++i) {
151         for (int j = 0; j < 2; ++j) {
152             cur_plane = Box3D::planes[i];
153             cur_pos = Box3D::face_positions[j];
154             // FIXME: The following code could theoretically be moved to
155             //        the constructor of Box3DFace (but see the comment there).
156             axis = (cur_pos == Box3D::FRONT ? Box3D::NONE : Box3D::third_axis_direction (cur_plane));
157             dir1 = extract_first_axis_direction (cur_plane);
158             dir2 = extract_second_axis_direction (cur_plane);
159             
160             box->faces[Box3D::face_to_int(cur_plane ^ cur_pos)] =
161                 new Box3DFace (box, box->corners[axis], box->corners[axis ^ dir1],
162                                     box->corners[axis ^ dir1 ^ dir2], box->corners[axis ^ dir2],
163                                     cur_plane, cur_pos);
164         }
165     }
167     // Check whether the paths of the faces of the box need to be linked to existing paths in the
168     // document (e.g., after a 'redo' operation or after opening a file) and do so if necessary.
169     sp_3dbox_link_to_existing_paths (box, repr);
171     sp_3dbox_set_ratios (box, Box3D::XYZ);
174 static void
175 sp_3dbox_release (SPObject *object)
177         SP3DBox *box = SP_3DBOX(object);
178         for (int i = 0; i < 6; ++i) {
179             if (box->faces[i]) {
180                 delete box->faces[i]; // FIXME: Anything else to do? Do we need to clean up the face first?
181             }
182         }
184         // FIXME: We do not duplicate perspectives if they are the same for several boxes.
185         //        Thus, don't delete the perspective when deleting a box but rather unlink the box from it.
186         SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->remove_box (box);
188         if (((SPObjectClass *) parent_class)->release) {
189           ((SPObjectClass *) parent_class)->release (object);
190         }
193 static void sp_3dbox_set(SPObject *object, unsigned int key, const gchar *value)
195     switch (key) {
196         case SP_ATTR_INKSCAPE_3DBOX_CORNER_A:
197             sp_3dbox_update_corner_with_value_from_svg (object, 2, value);
198             break;
199         case SP_ATTR_INKSCAPE_3DBOX_CORNER_B:
200             sp_3dbox_update_corner_with_value_from_svg (object, 1, value);
201             break;
202         case SP_ATTR_INKSCAPE_3DBOX_CORNER_C:
203             sp_3dbox_update_corner_with_value_from_svg (object, 5, value);
204             break;
205         case SP_ATTR_INKSCAPE_3DBOX_PERSPECTIVE:
206         {
207             SP3DBox *box = SP_3DBOX (object);
208             sp_3dbox_update_perspective (SP_OBJECT_DOCUMENT (object)->get_persp_of_box (box), value);
209             break;
210         }
211         default:
212             if (((SPObjectClass *) (parent_class))->set) {
213                 ((SPObjectClass *) (parent_class))->set(object, key, value);
214             }
215             break;
216     }
219 static void
220 sp_3dbox_update(SPObject *object, SPCtx *ctx, guint flags)
222     if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
223         SP3DBox *box = SP_3DBOX(object);
224         sp_3dbox_link_to_existing_paths (box, SP_OBJECT_REPR(object));
225     }
227     /* Invoke parent method */
228     if (((SPObjectClass *) (parent_class))->update)
229         ((SPObjectClass *) (parent_class))->update(object, ctx, flags);
234 static Inkscape::XML::Node *sp_3dbox_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
236     SP3DBox *box = SP_3DBOX(object);
237     // FIXME: How to handle other contexts???
238     // FIXME: Is tools_isactive(..) more recommended to check for the current context/tool?
239     if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
240         return repr;
242     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
243         Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
244         repr = xml_doc->createElement("svg:g");
245         repr->setAttribute("sodipodi:type", "inkscape:3dbox");
246         /* Hook paths to the faces of the box */
247         for (int i = 0; i < 6; ++i) {
248             box->faces[i]->hook_path_to_3dbox();
249         }
250     }
252     for (int i = 0; i < 6; ++i) {
253         box->faces[i]->set_path_repr();
254     }
256     if (flags & SP_OBJECT_WRITE_EXT) {
257         gchar *str;
258         str = sp_3dbox_get_corner_coords_string (box, 2);
259         repr->setAttribute("inkscape:box3dcornerA", str);
261         str = sp_3dbox_get_corner_coords_string (box, 1);
262         repr->setAttribute("inkscape:box3dcornerB", str);
264         str = sp_3dbox_get_corner_coords_string (box, 5);
265         repr->setAttribute("inkscape:box3dcornerC", str);
267         str = sp_3dbox_get_perspective_string (box);
268         repr->setAttribute("inkscape:perspective", str);
269         sp_3dbox_set_ratios (box);
271         g_free ((void *) str);
272     }
274     if (((SPObjectClass *) (parent_class))->write) {
275         ((SPObjectClass *) (parent_class))->write(object, repr, flags);
276     }
278     return repr;
281 static gchar *
282 sp_3dbox_description(SPItem *item)
284     g_return_val_if_fail(SP_IS_3DBOX(item), NULL);
286     return g_strdup(_("<b>3D Box</b>"));
289 void sp_3dbox_set_ratios (SP3DBox *box, Box3D::Axis axes)
291     Box3D::Perspective3D *persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
292     NR::Point pt;
294     if (axes & Box3D::X) {
295         pt = persp->get_vanishing_point (Box3D::X)->get_pos();
296         box->ratio_x = NR::L2 (pt - box->corners[2]) / NR::L2 (pt - box->corners[3]);
297     }
299     if (axes & Box3D::Y) {
300         pt = persp->get_vanishing_point (Box3D::Y)->get_pos();
301         box->ratio_y = NR::L2 (pt - box->corners[2]) / NR::L2 (pt - box->corners[0]);
302     }
304     if (axes & Box3D::Z) {
305         pt = persp->get_vanishing_point (Box3D::Z)->get_pos();
306         box->ratio_z = NR::L2 (pt - box->corners[4]) / NR::L2 (pt - box->corners[0]);
307     }
310 void
311 sp_3dbox_switch_front_face (SP3DBox *box, Box3D::Axis axis)
313     if (SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point (axis)->is_finite()) {
314         box->front_bits = box->front_bits ^ axis;
315     }
319 void
320 sp_3dbox_position_set (SP3DBoxContext &bc)
322     SP3DBox *box3d = SP_3DBOX(bc.item);
324     sp_3dbox_set_shape(box3d);
326     // FIXME: Why does the following call not automatically update the children
327     //        of box3d (which is an SPGroup, which should do this)?
328     //SP_OBJECT(box3d)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
330     /**
331     SP_OBJECT(box3d->path_face1)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
332     SP_OBJECT(box3d->path_face2)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
333     SP_OBJECT(box3d->path_face3)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
334     SP_OBJECT(box3d->path_face4)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
335     SP_OBJECT(box3d->path_face5)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
336     SP_OBJECT(box3d->path_face6)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
337     ***/
340 //static
341 void
342 // FIXME: Note that this is _not_ the virtual set_shape() method inherited from SPShape,
343 //        since SP3DBox is inherited from SPGroup. The following method is "artificially"
344 //        called from sp_3dbox_update().
345 //sp_3dbox_set_shape(SPShape *shape)
346 sp_3dbox_set_shape(SP3DBox *box3d, bool use_previous_corners)
348     // FIXME: How to handle other contexts???
349     // FIXME: Is tools_isactive(..) more recommended to check for the current context/tool?
350     if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
351         return;
352     SP3DBoxContext *bc = SP_3DBOX_CONTEXT(inkscape_active_event_context());
354     /* Only update the curves during dragging; setting the svg representations 
355        is expensive and only done once at the end */
356     if (!use_previous_corners) {
357         sp_3dbox_recompute_corners (box3d, bc->drag_origin, bc->drag_ptB, bc->drag_ptC);
358     } else {
359         sp_3dbox_recompute_corners (box3d, box3d->corners[2], box3d->corners[1], box3d->corners[5]);
360     }
361     if (bc->extruded) {
362         box3d->faces[0]->set_corners (box3d->corners[0], box3d->corners[4], box3d->corners[6], box3d->corners[2]);
363         box3d->faces[1]->set_corners (box3d->corners[1], box3d->corners[5], box3d->corners[7], box3d->corners[3]);
364         box3d->faces[2]->set_corners (box3d->corners[0], box3d->corners[1], box3d->corners[5], box3d->corners[4]);
365         box3d->faces[3]->set_corners (box3d->corners[2], box3d->corners[3], box3d->corners[7], box3d->corners[6]);
366         box3d->faces[5]->set_corners (box3d->corners[4], box3d->corners[5], box3d->corners[7], box3d->corners[6]);
367     }
368     box3d->faces[4]->set_corners (box3d->corners[0], box3d->corners[1], box3d->corners[3], box3d->corners[2]);
370     sp_3dbox_update_curves (box3d);
374 void sp_3dbox_recompute_corners (SP3DBox *box, NR::Point const A, NR::Point const B, NR::Point const C)
376     sp_3dbox_move_corner_in_XY_plane (box, 2, A);
377     sp_3dbox_move_corner_in_XY_plane (box, 1, B);
378     sp_3dbox_move_corner_in_Z_direction (box, 5, C);
381 inline static double
382 normalized_angle (double angle) {
383     if (angle < -M_PI) {
384         return angle + 2*M_PI;
385     } else if (angle > M_PI) {
386         return angle - 2*M_PI;
387     }
388     return angle;
391 static gdouble
392 sp_3dbox_corner_angle_to_VP (SP3DBox *box, Box3D::Axis axis, guint extreme_corner)
394     Box3D::VanishingPoint *vp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point (axis);
395     NR::Point dir;
397     if (vp->is_finite()) {
398         dir = NR::unit_vector (vp->get_pos() - box->corners[extreme_corner]);
399     } else {
400         dir = NR::unit_vector (vp->v_dir);
401     }
403     return atan2 (dir[NR::Y], dir[NR::X]);
407 bool sp_3dbox_recompute_z_orders (SP3DBox *box)
409     guint new_z_orders[6];
411     Box3D::Perspective3D *persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
413     // TODO: Determine the front corner depending on the distance from VPs and/or the user presets
414     guint front_corner = sp_3dbox_get_front_corner_id (box);
416     gdouble dir_1x = sp_3dbox_corner_angle_to_VP (box, Box3D::X, front_corner);
417     gdouble dir_3x = sp_3dbox_corner_angle_to_VP (box, Box3D::X, front_corner ^ Box3D::Y);
419     gdouble dir_1y = sp_3dbox_corner_angle_to_VP (box, Box3D::Y, front_corner);
420     gdouble dir_0y = sp_3dbox_corner_angle_to_VP (box, Box3D::Y, front_corner ^ Box3D::X);
422     gdouble dir_1z = sp_3dbox_corner_angle_to_VP (box, Box3D::Z, front_corner);
423     gdouble dir_3z = sp_3dbox_corner_angle_to_VP (box, Box3D::Z, front_corner ^ Box3D::Y);
425     // Still not perfect, but only fails in some rather degenerate cases.
426     // I suspect that there is a more elegant model, though. :)
427     new_z_orders[0] = Box3D::face_containing_corner (Box3D::XY, front_corner);
428     if (normalized_angle (dir_1y - dir_1z) > 0) {
429         new_z_orders[1] = Box3D::face_containing_corner (Box3D::YZ, front_corner);
430         if (normalized_angle (dir_1x - dir_1z) > 0) {
431             new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner ^ Box3D::Y);
432         } else {
433             new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
434         }
435     } else {
436         if (normalized_angle (dir_3x - dir_3z) > 0) {
437             new_z_orders[1] = Box3D::face_containing_corner (Box3D::XZ, front_corner ^ Box3D::Y);
438             new_z_orders[2] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
439         } else {
440             if (normalized_angle (dir_1x - dir_1z) > 0) {
441                 new_z_orders[1] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
442                 new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
443             } else {
444                 new_z_orders[1] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
445                 new_z_orders[2] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
446             }
447         }
448     }
450     new_z_orders[3] = Box3D::opposite_face (new_z_orders[2]);
451     new_z_orders[4] = Box3D::opposite_face (new_z_orders[1]);
452     new_z_orders[5] = Box3D::opposite_face (new_z_orders[0]);
454     /* We only need to look for changes among the topmost three faces because the order
455        of the other ones is just inverted. */
456     if ((box->z_orders[0] != new_z_orders[0]) ||
457         (box->z_orders[1] != new_z_orders[1]) ||
458         (box->z_orders[2] != new_z_orders[2]))
459     {
460         for (int i = 0; i < 6; ++i) {
461             box->z_orders[i] = new_z_orders[i];
462         }
463         return true;
464     }
466     return false;
469 void sp_3dbox_set_z_orders (SP3DBox *box)
471     GSList *items = sp_item_group_item_list(SP_GROUP(box));
473     // For efficiency reasons, we only set the new z-orders if something really changed
474     if (sp_3dbox_recompute_z_orders (box)) {
475         box->faces[box->z_orders[0]]->lower_to_bottom ();
476         box->faces[box->z_orders[1]]->lower_to_bottom ();
477         box->faces[box->z_orders[2]]->lower_to_bottom ();
478         box->faces[box->z_orders[3]]->lower_to_bottom ();
479         box->faces[box->z_orders[4]]->lower_to_bottom ();
480         box->faces[box->z_orders[5]]->lower_to_bottom ();
481     }
484 void
485 sp_3dbox_update_curves (SP3DBox *box) {
486     for (int i = 0; i < 6; ++i) {
487         if (box->faces[i]) box->faces[i]->set_curve();
488     }
491 /**
492  * In some situations (e.g., after cloning boxes, undo & redo, or reading boxes from a file) there are
493  * paths already present in the document which correspond to the faces of newly created boxes, but their
494  * 'path' members don't link to them yet. The following function corrects this if necessary.
495  */
496 void
497 sp_3dbox_link_to_existing_paths (SP3DBox *box, Inkscape::XML::Node *repr) {
498     // TODO: We should probably destroy the existing paths and recreate them because we don't know
499     //       precisely which path corresponds to which face. Does this make a difference?
500     //       In sp_3dbox_write we write the correct paths anyway, don't we? But we could get into
501     //       trouble at a later stage when we only write single faces for degenerate boxes.
503     SPDocument *document = SP_OBJECT_DOCUMENT(box);
504     guint face_id = 0;
506     for (Inkscape::XML::Node *i = sp_repr_children(repr); i != NULL; i = sp_repr_next(i)) {
507         if (face_id > 5) {
508             g_warning ("SVG representation of 3D boxes must contain 6 paths or less.\n");
509             break;
510         }
512         SPObject *face_object = document->getObjectByRepr((Inkscape::XML::Node *) i);
513         if (!SP_IS_PATH(face_object)) {
514             g_warning ("SVG representation of 3D boxes should only contain paths.\n");
515             continue;
516         }
517         // TODO: Currently we don't check whether all paths are being linked to different faces.
518         //       This is no problem with valid SVG files. It may lead to crashes, however,
519         //       in case a file is corrupt (e.g., two or more faces have identical descriptions).
520         gint id = Box3DFace::descr_to_id (i->attribute ("inkscape:box3dface"));
521         box->faces[id]->hook_path_to_3dbox(SP_PATH(face_object));
522         ++face_id;
523     }
524     if (face_id < 6) {
525         //g_warning ("SVG representation of 3D boxes should contain exactly 6 paths (degenerate boxes are not yet supported).\n");
526         // TODO: Check whether it is safe to add the remaining paths to the box and do so in case it is.
527         //       (But we also land here for newly created boxes where we shouldn't add any paths because
528         //       This is done in sp_3dbox_write later on.
529     }
532 void
533 sp_3dbox_move_corner_in_XY_plane (SP3DBox *box, guint id, NR::Point pt, Box3D::Axis axes)
535     Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
537     NR::Point A (box->corners[id ^ Box3D::XY]);
538     if (Box3D::is_single_axis_direction (axes)) {
539         pt = Box3D::PerspectiveLine (box->corners[id], axes, persp).closest_to(pt);
540     }
542     /* set the 'front' corners */
543     box->corners[id] = pt;
545     Box3D::PerspectiveLine pl_one (A, Box3D::Y, persp);
546     Box3D::PerspectiveLine pl_two (pt, Box3D::X, persp);
547     box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
549     pl_one = Box3D::PerspectiveLine (A, Box3D::X, persp);
550     pl_two = Box3D::PerspectiveLine (pt, Box3D::Y, persp);
551     box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
553     /* set the 'rear' corners */
554     NR::Point B (box->corners[id ^ Box3D::XYZ]);
556     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Z, persp);
557     pl_two = Box3D::PerspectiveLine (B, Box3D::Y, persp);
558     box->corners[id ^ Box3D::XZ] = pl_one.meet(pl_two);
560     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XZ], Box3D::X, persp);
561     pl_two = Box3D::PerspectiveLine (pt, Box3D::Z, persp);
562     box->corners[id ^ Box3D::Z] = pl_one.meet(pl_two);
564     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::Z], Box3D::Y, persp);
565     pl_two = Box3D::PerspectiveLine (B, Box3D::X, persp);
566     box->corners[id ^ Box3D::YZ] = pl_one.meet(pl_two);
567     
570 void
571 sp_3dbox_move_corner_in_Z_direction (SP3DBox *box, guint id, NR::Point pt, bool constrained)
573     if (!constrained) sp_3dbox_move_corner_in_XY_plane (box, id, pt, Box3D::XY);
575     Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
577     /* set the four corners of the face containing corners[id] */
578     box->corners[id] = Box3D::PerspectiveLine (box->corners[id], Box3D::Z, persp).closest_to(pt);
580     Box3D::PerspectiveLine pl_one (box->corners[id], Box3D::X, persp);
581     Box3D::PerspectiveLine pl_two (box->corners[id ^ Box3D::XZ], Box3D::Z, persp);
582     box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
584     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Y, persp);
585     pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XYZ], Box3D::Z, persp);
586     box->corners[id ^ Box3D::XY] = pl_one.meet(pl_two);
588     pl_one = Box3D::PerspectiveLine (box->corners[id], Box3D::Y, persp);
589     pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::YZ], Box3D::Z, persp);
590     box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
593 static void
594 sp_3dbox_reshape_edge_after_VP_toggling (SP3DBox *box, const guint corner, const Box3D::Axis axis, Box3D::Perspective3D *persp)
596     /* Hmm, perhaps we should simply use one of the corners as the pivot point.
597        But this way we minimize the amount of reshaping.
598        On second thought, we need to find a way to ensure that all boxes sharing the same
599        perspective are updated consistently _as a group_. That is, they should also retain
600        their relative positions towards each other. */
601     NR::Maybe<NR::Point> pt = sp_3dbox_get_midpoint_between_corners (box, corner, corner ^ axis);
602     g_return_if_fail (pt);
604     Box3D::Axis axis2 = ((axis == Box3D::Y) ? Box3D::X : Box3D::Y);
606     Box3D::PerspectiveLine line1 (box->corners[corner], axis2, persp);
607     Box3D::PerspectiveLine line2 (box->corners[corner ^ axis], axis2, persp);
609     Box3D::PerspectiveLine line3 (*pt, axis, persp);
611     NR::Point new_corner1 = line1.meet (line3);
612     NR::Point new_corner2 = line2.meet (line3);
614     box->corners[corner] = new_corner1;
615     box->corners[corner ^ axis] = new_corner2;
618 void
619 sp_3dbox_reshape_after_VP_toggling (SP3DBox *box, Box3D::Axis axis)
621     Box3D::Perspective3D *persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
622     std::pair<Box3D::Axis, Box3D::Axis> dirs = Box3D::get_remaining_axes (axis);
624     sp_3dbox_reshape_edge_after_VP_toggling (box, 0, axis, persp);
625     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first, axis, persp);
626     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first ^ dirs.second, axis, persp);
627     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.second, axis, persp);
630 NR::Maybe<NR::Point>
631 sp_3dbox_get_center (SP3DBox *box)
633     return sp_3dbox_get_midpoint_between_corners (box, 0, 7);
636 // TODO: The following function can probably be rewritten in a much more elegant and robust way
637 //        by using projective coordinates for all points and using the cross ratio.
638 NR::Maybe<NR::Point>
639 sp_3dbox_get_midpoint_between_corners (SP3DBox *box, guint id_corner1, guint id_corner2)
641     Box3D::Axis corner_axes = (Box3D::Axis) (id_corner1 ^ id_corner2);
643     // Is all this sufficiently precise also for degenerate cases?
644     if (sp_3dbox_corners_are_adjacent (id_corner1, id_corner2)) {
645         Box3D::Axis orth_dir = get_perpendicular_axis_direction (corner_axes);
647         Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2 ^ orth_dir]);
648         Box3D::Line diag2 (box->corners[id_corner1 ^ orth_dir], box->corners[id_corner2]);
649         NR::Maybe<NR::Point> adjacent_face_center = diag1.intersect(diag2);
651         if (!adjacent_face_center) return NR::Nothing();
653         Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
655         Box3D::PerspectiveLine pl (*adjacent_face_center, orth_dir, persp);
656         return pl.intersect(Box3D::PerspectiveLine(box->corners[id_corner1], corner_axes, persp));
657     } else {
658         Box3D::Axis dir = Box3D::extract_first_axis_direction (corner_axes);
659         Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2]);
660         Box3D::Line diag2 (box->corners[id_corner1 ^ dir], box->corners[id_corner2 ^ dir]);
661         return diag1.intersect(diag2);
662     }
665 static gchar *
666 sp_3dbox_get_corner_coords_string (SP3DBox *box, guint id)
668     id = id % 8;
669     Inkscape::SVGOStringStream os;
670     os << box->corners[id][NR::X] << "," << box->corners[id][NR::Y];
671     return g_strdup(os.str().c_str());
674 static std::pair<gdouble, gdouble>
675 sp_3dbox_get_coord_pair_from_string (const gchar *coords)
677     gchar **coordpair = g_strsplit( coords, ",", 0);
678     // We might as well rely on g_ascii_strtod to convert the NULL pointer to 0.0,
679     // but we include the following test anyway
680     if (coordpair[0] == NULL || coordpair[1] == NULL) {
681         g_strfreev (coordpair);
682         g_warning ("Coordinate conversion failed.\n");
683         return std::make_pair(0.0, 0.0);
684     }
686     gdouble coord1 = g_ascii_strtod(coordpair[0], NULL);
687     gdouble coord2 = g_ascii_strtod(coordpair[1], NULL);
688     g_strfreev (coordpair);
690     return std::make_pair(coord1, coord2);
693 static gchar *
694 sp_3dbox_get_perspective_string (SP3DBox *box)
696     
697     return sp_3dbox_get_svg_descr_of_persp (SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box));
699   
700 gchar *
701 sp_3dbox_get_svg_descr_of_persp (Box3D::Perspective3D *persp)
703     // FIXME: We should move this code to perspective3d.cpp, but this yields compiler errors. Why?
704     Inkscape::SVGOStringStream os;
706     Box3D::VanishingPoint vp = *(persp->get_vanishing_point (Box3D::X));
707     os << vp[NR::X] << "," << vp[NR::Y] << ",";
708     os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
709     if (vp.is_finite()) {
710         os << "finite,";
711     } else {
712         os << "infinite,";
713     }
715     vp = *(persp->get_vanishing_point (Box3D::Y));
716     os << vp[NR::X] << "," << vp[NR::Y] << ",";
717     os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
718     if (vp.is_finite()) {
719         os << "finite,";
720     } else {
721         os << "infinite,";
722     }
724     vp = *(persp->get_vanishing_point (Box3D::Z));
725     os << vp[NR::X] << "," << vp[NR::Y] << ",";
726     os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
727     if (vp.is_finite()) {
728         os << "finite";
729     } else {
730         os << "infinite";
731     }
733     return g_strdup(os.str().c_str());
736 void sp_3dbox_update_perspective_lines()
738     SPEventContext *ec = inkscape_active_event_context();
739     if (!SP_IS_3DBOX_CONTEXT (ec))
740         return;
742     SP_3DBOX_CONTEXT (ec)->_vpdrag->updateLines();
745 /*
746  * Manipulates corner1 through corner4 to contain the indices of the corners
747  * from which the perspective lines in the direction of 'axis' emerge
748  */
749 void sp_3dbox_corners_for_perspective_lines (const SP3DBox * box, Box3D::Axis axis, 
750                                              NR::Point &corner1, NR::Point &corner2, NR::Point &corner3, NR::Point &corner4)
752     // along which axis to switch when takint
753     Box3D::Axis switch_axis;
754     if (axis == Box3D::X || axis == Box3D::Y) {
755         switch_axis = (box->front_bits & axis) ? Box3D::Z : Box3D::NONE;
756     } else {
757         switch_axis = (box->front_bits & axis) ? Box3D::X : Box3D::NONE;
758     }
760     switch (axis) {
761         case Box3D::X:
762             corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
763             corner2 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
764             corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
765             corner4 = sp_3dbox_get_corner_along_edge (box, 6 ^ switch_axis, axis, Box3D::REAR);
766             break;
767         case Box3D::Y:
768             corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
769             corner2 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
770             corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
771             corner4 = sp_3dbox_get_corner_along_edge (box, 5 ^ switch_axis, axis, Box3D::REAR);
772             break;
773         case Box3D::Z:
774             corner1 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
775             corner2 = sp_3dbox_get_corner_along_edge (box, 3 ^ switch_axis, axis, Box3D::REAR);
776             corner3 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
777             corner4 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
778             break;
779     }            
782 /**
783  * Returns the id of the corner on the edge along 'axis' and passing through 'corner' that
784  * lies on the front/rear face in this direction.
785  */
786 guint
787 sp_3dbox_get_corner_id_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
789     guint result;
790     guint other_corner = corner ^ axis;
791     Box3D::VanishingPoint *vp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point(axis);
792     if (vp->is_finite()) {
793         result = (  NR::L2 (vp->get_pos() - box->corners[corner])
794                   < NR::L2 (vp->get_pos() - box->corners[other_corner]) ? other_corner : corner);
795     } else {
796         // clear the axis bit and switch to the appropriate corner along axis, depending on the value of front_bits
797         result = ((corner & (0xF ^ axis)) ^ (box->front_bits & axis));
798     }
800     if (rel_pos == Box3D::FRONT) {
801         return result;
802     } else {
803         return result ^ axis;
804     }
807 NR::Point
808 sp_3dbox_get_corner_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
810     return box->corners[sp_3dbox_get_corner_id_along_edge (box, corner, axis, rel_pos)];
813 guint
814 sp_3dbox_get_front_corner_id (const SP3DBox *box)
816     guint front_corner = 1; // this could in fact be any corner, but we choose the one that is normally in front
817     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::X, Box3D::FRONT);
818     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Y, Box3D::FRONT);
819     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Z, Box3D::FRONT);
820     return front_corner;
823 // auxiliary functions
824 static void
825 sp_3dbox_update_corner_with_value_from_svg (SPObject *object, guint corner_id, const gchar *value)
827     if (value == NULL) return;
828     SP3DBox *box = SP_3DBOX(object);
830     std::pair<gdouble, gdouble> coord_pair = sp_3dbox_get_coord_pair_from_string (value);
831     box->corners[corner_id] = NR::Point (coord_pair.first, coord_pair.second);
832     sp_3dbox_recompute_corners (box, box->corners[2], box->corners[1], box->corners[5]);
833     object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
836 static void
837 sp_3dbox_update_perspective (Box3D::Perspective3D *persp, const gchar *value)
839     // WARNING! This function changes the perspective associated to 'box'. Since there may be
840     // many other boxes linked to the same perspective, their perspective is also changed.
841     // If this behaviour is not desired in all cases, we need a different function.
842     if (value == NULL) return;
844     gchar **vps = g_strsplit( value, ",", 0);
845     for (int i = 0; i < 15; ++i) {
846         if (vps[i] == NULL) {
847             g_warning ("Malformed svg attribute 'perspective'\n");
848             return;
849         }
850     }
852     persp->set_vanishing_point (Box3D::X, g_ascii_strtod (vps[0], NULL), g_ascii_strtod (vps[1], NULL),
853                                           g_ascii_strtod (vps[2], NULL), g_ascii_strtod (vps[3], NULL),
854                                           strcmp (vps[4], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
855     persp->set_vanishing_point (Box3D::Y, g_ascii_strtod (vps[5], NULL), g_ascii_strtod (vps[6], NULL),
856                                           g_ascii_strtod (vps[7], NULL), g_ascii_strtod (vps[8], NULL),
857                                           strcmp (vps[9], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
858     persp->set_vanishing_point (Box3D::Z, g_ascii_strtod (vps[10], NULL), g_ascii_strtod (vps[11], NULL),
859                                           g_ascii_strtod (vps[12], NULL), g_ascii_strtod (vps[13], NULL),
860                                           strcmp (vps[14], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
862     // update the other boxes linked to the same perspective
863     persp->reshape_boxes (Box3D::XYZ);
866 /*
867   Local Variables:
868   mode:c++
869   c-file-style:"stroustrup"
870   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
871   indent-tabs-mode:nil
872   fill-column:99
873   End:
874 */
875 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :