dcbe5b531ab3abc057f43f6afa23b1d110498ef0
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 // TODO: Determine the front corner depending on the distance from VPs and/or the user presets
443 guint front_corner = sp_3dbox_get_front_corner_id (box);
445 gdouble dir_1x = sp_3dbox_corner_angle_to_VP (box, Box3D::X, front_corner);
446 gdouble dir_3x = sp_3dbox_corner_angle_to_VP (box, Box3D::X, front_corner ^ Box3D::Y);
448 gdouble dir_1y = sp_3dbox_corner_angle_to_VP (box, Box3D::Y, front_corner);
449 //gdouble dir_0y = sp_3dbox_corner_angle_to_VP (box, Box3D::Y, front_corner ^ Box3D::X);
451 gdouble dir_1z = sp_3dbox_corner_angle_to_VP (box, Box3D::Z, front_corner);
452 gdouble dir_3z = sp_3dbox_corner_angle_to_VP (box, Box3D::Z, front_corner ^ Box3D::Y);
454 // Still not perfect, but only fails in some rather degenerate cases.
455 // I suspect that there is a more elegant model, though. :)
456 new_z_orders[0] = Box3D::face_containing_corner (Box3D::XY, front_corner);
457 if (normalized_angle (dir_1y - dir_1z) > 0) {
458 new_z_orders[1] = Box3D::face_containing_corner (Box3D::YZ, front_corner);
459 if (normalized_angle (dir_1x - dir_1z) > 0) {
460 new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner ^ Box3D::Y);
461 } else {
462 new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
463 }
464 } else {
465 if (normalized_angle (dir_3x - dir_3z) > 0) {
466 new_z_orders[1] = Box3D::face_containing_corner (Box3D::XZ, front_corner ^ Box3D::Y);
467 new_z_orders[2] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
468 } else {
469 if (normalized_angle (dir_1x - dir_1z) > 0) {
470 new_z_orders[1] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
471 new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
472 } else {
473 new_z_orders[1] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
474 new_z_orders[2] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
475 }
476 }
477 }
479 new_z_orders[3] = Box3D::opposite_face (new_z_orders[2]);
480 new_z_orders[4] = Box3D::opposite_face (new_z_orders[1]);
481 new_z_orders[5] = Box3D::opposite_face (new_z_orders[0]);
483 /* We only need to look for changes among the topmost three faces because the order
484 of the other ones is just inverted. */
485 if ((box->z_orders[0] != new_z_orders[0]) ||
486 (box->z_orders[1] != new_z_orders[1]) ||
487 (box->z_orders[2] != new_z_orders[2]))
488 {
489 for (int i = 0; i < 6; ++i) {
490 box->z_orders[i] = new_z_orders[i];
491 }
492 return true;
493 }
495 return false;
496 }
498 void sp_3dbox_set_z_orders (SP3DBox *box)
499 {
500 // For efficiency reasons, we only set the new z-orders if something really changed
501 if (sp_3dbox_recompute_z_orders (box)) {
502 box->faces[box->z_orders[0]]->lower_to_bottom ();
503 box->faces[box->z_orders[1]]->lower_to_bottom ();
504 box->faces[box->z_orders[2]]->lower_to_bottom ();
505 box->faces[box->z_orders[3]]->lower_to_bottom ();
506 box->faces[box->z_orders[4]]->lower_to_bottom ();
507 box->faces[box->z_orders[5]]->lower_to_bottom ();
508 }
509 }
511 void
512 sp_3dbox_update_curves (SP3DBox *box) {
513 for (int i = 0; i < 6; ++i) {
514 if (box->faces[i]) box->faces[i]->set_curve();
515 }
516 }
518 /**
519 * In some situations (e.g., after cloning boxes, undo & redo, or reading boxes from a file) there are
520 * paths already present in the document which correspond to the faces of newly created boxes, but their
521 * 'path' members don't link to them yet. The following function corrects this if necessary.
522 */
523 void
524 sp_3dbox_link_to_existing_paths (SP3DBox *box, Inkscape::XML::Node *repr) {
525 // TODO: We should probably destroy the existing paths and recreate them because we don't know
526 // precisely which path corresponds to which face. Does this make a difference?
527 // In sp_3dbox_write we write the correct paths anyway, don't we? But we could get into
528 // trouble at a later stage when we only write single faces for degenerate boxes.
530 SPDocument *document = SP_OBJECT_DOCUMENT(box);
531 guint face_id = 0;
533 for (Inkscape::XML::Node *i = sp_repr_children(repr); i != NULL; i = sp_repr_next(i)) {
534 if (face_id > 5) {
535 g_warning ("SVG representation of 3D boxes must contain 6 paths or less.\n");
536 break;
537 }
539 SPObject *face_object = document->getObjectByRepr((Inkscape::XML::Node *) i);
540 if (!SP_IS_PATH(face_object)) {
541 g_warning ("SVG representation of 3D boxes should only contain paths.\n");
542 continue;
543 }
544 // TODO: Currently we don't check whether all paths are being linked to different faces.
545 // This is no problem with valid SVG files. It may lead to crashes, however,
546 // in case a file is corrupt (e.g., two or more faces have identical descriptions).
547 gint id = Box3DFace::descr_to_id (i->attribute ("inkscape:box3dface"));
548 box->faces[id]->hook_path_to_3dbox(SP_PATH(face_object));
549 ++face_id;
550 }
551 if (face_id < 6) {
552 //g_warning ("SVG representation of 3D boxes should contain exactly 6 paths (degenerate boxes are not yet supported).\n");
553 // TODO: Check whether it is safe to add the remaining paths to the box and do so in case it is.
554 // (But we also land here for newly created boxes where we shouldn't add any paths because
555 // This is done in sp_3dbox_write later on.
556 }
557 }
559 void
560 sp_3dbox_move_corner_in_XY_plane (SP3DBox *box, guint id, NR::Point pt, Box3D::Axis axes)
561 {
562 Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
564 NR::Point A (box->corners[id ^ Box3D::XY]);
565 if (Box3D::is_single_axis_direction (axes)) {
566 pt = Box3D::PerspectiveLine (box->corners[id], axes, persp).closest_to(pt);
567 }
569 /* set the 'front' corners */
570 box->corners[id] = pt;
572 Box3D::PerspectiveLine pl_one (A, Box3D::Y, persp);
573 Box3D::PerspectiveLine pl_two (pt, Box3D::X, persp);
574 box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
576 pl_one = Box3D::PerspectiveLine (A, Box3D::X, persp);
577 pl_two = Box3D::PerspectiveLine (pt, Box3D::Y, persp);
578 box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
580 /* set the 'rear' corners */
581 NR::Point B (box->corners[id ^ Box3D::XYZ]);
583 pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Z, persp);
584 pl_two = Box3D::PerspectiveLine (B, Box3D::Y, persp);
585 box->corners[id ^ Box3D::XZ] = pl_one.meet(pl_two);
587 pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XZ], Box3D::X, persp);
588 pl_two = Box3D::PerspectiveLine (pt, Box3D::Z, persp);
589 box->corners[id ^ Box3D::Z] = pl_one.meet(pl_two);
591 pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::Z], Box3D::Y, persp);
592 pl_two = Box3D::PerspectiveLine (B, Box3D::X, persp);
593 box->corners[id ^ Box3D::YZ] = pl_one.meet(pl_two);
595 }
597 void
598 sp_3dbox_move_corner_in_Z_direction (SP3DBox *box, guint id, NR::Point pt, bool constrained)
599 {
600 if (!constrained) sp_3dbox_move_corner_in_XY_plane (box, id, pt, Box3D::XY);
602 Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
604 /* set the four corners of the face containing corners[id] */
605 box->corners[id] = Box3D::PerspectiveLine (box->corners[id], Box3D::Z, persp).closest_to(pt);
607 Box3D::PerspectiveLine pl_one (box->corners[id], Box3D::X, persp);
608 Box3D::PerspectiveLine pl_two (box->corners[id ^ Box3D::XZ], Box3D::Z, persp);
609 box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
611 pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Y, persp);
612 pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XYZ], Box3D::Z, persp);
613 box->corners[id ^ Box3D::XY] = pl_one.meet(pl_two);
615 pl_one = Box3D::PerspectiveLine (box->corners[id], Box3D::Y, persp);
616 pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::YZ], Box3D::Z, persp);
617 box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
618 }
620 static void
621 sp_3dbox_reshape_edge_after_VP_toggling (SP3DBox *box, const guint corner, const Box3D::Axis axis, Box3D::Perspective3D *persp)
622 {
623 /* Hmm, perhaps we should simply use one of the corners as the pivot point.
624 But this way we minimize the amount of reshaping.
625 On second thought, we need to find a way to ensure that all boxes sharing the same
626 perspective are updated consistently _as a group_. That is, they should also retain
627 their relative positions towards each other. */
628 NR::Maybe<NR::Point> pt = sp_3dbox_get_midpoint_between_corners (box, corner, corner ^ axis);
629 g_return_if_fail (pt);
631 Box3D::Axis axis2 = ((axis == Box3D::Y) ? Box3D::X : Box3D::Y);
633 Box3D::PerspectiveLine line1 (box->corners[corner], axis2, persp);
634 Box3D::PerspectiveLine line2 (box->corners[corner ^ axis], axis2, persp);
636 Box3D::PerspectiveLine line3 (*pt, axis, persp);
638 NR::Point new_corner1 = line1.meet (line3);
639 NR::Point new_corner2 = line2.meet (line3);
641 box->corners[corner] = new_corner1;
642 box->corners[corner ^ axis] = new_corner2;
643 }
645 void
646 sp_3dbox_reshape_after_VP_toggling (SP3DBox *box, Box3D::Axis axis)
647 {
648 Box3D::Perspective3D *persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
649 std::pair<Box3D::Axis, Box3D::Axis> dirs = Box3D::get_remaining_axes (axis);
651 sp_3dbox_reshape_edge_after_VP_toggling (box, 0, axis, persp);
652 sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first, axis, persp);
653 sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first ^ dirs.second, axis, persp);
654 sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.second, axis, persp);
655 }
657 NR::Maybe<NR::Point>
658 sp_3dbox_get_center (SP3DBox *box)
659 {
660 return sp_3dbox_get_midpoint_between_corners (box, 0, 7);
661 }
663 NR::Point
664 sp_3dbox_get_midpoint_in_axis_direction (NR::Point const &C, NR::Point const &D, Box3D::Axis axis, Box3D::Perspective3D *persp)
665 {
666 Box3D::PerspectiveLine pl (D, axis, persp);
667 return pl.pt_with_given_cross_ratio (C, D, -1.0);
668 }
670 // TODO: The following function can probably be rewritten in a much more elegant and robust way
671 // by using projective coordinates for all points and using the cross ratio.
672 NR::Maybe<NR::Point>
673 sp_3dbox_get_midpoint_between_corners (SP3DBox *box, guint id_corner1, guint id_corner2)
674 {
675 Box3D::Axis corner_axes = (Box3D::Axis) (id_corner1 ^ id_corner2);
677 // Is all this sufficiently precise also for degenerate cases?
678 if (sp_3dbox_corners_are_adjacent (id_corner1, id_corner2)) {
679 Box3D::Axis orth_dir = get_perpendicular_axis_direction (corner_axes);
681 Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2 ^ orth_dir]);
682 Box3D::Line diag2 (box->corners[id_corner1 ^ orth_dir], box->corners[id_corner2]);
683 NR::Maybe<NR::Point> adjacent_face_center = diag1.intersect(diag2);
685 if (!adjacent_face_center) return NR::Nothing();
687 Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
689 Box3D::PerspectiveLine pl (*adjacent_face_center, orth_dir, persp);
690 return pl.intersect(Box3D::PerspectiveLine(box->corners[id_corner1], corner_axes, persp));
691 } else {
692 Box3D::Axis dir = Box3D::extract_first_axis_direction (corner_axes);
693 Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2]);
694 Box3D::Line diag2 (box->corners[id_corner1 ^ dir], box->corners[id_corner2 ^ dir]);
695 return diag1.intersect(diag2);
696 }
697 }
699 static gchar *
700 sp_3dbox_get_corner_coords_string (SP3DBox *box, guint id)
701 {
702 id = id % 8;
703 Inkscape::SVGOStringStream os;
704 os << box->corners[id][NR::X] << "," << box->corners[id][NR::Y];
705 return g_strdup(os.str().c_str());
706 }
708 static std::pair<gdouble, gdouble>
709 sp_3dbox_get_coord_pair_from_string (const gchar *coords)
710 {
711 gchar **coordpair = g_strsplit( coords, ",", 0);
712 // We might as well rely on g_ascii_strtod to convert the NULL pointer to 0.0,
713 // but we include the following test anyway
714 if (coordpair[0] == NULL || coordpair[1] == NULL) {
715 g_strfreev (coordpair);
716 g_warning ("Coordinate conversion failed.\n");
717 return std::make_pair(0.0, 0.0);
718 }
720 gdouble coord1 = g_ascii_strtod(coordpair[0], NULL);
721 gdouble coord2 = g_ascii_strtod(coordpair[1], NULL);
722 g_strfreev (coordpair);
724 return std::make_pair(coord1, coord2);
725 }
727 static gchar *
728 sp_3dbox_get_perspective_string (SP3DBox *box)
729 {
731 return sp_3dbox_get_svg_descr_of_persp (SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box));
732 }
734 gchar *
735 sp_3dbox_get_svg_descr_of_persp (Box3D::Perspective3D *persp)
736 {
737 // FIXME: We should move this code to perspective3d.cpp, but this yields compiler errors. Why?
738 Inkscape::SVGOStringStream os;
740 Box3D::VanishingPoint vp = *(persp->get_vanishing_point (Box3D::X));
741 os << vp[NR::X] << "," << vp[NR::Y] << ",";
742 os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
743 if (vp.is_finite()) {
744 os << "finite,";
745 } else {
746 os << "infinite,";
747 }
749 vp = *(persp->get_vanishing_point (Box3D::Y));
750 os << vp[NR::X] << "," << vp[NR::Y] << ",";
751 os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
752 if (vp.is_finite()) {
753 os << "finite,";
754 } else {
755 os << "infinite,";
756 }
758 vp = *(persp->get_vanishing_point (Box3D::Z));
759 os << vp[NR::X] << "," << vp[NR::Y] << ",";
760 os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
761 if (vp.is_finite()) {
762 os << "finite";
763 } else {
764 os << "infinite";
765 }
767 return g_strdup(os.str().c_str());
768 }
770 // auxiliary function
771 static std::pair<NR::Point, NR::Point>
772 sp_3dbox_new_midpoints (Box3D::Perspective3D *persp, Box3D::Axis axis, NR::Point const &M0, NR::Point const &M, NR::Point const &A, NR::Point const &B)
773 {
774 double cr1 = Box3D::cross_ratio (*persp->get_vanishing_point (axis), M0, M, A);
775 double cr2 = Box3D::cross_ratio (*persp->get_vanishing_point (axis), M, B, M0);
776 if (fabs (cr1 - 1) < Box3D::epsilon) {
777 // FIXME: cr == 1 is a degenerate case; how should we deal with it?
778 return std::make_pair (NR::Point (0,0), NR::Point (0,0));
779 }
780 Box3D::PerspectiveLine pl (M0, axis, persp);
781 NR::Point B_new = pl.pt_with_given_cross_ratio (M0, M, cr1 / (cr1 - 1));
782 NR::Point A_new = pl.pt_with_given_cross_ratio (M0, M, 1 - cr2);
783 return std::make_pair (A_new, B_new);
784 }
786 void sp_3dbox_recompute_XY_corners_from_new_center (SP3DBox *box, NR::Point const new_center)
787 {
788 // TODO: Clean this function up
790 Box3D::Perspective3D *persp = sp_desktop_document (inkscape_active_desktop())->get_persp_of_box (box);
791 NR::Point old_center = box->old_center;
793 NR::Point A0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner2, box->old_corner0, Box3D::Y, persp));
794 NR::Point B0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner1, box->old_corner3, Box3D::Y, persp));
796 /* we first move the box along the X-axis ... */
797 Box3D::PerspectiveLine aux_line1 (old_center, Box3D::X, persp);
798 Box3D::PerspectiveLine aux_line2 (new_center, Box3D::Y, persp);
799 NR::Point Z1 = aux_line1.meet (aux_line2);
801 Box3D::PerspectiveLine ref_line (B0, Box3D::X, persp);
802 Box3D::PerspectiveLine pline2 (old_center, Box3D::Z, persp);
803 Box3D::PerspectiveLine pline3 (Z1, Box3D::Z, persp);
804 NR::Point M0 = ref_line.meet (pline2);
805 NR::Point M1 = ref_line.meet (pline3);
807 std::pair<NR::Point, NR::Point> new_midpts = sp_3dbox_new_midpoints (persp, Box3D::X, M0, M1, A0, B0);
808 NR::Point A1 (new_midpts.first);
809 NR::Point B1 (new_midpts.second);
811 /* ... and then along the Y-axis */
812 Box3D::PerspectiveLine pline4 (box->old_corner1, Box3D::X, persp);
813 Box3D::PerspectiveLine pline5 (box->old_corner3, Box3D::X, persp);
814 Box3D::PerspectiveLine aux_line3 (M1, Box3D::Y, persp);
815 NR::Point C1 = aux_line3.meet (pline4);
816 NR::Point D1 = aux_line3.meet (pline5);
818 Box3D::PerspectiveLine aux_line4 (new_center, Box3D::Z, persp);
819 NR::Point M2 = aux_line4.meet (aux_line3);
821 std::pair<NR::Point, NR::Point> other_new_midpts = sp_3dbox_new_midpoints (persp, Box3D::Y, M1, M2, C1, D1);
822 NR::Point C2 (other_new_midpts.first);
823 NR::Point D2 (other_new_midpts.second);
825 Box3D::PerspectiveLine plXC (C2, Box3D::X, persp);
826 Box3D::PerspectiveLine plXD (D2, Box3D::X, persp);
827 Box3D::PerspectiveLine plYA (A1, Box3D::Y, persp);
828 Box3D::PerspectiveLine plYB (B1, Box3D::Y, persp);
830 NR::Point new_corner2 (plXD.meet (plYA));
831 NR::Point new_corner1 (plXC.meet (plYB));
833 NR::Point tmp_corner1 (pline4.meet (plYB));
834 Box3D::PerspectiveLine pline6 (box->old_corner5, Box3D::X, persp);
835 Box3D::PerspectiveLine pline7 (tmp_corner1, Box3D::Z, persp);
836 NR::Point tmp_corner5 (pline6.meet (pline7));
838 Box3D::PerspectiveLine pline8 (tmp_corner5, Box3D::Y, persp);
839 Box3D::PerspectiveLine pline9 (new_corner1, Box3D::Z, persp);
840 NR::Point new_corner5 (pline8.meet (pline9));
842 sp_3dbox_set_shape_from_points (box, new_corner2, new_corner1, new_corner5);
843 }
845 void sp_3dbox_update_perspective_lines()
846 {
847 SPEventContext *ec = inkscape_active_event_context();
848 if (!SP_IS_3DBOX_CONTEXT (ec))
849 return;
851 SP_3DBOX_CONTEXT (ec)->_vpdrag->updateLines();
852 }
854 /*
855 * Manipulates corner1 through corner4 to contain the indices of the corners
856 * from which the perspective lines in the direction of 'axis' emerge
857 */
858 void sp_3dbox_corners_for_perspective_lines (const SP3DBox * box, Box3D::Axis axis,
859 NR::Point &corner1, NR::Point &corner2, NR::Point &corner3, NR::Point &corner4)
860 {
861 // along which axis to switch when takint
862 Box3D::Axis switch_axis;
863 if (axis == Box3D::X || axis == Box3D::Y) {
864 switch_axis = (box->front_bits & axis) ? Box3D::Z : Box3D::NONE;
865 } else {
866 switch_axis = (box->front_bits & axis) ? Box3D::X : Box3D::NONE;
867 }
869 switch (axis) {
870 case Box3D::X:
871 corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
872 corner2 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
873 corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
874 corner4 = sp_3dbox_get_corner_along_edge (box, 6 ^ switch_axis, axis, Box3D::REAR);
875 break;
876 case Box3D::Y:
877 corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
878 corner2 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
879 corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
880 corner4 = sp_3dbox_get_corner_along_edge (box, 5 ^ switch_axis, axis, Box3D::REAR);
881 break;
882 case Box3D::Z:
883 corner1 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
884 corner2 = sp_3dbox_get_corner_along_edge (box, 3 ^ switch_axis, axis, Box3D::REAR);
885 corner3 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
886 corner4 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
887 break;
888 default:
889 // do nothing
890 break;
891 }
892 }
894 /**
895 * Returns the id of the corner on the edge along 'axis' and passing through 'corner' that
896 * lies on the front/rear face in this direction.
897 */
898 guint
899 sp_3dbox_get_corner_id_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
900 {
901 guint result;
902 guint other_corner = corner ^ axis;
903 Box3D::VanishingPoint *vp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point(axis);
904 if (vp->is_finite()) {
905 result = ( NR::L2 (vp->get_pos() - box->corners[corner])
906 < NR::L2 (vp->get_pos() - box->corners[other_corner]) ? other_corner : corner);
907 } else {
908 // clear the axis bit and switch to the appropriate corner along axis, depending on the value of front_bits
909 result = ((corner & (0xF ^ axis)) ^ (box->front_bits & axis));
910 }
912 if (rel_pos == Box3D::FRONT) {
913 return result;
914 } else {
915 return result ^ axis;
916 }
917 }
919 NR::Point
920 sp_3dbox_get_corner_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
921 {
922 return box->corners[sp_3dbox_get_corner_id_along_edge (box, corner, axis, rel_pos)];
923 }
925 guint
926 sp_3dbox_get_front_corner_id (const SP3DBox *box)
927 {
928 guint front_corner = 1; // this could in fact be any corner, but we choose the one that is normally in front
929 front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::X, Box3D::FRONT);
930 front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Y, Box3D::FRONT);
931 front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Z, Box3D::FRONT);
932 return front_corner;
933 }
935 // auxiliary functions
936 static void
937 sp_3dbox_update_corner_with_value_from_svg (SPObject *object, guint corner_id, const gchar *value)
938 {
939 if (value == NULL) return;
940 SP3DBox *box = SP_3DBOX(object);
942 std::pair<gdouble, gdouble> coord_pair = sp_3dbox_get_coord_pair_from_string (value);
943 box->corners[corner_id] = NR::Point (coord_pair.first, coord_pair.second);
944 sp_3dbox_recompute_corners (box, box->corners[2], box->corners[1], box->corners[5]);
945 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
946 }
948 static void
949 sp_3dbox_update_perspective (Box3D::Perspective3D *persp, const gchar *value)
950 {
951 // WARNING! This function changes the perspective associated to 'box'. Since there may be
952 // many other boxes linked to the same perspective, their perspective is also changed.
953 // If this behaviour is not desired in all cases, we need a different function.
954 if (value == NULL) return;
956 gchar **vps = g_strsplit( value, ",", 0);
957 for (int i = 0; i < 15; ++i) {
958 if (vps[i] == NULL) {
959 g_warning ("Malformed svg attribute 'perspective'\n");
960 return;
961 }
962 }
964 persp->set_vanishing_point (Box3D::X, g_ascii_strtod (vps[0], NULL), g_ascii_strtod (vps[1], NULL),
965 g_ascii_strtod (vps[2], NULL), g_ascii_strtod (vps[3], NULL),
966 strcmp (vps[4], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
967 persp->set_vanishing_point (Box3D::Y, g_ascii_strtod (vps[5], NULL), g_ascii_strtod (vps[6], NULL),
968 g_ascii_strtod (vps[7], NULL), g_ascii_strtod (vps[8], NULL),
969 strcmp (vps[9], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
970 persp->set_vanishing_point (Box3D::Z, g_ascii_strtod (vps[10], NULL), g_ascii_strtod (vps[11], NULL),
971 g_ascii_strtod (vps[12], NULL), g_ascii_strtod (vps[13], NULL),
972 strcmp (vps[14], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
974 // update the other boxes linked to the same perspective
975 persp->reshape_boxes (Box3D::XYZ);
976 }
978 /*
979 Local Variables:
980 mode:c++
981 c-file-style:"stroustrup"
982 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
983 indent-tabs-mode:nil
984 fill-column:99
985 End:
986 */
987 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :