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