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"
23 static void sp_3dbox_class_init(SP3DBoxClass *klass);
24 static void sp_3dbox_init(SP3DBox *box3d);
26 static void sp_3dbox_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
27 static void sp_3dbox_release (SPObject *object);
28 static void sp_3dbox_set(SPObject *object, unsigned int key, const gchar *value);
29 static void sp_3dbox_update(SPObject *object, SPCtx *ctx, guint flags);
30 static Inkscape::XML::Node *sp_3dbox_write(SPObject *object, Inkscape::XML::Node *repr, guint flags);
32 static gchar *sp_3dbox_description(SPItem *item);
34 //static void sp_3dbox_set_shape(SPShape *shape);
35 //static void sp_3dbox_set_shape(SP3DBox *box3d);
37 static void sp_3dbox_update_corner_with_value_from_svg (SPObject *object, guint corner_id, const gchar *value);
38 static void sp_3dbox_update_perspective (Box3D::Perspective3D *persp, const gchar *value);
39 static gchar * sp_3dbox_get_corner_coords_string (SP3DBox *box, guint id);
40 static std::pair<gdouble, gdouble> sp_3dbox_get_coord_pair_from_string (const gchar *);
41 static gchar * sp_3dbox_get_perspective_string (SP3DBox *box);
43 static SPGroupClass *parent_class;
45 static gint counter = 0;
47 GType
48 sp_3dbox_get_type(void)
49 {
50 static GType type = 0;
52 if (!type) {
53 GTypeInfo info = {
54 sizeof(SP3DBoxClass),
55 NULL, /* base_init */
56 NULL, /* base_finalize */
57 (GClassInitFunc) sp_3dbox_class_init,
58 NULL, /* class_finalize */
59 NULL, /* class_data */
60 sizeof(SP3DBox),
61 16, /* n_preallocs */
62 (GInstanceInitFunc) sp_3dbox_init,
63 NULL, /* value_table */
64 };
65 type = g_type_register_static(SP_TYPE_GROUP, "SP3DBox", &info, (GTypeFlags) 0);
66 }
68 return type;
69 }
71 static void
72 sp_3dbox_class_init(SP3DBoxClass *klass)
73 {
74 SPObjectClass *sp_object_class = (SPObjectClass *) klass;
75 SPItemClass *item_class = (SPItemClass *) klass;
77 parent_class = (SPGroupClass *) g_type_class_ref(SP_TYPE_GROUP);
79 sp_object_class->build = sp_3dbox_build;
80 sp_object_class->set = sp_3dbox_set;
81 sp_object_class->write = sp_3dbox_write;
82 sp_object_class->update = sp_3dbox_update;
83 sp_object_class->release = sp_3dbox_release;
85 item_class->description = sp_3dbox_description;
86 }
88 static void
89 sp_3dbox_init(SP3DBox *box)
90 {
91 for (int i = 0; i < 8; ++i) box->corners[i] = NR::Point(0,0);
92 for (int i = 0; i < 6; ++i) box->faces[i] = NULL;
93 }
95 static void
96 sp_3dbox_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
97 {
98 if (((SPObjectClass *) (parent_class))->build) {
99 ((SPObjectClass *) (parent_class))->build(object, document, repr);
100 }
102 SP3DBox *box = SP_3DBOX (object);
104 box->my_counter = counter++;
106 /* we initialize the z-orders to zero so that they are updated during dragging */
107 for (int i = 0; i < 6; ++i) {
108 box->z_orders[i] = 0;
109 }
111 box->front_bits = 0x0;
113 if (repr->attribute ("inkscape:perspective") == NULL) {
114 // we are creating a new box; link it to the current perspective
115 Box3D::Perspective3D::current_perspective->add_box (box);
116 } else {
117 // create a new perspective that we can compare with existing ones
118 Box3D::Perspective3D *persp = new Box3D::Perspective3D (Box3D::VanishingPoint (0,0),
119 Box3D::VanishingPoint (0,0),
120 Box3D::VanishingPoint (0,0));
121 sp_3dbox_update_perspective (persp, repr->attribute ("inkscape:perspective"));
122 Box3D::Perspective3D *comp = Box3D::Perspective3D::find_perspective (persp);
123 if (comp == NULL) {
124 // perspective doesn't exist yet
125 Box3D::Perspective3D::add_perspective (persp);
126 persp->add_box (box);
127 } else {
128 // link the box to the existing perspective and delete the temporary one
129 comp->add_box (box);
130 delete persp;
131 //g_assert (Box3D::get_persp_of_box (box) == comp);
133 // FIXME: If the paths of the box's faces do not correspond to the svg representation of the perspective
134 // the box is shown with a "wrong" initial shape that is only corrected after dragging.
135 // Should we "repair" this by updating the paths at the end of sp_3dbox_build()?
136 // Maybe it would be better to simply destroy and rebuild them in sp_3dbox_link_to_existing_paths().
137 }
138 }
140 sp_object_read_attr(object, "inkscape:box3dcornerA");
141 sp_object_read_attr(object, "inkscape:box3dcornerB");
142 sp_object_read_attr(object, "inkscape:box3dcornerC");
144 // TODO: We create all faces in the beginning, but only the non-degenerate ones
145 // should be written to the svg representation later in sp_3dbox_write.
146 Box3D::Axis cur_plane, axis, dir1, dir2;
147 Box3D::FrontOrRear cur_pos;
148 for (int i = 0; i < 3; ++i) {
149 for (int j = 0; j < 2; ++j) {
150 cur_plane = Box3D::planes[i];
151 cur_pos = Box3D::face_positions[j];
152 // FIXME: The following code could theoretically be moved to
153 // the constructor of Box3DFace (but see the comment there).
154 axis = (cur_pos == Box3D::FRONT ? Box3D::NONE : Box3D::third_axis_direction (cur_plane));
155 dir1 = extract_first_axis_direction (cur_plane);
156 dir2 = extract_second_axis_direction (cur_plane);
158 box->faces[Box3D::face_to_int(cur_plane ^ cur_pos)] =
159 new Box3DFace (box, box->corners[axis], box->corners[axis ^ dir1],
160 box->corners[axis ^ dir1 ^ dir2], box->corners[axis ^ dir2],
161 cur_plane, cur_pos);
162 }
163 }
165 // Check whether the paths of the faces of the box need to be linked to existing paths in the
166 // document (e.g., after a 'redo' operation or after opening a file) and do so if necessary.
167 sp_3dbox_link_to_existing_paths (box, repr);
169 sp_3dbox_set_ratios (box);
170 }
172 static void
173 sp_3dbox_release (SPObject *object)
174 {
175 SP3DBox *box = SP_3DBOX(object);
176 for (int i = 0; i < 6; ++i) {
177 if (box->faces[i]) {
178 delete box->faces[i]; // FIXME: Anything else to do? Do we need to clean up the face first?
179 }
180 }
182 // FIXME: We do not duplicate perspectives if they are the same for several boxes.
183 // Thus, don't delete the perspective when deleting a box but rather unlink the box from it.
184 Box3D::get_persp_of_box (box)->remove_box (box);
186 if (((SPObjectClass *) parent_class)->release) {
187 ((SPObjectClass *) parent_class)->release (object);
188 }
189 }
191 static void sp_3dbox_set(SPObject *object, unsigned int key, const gchar *value)
192 {
193 switch (key) {
194 case SP_ATTR_INKSCAPE_3DBOX_CORNER_A:
195 sp_3dbox_update_corner_with_value_from_svg (object, 2, value);
196 break;
197 case SP_ATTR_INKSCAPE_3DBOX_CORNER_B:
198 sp_3dbox_update_corner_with_value_from_svg (object, 1, value);
199 break;
200 case SP_ATTR_INKSCAPE_3DBOX_CORNER_C:
201 sp_3dbox_update_corner_with_value_from_svg (object, 5, value);
202 break;
203 case SP_ATTR_INKSCAPE_3DBOX_PERSPECTIVE:
204 sp_3dbox_update_perspective (Box3D::get_persp_of_box (SP_3DBOX (object)), value);
205 break;
206 default:
207 if (((SPObjectClass *) (parent_class))->set) {
208 ((SPObjectClass *) (parent_class))->set(object, key, value);
209 }
210 break;
211 }
212 }
214 static void
215 sp_3dbox_update(SPObject *object, SPCtx *ctx, guint flags)
216 {
217 if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
218 SP3DBox *box = SP_3DBOX(object);
219 sp_3dbox_link_to_existing_paths (box, SP_OBJECT_REPR(object));
220 }
222 /* Invoke parent method */
223 if (((SPObjectClass *) (parent_class))->update)
224 ((SPObjectClass *) (parent_class))->update(object, ctx, flags);
225 }
229 static Inkscape::XML::Node *sp_3dbox_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
230 {
231 SP3DBox *box = SP_3DBOX(object);
232 // FIXME: How to handle other contexts???
233 // FIXME: Is tools_isactive(..) more recommended to check for the current context/tool?
234 if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
235 return repr;
237 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
238 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
239 repr = xml_doc->createElement("svg:g");
240 repr->setAttribute("sodipodi:type", "inkscape:3dbox");
241 /* Hook paths to the faces of the box */
242 for (int i = 0; i < 6; ++i) {
243 box->faces[i]->hook_path_to_3dbox();
244 }
245 }
247 for (int i = 0; i < 6; ++i) {
248 box->faces[i]->set_path_repr();
249 }
251 if (flags & SP_OBJECT_WRITE_EXT) {
252 gchar *str;
253 str = sp_3dbox_get_corner_coords_string (box, 2);
254 repr->setAttribute("inkscape:box3dcornerA", str);
256 str = sp_3dbox_get_corner_coords_string (box, 1);
257 repr->setAttribute("inkscape:box3dcornerB", str);
259 str = sp_3dbox_get_corner_coords_string (box, 5);
260 repr->setAttribute("inkscape:box3dcornerC", str);
262 str = sp_3dbox_get_perspective_string (box);
263 repr->setAttribute("inkscape:perspective", str);
264 sp_3dbox_set_ratios (box);
266 g_free ((void *) str);
267 }
269 if (((SPObjectClass *) (parent_class))->write) {
270 ((SPObjectClass *) (parent_class))->write(object, repr, flags);
271 }
273 return repr;
274 }
276 static gchar *
277 sp_3dbox_description(SPItem *item)
278 {
279 g_return_val_if_fail(SP_IS_3DBOX(item), NULL);
281 return g_strdup(_("<b>3D Box</b>"));
282 }
284 void sp_3dbox_set_ratios (SP3DBox *box, Box3D::Axis axes)
285 {
286 Box3D::Perspective3D *persp = Box3D::get_persp_of_box (box);
287 NR::Point pt;
289 if (axes & Box3D::X) {
290 pt = persp->get_vanishing_point (Box3D::X)->get_pos();
291 box->ratio_x = NR::L2 (pt - box->corners[2]) / NR::L2 (pt - box->corners[3]);
292 }
294 if (axes & Box3D::Y) {
295 pt = persp->get_vanishing_point (Box3D::Y)->get_pos();
296 box->ratio_y = NR::L2 (pt - box->corners[2]) / NR::L2 (pt - box->corners[0]);
297 }
299 if (axes & Box3D::Z) {
300 pt = persp->get_vanishing_point (Box3D::Z)->get_pos();
301 box->ratio_z = NR::L2 (pt - box->corners[4]) / NR::L2 (pt - box->corners[0]);
302 }
303 }
305 void
306 sp_3dbox_switch_front_face (SP3DBox *box, Box3D::Axis axis)
307 {
308 if (Box3D::get_persp_of_box (box)->get_vanishing_point (axis)->is_finite()) {
309 box->front_bits = box->front_bits ^ axis;
310 }
311 }
314 void
315 sp_3dbox_position_set (SP3DBoxContext &bc)
316 {
317 SP3DBox *box3d = SP_3DBOX(bc.item);
319 sp_3dbox_set_shape(box3d);
321 // FIXME: Why does the following call not automatically update the children
322 // of box3d (which is an SPGroup, which should do this)?
323 //SP_OBJECT(box3d)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
325 /**
326 SP_OBJECT(box3d->path_face1)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
327 SP_OBJECT(box3d->path_face2)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
328 SP_OBJECT(box3d->path_face3)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
329 SP_OBJECT(box3d->path_face4)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
330 SP_OBJECT(box3d->path_face5)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
331 SP_OBJECT(box3d->path_face6)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
332 ***/
333 }
335 //static
336 void
337 // FIXME: Note that this is _not_ the virtual set_shape() method inherited from SPShape,
338 // since SP3DBox is inherited from SPGroup. The following method is "artificially"
339 // called from sp_3dbox_update().
340 //sp_3dbox_set_shape(SPShape *shape)
341 sp_3dbox_set_shape(SP3DBox *box3d, bool use_previous_corners)
342 {
343 // FIXME: How to handle other contexts???
344 // FIXME: Is tools_isactive(..) more recommended to check for the current context/tool?
345 if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
346 return;
347 SP3DBoxContext *bc = SP_3DBOX_CONTEXT(inkscape_active_event_context());
349 /* Only update the curves during dragging; setting the svg representations
350 is expensive and only done once at the end */
351 if (!use_previous_corners) {
352 sp_3dbox_recompute_corners (box3d, bc->drag_origin, bc->drag_ptB, bc->drag_ptC);
353 } else {
354 sp_3dbox_recompute_corners (box3d, box3d->corners[2], box3d->corners[1], box3d->corners[5]);
355 }
356 if (bc->extruded) {
357 box3d->faces[0]->set_corners (box3d->corners[0], box3d->corners[4], box3d->corners[6], box3d->corners[2]);
358 box3d->faces[1]->set_corners (box3d->corners[1], box3d->corners[5], box3d->corners[7], box3d->corners[3]);
359 box3d->faces[2]->set_corners (box3d->corners[0], box3d->corners[1], box3d->corners[5], box3d->corners[4]);
360 box3d->faces[3]->set_corners (box3d->corners[2], box3d->corners[3], box3d->corners[7], box3d->corners[6]);
361 box3d->faces[5]->set_corners (box3d->corners[4], box3d->corners[5], box3d->corners[7], box3d->corners[6]);
362 }
363 box3d->faces[4]->set_corners (box3d->corners[0], box3d->corners[1], box3d->corners[3], box3d->corners[2]);
365 sp_3dbox_update_curves (box3d);
366 }
369 void sp_3dbox_recompute_corners (SP3DBox *box, NR::Point const A, NR::Point const B, NR::Point const C)
370 {
371 sp_3dbox_move_corner_in_XY_plane (box, 2, A);
372 sp_3dbox_move_corner_in_XY_plane (box, 1, B);
373 sp_3dbox_move_corner_in_Z_direction (box, 5, C);
374 }
376 inline static double
377 normalized_angle (double angle) {
378 if (angle < -M_PI) {
379 return angle + 2*M_PI;
380 } else if (angle > M_PI) {
381 return angle - 2*M_PI;
382 }
383 return angle;
384 }
386 static gdouble
387 sp_3dbox_corner_angle_to_VP (SP3DBox *box, Box3D::Axis axis, guint extreme_corner)
388 {
389 Box3D::VanishingPoint *vp = Box3D::get_persp_of_box (box)->get_vanishing_point (axis);
390 NR::Point dir;
392 if (vp->is_finite()) {
393 dir = NR::unit_vector (vp->get_pos() - box->corners[extreme_corner]);
394 } else {
395 dir = NR::unit_vector (vp->v_dir);
396 }
398 return atan2 (dir[NR::Y], dir[NR::X]);
399 }
402 bool sp_3dbox_recompute_z_orders (SP3DBox *box)
403 {
404 guint new_z_orders[6];
406 Box3D::Perspective3D *persp = Box3D::get_persp_of_box (box);
408 // TODO: Determine the front corner depending on the distance from VPs and/or the user presets
409 guint front_corner = sp_3dbox_get_front_corner_id (box);
411 gdouble dir_1x = sp_3dbox_corner_angle_to_VP (box, Box3D::X, front_corner);
412 gdouble dir_3x = sp_3dbox_corner_angle_to_VP (box, Box3D::X, front_corner ^ Box3D::Y);
414 gdouble dir_1y = sp_3dbox_corner_angle_to_VP (box, Box3D::Y, front_corner);
415 gdouble dir_0y = sp_3dbox_corner_angle_to_VP (box, Box3D::Y, front_corner ^ Box3D::X);
417 gdouble dir_1z = sp_3dbox_corner_angle_to_VP (box, Box3D::Z, front_corner);
418 gdouble dir_3z = sp_3dbox_corner_angle_to_VP (box, Box3D::Z, front_corner ^ Box3D::Y);
420 // Still not perfect, but only fails in some rather degenerate cases.
421 // I suspect that there is a more elegant model, though. :)
422 new_z_orders[0] = Box3D::face_containing_corner (Box3D::XY, front_corner);
423 if (normalized_angle (dir_1y - dir_1z) > 0) {
424 new_z_orders[1] = Box3D::face_containing_corner (Box3D::YZ, front_corner);
425 if (normalized_angle (dir_1x - dir_1z) > 0) {
426 new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner ^ Box3D::Y);
427 } else {
428 new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
429 }
430 } else {
431 if (normalized_angle (dir_3x - dir_3z) > 0) {
432 new_z_orders[1] = Box3D::face_containing_corner (Box3D::XZ, front_corner ^ Box3D::Y);
433 new_z_orders[2] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
434 } else {
435 if (normalized_angle (dir_1x - dir_1z) > 0) {
436 new_z_orders[1] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
437 new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
438 } else {
439 new_z_orders[1] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
440 new_z_orders[2] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
441 }
442 }
443 }
445 new_z_orders[3] = Box3D::opposite_face (new_z_orders[2]);
446 new_z_orders[4] = Box3D::opposite_face (new_z_orders[1]);
447 new_z_orders[5] = Box3D::opposite_face (new_z_orders[0]);
449 /* We only need to look for changes among the topmost three faces because the order
450 of the other ones is just inverted. */
451 if ((box->z_orders[0] != new_z_orders[0]) ||
452 (box->z_orders[1] != new_z_orders[1]) ||
453 (box->z_orders[2] != new_z_orders[2]))
454 {
455 for (int i = 0; i < 6; ++i) {
456 box->z_orders[i] = new_z_orders[i];
457 }
458 return true;
459 }
461 return false;
462 }
464 void sp_3dbox_set_z_orders (SP3DBox *box)
465 {
466 GSList *items = sp_item_group_item_list(SP_GROUP(box));
468 // For efficiency reasons, we only set the new z-orders if something really changed
469 if (sp_3dbox_recompute_z_orders (box)) {
470 box->faces[box->z_orders[0]]->lower_to_bottom ();
471 box->faces[box->z_orders[1]]->lower_to_bottom ();
472 box->faces[box->z_orders[2]]->lower_to_bottom ();
473 box->faces[box->z_orders[3]]->lower_to_bottom ();
474 box->faces[box->z_orders[4]]->lower_to_bottom ();
475 box->faces[box->z_orders[5]]->lower_to_bottom ();
476 }
477 }
479 void
480 sp_3dbox_update_curves (SP3DBox *box) {
481 for (int i = 0; i < 6; ++i) {
482 if (box->faces[i]) box->faces[i]->set_curve();
483 }
484 }
486 /**
487 * In some situations (e.g., after cloning boxes, undo & redo, or reading boxes from a file) there are
488 * paths already present in the document which correspond to the faces of newly created boxes, but their
489 * 'path' members don't link to them yet. The following function corrects this if necessary.
490 */
491 void
492 sp_3dbox_link_to_existing_paths (SP3DBox *box, Inkscape::XML::Node *repr) {
493 // TODO: We should probably destroy the existing paths and recreate them because we don't know
494 // precisely which path corresponds to which face. Does this make a difference?
495 // In sp_3dbox_write we write the correct paths anyway, don't we? But we could get into
496 // trouble at a later stage when we only write single faces for degenerate boxes.
498 SPDocument *document = SP_OBJECT_DOCUMENT(box);
499 guint face_id = 0;
501 for (Inkscape::XML::Node *i = sp_repr_children(repr); i != NULL; i = sp_repr_next(i)) {
502 if (face_id > 5) {
503 g_warning ("SVG representation of 3D boxes must contain 6 paths or less.\n");
504 break;
505 }
507 SPObject *face_object = document->getObjectByRepr((Inkscape::XML::Node *) i);
508 if (!SP_IS_PATH(face_object)) {
509 g_warning ("SVG representation of 3D boxes should only contain paths.\n");
510 continue;
511 }
512 box->faces[face_id]->hook_path_to_3dbox(SP_PATH(face_object));
513 ++face_id;
514 }
515 if (face_id < 6) {
516 //g_warning ("SVG representation of 3D boxes should contain exactly 6 paths (degenerate boxes are not yet supported).\n");
517 // TODO: Check whether it is safe to add the remaining paths to the box and do so in case it is.
518 // (But we also land here for newly created boxes where we shouldn't add any paths because
519 // This is done in sp_3dbox_write later on.
520 }
521 }
523 void
524 sp_3dbox_move_corner_in_XY_plane (SP3DBox *box, guint id, NR::Point pt, Box3D::Axis axes)
525 {
526 Box3D::Perspective3D * persp = Box3D::get_persp_of_box (box);
528 NR::Point A (box->corners[id ^ Box3D::XY]);
529 if (Box3D::is_single_axis_direction (axes)) {
530 pt = Box3D::PerspectiveLine (box->corners[id], axes, persp).closest_to(pt);
531 }
533 /* set the 'front' corners */
534 box->corners[id] = pt;
536 Box3D::PerspectiveLine pl_one (A, Box3D::Y, persp);
537 Box3D::PerspectiveLine pl_two (pt, Box3D::X, persp);
538 box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
540 pl_one = Box3D::PerspectiveLine (A, Box3D::X, persp);
541 pl_two = Box3D::PerspectiveLine (pt, Box3D::Y, persp);
542 box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
544 /* set the 'rear' corners */
545 NR::Point B (box->corners[id ^ Box3D::XYZ]);
547 pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Z, persp);
548 pl_two = Box3D::PerspectiveLine (B, Box3D::Y, persp);
549 box->corners[id ^ Box3D::XZ] = pl_one.meet(pl_two);
551 pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XZ], Box3D::X, persp);
552 pl_two = Box3D::PerspectiveLine (pt, Box3D::Z, persp);
553 box->corners[id ^ Box3D::Z] = pl_one.meet(pl_two);
555 pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::Z], Box3D::Y, persp);
556 pl_two = Box3D::PerspectiveLine (B, Box3D::X, persp);
557 box->corners[id ^ Box3D::YZ] = pl_one.meet(pl_two);
559 }
561 void
562 sp_3dbox_move_corner_in_Z_direction (SP3DBox *box, guint id, NR::Point pt, bool constrained)
563 {
564 if (!constrained) sp_3dbox_move_corner_in_XY_plane (box, id, pt, Box3D::XY);
566 Box3D::Perspective3D * persp = Box3D::get_persp_of_box (box);
568 /* set the four corners of the face containing corners[id] */
569 box->corners[id] = Box3D::PerspectiveLine (box->corners[id], Box3D::Z, persp).closest_to(pt);
571 Box3D::PerspectiveLine pl_one (box->corners[id], Box3D::X, persp);
572 Box3D::PerspectiveLine pl_two (box->corners[id ^ Box3D::XZ], Box3D::Z, persp);
573 box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
575 pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Y, persp);
576 pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XYZ], Box3D::Z, persp);
577 box->corners[id ^ Box3D::XY] = pl_one.meet(pl_two);
579 pl_one = Box3D::PerspectiveLine (box->corners[id], Box3D::Y, persp);
580 pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::YZ], Box3D::Z, persp);
581 box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
582 }
584 static void
585 sp_3dbox_reshape_edge_after_VP_toggling (SP3DBox *box, const guint corner, const Box3D::Axis axis, Box3D::Perspective3D *persp)
586 {
587 /* Hmm, perhaps we should simply use one of the corners as the pivot point.
588 But this way we minimize the amount of reshaping.
589 On second thought, we need to find a way to ensure that all boxes sharing the same
590 perspective are updated consistently _as a group_. That is, they should also retain
591 their relative positions towards each other. */
592 NR::Maybe<NR::Point> pt = sp_3dbox_get_midpoint_between_corners (box, corner, corner ^ axis);
593 g_return_if_fail (pt);
595 Box3D::Axis axis2 = ((axis == Box3D::Y) ? Box3D::X : Box3D::Y);
597 Box3D::PerspectiveLine line1 (box->corners[corner], axis2, persp);
598 Box3D::PerspectiveLine line2 (box->corners[corner ^ axis], axis2, persp);
600 Box3D::PerspectiveLine line3 (*pt, axis, persp);
602 NR::Point new_corner1 = line1.meet (line3);
603 NR::Point new_corner2 = line2.meet (line3);
605 box->corners[corner] = new_corner1;
606 box->corners[corner ^ axis] = new_corner2;
607 }
609 void
610 sp_3dbox_reshape_after_VP_toggling (SP3DBox *box, Box3D::Axis axis)
611 {
612 Box3D::Perspective3D *persp = Box3D::get_persp_of_box (box);
613 std::pair<Box3D::Axis, Box3D::Axis> dirs = Box3D::get_remaining_axes (axis);
615 sp_3dbox_reshape_edge_after_VP_toggling (box, 0, axis, persp);
616 sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first, axis, persp);
617 sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first ^ dirs.second, axis, persp);
618 sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.second, axis, persp);
619 }
621 NR::Maybe<NR::Point>
622 sp_3dbox_get_center (SP3DBox *box)
623 {
624 return sp_3dbox_get_midpoint_between_corners (box, 0, 7);
625 }
627 // TODO: The following function can probably be rewritten in a much more elegant and robust way
628 // by using projective coordinates for all points and using the cross ratio.
629 NR::Maybe<NR::Point>
630 sp_3dbox_get_midpoint_between_corners (SP3DBox *box, guint id_corner1, guint id_corner2)
631 {
632 Box3D::Axis corner_axes = (Box3D::Axis) (id_corner1 ^ id_corner2);
634 // Is all this sufficiently precise also for degenerate cases?
635 if (sp_3dbox_corners_are_adjacent (id_corner1, id_corner2)) {
636 Box3D::Axis orth_dir = get_perpendicular_axis_direction (corner_axes);
638 Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2 ^ orth_dir]);
639 Box3D::Line diag2 (box->corners[id_corner1 ^ orth_dir], box->corners[id_corner2]);
640 NR::Maybe<NR::Point> adjacent_face_center = diag1.intersect(diag2);
642 if (!adjacent_face_center) return NR::Nothing();
644 Box3D::Perspective3D * persp = Box3D::get_persp_of_box (box);
646 Box3D::PerspectiveLine pl (*adjacent_face_center, orth_dir, persp);
647 return pl.intersect(Box3D::PerspectiveLine(box->corners[id_corner1], corner_axes, persp));
648 } else {
649 Box3D::Axis dir = Box3D::extract_first_axis_direction (corner_axes);
650 Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2]);
651 Box3D::Line diag2 (box->corners[id_corner1 ^ dir], box->corners[id_corner2 ^ dir]);
652 return diag1.intersect(diag2);
653 }
654 }
656 static gchar *
657 sp_3dbox_get_corner_coords_string (SP3DBox *box, guint id)
658 {
659 id = id % 8;
660 Inkscape::SVGOStringStream os;
661 os << box->corners[id][NR::X] << "," << box->corners[id][NR::Y];
662 return g_strdup(os.str().c_str());
663 }
665 static std::pair<gdouble, gdouble>
666 sp_3dbox_get_coord_pair_from_string (const gchar *coords)
667 {
668 gchar **coordpair = g_strsplit( coords, ",", 0);
669 // We might as well rely on g_ascii_strtod to convert the NULL pointer to 0.0,
670 // but we include the following test anyway
671 if (coordpair[0] == NULL || coordpair[1] == NULL) {
672 g_strfreev (coordpair);
673 g_warning ("Coordinate conversion failed.\n");
674 return std::make_pair(0.0, 0.0);
675 }
677 gdouble coord1 = g_ascii_strtod(coordpair[0], NULL);
678 gdouble coord2 = g_ascii_strtod(coordpair[1], NULL);
679 g_strfreev (coordpair);
681 return std::make_pair(coord1, coord2);
682 }
684 static gchar *
685 sp_3dbox_get_perspective_string (SP3DBox *box)
686 {
688 return sp_3dbox_get_svg_descr_of_persp (Box3D::get_persp_of_box (box));
689 }
691 gchar *
692 sp_3dbox_get_svg_descr_of_persp (Box3D::Perspective3D *persp)
693 {
694 // FIXME: We should move this code to perspective3d.cpp, but this yields compiler errors. Why?
695 Inkscape::SVGOStringStream os;
697 Box3D::VanishingPoint vp = *(persp->get_vanishing_point (Box3D::X));
698 os << vp[NR::X] << "," << vp[NR::Y] << ",";
699 os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
700 if (vp.is_finite()) {
701 os << "finite,";
702 } else {
703 os << "infinite,";
704 }
706 vp = *(persp->get_vanishing_point (Box3D::Y));
707 os << vp[NR::X] << "," << vp[NR::Y] << ",";
708 os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
709 if (vp.is_finite()) {
710 os << "finite,";
711 } else {
712 os << "infinite,";
713 }
715 vp = *(persp->get_vanishing_point (Box3D::Z));
716 os << vp[NR::X] << "," << vp[NR::Y] << ",";
717 os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
718 if (vp.is_finite()) {
719 os << "finite";
720 } else {
721 os << "infinite";
722 }
724 return g_strdup(os.str().c_str());
725 }
727 void sp_3dbox_update_perspective_lines()
728 {
729 SPEventContext *ec = inkscape_active_event_context();
730 if (!SP_IS_3DBOX_CONTEXT (ec))
731 return;
733 SP_3DBOX_CONTEXT (ec)->_vpdrag->updateLines();
734 }
736 /*
737 * Manipulates corner1 through corner4 to contain the indices of the corners
738 * from which the perspective lines in the direction of 'axis' emerge
739 */
740 void sp_3dbox_corners_for_perspective_lines (const SP3DBox * box, Box3D::Axis axis,
741 NR::Point &corner1, NR::Point &corner2, NR::Point &corner3, NR::Point &corner4)
742 {
743 // along which axis to switch when takint
744 Box3D::Axis switch_axis;
745 if (axis == Box3D::X || axis == Box3D::Y) {
746 switch_axis = (box->front_bits & axis) ? Box3D::Z : Box3D::NONE;
747 } else {
748 switch_axis = (box->front_bits & axis) ? Box3D::X : Box3D::NONE;
749 }
751 switch (axis) {
752 case Box3D::X:
753 corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
754 corner2 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
755 corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
756 corner4 = sp_3dbox_get_corner_along_edge (box, 6 ^ switch_axis, axis, Box3D::REAR);
757 break;
758 case Box3D::Y:
759 corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
760 corner2 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
761 corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
762 corner4 = sp_3dbox_get_corner_along_edge (box, 5 ^ switch_axis, axis, Box3D::REAR);
763 break;
764 case Box3D::Z:
765 corner1 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
766 corner2 = sp_3dbox_get_corner_along_edge (box, 3 ^ switch_axis, axis, Box3D::REAR);
767 corner3 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
768 corner4 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
769 break;
770 }
771 }
773 /**
774 * Returns the id of the corner on the edge along 'axis' and passing through 'corner' that
775 * lies on the front/rear face in this direction.
776 */
777 guint
778 sp_3dbox_get_corner_id_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
779 {
780 guint result;
781 guint other_corner = corner ^ axis;
782 Box3D::VanishingPoint *vp = Box3D::get_persp_of_box (box)->get_vanishing_point(axis);
783 if (vp->is_finite()) {
784 result = ( NR::L2 (vp->get_pos() - box->corners[corner])
785 < NR::L2 (vp->get_pos() - box->corners[other_corner]) ? other_corner : corner);
786 } else {
787 // clear the axis bit and switch to the appropriate corner along axis, depending on the value of front_bits
788 result = ((corner & (0xF ^ axis)) ^ (box->front_bits & axis));
789 }
791 if (rel_pos == Box3D::FRONT) {
792 return result;
793 } else {
794 return result ^ axis;
795 }
796 }
798 NR::Point
799 sp_3dbox_get_corner_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
800 {
801 return box->corners[sp_3dbox_get_corner_id_along_edge (box, corner, axis, rel_pos)];
802 }
804 guint
805 sp_3dbox_get_front_corner_id (const SP3DBox *box)
806 {
807 guint front_corner = 1; // this could in fact be any corner, but we choose the one that is normally in front
808 front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::X, Box3D::FRONT);
809 front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Y, Box3D::FRONT);
810 front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Z, Box3D::FRONT);
811 return front_corner;
812 }
814 // auxiliary functions
815 static void
816 sp_3dbox_update_corner_with_value_from_svg (SPObject *object, guint corner_id, const gchar *value)
817 {
818 if (value == NULL) return;
819 SP3DBox *box = SP_3DBOX(object);
821 std::pair<gdouble, gdouble> coord_pair = sp_3dbox_get_coord_pair_from_string (value);
822 box->corners[corner_id] = NR::Point (coord_pair.first, coord_pair.second);
823 sp_3dbox_recompute_corners (box, box->corners[2], box->corners[1], box->corners[5]);
824 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
825 }
827 static void
828 sp_3dbox_update_perspective (Box3D::Perspective3D *persp, const gchar *value)
829 {
830 // WARNING! This function changes the perspective associated to 'box'. Since there may be
831 // many other boxes linked to the same perspective, their perspective is also changed.
832 // If this behaviour is not desired in all cases, we need a different function.
833 if (value == NULL) return;
835 gchar **vps = g_strsplit( value, ",", 0);
836 for (int i = 0; i < 15; ++i) {
837 if (vps[i] == NULL) {
838 g_warning ("Malformed svg attribute 'perspective'\n");
839 return;
840 }
841 }
843 persp->set_vanishing_point (Box3D::X, g_ascii_strtod (vps[0], NULL), g_ascii_strtod (vps[1], NULL),
844 g_ascii_strtod (vps[2], NULL), g_ascii_strtod (vps[3], NULL),
845 strcmp (vps[4], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
846 persp->set_vanishing_point (Box3D::Y, g_ascii_strtod (vps[5], NULL), g_ascii_strtod (vps[6], NULL),
847 g_ascii_strtod (vps[7], NULL), g_ascii_strtod (vps[8], NULL),
848 strcmp (vps[9], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
849 persp->set_vanishing_point (Box3D::Z, g_ascii_strtod (vps[10], NULL), g_ascii_strtod (vps[11], NULL),
850 g_ascii_strtod (vps[12], NULL), g_ascii_strtod (vps[13], NULL),
851 strcmp (vps[14], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
853 // update the other boxes linked to the same perspective
854 persp->reshape_boxes (Box3D::XYZ);
855 }
857 /*
858 Local Variables:
859 mode:c++
860 c-file-style:"stroustrup"
861 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
862 indent-tabs-mode:nil
863 fill-column:99
864 End:
865 */
866 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :