1942ea3d66494438522e0eebb575b3f4cf37282e
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_reshape_after_VP_rotation (SP3DBox *box, Box3D::Axis axis)
803 {
804 Box3D::Perspective3D *persp = inkscape_active_document()->get_persp_of_box (box);
805 Box3D::VanishingPoint *vp = persp->get_vanishing_point (axis);
807 guint c1 = (axis == Box3D::Z) ? 1 : sp_3dbox_get_front_corner_id (box); // hack
808 guint c2 = c1 ^ axis;
809 NR::Point v = box->corners[c1] - box->corners[c2];
810 double dist = NR::L2 (v) * ((NR::dot (v, vp->v_dir) < 0) ? 1 : -1); // "directed" distance
812 Box3D::PerspectiveLine pline (box->corners[c1], axis, persp);
813 NR::Point pt = pline.point_from_lambda (dist);
815 sp_3dbox_move_corner_in_Z_direction (box, c2, pt, axis == Box3D::Z);
816 }
818 void
819 sp_3dbox_move_corner_in_XY_plane (SP3DBox *box, guint id, NR::Point pt, Box3D::Axis axes)
820 {
821 Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
823 NR::Point A (box->corners[id ^ Box3D::XY]);
824 if (Box3D::is_single_axis_direction (axes)) {
825 pt = Box3D::PerspectiveLine (box->corners[id], axes, persp).closest_to(pt);
826 }
828 /* set the 'front' corners */
829 box->corners[id] = pt;
831 Box3D::PerspectiveLine pl_one (A, Box3D::Y, persp);
832 Box3D::PerspectiveLine pl_two (pt, Box3D::X, persp);
833 box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
835 pl_one = Box3D::PerspectiveLine (A, Box3D::X, persp);
836 pl_two = Box3D::PerspectiveLine (pt, Box3D::Y, persp);
837 box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
839 /* set the 'rear' corners */
840 NR::Point B (box->corners[id ^ Box3D::XYZ]);
842 pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Z, persp);
843 pl_two = Box3D::PerspectiveLine (B, Box3D::Y, persp);
844 box->corners[id ^ Box3D::XZ] = pl_one.meet(pl_two);
846 pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XZ], Box3D::X, persp);
847 pl_two = Box3D::PerspectiveLine (pt, Box3D::Z, persp);
848 box->corners[id ^ Box3D::Z] = pl_one.meet(pl_two);
850 pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::Z], Box3D::Y, persp);
851 pl_two = Box3D::PerspectiveLine (B, Box3D::X, persp);
852 box->corners[id ^ Box3D::YZ] = pl_one.meet(pl_two);
854 }
856 void
857 sp_3dbox_move_corner_in_Z_direction (SP3DBox *box, guint id, NR::Point pt, bool constrained)
858 {
859 if (!constrained) sp_3dbox_move_corner_in_XY_plane (box, id, pt, Box3D::XY);
861 Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
863 /* set the four corners of the face containing corners[id] */
864 box->corners[id] = Box3D::PerspectiveLine (box->corners[id], Box3D::Z, persp).closest_to(pt);
866 Box3D::PerspectiveLine pl_one (box->corners[id], Box3D::X, persp);
867 Box3D::PerspectiveLine pl_two (box->corners[id ^ Box3D::XZ], Box3D::Z, persp);
868 box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
870 pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Y, persp);
871 pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XYZ], Box3D::Z, persp);
872 box->corners[id ^ Box3D::XY] = pl_one.meet(pl_two);
874 pl_one = Box3D::PerspectiveLine (box->corners[id], Box3D::Y, persp);
875 pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::YZ], Box3D::Z, persp);
876 box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
877 }
879 static void
880 sp_3dbox_reshape_edge_after_VP_toggling (SP3DBox *box, const guint corner, const Box3D::Axis axis, Box3D::Perspective3D *persp)
881 {
882 /* Hmm, perhaps we should simply use one of the corners as the pivot point.
883 But this way we minimize the amount of reshaping.
884 On second thought, we need to find a way to ensure that all boxes sharing the same
885 perspective are updated consistently _as a group_. That is, they should also retain
886 their relative positions towards each other. */
887 NR::Maybe<NR::Point> pt = sp_3dbox_get_midpoint_between_corners (box, corner, corner ^ axis);
888 g_return_if_fail (pt);
890 Box3D::Axis axis2 = ((axis == Box3D::Y) ? Box3D::X : Box3D::Y);
892 Box3D::PerspectiveLine line1 (box->corners[corner], axis2, persp);
893 Box3D::PerspectiveLine line2 (box->corners[corner ^ axis], axis2, persp);
895 Box3D::PerspectiveLine line3 (*pt, axis, persp);
897 NR::Point new_corner1 = line1.meet (line3);
898 NR::Point new_corner2 = line2.meet (line3);
900 box->corners[corner] = new_corner1;
901 box->corners[corner ^ axis] = new_corner2;
902 }
904 void
905 sp_3dbox_reshape_after_VP_toggling (SP3DBox *box, Box3D::Axis axis)
906 {
907 Box3D::Perspective3D *persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
908 std::pair<Box3D::Axis, Box3D::Axis> dirs = Box3D::get_remaining_axes (axis);
910 sp_3dbox_reshape_edge_after_VP_toggling (box, 0, axis, persp);
911 sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first, axis, persp);
912 sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first ^ dirs.second, axis, persp);
913 sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.second, axis, persp);
914 }
916 NR::Maybe<NR::Point>
917 sp_3dbox_get_center (SP3DBox *box)
918 {
919 return sp_3dbox_get_midpoint_between_corners (box, 0, 7);
920 }
922 NR::Point
923 sp_3dbox_get_midpoint_in_axis_direction (NR::Point const &C, NR::Point const &D, Box3D::Axis axis, Box3D::Perspective3D *persp)
924 {
925 Box3D::PerspectiveLine pl (D, axis, persp);
926 return pl.pt_with_given_cross_ratio (C, D, -1.0);
927 }
929 // TODO: The following function can probably be rewritten in a much more elegant and robust way
930 // by using projective coordinates for all points and using the cross ratio.
931 NR::Maybe<NR::Point>
932 sp_3dbox_get_midpoint_between_corners (SP3DBox *box, guint id_corner1, guint id_corner2)
933 {
934 Box3D::Axis corner_axes = (Box3D::Axis) (id_corner1 ^ id_corner2);
936 // Is all this sufficiently precise also for degenerate cases?
937 if (sp_3dbox_corners_are_adjacent (id_corner1, id_corner2)) {
938 Box3D::Axis orth_dir = get_perpendicular_axis_direction (corner_axes);
940 Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2 ^ orth_dir]);
941 Box3D::Line diag2 (box->corners[id_corner1 ^ orth_dir], box->corners[id_corner2]);
942 NR::Maybe<NR::Point> adjacent_face_center = diag1.intersect(diag2);
944 if (!adjacent_face_center) return NR::Nothing();
946 Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
948 Box3D::PerspectiveLine pl (*adjacent_face_center, orth_dir, persp);
949 return pl.intersect(Box3D::PerspectiveLine(box->corners[id_corner1], corner_axes, persp));
950 } else {
951 Box3D::Axis dir = Box3D::extract_first_axis_direction (corner_axes);
952 Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2]);
953 Box3D::Line diag2 (box->corners[id_corner1 ^ dir], box->corners[id_corner2 ^ dir]);
954 return diag1.intersect(diag2);
955 }
956 }
958 static gchar *
959 sp_3dbox_get_corner_coords_string (SP3DBox *box, guint id)
960 {
961 id = id % 8;
962 Inkscape::SVGOStringStream os;
963 os << box->corners[id][NR::X] << "," << box->corners[id][NR::Y];
964 return g_strdup(os.str().c_str());
965 }
967 static std::pair<gdouble, gdouble>
968 sp_3dbox_get_coord_pair_from_string (const gchar *coords)
969 {
970 gchar **coordpair = g_strsplit( coords, ",", 0);
971 // We might as well rely on g_ascii_strtod to convert the NULL pointer to 0.0,
972 // but we include the following test anyway
973 if (coordpair[0] == NULL || coordpair[1] == NULL) {
974 g_strfreev (coordpair);
975 g_warning ("Coordinate conversion failed.\n");
976 return std::make_pair(0.0, 0.0);
977 }
979 gdouble coord1 = g_ascii_strtod(coordpair[0], NULL);
980 gdouble coord2 = g_ascii_strtod(coordpair[1], NULL);
981 g_strfreev (coordpair);
983 return std::make_pair(coord1, coord2);
984 }
986 static gchar *
987 sp_3dbox_get_perspective_string (SP3DBox *box)
988 {
990 return sp_3dbox_get_svg_descr_of_persp (SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box));
991 }
993 gchar *
994 sp_3dbox_get_svg_descr_of_persp (Box3D::Perspective3D *persp)
995 {
996 // FIXME: We should move this code to perspective3d.cpp, but this yields compiler errors. Why?
997 Inkscape::SVGOStringStream os;
999 Box3D::VanishingPoint vp = *(persp->get_vanishing_point (Box3D::X));
1000 os << vp[NR::X] << "," << vp[NR::Y] << ",";
1001 os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
1002 if (vp.is_finite()) {
1003 os << "finite,";
1004 } else {
1005 os << "infinite,";
1006 }
1008 vp = *(persp->get_vanishing_point (Box3D::Y));
1009 os << vp[NR::X] << "," << vp[NR::Y] << ",";
1010 os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
1011 if (vp.is_finite()) {
1012 os << "finite,";
1013 } else {
1014 os << "infinite,";
1015 }
1017 vp = *(persp->get_vanishing_point (Box3D::Z));
1018 os << vp[NR::X] << "," << vp[NR::Y] << ",";
1019 os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
1020 if (vp.is_finite()) {
1021 os << "finite";
1022 } else {
1023 os << "infinite";
1024 }
1026 return g_strdup(os.str().c_str());
1027 }
1029 // auxiliary function
1030 static std::pair<NR::Point, NR::Point>
1031 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)
1032 {
1033 double cr1 = Box3D::cross_ratio (*persp->get_vanishing_point (axis), M0, M, A);
1034 double cr2 = Box3D::cross_ratio (*persp->get_vanishing_point (axis), M, B, M0);
1035 if (fabs (cr1 - 1) < Box3D::epsilon) {
1036 // FIXME: cr == 1 is a degenerate case; how should we deal with it?
1037 return std::make_pair (NR::Point (0,0), NR::Point (0,0));
1038 }
1039 if (cr1 == NR_HUGE) {
1040 return std::make_pair (A, B);
1041 }
1042 Box3D::PerspectiveLine pl (M0, axis, persp);
1043 NR::Point B_new = pl.pt_with_given_cross_ratio (M0, M, cr1 / (cr1 - 1));
1044 NR::Point A_new = pl.pt_with_given_cross_ratio (M0, M, 1 - cr2);
1045 return std::make_pair (A_new, B_new);
1046 }
1048 void sp_3dbox_recompute_Z_corners_from_new_center (SP3DBox *box, NR::Point const new_center)
1049 {
1050 // TODO: Clean this function up
1052 Box3D::Perspective3D *persp = sp_desktop_document (inkscape_active_desktop())->get_persp_of_box (box);
1053 NR::Point old_center = box->old_center;
1055 Box3D::PerspectiveLine aux_line1 (old_center, Box3D::Z, persp);
1056 Box3D::PerspectiveLine aux_line2 (new_center, Box3D::Y, persp);
1057 NR::Point Z1 = aux_line1.meet (aux_line2);
1059 NR::Point A0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner2, box->old_corner0, Box3D::Y, persp));
1060 NR::Point B0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner7, box->old_corner5, Box3D::Y, persp));
1061 Box3D::PerspectiveLine aux_line3 (A0, Box3D::X, persp);
1062 Box3D::PerspectiveLine aux_line4 (B0, Box3D::X, persp);
1064 NR::Point C0 = aux_line3.meet (aux_line1);
1065 NR::Point D0 = aux_line4.meet (aux_line1);
1067 std::pair<NR::Point, NR::Point> new_midpts = sp_3dbox_new_midpoints (persp, Box3D::Z, old_center, Z1, C0, D0);
1068 NR::Point C1 (new_midpts.first);
1069 NR::Point D1 (new_midpts.second);
1070 Box3D::PerspectiveLine aux_line5 (C1, Box3D::X, persp);
1071 Box3D::PerspectiveLine aux_line6 (D1, Box3D::X, persp);
1073 Box3D::PerspectiveLine aux_line7 (A0, Box3D::Z, persp);
1074 Box3D::PerspectiveLine aux_line8 (B0, Box3D::Z, persp);
1076 NR::Point A1 = aux_line5.meet (aux_line7);
1077 NR::Point B1 = aux_line6.meet (aux_line8);
1079 Box3D::PerspectiveLine aux_line9 (box->old_corner2, Box3D::Z, persp);
1080 Box3D::PerspectiveLine aux_line10 (box->old_corner5, Box3D::Z, persp);
1082 Box3D::PerspectiveLine aux_line11 (A1, Box3D::Y, persp);
1083 Box3D::PerspectiveLine aux_line12 (B1, Box3D::Y, persp);
1085 NR::Point new_corner2 = aux_line9.meet (aux_line11);
1086 NR::Point new_corner5 = aux_line10.meet (aux_line12);
1088 Box3D::PerspectiveLine aux_line13 (A1, Box3D::X, persp);
1089 NR::Point E1 = aux_line13.meet (aux_line8);
1090 Box3D::PerspectiveLine aux_line14 (E1, Box3D::Y, persp);
1092 NR::Point new_corner1 = aux_line10.meet (aux_line14);
1094 sp_3dbox_set_shape_from_points (box, new_corner2, new_corner1, new_corner5);
1095 }
1097 void sp_3dbox_recompute_XY_corners_from_new_center (SP3DBox *box, NR::Point const new_center)
1098 {
1099 // TODO: Clean this function up
1101 Box3D::Perspective3D *persp = sp_desktop_document (inkscape_active_desktop())->get_persp_of_box (box);
1102 NR::Point old_center = box->old_center;
1104 NR::Point A0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner2, box->old_corner0, Box3D::Y, persp));
1105 NR::Point B0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner1, box->old_corner3, Box3D::Y, persp));
1107 /* we first move the box along the X-axis ... */
1108 Box3D::PerspectiveLine aux_line1 (old_center, Box3D::X, persp);
1109 Box3D::PerspectiveLine aux_line2 (new_center, Box3D::Y, persp);
1110 NR::Point Z1 = aux_line1.meet (aux_line2);
1112 Box3D::PerspectiveLine ref_line (B0, Box3D::X, persp);
1113 Box3D::PerspectiveLine pline2 (old_center, Box3D::Z, persp);
1114 Box3D::PerspectiveLine pline3 (Z1, Box3D::Z, persp);
1115 NR::Point M0 = ref_line.meet (pline2);
1116 NR::Point M1 = ref_line.meet (pline3);
1118 std::pair<NR::Point, NR::Point> new_midpts = sp_3dbox_new_midpoints (persp, Box3D::X, M0, M1, A0, B0);
1119 NR::Point A1 (new_midpts.first);
1120 NR::Point B1 (new_midpts.second);
1122 /* ... and then along the Y-axis */
1123 Box3D::PerspectiveLine pline4 (box->old_corner1, Box3D::X, persp);
1124 Box3D::PerspectiveLine pline5 (box->old_corner3, Box3D::X, persp);
1125 Box3D::PerspectiveLine aux_line3 (M1, Box3D::Y, persp);
1126 NR::Point C1 = aux_line3.meet (pline4);
1127 NR::Point D1 = aux_line3.meet (pline5);
1129 Box3D::PerspectiveLine aux_line4 (new_center, Box3D::Z, persp);
1130 NR::Point M2 = aux_line4.meet (aux_line3);
1132 std::pair<NR::Point, NR::Point> other_new_midpts = sp_3dbox_new_midpoints (persp, Box3D::Y, M1, M2, C1, D1);
1133 NR::Point C2 (other_new_midpts.first);
1134 NR::Point D2 (other_new_midpts.second);
1136 Box3D::PerspectiveLine plXC (C2, Box3D::X, persp);
1137 Box3D::PerspectiveLine plXD (D2, Box3D::X, persp);
1138 Box3D::PerspectiveLine plYA (A1, Box3D::Y, persp);
1139 Box3D::PerspectiveLine plYB (B1, Box3D::Y, persp);
1141 NR::Point new_corner2 (plXD.meet (plYA));
1142 NR::Point new_corner1 (plXC.meet (plYB));
1144 NR::Point tmp_corner1 (pline4.meet (plYB));
1145 Box3D::PerspectiveLine pline6 (box->old_corner5, Box3D::X, persp);
1146 Box3D::PerspectiveLine pline7 (tmp_corner1, Box3D::Z, persp);
1147 NR::Point tmp_corner5 (pline6.meet (pline7));
1149 Box3D::PerspectiveLine pline8 (tmp_corner5, Box3D::Y, persp);
1150 Box3D::PerspectiveLine pline9 (new_corner1, Box3D::Z, persp);
1151 NR::Point new_corner5 (pline8.meet (pline9));
1153 sp_3dbox_set_shape_from_points (box, new_corner2, new_corner1, new_corner5);
1154 }
1156 void sp_3dbox_update_perspective_lines()
1157 {
1158 SPEventContext *ec = inkscape_active_event_context();
1159 if (!SP_IS_3DBOX_CONTEXT (ec))
1160 return;
1162 SP_3DBOX_CONTEXT (ec)->_vpdrag->updateLines();
1163 }
1165 /*
1166 * Manipulates corner1 through corner4 to contain the indices of the corners
1167 * from which the perspective lines in the direction of 'axis' emerge
1168 */
1169 void sp_3dbox_corners_for_perspective_lines (const SP3DBox * box, Box3D::Axis axis,
1170 NR::Point &corner1, NR::Point &corner2, NR::Point &corner3, NR::Point &corner4)
1171 {
1172 // along which axis to switch when takint
1173 Box3D::Axis switch_axis;
1174 if (axis == Box3D::X || axis == Box3D::Y) {
1175 switch_axis = (box->front_bits & axis) ? Box3D::Z : Box3D::NONE;
1176 } else {
1177 switch_axis = (box->front_bits & axis) ? Box3D::X : Box3D::NONE;
1178 }
1180 switch (axis) {
1181 case Box3D::X:
1182 corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
1183 corner2 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
1184 corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
1185 corner4 = sp_3dbox_get_corner_along_edge (box, 6 ^ switch_axis, axis, Box3D::REAR);
1186 break;
1187 case Box3D::Y:
1188 corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
1189 corner2 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
1190 corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
1191 corner4 = sp_3dbox_get_corner_along_edge (box, 5 ^ switch_axis, axis, Box3D::REAR);
1192 break;
1193 case Box3D::Z:
1194 corner1 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
1195 corner2 = sp_3dbox_get_corner_along_edge (box, 3 ^ switch_axis, axis, Box3D::REAR);
1196 corner3 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
1197 corner4 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
1198 break;
1199 default:
1200 // do nothing
1201 break;
1202 }
1203 }
1205 /**
1206 * Returns the id of the corner on the edge along 'axis' and passing through 'corner' that
1207 * lies on the front/rear face in this direction.
1208 */
1209 guint
1210 sp_3dbox_get_corner_id_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
1211 {
1212 guint result;
1213 guint other_corner = corner ^ axis;
1214 Box3D::VanishingPoint *vp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point(axis);
1215 if (vp->is_finite()) {
1216 result = ( NR::L2 (vp->get_pos() - box->corners[corner])
1217 < NR::L2 (vp->get_pos() - box->corners[other_corner]) ? other_corner : corner);
1218 } else {
1219 // clear the axis bit and switch to the appropriate corner along axis, depending on the value of front_bits
1220 result = ((corner & (0xF ^ axis)) ^ (box->front_bits & axis));
1221 }
1223 if (rel_pos == Box3D::FRONT) {
1224 return result;
1225 } else {
1226 return result ^ axis;
1227 }
1228 }
1230 NR::Point
1231 sp_3dbox_get_corner_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
1232 {
1233 return box->corners[sp_3dbox_get_corner_id_along_edge (box, corner, axis, rel_pos)];
1234 }
1236 guint
1237 sp_3dbox_get_front_corner_id (const SP3DBox *box)
1238 {
1239 guint front_corner = 1; // this could in fact be any corner, but we choose the one that is normally in front
1240 front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::X, Box3D::FRONT);
1241 front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Y, Box3D::FRONT);
1242 front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Z, Box3D::FRONT);
1243 return front_corner;
1244 }
1246 // auxiliary functions
1247 static void
1248 sp_3dbox_update_corner_with_value_from_svg (SPObject *object, guint corner_id, const gchar *value)
1249 {
1250 if (value == NULL) return;
1251 SP3DBox *box = SP_3DBOX(object);
1253 std::pair<gdouble, gdouble> coord_pair = sp_3dbox_get_coord_pair_from_string (value);
1254 box->corners[corner_id] = NR::Point (coord_pair.first, coord_pair.second);
1255 sp_3dbox_recompute_corners (box, box->corners[2], box->corners[1], box->corners[5]);
1256 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1257 }
1259 static void
1260 sp_3dbox_update_perspective (Box3D::Perspective3D *persp, const gchar *value)
1261 {
1262 // WARNING! This function changes the perspective associated to 'box'. Since there may be
1263 // many other boxes linked to the same perspective, their perspective is also changed.
1264 // If this behaviour is not desired in all cases, we need a different function.
1265 if (value == NULL) return;
1267 gchar **vps = g_strsplit( value, ",", 0);
1268 for (int i = 0; i < 15; ++i) {
1269 if (vps[i] == NULL) {
1270 g_warning ("Malformed svg attribute 'perspective'\n");
1271 return;
1272 }
1273 }
1275 persp->set_vanishing_point (Box3D::X, g_ascii_strtod (vps[0], NULL), g_ascii_strtod (vps[1], NULL),
1276 g_ascii_strtod (vps[2], NULL), g_ascii_strtod (vps[3], NULL),
1277 strcmp (vps[4], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
1278 persp->set_vanishing_point (Box3D::Y, g_ascii_strtod (vps[5], NULL), g_ascii_strtod (vps[6], NULL),
1279 g_ascii_strtod (vps[7], NULL), g_ascii_strtod (vps[8], NULL),
1280 strcmp (vps[9], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
1281 persp->set_vanishing_point (Box3D::Z, g_ascii_strtod (vps[10], NULL), g_ascii_strtod (vps[11], NULL),
1282 g_ascii_strtod (vps[12], NULL), g_ascii_strtod (vps[13], NULL),
1283 strcmp (vps[14], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
1285 // update the other boxes linked to the same perspective
1286 persp->reshape_boxes (Box3D::XYZ);
1287 }
1289 /*
1290 Local Variables:
1291 mode:c++
1292 c-file-style:"stroustrup"
1293 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1294 indent-tabs-mode:nil
1295 fill-column:99
1296 End:
1297 */
1298 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :