Code

Avoid 'flashing' between front and rear face during initial dragging process of 3D...
[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     Box3D::Perspective3D *persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
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 void sp_3dbox_set_z_orders (SP3DBox *box)
502     GSList *items = sp_item_group_item_list(SP_GROUP(box));
504     // For efficiency reasons, we only set the new z-orders if something really changed
505     if (sp_3dbox_recompute_z_orders (box)) {
506         box->faces[box->z_orders[0]]->lower_to_bottom ();
507         box->faces[box->z_orders[1]]->lower_to_bottom ();
508         box->faces[box->z_orders[2]]->lower_to_bottom ();
509         box->faces[box->z_orders[3]]->lower_to_bottom ();
510         box->faces[box->z_orders[4]]->lower_to_bottom ();
511         box->faces[box->z_orders[5]]->lower_to_bottom ();
512     }
515 void
516 sp_3dbox_update_curves (SP3DBox *box) {
517     for (int i = 0; i < 6; ++i) {
518         if (box->faces[i]) box->faces[i]->set_curve();
519     }
522 /**
523  * In some situations (e.g., after cloning boxes, undo & redo, or reading boxes from a file) there are
524  * paths already present in the document which correspond to the faces of newly created boxes, but their
525  * 'path' members don't link to them yet. The following function corrects this if necessary.
526  */
527 void
528 sp_3dbox_link_to_existing_paths (SP3DBox *box, Inkscape::XML::Node *repr) {
529     // TODO: We should probably destroy the existing paths and recreate them because we don't know
530     //       precisely which path corresponds to which face. Does this make a difference?
531     //       In sp_3dbox_write we write the correct paths anyway, don't we? But we could get into
532     //       trouble at a later stage when we only write single faces for degenerate boxes.
534     SPDocument *document = SP_OBJECT_DOCUMENT(box);
535     guint face_id = 0;
537     for (Inkscape::XML::Node *i = sp_repr_children(repr); i != NULL; i = sp_repr_next(i)) {
538         if (face_id > 5) {
539             g_warning ("SVG representation of 3D boxes must contain 6 paths or less.\n");
540             break;
541         }
543         SPObject *face_object = document->getObjectByRepr((Inkscape::XML::Node *) i);
544         if (!SP_IS_PATH(face_object)) {
545             g_warning ("SVG representation of 3D boxes should only contain paths.\n");
546             continue;
547         }
548         // TODO: Currently we don't check whether all paths are being linked to different faces.
549         //       This is no problem with valid SVG files. It may lead to crashes, however,
550         //       in case a file is corrupt (e.g., two or more faces have identical descriptions).
551         gint id = Box3DFace::descr_to_id (i->attribute ("inkscape:box3dface"));
552         box->faces[id]->hook_path_to_3dbox(SP_PATH(face_object));
553         ++face_id;
554     }
555     if (face_id < 6) {
556         //g_warning ("SVG representation of 3D boxes should contain exactly 6 paths (degenerate boxes are not yet supported).\n");
557         // TODO: Check whether it is safe to add the remaining paths to the box and do so in case it is.
558         //       (But we also land here for newly created boxes where we shouldn't add any paths because
559         //       This is done in sp_3dbox_write later on.
560     }
563 void
564 sp_3dbox_move_corner_in_XY_plane (SP3DBox *box, guint id, NR::Point pt, Box3D::Axis axes)
566     Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
568     NR::Point A (box->corners[id ^ Box3D::XY]);
569     if (Box3D::is_single_axis_direction (axes)) {
570         pt = Box3D::PerspectiveLine (box->corners[id], axes, persp).closest_to(pt);
571     }
573     /* set the 'front' corners */
574     box->corners[id] = pt;
576     Box3D::PerspectiveLine pl_one (A, Box3D::Y, persp);
577     Box3D::PerspectiveLine pl_two (pt, Box3D::X, persp);
578     box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
580     pl_one = Box3D::PerspectiveLine (A, Box3D::X, persp);
581     pl_two = Box3D::PerspectiveLine (pt, Box3D::Y, persp);
582     box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
584     /* set the 'rear' corners */
585     NR::Point B (box->corners[id ^ Box3D::XYZ]);
587     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Z, persp);
588     pl_two = Box3D::PerspectiveLine (B, Box3D::Y, persp);
589     box->corners[id ^ Box3D::XZ] = pl_one.meet(pl_two);
591     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XZ], Box3D::X, persp);
592     pl_two = Box3D::PerspectiveLine (pt, Box3D::Z, persp);
593     box->corners[id ^ Box3D::Z] = pl_one.meet(pl_two);
595     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::Z], Box3D::Y, persp);
596     pl_two = Box3D::PerspectiveLine (B, Box3D::X, persp);
597     box->corners[id ^ Box3D::YZ] = pl_one.meet(pl_two);
598     
601 void
602 sp_3dbox_move_corner_in_Z_direction (SP3DBox *box, guint id, NR::Point pt, bool constrained)
604     if (!constrained) sp_3dbox_move_corner_in_XY_plane (box, id, pt, Box3D::XY);
606     Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
608     /* set the four corners of the face containing corners[id] */
609     box->corners[id] = Box3D::PerspectiveLine (box->corners[id], Box3D::Z, persp).closest_to(pt);
611     Box3D::PerspectiveLine pl_one (box->corners[id], Box3D::X, persp);
612     Box3D::PerspectiveLine pl_two (box->corners[id ^ Box3D::XZ], Box3D::Z, persp);
613     box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
615     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Y, persp);
616     pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XYZ], Box3D::Z, persp);
617     box->corners[id ^ Box3D::XY] = pl_one.meet(pl_two);
619     pl_one = Box3D::PerspectiveLine (box->corners[id], Box3D::Y, persp);
620     pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::YZ], Box3D::Z, persp);
621     box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
624 static void
625 sp_3dbox_reshape_edge_after_VP_toggling (SP3DBox *box, const guint corner, const Box3D::Axis axis, Box3D::Perspective3D *persp)
627     /* Hmm, perhaps we should simply use one of the corners as the pivot point.
628        But this way we minimize the amount of reshaping.
629        On second thought, we need to find a way to ensure that all boxes sharing the same
630        perspective are updated consistently _as a group_. That is, they should also retain
631        their relative positions towards each other. */
632     NR::Maybe<NR::Point> pt = sp_3dbox_get_midpoint_between_corners (box, corner, corner ^ axis);
633     g_return_if_fail (pt);
635     Box3D::Axis axis2 = ((axis == Box3D::Y) ? Box3D::X : Box3D::Y);
637     Box3D::PerspectiveLine line1 (box->corners[corner], axis2, persp);
638     Box3D::PerspectiveLine line2 (box->corners[corner ^ axis], axis2, persp);
640     Box3D::PerspectiveLine line3 (*pt, axis, persp);
642     NR::Point new_corner1 = line1.meet (line3);
643     NR::Point new_corner2 = line2.meet (line3);
645     box->corners[corner] = new_corner1;
646     box->corners[corner ^ axis] = new_corner2;
649 void
650 sp_3dbox_reshape_after_VP_toggling (SP3DBox *box, Box3D::Axis axis)
652     Box3D::Perspective3D *persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
653     std::pair<Box3D::Axis, Box3D::Axis> dirs = Box3D::get_remaining_axes (axis);
655     sp_3dbox_reshape_edge_after_VP_toggling (box, 0, axis, persp);
656     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first, axis, persp);
657     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first ^ dirs.second, axis, persp);
658     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.second, axis, persp);
661 NR::Maybe<NR::Point>
662 sp_3dbox_get_center (SP3DBox *box)
664     return sp_3dbox_get_midpoint_between_corners (box, 0, 7);
667 NR::Point
668 sp_3dbox_get_midpoint_in_axis_direction (NR::Point const &C, NR::Point const &D, Box3D::Axis axis, Box3D::Perspective3D *persp)
670     Box3D::PerspectiveLine pl (D, axis, persp);
671     return pl.pt_with_given_cross_ratio (C, D, -1.0);
674 // TODO: The following function can probably be rewritten in a much more elegant and robust way
675 //        by using projective coordinates for all points and using the cross ratio.
676 NR::Maybe<NR::Point>
677 sp_3dbox_get_midpoint_between_corners (SP3DBox *box, guint id_corner1, guint id_corner2)
679     Box3D::Axis corner_axes = (Box3D::Axis) (id_corner1 ^ id_corner2);
681     // Is all this sufficiently precise also for degenerate cases?
682     if (sp_3dbox_corners_are_adjacent (id_corner1, id_corner2)) {
683         Box3D::Axis orth_dir = get_perpendicular_axis_direction (corner_axes);
685         Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2 ^ orth_dir]);
686         Box3D::Line diag2 (box->corners[id_corner1 ^ orth_dir], box->corners[id_corner2]);
687         NR::Maybe<NR::Point> adjacent_face_center = diag1.intersect(diag2);
689         if (!adjacent_face_center) return NR::Nothing();
691         Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
693         Box3D::PerspectiveLine pl (*adjacent_face_center, orth_dir, persp);
694         return pl.intersect(Box3D::PerspectiveLine(box->corners[id_corner1], corner_axes, persp));
695     } else {
696         Box3D::Axis dir = Box3D::extract_first_axis_direction (corner_axes);
697         Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2]);
698         Box3D::Line diag2 (box->corners[id_corner1 ^ dir], box->corners[id_corner2 ^ dir]);
699         return diag1.intersect(diag2);
700     }
703 static gchar *
704 sp_3dbox_get_corner_coords_string (SP3DBox *box, guint id)
706     id = id % 8;
707     Inkscape::SVGOStringStream os;
708     os << box->corners[id][NR::X] << "," << box->corners[id][NR::Y];
709     return g_strdup(os.str().c_str());
712 static std::pair<gdouble, gdouble>
713 sp_3dbox_get_coord_pair_from_string (const gchar *coords)
715     gchar **coordpair = g_strsplit( coords, ",", 0);
716     // We might as well rely on g_ascii_strtod to convert the NULL pointer to 0.0,
717     // but we include the following test anyway
718     if (coordpair[0] == NULL || coordpair[1] == NULL) {
719         g_strfreev (coordpair);
720         g_warning ("Coordinate conversion failed.\n");
721         return std::make_pair(0.0, 0.0);
722     }
724     gdouble coord1 = g_ascii_strtod(coordpair[0], NULL);
725     gdouble coord2 = g_ascii_strtod(coordpair[1], NULL);
726     g_strfreev (coordpair);
728     return std::make_pair(coord1, coord2);
731 static gchar *
732 sp_3dbox_get_perspective_string (SP3DBox *box)
734     
735     return sp_3dbox_get_svg_descr_of_persp (SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box));
737   
738 gchar *
739 sp_3dbox_get_svg_descr_of_persp (Box3D::Perspective3D *persp)
741     // FIXME: We should move this code to perspective3d.cpp, but this yields compiler errors. Why?
742     Inkscape::SVGOStringStream os;
744     Box3D::VanishingPoint vp = *(persp->get_vanishing_point (Box3D::X));
745     os << vp[NR::X] << "," << vp[NR::Y] << ",";
746     os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
747     if (vp.is_finite()) {
748         os << "finite,";
749     } else {
750         os << "infinite,";
751     }
753     vp = *(persp->get_vanishing_point (Box3D::Y));
754     os << vp[NR::X] << "," << vp[NR::Y] << ",";
755     os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
756     if (vp.is_finite()) {
757         os << "finite,";
758     } else {
759         os << "infinite,";
760     }
762     vp = *(persp->get_vanishing_point (Box3D::Z));
763     os << vp[NR::X] << "," << vp[NR::Y] << ",";
764     os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
765     if (vp.is_finite()) {
766         os << "finite";
767     } else {
768         os << "infinite";
769     }
771     return g_strdup(os.str().c_str());
774 // auxiliary function
775 static std::pair<NR::Point, NR::Point>
776 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)
778     double cr1 = Box3D::cross_ratio (*persp->get_vanishing_point (axis), M0, M, A);
779     double cr2 = Box3D::cross_ratio (*persp->get_vanishing_point (axis), M, B, M0);
780     if (fabs (cr1 - 1) < Box3D::epsilon) {
781         // FIXME: cr == 1 is a degenerate case; how should we deal with it?
782         return std::make_pair (NR::Point (0,0), NR::Point (0,0));
783     }
784     Box3D::PerspectiveLine pl (M0, axis, persp);
785     NR::Point B_new = pl.pt_with_given_cross_ratio (M0, M, cr1 / (cr1 - 1));
786     NR::Point A_new = pl.pt_with_given_cross_ratio (M0, M, 1 - cr2);
787     return std::make_pair (A_new, B_new);
790 void sp_3dbox_recompute_XY_corners_from_new_center (SP3DBox *box, NR::Point const new_center)
792     // TODO: Clean this function up
794     Box3D::Perspective3D *persp = sp_desktop_document (inkscape_active_desktop())->get_persp_of_box (box);
795     NR::Point old_center = box->old_center;
797     NR::Point A0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner2, box->old_corner0, Box3D::Y, persp));
798     NR::Point B0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner1, box->old_corner3, Box3D::Y, persp));
800     /* we first move the box along the X-axis ... */
801     Box3D::PerspectiveLine aux_line1 (old_center, Box3D::X, persp);
802     Box3D::PerspectiveLine aux_line2 (new_center, Box3D::Y, persp);
803     NR::Point Z1 = aux_line1.meet (aux_line2);
805     Box3D::PerspectiveLine ref_line (B0, Box3D::X, persp);
806     Box3D::PerspectiveLine pline2 (old_center, Box3D::Z, persp);
807     Box3D::PerspectiveLine pline3 (Z1, Box3D::Z, persp);
808     NR::Point M0 = ref_line.meet (pline2);
809     NR::Point M1 = ref_line.meet (pline3);
811     std::pair<NR::Point, NR::Point> new_midpts = sp_3dbox_new_midpoints (persp, Box3D::X, M0, M1, A0, B0);
812     NR::Point A1 (new_midpts.first);
813     NR::Point B1 (new_midpts.second);
815     /* ... and then along the Y-axis */
816     Box3D::PerspectiveLine pline4 (box->old_corner1, Box3D::X, persp);
817     Box3D::PerspectiveLine pline5 (box->old_corner3, Box3D::X, persp);
818     Box3D::PerspectiveLine aux_line3 (M1, Box3D::Y, persp);
819     NR::Point C1 = aux_line3.meet (pline4);
820     NR::Point D1 = aux_line3.meet (pline5);
822     Box3D::PerspectiveLine aux_line4 (new_center, Box3D::Z, persp);
823     NR::Point M2 = aux_line4.meet (aux_line3);
825     Box3D::VanishingPoint *vp_y = persp->get_vanishing_point (Box3D::Y);
826     std::pair<NR::Point, NR::Point> other_new_midpts = sp_3dbox_new_midpoints (persp, Box3D::Y, M1, M2, C1, D1);
827     NR::Point C2 (other_new_midpts.first);
828     NR::Point D2 (other_new_midpts.second);
830     Box3D::PerspectiveLine plXC (C2, Box3D::X, persp);
831     Box3D::PerspectiveLine plXD (D2, Box3D::X, persp);
832     Box3D::PerspectiveLine plYA (A1, Box3D::Y, persp);
833     Box3D::PerspectiveLine plYB (B1, Box3D::Y, persp);
835     NR::Point new_corner2 (plXD.meet (plYA));
836     NR::Point new_corner1 (plXC.meet (plYB));
838     NR::Point tmp_corner1 (pline4.meet (plYB));
839     Box3D::PerspectiveLine pline6 (box->old_corner5, Box3D::X, persp);
840     Box3D::PerspectiveLine pline7 (tmp_corner1, Box3D::Z, persp);
841     NR::Point tmp_corner5 (pline6.meet (pline7));
843     Box3D::PerspectiveLine pline8 (tmp_corner5, Box3D::Y, persp);
844     Box3D::PerspectiveLine pline9 (new_corner1, Box3D::Z, persp);
845     NR::Point new_corner5 (pline8.meet (pline9));
847     sp_3dbox_set_shape_from_points (box, new_corner2, new_corner1, new_corner5);
850 void sp_3dbox_update_perspective_lines()
852     SPEventContext *ec = inkscape_active_event_context();
853     if (!SP_IS_3DBOX_CONTEXT (ec))
854         return;
856     SP_3DBOX_CONTEXT (ec)->_vpdrag->updateLines();
859 /*
860  * Manipulates corner1 through corner4 to contain the indices of the corners
861  * from which the perspective lines in the direction of 'axis' emerge
862  */
863 void sp_3dbox_corners_for_perspective_lines (const SP3DBox * box, Box3D::Axis axis, 
864                                              NR::Point &corner1, NR::Point &corner2, NR::Point &corner3, NR::Point &corner4)
866     // along which axis to switch when takint
867     Box3D::Axis switch_axis;
868     if (axis == Box3D::X || axis == Box3D::Y) {
869         switch_axis = (box->front_bits & axis) ? Box3D::Z : Box3D::NONE;
870     } else {
871         switch_axis = (box->front_bits & axis) ? Box3D::X : Box3D::NONE;
872     }
874     switch (axis) {
875         case Box3D::X:
876             corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
877             corner2 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
878             corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
879             corner4 = sp_3dbox_get_corner_along_edge (box, 6 ^ switch_axis, axis, Box3D::REAR);
880             break;
881         case Box3D::Y:
882             corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
883             corner2 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
884             corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
885             corner4 = sp_3dbox_get_corner_along_edge (box, 5 ^ switch_axis, axis, Box3D::REAR);
886             break;
887         case Box3D::Z:
888             corner1 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
889             corner2 = sp_3dbox_get_corner_along_edge (box, 3 ^ switch_axis, axis, Box3D::REAR);
890             corner3 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
891             corner4 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
892             break;
893     }            
896 /**
897  * Returns the id of the corner on the edge along 'axis' and passing through 'corner' that
898  * lies on the front/rear face in this direction.
899  */
900 guint
901 sp_3dbox_get_corner_id_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
903     guint result;
904     guint other_corner = corner ^ axis;
905     Box3D::VanishingPoint *vp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point(axis);
906     if (vp->is_finite()) {
907         result = (  NR::L2 (vp->get_pos() - box->corners[corner])
908                   < NR::L2 (vp->get_pos() - box->corners[other_corner]) ? other_corner : corner);
909     } else {
910         // clear the axis bit and switch to the appropriate corner along axis, depending on the value of front_bits
911         result = ((corner & (0xF ^ axis)) ^ (box->front_bits & axis));
912     }
914     if (rel_pos == Box3D::FRONT) {
915         return result;
916     } else {
917         return result ^ axis;
918     }
921 NR::Point
922 sp_3dbox_get_corner_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
924     return box->corners[sp_3dbox_get_corner_id_along_edge (box, corner, axis, rel_pos)];
927 guint
928 sp_3dbox_get_front_corner_id (const SP3DBox *box)
930     guint front_corner = 1; // this could in fact be any corner, but we choose the one that is normally in front
931     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::X, Box3D::FRONT);
932     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Y, Box3D::FRONT);
933     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Z, Box3D::FRONT);
934     return front_corner;
937 // auxiliary functions
938 static void
939 sp_3dbox_update_corner_with_value_from_svg (SPObject *object, guint corner_id, const gchar *value)
941     if (value == NULL) return;
942     SP3DBox *box = SP_3DBOX(object);
944     std::pair<gdouble, gdouble> coord_pair = sp_3dbox_get_coord_pair_from_string (value);
945     box->corners[corner_id] = NR::Point (coord_pair.first, coord_pair.second);
946     sp_3dbox_recompute_corners (box, box->corners[2], box->corners[1], box->corners[5]);
947     object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
950 static void
951 sp_3dbox_update_perspective (Box3D::Perspective3D *persp, const gchar *value)
953     // WARNING! This function changes the perspective associated to 'box'. Since there may be
954     // many other boxes linked to the same perspective, their perspective is also changed.
955     // If this behaviour is not desired in all cases, we need a different function.
956     if (value == NULL) return;
958     gchar **vps = g_strsplit( value, ",", 0);
959     for (int i = 0; i < 15; ++i) {
960         if (vps[i] == NULL) {
961             g_warning ("Malformed svg attribute 'perspective'\n");
962             return;
963         }
964     }
966     persp->set_vanishing_point (Box3D::X, g_ascii_strtod (vps[0], NULL), g_ascii_strtod (vps[1], NULL),
967                                           g_ascii_strtod (vps[2], NULL), g_ascii_strtod (vps[3], NULL),
968                                           strcmp (vps[4], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
969     persp->set_vanishing_point (Box3D::Y, g_ascii_strtod (vps[5], NULL), g_ascii_strtod (vps[6], NULL),
970                                           g_ascii_strtod (vps[7], NULL), g_ascii_strtod (vps[8], NULL),
971                                           strcmp (vps[9], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
972     persp->set_vanishing_point (Box3D::Z, g_ascii_strtod (vps[10], NULL), g_ascii_strtod (vps[11], NULL),
973                                           g_ascii_strtod (vps[12], NULL), g_ascii_strtod (vps[13], NULL),
974                                           strcmp (vps[14], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
976     // update the other boxes linked to the same perspective
977     persp->reshape_boxes (Box3D::XYZ);
980 /*
981   Local Variables:
982   mode:c++
983   c-file-style:"stroustrup"
984   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
985   indent-tabs-mode:nil
986   fill-column:99
987   End:
988 */
989 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :