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;
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);
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 }
186 static void
187 sp_3dbox_release (SPObject *object)
188 {
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 }
203 }
205 static void sp_3dbox_set(SPObject *object, unsigned int key, const gchar *value)
206 {
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 }
229 }
231 static void
232 sp_3dbox_update(SPObject *object, SPCtx *ctx, guint flags)
233 {
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);
242 }
246 static Inkscape::XML::Node *sp_3dbox_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
247 {
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;
302 }
304 static gchar *
305 sp_3dbox_description(SPItem *item)
306 {
307 g_return_val_if_fail(SP_IS_3DBOX(item), NULL);
309 return g_strdup(_("<b>3D Box</b>"));
310 }
312 void sp_3dbox_set_ratios (SP3DBox *box, Box3D::Axis axes)
313 {
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 }
331 }
333 void
334 sp_3dbox_switch_front_face (SP3DBox *box, Box3D::Axis axis)
335 {
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 }
339 }
342 void
343 sp_3dbox_position_set (SP3DBoxContext &bc)
344 {
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 ***/
361 }
363 static void
364 sp_3dbox_set_shape_from_points (SP3DBox *box, NR::Point const &cornerA, NR::Point const &cornerB, NR::Point const &cornerC)
365 {
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);
384 }
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)
392 {
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 }
402 }
405 void sp_3dbox_recompute_corners (SP3DBox *box, NR::Point const A, NR::Point const B, NR::Point const C)
406 {
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);
410 }
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;
420 }
422 static gdouble
423 sp_3dbox_corner_angle_to_VP (SP3DBox *box, Box3D::Axis axis, guint extreme_corner)
424 {
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]);
435 }
438 bool sp_3dbox_recompute_z_orders (SP3DBox *box)
439 {
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;
498 }
500 void sp_3dbox_set_z_orders (SP3DBox *box)
501 {
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 }
513 }
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 }
520 }
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 }
561 }
563 void
564 sp_3dbox_move_corner_in_XY_plane (SP3DBox *box, guint id, NR::Point pt, Box3D::Axis axes)
565 {
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);
599 }
601 void
602 sp_3dbox_move_corner_in_Z_direction (SP3DBox *box, guint id, NR::Point pt, bool constrained)
603 {
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);
622 }
624 static void
625 sp_3dbox_reshape_edge_after_VP_toggling (SP3DBox *box, const guint corner, const Box3D::Axis axis, Box3D::Perspective3D *persp)
626 {
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;
647 }
649 void
650 sp_3dbox_reshape_after_VP_toggling (SP3DBox *box, Box3D::Axis axis)
651 {
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);
659 }
661 NR::Maybe<NR::Point>
662 sp_3dbox_get_center (SP3DBox *box)
663 {
664 return sp_3dbox_get_midpoint_between_corners (box, 0, 7);
665 }
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)
669 {
670 Box3D::PerspectiveLine pl (D, axis, persp);
671 return pl.pt_with_given_cross_ratio (C, D, -1.0);
672 }
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)
678 {
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 }
701 }
703 static gchar *
704 sp_3dbox_get_corner_coords_string (SP3DBox *box, guint id)
705 {
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());
710 }
712 static std::pair<gdouble, gdouble>
713 sp_3dbox_get_coord_pair_from_string (const gchar *coords)
714 {
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);
729 }
731 static gchar *
732 sp_3dbox_get_perspective_string (SP3DBox *box)
733 {
735 return sp_3dbox_get_svg_descr_of_persp (SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box));
736 }
738 gchar *
739 sp_3dbox_get_svg_descr_of_persp (Box3D::Perspective3D *persp)
740 {
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());
772 }
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)
777 {
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);
788 }
790 void sp_3dbox_recompute_XY_corners_from_new_center (SP3DBox *box, NR::Point const new_center)
791 {
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);
848 }
850 void sp_3dbox_update_perspective_lines()
851 {
852 SPEventContext *ec = inkscape_active_event_context();
853 if (!SP_IS_3DBOX_CONTEXT (ec))
854 return;
856 SP_3DBOX_CONTEXT (ec)->_vpdrag->updateLines();
857 }
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)
865 {
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 }
894 }
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)
902 {
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 }
919 }
921 NR::Point
922 sp_3dbox_get_corner_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
923 {
924 return box->corners[sp_3dbox_get_corner_id_along_edge (box, corner, axis, rel_pos)];
925 }
927 guint
928 sp_3dbox_get_front_corner_id (const SP3DBox *box)
929 {
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;
935 }
937 // auxiliary functions
938 static void
939 sp_3dbox_update_corner_with_value_from_svg (SPObject *object, guint corner_id, const gchar *value)
940 {
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);
948 }
950 static void
951 sp_3dbox_update_perspective (Box3D::Perspective3D *persp, const gchar *value)
952 {
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);
978 }
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 :