Code

Status bar tips for VP draggers
[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     if (repr->attribute ("inkscape:perspective") == NULL) {
114         // we are creating a new box; link it to the current perspective
115         Box3D::Perspective3D::current_perspective->add_box (box);
116     } else {
117         // create a new perspective that we can compare with existing ones
118         Box3D::Perspective3D *persp = new Box3D::Perspective3D (Box3D::VanishingPoint (0,0),
119                                                                 Box3D::VanishingPoint (0,0),
120                                                                 Box3D::VanishingPoint (0,0));
121         sp_3dbox_update_perspective (persp, repr->attribute ("inkscape:perspective"));
122         Box3D::Perspective3D *comp =  Box3D::Perspective3D::find_perspective (persp);
123         if (comp == NULL) {
124             // perspective doesn't exist yet
125             Box3D::Perspective3D::add_perspective (persp);
126             persp->add_box (box);
127         } else {
128             // link the box to the existing perspective and delete the temporary one
129             comp->add_box (box);
130             delete persp;
131             //g_assert (Box3D::get_persp_of_box (box) == comp);
133             // FIXME: If the paths of the box's faces do not correspond to the svg representation of the perspective
134             //        the box is shown with a "wrong" initial shape that is only corrected after dragging.
135             //        Should we "repair" this by updating the paths at the end of sp_3dbox_build()?
136             //        Maybe it would be better to simply destroy and rebuild them in sp_3dbox_link_to_existing_paths().
137         }
138     }
140     sp_object_read_attr(object, "inkscape:box3dcornerA");
141     sp_object_read_attr(object, "inkscape:box3dcornerB");
142     sp_object_read_attr(object, "inkscape:box3dcornerC");
144     // TODO: We create all faces in the beginning, but only the non-degenerate ones
145     //       should be written to the svg representation later in sp_3dbox_write.
146     Box3D::Axis cur_plane, axis, dir1, dir2;
147     Box3D::FrontOrRear cur_pos;
148     for (int i = 0; i < 3; ++i) {
149         for (int j = 0; j < 2; ++j) {
150             cur_plane = Box3D::planes[i];
151             cur_pos = Box3D::face_positions[j];
152             // FIXME: The following code could theoretically be moved to
153             //        the constructor of Box3DFace (but see the comment there).
154             axis = (cur_pos == Box3D::FRONT ? Box3D::NONE : Box3D::third_axis_direction (cur_plane));
155             dir1 = extract_first_axis_direction (cur_plane);
156             dir2 = extract_second_axis_direction (cur_plane);
157             
158             box->faces[Box3D::face_to_int(cur_plane ^ cur_pos)] =
159                 new Box3DFace (box, box->corners[axis], box->corners[axis ^ dir1],
160                                     box->corners[axis ^ dir1 ^ dir2], box->corners[axis ^ dir2],
161                                     cur_plane, cur_pos);
162         }
163     }
165     // Check whether the paths of the faces of the box need to be linked to existing paths in the
166     // document (e.g., after a 'redo' operation or after opening a file) and do so if necessary.
167     sp_3dbox_link_to_existing_paths (box, repr);
169     sp_3dbox_set_ratios (box);
172 static void
173 sp_3dbox_release (SPObject *object)
175         SP3DBox *box = SP_3DBOX(object);
176         for (int i = 0; i < 6; ++i) {
177             if (box->faces[i]) {
178                 delete box->faces[i]; // FIXME: Anything else to do? Do we need to clean up the face first?
179             }
180         }
182         // FIXME: We do not duplicate perspectives if they are the same for several boxes.
183         //        Thus, don't delete the perspective when deleting a box but rather unlink the box from it.
184         Box3D::get_persp_of_box (box)->remove_box (box);
186         if (((SPObjectClass *) parent_class)->release) {
187           ((SPObjectClass *) parent_class)->release (object);
188         }
191 static void sp_3dbox_set(SPObject *object, unsigned int key, const gchar *value)
193     switch (key) {
194         case SP_ATTR_INKSCAPE_3DBOX_CORNER_A:
195             sp_3dbox_update_corner_with_value_from_svg (object, 2, value);
196             break;
197         case SP_ATTR_INKSCAPE_3DBOX_CORNER_B:
198             sp_3dbox_update_corner_with_value_from_svg (object, 1, value);
199             break;
200         case SP_ATTR_INKSCAPE_3DBOX_CORNER_C:
201             sp_3dbox_update_corner_with_value_from_svg (object, 5, value);
202             break;
203         case SP_ATTR_INKSCAPE_3DBOX_PERSPECTIVE:
204             sp_3dbox_update_perspective (Box3D::get_persp_of_box (SP_3DBOX (object)), value);
205             break;
206         default:
207             if (((SPObjectClass *) (parent_class))->set) {
208                 ((SPObjectClass *) (parent_class))->set(object, key, value);
209             }
210             break;
211     }
214 static void
215 sp_3dbox_update(SPObject *object, SPCtx *ctx, guint flags)
217     if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
218         SP3DBox *box = SP_3DBOX(object);
219         sp_3dbox_link_to_existing_paths (box, SP_OBJECT_REPR(object));
220     }
222     /* Invoke parent method */
223     if (((SPObjectClass *) (parent_class))->update)
224         ((SPObjectClass *) (parent_class))->update(object, ctx, flags);
229 static Inkscape::XML::Node *sp_3dbox_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
231     SP3DBox *box = SP_3DBOX(object);
232     // FIXME: How to handle other contexts???
233     // FIXME: Is tools_isactive(..) more recommended to check for the current context/tool?
234     if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
235         return repr;
237     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
238         Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
239         repr = xml_doc->createElement("svg:g");
240         repr->setAttribute("sodipodi:type", "inkscape:3dbox");
241         /* Hook paths to the faces of the box */
242         for (int i = 0; i < 6; ++i) {
243             box->faces[i]->hook_path_to_3dbox();
244         }
245     }
247     for (int i = 0; i < 6; ++i) {
248         box->faces[i]->set_path_repr();
249     }
251     if (flags & SP_OBJECT_WRITE_EXT) {
252         gchar *str;
253         str = sp_3dbox_get_corner_coords_string (box, 2);
254         repr->setAttribute("inkscape:box3dcornerA", str);
256         str = sp_3dbox_get_corner_coords_string (box, 1);
257         repr->setAttribute("inkscape:box3dcornerB", str);
259         str = sp_3dbox_get_corner_coords_string (box, 5);
260         repr->setAttribute("inkscape:box3dcornerC", str);
262         str = sp_3dbox_get_perspective_string (box);
263         repr->setAttribute("inkscape:perspective", str);
264         sp_3dbox_set_ratios (box);
266         g_free ((void *) str);
267     }
269     if (((SPObjectClass *) (parent_class))->write) {
270         ((SPObjectClass *) (parent_class))->write(object, repr, flags);
271     }
273     return repr;
276 static gchar *
277 sp_3dbox_description(SPItem *item)
279     g_return_val_if_fail(SP_IS_3DBOX(item), NULL);
281     return g_strdup(_("<b>3D Box</b>"));
284 void sp_3dbox_set_ratios (SP3DBox *box, Box3D::Axis axes)
286     Box3D::Perspective3D *persp = Box3D::get_persp_of_box (box);
287     NR::Point pt;
289     if (axes & Box3D::X) {
290         pt = persp->get_vanishing_point (Box3D::X)->get_pos();
291         box->ratio_x = NR::L2 (pt - box->corners[2]) / NR::L2 (pt - box->corners[3]);
292     }
294     if (axes & Box3D::Y) {
295         pt = persp->get_vanishing_point (Box3D::Y)->get_pos();
296         box->ratio_y = NR::L2 (pt - box->corners[2]) / NR::L2 (pt - box->corners[0]);
297     }
299     if (axes & Box3D::Z) {
300         pt = persp->get_vanishing_point (Box3D::Z)->get_pos();
301         box->ratio_z = NR::L2 (pt - box->corners[4]) / NR::L2 (pt - box->corners[0]);
302     }
305 void
306 sp_3dbox_switch_front_face (SP3DBox *box, Box3D::Axis axis)
308     if (Box3D::get_persp_of_box (box)->get_vanishing_point (axis)->is_finite()) {
309         box->front_bits = box->front_bits ^ axis;
310     }
314 void
315 sp_3dbox_position_set (SP3DBoxContext &bc)
317     SP3DBox *box3d = SP_3DBOX(bc.item);
319     sp_3dbox_set_shape(box3d);
321     // FIXME: Why does the following call not automatically update the children
322     //        of box3d (which is an SPGroup, which should do this)?
323     //SP_OBJECT(box3d)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
325     /**
326     SP_OBJECT(box3d->path_face1)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
327     SP_OBJECT(box3d->path_face2)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
328     SP_OBJECT(box3d->path_face3)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
329     SP_OBJECT(box3d->path_face4)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
330     SP_OBJECT(box3d->path_face5)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
331     SP_OBJECT(box3d->path_face6)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
332     ***/
335 //static
336 void
337 // FIXME: Note that this is _not_ the virtual set_shape() method inherited from SPShape,
338 //        since SP3DBox is inherited from SPGroup. The following method is "artificially"
339 //        called from sp_3dbox_update().
340 //sp_3dbox_set_shape(SPShape *shape)
341 sp_3dbox_set_shape(SP3DBox *box3d, bool use_previous_corners)
343     // FIXME: How to handle other contexts???
344     // FIXME: Is tools_isactive(..) more recommended to check for the current context/tool?
345     if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
346         return;
347     SP3DBoxContext *bc = SP_3DBOX_CONTEXT(inkscape_active_event_context());
349     /* Only update the curves during dragging; setting the svg representations 
350        is expensive and only done once at the end */
351     if (!use_previous_corners) {
352         sp_3dbox_recompute_corners (box3d, bc->drag_origin, bc->drag_ptB, bc->drag_ptC);
353     } else {
354         sp_3dbox_recompute_corners (box3d, box3d->corners[2], box3d->corners[1], box3d->corners[5]);
355     }
356     if (bc->extruded) {
357         box3d->faces[0]->set_corners (box3d->corners[0], box3d->corners[4], box3d->corners[6], box3d->corners[2]);
358         box3d->faces[1]->set_corners (box3d->corners[1], box3d->corners[5], box3d->corners[7], box3d->corners[3]);
359         box3d->faces[2]->set_corners (box3d->corners[0], box3d->corners[1], box3d->corners[5], box3d->corners[4]);
360         box3d->faces[3]->set_corners (box3d->corners[2], box3d->corners[3], box3d->corners[7], box3d->corners[6]);
361         box3d->faces[5]->set_corners (box3d->corners[4], box3d->corners[5], box3d->corners[7], box3d->corners[6]);
362     }
363     box3d->faces[4]->set_corners (box3d->corners[0], box3d->corners[1], box3d->corners[3], box3d->corners[2]);
365     sp_3dbox_update_curves (box3d);
369 void sp_3dbox_recompute_corners (SP3DBox *box, NR::Point const A, NR::Point const B, NR::Point const C)
371     sp_3dbox_move_corner_in_XY_plane (box, 2, A);
372     sp_3dbox_move_corner_in_XY_plane (box, 1, B);
373     sp_3dbox_move_corner_in_Z_direction (box, 5, C);
376 inline static double
377 normalized_angle (double angle) {
378     if (angle < -M_PI) {
379         return angle + 2*M_PI;
380     } else if (angle > M_PI) {
381         return angle - 2*M_PI;
382     }
383     return angle;
386 static gdouble
387 sp_3dbox_corner_angle_to_VP (SP3DBox *box, Box3D::Axis axis, guint extreme_corner)
389     Box3D::VanishingPoint *vp = Box3D::get_persp_of_box (box)->get_vanishing_point (axis);
390     NR::Point dir;
392     if (vp->is_finite()) {
393         dir = NR::unit_vector (vp->get_pos() - box->corners[extreme_corner]);
394     } else {
395         dir = NR::unit_vector (vp->v_dir);
396     }
398     return atan2 (dir[NR::Y], dir[NR::X]);
402 bool sp_3dbox_recompute_z_orders (SP3DBox *box)
404     guint new_z_orders[6];
406     Box3D::Perspective3D *persp = Box3D::get_persp_of_box (box);
408     // TODO: Determine the front corner depending on the distance from VPs and/or the user presets
409     guint front_corner = sp_3dbox_get_front_corner_id (box);
411     gdouble dir_1x = sp_3dbox_corner_angle_to_VP (box, Box3D::X, front_corner);
412     gdouble dir_3x = sp_3dbox_corner_angle_to_VP (box, Box3D::X, front_corner ^ Box3D::Y);
414     gdouble dir_1y = sp_3dbox_corner_angle_to_VP (box, Box3D::Y, front_corner);
415     gdouble dir_0y = sp_3dbox_corner_angle_to_VP (box, Box3D::Y, front_corner ^ Box3D::X);
417     gdouble dir_1z = sp_3dbox_corner_angle_to_VP (box, Box3D::Z, front_corner);
418     gdouble dir_3z = sp_3dbox_corner_angle_to_VP (box, Box3D::Z, front_corner ^ Box3D::Y);
420     // Still not perfect, but only fails in some rather degenerate cases.
421     // I suspect that there is a more elegant model, though. :)
422     new_z_orders[0] = Box3D::face_containing_corner (Box3D::XY, front_corner);
423     if (normalized_angle (dir_1y - dir_1z) > 0) {
424         new_z_orders[1] = Box3D::face_containing_corner (Box3D::YZ, front_corner);
425         if (normalized_angle (dir_1x - dir_1z) > 0) {
426             new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner ^ Box3D::Y);
427         } else {
428             new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
429         }
430     } else {
431         if (normalized_angle (dir_3x - dir_3z) > 0) {
432             new_z_orders[1] = Box3D::face_containing_corner (Box3D::XZ, front_corner ^ Box3D::Y);
433             new_z_orders[2] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
434         } else {
435             if (normalized_angle (dir_1x - dir_1z) > 0) {
436                 new_z_orders[1] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
437                 new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
438             } else {
439                 new_z_orders[1] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
440                 new_z_orders[2] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
441             }
442         }
443     }
445     new_z_orders[3] = Box3D::opposite_face (new_z_orders[2]);
446     new_z_orders[4] = Box3D::opposite_face (new_z_orders[1]);
447     new_z_orders[5] = Box3D::opposite_face (new_z_orders[0]);
449     /* We only need to look for changes among the topmost three faces because the order
450        of the other ones is just inverted. */
451     if ((box->z_orders[0] != new_z_orders[0]) ||
452         (box->z_orders[1] != new_z_orders[1]) ||
453         (box->z_orders[2] != new_z_orders[2]))
454     {
455         for (int i = 0; i < 6; ++i) {
456             box->z_orders[i] = new_z_orders[i];
457         }
458         return true;
459     }
461     return false;
464 void sp_3dbox_set_z_orders (SP3DBox *box)
466     GSList *items = sp_item_group_item_list(SP_GROUP(box));
468     // For efficiency reasons, we only set the new z-orders if something really changed
469     if (sp_3dbox_recompute_z_orders (box)) {
470         box->faces[box->z_orders[0]]->lower_to_bottom ();
471         box->faces[box->z_orders[1]]->lower_to_bottom ();
472         box->faces[box->z_orders[2]]->lower_to_bottom ();
473         box->faces[box->z_orders[3]]->lower_to_bottom ();
474         box->faces[box->z_orders[4]]->lower_to_bottom ();
475         box->faces[box->z_orders[5]]->lower_to_bottom ();
476     }
479 void
480 sp_3dbox_update_curves (SP3DBox *box) {
481     for (int i = 0; i < 6; ++i) {
482         if (box->faces[i]) box->faces[i]->set_curve();
483     }
486 /**
487  * In some situations (e.g., after cloning boxes, undo & redo, or reading boxes from a file) there are
488  * paths already present in the document which correspond to the faces of newly created boxes, but their
489  * 'path' members don't link to them yet. The following function corrects this if necessary.
490  */
491 void
492 sp_3dbox_link_to_existing_paths (SP3DBox *box, Inkscape::XML::Node *repr) {
493     // TODO: We should probably destroy the existing paths and recreate them because we don't know
494     //       precisely which path corresponds to which face. Does this make a difference?
495     //       In sp_3dbox_write we write the correct paths anyway, don't we? But we could get into
496     //       trouble at a later stage when we only write single faces for degenerate boxes.
498     SPDocument *document = SP_OBJECT_DOCUMENT(box);
499     guint face_id = 0;
501     for (Inkscape::XML::Node *i = sp_repr_children(repr); i != NULL; i = sp_repr_next(i)) {
502         if (face_id > 5) {
503             g_warning ("SVG representation of 3D boxes must contain 6 paths or less.\n");
504             break;
505         }
507         SPObject *face_object = document->getObjectByRepr((Inkscape::XML::Node *) i);
508         if (!SP_IS_PATH(face_object)) {
509             g_warning ("SVG representation of 3D boxes should only contain paths.\n");
510             continue;
511         }
512         box->faces[face_id]->hook_path_to_3dbox(SP_PATH(face_object));
513         ++face_id;
514     }
515     if (face_id < 6) {
516         //g_warning ("SVG representation of 3D boxes should contain exactly 6 paths (degenerate boxes are not yet supported).\n");
517         // TODO: Check whether it is safe to add the remaining paths to the box and do so in case it is.
518         //       (But we also land here for newly created boxes where we shouldn't add any paths because
519         //       This is done in sp_3dbox_write later on.
520     }
523 void
524 sp_3dbox_move_corner_in_XY_plane (SP3DBox *box, guint id, NR::Point pt, Box3D::Axis axes)
526     Box3D::Perspective3D * persp = Box3D::get_persp_of_box (box);
528     NR::Point A (box->corners[id ^ Box3D::XY]);
529     if (Box3D::is_single_axis_direction (axes)) {
530         pt = Box3D::PerspectiveLine (box->corners[id], axes, persp).closest_to(pt);
531     }
533     /* set the 'front' corners */
534     box->corners[id] = pt;
536     Box3D::PerspectiveLine pl_one (A, Box3D::Y, persp);
537     Box3D::PerspectiveLine pl_two (pt, Box3D::X, persp);
538     box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
540     pl_one = Box3D::PerspectiveLine (A, Box3D::X, persp);
541     pl_two = Box3D::PerspectiveLine (pt, Box3D::Y, persp);
542     box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
544     /* set the 'rear' corners */
545     NR::Point B (box->corners[id ^ Box3D::XYZ]);
547     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Z, persp);
548     pl_two = Box3D::PerspectiveLine (B, Box3D::Y, persp);
549     box->corners[id ^ Box3D::XZ] = pl_one.meet(pl_two);
551     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XZ], Box3D::X, persp);
552     pl_two = Box3D::PerspectiveLine (pt, Box3D::Z, persp);
553     box->corners[id ^ Box3D::Z] = pl_one.meet(pl_two);
555     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::Z], Box3D::Y, persp);
556     pl_two = Box3D::PerspectiveLine (B, Box3D::X, persp);
557     box->corners[id ^ Box3D::YZ] = pl_one.meet(pl_two);
558     
561 void
562 sp_3dbox_move_corner_in_Z_direction (SP3DBox *box, guint id, NR::Point pt, bool constrained)
564     if (!constrained) sp_3dbox_move_corner_in_XY_plane (box, id, pt, Box3D::XY);
566     Box3D::Perspective3D * persp = Box3D::get_persp_of_box (box);
568     /* set the four corners of the face containing corners[id] */
569     box->corners[id] = Box3D::PerspectiveLine (box->corners[id], Box3D::Z, persp).closest_to(pt);
571     Box3D::PerspectiveLine pl_one (box->corners[id], Box3D::X, persp);
572     Box3D::PerspectiveLine pl_two (box->corners[id ^ Box3D::XZ], Box3D::Z, persp);
573     box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
575     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Y, persp);
576     pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XYZ], Box3D::Z, persp);
577     box->corners[id ^ Box3D::XY] = pl_one.meet(pl_two);
579     pl_one = Box3D::PerspectiveLine (box->corners[id], Box3D::Y, persp);
580     pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::YZ], Box3D::Z, persp);
581     box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
584 static void
585 sp_3dbox_reshape_edge_after_VP_toggling (SP3DBox *box, const guint corner, const Box3D::Axis axis, Box3D::Perspective3D *persp)
587     /* Hmm, perhaps we should simply use one of the corners as the pivot point.
588        But this way we minimize the amount of reshaping.
589        On second thought, we need to find a way to ensure that all boxes sharing the same
590        perspective are updated consistently _as a group_. That is, they should also retain
591        their relative positions towards each other. */
592     NR::Maybe<NR::Point> pt = sp_3dbox_get_midpoint_between_corners (box, corner, corner ^ axis);
593     g_return_if_fail (pt);
595     Box3D::Axis axis2 = ((axis == Box3D::Y) ? Box3D::X : Box3D::Y);
597     Box3D::PerspectiveLine line1 (box->corners[corner], axis2, persp);
598     Box3D::PerspectiveLine line2 (box->corners[corner ^ axis], axis2, persp);
600     Box3D::PerspectiveLine line3 (*pt, axis, persp);
602     NR::Point new_corner1 = line1.meet (line3);
603     NR::Point new_corner2 = line2.meet (line3);
605     box->corners[corner] = new_corner1;
606     box->corners[corner ^ axis] = new_corner2;
609 void
610 sp_3dbox_reshape_after_VP_toggling (SP3DBox *box, Box3D::Axis axis)
612     Box3D::Perspective3D *persp = Box3D::get_persp_of_box (box);
613     std::pair<Box3D::Axis, Box3D::Axis> dirs = Box3D::get_remaining_axes (axis);
615     sp_3dbox_reshape_edge_after_VP_toggling (box, 0, axis, persp);
616     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first, axis, persp);
617     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first ^ dirs.second, axis, persp);
618     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.second, axis, persp);
621 NR::Maybe<NR::Point>
622 sp_3dbox_get_center (SP3DBox *box)
624     return sp_3dbox_get_midpoint_between_corners (box, 0, 7);
627 // TODO: The following function can probably be rewritten in a much more elegant and robust way
628 //        by using projective coordinates for all points and using the cross ratio.
629 NR::Maybe<NR::Point>
630 sp_3dbox_get_midpoint_between_corners (SP3DBox *box, guint id_corner1, guint id_corner2)
632     Box3D::Axis corner_axes = (Box3D::Axis) (id_corner1 ^ id_corner2);
634     // Is all this sufficiently precise also for degenerate cases?
635     if (sp_3dbox_corners_are_adjacent (id_corner1, id_corner2)) {
636         Box3D::Axis orth_dir = get_perpendicular_axis_direction (corner_axes);
638         Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2 ^ orth_dir]);
639         Box3D::Line diag2 (box->corners[id_corner1 ^ orth_dir], box->corners[id_corner2]);
640         NR::Maybe<NR::Point> adjacent_face_center = diag1.intersect(diag2);
642         if (!adjacent_face_center) return NR::Nothing();
644         Box3D::Perspective3D * persp = Box3D::get_persp_of_box (box);
646         Box3D::PerspectiveLine pl (*adjacent_face_center, orth_dir, persp);
647         return pl.intersect(Box3D::PerspectiveLine(box->corners[id_corner1], corner_axes, persp));
648     } else {
649         Box3D::Axis dir = Box3D::extract_first_axis_direction (corner_axes);
650         Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2]);
651         Box3D::Line diag2 (box->corners[id_corner1 ^ dir], box->corners[id_corner2 ^ dir]);
652         return diag1.intersect(diag2);
653     }
656 static gchar *
657 sp_3dbox_get_corner_coords_string (SP3DBox *box, guint id)
659     id = id % 8;
660     Inkscape::SVGOStringStream os;
661     os << box->corners[id][NR::X] << "," << box->corners[id][NR::Y];
662     return g_strdup(os.str().c_str());
665 static std::pair<gdouble, gdouble>
666 sp_3dbox_get_coord_pair_from_string (const gchar *coords)
668     gchar **coordpair = g_strsplit( coords, ",", 0);
669     // We might as well rely on g_ascii_strtod to convert the NULL pointer to 0.0,
670     // but we include the following test anyway
671     if (coordpair[0] == NULL || coordpair[1] == NULL) {
672         g_strfreev (coordpair);
673         g_warning ("Coordinate conversion failed.\n");
674         return std::make_pair(0.0, 0.0);
675     }
677     gdouble coord1 = g_ascii_strtod(coordpair[0], NULL);
678     gdouble coord2 = g_ascii_strtod(coordpair[1], NULL);
679     g_strfreev (coordpair);
681     return std::make_pair(coord1, coord2);
684 static gchar *
685 sp_3dbox_get_perspective_string (SP3DBox *box)
687     
688     return sp_3dbox_get_svg_descr_of_persp (Box3D::get_persp_of_box (box));
690   
691 gchar *
692 sp_3dbox_get_svg_descr_of_persp (Box3D::Perspective3D *persp)
694     // FIXME: We should move this code to perspective3d.cpp, but this yields compiler errors. Why?
695     Inkscape::SVGOStringStream os;
697     Box3D::VanishingPoint vp = *(persp->get_vanishing_point (Box3D::X));
698     os << vp[NR::X] << "," << vp[NR::Y] << ",";
699     os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
700     if (vp.is_finite()) {
701         os << "finite,";
702     } else {
703         os << "infinite,";
704     }
706     vp = *(persp->get_vanishing_point (Box3D::Y));
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::Z));
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     return g_strdup(os.str().c_str());
727 void sp_3dbox_update_perspective_lines()
729     SPEventContext *ec = inkscape_active_event_context();
730     if (!SP_IS_3DBOX_CONTEXT (ec))
731         return;
733     SP_3DBOX_CONTEXT (ec)->_vpdrag->updateLines();
736 /*
737  * Manipulates corner1 through corner4 to contain the indices of the corners
738  * from which the perspective lines in the direction of 'axis' emerge
739  */
740 void sp_3dbox_corners_for_perspective_lines (const SP3DBox * box, Box3D::Axis axis, 
741                                              NR::Point &corner1, NR::Point &corner2, NR::Point &corner3, NR::Point &corner4)
743     // along which axis to switch when takint
744     Box3D::Axis switch_axis;
745     if (axis == Box3D::X || axis == Box3D::Y) {
746         switch_axis = (box->front_bits & axis) ? Box3D::Z : Box3D::NONE;
747     } else {
748         switch_axis = (box->front_bits & axis) ? Box3D::X : Box3D::NONE;
749     }
751     switch (axis) {
752         case Box3D::X:
753             corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
754             corner2 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
755             corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
756             corner4 = sp_3dbox_get_corner_along_edge (box, 6 ^ switch_axis, axis, Box3D::REAR);
757             break;
758         case Box3D::Y:
759             corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
760             corner2 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
761             corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
762             corner4 = sp_3dbox_get_corner_along_edge (box, 5 ^ switch_axis, axis, Box3D::REAR);
763             break;
764         case Box3D::Z:
765             corner1 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
766             corner2 = sp_3dbox_get_corner_along_edge (box, 3 ^ switch_axis, axis, Box3D::REAR);
767             corner3 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
768             corner4 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
769             break;
770     }            
773 /**
774  * Returns the id of the corner on the edge along 'axis' and passing through 'corner' that
775  * lies on the front/rear face in this direction.
776  */
777 guint
778 sp_3dbox_get_corner_id_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
780     guint result;
781     guint other_corner = corner ^ axis;
782     Box3D::VanishingPoint *vp = Box3D::get_persp_of_box (box)->get_vanishing_point(axis);
783     if (vp->is_finite()) {
784         result = (  NR::L2 (vp->get_pos() - box->corners[corner])
785                   < NR::L2 (vp->get_pos() - box->corners[other_corner]) ? other_corner : corner);
786     } else {
787         // clear the axis bit and switch to the appropriate corner along axis, depending on the value of front_bits
788         result = ((corner & (0xF ^ axis)) ^ (box->front_bits & axis));
789     }
791     if (rel_pos == Box3D::FRONT) {
792         return result;
793     } else {
794         return result ^ axis;
795     }
798 NR::Point
799 sp_3dbox_get_corner_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
801     return box->corners[sp_3dbox_get_corner_id_along_edge (box, corner, axis, rel_pos)];
804 guint
805 sp_3dbox_get_front_corner_id (const SP3DBox *box)
807     guint front_corner = 1; // this could in fact be any corner, but we choose the one that is normally in front
808     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::X, Box3D::FRONT);
809     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Y, Box3D::FRONT);
810     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Z, Box3D::FRONT);
811     return front_corner;
814 // auxiliary functions
815 static void
816 sp_3dbox_update_corner_with_value_from_svg (SPObject *object, guint corner_id, const gchar *value)
818     if (value == NULL) return;
819     SP3DBox *box = SP_3DBOX(object);
821     std::pair<gdouble, gdouble> coord_pair = sp_3dbox_get_coord_pair_from_string (value);
822     box->corners[corner_id] = NR::Point (coord_pair.first, coord_pair.second);
823     sp_3dbox_recompute_corners (box, box->corners[2], box->corners[1], box->corners[5]);
824     object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
827 static void
828 sp_3dbox_update_perspective (Box3D::Perspective3D *persp, const gchar *value)
830     // WARNING! This function changes the perspective associated to 'box'. Since there may be
831     // many other boxes linked to the same perspective, their perspective is also changed.
832     // If this behaviour is not desired in all cases, we need a different function.
833     if (value == NULL) return;
835     gchar **vps = g_strsplit( value, ",", 0);
836     for (int i = 0; i < 15; ++i) {
837         if (vps[i] == NULL) {
838             g_warning ("Malformed svg attribute 'perspective'\n");
839             return;
840         }
841     }
843     persp->set_vanishing_point (Box3D::X, g_ascii_strtod (vps[0], NULL), g_ascii_strtod (vps[1], NULL),
844                                           g_ascii_strtod (vps[2], NULL), g_ascii_strtod (vps[3], NULL),
845                                           strcmp (vps[4], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
846     persp->set_vanishing_point (Box3D::Y, g_ascii_strtod (vps[5], NULL), g_ascii_strtod (vps[6], NULL),
847                                           g_ascii_strtod (vps[7], NULL), g_ascii_strtod (vps[8], NULL),
848                                           strcmp (vps[9], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
849     persp->set_vanishing_point (Box3D::Z, g_ascii_strtod (vps[10], NULL), g_ascii_strtod (vps[11], NULL),
850                                           g_ascii_strtod (vps[12], NULL), g_ascii_strtod (vps[13], NULL),
851                                           strcmp (vps[14], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
853     // update the other boxes linked to the same perspective
854     persp->reshape_boxes (Box3D::XYZ);
857 /*
858   Local Variables:
859   mode:c++
860   c-file-style:"stroustrup"
861   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
862   indent-tabs-mode:nil
863   fill-column:99
864   End:
865 */
866 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :