5e67f46ab33b148e2a14833968c50fd8c2fc9369
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 sp_3dbox_link_to_existing_paths (box, SP_OBJECT_REPR(object));
238 }
240 /* Invoke parent method */
241 if (((SPObjectClass *) (parent_class))->update)
242 ((SPObjectClass *) (parent_class))->update(object, ctx, flags);
243 }
245 static Inkscape::XML::Node *sp_3dbox_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
246 {
247 SP3DBox *box = SP_3DBOX(object);
248 // FIXME: How to handle other contexts???
249 // FIXME: Is tools_isactive(..) more recommended to check for the current context/tool?
250 if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
251 return repr;
253 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
254 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
255 repr = xml_doc->createElement("svg:g");
256 repr->setAttribute("sodipodi:type", "inkscape:3dbox");
257 /* Hook paths to the faces of the box */
258 for (int i = 0; i < 6; ++i) {
259 box->faces[i]->hook_path_to_3dbox();
260 }
261 }
263 for (int i = 0; i < 6; ++i) {
264 box->faces[i]->set_path_repr();
265 }
267 if (flags & SP_OBJECT_WRITE_EXT) {
268 gchar *str;
269 str = sp_3dbox_get_corner_coords_string (box, 2);
270 repr->setAttribute("inkscape:box3dcornerA", str);
272 str = sp_3dbox_get_corner_coords_string (box, 1);
273 repr->setAttribute("inkscape:box3dcornerB", str);
275 str = sp_3dbox_get_corner_coords_string (box, 5);
276 repr->setAttribute("inkscape:box3dcornerC", str);
278 str = sp_3dbox_get_perspective_string (box);
279 repr->setAttribute("inkscape:perspective", str);
280 sp_3dbox_set_ratios (box);
282 g_free ((void *) str);
284 /* store center and construction-corners for later use during center-dragging */
285 NR::Maybe<NR::Point> cen = sp_3dbox_get_center (box);
286 if (cen) {
287 box->old_center = *cen;
288 }
289 box->old_corner2 = box->corners[2];
290 box->old_corner1 = box->corners[1];
291 box->old_corner0 = box->corners[0];
292 box->old_corner3 = box->corners[3];
293 box->old_corner5 = box->corners[5];
294 box->old_corner7 = box->corners[7];
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 gint 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 // convenience
499 static bool sp_3dbox_is_subset_or_superset (std::vector<gint> const &list1, std::vector<gint> const &list2)
500 {
501 return (std::includes (list1.begin(), list1.end(), list2.begin(), list2.end()) ||
502 std::includes (list2.begin(), list2.end(), list1.begin(), list1.end()));
503 }
505 static bool sp_3dbox_differ_by_opposite_faces (std::vector<gint> const &list1, std::vector<gint> const &list2)
506 {
507 std::vector<gint> diff1;
508 std::vector<gint> diff2;
509 std::set_difference (list1.begin(), list1.end(), list2.begin(), list2.end(),
510 std::insert_iterator<std::vector<gint> >(diff1, diff1.begin()));
511 std::set_difference (list2.begin(), list2.end(), list1.begin(), list1.end(),
512 std::insert_iterator<std::vector<gint> >(diff2, diff2.begin()));
514 if (diff1.size() == 3 || diff1.size() != diff2.size())
515 return false;
517 for (guint i = 0; i < diff1.size(); ++i) {
518 if (std::find (diff2.begin(), diff2.end(), Box3D::opposite_face (diff1[i])) == diff2.end()) {
519 return false;
520 }
521 }
522 return true;
523 }
525 static gint
526 sp_3dbox_face_containing_diagonal_corners (guint corner1, guint corner2)
527 {
528 Box3D::Axis plane = (Box3D::Axis) (corner1 ^ corner2);
529 if (!Box3D::is_plane (plane)) {
530 g_warning ("Corners %d and %d should span a plane.\n", corner1, corner2);
531 return 0;
532 }
534 return Box3D::face_containing_corner (plane, corner1);
535 }
537 static std::vector<gint> sp_3dbox_adjacent_faces_of_edge (guint corner1, guint corner2) {
538 std::vector<gint> adj_faces;
539 Box3D::Axis edge = (Box3D::Axis) (corner1 ^ corner2);
540 if (!Box3D::is_single_axis_direction (edge)) {
541 return adj_faces;
542 }
544 Box3D::Axis plane = Box3D::orth_plane_or_axis (edge);
545 Box3D::Axis axis1 = Box3D::extract_first_axis_direction (plane);
546 Box3D::Axis axis2 = Box3D::extract_second_axis_direction (plane);
547 adj_faces.push_back (Box3D::face_containing_corner ((Box3D::Axis) (edge ^ axis1), corner1));
548 adj_faces.push_back (Box3D::face_containing_corner ((Box3D::Axis) (edge ^ axis2), corner1));
549 return adj_faces;
550 }
552 static std::vector<gint> sp_3dbox_faces_meeting_in_corner (guint corner) {
553 std::vector<gint> faces;
554 for (int i = 0; i < 3; ++i) {
555 faces.push_back (sp_3dbox_face_containing_diagonal_corners (corner, corner ^ Box3D::planes[i]));
556 }
557 return faces;
558 }
560 static void sp_3dbox_remaining_faces (std::vector<gint> const &faces, std::vector<gint> &rem_faces)
561 {
562 rem_faces.clear();
563 for (gint i = 0; i < 6; ++i) {
564 if (std::find (faces.begin(), faces.end(), i) == faces.end()) {
565 rem_faces.push_back (i);
566 }
567 }
568 }
570 /*
571 * Given two adjacent edges (\a c2,\a c1) and (\a c2, \a c3) of \a box (with common corner \a c2),
572 * check whether both lie on the convex hull of the point configuration given by \a box's corners.
573 */
574 static bool
575 sp_3dbox_is_border_edge_pair (SP3DBox *box, guint const c1, guint const c2, guint const c3)
576 {
577 Box3D::Axis edge21 = (Box3D::Axis) (c2 ^ c1);
578 Box3D::Axis edge23 = (Box3D::Axis) (c2 ^ c3);
579 Box3D::Axis rear_axis = Box3D::orth_plane_or_axis ((Box3D::Axis) (edge21 ^ edge23));
581 NR::Point corner2 = box->corners[c2];
582 NR::Point dir21 = box->corners[c1] - corner2;
583 NR::Point dir23 = box->corners[c3] - corner2;
585 if (!Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ edge21 ^ edge23] - corner2) ||
586 !Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ rear_axis] - corner2) ||
587 !Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ rear_axis ^ edge21] - corner2) ||
588 !Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ rear_axis ^ edge21 ^ edge23] - corner2) ||
589 !Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ rear_axis ^ edge23] - corner2)) {
590 // corner triple c1, c2, c3 doesn't bound the convex hull
591 return false;
592 }
593 // corner triple c1, c2, c3 bounds the convex hull
594 return true;
595 }
597 /*
598 * Test whether there are any adjacent corners of \a corner (i.e., connected with it along one of the axes)
599 * such that the corresponding edges bound the convex hull of the box (as a point configuration in the plane)
600 * If this is the case, return the corresponding two adjacent corners; otherwise return (-1, -1).
601 */
602 static Box3D::Axis
603 sp_3dbox_axis_pair_bounding_convex_hull (SP3DBox *box, guint corner)
604 {
605 guint adj1 = corner ^ Box3D::X;
606 guint adj2 = corner ^ Box3D::Y;
607 guint adj3 = corner ^ Box3D::Z;
609 if (sp_3dbox_is_border_edge_pair (box, adj1, corner, adj2)) {
610 return Box3D::XY;
611 }
612 if (sp_3dbox_is_border_edge_pair (box, adj1, corner, adj3)) {
613 return Box3D::XZ;
614 }
615 if (sp_3dbox_is_border_edge_pair (box, adj2, corner, adj3)) {
616 return Box3D::YZ;
617 }
618 return Box3D::NONE;
619 }
621 // inside_hull is modified 'in place' by the following function
622 static void sp_3dbox_corner_configuration (SP3DBox *box, std::vector<gint> &on_hull, std::vector<gint> &inside_hull)
623 {
624 for (int i = 0; i < 8; ++i) {
625 Box3D::Axis bounding_edges = sp_3dbox_axis_pair_bounding_convex_hull (box, i);
626 if (bounding_edges != Box3D::NONE) {
627 on_hull.push_back (i);
628 } else {
629 inside_hull.push_back (i);
630 }
631 }
632 }
634 /* returns true if there was a change in the z-orders (which triggers an update of the repr) */
635 static bool sp_3dbox_recompute_z_orders_by_corner_configuration (SP3DBox *box)
636 {
637 gint new_z_orders[6];
638 Box3D::Axis front_rear_axis = Box3D::Z;
640 std::vector<gint> on_hull;
641 std::vector<gint> inside_hull;
642 std::vector<gint> visible_faces;
644 sp_3dbox_corner_configuration (box, on_hull, inside_hull);
646 switch (on_hull.size()) {
647 case 4:
648 {
649 // the following works because on_hull is sorted
650 gint front_face = sp_3dbox_face_containing_diagonal_corners (on_hull[0], on_hull[3]);
651 visible_faces.push_back (front_face);
652 }
653 break;
655 case 6:
656 {
657 guint c1 = inside_hull[0] ^ Box3D::XYZ;
658 guint c2 = inside_hull[1] ^ Box3D::XYZ;
659 Box3D::Axis edge = (Box3D::Axis) (c1 ^ c2);
660 if (Box3D::is_single_axis_direction (edge)) {
661 visible_faces = sp_3dbox_adjacent_faces_of_edge (c1, c2);
662 } else if (c1 == c2 ^ Box3D::XYZ) {
663 guint c_cmp = sp_3dbox_get_corner_id_along_edge (box, 0, front_rear_axis, Box3D::FRONT);
664 guint visible_front_corner = (((c_cmp & front_rear_axis) == (c1 & front_rear_axis)) ? c1 : c2);
665 visible_faces = sp_3dbox_faces_meeting_in_corner (visible_front_corner);
666 } else {
667 /* Under what conditions do we end up here? Can we safely ignore this case? */
668 return false;
669 }
670 break;
671 }
673 default:
674 /* Under what conditions do we end up here? Can we safely ignore this case? */
675 return false;
676 }
678 /* catch weird corner configurations; these should be theoretically impossible, but maybe
679 occur in (almost) degenerate cases due to rounding errors, for example */
680 if (std::find (visible_faces.begin(), visible_faces.end(), -1) != visible_faces.end()) {
681 return false;
682 }
684 /* sort the list of visible faces for later use (although it may be already sorted anyway) */
685 std::sort (visible_faces.begin(), visible_faces.end());
687 std::vector<gint> invisible_faces;
688 sp_3dbox_remaining_faces (visible_faces, invisible_faces);
691 if (!sp_3dbox_is_subset_or_superset (visible_faces, box->currently_visible_faces) &&
692 !sp_3dbox_differ_by_opposite_faces (visible_faces, box->currently_visible_faces)) {
693 std::swap (visible_faces, invisible_faces);
694 if (!sp_3dbox_is_subset_or_superset (visible_faces, box->currently_visible_faces) &&
695 !sp_3dbox_differ_by_opposite_faces (visible_faces, box->currently_visible_faces)) {
696 /* Hopefully this case is only caused by rounding errors or something similar;
697 does it need further investigation? */
698 return false;
699 }
700 }
702 box->currently_visible_faces = visible_faces;
704 // set new z-orders according to the visible/invisible faces
705 guint vis_size = visible_faces.size();
706 for (guint i = 0; i < vis_size; ++i) {
707 new_z_orders[i] = visible_faces[i];
708 }
709 for (guint i = 0; i < invisible_faces.size(); ++i) {
710 new_z_orders[vis_size + i] = invisible_faces[i];
711 }
713 // test whether any z-orders actually changed and indicate this in the return status
714 for (int i = 0; i < 6; ++i) {
715 if (box->z_orders[i] != new_z_orders[i]) {
716 // we update the z-orders starting from the index where the change occurs
717 for (int j = i; j < 6; ++j) {
718 box->z_orders[j] = new_z_orders[j];
719 }
720 return true;
721 }
722 }
723 return false;
724 }
726 // FIXME: Can we unify this and the next function for setting the z-orders?
727 void sp_3dbox_set_z_orders_in_the_first_place (SP3DBox *box)
728 {
729 // For efficiency reasons, we only set the new z-orders if something really changed
730 if (sp_3dbox_recompute_z_orders (box)) {
731 box->faces[box->z_orders[0]]->lower_to_bottom ();
732 box->faces[box->z_orders[1]]->lower_to_bottom ();
733 box->faces[box->z_orders[2]]->lower_to_bottom ();
734 box->faces[box->z_orders[3]]->lower_to_bottom ();
735 box->faces[box->z_orders[4]]->lower_to_bottom ();
736 box->faces[box->z_orders[5]]->lower_to_bottom ();
737 }
738 }
740 void sp_3dbox_set_z_orders_later_on (SP3DBox *box)
741 {
742 // For efficiency reasons, we only set the new z-orders if something really changed
743 if (sp_3dbox_recompute_z_orders_by_corner_configuration (box)) {
744 box->faces[box->z_orders[0]]->lower_to_bottom ();
745 box->faces[box->z_orders[1]]->lower_to_bottom ();
746 box->faces[box->z_orders[2]]->lower_to_bottom ();
747 box->faces[box->z_orders[3]]->lower_to_bottom ();
748 box->faces[box->z_orders[4]]->lower_to_bottom ();
749 box->faces[box->z_orders[5]]->lower_to_bottom ();
750 }
751 }
753 void
754 sp_3dbox_update_curves (SP3DBox *box) {
755 for (int i = 0; i < 6; ++i) {
756 if (box->faces[i]) box->faces[i]->set_curve();
757 }
758 }
760 /**
761 * In some situations (e.g., after cloning boxes, undo & redo, or reading boxes from a file) there are
762 * paths already present in the document which correspond to the faces of newly created boxes, but their
763 * 'path' members don't link to them yet. The following function corrects this if necessary.
764 */
765 void
766 sp_3dbox_link_to_existing_paths (SP3DBox *box, Inkscape::XML::Node *repr) {
767 // TODO: We should probably destroy the existing paths and recreate them because we don't know
768 // precisely which path corresponds to which face. Does this make a difference?
769 // In sp_3dbox_write we write the correct paths anyway, don't we? But we could get into
770 // trouble at a later stage when we only write single faces for degenerate boxes.
772 SPDocument *document = SP_OBJECT_DOCUMENT(box);
773 guint face_id = 0;
775 for (Inkscape::XML::Node *i = sp_repr_children(repr); i != NULL; i = sp_repr_next(i)) {
776 if (face_id > 5) {
777 g_warning ("SVG representation of 3D boxes must contain 6 paths or less.\n");
778 break;
779 }
781 SPObject *face_object = document->getObjectByRepr((Inkscape::XML::Node *) i);
782 if (!SP_IS_PATH(face_object)) {
783 g_warning ("SVG representation of 3D boxes should only contain paths.\n");
784 continue;
785 }
786 // TODO: Currently we don't check whether all paths are being linked to different faces.
787 // This is no problem with valid SVG files. It may lead to crashes, however,
788 // in case a file is corrupt (e.g., two or more faces have identical descriptions).
789 gint id = Box3DFace::descr_to_id (i->attribute ("inkscape:box3dface"));
790 box->faces[id]->hook_path_to_3dbox(SP_PATH(face_object));
791 ++face_id;
792 }
793 if (face_id < 6) {
794 //g_warning ("SVG representation of 3D boxes should contain exactly 6 paths (degenerate boxes are not yet supported).\n");
795 // TODO: Check whether it is safe to add the remaining paths to the box and do so in case it is.
796 // (But we also land here for newly created boxes where we shouldn't add any paths because
797 // This is done in sp_3dbox_write later on.
798 }
799 }
801 void
802 sp_3dbox_move_corner_in_XY_plane (SP3DBox *box, guint id, NR::Point pt, Box3D::Axis axes)
803 {
804 Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
806 NR::Point A (box->corners[id ^ Box3D::XY]);
807 if (Box3D::is_single_axis_direction (axes)) {
808 pt = Box3D::PerspectiveLine (box->corners[id], axes, persp).closest_to(pt);
809 }
811 /* set the 'front' corners */
812 box->corners[id] = pt;
814 Box3D::PerspectiveLine pl_one (A, Box3D::Y, persp);
815 Box3D::PerspectiveLine pl_two (pt, Box3D::X, persp);
816 box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
818 pl_one = Box3D::PerspectiveLine (A, Box3D::X, persp);
819 pl_two = Box3D::PerspectiveLine (pt, Box3D::Y, persp);
820 box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
822 /* set the 'rear' corners */
823 NR::Point B (box->corners[id ^ Box3D::XYZ]);
825 pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Z, persp);
826 pl_two = Box3D::PerspectiveLine (B, Box3D::Y, persp);
827 box->corners[id ^ Box3D::XZ] = pl_one.meet(pl_two);
829 pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XZ], Box3D::X, persp);
830 pl_two = Box3D::PerspectiveLine (pt, Box3D::Z, persp);
831 box->corners[id ^ Box3D::Z] = pl_one.meet(pl_two);
833 pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::Z], Box3D::Y, persp);
834 pl_two = Box3D::PerspectiveLine (B, Box3D::X, persp);
835 box->corners[id ^ Box3D::YZ] = pl_one.meet(pl_two);
837 }
839 void
840 sp_3dbox_move_corner_in_Z_direction (SP3DBox *box, guint id, NR::Point pt, bool constrained)
841 {
842 if (!constrained) sp_3dbox_move_corner_in_XY_plane (box, id, pt, Box3D::XY);
844 Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
846 /* set the four corners of the face containing corners[id] */
847 box->corners[id] = Box3D::PerspectiveLine (box->corners[id], Box3D::Z, persp).closest_to(pt);
849 Box3D::PerspectiveLine pl_one (box->corners[id], Box3D::X, persp);
850 Box3D::PerspectiveLine pl_two (box->corners[id ^ Box3D::XZ], Box3D::Z, persp);
851 box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
853 pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Y, persp);
854 pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XYZ], Box3D::Z, persp);
855 box->corners[id ^ Box3D::XY] = pl_one.meet(pl_two);
857 pl_one = Box3D::PerspectiveLine (box->corners[id], Box3D::Y, persp);
858 pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::YZ], Box3D::Z, persp);
859 box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
860 }
862 static void
863 sp_3dbox_reshape_edge_after_VP_toggling (SP3DBox *box, const guint corner, const Box3D::Axis axis, Box3D::Perspective3D *persp)
864 {
865 /* Hmm, perhaps we should simply use one of the corners as the pivot point.
866 But this way we minimize the amount of reshaping.
867 On second thought, we need to find a way to ensure that all boxes sharing the same
868 perspective are updated consistently _as a group_. That is, they should also retain
869 their relative positions towards each other. */
870 NR::Maybe<NR::Point> pt = sp_3dbox_get_midpoint_between_corners (box, corner, corner ^ axis);
871 g_return_if_fail (pt);
873 Box3D::Axis axis2 = ((axis == Box3D::Y) ? Box3D::X : Box3D::Y);
875 Box3D::PerspectiveLine line1 (box->corners[corner], axis2, persp);
876 Box3D::PerspectiveLine line2 (box->corners[corner ^ axis], axis2, persp);
878 Box3D::PerspectiveLine line3 (*pt, axis, persp);
880 NR::Point new_corner1 = line1.meet (line3);
881 NR::Point new_corner2 = line2.meet (line3);
883 box->corners[corner] = new_corner1;
884 box->corners[corner ^ axis] = new_corner2;
885 }
887 void
888 sp_3dbox_reshape_after_VP_toggling (SP3DBox *box, Box3D::Axis axis)
889 {
890 Box3D::Perspective3D *persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
891 std::pair<Box3D::Axis, Box3D::Axis> dirs = Box3D::get_remaining_axes (axis);
893 sp_3dbox_reshape_edge_after_VP_toggling (box, 0, axis, persp);
894 sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first, axis, persp);
895 sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first ^ dirs.second, axis, persp);
896 sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.second, axis, persp);
897 }
899 NR::Maybe<NR::Point>
900 sp_3dbox_get_center (SP3DBox *box)
901 {
902 return sp_3dbox_get_midpoint_between_corners (box, 0, 7);
903 }
905 NR::Point
906 sp_3dbox_get_midpoint_in_axis_direction (NR::Point const &C, NR::Point const &D, Box3D::Axis axis, Box3D::Perspective3D *persp)
907 {
908 Box3D::PerspectiveLine pl (D, axis, persp);
909 return pl.pt_with_given_cross_ratio (C, D, -1.0);
910 }
912 // TODO: The following function can probably be rewritten in a much more elegant and robust way
913 // by using projective coordinates for all points and using the cross ratio.
914 NR::Maybe<NR::Point>
915 sp_3dbox_get_midpoint_between_corners (SP3DBox *box, guint id_corner1, guint id_corner2)
916 {
917 Box3D::Axis corner_axes = (Box3D::Axis) (id_corner1 ^ id_corner2);
919 // Is all this sufficiently precise also for degenerate cases?
920 if (sp_3dbox_corners_are_adjacent (id_corner1, id_corner2)) {
921 Box3D::Axis orth_dir = get_perpendicular_axis_direction (corner_axes);
923 Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2 ^ orth_dir]);
924 Box3D::Line diag2 (box->corners[id_corner1 ^ orth_dir], box->corners[id_corner2]);
925 NR::Maybe<NR::Point> adjacent_face_center = diag1.intersect(diag2);
927 if (!adjacent_face_center) return NR::Nothing();
929 Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
931 Box3D::PerspectiveLine pl (*adjacent_face_center, orth_dir, persp);
932 return pl.intersect(Box3D::PerspectiveLine(box->corners[id_corner1], corner_axes, persp));
933 } else {
934 Box3D::Axis dir = Box3D::extract_first_axis_direction (corner_axes);
935 Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2]);
936 Box3D::Line diag2 (box->corners[id_corner1 ^ dir], box->corners[id_corner2 ^ dir]);
937 return diag1.intersect(diag2);
938 }
939 }
941 static gchar *
942 sp_3dbox_get_corner_coords_string (SP3DBox *box, guint id)
943 {
944 id = id % 8;
945 Inkscape::SVGOStringStream os;
946 os << box->corners[id][NR::X] << "," << box->corners[id][NR::Y];
947 return g_strdup(os.str().c_str());
948 }
950 static std::pair<gdouble, gdouble>
951 sp_3dbox_get_coord_pair_from_string (const gchar *coords)
952 {
953 gchar **coordpair = g_strsplit( coords, ",", 0);
954 // We might as well rely on g_ascii_strtod to convert the NULL pointer to 0.0,
955 // but we include the following test anyway
956 if (coordpair[0] == NULL || coordpair[1] == NULL) {
957 g_strfreev (coordpair);
958 g_warning ("Coordinate conversion failed.\n");
959 return std::make_pair(0.0, 0.0);
960 }
962 gdouble coord1 = g_ascii_strtod(coordpair[0], NULL);
963 gdouble coord2 = g_ascii_strtod(coordpair[1], NULL);
964 g_strfreev (coordpair);
966 return std::make_pair(coord1, coord2);
967 }
969 static gchar *
970 sp_3dbox_get_perspective_string (SP3DBox *box)
971 {
973 return sp_3dbox_get_svg_descr_of_persp (SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box));
974 }
976 gchar *
977 sp_3dbox_get_svg_descr_of_persp (Box3D::Perspective3D *persp)
978 {
979 // FIXME: We should move this code to perspective3d.cpp, but this yields compiler errors. Why?
980 Inkscape::SVGOStringStream os;
982 Box3D::VanishingPoint vp = *(persp->get_vanishing_point (Box3D::X));
983 os << vp[NR::X] << "," << vp[NR::Y] << ",";
984 os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
985 if (vp.is_finite()) {
986 os << "finite,";
987 } else {
988 os << "infinite,";
989 }
991 vp = *(persp->get_vanishing_point (Box3D::Y));
992 os << vp[NR::X] << "," << vp[NR::Y] << ",";
993 os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
994 if (vp.is_finite()) {
995 os << "finite,";
996 } else {
997 os << "infinite,";
998 }
1000 vp = *(persp->get_vanishing_point (Box3D::Z));
1001 os << vp[NR::X] << "," << vp[NR::Y] << ",";
1002 os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
1003 if (vp.is_finite()) {
1004 os << "finite";
1005 } else {
1006 os << "infinite";
1007 }
1009 return g_strdup(os.str().c_str());
1010 }
1012 // auxiliary function
1013 static std::pair<NR::Point, NR::Point>
1014 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)
1015 {
1016 double cr1 = Box3D::cross_ratio (*persp->get_vanishing_point (axis), M0, M, A);
1017 double cr2 = Box3D::cross_ratio (*persp->get_vanishing_point (axis), M, B, M0);
1018 if (fabs (cr1 - 1) < Box3D::epsilon) {
1019 // FIXME: cr == 1 is a degenerate case; how should we deal with it?
1020 return std::make_pair (NR::Point (0,0), NR::Point (0,0));
1021 }
1022 if (cr1 == NR_HUGE) {
1023 return std::make_pair (A, B);
1024 }
1025 Box3D::PerspectiveLine pl (M0, axis, persp);
1026 NR::Point B_new = pl.pt_with_given_cross_ratio (M0, M, cr1 / (cr1 - 1));
1027 NR::Point A_new = pl.pt_with_given_cross_ratio (M0, M, 1 - cr2);
1028 return std::make_pair (A_new, B_new);
1029 }
1031 void sp_3dbox_recompute_Z_corners_from_new_center (SP3DBox *box, NR::Point const new_center)
1032 {
1033 // TODO: Clean this function up
1035 Box3D::Perspective3D *persp = sp_desktop_document (inkscape_active_desktop())->get_persp_of_box (box);
1036 NR::Point old_center = box->old_center;
1038 Box3D::PerspectiveLine aux_line1 (old_center, Box3D::Z, persp);
1039 Box3D::PerspectiveLine aux_line2 (new_center, Box3D::Y, persp);
1040 NR::Point Z1 = aux_line1.meet (aux_line2);
1042 NR::Point A0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner2, box->old_corner0, Box3D::Y, persp));
1043 NR::Point B0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner7, box->old_corner5, Box3D::Y, persp));
1044 Box3D::PerspectiveLine aux_line3 (A0, Box3D::X, persp);
1045 Box3D::PerspectiveLine aux_line4 (B0, Box3D::X, persp);
1047 NR::Point C0 = aux_line3.meet (aux_line1);
1048 NR::Point D0 = aux_line4.meet (aux_line1);
1050 std::pair<NR::Point, NR::Point> new_midpts = sp_3dbox_new_midpoints (persp, Box3D::Z, old_center, Z1, C0, D0);
1051 NR::Point C1 (new_midpts.first);
1052 NR::Point D1 (new_midpts.second);
1053 Box3D::PerspectiveLine aux_line5 (C1, Box3D::X, persp);
1054 Box3D::PerspectiveLine aux_line6 (D1, Box3D::X, persp);
1056 Box3D::PerspectiveLine aux_line7 (A0, Box3D::Z, persp);
1057 Box3D::PerspectiveLine aux_line8 (B0, Box3D::Z, persp);
1059 NR::Point A1 = aux_line5.meet (aux_line7);
1060 NR::Point B1 = aux_line6.meet (aux_line8);
1062 Box3D::PerspectiveLine aux_line9 (box->old_corner2, Box3D::Z, persp);
1063 Box3D::PerspectiveLine aux_line10 (box->old_corner5, Box3D::Z, persp);
1065 Box3D::PerspectiveLine aux_line11 (A1, Box3D::Y, persp);
1066 Box3D::PerspectiveLine aux_line12 (B1, Box3D::Y, persp);
1068 NR::Point new_corner2 = aux_line9.meet (aux_line11);
1069 NR::Point new_corner5 = aux_line10.meet (aux_line12);
1071 Box3D::PerspectiveLine aux_line13 (A1, Box3D::X, persp);
1072 NR::Point E1 = aux_line13.meet (aux_line8);
1073 Box3D::PerspectiveLine aux_line14 (E1, Box3D::Y, persp);
1075 NR::Point new_corner1 = aux_line10.meet (aux_line14);
1077 sp_3dbox_set_shape_from_points (box, new_corner2, new_corner1, new_corner5);
1078 }
1080 void sp_3dbox_recompute_XY_corners_from_new_center (SP3DBox *box, NR::Point const new_center)
1081 {
1082 // TODO: Clean this function up
1084 Box3D::Perspective3D *persp = sp_desktop_document (inkscape_active_desktop())->get_persp_of_box (box);
1085 NR::Point old_center = box->old_center;
1087 NR::Point A0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner2, box->old_corner0, Box3D::Y, persp));
1088 NR::Point B0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner1, box->old_corner3, Box3D::Y, persp));
1090 /* we first move the box along the X-axis ... */
1091 Box3D::PerspectiveLine aux_line1 (old_center, Box3D::X, persp);
1092 Box3D::PerspectiveLine aux_line2 (new_center, Box3D::Y, persp);
1093 NR::Point Z1 = aux_line1.meet (aux_line2);
1095 Box3D::PerspectiveLine ref_line (B0, Box3D::X, persp);
1096 Box3D::PerspectiveLine pline2 (old_center, Box3D::Z, persp);
1097 Box3D::PerspectiveLine pline3 (Z1, Box3D::Z, persp);
1098 NR::Point M0 = ref_line.meet (pline2);
1099 NR::Point M1 = ref_line.meet (pline3);
1101 std::pair<NR::Point, NR::Point> new_midpts = sp_3dbox_new_midpoints (persp, Box3D::X, M0, M1, A0, B0);
1102 NR::Point A1 (new_midpts.first);
1103 NR::Point B1 (new_midpts.second);
1105 /* ... and then along the Y-axis */
1106 Box3D::PerspectiveLine pline4 (box->old_corner1, Box3D::X, persp);
1107 Box3D::PerspectiveLine pline5 (box->old_corner3, Box3D::X, persp);
1108 Box3D::PerspectiveLine aux_line3 (M1, Box3D::Y, persp);
1109 NR::Point C1 = aux_line3.meet (pline4);
1110 NR::Point D1 = aux_line3.meet (pline5);
1112 Box3D::PerspectiveLine aux_line4 (new_center, Box3D::Z, persp);
1113 NR::Point M2 = aux_line4.meet (aux_line3);
1115 std::pair<NR::Point, NR::Point> other_new_midpts = sp_3dbox_new_midpoints (persp, Box3D::Y, M1, M2, C1, D1);
1116 NR::Point C2 (other_new_midpts.first);
1117 NR::Point D2 (other_new_midpts.second);
1119 Box3D::PerspectiveLine plXC (C2, Box3D::X, persp);
1120 Box3D::PerspectiveLine plXD (D2, Box3D::X, persp);
1121 Box3D::PerspectiveLine plYA (A1, Box3D::Y, persp);
1122 Box3D::PerspectiveLine plYB (B1, Box3D::Y, persp);
1124 NR::Point new_corner2 (plXD.meet (plYA));
1125 NR::Point new_corner1 (plXC.meet (plYB));
1127 NR::Point tmp_corner1 (pline4.meet (plYB));
1128 Box3D::PerspectiveLine pline6 (box->old_corner5, Box3D::X, persp);
1129 Box3D::PerspectiveLine pline7 (tmp_corner1, Box3D::Z, persp);
1130 NR::Point tmp_corner5 (pline6.meet (pline7));
1132 Box3D::PerspectiveLine pline8 (tmp_corner5, Box3D::Y, persp);
1133 Box3D::PerspectiveLine pline9 (new_corner1, Box3D::Z, persp);
1134 NR::Point new_corner5 (pline8.meet (pline9));
1136 sp_3dbox_set_shape_from_points (box, new_corner2, new_corner1, new_corner5);
1137 }
1139 void sp_3dbox_update_perspective_lines()
1140 {
1141 SPEventContext *ec = inkscape_active_event_context();
1142 if (!SP_IS_3DBOX_CONTEXT (ec))
1143 return;
1145 SP_3DBOX_CONTEXT (ec)->_vpdrag->updateLines();
1146 }
1148 /*
1149 * Manipulates corner1 through corner4 to contain the indices of the corners
1150 * from which the perspective lines in the direction of 'axis' emerge
1151 */
1152 void sp_3dbox_corners_for_perspective_lines (const SP3DBox * box, Box3D::Axis axis,
1153 NR::Point &corner1, NR::Point &corner2, NR::Point &corner3, NR::Point &corner4)
1154 {
1155 // along which axis to switch when takint
1156 Box3D::Axis switch_axis;
1157 if (axis == Box3D::X || axis == Box3D::Y) {
1158 switch_axis = (box->front_bits & axis) ? Box3D::Z : Box3D::NONE;
1159 } else {
1160 switch_axis = (box->front_bits & axis) ? Box3D::X : Box3D::NONE;
1161 }
1163 switch (axis) {
1164 case Box3D::X:
1165 corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
1166 corner2 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
1167 corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
1168 corner4 = sp_3dbox_get_corner_along_edge (box, 6 ^ switch_axis, axis, Box3D::REAR);
1169 break;
1170 case Box3D::Y:
1171 corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
1172 corner2 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
1173 corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
1174 corner4 = sp_3dbox_get_corner_along_edge (box, 5 ^ switch_axis, axis, Box3D::REAR);
1175 break;
1176 case Box3D::Z:
1177 corner1 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
1178 corner2 = sp_3dbox_get_corner_along_edge (box, 3 ^ switch_axis, axis, Box3D::REAR);
1179 corner3 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
1180 corner4 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
1181 break;
1182 default:
1183 // do nothing
1184 break;
1185 }
1186 }
1188 /**
1189 * Returns the id of the corner on the edge along 'axis' and passing through 'corner' that
1190 * lies on the front/rear face in this direction.
1191 */
1192 guint
1193 sp_3dbox_get_corner_id_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
1194 {
1195 guint result;
1196 guint other_corner = corner ^ axis;
1197 Box3D::VanishingPoint *vp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point(axis);
1198 if (vp->is_finite()) {
1199 result = ( NR::L2 (vp->get_pos() - box->corners[corner])
1200 < NR::L2 (vp->get_pos() - box->corners[other_corner]) ? other_corner : corner);
1201 } else {
1202 // clear the axis bit and switch to the appropriate corner along axis, depending on the value of front_bits
1203 result = ((corner & (0xF ^ axis)) ^ (box->front_bits & axis));
1204 }
1206 if (rel_pos == Box3D::FRONT) {
1207 return result;
1208 } else {
1209 return result ^ axis;
1210 }
1211 }
1213 NR::Point
1214 sp_3dbox_get_corner_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
1215 {
1216 return box->corners[sp_3dbox_get_corner_id_along_edge (box, corner, axis, rel_pos)];
1217 }
1219 guint
1220 sp_3dbox_get_front_corner_id (const SP3DBox *box)
1221 {
1222 guint front_corner = 1; // this could in fact be any corner, but we choose the one that is normally in front
1223 front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::X, Box3D::FRONT);
1224 front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Y, Box3D::FRONT);
1225 front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Z, Box3D::FRONT);
1226 return front_corner;
1227 }
1229 // auxiliary functions
1230 static void
1231 sp_3dbox_update_corner_with_value_from_svg (SPObject *object, guint corner_id, const gchar *value)
1232 {
1233 if (value == NULL) return;
1234 SP3DBox *box = SP_3DBOX(object);
1236 std::pair<gdouble, gdouble> coord_pair = sp_3dbox_get_coord_pair_from_string (value);
1237 box->corners[corner_id] = NR::Point (coord_pair.first, coord_pair.second);
1238 sp_3dbox_recompute_corners (box, box->corners[2], box->corners[1], box->corners[5]);
1239 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1240 }
1242 static void
1243 sp_3dbox_update_perspective (Box3D::Perspective3D *persp, const gchar *value)
1244 {
1245 // WARNING! This function changes the perspective associated to 'box'. Since there may be
1246 // many other boxes linked to the same perspective, their perspective is also changed.
1247 // If this behaviour is not desired in all cases, we need a different function.
1248 if (value == NULL) return;
1250 gchar **vps = g_strsplit( value, ",", 0);
1251 for (int i = 0; i < 15; ++i) {
1252 if (vps[i] == NULL) {
1253 g_warning ("Malformed svg attribute 'perspective'\n");
1254 return;
1255 }
1256 }
1258 persp->set_vanishing_point (Box3D::X, g_ascii_strtod (vps[0], NULL), g_ascii_strtod (vps[1], NULL),
1259 g_ascii_strtod (vps[2], NULL), g_ascii_strtod (vps[3], NULL),
1260 strcmp (vps[4], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
1261 persp->set_vanishing_point (Box3D::Y, g_ascii_strtod (vps[5], NULL), g_ascii_strtod (vps[6], NULL),
1262 g_ascii_strtod (vps[7], NULL), g_ascii_strtod (vps[8], NULL),
1263 strcmp (vps[9], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
1264 persp->set_vanishing_point (Box3D::Z, g_ascii_strtod (vps[10], NULL), g_ascii_strtod (vps[11], NULL),
1265 g_ascii_strtod (vps[12], NULL), g_ascii_strtod (vps[13], NULL),
1266 strcmp (vps[14], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
1268 // update the other boxes linked to the same perspective
1269 persp->reshape_boxes (Box3D::XYZ);
1270 }
1272 /*
1273 Local Variables:
1274 mode:c++
1275 c-file-style:"stroustrup"
1276 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1277 indent-tabs-mode:nil
1278 fill-column:99
1279 End:
1280 */
1281 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :