Code

dcbe5b531ab3abc057f43f6afa23b1d110498ef0
[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];
186 static void
187 sp_3dbox_release (SPObject *object)
189         SP3DBox *box = SP_3DBOX(object);
190         for (int i = 0; i < 6; ++i) {
191             if (box->faces[i]) {
192                 delete box->faces[i]; // FIXME: Anything else to do? Do we need to clean up the face first?
193             }
194         }
196         // FIXME: We do not duplicate perspectives if they are the same for several boxes.
197         //        Thus, don't delete the perspective when deleting a box but rather unlink the box from it.
198         SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->remove_box (box);
200         if (((SPObjectClass *) parent_class)->release) {
201           ((SPObjectClass *) parent_class)->release (object);
202         }
205 static void sp_3dbox_set(SPObject *object, unsigned int key, const gchar *value)
207     switch (key) {
208         case SP_ATTR_INKSCAPE_3DBOX_CORNER_A:
209             sp_3dbox_update_corner_with_value_from_svg (object, 2, value);
210             break;
211         case SP_ATTR_INKSCAPE_3DBOX_CORNER_B:
212             sp_3dbox_update_corner_with_value_from_svg (object, 1, value);
213             break;
214         case SP_ATTR_INKSCAPE_3DBOX_CORNER_C:
215             sp_3dbox_update_corner_with_value_from_svg (object, 5, value);
216             break;
217         case SP_ATTR_INKSCAPE_3DBOX_PERSPECTIVE:
218         {
219             SP3DBox *box = SP_3DBOX (object);
220             sp_3dbox_update_perspective (SP_OBJECT_DOCUMENT (object)->get_persp_of_box (box), value);
221             break;
222         }
223         default:
224             if (((SPObjectClass *) (parent_class))->set) {
225                 ((SPObjectClass *) (parent_class))->set(object, key, value);
226             }
227             break;
228     }
231 static void
232 sp_3dbox_update(SPObject *object, SPCtx *ctx, guint flags)
234     if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
235         SP3DBox *box = SP_3DBOX(object);
236         sp_3dbox_link_to_existing_paths (box, SP_OBJECT_REPR(object));
237     }
239     /* Invoke parent method */
240     if (((SPObjectClass *) (parent_class))->update)
241         ((SPObjectClass *) (parent_class))->update(object, ctx, flags);
246 static Inkscape::XML::Node *sp_3dbox_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
248     SP3DBox *box = SP_3DBOX(object);
249     // FIXME: How to handle other contexts???
250     // FIXME: Is tools_isactive(..) more recommended to check for the current context/tool?
251     if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
252         return repr;
254     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
255         Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
256         repr = xml_doc->createElement("svg:g");
257         repr->setAttribute("sodipodi:type", "inkscape:3dbox");
258         /* Hook paths to the faces of the box */
259         for (int i = 0; i < 6; ++i) {
260             box->faces[i]->hook_path_to_3dbox();
261         }
262     }
264     for (int i = 0; i < 6; ++i) {
265         box->faces[i]->set_path_repr();
266     }
268     if (flags & SP_OBJECT_WRITE_EXT) {
269         gchar *str;
270         str = sp_3dbox_get_corner_coords_string (box, 2);
271         repr->setAttribute("inkscape:box3dcornerA", str);
273         str = sp_3dbox_get_corner_coords_string (box, 1);
274         repr->setAttribute("inkscape:box3dcornerB", str);
276         str = sp_3dbox_get_corner_coords_string (box, 5);
277         repr->setAttribute("inkscape:box3dcornerC", str);
279         str = sp_3dbox_get_perspective_string (box);
280         repr->setAttribute("inkscape:perspective", str);
281         sp_3dbox_set_ratios (box);
283         g_free ((void *) str);
285         /* store center and construction-corners for later use during center-dragging */
286         NR::Maybe<NR::Point> cen = sp_3dbox_get_center (box);
287         if (cen) {
288             box->old_center = *cen;
289         }
290         box->old_corner2 = box->corners[2];
291         box->old_corner1 = box->corners[1];
292         box->old_corner0 = box->corners[0];
293         box->old_corner3 = box->corners[3];
294         box->old_corner5 = box->corners[5];
295     }
297     if (((SPObjectClass *) (parent_class))->write) {
298         ((SPObjectClass *) (parent_class))->write(object, repr, flags);
299     }
301     return repr;
304 static gchar *
305 sp_3dbox_description(SPItem *item)
307     g_return_val_if_fail(SP_IS_3DBOX(item), NULL);
309     return g_strdup(_("<b>3D Box</b>"));
312 void sp_3dbox_set_ratios (SP3DBox *box, Box3D::Axis axes)
314     Box3D::Perspective3D *persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
315     NR::Point pt;
317     if (axes & Box3D::X) {
318         pt = persp->get_vanishing_point (Box3D::X)->get_pos();
319         box->ratio_x = NR::L2 (pt - box->corners[2]) / NR::L2 (pt - box->corners[3]);
320     }
322     if (axes & Box3D::Y) {
323         pt = persp->get_vanishing_point (Box3D::Y)->get_pos();
324         box->ratio_y = NR::L2 (pt - box->corners[2]) / NR::L2 (pt - box->corners[0]);
325     }
327     if (axes & Box3D::Z) {
328         pt = persp->get_vanishing_point (Box3D::Z)->get_pos();
329         box->ratio_z = NR::L2 (pt - box->corners[4]) / NR::L2 (pt - box->corners[0]);
330     }
333 void
334 sp_3dbox_switch_front_face (SP3DBox *box, Box3D::Axis axis)
336     if (SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point (axis)->is_finite()) {
337         box->front_bits = box->front_bits ^ axis;
338     }
342 void
343 sp_3dbox_position_set (SP3DBoxContext &bc)
345     SP3DBox *box3d = SP_3DBOX(bc.item);
347     sp_3dbox_set_shape(box3d);
349     // FIXME: Why does the following call not automatically update the children
350     //        of box3d (which is an SPGroup, which should do this)?
351     //SP_OBJECT(box3d)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
353     /**
354     SP_OBJECT(box3d->path_face1)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
355     SP_OBJECT(box3d->path_face2)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
356     SP_OBJECT(box3d->path_face3)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
357     SP_OBJECT(box3d->path_face4)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
358     SP_OBJECT(box3d->path_face5)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
359     SP_OBJECT(box3d->path_face6)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
360     ***/
363 static void
364 sp_3dbox_set_shape_from_points (SP3DBox *box, NR::Point const &cornerA, NR::Point const &cornerB, NR::Point const &cornerC)
366     sp_3dbox_recompute_corners (box, cornerA, cornerB, cornerC);
368     // FIXME: How to handle other contexts???
369     // FIXME: Is tools_isactive(..) more recommended to check for the current context/tool?
370     if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
371         return;
372     SP3DBoxContext *bc = SP_3DBOX_CONTEXT(inkscape_active_event_context());
374     if (bc->extruded) {
375         box->faces[0]->set_corners (box->corners[0], box->corners[4], box->corners[6], box->corners[2]);
376         box->faces[1]->set_corners (box->corners[1], box->corners[5], box->corners[7], box->corners[3]);
377         box->faces[2]->set_corners (box->corners[0], box->corners[1], box->corners[5], box->corners[4]);
378         box->faces[3]->set_corners (box->corners[2], box->corners[3], box->corners[7], box->corners[6]);
379         box->faces[5]->set_corners (box->corners[4], box->corners[5], box->corners[7], box->corners[6]);
380     }
381     box->faces[4]->set_corners (box->corners[0], box->corners[1], box->corners[3], box->corners[2]);
383     sp_3dbox_update_curves (box);
386 void
387 // FIXME: Note that this is _not_ the virtual set_shape() method inherited from SPShape,
388 //        since SP3DBox is inherited from SPGroup. The following method is "artificially"
389 //        called from sp_3dbox_update().
390 //sp_3dbox_set_shape(SPShape *shape)
391 sp_3dbox_set_shape(SP3DBox *box, bool use_previous_corners)
393     if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
394         return;
395     SP3DBoxContext *bc = SP_3DBOX_CONTEXT(inkscape_active_event_context());
397     if (!use_previous_corners) {
398         sp_3dbox_set_shape_from_points (box, bc->drag_origin, bc->drag_ptB, bc->drag_ptC);
399     } else {
400         sp_3dbox_set_shape_from_points (box, box->corners[2], box->corners[1], box->corners[5]);
401     }
405 void sp_3dbox_recompute_corners (SP3DBox *box, NR::Point const A, NR::Point const B, NR::Point const C)
407     sp_3dbox_move_corner_in_XY_plane (box, 2, A);
408     sp_3dbox_move_corner_in_XY_plane (box, 1, B);
409     sp_3dbox_move_corner_in_Z_direction (box, 5, C);
412 inline static double
413 normalized_angle (double angle) {
414     if (angle < -M_PI) {
415         return angle + 2*M_PI;
416     } else if (angle > M_PI) {
417         return angle - 2*M_PI;
418     }
419     return angle;
422 static gdouble
423 sp_3dbox_corner_angle_to_VP (SP3DBox *box, Box3D::Axis axis, guint extreme_corner)
425     Box3D::VanishingPoint *vp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point (axis);
426     NR::Point dir;
428     if (vp->is_finite()) {
429         dir = NR::unit_vector (vp->get_pos() - box->corners[extreme_corner]);
430     } else {
431         dir = NR::unit_vector (vp->v_dir);
432     }
434     return atan2 (dir[NR::Y], dir[NR::X]);
438 bool sp_3dbox_recompute_z_orders (SP3DBox *box)
440     guint new_z_orders[6];
442     // TODO: Determine the front corner depending on the distance from VPs and/or the user presets
443     guint front_corner = sp_3dbox_get_front_corner_id (box);
445     gdouble dir_1x = sp_3dbox_corner_angle_to_VP (box, Box3D::X, front_corner);
446     gdouble dir_3x = sp_3dbox_corner_angle_to_VP (box, Box3D::X, front_corner ^ Box3D::Y);
448     gdouble dir_1y = sp_3dbox_corner_angle_to_VP (box, Box3D::Y, front_corner);
449     //gdouble dir_0y = sp_3dbox_corner_angle_to_VP (box, Box3D::Y, front_corner ^ Box3D::X);
451     gdouble dir_1z = sp_3dbox_corner_angle_to_VP (box, Box3D::Z, front_corner);
452     gdouble dir_3z = sp_3dbox_corner_angle_to_VP (box, Box3D::Z, front_corner ^ Box3D::Y);
454     // Still not perfect, but only fails in some rather degenerate cases.
455     // I suspect that there is a more elegant model, though. :)
456     new_z_orders[0] = Box3D::face_containing_corner (Box3D::XY, front_corner);
457     if (normalized_angle (dir_1y - dir_1z) > 0) {
458         new_z_orders[1] = Box3D::face_containing_corner (Box3D::YZ, front_corner);
459         if (normalized_angle (dir_1x - dir_1z) > 0) {
460             new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner ^ Box3D::Y);
461         } else {
462             new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
463         }
464     } else {
465         if (normalized_angle (dir_3x - dir_3z) > 0) {
466             new_z_orders[1] = Box3D::face_containing_corner (Box3D::XZ, front_corner ^ Box3D::Y);
467             new_z_orders[2] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
468         } else {
469             if (normalized_angle (dir_1x - dir_1z) > 0) {
470                 new_z_orders[1] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
471                 new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
472             } else {
473                 new_z_orders[1] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
474                 new_z_orders[2] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
475             }
476         }
477     }
479     new_z_orders[3] = Box3D::opposite_face (new_z_orders[2]);
480     new_z_orders[4] = Box3D::opposite_face (new_z_orders[1]);
481     new_z_orders[5] = Box3D::opposite_face (new_z_orders[0]);
483     /* We only need to look for changes among the topmost three faces because the order
484        of the other ones is just inverted. */
485     if ((box->z_orders[0] != new_z_orders[0]) ||
486         (box->z_orders[1] != new_z_orders[1]) ||
487         (box->z_orders[2] != new_z_orders[2]))
488     {
489         for (int i = 0; i < 6; ++i) {
490             box->z_orders[i] = new_z_orders[i];
491         }
492         return true;
493     }
495     return false;
498 void sp_3dbox_set_z_orders (SP3DBox *box)
500     // For efficiency reasons, we only set the new z-orders if something really changed
501     if (sp_3dbox_recompute_z_orders (box)) {
502         box->faces[box->z_orders[0]]->lower_to_bottom ();
503         box->faces[box->z_orders[1]]->lower_to_bottom ();
504         box->faces[box->z_orders[2]]->lower_to_bottom ();
505         box->faces[box->z_orders[3]]->lower_to_bottom ();
506         box->faces[box->z_orders[4]]->lower_to_bottom ();
507         box->faces[box->z_orders[5]]->lower_to_bottom ();
508     }
511 void
512 sp_3dbox_update_curves (SP3DBox *box) {
513     for (int i = 0; i < 6; ++i) {
514         if (box->faces[i]) box->faces[i]->set_curve();
515     }
518 /**
519  * In some situations (e.g., after cloning boxes, undo & redo, or reading boxes from a file) there are
520  * paths already present in the document which correspond to the faces of newly created boxes, but their
521  * 'path' members don't link to them yet. The following function corrects this if necessary.
522  */
523 void
524 sp_3dbox_link_to_existing_paths (SP3DBox *box, Inkscape::XML::Node *repr) {
525     // TODO: We should probably destroy the existing paths and recreate them because we don't know
526     //       precisely which path corresponds to which face. Does this make a difference?
527     //       In sp_3dbox_write we write the correct paths anyway, don't we? But we could get into
528     //       trouble at a later stage when we only write single faces for degenerate boxes.
530     SPDocument *document = SP_OBJECT_DOCUMENT(box);
531     guint face_id = 0;
533     for (Inkscape::XML::Node *i = sp_repr_children(repr); i != NULL; i = sp_repr_next(i)) {
534         if (face_id > 5) {
535             g_warning ("SVG representation of 3D boxes must contain 6 paths or less.\n");
536             break;
537         }
539         SPObject *face_object = document->getObjectByRepr((Inkscape::XML::Node *) i);
540         if (!SP_IS_PATH(face_object)) {
541             g_warning ("SVG representation of 3D boxes should only contain paths.\n");
542             continue;
543         }
544         // TODO: Currently we don't check whether all paths are being linked to different faces.
545         //       This is no problem with valid SVG files. It may lead to crashes, however,
546         //       in case a file is corrupt (e.g., two or more faces have identical descriptions).
547         gint id = Box3DFace::descr_to_id (i->attribute ("inkscape:box3dface"));
548         box->faces[id]->hook_path_to_3dbox(SP_PATH(face_object));
549         ++face_id;
550     }
551     if (face_id < 6) {
552         //g_warning ("SVG representation of 3D boxes should contain exactly 6 paths (degenerate boxes are not yet supported).\n");
553         // TODO: Check whether it is safe to add the remaining paths to the box and do so in case it is.
554         //       (But we also land here for newly created boxes where we shouldn't add any paths because
555         //       This is done in sp_3dbox_write later on.
556     }
559 void
560 sp_3dbox_move_corner_in_XY_plane (SP3DBox *box, guint id, NR::Point pt, Box3D::Axis axes)
562     Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
564     NR::Point A (box->corners[id ^ Box3D::XY]);
565     if (Box3D::is_single_axis_direction (axes)) {
566         pt = Box3D::PerspectiveLine (box->corners[id], axes, persp).closest_to(pt);
567     }
569     /* set the 'front' corners */
570     box->corners[id] = pt;
572     Box3D::PerspectiveLine pl_one (A, Box3D::Y, persp);
573     Box3D::PerspectiveLine pl_two (pt, Box3D::X, persp);
574     box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
576     pl_one = Box3D::PerspectiveLine (A, Box3D::X, persp);
577     pl_two = Box3D::PerspectiveLine (pt, Box3D::Y, persp);
578     box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
580     /* set the 'rear' corners */
581     NR::Point B (box->corners[id ^ Box3D::XYZ]);
583     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Z, persp);
584     pl_two = Box3D::PerspectiveLine (B, Box3D::Y, persp);
585     box->corners[id ^ Box3D::XZ] = pl_one.meet(pl_two);
587     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XZ], Box3D::X, persp);
588     pl_two = Box3D::PerspectiveLine (pt, Box3D::Z, persp);
589     box->corners[id ^ Box3D::Z] = pl_one.meet(pl_two);
591     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::Z], Box3D::Y, persp);
592     pl_two = Box3D::PerspectiveLine (B, Box3D::X, persp);
593     box->corners[id ^ Box3D::YZ] = pl_one.meet(pl_two);
594     
597 void
598 sp_3dbox_move_corner_in_Z_direction (SP3DBox *box, guint id, NR::Point pt, bool constrained)
600     if (!constrained) sp_3dbox_move_corner_in_XY_plane (box, id, pt, Box3D::XY);
602     Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
604     /* set the four corners of the face containing corners[id] */
605     box->corners[id] = Box3D::PerspectiveLine (box->corners[id], Box3D::Z, persp).closest_to(pt);
607     Box3D::PerspectiveLine pl_one (box->corners[id], Box3D::X, persp);
608     Box3D::PerspectiveLine pl_two (box->corners[id ^ Box3D::XZ], Box3D::Z, persp);
609     box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
611     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Y, persp);
612     pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XYZ], Box3D::Z, persp);
613     box->corners[id ^ Box3D::XY] = pl_one.meet(pl_two);
615     pl_one = Box3D::PerspectiveLine (box->corners[id], Box3D::Y, persp);
616     pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::YZ], Box3D::Z, persp);
617     box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
620 static void
621 sp_3dbox_reshape_edge_after_VP_toggling (SP3DBox *box, const guint corner, const Box3D::Axis axis, Box3D::Perspective3D *persp)
623     /* Hmm, perhaps we should simply use one of the corners as the pivot point.
624        But this way we minimize the amount of reshaping.
625        On second thought, we need to find a way to ensure that all boxes sharing the same
626        perspective are updated consistently _as a group_. That is, they should also retain
627        their relative positions towards each other. */
628     NR::Maybe<NR::Point> pt = sp_3dbox_get_midpoint_between_corners (box, corner, corner ^ axis);
629     g_return_if_fail (pt);
631     Box3D::Axis axis2 = ((axis == Box3D::Y) ? Box3D::X : Box3D::Y);
633     Box3D::PerspectiveLine line1 (box->corners[corner], axis2, persp);
634     Box3D::PerspectiveLine line2 (box->corners[corner ^ axis], axis2, persp);
636     Box3D::PerspectiveLine line3 (*pt, axis, persp);
638     NR::Point new_corner1 = line1.meet (line3);
639     NR::Point new_corner2 = line2.meet (line3);
641     box->corners[corner] = new_corner1;
642     box->corners[corner ^ axis] = new_corner2;
645 void
646 sp_3dbox_reshape_after_VP_toggling (SP3DBox *box, Box3D::Axis axis)
648     Box3D::Perspective3D *persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
649     std::pair<Box3D::Axis, Box3D::Axis> dirs = Box3D::get_remaining_axes (axis);
651     sp_3dbox_reshape_edge_after_VP_toggling (box, 0, axis, persp);
652     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first, axis, persp);
653     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first ^ dirs.second, axis, persp);
654     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.second, axis, persp);
657 NR::Maybe<NR::Point>
658 sp_3dbox_get_center (SP3DBox *box)
660     return sp_3dbox_get_midpoint_between_corners (box, 0, 7);
663 NR::Point
664 sp_3dbox_get_midpoint_in_axis_direction (NR::Point const &C, NR::Point const &D, Box3D::Axis axis, Box3D::Perspective3D *persp)
666     Box3D::PerspectiveLine pl (D, axis, persp);
667     return pl.pt_with_given_cross_ratio (C, D, -1.0);
670 // TODO: The following function can probably be rewritten in a much more elegant and robust way
671 //        by using projective coordinates for all points and using the cross ratio.
672 NR::Maybe<NR::Point>
673 sp_3dbox_get_midpoint_between_corners (SP3DBox *box, guint id_corner1, guint id_corner2)
675     Box3D::Axis corner_axes = (Box3D::Axis) (id_corner1 ^ id_corner2);
677     // Is all this sufficiently precise also for degenerate cases?
678     if (sp_3dbox_corners_are_adjacent (id_corner1, id_corner2)) {
679         Box3D::Axis orth_dir = get_perpendicular_axis_direction (corner_axes);
681         Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2 ^ orth_dir]);
682         Box3D::Line diag2 (box->corners[id_corner1 ^ orth_dir], box->corners[id_corner2]);
683         NR::Maybe<NR::Point> adjacent_face_center = diag1.intersect(diag2);
685         if (!adjacent_face_center) return NR::Nothing();
687         Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
689         Box3D::PerspectiveLine pl (*adjacent_face_center, orth_dir, persp);
690         return pl.intersect(Box3D::PerspectiveLine(box->corners[id_corner1], corner_axes, persp));
691     } else {
692         Box3D::Axis dir = Box3D::extract_first_axis_direction (corner_axes);
693         Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2]);
694         Box3D::Line diag2 (box->corners[id_corner1 ^ dir], box->corners[id_corner2 ^ dir]);
695         return diag1.intersect(diag2);
696     }
699 static gchar *
700 sp_3dbox_get_corner_coords_string (SP3DBox *box, guint id)
702     id = id % 8;
703     Inkscape::SVGOStringStream os;
704     os << box->corners[id][NR::X] << "," << box->corners[id][NR::Y];
705     return g_strdup(os.str().c_str());
708 static std::pair<gdouble, gdouble>
709 sp_3dbox_get_coord_pair_from_string (const gchar *coords)
711     gchar **coordpair = g_strsplit( coords, ",", 0);
712     // We might as well rely on g_ascii_strtod to convert the NULL pointer to 0.0,
713     // but we include the following test anyway
714     if (coordpair[0] == NULL || coordpair[1] == NULL) {
715         g_strfreev (coordpair);
716         g_warning ("Coordinate conversion failed.\n");
717         return std::make_pair(0.0, 0.0);
718     }
720     gdouble coord1 = g_ascii_strtod(coordpair[0], NULL);
721     gdouble coord2 = g_ascii_strtod(coordpair[1], NULL);
722     g_strfreev (coordpair);
724     return std::make_pair(coord1, coord2);
727 static gchar *
728 sp_3dbox_get_perspective_string (SP3DBox *box)
730     
731     return sp_3dbox_get_svg_descr_of_persp (SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box));
733   
734 gchar *
735 sp_3dbox_get_svg_descr_of_persp (Box3D::Perspective3D *persp)
737     // FIXME: We should move this code to perspective3d.cpp, but this yields compiler errors. Why?
738     Inkscape::SVGOStringStream os;
740     Box3D::VanishingPoint vp = *(persp->get_vanishing_point (Box3D::X));
741     os << vp[NR::X] << "," << vp[NR::Y] << ",";
742     os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
743     if (vp.is_finite()) {
744         os << "finite,";
745     } else {
746         os << "infinite,";
747     }
749     vp = *(persp->get_vanishing_point (Box3D::Y));
750     os << vp[NR::X] << "," << vp[NR::Y] << ",";
751     os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
752     if (vp.is_finite()) {
753         os << "finite,";
754     } else {
755         os << "infinite,";
756     }
758     vp = *(persp->get_vanishing_point (Box3D::Z));
759     os << vp[NR::X] << "," << vp[NR::Y] << ",";
760     os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
761     if (vp.is_finite()) {
762         os << "finite";
763     } else {
764         os << "infinite";
765     }
767     return g_strdup(os.str().c_str());
770 // auxiliary function
771 static std::pair<NR::Point, NR::Point>
772 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)
774     double cr1 = Box3D::cross_ratio (*persp->get_vanishing_point (axis), M0, M, A);
775     double cr2 = Box3D::cross_ratio (*persp->get_vanishing_point (axis), M, B, M0);
776     if (fabs (cr1 - 1) < Box3D::epsilon) {
777         // FIXME: cr == 1 is a degenerate case; how should we deal with it?
778         return std::make_pair (NR::Point (0,0), NR::Point (0,0));
779     }
780     Box3D::PerspectiveLine pl (M0, axis, persp);
781     NR::Point B_new = pl.pt_with_given_cross_ratio (M0, M, cr1 / (cr1 - 1));
782     NR::Point A_new = pl.pt_with_given_cross_ratio (M0, M, 1 - cr2);
783     return std::make_pair (A_new, B_new);
786 void sp_3dbox_recompute_XY_corners_from_new_center (SP3DBox *box, NR::Point const new_center)
788     // TODO: Clean this function up
790     Box3D::Perspective3D *persp = sp_desktop_document (inkscape_active_desktop())->get_persp_of_box (box);
791     NR::Point old_center = box->old_center;
793     NR::Point A0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner2, box->old_corner0, Box3D::Y, persp));
794     NR::Point B0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner1, box->old_corner3, Box3D::Y, persp));
796     /* we first move the box along the X-axis ... */
797     Box3D::PerspectiveLine aux_line1 (old_center, Box3D::X, persp);
798     Box3D::PerspectiveLine aux_line2 (new_center, Box3D::Y, persp);
799     NR::Point Z1 = aux_line1.meet (aux_line2);
801     Box3D::PerspectiveLine ref_line (B0, Box3D::X, persp);
802     Box3D::PerspectiveLine pline2 (old_center, Box3D::Z, persp);
803     Box3D::PerspectiveLine pline3 (Z1, Box3D::Z, persp);
804     NR::Point M0 = ref_line.meet (pline2);
805     NR::Point M1 = ref_line.meet (pline3);
807     std::pair<NR::Point, NR::Point> new_midpts = sp_3dbox_new_midpoints (persp, Box3D::X, M0, M1, A0, B0);
808     NR::Point A1 (new_midpts.first);
809     NR::Point B1 (new_midpts.second);
811     /* ... and then along the Y-axis */
812     Box3D::PerspectiveLine pline4 (box->old_corner1, Box3D::X, persp);
813     Box3D::PerspectiveLine pline5 (box->old_corner3, Box3D::X, persp);
814     Box3D::PerspectiveLine aux_line3 (M1, Box3D::Y, persp);
815     NR::Point C1 = aux_line3.meet (pline4);
816     NR::Point D1 = aux_line3.meet (pline5);
818     Box3D::PerspectiveLine aux_line4 (new_center, Box3D::Z, persp);
819     NR::Point M2 = aux_line4.meet (aux_line3);
821     std::pair<NR::Point, NR::Point> other_new_midpts = sp_3dbox_new_midpoints (persp, Box3D::Y, M1, M2, C1, D1);
822     NR::Point C2 (other_new_midpts.first);
823     NR::Point D2 (other_new_midpts.second);
825     Box3D::PerspectiveLine plXC (C2, Box3D::X, persp);
826     Box3D::PerspectiveLine plXD (D2, Box3D::X, persp);
827     Box3D::PerspectiveLine plYA (A1, Box3D::Y, persp);
828     Box3D::PerspectiveLine plYB (B1, Box3D::Y, persp);
830     NR::Point new_corner2 (plXD.meet (plYA));
831     NR::Point new_corner1 (plXC.meet (plYB));
833     NR::Point tmp_corner1 (pline4.meet (plYB));
834     Box3D::PerspectiveLine pline6 (box->old_corner5, Box3D::X, persp);
835     Box3D::PerspectiveLine pline7 (tmp_corner1, Box3D::Z, persp);
836     NR::Point tmp_corner5 (pline6.meet (pline7));
838     Box3D::PerspectiveLine pline8 (tmp_corner5, Box3D::Y, persp);
839     Box3D::PerspectiveLine pline9 (new_corner1, Box3D::Z, persp);
840     NR::Point new_corner5 (pline8.meet (pline9));
842     sp_3dbox_set_shape_from_points (box, new_corner2, new_corner1, new_corner5);
845 void sp_3dbox_update_perspective_lines()
847     SPEventContext *ec = inkscape_active_event_context();
848     if (!SP_IS_3DBOX_CONTEXT (ec))
849         return;
851     SP_3DBOX_CONTEXT (ec)->_vpdrag->updateLines();
854 /*
855  * Manipulates corner1 through corner4 to contain the indices of the corners
856  * from which the perspective lines in the direction of 'axis' emerge
857  */
858 void sp_3dbox_corners_for_perspective_lines (const SP3DBox * box, Box3D::Axis axis, 
859                                              NR::Point &corner1, NR::Point &corner2, NR::Point &corner3, NR::Point &corner4)
861     // along which axis to switch when takint
862     Box3D::Axis switch_axis;
863     if (axis == Box3D::X || axis == Box3D::Y) {
864         switch_axis = (box->front_bits & axis) ? Box3D::Z : Box3D::NONE;
865     } else {
866         switch_axis = (box->front_bits & axis) ? Box3D::X : Box3D::NONE;
867     }
869     switch (axis) {
870         case Box3D::X:
871             corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
872             corner2 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
873             corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
874             corner4 = sp_3dbox_get_corner_along_edge (box, 6 ^ switch_axis, axis, Box3D::REAR);
875             break;
876         case Box3D::Y:
877             corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
878             corner2 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
879             corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
880             corner4 = sp_3dbox_get_corner_along_edge (box, 5 ^ switch_axis, axis, Box3D::REAR);
881             break;
882         case Box3D::Z:
883             corner1 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
884             corner2 = sp_3dbox_get_corner_along_edge (box, 3 ^ switch_axis, axis, Box3D::REAR);
885             corner3 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
886             corner4 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
887             break;
888         default:
889             // do nothing
890             break;
891     }            
894 /**
895  * Returns the id of the corner on the edge along 'axis' and passing through 'corner' that
896  * lies on the front/rear face in this direction.
897  */
898 guint
899 sp_3dbox_get_corner_id_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
901     guint result;
902     guint other_corner = corner ^ axis;
903     Box3D::VanishingPoint *vp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point(axis);
904     if (vp->is_finite()) {
905         result = (  NR::L2 (vp->get_pos() - box->corners[corner])
906                   < NR::L2 (vp->get_pos() - box->corners[other_corner]) ? other_corner : corner);
907     } else {
908         // clear the axis bit and switch to the appropriate corner along axis, depending on the value of front_bits
909         result = ((corner & (0xF ^ axis)) ^ (box->front_bits & axis));
910     }
912     if (rel_pos == Box3D::FRONT) {
913         return result;
914     } else {
915         return result ^ axis;
916     }
919 NR::Point
920 sp_3dbox_get_corner_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
922     return box->corners[sp_3dbox_get_corner_id_along_edge (box, corner, axis, rel_pos)];
925 guint
926 sp_3dbox_get_front_corner_id (const SP3DBox *box)
928     guint front_corner = 1; // this could in fact be any corner, but we choose the one that is normally in front
929     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::X, Box3D::FRONT);
930     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Y, Box3D::FRONT);
931     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Z, Box3D::FRONT);
932     return front_corner;
935 // auxiliary functions
936 static void
937 sp_3dbox_update_corner_with_value_from_svg (SPObject *object, guint corner_id, const gchar *value)
939     if (value == NULL) return;
940     SP3DBox *box = SP_3DBOX(object);
942     std::pair<gdouble, gdouble> coord_pair = sp_3dbox_get_coord_pair_from_string (value);
943     box->corners[corner_id] = NR::Point (coord_pair.first, coord_pair.second);
944     sp_3dbox_recompute_corners (box, box->corners[2], box->corners[1], box->corners[5]);
945     object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
948 static void
949 sp_3dbox_update_perspective (Box3D::Perspective3D *persp, const gchar *value)
951     // WARNING! This function changes the perspective associated to 'box'. Since there may be
952     // many other boxes linked to the same perspective, their perspective is also changed.
953     // If this behaviour is not desired in all cases, we need a different function.
954     if (value == NULL) return;
956     gchar **vps = g_strsplit( value, ",", 0);
957     for (int i = 0; i < 15; ++i) {
958         if (vps[i] == NULL) {
959             g_warning ("Malformed svg attribute 'perspective'\n");
960             return;
961         }
962     }
964     persp->set_vanishing_point (Box3D::X, g_ascii_strtod (vps[0], NULL), g_ascii_strtod (vps[1], NULL),
965                                           g_ascii_strtod (vps[2], NULL), g_ascii_strtod (vps[3], NULL),
966                                           strcmp (vps[4], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
967     persp->set_vanishing_point (Box3D::Y, g_ascii_strtod (vps[5], NULL), g_ascii_strtod (vps[6], NULL),
968                                           g_ascii_strtod (vps[7], NULL), g_ascii_strtod (vps[8], NULL),
969                                           strcmp (vps[9], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
970     persp->set_vanishing_point (Box3D::Z, g_ascii_strtod (vps[10], NULL), g_ascii_strtod (vps[11], NULL),
971                                           g_ascii_strtod (vps[12], NULL), g_ascii_strtod (vps[13], NULL),
972                                           strcmp (vps[14], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
974     // update the other boxes linked to the same perspective
975     persp->reshape_boxes (Box3D::XYZ);
978 /*
979   Local Variables:
980   mode:c++
981   c-file-style:"stroustrup"
982   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
983   indent-tabs-mode:nil
984   fill-column:99
985   End:
986 */
987 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :