dad0ae88cd7df11059a199153ae126dae9a9f9cb
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;
114 if (repr->attribute ("inkscape:perspective") == NULL) {
115 // we are creating a new box; link it to the current perspective
116 document->current_perspective->add_box (box);
117 } else {
118 // create a new perspective that we can compare with existing ones
119 Box3D::Perspective3D *persp = new Box3D::Perspective3D (Box3D::VanishingPoint (0,0),
120 Box3D::VanishingPoint (0,0),
121 Box3D::VanishingPoint (0,0),
122 document);
123 sp_3dbox_update_perspective (persp, repr->attribute ("inkscape:perspective"));
124 Box3D::Perspective3D *comp = document->find_perspective (persp);
125 if (comp == NULL) {
126 // perspective doesn't exist yet
127 document->add_perspective (persp);
128 persp->add_box (box);
129 } else {
130 // link the box to the existing perspective and delete the temporary one
131 comp->add_box (box);
132 delete persp;
133 //g_assert (Box3D::get_persp_of_box (box) == comp);
135 // FIXME: If the paths of the box's faces do not correspond to the svg representation of the perspective
136 // the box is shown with a "wrong" initial shape that is only corrected after dragging.
137 // Should we "repair" this by updating the paths at the end of sp_3dbox_build()?
138 // Maybe it would be better to simply destroy and rebuild them in sp_3dbox_link_to_existing_paths().
139 }
140 }
142 sp_object_read_attr(object, "inkscape:box3dcornerA");
143 sp_object_read_attr(object, "inkscape:box3dcornerB");
144 sp_object_read_attr(object, "inkscape:box3dcornerC");
146 // TODO: We create all faces in the beginning, but only the non-degenerate ones
147 // should be written to the svg representation later in sp_3dbox_write.
148 Box3D::Axis cur_plane, axis, dir1, dir2;
149 Box3D::FrontOrRear cur_pos;
150 for (int i = 0; i < 3; ++i) {
151 for (int j = 0; j < 2; ++j) {
152 cur_plane = Box3D::planes[i];
153 cur_pos = Box3D::face_positions[j];
154 // FIXME: The following code could theoretically be moved to
155 // the constructor of Box3DFace (but see the comment there).
156 axis = (cur_pos == Box3D::FRONT ? Box3D::NONE : Box3D::third_axis_direction (cur_plane));
157 dir1 = extract_first_axis_direction (cur_plane);
158 dir2 = extract_second_axis_direction (cur_plane);
160 box->faces[Box3D::face_to_int(cur_plane ^ cur_pos)] =
161 new Box3DFace (box, box->corners[axis], box->corners[axis ^ dir1],
162 box->corners[axis ^ dir1 ^ dir2], box->corners[axis ^ dir2],
163 cur_plane, cur_pos);
164 }
165 }
167 // Check whether the paths of the faces of the box need to be linked to existing paths in the
168 // document (e.g., after a 'redo' operation or after opening a file) and do so if necessary.
169 sp_3dbox_link_to_existing_paths (box, repr);
171 sp_3dbox_set_ratios (box, Box3D::XYZ);
172 }
174 static void
175 sp_3dbox_release (SPObject *object)
176 {
177 SP3DBox *box = SP_3DBOX(object);
178 for (int i = 0; i < 6; ++i) {
179 if (box->faces[i]) {
180 delete box->faces[i]; // FIXME: Anything else to do? Do we need to clean up the face first?
181 }
182 }
184 // FIXME: We do not duplicate perspectives if they are the same for several boxes.
185 // Thus, don't delete the perspective when deleting a box but rather unlink the box from it.
186 SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->remove_box (box);
188 if (((SPObjectClass *) parent_class)->release) {
189 ((SPObjectClass *) parent_class)->release (object);
190 }
191 }
193 static void sp_3dbox_set(SPObject *object, unsigned int key, const gchar *value)
194 {
195 switch (key) {
196 case SP_ATTR_INKSCAPE_3DBOX_CORNER_A:
197 sp_3dbox_update_corner_with_value_from_svg (object, 2, value);
198 break;
199 case SP_ATTR_INKSCAPE_3DBOX_CORNER_B:
200 sp_3dbox_update_corner_with_value_from_svg (object, 1, value);
201 break;
202 case SP_ATTR_INKSCAPE_3DBOX_CORNER_C:
203 sp_3dbox_update_corner_with_value_from_svg (object, 5, value);
204 break;
205 case SP_ATTR_INKSCAPE_3DBOX_PERSPECTIVE:
206 {
207 SP3DBox *box = SP_3DBOX (object);
208 sp_3dbox_update_perspective (SP_OBJECT_DOCUMENT (object)->get_persp_of_box (box), value);
209 break;
210 }
211 default:
212 if (((SPObjectClass *) (parent_class))->set) {
213 ((SPObjectClass *) (parent_class))->set(object, key, value);
214 }
215 break;
216 }
217 }
219 static void
220 sp_3dbox_update(SPObject *object, SPCtx *ctx, guint flags)
221 {
222 if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
223 SP3DBox *box = SP_3DBOX(object);
224 sp_3dbox_link_to_existing_paths (box, SP_OBJECT_REPR(object));
225 }
227 /* Invoke parent method */
228 if (((SPObjectClass *) (parent_class))->update)
229 ((SPObjectClass *) (parent_class))->update(object, ctx, flags);
230 }
234 static Inkscape::XML::Node *sp_3dbox_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
235 {
236 SP3DBox *box = SP_3DBOX(object);
237 // FIXME: How to handle other contexts???
238 // FIXME: Is tools_isactive(..) more recommended to check for the current context/tool?
239 if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
240 return repr;
242 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
243 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
244 repr = xml_doc->createElement("svg:g");
245 repr->setAttribute("sodipodi:type", "inkscape:3dbox");
246 /* Hook paths to the faces of the box */
247 for (int i = 0; i < 6; ++i) {
248 box->faces[i]->hook_path_to_3dbox();
249 }
250 }
252 for (int i = 0; i < 6; ++i) {
253 box->faces[i]->set_path_repr();
254 }
256 if (flags & SP_OBJECT_WRITE_EXT) {
257 gchar *str;
258 str = sp_3dbox_get_corner_coords_string (box, 2);
259 repr->setAttribute("inkscape:box3dcornerA", str);
261 str = sp_3dbox_get_corner_coords_string (box, 1);
262 repr->setAttribute("inkscape:box3dcornerB", str);
264 str = sp_3dbox_get_corner_coords_string (box, 5);
265 repr->setAttribute("inkscape:box3dcornerC", str);
267 str = sp_3dbox_get_perspective_string (box);
268 repr->setAttribute("inkscape:perspective", str);
269 sp_3dbox_set_ratios (box);
271 g_free ((void *) str);
272 }
274 if (((SPObjectClass *) (parent_class))->write) {
275 ((SPObjectClass *) (parent_class))->write(object, repr, flags);
276 }
278 return repr;
279 }
281 static gchar *
282 sp_3dbox_description(SPItem *item)
283 {
284 g_return_val_if_fail(SP_IS_3DBOX(item), NULL);
286 return g_strdup(_("<b>3D Box</b>"));
287 }
289 void sp_3dbox_set_ratios (SP3DBox *box, Box3D::Axis axes)
290 {
291 Box3D::Perspective3D *persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
292 NR::Point pt;
294 if (axes & Box3D::X) {
295 pt = persp->get_vanishing_point (Box3D::X)->get_pos();
296 box->ratio_x = NR::L2 (pt - box->corners[2]) / NR::L2 (pt - box->corners[3]);
297 }
299 if (axes & Box3D::Y) {
300 pt = persp->get_vanishing_point (Box3D::Y)->get_pos();
301 box->ratio_y = NR::L2 (pt - box->corners[2]) / NR::L2 (pt - box->corners[0]);
302 }
304 if (axes & Box3D::Z) {
305 pt = persp->get_vanishing_point (Box3D::Z)->get_pos();
306 box->ratio_z = NR::L2 (pt - box->corners[4]) / NR::L2 (pt - box->corners[0]);
307 }
308 }
310 void
311 sp_3dbox_switch_front_face (SP3DBox *box, Box3D::Axis axis)
312 {
313 if (SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point (axis)->is_finite()) {
314 box->front_bits = box->front_bits ^ axis;
315 }
316 }
319 void
320 sp_3dbox_position_set (SP3DBoxContext &bc)
321 {
322 SP3DBox *box3d = SP_3DBOX(bc.item);
324 sp_3dbox_set_shape(box3d);
326 // FIXME: Why does the following call not automatically update the children
327 // of box3d (which is an SPGroup, which should do this)?
328 //SP_OBJECT(box3d)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
330 /**
331 SP_OBJECT(box3d->path_face1)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
332 SP_OBJECT(box3d->path_face2)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
333 SP_OBJECT(box3d->path_face3)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
334 SP_OBJECT(box3d->path_face4)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
335 SP_OBJECT(box3d->path_face5)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
336 SP_OBJECT(box3d->path_face6)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
337 ***/
338 }
340 //static
341 void
342 // FIXME: Note that this is _not_ the virtual set_shape() method inherited from SPShape,
343 // since SP3DBox is inherited from SPGroup. The following method is "artificially"
344 // called from sp_3dbox_update().
345 //sp_3dbox_set_shape(SPShape *shape)
346 sp_3dbox_set_shape(SP3DBox *box3d, bool use_previous_corners)
347 {
348 // FIXME: How to handle other contexts???
349 // FIXME: Is tools_isactive(..) more recommended to check for the current context/tool?
350 if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
351 return;
352 SP3DBoxContext *bc = SP_3DBOX_CONTEXT(inkscape_active_event_context());
354 /* Only update the curves during dragging; setting the svg representations
355 is expensive and only done once at the end */
356 if (!use_previous_corners) {
357 sp_3dbox_recompute_corners (box3d, bc->drag_origin, bc->drag_ptB, bc->drag_ptC);
358 } else {
359 sp_3dbox_recompute_corners (box3d, box3d->corners[2], box3d->corners[1], box3d->corners[5]);
360 }
361 if (bc->extruded) {
362 box3d->faces[0]->set_corners (box3d->corners[0], box3d->corners[4], box3d->corners[6], box3d->corners[2]);
363 box3d->faces[1]->set_corners (box3d->corners[1], box3d->corners[5], box3d->corners[7], box3d->corners[3]);
364 box3d->faces[2]->set_corners (box3d->corners[0], box3d->corners[1], box3d->corners[5], box3d->corners[4]);
365 box3d->faces[3]->set_corners (box3d->corners[2], box3d->corners[3], box3d->corners[7], box3d->corners[6]);
366 box3d->faces[5]->set_corners (box3d->corners[4], box3d->corners[5], box3d->corners[7], box3d->corners[6]);
367 }
368 box3d->faces[4]->set_corners (box3d->corners[0], box3d->corners[1], box3d->corners[3], box3d->corners[2]);
370 sp_3dbox_update_curves (box3d);
371 }
374 void sp_3dbox_recompute_corners (SP3DBox *box, NR::Point const A, NR::Point const B, NR::Point const C)
375 {
376 sp_3dbox_move_corner_in_XY_plane (box, 2, A);
377 sp_3dbox_move_corner_in_XY_plane (box, 1, B);
378 sp_3dbox_move_corner_in_Z_direction (box, 5, C);
379 }
381 inline static double
382 normalized_angle (double angle) {
383 if (angle < -M_PI) {
384 return angle + 2*M_PI;
385 } else if (angle > M_PI) {
386 return angle - 2*M_PI;
387 }
388 return angle;
389 }
391 static gdouble
392 sp_3dbox_corner_angle_to_VP (SP3DBox *box, Box3D::Axis axis, guint extreme_corner)
393 {
394 Box3D::VanishingPoint *vp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point (axis);
395 NR::Point dir;
397 if (vp->is_finite()) {
398 dir = NR::unit_vector (vp->get_pos() - box->corners[extreme_corner]);
399 } else {
400 dir = NR::unit_vector (vp->v_dir);
401 }
403 return atan2 (dir[NR::Y], dir[NR::X]);
404 }
407 bool sp_3dbox_recompute_z_orders (SP3DBox *box)
408 {
409 guint new_z_orders[6];
411 Box3D::Perspective3D *persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
413 // TODO: Determine the front corner depending on the distance from VPs and/or the user presets
414 guint front_corner = sp_3dbox_get_front_corner_id (box);
416 gdouble dir_1x = sp_3dbox_corner_angle_to_VP (box, Box3D::X, front_corner);
417 gdouble dir_3x = sp_3dbox_corner_angle_to_VP (box, Box3D::X, front_corner ^ Box3D::Y);
419 gdouble dir_1y = sp_3dbox_corner_angle_to_VP (box, Box3D::Y, front_corner);
420 gdouble dir_0y = sp_3dbox_corner_angle_to_VP (box, Box3D::Y, front_corner ^ Box3D::X);
422 gdouble dir_1z = sp_3dbox_corner_angle_to_VP (box, Box3D::Z, front_corner);
423 gdouble dir_3z = sp_3dbox_corner_angle_to_VP (box, Box3D::Z, front_corner ^ Box3D::Y);
425 // Still not perfect, but only fails in some rather degenerate cases.
426 // I suspect that there is a more elegant model, though. :)
427 new_z_orders[0] = Box3D::face_containing_corner (Box3D::XY, front_corner);
428 if (normalized_angle (dir_1y - dir_1z) > 0) {
429 new_z_orders[1] = Box3D::face_containing_corner (Box3D::YZ, front_corner);
430 if (normalized_angle (dir_1x - dir_1z) > 0) {
431 new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner ^ Box3D::Y);
432 } else {
433 new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
434 }
435 } else {
436 if (normalized_angle (dir_3x - dir_3z) > 0) {
437 new_z_orders[1] = Box3D::face_containing_corner (Box3D::XZ, front_corner ^ Box3D::Y);
438 new_z_orders[2] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
439 } else {
440 if (normalized_angle (dir_1x - dir_1z) > 0) {
441 new_z_orders[1] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
442 new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
443 } else {
444 new_z_orders[1] = Box3D::face_containing_corner (Box3D::XZ, front_corner);
445 new_z_orders[2] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X);
446 }
447 }
448 }
450 new_z_orders[3] = Box3D::opposite_face (new_z_orders[2]);
451 new_z_orders[4] = Box3D::opposite_face (new_z_orders[1]);
452 new_z_orders[5] = Box3D::opposite_face (new_z_orders[0]);
454 /* We only need to look for changes among the topmost three faces because the order
455 of the other ones is just inverted. */
456 if ((box->z_orders[0] != new_z_orders[0]) ||
457 (box->z_orders[1] != new_z_orders[1]) ||
458 (box->z_orders[2] != new_z_orders[2]))
459 {
460 for (int i = 0; i < 6; ++i) {
461 box->z_orders[i] = new_z_orders[i];
462 }
463 return true;
464 }
466 return false;
467 }
469 void sp_3dbox_set_z_orders (SP3DBox *box)
470 {
471 GSList *items = sp_item_group_item_list(SP_GROUP(box));
473 // For efficiency reasons, we only set the new z-orders if something really changed
474 if (sp_3dbox_recompute_z_orders (box)) {
475 box->faces[box->z_orders[0]]->lower_to_bottom ();
476 box->faces[box->z_orders[1]]->lower_to_bottom ();
477 box->faces[box->z_orders[2]]->lower_to_bottom ();
478 box->faces[box->z_orders[3]]->lower_to_bottom ();
479 box->faces[box->z_orders[4]]->lower_to_bottom ();
480 box->faces[box->z_orders[5]]->lower_to_bottom ();
481 }
482 }
484 void
485 sp_3dbox_update_curves (SP3DBox *box) {
486 for (int i = 0; i < 6; ++i) {
487 if (box->faces[i]) box->faces[i]->set_curve();
488 }
489 }
491 /**
492 * In some situations (e.g., after cloning boxes, undo & redo, or reading boxes from a file) there are
493 * paths already present in the document which correspond to the faces of newly created boxes, but their
494 * 'path' members don't link to them yet. The following function corrects this if necessary.
495 */
496 void
497 sp_3dbox_link_to_existing_paths (SP3DBox *box, Inkscape::XML::Node *repr) {
498 // TODO: We should probably destroy the existing paths and recreate them because we don't know
499 // precisely which path corresponds to which face. Does this make a difference?
500 // In sp_3dbox_write we write the correct paths anyway, don't we? But we could get into
501 // trouble at a later stage when we only write single faces for degenerate boxes.
503 SPDocument *document = SP_OBJECT_DOCUMENT(box);
504 guint face_id = 0;
506 for (Inkscape::XML::Node *i = sp_repr_children(repr); i != NULL; i = sp_repr_next(i)) {
507 if (face_id > 5) {
508 g_warning ("SVG representation of 3D boxes must contain 6 paths or less.\n");
509 break;
510 }
512 SPObject *face_object = document->getObjectByRepr((Inkscape::XML::Node *) i);
513 if (!SP_IS_PATH(face_object)) {
514 g_warning ("SVG representation of 3D boxes should only contain paths.\n");
515 continue;
516 }
517 // TODO: Currently we don't check whether all paths are being linked to different faces.
518 // This is no problem with valid SVG files. It may lead to crashes, however,
519 // in case a file is corrupt (e.g., two or more faces have identical descriptions).
520 gint id = Box3DFace::descr_to_id (i->attribute ("inkscape:box3dface"));
521 box->faces[id]->hook_path_to_3dbox(SP_PATH(face_object));
522 ++face_id;
523 }
524 if (face_id < 6) {
525 //g_warning ("SVG representation of 3D boxes should contain exactly 6 paths (degenerate boxes are not yet supported).\n");
526 // TODO: Check whether it is safe to add the remaining paths to the box and do so in case it is.
527 // (But we also land here for newly created boxes where we shouldn't add any paths because
528 // This is done in sp_3dbox_write later on.
529 }
530 }
532 void
533 sp_3dbox_move_corner_in_XY_plane (SP3DBox *box, guint id, NR::Point pt, Box3D::Axis axes)
534 {
535 Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
537 NR::Point A (box->corners[id ^ Box3D::XY]);
538 if (Box3D::is_single_axis_direction (axes)) {
539 pt = Box3D::PerspectiveLine (box->corners[id], axes, persp).closest_to(pt);
540 }
542 /* set the 'front' corners */
543 box->corners[id] = pt;
545 Box3D::PerspectiveLine pl_one (A, Box3D::Y, persp);
546 Box3D::PerspectiveLine pl_two (pt, Box3D::X, persp);
547 box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
549 pl_one = Box3D::PerspectiveLine (A, Box3D::X, persp);
550 pl_two = Box3D::PerspectiveLine (pt, Box3D::Y, persp);
551 box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
553 /* set the 'rear' corners */
554 NR::Point B (box->corners[id ^ Box3D::XYZ]);
556 pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Z, persp);
557 pl_two = Box3D::PerspectiveLine (B, Box3D::Y, persp);
558 box->corners[id ^ Box3D::XZ] = pl_one.meet(pl_two);
560 pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XZ], Box3D::X, persp);
561 pl_two = Box3D::PerspectiveLine (pt, Box3D::Z, persp);
562 box->corners[id ^ Box3D::Z] = pl_one.meet(pl_two);
564 pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::Z], Box3D::Y, persp);
565 pl_two = Box3D::PerspectiveLine (B, Box3D::X, persp);
566 box->corners[id ^ Box3D::YZ] = pl_one.meet(pl_two);
568 }
570 void
571 sp_3dbox_move_corner_in_Z_direction (SP3DBox *box, guint id, NR::Point pt, bool constrained)
572 {
573 if (!constrained) sp_3dbox_move_corner_in_XY_plane (box, id, pt, Box3D::XY);
575 Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
577 /* set the four corners of the face containing corners[id] */
578 box->corners[id] = Box3D::PerspectiveLine (box->corners[id], Box3D::Z, persp).closest_to(pt);
580 Box3D::PerspectiveLine pl_one (box->corners[id], Box3D::X, persp);
581 Box3D::PerspectiveLine pl_two (box->corners[id ^ Box3D::XZ], Box3D::Z, persp);
582 box->corners[id ^ Box3D::X] = pl_one.meet(pl_two);
584 pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Y, persp);
585 pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XYZ], Box3D::Z, persp);
586 box->corners[id ^ Box3D::XY] = pl_one.meet(pl_two);
588 pl_one = Box3D::PerspectiveLine (box->corners[id], Box3D::Y, persp);
589 pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::YZ], Box3D::Z, persp);
590 box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two);
591 }
593 static void
594 sp_3dbox_reshape_edge_after_VP_toggling (SP3DBox *box, const guint corner, const Box3D::Axis axis, Box3D::Perspective3D *persp)
595 {
596 /* Hmm, perhaps we should simply use one of the corners as the pivot point.
597 But this way we minimize the amount of reshaping.
598 On second thought, we need to find a way to ensure that all boxes sharing the same
599 perspective are updated consistently _as a group_. That is, they should also retain
600 their relative positions towards each other. */
601 NR::Maybe<NR::Point> pt = sp_3dbox_get_midpoint_between_corners (box, corner, corner ^ axis);
602 g_return_if_fail (pt);
604 Box3D::Axis axis2 = ((axis == Box3D::Y) ? Box3D::X : Box3D::Y);
606 Box3D::PerspectiveLine line1 (box->corners[corner], axis2, persp);
607 Box3D::PerspectiveLine line2 (box->corners[corner ^ axis], axis2, persp);
609 Box3D::PerspectiveLine line3 (*pt, axis, persp);
611 NR::Point new_corner1 = line1.meet (line3);
612 NR::Point new_corner2 = line2.meet (line3);
614 box->corners[corner] = new_corner1;
615 box->corners[corner ^ axis] = new_corner2;
616 }
618 void
619 sp_3dbox_reshape_after_VP_toggling (SP3DBox *box, Box3D::Axis axis)
620 {
621 Box3D::Perspective3D *persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
622 std::pair<Box3D::Axis, Box3D::Axis> dirs = Box3D::get_remaining_axes (axis);
624 sp_3dbox_reshape_edge_after_VP_toggling (box, 0, axis, persp);
625 sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first, axis, persp);
626 sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first ^ dirs.second, axis, persp);
627 sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.second, axis, persp);
628 }
630 NR::Maybe<NR::Point>
631 sp_3dbox_get_center (SP3DBox *box)
632 {
633 return sp_3dbox_get_midpoint_between_corners (box, 0, 7);
634 }
636 // TODO: The following function can probably be rewritten in a much more elegant and robust way
637 // by using projective coordinates for all points and using the cross ratio.
638 NR::Maybe<NR::Point>
639 sp_3dbox_get_midpoint_between_corners (SP3DBox *box, guint id_corner1, guint id_corner2)
640 {
641 Box3D::Axis corner_axes = (Box3D::Axis) (id_corner1 ^ id_corner2);
643 // Is all this sufficiently precise also for degenerate cases?
644 if (sp_3dbox_corners_are_adjacent (id_corner1, id_corner2)) {
645 Box3D::Axis orth_dir = get_perpendicular_axis_direction (corner_axes);
647 Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2 ^ orth_dir]);
648 Box3D::Line diag2 (box->corners[id_corner1 ^ orth_dir], box->corners[id_corner2]);
649 NR::Maybe<NR::Point> adjacent_face_center = diag1.intersect(diag2);
651 if (!adjacent_face_center) return NR::Nothing();
653 Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box);
655 Box3D::PerspectiveLine pl (*adjacent_face_center, orth_dir, persp);
656 return pl.intersect(Box3D::PerspectiveLine(box->corners[id_corner1], corner_axes, persp));
657 } else {
658 Box3D::Axis dir = Box3D::extract_first_axis_direction (corner_axes);
659 Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2]);
660 Box3D::Line diag2 (box->corners[id_corner1 ^ dir], box->corners[id_corner2 ^ dir]);
661 return diag1.intersect(diag2);
662 }
663 }
665 static gchar *
666 sp_3dbox_get_corner_coords_string (SP3DBox *box, guint id)
667 {
668 id = id % 8;
669 Inkscape::SVGOStringStream os;
670 os << box->corners[id][NR::X] << "," << box->corners[id][NR::Y];
671 return g_strdup(os.str().c_str());
672 }
674 static std::pair<gdouble, gdouble>
675 sp_3dbox_get_coord_pair_from_string (const gchar *coords)
676 {
677 gchar **coordpair = g_strsplit( coords, ",", 0);
678 // We might as well rely on g_ascii_strtod to convert the NULL pointer to 0.0,
679 // but we include the following test anyway
680 if (coordpair[0] == NULL || coordpair[1] == NULL) {
681 g_strfreev (coordpair);
682 g_warning ("Coordinate conversion failed.\n");
683 return std::make_pair(0.0, 0.0);
684 }
686 gdouble coord1 = g_ascii_strtod(coordpair[0], NULL);
687 gdouble coord2 = g_ascii_strtod(coordpair[1], NULL);
688 g_strfreev (coordpair);
690 return std::make_pair(coord1, coord2);
691 }
693 static gchar *
694 sp_3dbox_get_perspective_string (SP3DBox *box)
695 {
697 return sp_3dbox_get_svg_descr_of_persp (SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box));
698 }
700 gchar *
701 sp_3dbox_get_svg_descr_of_persp (Box3D::Perspective3D *persp)
702 {
703 // FIXME: We should move this code to perspective3d.cpp, but this yields compiler errors. Why?
704 Inkscape::SVGOStringStream os;
706 Box3D::VanishingPoint vp = *(persp->get_vanishing_point (Box3D::X));
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::Y));
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 vp = *(persp->get_vanishing_point (Box3D::Z));
725 os << vp[NR::X] << "," << vp[NR::Y] << ",";
726 os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ",";
727 if (vp.is_finite()) {
728 os << "finite";
729 } else {
730 os << "infinite";
731 }
733 return g_strdup(os.str().c_str());
734 }
736 void sp_3dbox_update_perspective_lines()
737 {
738 SPEventContext *ec = inkscape_active_event_context();
739 if (!SP_IS_3DBOX_CONTEXT (ec))
740 return;
742 SP_3DBOX_CONTEXT (ec)->_vpdrag->updateLines();
743 }
745 /*
746 * Manipulates corner1 through corner4 to contain the indices of the corners
747 * from which the perspective lines in the direction of 'axis' emerge
748 */
749 void sp_3dbox_corners_for_perspective_lines (const SP3DBox * box, Box3D::Axis axis,
750 NR::Point &corner1, NR::Point &corner2, NR::Point &corner3, NR::Point &corner4)
751 {
752 // along which axis to switch when takint
753 Box3D::Axis switch_axis;
754 if (axis == Box3D::X || axis == Box3D::Y) {
755 switch_axis = (box->front_bits & axis) ? Box3D::Z : Box3D::NONE;
756 } else {
757 switch_axis = (box->front_bits & axis) ? Box3D::X : Box3D::NONE;
758 }
760 switch (axis) {
761 case Box3D::X:
762 corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
763 corner2 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
764 corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
765 corner4 = sp_3dbox_get_corner_along_edge (box, 6 ^ switch_axis, axis, Box3D::REAR);
766 break;
767 case Box3D::Y:
768 corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
769 corner2 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
770 corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR);
771 corner4 = sp_3dbox_get_corner_along_edge (box, 5 ^ switch_axis, axis, Box3D::REAR);
772 break;
773 case Box3D::Z:
774 corner1 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR);
775 corner2 = sp_3dbox_get_corner_along_edge (box, 3 ^ switch_axis, axis, Box3D::REAR);
776 corner3 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR);
777 corner4 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR);
778 break;
779 }
780 }
782 /**
783 * Returns the id of the corner on the edge along 'axis' and passing through 'corner' that
784 * lies on the front/rear face in this direction.
785 */
786 guint
787 sp_3dbox_get_corner_id_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
788 {
789 guint result;
790 guint other_corner = corner ^ axis;
791 Box3D::VanishingPoint *vp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point(axis);
792 if (vp->is_finite()) {
793 result = ( NR::L2 (vp->get_pos() - box->corners[corner])
794 < NR::L2 (vp->get_pos() - box->corners[other_corner]) ? other_corner : corner);
795 } else {
796 // clear the axis bit and switch to the appropriate corner along axis, depending on the value of front_bits
797 result = ((corner & (0xF ^ axis)) ^ (box->front_bits & axis));
798 }
800 if (rel_pos == Box3D::FRONT) {
801 return result;
802 } else {
803 return result ^ axis;
804 }
805 }
807 NR::Point
808 sp_3dbox_get_corner_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos)
809 {
810 return box->corners[sp_3dbox_get_corner_id_along_edge (box, corner, axis, rel_pos)];
811 }
813 guint
814 sp_3dbox_get_front_corner_id (const SP3DBox *box)
815 {
816 guint front_corner = 1; // this could in fact be any corner, but we choose the one that is normally in front
817 front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::X, Box3D::FRONT);
818 front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Y, Box3D::FRONT);
819 front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Z, Box3D::FRONT);
820 return front_corner;
821 }
823 // auxiliary functions
824 static void
825 sp_3dbox_update_corner_with_value_from_svg (SPObject *object, guint corner_id, const gchar *value)
826 {
827 if (value == NULL) return;
828 SP3DBox *box = SP_3DBOX(object);
830 std::pair<gdouble, gdouble> coord_pair = sp_3dbox_get_coord_pair_from_string (value);
831 box->corners[corner_id] = NR::Point (coord_pair.first, coord_pair.second);
832 sp_3dbox_recompute_corners (box, box->corners[2], box->corners[1], box->corners[5]);
833 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
834 }
836 static void
837 sp_3dbox_update_perspective (Box3D::Perspective3D *persp, const gchar *value)
838 {
839 // WARNING! This function changes the perspective associated to 'box'. Since there may be
840 // many other boxes linked to the same perspective, their perspective is also changed.
841 // If this behaviour is not desired in all cases, we need a different function.
842 if (value == NULL) return;
844 gchar **vps = g_strsplit( value, ",", 0);
845 for (int i = 0; i < 15; ++i) {
846 if (vps[i] == NULL) {
847 g_warning ("Malformed svg attribute 'perspective'\n");
848 return;
849 }
850 }
852 persp->set_vanishing_point (Box3D::X, g_ascii_strtod (vps[0], NULL), g_ascii_strtod (vps[1], NULL),
853 g_ascii_strtod (vps[2], NULL), g_ascii_strtod (vps[3], NULL),
854 strcmp (vps[4], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
855 persp->set_vanishing_point (Box3D::Y, g_ascii_strtod (vps[5], NULL), g_ascii_strtod (vps[6], NULL),
856 g_ascii_strtod (vps[7], NULL), g_ascii_strtod (vps[8], NULL),
857 strcmp (vps[9], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
858 persp->set_vanishing_point (Box3D::Z, g_ascii_strtod (vps[10], NULL), g_ascii_strtod (vps[11], NULL),
859 g_ascii_strtod (vps[12], NULL), g_ascii_strtod (vps[13], NULL),
860 strcmp (vps[14], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE);
862 // update the other boxes linked to the same perspective
863 persp->reshape_boxes (Box3D::XYZ);
864 }
866 /*
867 Local Variables:
868 mode:c++
869 c-file-style:"stroustrup"
870 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
871 indent-tabs-mode:nil
872 fill-column:99
873 End:
874 */
875 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :