Code

Enable center-dragging of boxes in Z direction by using Shift
[inkscape.git] / src / box3d.cpp
1 #define __SP_3DBOX_C__
3 /*
4  * SVG <box3d> implementation
5  *
6  * Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   bulia byak <buliabyak@users.sf.net>
9  *   Maximilian Albert <Anhalter42@gmx.de>
10  *
11  * Copyright (C) 2007      Authors
12  * Copyright (C) 1999-2002 Lauris Kaplinski
13  * Copyright (C) 2000-2001 Ximian, Inc.
14  *
15  * Released under GNU GPL, read the file 'COPYING' for more information
16  */
18 #include <glibmm/i18n.h>
19 #include "attributes.h"
20 #include "svg/stringstream.h"
21 #include "box3d.h"
22 #include "desktop-handles.h"
24 static void sp_3dbox_class_init(SP3DBoxClass *klass);
25 static void sp_3dbox_init(SP3DBox *box3d);
27 static void sp_3dbox_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
28 static void sp_3dbox_release (SPObject *object);
29 static void sp_3dbox_set(SPObject *object, unsigned int key, const gchar *value);
30 static void sp_3dbox_update(SPObject *object, SPCtx *ctx, guint flags);
31 static Inkscape::XML::Node *sp_3dbox_write(SPObject *object, Inkscape::XML::Node *repr, guint flags);
33 static gchar *sp_3dbox_description(SPItem *item);
35 //static void sp_3dbox_set_shape(SPShape *shape);
36 //static void sp_3dbox_set_shape(SP3DBox *box3d);
38 static void sp_3dbox_update_corner_with_value_from_svg (SPObject *object, guint corner_id, const gchar *value);
39 static void sp_3dbox_update_perspective (Box3D::Perspective3D *persp, const gchar *value);
40 static gchar * sp_3dbox_get_corner_coords_string (SP3DBox *box, guint id);
41 static std::pair<gdouble, gdouble> sp_3dbox_get_coord_pair_from_string (const gchar *);
42 static gchar * sp_3dbox_get_perspective_string (SP3DBox *box);
44 static SPGroupClass *parent_class;
46 static gint counter = 0;
48 GType
49 sp_3dbox_get_type(void)
50 {
51     static GType type = 0;
53     if (!type) {
54         GTypeInfo info = {
55             sizeof(SP3DBoxClass),
56             NULL,   /* base_init */
57             NULL,   /* base_finalize */
58             (GClassInitFunc) sp_3dbox_class_init,
59             NULL,   /* class_finalize */
60             NULL,   /* class_data */
61             sizeof(SP3DBox),
62             16,     /* n_preallocs */
63             (GInstanceInitFunc) sp_3dbox_init,
64             NULL,   /* value_table */
65         };
66         type = g_type_register_static(SP_TYPE_GROUP, "SP3DBox", &info, (GTypeFlags) 0);
67     }
69     return type;
70 }
72 static void
73 sp_3dbox_class_init(SP3DBoxClass *klass)
74 {
75     SPObjectClass *sp_object_class = (SPObjectClass *) klass;
76     SPItemClass *item_class = (SPItemClass *) klass;
78     parent_class = (SPGroupClass *) g_type_class_ref(SP_TYPE_GROUP);
80     sp_object_class->build = sp_3dbox_build;
81     sp_object_class->set = sp_3dbox_set;
82     sp_object_class->write = sp_3dbox_write;
83     sp_object_class->update = sp_3dbox_update;
84     sp_object_class->release = sp_3dbox_release;
86     item_class->description = sp_3dbox_description;
87 }
89 static void
90 sp_3dbox_init(SP3DBox *box)
91 {
92     for (int i = 0; i < 8; ++i) box->corners[i] = NR::Point(0,0);
93     for (int i = 0; i < 6; ++i) box->faces[i] = NULL;
94 }
96 static void
97 sp_3dbox_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
98 {
99     if (((SPObjectClass *) (parent_class))->build) {
100         ((SPObjectClass *) (parent_class))->build(object, document, repr);
101     }
103     SP3DBox *box = SP_3DBOX (object);
105     box->my_counter = counter++;
107     /* we initialize the z-orders to zero so that they are updated during dragging */
108     for (int i = 0; i < 6; ++i) {
109         box->z_orders[i] = 0;
110     }
112     box->front_bits = 0x0;
114     
115     if (repr->attribute ("inkscape:perspective") == NULL) {
116         // we are creating a new box; link it to the current perspective
117         document->current_perspective->add_box (box);
118     } else {
119         // create a new perspective that we can compare with existing ones
120         Box3D::Perspective3D *persp = new Box3D::Perspective3D (Box3D::VanishingPoint (0,0),
121                                                                 Box3D::VanishingPoint (0,0),
122                                                                 Box3D::VanishingPoint (0,0),
123                                                                 document);
124         sp_3dbox_update_perspective (persp, repr->attribute ("inkscape:perspective"));
125         Box3D::Perspective3D *comp =  document->find_perspective (persp);
126         if (comp == NULL) {
127             // perspective doesn't exist yet
128             document->add_perspective (persp);
129             persp->add_box (box);
130         } else {
131             // link the box to the existing perspective and delete the temporary one
132             comp->add_box (box);
133             delete persp;
134             //g_assert (Box3D::get_persp_of_box (box) == comp);
136             // FIXME: If the paths of the box's faces do not correspond to the svg representation of the perspective
137             //        the box is shown with a "wrong" initial shape that is only corrected after dragging.
138             //        Should we "repair" this by updating the paths at the end of sp_3dbox_build()?
139             //        Maybe it would be better to simply destroy and rebuild them in sp_3dbox_link_to_existing_paths().
140         }
141     }
143     sp_object_read_attr(object, "inkscape:box3dcornerA");
144     sp_object_read_attr(object, "inkscape:box3dcornerB");
145     sp_object_read_attr(object, "inkscape:box3dcornerC");
147     // TODO: We create all faces in the beginning, but only the non-degenerate ones
148     //       should be written to the svg representation later in sp_3dbox_write.
149     Box3D::Axis cur_plane, axis, dir1, dir2;
150     Box3D::FrontOrRear cur_pos;
151     for (int i = 0; i < 3; ++i) {
152         for (int j = 0; j < 2; ++j) {
153             cur_plane = Box3D::planes[i];
154             cur_pos = Box3D::face_positions[j];
155             // FIXME: The following code could theoretically be moved to
156             //        the constructor of Box3DFace (but see the comment there).
157             axis = (cur_pos == Box3D::FRONT ? Box3D::NONE : Box3D::third_axis_direction (cur_plane));
158             dir1 = extract_first_axis_direction (cur_plane);
159             dir2 = extract_second_axis_direction (cur_plane);
160             
161             box->faces[Box3D::face_to_int(cur_plane ^ cur_pos)] =
162                 new Box3DFace (box, box->corners[axis], box->corners[axis ^ dir1],
163                                     box->corners[axis ^ dir1 ^ dir2], box->corners[axis ^ dir2],
164                                     cur_plane, cur_pos);
165         }
166     }
168     // Check whether the paths of the faces of the box need to be linked to existing paths in the
169     // document (e.g., after a 'redo' operation or after opening a file) and do so if necessary.
170     sp_3dbox_link_to_existing_paths (box, repr);
172     sp_3dbox_set_ratios (box, Box3D::XYZ);
174     // Store the center (if it already exists) and certain corners for later use during center-dragging
175     NR::Maybe<NR::Point> cen = sp_3dbox_get_center (box);
176     if (cen) {
177         box->old_center = *cen;
178     }
179     box->old_corner2 = box->corners[2];
180     box->old_corner1 = box->corners[1];
181     box->old_corner0 = box->corners[0];
182     box->old_corner3 = box->corners[3];
183     box->old_corner5 = box->corners[5];
184     box->old_corner7 = box->corners[7];
187 static void
188 sp_3dbox_release (SPObject *object)
190         SP3DBox *box = SP_3DBOX(object);
191         for (int i = 0; i < 6; ++i) {
192             if (box->faces[i]) {
193                 delete box->faces[i]; // FIXME: Anything else to do? Do we need to clean up the face first?
194             }
195         }
197         // FIXME: We do not duplicate perspectives if they are the same for several boxes.
198         //        Thus, don't delete the perspective when deleting a box but rather unlink the box from it.
199         SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->remove_box (box);
201         if (((SPObjectClass *) parent_class)->release) {
202           ((SPObjectClass *) parent_class)->release (object);
203         }
206 static void sp_3dbox_set(SPObject *object, unsigned int key, const gchar *value)
208     switch (key) {
209         case SP_ATTR_INKSCAPE_3DBOX_CORNER_A:
210             sp_3dbox_update_corner_with_value_from_svg (object, 2, value);
211             break;
212         case SP_ATTR_INKSCAPE_3DBOX_CORNER_B:
213             sp_3dbox_update_corner_with_value_from_svg (object, 1, value);
214             break;
215         case SP_ATTR_INKSCAPE_3DBOX_CORNER_C:
216             sp_3dbox_update_corner_with_value_from_svg (object, 5, value);
217             break;
218         case SP_ATTR_INKSCAPE_3DBOX_PERSPECTIVE:
219         {
220             SP3DBox *box = SP_3DBOX (object);
221             sp_3dbox_update_perspective (SP_OBJECT_DOCUMENT (object)->get_persp_of_box (box), value);
222             break;
223         }
224         default:
225             if (((SPObjectClass *) (parent_class))->set) {
226                 ((SPObjectClass *) (parent_class))->set(object, key, value);
227             }
228             break;
229     }
232 static void
233 sp_3dbox_update(SPObject *object, SPCtx *ctx, guint flags)
235     if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
236         SP3DBox *box = SP_3DBOX(object);
237         sp_3dbox_link_to_existing_paths (box, SP_OBJECT_REPR(object));
238     }
240     /* Invoke parent method */
241     if (((SPObjectClass *) (parent_class))->update)
242         ((SPObjectClass *) (parent_class))->update(object, ctx, flags);
247 static Inkscape::XML::Node *sp_3dbox_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
249     SP3DBox *box = SP_3DBOX(object);
250     // FIXME: How to handle other contexts???
251     // FIXME: Is tools_isactive(..) more recommended to check for the current context/tool?
252     if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
253         return repr;
255     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
256         Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
257         repr = xml_doc->createElement("svg:g");
258         repr->setAttribute("sodipodi:type", "inkscape:3dbox");
259         /* Hook paths to the faces of the box */
260         for (int i = 0; i < 6; ++i) {
261             box->faces[i]->hook_path_to_3dbox();
262         }
263     }
265     for (int i = 0; i < 6; ++i) {
266         box->faces[i]->set_path_repr();
267     }
269     if (flags & SP_OBJECT_WRITE_EXT) {
270         gchar *str;
271         str = sp_3dbox_get_corner_coords_string (box, 2);
272         repr->setAttribute("inkscape:box3dcornerA", str);
274         str = sp_3dbox_get_corner_coords_string (box, 1);
275         repr->setAttribute("inkscape:box3dcornerB", str);
277         str = sp_3dbox_get_corner_coords_string (box, 5);
278         repr->setAttribute("inkscape:box3dcornerC", str);
280         str = sp_3dbox_get_perspective_string (box);
281         repr->setAttribute("inkscape:perspective", str);
282         sp_3dbox_set_ratios (box);
284         g_free ((void *) str);
286         /* store center and construction-corners for later use during center-dragging */
287         NR::Maybe<NR::Point> cen = sp_3dbox_get_center (box);
288         if (cen) {
289             box->old_center = *cen;
290         }
291         box->old_corner2 = box->corners[2];
292         box->old_corner1 = box->corners[1];
293         box->old_corner0 = box->corners[0];
294         box->old_corner3 = box->corners[3];
295         box->old_corner5 = box->corners[5];
296         box->old_corner7 = box->corners[7];
297     }
299     if (((SPObjectClass *) (parent_class))->write) {
300         ((SPObjectClass *) (parent_class))->write(object, repr, flags);
301     }
303     return repr;
306 static gchar *
307 sp_3dbox_description(SPItem *item)
309     g_return_val_if_fail(SP_IS_3DBOX(item), NULL);
311     return g_strdup(_("<b>3D Box</b>"));
314 void sp_3dbox_set_ratios (SP3DBox *box, Box3D::Axis axes)
316     Box3D::Perspective3D *persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
317     NR::Point pt;
319     if (axes & Box3D::X) {
320         pt = persp->get_vanishing_point (Box3D::X)->get_pos();
321         box->ratio_x = NR::L2 (pt - box->corners[2]) / NR::L2 (pt - box->corners[3]);
322     }
324     if (axes & Box3D::Y) {
325         pt = persp->get_vanishing_point (Box3D::Y)->get_pos();
326         box->ratio_y = NR::L2 (pt - box->corners[2]) / NR::L2 (pt - box->corners[0]);
327     }
329     if (axes & Box3D::Z) {
330         pt = persp->get_vanishing_point (Box3D::Z)->get_pos();
331         box->ratio_z = NR::L2 (pt - box->corners[4]) / NR::L2 (pt - box->corners[0]);
332     }
335 void
336 sp_3dbox_switch_front_face (SP3DBox *box, Box3D::Axis axis)
338     if (SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point (axis)->is_finite()) {
339         box->front_bits = box->front_bits ^ axis;
340     }
344 void
345 sp_3dbox_position_set (SP3DBoxContext &bc)
347     SP3DBox *box3d = SP_3DBOX(bc.item);
349     sp_3dbox_set_shape(box3d);
351     // FIXME: Why does the following call not automatically update the children
352     //        of box3d (which is an SPGroup, which should do this)?
353     //SP_OBJECT(box3d)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
355     /**
356     SP_OBJECT(box3d->path_face1)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
357     SP_OBJECT(box3d->path_face2)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
358     SP_OBJECT(box3d->path_face3)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
359     SP_OBJECT(box3d->path_face4)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
360     SP_OBJECT(box3d->path_face5)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
361     SP_OBJECT(box3d->path_face6)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
362     ***/
365 static void
366 sp_3dbox_set_shape_from_points (SP3DBox *box, NR::Point const &cornerA, NR::Point const &cornerB, NR::Point const &cornerC)
368     sp_3dbox_recompute_corners (box, cornerA, cornerB, cornerC);
370     // FIXME: How to handle other contexts???
371     // FIXME: Is tools_isactive(..) more recommended to check for the current context/tool?
372     if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
373         return;
374     SP3DBoxContext *bc = SP_3DBOX_CONTEXT(inkscape_active_event_context());
376     if (bc->extruded) {
377         box->faces[0]->set_corners (box->corners[0], box->corners[4], box->corners[6], box->corners[2]);
378         box->faces[1]->set_corners (box->corners[1], box->corners[5], box->corners[7], box->corners[3]);
379         box->faces[2]->set_corners (box->corners[0], box->corners[1], box->corners[5], box->corners[4]);
380         box->faces[3]->set_corners (box->corners[2], box->corners[3], box->corners[7], box->corners[6]);
381         box->faces[5]->set_corners (box->corners[4], box->corners[5], box->corners[7], box->corners[6]);
382     }
383     box->faces[4]->set_corners (box->corners[0], box->corners[1], box->corners[3], box->corners[2]);
385     sp_3dbox_update_curves (box);
388 void
389 // FIXME: Note that this is _not_ the virtual set_shape() method inherited from SPShape,
390 //        since SP3DBox is inherited from SPGroup. The following method is "artificially"
391 //        called from sp_3dbox_update().
392 //sp_3dbox_set_shape(SPShape *shape)
393 sp_3dbox_set_shape(SP3DBox *box, bool use_previous_corners)
395     if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
396         return;
397     SP3DBoxContext *bc = SP_3DBOX_CONTEXT(inkscape_active_event_context());
399     if (!use_previous_corners) {
400         sp_3dbox_set_shape_from_points (box, bc->drag_origin, bc->drag_ptB, bc->drag_ptC);
401     } else {
402         sp_3dbox_set_shape_from_points (box, box->corners[2], box->corners[1], box->corners[5]);
403     }
407 void sp_3dbox_recompute_corners (SP3DBox *box, NR::Point const A, NR::Point const B, NR::Point const C)
409     sp_3dbox_move_corner_in_XY_plane (box, 2, A);
410     sp_3dbox_move_corner_in_XY_plane (box, 1, B);
411     sp_3dbox_move_corner_in_Z_direction (box, 5, C);
414 inline static double
415 normalized_angle (double angle) {
416     if (angle < -M_PI) {
417         return angle + 2*M_PI;
418     } else if (angle > M_PI) {
419         return angle - 2*M_PI;
420     }
421     return angle;
424 static gdouble
425 sp_3dbox_corner_angle_to_VP (SP3DBox *box, Box3D::Axis axis, guint extreme_corner)
427     Box3D::VanishingPoint *vp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point (axis);
428     NR::Point dir;
430     if (vp->is_finite()) {
431         dir = NR::unit_vector (vp->get_pos() - box->corners[extreme_corner]);
432     } else {
433         dir = NR::unit_vector (vp->v_dir);
434     }
436     return atan2 (dir[NR::Y], dir[NR::X]);
440 bool sp_3dbox_recompute_z_orders (SP3DBox *box)
442     guint new_z_orders[6];
444     // TODO: Determine the front corner depending on the distance from VPs and/or the user presets
445     guint front_corner = sp_3dbox_get_front_corner_id (box);
447     gdouble dir_1x = sp_3dbox_corner_angle_to_VP (box, Box3D::X, front_corner);
448     gdouble dir_3x = sp_3dbox_corner_angle_to_VP (box, Box3D::X, front_corner ^ Box3D::Y);
450     gdouble dir_1y = sp_3dbox_corner_angle_to_VP (box, Box3D::Y, front_corner);
451     //gdouble dir_0y = sp_3dbox_corner_angle_to_VP (box, Box3D::Y, front_corner ^ Box3D::X);
453     gdouble dir_1z = sp_3dbox_corner_angle_to_VP (box, Box3D::Z, front_corner);
454     gdouble dir_3z = sp_3dbox_corner_angle_to_VP (box, Box3D::Z, front_corner ^ Box3D::Y);
456     // Still not perfect, but only fails in some rather degenerate cases.
457     // I suspect that there is a more elegant model, though. :)
458     new_z_orders[0] = Box3D::face_containing_corner (Box3D::XY, front_corner);
459     if (normalized_angle (dir_1y - dir_1z) > 0) {
460         new_z_orders[1] = Box3D::face_containing_corner (Box3D::YZ, front_corner);
461         if (normalized_angle (dir_1x - dir_1z) > 0) {
462             new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner ^ Box3D::Y);
463         } else {
464             new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
465         }
466     } else {
467         if (normalized_angle (dir_3x - dir_3z) > 0) {
468             new_z_orders[1] = Box3D::face_containing_corner (Box3D::XZ, front_corner ^ Box3D::Y);
469             new_z_orders[2] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
470         } else {
471             if (normalized_angle (dir_1x - dir_1z) > 0) {
472                 new_z_orders[1] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
473                 new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
474             } else {
475                 new_z_orders[1] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
476                 new_z_orders[2] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
477             }
478         }
479     }
481     new_z_orders[3] = Box3D::opposite_face (new_z_orders[2]);
482     new_z_orders[4] = Box3D::opposite_face (new_z_orders[1]);
483     new_z_orders[5] = Box3D::opposite_face (new_z_orders[0]);
485     /* We only need to look for changes among the topmost three faces because the order
486        of the other ones is just inverted. */
487     if ((box->z_orders[0] != new_z_orders[0]) ||
488         (box->z_orders[1] != new_z_orders[1]) ||
489         (box->z_orders[2] != new_z_orders[2]))
490     {
491         for (int i = 0; i < 6; ++i) {
492             box->z_orders[i] = new_z_orders[i];
493         }
494         return true;
495     }
497     return false;
500 void sp_3dbox_set_z_orders (SP3DBox *box)
502     // For efficiency reasons, we only set the new z-orders if something really changed
503     if (sp_3dbox_recompute_z_orders (box)) {
504         box->faces[box->z_orders[0]]->lower_to_bottom ();
505         box->faces[box->z_orders[1]]->lower_to_bottom ();
506         box->faces[box->z_orders[2]]->lower_to_bottom ();
507         box->faces[box->z_orders[3]]->lower_to_bottom ();
508         box->faces[box->z_orders[4]]->lower_to_bottom ();
509         box->faces[box->z_orders[5]]->lower_to_bottom ();
510     }
513 void
514 sp_3dbox_update_curves (SP3DBox *box) {
515     for (int i = 0; i < 6; ++i) {
516         if (box->faces[i]) box->faces[i]->set_curve();
517     }
520 /**
521  * In some situations (e.g., after cloning boxes, undo & redo, or reading boxes from a file) there are
522  * paths already present in the document which correspond to the faces of newly created boxes, but their
523  * 'path' members don't link to them yet. The following function corrects this if necessary.
524  */
525 void
526 sp_3dbox_link_to_existing_paths (SP3DBox *box, Inkscape::XML::Node *repr) {
527     // TODO: We should probably destroy the existing paths and recreate them because we don't know
528     //       precisely which path corresponds to which face. Does this make a difference?
529     //       In sp_3dbox_write we write the correct paths anyway, don't we? But we could get into
530     //       trouble at a later stage when we only write single faces for degenerate boxes.
532     SPDocument *document = SP_OBJECT_DOCUMENT(box);
533     guint face_id = 0;
535     for (Inkscape::XML::Node *i = sp_repr_children(repr); i != NULL; i = sp_repr_next(i)) {
536         if (face_id > 5) {
537             g_warning ("SVG representation of 3D boxes must contain 6 paths or less.\n");
538             break;
539         }
541         SPObject *face_object = document->getObjectByRepr((Inkscape::XML::Node *) i);
542         if (!SP_IS_PATH(face_object)) {
543             g_warning ("SVG representation of 3D boxes should only contain paths.\n");
544             continue;
545         }
546         // TODO: Currently we don't check whether all paths are being linked to different faces.
547         //       This is no problem with valid SVG files. It may lead to crashes, however,
548         //       in case a file is corrupt (e.g., two or more faces have identical descriptions).
549         gint id = Box3DFace::descr_to_id (i->attribute ("inkscape:box3dface"));
550         box->faces[id]->hook_path_to_3dbox(SP_PATH(face_object));
551         ++face_id;
552     }
553     if (face_id < 6) {
554         //g_warning ("SVG representation of 3D boxes should contain exactly 6 paths (degenerate boxes are not yet supported).\n");
555         // TODO: Check whether it is safe to add the remaining paths to the box and do so in case it is.
556         //       (But we also land here for newly created boxes where we shouldn't add any paths because
557         //       This is done in sp_3dbox_write later on.
558     }
561 void
562 sp_3dbox_move_corner_in_XY_plane (SP3DBox *box, guint id, NR::Point pt, Box3D::Axis axes)
564     Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
566     NR::Point A (box->corners[id ^ Box3D::XY]);
567     if (Box3D::is_single_axis_direction (axes)) {
568         pt = Box3D::PerspectiveLine (box->corners[id], axes, persp).closest_to(pt);
569     }
571     /* set the 'front' corners */
572     box->corners[id] = pt;
574     Box3D::PerspectiveLine pl_one (A, Box3D::Y, persp);
575     Box3D::PerspectiveLine pl_two (pt, Box3D::X, persp);
576     box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
578     pl_one = Box3D::PerspectiveLine (A, Box3D::X, persp);
579     pl_two = Box3D::PerspectiveLine (pt, Box3D::Y, persp);
580     box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
582     /* set the 'rear' corners */
583     NR::Point B (box->corners[id ^ Box3D::XYZ]);
585     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Z, persp);
586     pl_two = Box3D::PerspectiveLine (B, Box3D::Y, persp);
587     box->corners[id ^ Box3D::XZ] = pl_one.meet(pl_two);
589     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XZ], Box3D::X, persp);
590     pl_two = Box3D::PerspectiveLine (pt, Box3D::Z, persp);
591     box->corners[id ^ Box3D::Z] = pl_one.meet(pl_two);
593     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::Z], Box3D::Y, persp);
594     pl_two = Box3D::PerspectiveLine (B, Box3D::X, persp);
595     box->corners[id ^ Box3D::YZ] = pl_one.meet(pl_two);
596     
599 void
600 sp_3dbox_move_corner_in_Z_direction (SP3DBox *box, guint id, NR::Point pt, bool constrained)
602     if (!constrained) sp_3dbox_move_corner_in_XY_plane (box, id, pt, Box3D::XY);
604     Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
606     /* set the four corners of the face containing corners[id] */
607     box->corners[id] = Box3D::PerspectiveLine (box->corners[id], Box3D::Z, persp).closest_to(pt);
609     Box3D::PerspectiveLine pl_one (box->corners[id], Box3D::X, persp);
610     Box3D::PerspectiveLine pl_two (box->corners[id ^ Box3D::XZ], Box3D::Z, persp);
611     box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
613     pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Y, persp);
614     pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XYZ], Box3D::Z, persp);
615     box->corners[id ^ Box3D::XY] = pl_one.meet(pl_two);
617     pl_one = Box3D::PerspectiveLine (box->corners[id], Box3D::Y, persp);
618     pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::YZ], Box3D::Z, persp);
619     box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
622 static void
623 sp_3dbox_reshape_edge_after_VP_toggling (SP3DBox *box, const guint corner, const Box3D::Axis axis, Box3D::Perspective3D *persp)
625     /* Hmm, perhaps we should simply use one of the corners as the pivot point.
626        But this way we minimize the amount of reshaping.
627        On second thought, we need to find a way to ensure that all boxes sharing the same
628        perspective are updated consistently _as a group_. That is, they should also retain
629        their relative positions towards each other. */
630     NR::Maybe<NR::Point> pt = sp_3dbox_get_midpoint_between_corners (box, corner, corner ^ axis);
631     g_return_if_fail (pt);
633     Box3D::Axis axis2 = ((axis == Box3D::Y) ? Box3D::X : Box3D::Y);
635     Box3D::PerspectiveLine line1 (box->corners[corner], axis2, persp);
636     Box3D::PerspectiveLine line2 (box->corners[corner ^ axis], axis2, persp);
638     Box3D::PerspectiveLine line3 (*pt, axis, persp);
640     NR::Point new_corner1 = line1.meet (line3);
641     NR::Point new_corner2 = line2.meet (line3);
643     box->corners[corner] = new_corner1;
644     box->corners[corner ^ axis] = new_corner2;
647 void
648 sp_3dbox_reshape_after_VP_toggling (SP3DBox *box, Box3D::Axis axis)
650     Box3D::Perspective3D *persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
651     std::pair<Box3D::Axis, Box3D::Axis> dirs = Box3D::get_remaining_axes (axis);
653     sp_3dbox_reshape_edge_after_VP_toggling (box, 0, axis, persp);
654     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first, axis, persp);
655     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first ^ dirs.second, axis, persp);
656     sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.second, axis, persp);
659 NR::Maybe<NR::Point>
660 sp_3dbox_get_center (SP3DBox *box)
662     return sp_3dbox_get_midpoint_between_corners (box, 0, 7);
665 NR::Point
666 sp_3dbox_get_midpoint_in_axis_direction (NR::Point const &C, NR::Point const &D, Box3D::Axis axis, Box3D::Perspective3D *persp)
668     Box3D::PerspectiveLine pl (D, axis, persp);
669     return pl.pt_with_given_cross_ratio (C, D, -1.0);
672 // TODO: The following function can probably be rewritten in a much more elegant and robust way
673 //        by using projective coordinates for all points and using the cross ratio.
674 NR::Maybe<NR::Point>
675 sp_3dbox_get_midpoint_between_corners (SP3DBox *box, guint id_corner1, guint id_corner2)
677     Box3D::Axis corner_axes = (Box3D::Axis) (id_corner1 ^ id_corner2);
679     // Is all this sufficiently precise also for degenerate cases?
680     if (sp_3dbox_corners_are_adjacent (id_corner1, id_corner2)) {
681         Box3D::Axis orth_dir = get_perpendicular_axis_direction (corner_axes);
683         Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2 ^ orth_dir]);
684         Box3D::Line diag2 (box->corners[id_corner1 ^ orth_dir], box->corners[id_corner2]);
685         NR::Maybe<NR::Point> adjacent_face_center = diag1.intersect(diag2);
687         if (!adjacent_face_center) return NR::Nothing();
689         Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
691         Box3D::PerspectiveLine pl (*adjacent_face_center, orth_dir, persp);
692         return pl.intersect(Box3D::PerspectiveLine(box->corners[id_corner1], corner_axes, persp));
693     } else {
694         Box3D::Axis dir = Box3D::extract_first_axis_direction (corner_axes);
695         Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2]);
696         Box3D::Line diag2 (box->corners[id_corner1 ^ dir], box->corners[id_corner2 ^ dir]);
697         return diag1.intersect(diag2);
698     }
701 static gchar *
702 sp_3dbox_get_corner_coords_string (SP3DBox *box, guint id)
704     id = id % 8;
705     Inkscape::SVGOStringStream os;
706     os << box->corners[id][NR::X] << "," << box->corners[id][NR::Y];
707     return g_strdup(os.str().c_str());
710 static std::pair<gdouble, gdouble>
711 sp_3dbox_get_coord_pair_from_string (const gchar *coords)
713     gchar **coordpair = g_strsplit( coords, ",", 0);
714     // We might as well rely on g_ascii_strtod to convert the NULL pointer to 0.0,
715     // but we include the following test anyway
716     if (coordpair[0] == NULL || coordpair[1] == NULL) {
717         g_strfreev (coordpair);
718         g_warning ("Coordinate conversion failed.\n");
719         return std::make_pair(0.0, 0.0);
720     }
722     gdouble coord1 = g_ascii_strtod(coordpair[0], NULL);
723     gdouble coord2 = g_ascii_strtod(coordpair[1], NULL);
724     g_strfreev (coordpair);
726     return std::make_pair(coord1, coord2);
729 static gchar *
730 sp_3dbox_get_perspective_string (SP3DBox *box)
732     
733     return sp_3dbox_get_svg_descr_of_persp (SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box));
735   
736 gchar *
737 sp_3dbox_get_svg_descr_of_persp (Box3D::Perspective3D *persp)
739     // FIXME: We should move this code to perspective3d.cpp, but this yields compiler errors. Why?
740     Inkscape::SVGOStringStream os;
742     Box3D::VanishingPoint vp = *(persp->get_vanishing_point (Box3D::X));
743     os << vp[NR::X] << "," << vp[NR::Y] << ",";
744     os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
745     if (vp.is_finite()) {
746         os << "finite,";
747     } else {
748         os << "infinite,";
749     }
751     vp = *(persp->get_vanishing_point (Box3D::Y));
752     os << vp[NR::X] << "," << vp[NR::Y] << ",";
753     os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
754     if (vp.is_finite()) {
755         os << "finite,";
756     } else {
757         os << "infinite,";
758     }
760     vp = *(persp->get_vanishing_point (Box3D::Z));
761     os << vp[NR::X] << "," << vp[NR::Y] << ",";
762     os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
763     if (vp.is_finite()) {
764         os << "finite";
765     } else {
766         os << "infinite";
767     }
769     return g_strdup(os.str().c_str());
772 // auxiliary function
773 static std::pair<NR::Point, NR::Point>
774 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)
776     double cr1 = Box3D::cross_ratio (*persp->get_vanishing_point (axis), M0, M, A);
777     double cr2 = Box3D::cross_ratio (*persp->get_vanishing_point (axis), M, B, M0);
778     if (fabs (cr1 - 1) < Box3D::epsilon) {
779         // FIXME: cr == 1 is a degenerate case; how should we deal with it?
780         return std::make_pair (NR::Point (0,0), NR::Point (0,0));
781     }
782     Box3D::PerspectiveLine pl (M0, axis, persp);
783     NR::Point B_new = pl.pt_with_given_cross_ratio (M0, M, cr1 / (cr1 - 1));
784     NR::Point A_new = pl.pt_with_given_cross_ratio (M0, M, 1 - cr2);
785     return std::make_pair (A_new, B_new);
788 void sp_3dbox_recompute_Z_corners_from_new_center (SP3DBox *box, NR::Point const new_center)
790     // TODO: Clean this function up
792     Box3D::Perspective3D *persp = sp_desktop_document (inkscape_active_desktop())->get_persp_of_box (box);
793     NR::Point old_center = box->old_center;
795     Box3D::PerspectiveLine aux_line1 (old_center, Box3D::Z, persp);
796     Box3D::PerspectiveLine aux_line2 (new_center, Box3D::Y, persp);
797     NR::Point Z1 = aux_line1.meet (aux_line2);
799     NR::Point A0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner2, box->old_corner0, Box3D::Y, persp));
800     NR::Point B0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner7, box->old_corner5, Box3D::Y, persp));
801     Box3D::PerspectiveLine aux_line3 (A0, Box3D::X, persp);
802     Box3D::PerspectiveLine aux_line4 (B0, Box3D::X, persp);
804     NR::Point C0 = aux_line3.meet (aux_line1);
805     NR::Point D0 = aux_line4.meet (aux_line1);
807     std::pair<NR::Point, NR::Point> new_midpts = sp_3dbox_new_midpoints (persp, Box3D::Z, old_center, Z1, C0, D0);
808     NR::Point C1 (new_midpts.first);
809     NR::Point D1 (new_midpts.second);
810     Box3D::PerspectiveLine aux_line5 (C1, Box3D::X, persp);
811     Box3D::PerspectiveLine aux_line6 (D1, Box3D::X, persp);
813     Box3D::PerspectiveLine aux_line7 (A0, Box3D::Z, persp);
814     Box3D::PerspectiveLine aux_line8 (B0, Box3D::Z, persp);
816     NR::Point A1 = aux_line5.meet (aux_line7);
817     NR::Point B1 = aux_line6.meet (aux_line8);
819     Box3D::PerspectiveLine aux_line9  (box->old_corner2, Box3D::Z, persp);
820     Box3D::PerspectiveLine aux_line10 (box->old_corner5, Box3D::Z, persp);
822     Box3D::PerspectiveLine aux_line11 (A1, Box3D::Y, persp);
823     Box3D::PerspectiveLine aux_line12 (B1, Box3D::Y, persp);
825     NR::Point new_corner2 = aux_line9.meet (aux_line11);
826     NR::Point new_corner5 = aux_line10.meet (aux_line12);
828     Box3D::PerspectiveLine aux_line13 (A1, Box3D::X, persp);
829     NR::Point E1 = aux_line13.meet (aux_line8);
830     Box3D::PerspectiveLine aux_line14 (E1, Box3D::Y, persp);
832     NR::Point new_corner1 = aux_line10.meet (aux_line14);
834     sp_3dbox_set_shape_from_points (box, new_corner2, new_corner1, new_corner5);
837 void sp_3dbox_recompute_XY_corners_from_new_center (SP3DBox *box, NR::Point const new_center)
839     // TODO: Clean this function up
841     Box3D::Perspective3D *persp = sp_desktop_document (inkscape_active_desktop())->get_persp_of_box (box);
842     NR::Point old_center = box->old_center;
844     NR::Point A0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner2, box->old_corner0, Box3D::Y, persp));
845     NR::Point B0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner1, box->old_corner3, Box3D::Y, persp));
847     /* we first move the box along the X-axis ... */
848     Box3D::PerspectiveLine aux_line1 (old_center, Box3D::X, persp);
849     Box3D::PerspectiveLine aux_line2 (new_center, Box3D::Y, persp);
850     NR::Point Z1 = aux_line1.meet (aux_line2);
852     Box3D::PerspectiveLine ref_line (B0, Box3D::X, persp);
853     Box3D::PerspectiveLine pline2 (old_center, Box3D::Z, persp);
854     Box3D::PerspectiveLine pline3 (Z1, Box3D::Z, persp);
855     NR::Point M0 = ref_line.meet (pline2);
856     NR::Point M1 = ref_line.meet (pline3);
858     std::pair<NR::Point, NR::Point> new_midpts = sp_3dbox_new_midpoints (persp, Box3D::X, M0, M1, A0, B0);
859     NR::Point A1 (new_midpts.first);
860     NR::Point B1 (new_midpts.second);
862     /* ... and then along the Y-axis */
863     Box3D::PerspectiveLine pline4 (box->old_corner1, Box3D::X, persp);
864     Box3D::PerspectiveLine pline5 (box->old_corner3, Box3D::X, persp);
865     Box3D::PerspectiveLine aux_line3 (M1, Box3D::Y, persp);
866     NR::Point C1 = aux_line3.meet (pline4);
867     NR::Point D1 = aux_line3.meet (pline5);
869     Box3D::PerspectiveLine aux_line4 (new_center, Box3D::Z, persp);
870     NR::Point M2 = aux_line4.meet (aux_line3);
872     std::pair<NR::Point, NR::Point> other_new_midpts = sp_3dbox_new_midpoints (persp, Box3D::Y, M1, M2, C1, D1);
873     NR::Point C2 (other_new_midpts.first);
874     NR::Point D2 (other_new_midpts.second);
876     Box3D::PerspectiveLine plXC (C2, Box3D::X, persp);
877     Box3D::PerspectiveLine plXD (D2, Box3D::X, persp);
878     Box3D::PerspectiveLine plYA (A1, Box3D::Y, persp);
879     Box3D::PerspectiveLine plYB (B1, Box3D::Y, persp);
881     NR::Point new_corner2 (plXD.meet (plYA));
882     NR::Point new_corner1 (plXC.meet (plYB));
884     NR::Point tmp_corner1 (pline4.meet (plYB));
885     Box3D::PerspectiveLine pline6 (box->old_corner5, Box3D::X, persp);
886     Box3D::PerspectiveLine pline7 (tmp_corner1, Box3D::Z, persp);
887     NR::Point tmp_corner5 (pline6.meet (pline7));
889     Box3D::PerspectiveLine pline8 (tmp_corner5, Box3D::Y, persp);
890     Box3D::PerspectiveLine pline9 (new_corner1, Box3D::Z, persp);
891     NR::Point new_corner5 (pline8.meet (pline9));
893     sp_3dbox_set_shape_from_points (box, new_corner2, new_corner1, new_corner5);
896 void sp_3dbox_update_perspective_lines()
898     SPEventContext *ec = inkscape_active_event_context();
899     if (!SP_IS_3DBOX_CONTEXT (ec))
900         return;
902     SP_3DBOX_CONTEXT (ec)->_vpdrag->updateLines();
905 /*
906  * Manipulates corner1 through corner4 to contain the indices of the corners
907  * from which the perspective lines in the direction of 'axis' emerge
908  */
909 void sp_3dbox_corners_for_perspective_lines (const SP3DBox * box, Box3D::Axis axis, 
910                                              NR::Point &corner1, NR::Point &corner2, NR::Point &corner3, NR::Point &corner4)
912     // along which axis to switch when takint
913     Box3D::Axis switch_axis;
914     if (axis == Box3D::X || axis == Box3D::Y) {
915         switch_axis = (box->front_bits & axis) ? Box3D::Z : Box3D::NONE;
916     } else {
917         switch_axis = (box->front_bits & axis) ? Box3D::X : Box3D::NONE;
918     }
920     switch (axis) {
921         case Box3D::X:
922             corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
923             corner2 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
924             corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
925             corner4 = sp_3dbox_get_corner_along_edge (box, 6 ^ switch_axis, axis, Box3D::REAR);
926             break;
927         case Box3D::Y:
928             corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
929             corner2 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
930             corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
931             corner4 = sp_3dbox_get_corner_along_edge (box, 5 ^ switch_axis, axis, Box3D::REAR);
932             break;
933         case Box3D::Z:
934             corner1 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
935             corner2 = sp_3dbox_get_corner_along_edge (box, 3 ^ switch_axis, axis, Box3D::REAR);
936             corner3 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
937             corner4 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
938             break;
939         default:
940             // do nothing
941             break;
942     }            
945 /**
946  * Returns the id of the corner on the edge along 'axis' and passing through 'corner' that
947  * lies on the front/rear face in this direction.
948  */
949 guint
950 sp_3dbox_get_corner_id_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
952     guint result;
953     guint other_corner = corner ^ axis;
954     Box3D::VanishingPoint *vp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point(axis);
955     if (vp->is_finite()) {
956         result = (  NR::L2 (vp->get_pos() - box->corners[corner])
957                   < NR::L2 (vp->get_pos() - box->corners[other_corner]) ? other_corner : corner);
958     } else {
959         // clear the axis bit and switch to the appropriate corner along axis, depending on the value of front_bits
960         result = ((corner & (0xF ^ axis)) ^ (box->front_bits & axis));
961     }
963     if (rel_pos == Box3D::FRONT) {
964         return result;
965     } else {
966         return result ^ axis;
967     }
970 NR::Point
971 sp_3dbox_get_corner_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
973     return box->corners[sp_3dbox_get_corner_id_along_edge (box, corner, axis, rel_pos)];
976 guint
977 sp_3dbox_get_front_corner_id (const SP3DBox *box)
979     guint front_corner = 1; // this could in fact be any corner, but we choose the one that is normally in front
980     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::X, Box3D::FRONT);
981     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Y, Box3D::FRONT);
982     front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Z, Box3D::FRONT);
983     return front_corner;
986 // auxiliary functions
987 static void
988 sp_3dbox_update_corner_with_value_from_svg (SPObject *object, guint corner_id, const gchar *value)
990     if (value == NULL) return;
991     SP3DBox *box = SP_3DBOX(object);
993     std::pair<gdouble, gdouble> coord_pair = sp_3dbox_get_coord_pair_from_string (value);
994     box->corners[corner_id] = NR::Point (coord_pair.first, coord_pair.second);
995     sp_3dbox_recompute_corners (box, box->corners[2], box->corners[1], box->corners[5]);
996     object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
999 static void
1000 sp_3dbox_update_perspective (Box3D::Perspective3D *persp, const gchar *value)
1002     // WARNING! This function changes the perspective associated to 'box'. Since there may be
1003     // many other boxes linked to the same perspective, their perspective is also changed.
1004     // If this behaviour is not desired in all cases, we need a different function.
1005     if (value == NULL) return;
1007     gchar **vps = g_strsplit( value, ",", 0);
1008     for (int i = 0; i < 15; ++i) {
1009         if (vps[i] == NULL) {
1010             g_warning ("Malformed svg attribute 'perspective'\n");
1011             return;
1012         }
1013     }
1015     persp->set_vanishing_point (Box3D::X, g_ascii_strtod (vps[0], NULL), g_ascii_strtod (vps[1], NULL),
1016                                           g_ascii_strtod (vps[2], NULL), g_ascii_strtod (vps[3], NULL),
1017                                           strcmp (vps[4], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
1018     persp->set_vanishing_point (Box3D::Y, g_ascii_strtod (vps[5], NULL), g_ascii_strtod (vps[6], NULL),
1019                                           g_ascii_strtod (vps[7], NULL), g_ascii_strtod (vps[8], NULL),
1020                                           strcmp (vps[9], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
1021     persp->set_vanishing_point (Box3D::Z, g_ascii_strtod (vps[10], NULL), g_ascii_strtod (vps[11], NULL),
1022                                           g_ascii_strtod (vps[12], NULL), g_ascii_strtod (vps[13], NULL),
1023                                           strcmp (vps[14], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
1025     // update the other boxes linked to the same perspective
1026     persp->reshape_boxes (Box3D::XYZ);
1029 /*
1030   Local Variables:
1031   mode:c++
1032   c-file-style:"stroustrup"
1033   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1034   indent-tabs-mode:nil
1035   fill-column:99
1036   End:
1037 */
1038 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :