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 box->old_corner7 = box->corners[7];
185 }
187 static void
188 sp_3dbox_release (SPObject *object)
189 {
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 }
204 }
206 static void sp_3dbox_set(SPObject *object, unsigned int key, const gchar *value)
207 {
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 }
230 }
232 static void
233 sp_3dbox_update(SPObject *object, SPCtx *ctx, guint flags)
234 {
235 if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
236 SP3DBox *box = SP_3DBOX(object);
237 Inkscape::XML::Node *repr = SP_OBJECT_REPR(object);
238 sp_3dbox_link_to_existing_paths (box, repr);
239 SPEventContext *ec = inkscape_active_event_context();
240 if (SP_IS_3DBOX_CONTEXT (ec)) {
241 SP_3DBOX_CONTEXT (ec)->_vpdrag->updateDraggers();
242 // FIXME: Should we update the corners here, too? Maybe this is the reason why the handles
243 // are off after an undo/redo! On the other hand, if we do so we get warnings about
244 // updates occuring while other updats are in progress ...
245 }
246 }
248 /* Invoke parent method */
249 if (((SPObjectClass *) (parent_class))->update)
250 ((SPObjectClass *) (parent_class))->update(object, ctx, flags);
251 }
253 static Inkscape::XML::Node *sp_3dbox_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
254 {
255 SP3DBox *box = SP_3DBOX(object);
256 // FIXME: How to handle other contexts???
257 // FIXME: Is tools_isactive(..) more recommended to check for the current context/tool?
258 if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
259 return repr;
261 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
262 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
263 repr = xml_doc->createElement("svg:g");
264 repr->setAttribute("sodipodi:type", "inkscape:3dbox");
265 /* Hook paths to the faces of the box */
266 for (int i = 0; i < 6; ++i) {
267 box->faces[i]->hook_path_to_3dbox();
268 }
269 }
271 for (int i = 0; i < 6; ++i) {
272 box->faces[i]->set_path_repr();
273 }
275 if (flags & SP_OBJECT_WRITE_EXT) {
276 gchar *str;
277 str = sp_3dbox_get_corner_coords_string (box, 2);
278 repr->setAttribute("inkscape:box3dcornerA", str);
280 str = sp_3dbox_get_corner_coords_string (box, 1);
281 repr->setAttribute("inkscape:box3dcornerB", str);
283 str = sp_3dbox_get_corner_coords_string (box, 5);
284 repr->setAttribute("inkscape:box3dcornerC", str);
286 str = sp_3dbox_get_perspective_string (box);
287 repr->setAttribute("inkscape:perspective", str);
288 sp_3dbox_set_ratios (box);
290 g_free ((void *) str);
292 /* store center and construction-corners for later use during center-dragging */
293 NR::Maybe<NR::Point> cen = sp_3dbox_get_center (box);
294 if (cen) {
295 box->old_center = *cen;
296 }
297 box->old_corner2 = box->corners[2];
298 box->old_corner1 = box->corners[1];
299 box->old_corner0 = box->corners[0];
300 box->old_corner3 = box->corners[3];
301 box->old_corner5 = box->corners[5];
302 box->old_corner7 = box->corners[7];
303 }
305 if (((SPObjectClass *) (parent_class))->write) {
306 ((SPObjectClass *) (parent_class))->write(object, repr, flags);
307 }
309 return repr;
310 }
312 static gchar *
313 sp_3dbox_description(SPItem *item)
314 {
315 g_return_val_if_fail(SP_IS_3DBOX(item), NULL);
317 return g_strdup(_("<b>3D Box</b>"));
318 }
320 void sp_3dbox_set_ratios (SP3DBox *box, Box3D::Axis axes)
321 {
322 Box3D::Perspective3D *persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
323 NR::Point pt;
325 if (axes & Box3D::X) {
326 pt = persp->get_vanishing_point (Box3D::X)->get_pos();
327 box->ratio_x = NR::L2 (pt - box->corners[2]) / NR::L2 (pt - box->corners[3]);
328 }
330 if (axes & Box3D::Y) {
331 pt = persp->get_vanishing_point (Box3D::Y)->get_pos();
332 box->ratio_y = NR::L2 (pt - box->corners[2]) / NR::L2 (pt - box->corners[0]);
333 }
335 if (axes & Box3D::Z) {
336 pt = persp->get_vanishing_point (Box3D::Z)->get_pos();
337 box->ratio_z = NR::L2 (pt - box->corners[4]) / NR::L2 (pt - box->corners[0]);
338 }
339 }
341 void
342 sp_3dbox_switch_front_face (SP3DBox *box, Box3D::Axis axis)
343 {
344 if (SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point (axis)->is_finite()) {
345 box->front_bits = box->front_bits ^ axis;
346 }
347 }
350 void
351 sp_3dbox_position_set (SP3DBoxContext &bc)
352 {
353 SP3DBox *box3d = SP_3DBOX(bc.item);
355 sp_3dbox_set_shape(box3d);
357 // FIXME: Why does the following call not automatically update the children
358 // of box3d (which is an SPGroup, which should do this)?
359 //SP_OBJECT(box3d)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
361 /**
362 SP_OBJECT(box3d->path_face1)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
363 SP_OBJECT(box3d->path_face2)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
364 SP_OBJECT(box3d->path_face3)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
365 SP_OBJECT(box3d->path_face4)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
366 SP_OBJECT(box3d->path_face5)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
367 SP_OBJECT(box3d->path_face6)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
368 ***/
369 }
371 static void
372 sp_3dbox_set_shape_from_points (SP3DBox *box, NR::Point const &cornerA, NR::Point const &cornerB, NR::Point const &cornerC)
373 {
374 sp_3dbox_recompute_corners (box, cornerA, cornerB, cornerC);
376 // FIXME: How to handle other contexts???
377 // FIXME: Is tools_isactive(..) more recommended to check for the current context/tool?
378 if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
379 return;
380 SP3DBoxContext *bc = SP_3DBOX_CONTEXT(inkscape_active_event_context());
382 if (bc->extruded) {
383 box->faces[0]->set_corners (box->corners[0], box->corners[4], box->corners[6], box->corners[2]);
384 box->faces[1]->set_corners (box->corners[1], box->corners[5], box->corners[7], box->corners[3]);
385 box->faces[2]->set_corners (box->corners[0], box->corners[1], box->corners[5], box->corners[4]);
386 box->faces[3]->set_corners (box->corners[2], box->corners[3], box->corners[7], box->corners[6]);
387 box->faces[5]->set_corners (box->corners[4], box->corners[5], box->corners[7], box->corners[6]);
388 }
389 box->faces[4]->set_corners (box->corners[0], box->corners[1], box->corners[3], box->corners[2]);
391 sp_3dbox_update_curves (box);
392 }
394 void
395 // FIXME: Note that this is _not_ the virtual set_shape() method inherited from SPShape,
396 // since SP3DBox is inherited from SPGroup. The following method is "artificially"
397 // called from sp_3dbox_update().
398 //sp_3dbox_set_shape(SPShape *shape)
399 sp_3dbox_set_shape(SP3DBox *box, bool use_previous_corners)
400 {
401 if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
402 return;
403 SP3DBoxContext *bc = SP_3DBOX_CONTEXT(inkscape_active_event_context());
405 if (!use_previous_corners) {
406 sp_3dbox_set_shape_from_points (box, bc->drag_origin, bc->drag_ptB, bc->drag_ptC);
407 } else {
408 sp_3dbox_set_shape_from_points (box, box->corners[2], box->corners[1], box->corners[5]);
409 }
410 }
413 void sp_3dbox_recompute_corners (SP3DBox *box, NR::Point const A, NR::Point const B, NR::Point const C)
414 {
415 sp_3dbox_move_corner_in_XY_plane (box, 2, A);
416 sp_3dbox_move_corner_in_XY_plane (box, 1, B);
417 sp_3dbox_move_corner_in_Z_direction (box, 5, C);
418 }
420 inline static double
421 normalized_angle (double angle) {
422 if (angle < -M_PI) {
423 return angle + 2*M_PI;
424 } else if (angle > M_PI) {
425 return angle - 2*M_PI;
426 }
427 return angle;
428 }
430 static gdouble
431 sp_3dbox_corner_angle_to_VP (SP3DBox *box, Box3D::Axis axis, guint extreme_corner)
432 {
433 Box3D::VanishingPoint *vp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point (axis);
434 NR::Point dir;
436 if (vp->is_finite()) {
437 dir = NR::unit_vector (vp->get_pos() - box->corners[extreme_corner]);
438 } else {
439 dir = NR::unit_vector (vp->v_dir);
440 }
442 return atan2 (dir[NR::Y], dir[NR::X]);
443 }
446 bool sp_3dbox_recompute_z_orders (SP3DBox *box)
447 {
448 gint new_z_orders[6];
450 // TODO: Determine the front corner depending on the distance from VPs and/or the user presets
451 guint front_corner = sp_3dbox_get_front_corner_id (box);
453 gdouble dir_1x = sp_3dbox_corner_angle_to_VP (box, Box3D::X, front_corner);
454 gdouble dir_3x = sp_3dbox_corner_angle_to_VP (box, Box3D::X, front_corner ^ Box3D::Y);
456 gdouble dir_1y = sp_3dbox_corner_angle_to_VP (box, Box3D::Y, front_corner);
457 //gdouble dir_0y = sp_3dbox_corner_angle_to_VP (box, Box3D::Y, front_corner ^ Box3D::X);
459 gdouble dir_1z = sp_3dbox_corner_angle_to_VP (box, Box3D::Z, front_corner);
460 gdouble dir_3z = sp_3dbox_corner_angle_to_VP (box, Box3D::Z, front_corner ^ Box3D::Y);
462 // Still not perfect, but only fails in some rather degenerate cases.
463 // I suspect that there is a more elegant model, though. :)
464 new_z_orders[0] = Box3D::face_containing_corner (Box3D::XY, front_corner);
465 if (normalized_angle (dir_1y - dir_1z) > 0) {
466 new_z_orders[1] = Box3D::face_containing_corner (Box3D::YZ, front_corner);
467 if (normalized_angle (dir_1x - dir_1z) > 0) {
468 new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner ^ Box3D::Y);
469 } else {
470 new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
471 }
472 } else {
473 if (normalized_angle (dir_3x - dir_3z) > 0) {
474 new_z_orders[1] = Box3D::face_containing_corner (Box3D::XZ, front_corner ^ Box3D::Y);
475 new_z_orders[2] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
476 } else {
477 if (normalized_angle (dir_1x - dir_1z) > 0) {
478 new_z_orders[1] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
479 new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
480 } else {
481 new_z_orders[1] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
482 new_z_orders[2] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
483 }
484 }
485 }
487 new_z_orders[3] = Box3D::opposite_face (new_z_orders[2]);
488 new_z_orders[4] = Box3D::opposite_face (new_z_orders[1]);
489 new_z_orders[5] = Box3D::opposite_face (new_z_orders[0]);
491 /* We only need to look for changes among the topmost three faces because the order
492 of the other ones is just inverted. */
493 if ((box->z_orders[0] != new_z_orders[0]) ||
494 (box->z_orders[1] != new_z_orders[1]) ||
495 (box->z_orders[2] != new_z_orders[2]))
496 {
497 for (int i = 0; i < 6; ++i) {
498 box->z_orders[i] = new_z_orders[i];
499 }
500 return true;
501 }
503 return false;
504 }
506 // convenience
507 static bool sp_3dbox_is_subset_or_superset (std::vector<gint> const &list1, std::vector<gint> const &list2)
508 {
509 return (std::includes (list1.begin(), list1.end(), list2.begin(), list2.end()) ||
510 std::includes (list2.begin(), list2.end(), list1.begin(), list1.end()));
511 }
513 static bool sp_3dbox_differ_by_opposite_faces (std::vector<gint> const &list1, std::vector<gint> const &list2)
514 {
515 std::vector<gint> diff1;
516 std::vector<gint> diff2;
517 std::set_difference (list1.begin(), list1.end(), list2.begin(), list2.end(),
518 std::insert_iterator<std::vector<gint> >(diff1, diff1.begin()));
519 std::set_difference (list2.begin(), list2.end(), list1.begin(), list1.end(),
520 std::insert_iterator<std::vector<gint> >(diff2, diff2.begin()));
522 if (diff1.size() == 3 || diff1.size() != diff2.size())
523 return false;
525 for (guint i = 0; i < diff1.size(); ++i) {
526 if (std::find (diff2.begin(), diff2.end(), Box3D::opposite_face (diff1[i])) == diff2.end()) {
527 return false;
528 }
529 }
530 return true;
531 }
533 static gint
534 sp_3dbox_face_containing_diagonal_corners (guint corner1, guint corner2)
535 {
536 Box3D::Axis plane = (Box3D::Axis) (corner1 ^ corner2);
537 if (!Box3D::is_plane (plane)) {
538 g_warning ("Corners %d and %d should span a plane.\n", corner1, corner2);
539 return 0;
540 }
542 return Box3D::face_containing_corner (plane, corner1);
543 }
545 static std::vector<gint> sp_3dbox_adjacent_faces_of_edge (guint corner1, guint corner2) {
546 std::vector<gint> adj_faces;
547 Box3D::Axis edge = (Box3D::Axis) (corner1 ^ corner2);
548 if (!Box3D::is_single_axis_direction (edge)) {
549 return adj_faces;
550 }
552 Box3D::Axis plane = Box3D::orth_plane_or_axis (edge);
553 Box3D::Axis axis1 = Box3D::extract_first_axis_direction (plane);
554 Box3D::Axis axis2 = Box3D::extract_second_axis_direction (plane);
555 adj_faces.push_back (Box3D::face_containing_corner ((Box3D::Axis) (edge ^ axis1), corner1));
556 adj_faces.push_back (Box3D::face_containing_corner ((Box3D::Axis) (edge ^ axis2), corner1));
557 return adj_faces;
558 }
560 static std::vector<gint> sp_3dbox_faces_meeting_in_corner (guint corner) {
561 std::vector<gint> faces;
562 for (int i = 0; i < 3; ++i) {
563 faces.push_back (sp_3dbox_face_containing_diagonal_corners (corner, corner ^ Box3D::planes[i]));
564 }
565 return faces;
566 }
568 static void sp_3dbox_remaining_faces (std::vector<gint> const &faces, std::vector<gint> &rem_faces)
569 {
570 rem_faces.clear();
571 for (gint i = 0; i < 6; ++i) {
572 if (std::find (faces.begin(), faces.end(), i) == faces.end()) {
573 rem_faces.push_back (i);
574 }
575 }
576 }
578 /*
579 * Given two adjacent edges (\a c2,\a c1) and (\a c2, \a c3) of \a box (with common corner \a c2),
580 * check whether both lie on the convex hull of the point configuration given by \a box's corners.
581 */
582 static bool
583 sp_3dbox_is_border_edge_pair (SP3DBox *box, guint const c1, guint const c2, guint const c3)
584 {
585 Box3D::Axis edge21 = (Box3D::Axis) (c2 ^ c1);
586 Box3D::Axis edge23 = (Box3D::Axis) (c2 ^ c3);
587 Box3D::Axis rear_axis = Box3D::orth_plane_or_axis ((Box3D::Axis) (edge21 ^ edge23));
589 NR::Point corner2 = box->corners[c2];
590 NR::Point dir21 = box->corners[c1] - corner2;
591 NR::Point dir23 = box->corners[c3] - corner2;
593 if (!Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ edge21 ^ edge23] - corner2) ||
594 !Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ rear_axis] - corner2) ||
595 !Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ rear_axis ^ edge21] - corner2) ||
596 !Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ rear_axis ^ edge21 ^ edge23] - corner2) ||
597 !Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ rear_axis ^ edge23] - corner2)) {
598 // corner triple c1, c2, c3 doesn't bound the convex hull
599 return false;
600 }
601 // corner triple c1, c2, c3 bounds the convex hull
602 return true;
603 }
605 /*
606 * Test whether there are any adjacent corners of \a corner (i.e., connected with it along one of the axes)
607 * such that the corresponding edges bound the convex hull of the box (as a point configuration in the plane)
608 * If this is the case, return the corresponding two adjacent corners; otherwise return (-1, -1).
609 */
610 static Box3D::Axis
611 sp_3dbox_axis_pair_bounding_convex_hull (SP3DBox *box, guint corner)
612 {
613 guint adj1 = corner ^ Box3D::X;
614 guint adj2 = corner ^ Box3D::Y;
615 guint adj3 = corner ^ Box3D::Z;
617 if (sp_3dbox_is_border_edge_pair (box, adj1, corner, adj2)) {
618 return Box3D::XY;
619 }
620 if (sp_3dbox_is_border_edge_pair (box, adj1, corner, adj3)) {
621 return Box3D::XZ;
622 }
623 if (sp_3dbox_is_border_edge_pair (box, adj2, corner, adj3)) {
624 return Box3D::YZ;
625 }
626 return Box3D::NONE;
627 }
629 // inside_hull is modified 'in place' by the following function
630 static void sp_3dbox_corner_configuration (SP3DBox *box, std::vector<gint> &on_hull, std::vector<gint> &inside_hull)
631 {
632 for (int i = 0; i < 8; ++i) {
633 Box3D::Axis bounding_edges = sp_3dbox_axis_pair_bounding_convex_hull (box, i);
634 if (bounding_edges != Box3D::NONE) {
635 on_hull.push_back (i);
636 } else {
637 inside_hull.push_back (i);
638 }
639 }
640 }
642 /* returns true if there was a change in the z-orders (which triggers an update of the repr) */
643 static bool sp_3dbox_recompute_z_orders_by_corner_configuration (SP3DBox *box)
644 {
645 gint new_z_orders[6];
646 Box3D::Axis front_rear_axis = Box3D::Z;
648 std::vector<gint> on_hull;
649 std::vector<gint> inside_hull;
650 std::vector<gint> visible_faces;
652 sp_3dbox_corner_configuration (box, on_hull, inside_hull);
654 switch (on_hull.size()) {
655 case 4:
656 {
657 // the following works because on_hull is sorted
658 gint front_face = sp_3dbox_face_containing_diagonal_corners (on_hull[0], on_hull[3]);
659 visible_faces.push_back (front_face);
660 }
661 break;
663 case 6:
664 {
665 guint c1 = inside_hull[0] ^ Box3D::XYZ;
666 guint c2 = inside_hull[1] ^ Box3D::XYZ;
667 Box3D::Axis edge = (Box3D::Axis) (c1 ^ c2);
668 if (Box3D::is_single_axis_direction (edge)) {
669 visible_faces = sp_3dbox_adjacent_faces_of_edge (c1, c2);
670 } else if (c1 == c2 ^ Box3D::XYZ) {
671 guint c_cmp = sp_3dbox_get_corner_id_along_edge (box, 0, front_rear_axis, Box3D::FRONT);
672 guint visible_front_corner = (((c_cmp & front_rear_axis) == (c1 & front_rear_axis)) ? c1 : c2);
673 visible_faces = sp_3dbox_faces_meeting_in_corner (visible_front_corner);
674 } else {
675 /* Under what conditions do we end up here? Can we safely ignore this case? */
676 return false;
677 }
678 break;
679 }
681 default:
682 /* Under what conditions do we end up here? Can we safely ignore this case? */
683 return false;
684 }
686 /* catch weird corner configurations; these should be theoretically impossible, but maybe
687 occur in (almost) degenerate cases due to rounding errors, for example */
688 if (std::find (visible_faces.begin(), visible_faces.end(), -1) != visible_faces.end()) {
689 return false;
690 }
692 /* sort the list of visible faces for later use (although it may be already sorted anyway) */
693 std::sort (visible_faces.begin(), visible_faces.end());
695 std::vector<gint> invisible_faces;
696 sp_3dbox_remaining_faces (visible_faces, invisible_faces);
699 if (!sp_3dbox_is_subset_or_superset (visible_faces, box->currently_visible_faces) &&
700 !sp_3dbox_differ_by_opposite_faces (visible_faces, box->currently_visible_faces)) {
701 std::swap (visible_faces, invisible_faces);
702 if (!sp_3dbox_is_subset_or_superset (visible_faces, box->currently_visible_faces) &&
703 !sp_3dbox_differ_by_opposite_faces (visible_faces, box->currently_visible_faces)) {
704 /* Hopefully this case is only caused by rounding errors or something similar;
705 does it need further investigation? */
706 return false;
707 }
708 }
710 box->currently_visible_faces = visible_faces;
712 // set new z-orders according to the visible/invisible faces
713 guint vis_size = visible_faces.size();
714 for (guint i = 0; i < vis_size; ++i) {
715 new_z_orders[i] = visible_faces[i];
716 }
717 for (guint i = 0; i < invisible_faces.size(); ++i) {
718 new_z_orders[vis_size + i] = invisible_faces[i];
719 }
721 // test whether any z-orders actually changed and indicate this in the return status
722 for (int i = 0; i < 6; ++i) {
723 if (box->z_orders[i] != new_z_orders[i]) {
724 // we update the z-orders starting from the index where the change occurs
725 for (int j = i; j < 6; ++j) {
726 box->z_orders[j] = new_z_orders[j];
727 }
728 return true;
729 }
730 }
731 return false;
732 }
734 // FIXME: Can we unify this and the next function for setting the z-orders?
735 void sp_3dbox_set_z_orders_in_the_first_place (SP3DBox *box)
736 {
737 // For efficiency reasons, we only set the new z-orders if something really changed
738 if (sp_3dbox_recompute_z_orders (box)) {
739 box->faces[box->z_orders[0]]->lower_to_bottom ();
740 box->faces[box->z_orders[1]]->lower_to_bottom ();
741 box->faces[box->z_orders[2]]->lower_to_bottom ();
742 box->faces[box->z_orders[3]]->lower_to_bottom ();
743 box->faces[box->z_orders[4]]->lower_to_bottom ();
744 box->faces[box->z_orders[5]]->lower_to_bottom ();
745 }
746 }
748 void sp_3dbox_set_z_orders_later_on (SP3DBox *box)
749 {
750 // For efficiency reasons, we only set the new z-orders if something really changed
751 if (sp_3dbox_recompute_z_orders_by_corner_configuration (box)) {
752 box->faces[box->z_orders[0]]->lower_to_bottom ();
753 box->faces[box->z_orders[1]]->lower_to_bottom ();
754 box->faces[box->z_orders[2]]->lower_to_bottom ();
755 box->faces[box->z_orders[3]]->lower_to_bottom ();
756 box->faces[box->z_orders[4]]->lower_to_bottom ();
757 box->faces[box->z_orders[5]]->lower_to_bottom ();
758 }
759 }
761 void
762 sp_3dbox_update_curves (SP3DBox *box) {
763 for (int i = 0; i < 6; ++i) {
764 if (box->faces[i]) box->faces[i]->set_curve();
765 }
766 }
768 /**
769 * In some situations (e.g., after cloning boxes, undo & redo, or reading boxes from a file) there are
770 * paths already present in the document which correspond to the faces of newly created boxes, but their
771 * 'path' members don't link to them yet. The following function corrects this if necessary.
772 */
773 void
774 sp_3dbox_link_to_existing_paths (SP3DBox *box, Inkscape::XML::Node *repr) {
775 // TODO: We should probably destroy the existing paths and recreate them because we don't know
776 // precisely which path corresponds to which face. Does this make a difference?
777 // In sp_3dbox_write we write the correct paths anyway, don't we? But we could get into
778 // trouble at a later stage when we only write single faces for degenerate boxes.
780 SPDocument *document = SP_OBJECT_DOCUMENT(box);
781 guint face_id = 0;
783 for (Inkscape::XML::Node *i = sp_repr_children(repr); i != NULL; i = sp_repr_next(i)) {
784 if (face_id > 5) {
785 g_warning ("SVG representation of 3D boxes must contain 6 paths or less.\n");
786 break;
787 }
789 SPObject *face_object = document->getObjectByRepr((Inkscape::XML::Node *) i);
790 if (!SP_IS_PATH(face_object)) {
791 g_warning ("SVG representation of 3D boxes should only contain paths.\n");
792 continue;
793 }
794 // TODO: Currently we don't check whether all paths are being linked to different faces.
795 // This is no problem with valid SVG files. It may lead to crashes, however,
796 // in case a file is corrupt (e.g., two or more faces have identical descriptions).
797 gint id = Box3DFace::descr_to_id (i->attribute ("inkscape:box3dface"));
798 box->faces[id]->hook_path_to_3dbox(SP_PATH(face_object));
799 ++face_id;
800 }
801 if (face_id < 6) {
802 //g_warning ("SVG representation of 3D boxes should contain exactly 6 paths (degenerate boxes are not yet supported).\n");
803 // TODO: Check whether it is safe to add the remaining paths to the box and do so in case it is.
804 // (But we also land here for newly created boxes where we shouldn't add any paths because
805 // This is done in sp_3dbox_write later on.
806 }
807 }
809 void
810 sp_3dbox_reshape_after_VP_rotation (SP3DBox *box, Box3D::Axis axis)
811 {
812 Box3D::Perspective3D *persp = inkscape_active_document()->get_persp_of_box (box);
813 Box3D::VanishingPoint *vp = persp->get_vanishing_point (axis);
815 guint c1 = (axis == Box3D::Z) ? 1 : sp_3dbox_get_front_corner_id (box); // hack
816 guint c2 = c1 ^ axis;
817 NR::Point v = box->corners[c1] - box->corners[c2];
818 double dist = NR::L2 (v) * ((NR::dot (v, vp->v_dir) < 0) ? 1 : -1); // "directed" distance
820 Box3D::PerspectiveLine pline (box->corners[c1], axis, persp);
821 NR::Point pt = pline.point_from_lambda (dist);
823 sp_3dbox_move_corner_in_Z_direction (box, c2, pt, axis == Box3D::Z);
824 }
826 void
827 sp_3dbox_move_corner_in_XY_plane (SP3DBox *box, guint id, NR::Point pt, Box3D::Axis axes)
828 {
829 Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
831 NR::Point A (box->corners[id ^ Box3D::XY]);
832 if (Box3D::is_single_axis_direction (axes)) {
833 pt = Box3D::PerspectiveLine (box->corners[id], axes, persp).closest_to(pt);
834 }
836 /* set the 'front' corners */
837 box->corners[id] = pt;
839 Box3D::PerspectiveLine pl_one (A, Box3D::Y, persp);
840 Box3D::PerspectiveLine pl_two (pt, Box3D::X, persp);
841 box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
843 pl_one = Box3D::PerspectiveLine (A, Box3D::X, persp);
844 pl_two = Box3D::PerspectiveLine (pt, Box3D::Y, persp);
845 box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
847 /* set the 'rear' corners */
848 NR::Point B (box->corners[id ^ Box3D::XYZ]);
850 pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Z, persp);
851 pl_two = Box3D::PerspectiveLine (B, Box3D::Y, persp);
852 box->corners[id ^ Box3D::XZ] = pl_one.meet(pl_two);
854 pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XZ], Box3D::X, persp);
855 pl_two = Box3D::PerspectiveLine (pt, Box3D::Z, persp);
856 box->corners[id ^ Box3D::Z] = pl_one.meet(pl_two);
858 pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::Z], Box3D::Y, persp);
859 pl_two = Box3D::PerspectiveLine (B, Box3D::X, persp);
860 box->corners[id ^ Box3D::YZ] = pl_one.meet(pl_two);
862 }
864 void
865 sp_3dbox_move_corner_in_Z_direction (SP3DBox *box, guint id, NR::Point pt, bool constrained)
866 {
867 if (!constrained) sp_3dbox_move_corner_in_XY_plane (box, id, pt, Box3D::XY);
869 Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
871 /* set the four corners of the face containing corners[id] */
872 box->corners[id] = Box3D::PerspectiveLine (box->corners[id], Box3D::Z, persp).closest_to(pt);
874 Box3D::PerspectiveLine pl_one (box->corners[id], Box3D::X, persp);
875 Box3D::PerspectiveLine pl_two (box->corners[id ^ Box3D::XZ], Box3D::Z, persp);
876 box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
878 pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Y, persp);
879 pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XYZ], Box3D::Z, persp);
880 box->corners[id ^ Box3D::XY] = pl_one.meet(pl_two);
882 pl_one = Box3D::PerspectiveLine (box->corners[id], Box3D::Y, persp);
883 pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::YZ], Box3D::Z, persp);
884 box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
885 }
887 static void
888 sp_3dbox_reshape_edge_after_VP_toggling (SP3DBox *box, const guint corner, const Box3D::Axis axis, Box3D::Perspective3D *persp)
889 {
890 /* Hmm, perhaps we should simply use one of the corners as the pivot point.
891 But this way we minimize the amount of reshaping.
892 On second thought, we need to find a way to ensure that all boxes sharing the same
893 perspective are updated consistently _as a group_. That is, they should also retain
894 their relative positions towards each other. */
895 NR::Maybe<NR::Point> pt = sp_3dbox_get_midpoint_between_corners (box, corner, corner ^ axis);
896 g_return_if_fail (pt);
898 Box3D::Axis axis2 = ((axis == Box3D::Y) ? Box3D::X : Box3D::Y);
900 Box3D::PerspectiveLine line1 (box->corners[corner], axis2, persp);
901 Box3D::PerspectiveLine line2 (box->corners[corner ^ axis], axis2, persp);
903 Box3D::PerspectiveLine line3 (*pt, axis, persp);
905 NR::Point new_corner1 = line1.meet (line3);
906 NR::Point new_corner2 = line2.meet (line3);
908 box->corners[corner] = new_corner1;
909 box->corners[corner ^ axis] = new_corner2;
910 }
912 void
913 sp_3dbox_reshape_after_VP_toggling (SP3DBox *box, Box3D::Axis axis)
914 {
915 Box3D::Perspective3D *persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
916 std::pair<Box3D::Axis, Box3D::Axis> dirs = Box3D::get_remaining_axes (axis);
918 sp_3dbox_reshape_edge_after_VP_toggling (box, 0, axis, persp);
919 sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first, axis, persp);
920 sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first ^ dirs.second, axis, persp);
921 sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.second, axis, persp);
922 }
924 NR::Maybe<NR::Point>
925 sp_3dbox_get_center (SP3DBox *box)
926 {
927 return sp_3dbox_get_midpoint_between_corners (box, 0, 7);
928 }
930 NR::Point
931 sp_3dbox_get_midpoint_in_axis_direction (NR::Point const &C, NR::Point const &D, Box3D::Axis axis, Box3D::Perspective3D *persp)
932 {
933 Box3D::PerspectiveLine pl (D, axis, persp);
934 return pl.pt_with_given_cross_ratio (C, D, -1.0);
935 }
937 // TODO: The following function can probably be rewritten in a much more elegant and robust way
938 // by using projective coordinates for all points and using the cross ratio.
939 NR::Maybe<NR::Point>
940 sp_3dbox_get_midpoint_between_corners (SP3DBox *box, guint id_corner1, guint id_corner2)
941 {
942 Box3D::Axis corner_axes = (Box3D::Axis) (id_corner1 ^ id_corner2);
944 // Is all this sufficiently precise also for degenerate cases?
945 if (sp_3dbox_corners_are_adjacent (id_corner1, id_corner2)) {
946 Box3D::Axis orth_dir = get_perpendicular_axis_direction (corner_axes);
948 Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2 ^ orth_dir]);
949 Box3D::Line diag2 (box->corners[id_corner1 ^ orth_dir], box->corners[id_corner2]);
950 NR::Maybe<NR::Point> adjacent_face_center = diag1.intersect(diag2);
952 if (!adjacent_face_center) return NR::Nothing();
954 Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
956 Box3D::PerspectiveLine pl (*adjacent_face_center, orth_dir, persp);
957 return pl.intersect(Box3D::PerspectiveLine(box->corners[id_corner1], corner_axes, persp));
958 } else {
959 Box3D::Axis dir = Box3D::extract_first_axis_direction (corner_axes);
960 Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2]);
961 Box3D::Line diag2 (box->corners[id_corner1 ^ dir], box->corners[id_corner2 ^ dir]);
962 return diag1.intersect(diag2);
963 }
964 }
966 static gchar *
967 sp_3dbox_get_corner_coords_string (SP3DBox *box, guint id)
968 {
969 id = id % 8;
970 Inkscape::SVGOStringStream os;
971 os << box->corners[id][NR::X] << "," << box->corners[id][NR::Y];
972 return g_strdup(os.str().c_str());
973 }
975 static std::pair<gdouble, gdouble>
976 sp_3dbox_get_coord_pair_from_string (const gchar *coords)
977 {
978 gchar **coordpair = g_strsplit( coords, ",", 0);
979 // We might as well rely on g_ascii_strtod to convert the NULL pointer to 0.0,
980 // but we include the following test anyway
981 if (coordpair[0] == NULL || coordpair[1] == NULL) {
982 g_strfreev (coordpair);
983 g_warning ("Coordinate conversion failed.\n");
984 return std::make_pair(0.0, 0.0);
985 }
987 gdouble coord1 = g_ascii_strtod(coordpair[0], NULL);
988 gdouble coord2 = g_ascii_strtod(coordpair[1], NULL);
989 g_strfreev (coordpair);
991 return std::make_pair(coord1, coord2);
992 }
994 static gchar *
995 sp_3dbox_get_perspective_string (SP3DBox *box)
996 {
998 return sp_3dbox_get_svg_descr_of_persp (SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box));
999 }
1001 gchar *
1002 sp_3dbox_get_svg_descr_of_persp (Box3D::Perspective3D *persp)
1003 {
1004 // FIXME: We should move this code to perspective3d.cpp, but this yields compiler errors. Why?
1005 Inkscape::SVGOStringStream os;
1007 Box3D::VanishingPoint vp = *(persp->get_vanishing_point (Box3D::X));
1008 os << vp[NR::X] << "," << vp[NR::Y] << ",";
1009 os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
1010 if (vp.is_finite()) {
1011 os << "finite,";
1012 } else {
1013 os << "infinite,";
1014 }
1016 vp = *(persp->get_vanishing_point (Box3D::Y));
1017 os << vp[NR::X] << "," << vp[NR::Y] << ",";
1018 os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
1019 if (vp.is_finite()) {
1020 os << "finite,";
1021 } else {
1022 os << "infinite,";
1023 }
1025 vp = *(persp->get_vanishing_point (Box3D::Z));
1026 os << vp[NR::X] << "," << vp[NR::Y] << ",";
1027 os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
1028 if (vp.is_finite()) {
1029 os << "finite";
1030 } else {
1031 os << "infinite";
1032 }
1034 return g_strdup(os.str().c_str());
1035 }
1037 // auxiliary function
1038 static std::pair<NR::Point, NR::Point>
1039 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)
1040 {
1041 double cr1 = Box3D::cross_ratio (*persp->get_vanishing_point (axis), M0, M, A);
1042 double cr2 = Box3D::cross_ratio (*persp->get_vanishing_point (axis), M, B, M0);
1043 if (fabs (cr1 - 1) < Box3D::epsilon) {
1044 // FIXME: cr == 1 is a degenerate case; how should we deal with it?
1045 return std::make_pair (NR::Point (0,0), NR::Point (0,0));
1046 }
1047 if (cr1 == NR_HUGE) {
1048 return std::make_pair (A, B);
1049 }
1050 Box3D::PerspectiveLine pl (M0, axis, persp);
1051 NR::Point B_new = pl.pt_with_given_cross_ratio (M0, M, cr1 / (cr1 - 1));
1052 NR::Point A_new = pl.pt_with_given_cross_ratio (M0, M, 1 - cr2);
1053 return std::make_pair (A_new, B_new);
1054 }
1056 void sp_3dbox_recompute_Z_corners_from_new_center (SP3DBox *box, NR::Point const new_center)
1057 {
1058 // TODO: Clean this function up
1060 Box3D::Perspective3D *persp = sp_desktop_document (inkscape_active_desktop())->get_persp_of_box (box);
1061 NR::Point old_center = box->old_center;
1063 Box3D::PerspectiveLine aux_line1 (old_center, Box3D::Z, persp);
1064 Box3D::PerspectiveLine aux_line2 (new_center, Box3D::Y, persp);
1065 NR::Point Z1 = aux_line1.meet (aux_line2);
1067 NR::Point A0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner2, box->old_corner0, Box3D::Y, persp));
1068 NR::Point B0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner7, box->old_corner5, Box3D::Y, persp));
1069 Box3D::PerspectiveLine aux_line3 (A0, Box3D::X, persp);
1070 Box3D::PerspectiveLine aux_line4 (B0, Box3D::X, persp);
1072 NR::Point C0 = aux_line3.meet (aux_line1);
1073 NR::Point D0 = aux_line4.meet (aux_line1);
1075 std::pair<NR::Point, NR::Point> new_midpts = sp_3dbox_new_midpoints (persp, Box3D::Z, old_center, Z1, C0, D0);
1076 NR::Point C1 (new_midpts.first);
1077 NR::Point D1 (new_midpts.second);
1078 Box3D::PerspectiveLine aux_line5 (C1, Box3D::X, persp);
1079 Box3D::PerspectiveLine aux_line6 (D1, Box3D::X, persp);
1081 Box3D::PerspectiveLine aux_line7 (A0, Box3D::Z, persp);
1082 Box3D::PerspectiveLine aux_line8 (B0, Box3D::Z, persp);
1084 NR::Point A1 = aux_line5.meet (aux_line7);
1085 NR::Point B1 = aux_line6.meet (aux_line8);
1087 Box3D::PerspectiveLine aux_line9 (box->old_corner2, Box3D::Z, persp);
1088 Box3D::PerspectiveLine aux_line10 (box->old_corner5, Box3D::Z, persp);
1090 Box3D::PerspectiveLine aux_line11 (A1, Box3D::Y, persp);
1091 Box3D::PerspectiveLine aux_line12 (B1, Box3D::Y, persp);
1093 NR::Point new_corner2 = aux_line9.meet (aux_line11);
1094 NR::Point new_corner5 = aux_line10.meet (aux_line12);
1096 Box3D::PerspectiveLine aux_line13 (A1, Box3D::X, persp);
1097 NR::Point E1 = aux_line13.meet (aux_line8);
1098 Box3D::PerspectiveLine aux_line14 (E1, Box3D::Y, persp);
1100 NR::Point new_corner1 = aux_line10.meet (aux_line14);
1102 sp_3dbox_set_shape_from_points (box, new_corner2, new_corner1, new_corner5);
1103 }
1105 void sp_3dbox_recompute_XY_corners_from_new_center (SP3DBox *box, NR::Point const new_center)
1106 {
1107 // TODO: Clean this function up
1109 Box3D::Perspective3D *persp = sp_desktop_document (inkscape_active_desktop())->get_persp_of_box (box);
1110 NR::Point old_center = box->old_center;
1112 NR::Point A0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner2, box->old_corner0, Box3D::Y, persp));
1113 NR::Point B0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner1, box->old_corner3, Box3D::Y, persp));
1115 /* we first move the box along the X-axis ... */
1116 Box3D::PerspectiveLine aux_line1 (old_center, Box3D::X, persp);
1117 Box3D::PerspectiveLine aux_line2 (new_center, Box3D::Y, persp);
1118 NR::Point Z1 = aux_line1.meet (aux_line2);
1120 Box3D::PerspectiveLine ref_line (B0, Box3D::X, persp);
1121 Box3D::PerspectiveLine pline2 (old_center, Box3D::Z, persp);
1122 Box3D::PerspectiveLine pline3 (Z1, Box3D::Z, persp);
1123 NR::Point M0 = ref_line.meet (pline2);
1124 NR::Point M1 = ref_line.meet (pline3);
1126 std::pair<NR::Point, NR::Point> new_midpts = sp_3dbox_new_midpoints (persp, Box3D::X, M0, M1, A0, B0);
1127 NR::Point A1 (new_midpts.first);
1128 NR::Point B1 (new_midpts.second);
1130 /* ... and then along the Y-axis */
1131 Box3D::PerspectiveLine pline4 (box->old_corner1, Box3D::X, persp);
1132 Box3D::PerspectiveLine pline5 (box->old_corner3, Box3D::X, persp);
1133 Box3D::PerspectiveLine aux_line3 (M1, Box3D::Y, persp);
1134 NR::Point C1 = aux_line3.meet (pline4);
1135 NR::Point D1 = aux_line3.meet (pline5);
1137 Box3D::PerspectiveLine aux_line4 (new_center, Box3D::Z, persp);
1138 NR::Point M2 = aux_line4.meet (aux_line3);
1140 std::pair<NR::Point, NR::Point> other_new_midpts = sp_3dbox_new_midpoints (persp, Box3D::Y, M1, M2, C1, D1);
1141 NR::Point C2 (other_new_midpts.first);
1142 NR::Point D2 (other_new_midpts.second);
1144 Box3D::PerspectiveLine plXC (C2, Box3D::X, persp);
1145 Box3D::PerspectiveLine plXD (D2, Box3D::X, persp);
1146 Box3D::PerspectiveLine plYA (A1, Box3D::Y, persp);
1147 Box3D::PerspectiveLine plYB (B1, Box3D::Y, persp);
1149 NR::Point new_corner2 (plXD.meet (plYA));
1150 NR::Point new_corner1 (plXC.meet (plYB));
1152 NR::Point tmp_corner1 (pline4.meet (plYB));
1153 Box3D::PerspectiveLine pline6 (box->old_corner5, Box3D::X, persp);
1154 Box3D::PerspectiveLine pline7 (tmp_corner1, Box3D::Z, persp);
1155 NR::Point tmp_corner5 (pline6.meet (pline7));
1157 Box3D::PerspectiveLine pline8 (tmp_corner5, Box3D::Y, persp);
1158 Box3D::PerspectiveLine pline9 (new_corner1, Box3D::Z, persp);
1159 NR::Point new_corner5 (pline8.meet (pline9));
1161 sp_3dbox_set_shape_from_points (box, new_corner2, new_corner1, new_corner5);
1162 }
1164 void sp_3dbox_update_perspective_lines()
1165 {
1166 SPEventContext *ec = inkscape_active_event_context();
1167 if (!SP_IS_3DBOX_CONTEXT (ec))
1168 return;
1170 SP_3DBOX_CONTEXT (ec)->_vpdrag->updateLines();
1171 }
1173 /*
1174 * Manipulates corner1 through corner4 to contain the indices of the corners
1175 * from which the perspective lines in the direction of 'axis' emerge
1176 */
1177 void sp_3dbox_corners_for_perspective_lines (const SP3DBox * box, Box3D::Axis axis,
1178 NR::Point &corner1, NR::Point &corner2, NR::Point &corner3, NR::Point &corner4)
1179 {
1180 // along which axis to switch when takint
1181 Box3D::Axis switch_axis;
1182 if (axis == Box3D::X || axis == Box3D::Y) {
1183 switch_axis = (box->front_bits & axis) ? Box3D::Z : Box3D::NONE;
1184 } else {
1185 switch_axis = (box->front_bits & axis) ? Box3D::X : Box3D::NONE;
1186 }
1188 switch (axis) {
1189 case Box3D::X:
1190 corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
1191 corner2 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
1192 corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
1193 corner4 = sp_3dbox_get_corner_along_edge (box, 6 ^ switch_axis, axis, Box3D::REAR);
1194 break;
1195 case Box3D::Y:
1196 corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
1197 corner2 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
1198 corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
1199 corner4 = sp_3dbox_get_corner_along_edge (box, 5 ^ switch_axis, axis, Box3D::REAR);
1200 break;
1201 case Box3D::Z:
1202 corner1 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
1203 corner2 = sp_3dbox_get_corner_along_edge (box, 3 ^ switch_axis, axis, Box3D::REAR);
1204 corner3 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
1205 corner4 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
1206 break;
1207 default:
1208 // do nothing
1209 break;
1210 }
1211 }
1213 /**
1214 * Returns the id of the corner on the edge along 'axis' and passing through 'corner' that
1215 * lies on the front/rear face in this direction.
1216 */
1217 guint
1218 sp_3dbox_get_corner_id_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
1219 {
1220 guint result;
1221 guint other_corner = corner ^ axis;
1222 Box3D::VanishingPoint *vp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point(axis);
1223 if (vp->is_finite()) {
1224 result = ( NR::L2 (vp->get_pos() - box->corners[corner])
1225 < NR::L2 (vp->get_pos() - box->corners[other_corner]) ? other_corner : corner);
1226 } else {
1227 // clear the axis bit and switch to the appropriate corner along axis, depending on the value of front_bits
1228 result = ((corner & (0xF ^ axis)) ^ (box->front_bits & axis));
1229 }
1231 if (rel_pos == Box3D::FRONT) {
1232 return result;
1233 } else {
1234 return result ^ axis;
1235 }
1236 }
1238 NR::Point
1239 sp_3dbox_get_corner_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
1240 {
1241 return box->corners[sp_3dbox_get_corner_id_along_edge (box, corner, axis, rel_pos)];
1242 }
1244 guint
1245 sp_3dbox_get_front_corner_id (const SP3DBox *box)
1246 {
1247 guint front_corner = 1; // this could in fact be any corner, but we choose the one that is normally in front
1248 front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::X, Box3D::FRONT);
1249 front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Y, Box3D::FRONT);
1250 front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Z, Box3D::FRONT);
1251 return front_corner;
1252 }
1254 // auxiliary functions
1255 static void
1256 sp_3dbox_update_corner_with_value_from_svg (SPObject *object, guint corner_id, const gchar *value)
1257 {
1258 if (value == NULL) return;
1259 SP3DBox *box = SP_3DBOX(object);
1261 std::pair<gdouble, gdouble> coord_pair = sp_3dbox_get_coord_pair_from_string (value);
1262 box->corners[corner_id] = NR::Point (coord_pair.first, coord_pair.second);
1263 sp_3dbox_recompute_corners (box, box->corners[2], box->corners[1], box->corners[5]);
1264 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1265 }
1267 static void
1268 sp_3dbox_update_perspective (Box3D::Perspective3D *persp, const gchar *value)
1269 {
1270 // WARNING! This function changes the perspective associated to 'box'. Since there may be
1271 // many other boxes linked to the same perspective, their perspective is also changed.
1272 // If this behaviour is not desired in all cases, we need a different function.
1273 if (value == NULL) return;
1275 gchar **vps = g_strsplit( value, ",", 0);
1276 for (int i = 0; i < 15; ++i) {
1277 if (vps[i] == NULL) {
1278 g_warning ("Malformed svg attribute 'perspective'\n");
1279 return;
1280 }
1281 }
1283 persp->set_vanishing_point (Box3D::X, g_ascii_strtod (vps[0], NULL), g_ascii_strtod (vps[1], NULL),
1284 g_ascii_strtod (vps[2], NULL), g_ascii_strtod (vps[3], NULL),
1285 strcmp (vps[4], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
1286 persp->set_vanishing_point (Box3D::Y, g_ascii_strtod (vps[5], NULL), g_ascii_strtod (vps[6], NULL),
1287 g_ascii_strtod (vps[7], NULL), g_ascii_strtod (vps[8], NULL),
1288 strcmp (vps[9], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
1289 persp->set_vanishing_point (Box3D::Z, g_ascii_strtod (vps[10], NULL), g_ascii_strtod (vps[11], NULL),
1290 g_ascii_strtod (vps[12], NULL), g_ascii_strtod (vps[13], NULL),
1291 strcmp (vps[14], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
1293 // update the other boxes linked to the same perspective
1294 persp->reshape_boxes (Box3D::XYZ);
1295 }
1297 /*
1298 Local Variables:
1299 mode:c++
1300 c-file-style:"stroustrup"
1301 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1302 indent-tabs-mode:nil
1303 fill-column:99
1304 End:
1305 */
1306 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :