1 /*
2 * SVG <box3d> implementation
3 *
4 * Authors:
5 * Maximilian Albert <Anhalter42@gmx.de>
6 * Lauris Kaplinski <lauris@kaplinski.com>
7 * bulia byak <buliabyak@users.sf.net>
8 * Abhishek Sharma
9 *
10 * Copyright (C) 2007 Authors
11 * Copyright (C) 1999-2002 Lauris Kaplinski
12 * Copyright (C) 2000-2001 Ximian, Inc.
13 *
14 * Released under GNU GPL, read the file 'COPYING' for more information
15 */
17 #include <glibmm/i18n.h>
18 #include "attributes.h"
19 #include "xml/document.h"
20 #include "xml/repr.h"
22 #include "box3d.h"
23 #include "box3d-side.h"
24 #include "box3d-context.h"
25 #include "proj_pt.h"
26 #include "transf_mat_3x4.h"
27 #include "perspective-line.h"
28 #include "inkscape.h"
29 #include "persp3d.h"
30 #include "line-geometry.h"
31 #include "persp3d-reference.h"
32 #include "uri.h"
33 #include <2geom/line.h>
34 #include "sp-guide.h"
35 #include "sp-namedview.h"
36 #include "preferences.h"
38 #include "desktop.h"
39 #include "desktop-handles.h"
40 #include "macros.h"
42 static void box3d_class_init(SPBox3DClass *klass);
43 static void box3d_init(SPBox3D *box3d);
45 static void box3d_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
46 static void box3d_release(SPObject *object);
47 static void box3d_set(SPObject *object, unsigned int key, const gchar *value);
48 static void box3d_update(SPObject *object, SPCtx *ctx, guint flags);
49 static Inkscape::XML::Node *box3d_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags);
51 static gchar *box3d_description(SPItem *item);
52 static Geom::Matrix box3d_set_transform(SPItem *item, Geom::Matrix const &xform);
53 static void box3d_convert_to_guides(SPItem *item);
55 static void box3d_ref_changed(SPObject *old_ref, SPObject *ref, SPBox3D *box);
57 static SPGroupClass *parent_class;
59 static gint counter = 0;
61 GType
62 box3d_get_type(void)
63 {
64 static GType type = 0;
66 if (!type) {
67 GTypeInfo info = {
68 sizeof(SPBox3DClass),
69 NULL, /* base_init */
70 NULL, /* base_finalize */
71 (GClassInitFunc) box3d_class_init,
72 NULL, /* class_finalize */
73 NULL, /* class_data */
74 sizeof(SPBox3D),
75 16, /* n_preallocs */
76 (GInstanceInitFunc) box3d_init,
77 NULL, /* value_table */
78 };
79 type = g_type_register_static(SP_TYPE_GROUP, "SPBox3D", &info, (GTypeFlags) 0);
80 }
82 return type;
83 }
85 static void
86 box3d_class_init(SPBox3DClass *klass)
87 {
88 SPObjectClass *sp_object_class = (SPObjectClass *) klass;
89 SPItemClass *item_class = (SPItemClass *) klass;
91 parent_class = (SPGroupClass *) g_type_class_ref(SP_TYPE_GROUP);
93 sp_object_class->build = box3d_build;
94 sp_object_class->release = box3d_release;
95 sp_object_class->set = box3d_set;
96 sp_object_class->write = box3d_write;
97 sp_object_class->update = box3d_update;
99 item_class->description = box3d_description;
100 item_class->set_transform = box3d_set_transform;
101 item_class->convert_to_guides = box3d_convert_to_guides;
102 }
104 static void
105 box3d_init(SPBox3D *box)
106 {
107 box->persp_href = NULL;
108 box->persp_ref = new Persp3DReference(SP_OBJECT(box));
109 }
111 static void
112 box3d_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
113 {
114 if (((SPObjectClass *) (parent_class))->build) {
115 ((SPObjectClass *) (parent_class))->build(object, document, repr);
116 }
118 SPBox3D *box = SP_BOX3D (object);
119 box->my_counter = counter++;
121 /* we initialize the z-orders to zero so that they are updated during dragging */
122 for (int i = 0; i < 6; ++i) {
123 box->z_orders[i] = 0;
124 }
126 // TODO: Create/link to the correct perspective
128 SPDocument *doc = SP_OBJECT_DOCUMENT(box);
129 if (!doc)
130 return;
132 box->persp_ref->changedSignal().connect(sigc::bind(sigc::ptr_fun(box3d_ref_changed), box));
134 object->readAttr( "inkscape:perspectiveID" );
135 object->readAttr( "inkscape:corner0" );
136 object->readAttr( "inkscape:corner7" );
137 }
139 /**
140 * Virtual release of SPBox3D members before destruction.
141 */
142 static void
143 box3d_release(SPObject *object)
144 {
145 SPBox3D *box = (SPBox3D *) object;
147 if (box->persp_href) {
148 g_free(box->persp_href);
149 }
151 // We have to store this here because the Persp3DReference gets destroyed below, but we need to
152 // access it to call persp3d_remove_box(), which cannot be called earlier because the reference
153 // needs to be destroyed first.
154 Persp3D *persp = box3d_get_perspective(box);
156 if (box->persp_ref) {
157 box->persp_ref->detach();
158 delete box->persp_ref;
159 box->persp_ref = NULL;
160 }
162 if (persp) {
163 persp3d_remove_box (persp, box);
164 /*
165 // TODO: This deletes a perspective when the last box referring to it is gone. Eventually,
166 // it would be nice to have this but currently it crashes when undoing/redoing box deletion
167 // Reason: When redoing a box deletion, the associated perspective is deleted twice, first
168 // by the following code and then again by the redo mechanism! Perhaps we should perform
169 // deletion of the perspective from another location "outside" the undo/redo mechanism?
170 if (persp->perspective_impl->boxes.empty()) {
171 SPDocument *doc = SP_OBJECT_DOCUMENT(box);
172 persp->deleteObject();
173 doc->setCurrentPersp3D(persp3d_document_first_persp(doc));
174 }
175 */
176 }
178 if (((SPObjectClass *) parent_class)->release)
179 ((SPObjectClass *) parent_class)->release(object);
180 }
182 static void
183 box3d_set(SPObject *object, unsigned int key, const gchar *value)
184 {
185 SPBox3D *box = SP_BOX3D(object);
187 switch (key) {
188 case SP_ATTR_INKSCAPE_BOX3D_PERSPECTIVE_ID:
189 if ( value && box->persp_href && ( strcmp(value, box->persp_href) == 0 ) ) {
190 /* No change, do nothing. */
191 } else {
192 if (box->persp_href) {
193 g_free(box->persp_href);
194 box->persp_href = NULL;
195 }
196 if (value) {
197 box->persp_href = g_strdup(value);
199 // Now do the attaching, which emits the changed signal.
200 try {
201 box->persp_ref->attach(Inkscape::URI(value));
202 } catch (Inkscape::BadURIException &e) {
203 g_warning("%s", e.what());
204 box->persp_ref->detach();
205 }
206 } else {
207 // Detach, which emits the changed signal.
208 box->persp_ref->detach();
209 }
210 }
212 // FIXME: Is the following update doubled by some call in either persp3d.cpp or vanishing_point_new.cpp?
213 box3d_position_set(box);
214 break;
215 case SP_ATTR_INKSCAPE_BOX3D_CORNER0:
216 if (value && strcmp(value, "0 : 0 : 0 : 0")) {
217 box->orig_corner0 = Proj::Pt3(value);
218 box->save_corner0 = box->orig_corner0;
219 box3d_position_set(box);
220 }
221 break;
222 case SP_ATTR_INKSCAPE_BOX3D_CORNER7:
223 if (value && strcmp(value, "0 : 0 : 0 : 0")) {
224 box->orig_corner7 = Proj::Pt3(value);
225 box->save_corner7 = box->orig_corner7;
226 box3d_position_set(box);
227 }
228 break;
229 default:
230 if (((SPObjectClass *) (parent_class))->set) {
231 ((SPObjectClass *) (parent_class))->set(object, key, value);
232 }
233 break;
234 }
235 }
237 /**
238 * Gets called when (re)attached to another perspective.
239 */
240 static void
241 box3d_ref_changed(SPObject *old_ref, SPObject *ref, SPBox3D *box)
242 {
243 if (old_ref) {
244 sp_signal_disconnect_by_data(old_ref, box);
245 persp3d_remove_box (SP_PERSP3D(old_ref), box);
246 }
247 if ( SP_IS_PERSP3D(ref) && ref != box ) // FIXME: Comparisons sane?
248 {
249 persp3d_add_box (SP_PERSP3D(ref), box);
250 }
251 }
253 static void
254 box3d_update(SPObject *object, SPCtx *ctx, guint flags)
255 {
256 if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
258 /* FIXME?: Perhaps the display updates of box sides should be instantiated from here, but this
259 causes evil update loops so it's all done from box3d_position_set, which is called from
260 various other places (like the handlers in object-edit.cpp, vanishing-point.cpp, etc. */
262 }
264 // Invoke parent method
265 if (((SPObjectClass *) (parent_class))->update)
266 ((SPObjectClass *) (parent_class))->update(object, ctx, flags);
267 }
270 static Inkscape::XML::Node * box3d_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
271 {
272 SPBox3D *box = SP_BOX3D(object);
274 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
275 // this is where we end up when saving as plain SVG (also in other circumstances?)
276 // thus we don' set "sodipodi:type" so that the box is only saved as an ordinary svg:g
277 repr = xml_doc->createElement("svg:g");
278 }
280 if (flags & SP_OBJECT_WRITE_EXT) {
282 if (box->persp_href) {
283 repr->setAttribute("inkscape:perspectiveID", box->persp_href);
284 } else {
285 /* box is not yet linked to a perspective; use the document's current perspective */
286 SPDocument *doc = SP_OBJECT_DOCUMENT(object);
287 if (box->persp_ref->getURI()) {
288 gchar *uri_string = box->persp_ref->getURI()->toString();
289 repr->setAttribute("inkscape:perspectiveID", uri_string);
290 g_free(uri_string);
291 } else {
292 Glib::ustring href = "#";
293 href += doc->getCurrentPersp3D()->getId();
294 repr->setAttribute("inkscape:perspectiveID", href.c_str());
295 }
296 }
298 gchar *coordstr0 = box->orig_corner0.coord_string();
299 gchar *coordstr7 = box->orig_corner7.coord_string();
300 repr->setAttribute("inkscape:corner0", coordstr0);
301 repr->setAttribute("inkscape:corner7", coordstr7);
302 g_free(coordstr0);
303 g_free(coordstr7);
305 box->orig_corner0.normalize();
306 box->orig_corner7.normalize();
308 box->save_corner0 = box->orig_corner0;
309 box->save_corner7 = box->orig_corner7;
310 }
312 if (((SPObjectClass *) (parent_class))->write) {
313 ((SPObjectClass *) (parent_class))->write(object, xml_doc, repr, flags);
314 }
316 return repr;
317 }
319 static gchar *
320 box3d_description(SPItem *item)
321 {
322 g_return_val_if_fail(SP_IS_BOX3D(item), NULL);
324 return g_strdup(_("<b>3D Box</b>"));
325 }
327 void box3d_position_set(SPBox3D *box)
328 {
329 /* This draws the curve and calls requestDisplayUpdate() for each side (the latter is done in
330 box3d_side_position_set() to avoid update conflicts with the parent box) */
331 for ( SPObject *child = box->firstChild(); child; child = child->getNext() ) {
332 if (SP_IS_BOX3D_SIDE(child)) {
333 box3d_side_position_set(SP_BOX3D_SIDE(child));
334 }
335 }
336 }
338 static Geom::Matrix
339 box3d_set_transform(SPItem *item, Geom::Matrix const &xform)
340 {
341 SPBox3D *box = SP_BOX3D(item);
343 // We don't apply the transform to the box directly but instead to its perspective (which is
344 // done in sp_selection_apply_affine). Here we only adjust strokes, patterns, etc.
346 Geom::Matrix ret(Geom::Matrix(xform).without_translation());
347 gdouble const sw = hypot(ret[0], ret[1]);
348 gdouble const sh = hypot(ret[2], ret[3]);
350 for ( SPObject *child = box->firstChild(); child; child = child->getNext() ) {
351 if (SP_IS_ITEM(child)) {
352 SPItem *childitem = SP_ITEM(child);
354 // Adjust stroke width
355 childitem->adjust_stroke(sqrt(fabs(sw * sh)));
357 // Adjust pattern fill
358 childitem->adjust_pattern(xform);
360 // Adjust gradient fill
361 childitem->adjust_gradient(xform);
363 // Adjust LPE
364 childitem->adjust_livepatheffect(xform);
365 }
366 }
368 return Geom::identity();
369 }
371 Proj::Pt3
372 box3d_get_proj_corner (guint id, Proj::Pt3 const &c0, Proj::Pt3 const &c7) {
373 return Proj::Pt3 ((id & Box3D::X) ? c7[Proj::X] : c0[Proj::X],
374 (id & Box3D::Y) ? c7[Proj::Y] : c0[Proj::Y],
375 (id & Box3D::Z) ? c7[Proj::Z] : c0[Proj::Z],
376 1.0);
377 }
379 Proj::Pt3
380 box3d_get_proj_corner (SPBox3D const *box, guint id) {
381 return Proj::Pt3 ((id & Box3D::X) ? box->orig_corner7[Proj::X] : box->orig_corner0[Proj::X],
382 (id & Box3D::Y) ? box->orig_corner7[Proj::Y] : box->orig_corner0[Proj::Y],
383 (id & Box3D::Z) ? box->orig_corner7[Proj::Z] : box->orig_corner0[Proj::Z],
384 1.0);
385 }
387 Geom::Point
388 box3d_get_corner_screen (SPBox3D const *box, guint id, bool item_coords) {
389 Proj::Pt3 proj_corner (box3d_get_proj_corner (box, id));
390 if (!box3d_get_perspective(box)) {
391 return Geom::Point (NR_HUGE, NR_HUGE);
392 }
393 Geom::Matrix const i2d (SP_ITEM(box)->i2d_affine ());
394 if (item_coords) {
395 return box3d_get_perspective(box)->perspective_impl->tmat.image(proj_corner).affine() * i2d.inverse();
396 } else {
397 return box3d_get_perspective(box)->perspective_impl->tmat.image(proj_corner).affine();
398 }
399 }
401 Proj::Pt3
402 box3d_get_proj_center (SPBox3D *box) {
403 box->orig_corner0.normalize();
404 box->orig_corner7.normalize();
405 return Proj::Pt3 ((box->orig_corner0[Proj::X] + box->orig_corner7[Proj::X]) / 2,
406 (box->orig_corner0[Proj::Y] + box->orig_corner7[Proj::Y]) / 2,
407 (box->orig_corner0[Proj::Z] + box->orig_corner7[Proj::Z]) / 2,
408 1.0);
409 }
411 Geom::Point
412 box3d_get_center_screen (SPBox3D *box) {
413 Proj::Pt3 proj_center (box3d_get_proj_center (box));
414 if (!box3d_get_perspective(box)) {
415 return Geom::Point (NR_HUGE, NR_HUGE);
416 }
417 Geom::Matrix const i2d (SP_ITEM(box)->i2d_affine ());
418 return box3d_get_perspective(box)->perspective_impl->tmat.image(proj_center).affine() * i2d.inverse();
419 }
421 /*
422 * To keep the snappoint from jumping randomly between the two lines when the mouse pointer is close to
423 * their intersection, we remember the last snapped line and keep snapping to this specific line as long
424 * as the distance from the intersection to the mouse pointer is less than remember_snap_threshold.
425 */
427 // Should we make the threshold settable in the preferences?
428 static double remember_snap_threshold = 30;
429 static guint remember_snap_index = 0;
431 static Proj::Pt3
432 box3d_snap (SPBox3D *box, int id, Proj::Pt3 const &pt_proj, Proj::Pt3 const &start_pt) {
433 double z_coord = start_pt[Proj::Z];
434 double diff_x = box->save_corner7[Proj::X] - box->save_corner0[Proj::X];
435 double diff_y = box->save_corner7[Proj::Y] - box->save_corner0[Proj::Y];
436 double x_coord = start_pt[Proj::X];
437 double y_coord = start_pt[Proj::Y];
438 Proj::Pt3 A_proj (x_coord, y_coord, z_coord, 1.0);
439 Proj::Pt3 B_proj (x_coord + diff_x, y_coord, z_coord, 1.0);
440 Proj::Pt3 C_proj (x_coord + diff_x, y_coord + diff_y, z_coord, 1.0);
441 Proj::Pt3 D_proj (x_coord, y_coord + diff_y, z_coord, 1.0);
442 Proj::Pt3 E_proj (x_coord - diff_x, y_coord + diff_y, z_coord, 1.0);
444 Persp3DImpl *persp_impl = box3d_get_perspective(box)->perspective_impl;
445 Geom::Point A = persp_impl->tmat.image(A_proj).affine();
446 Geom::Point B = persp_impl->tmat.image(B_proj).affine();
447 Geom::Point C = persp_impl->tmat.image(C_proj).affine();
448 Geom::Point D = persp_impl->tmat.image(D_proj).affine();
449 Geom::Point E = persp_impl->tmat.image(E_proj).affine();
450 Geom::Point pt = persp_impl->tmat.image(pt_proj).affine();
452 // TODO: Replace these lines between corners with lines from a corner to a vanishing point
453 // (this might help to prevent rounding errors if the box is small)
454 Box3D::Line pl1(A, B);
455 Box3D::Line pl2(A, D);
456 Box3D::Line diag1(A, (id == -1 || (!(id & Box3D::X) == !(id & Box3D::Y))) ? C : E);
457 Box3D::Line diag2(A, E); // diag2 is only taken into account if id equals -1, i.e., if we are snapping the center
459 int num_snap_lines = (id != -1) ? 3 : 4;
460 Geom::Point snap_pts[num_snap_lines];
462 snap_pts[0] = pl1.closest_to (pt);
463 snap_pts[1] = pl2.closest_to (pt);
464 snap_pts[2] = diag1.closest_to (pt);
465 if (id == -1) {
466 snap_pts[3] = diag2.closest_to (pt);
467 }
469 gdouble const zoom = inkscape_active_desktop()->current_zoom();
471 // determine the distances to all potential snapping points
472 double snap_dists[num_snap_lines];
473 for (int i = 0; i < num_snap_lines; ++i) {
474 snap_dists[i] = Geom::L2 (snap_pts[i] - pt) * zoom;
475 }
477 // while we are within a given tolerance of the starting point,
478 // keep snapping to the same point to avoid jumping
479 bool within_tolerance = true;
480 for (int i = 0; i < num_snap_lines; ++i) {
481 if (snap_dists[i] > remember_snap_threshold) {
482 within_tolerance = false;
483 break;
484 }
485 }
487 // find the closest snapping point
488 int snap_index = -1;
489 double snap_dist = NR_HUGE;
490 for (int i = 0; i < num_snap_lines; ++i) {
491 if (snap_dists[i] < snap_dist) {
492 snap_index = i;
493 snap_dist = snap_dists[i];
494 }
495 }
497 // snap to the closest point (or the previously remembered one
498 // if we are within tolerance of the starting point)
499 Geom::Point result;
500 if (within_tolerance) {
501 result = snap_pts[remember_snap_index];
502 } else {
503 remember_snap_index = snap_index;
504 result = snap_pts[snap_index];
505 }
506 return box3d_get_perspective(box)->perspective_impl->tmat.preimage (result, z_coord, Proj::Z);
507 }
509 SPBox3D * SPBox3D::createBox3D(SPItem * parent)
510 {
511 SPBox3D *box3d = 0;
512 Inkscape::XML::Document *xml_doc = parent->document->rdoc;
513 Inkscape::XML::Node *repr = xml_doc->createElement("svg:g");
514 repr->setAttribute("sodipodi:type", "inkscape:box3d");
515 box3d = reinterpret_cast<SPBox3D *>(parent->appendChildRepr(repr));
516 return box3d;
517 }
519 void
520 box3d_set_corner (SPBox3D *box, const guint id, Geom::Point const &new_pos, const Box3D::Axis movement, bool constrained) {
521 g_return_if_fail ((movement != Box3D::NONE) && (movement != Box3D::XYZ));
523 box->orig_corner0.normalize();
524 box->orig_corner7.normalize();
526 /* update corners 0 and 7 according to which handle was moved and to the axes of movement */
527 if (!(movement & Box3D::Z)) {
528 Persp3DImpl *persp_impl = box3d_get_perspective(box)->perspective_impl;
529 Proj::Pt3 pt_proj (persp_impl->tmat.preimage (new_pos, (id < 4) ? box->orig_corner0[Proj::Z] :
530 box->orig_corner7[Proj::Z], Proj::Z));
531 if (constrained) {
532 pt_proj = box3d_snap (box, id, pt_proj, box3d_get_proj_corner (id, box->save_corner0, box->save_corner7));
533 }
535 // normalizing pt_proj is essential because we want to mingle affine coordinates
536 pt_proj.normalize();
537 box->orig_corner0 = Proj::Pt3 ((id & Box3D::X) ? box->save_corner0[Proj::X] : pt_proj[Proj::X],
538 (id & Box3D::Y) ? box->save_corner0[Proj::Y] : pt_proj[Proj::Y],
539 box->save_corner0[Proj::Z],
540 1.0);
541 box->orig_corner7 = Proj::Pt3 ((id & Box3D::X) ? pt_proj[Proj::X] : box->save_corner7[Proj::X],
542 (id & Box3D::Y) ? pt_proj[Proj::Y] : box->save_corner7[Proj::Y],
543 box->save_corner7[Proj::Z],
544 1.0);
545 } else {
546 Persp3D *persp = box3d_get_perspective(box);
547 Persp3DImpl *persp_impl = box3d_get_perspective(box)->perspective_impl;
548 Box3D::PerspectiveLine pl(persp_impl->tmat.image(
549 box3d_get_proj_corner (id, box->save_corner0, box->save_corner7)).affine(),
550 Proj::Z, persp);
551 Geom::Point new_pos_snapped(pl.closest_to(new_pos));
552 Proj::Pt3 pt_proj (persp_impl->tmat.preimage (new_pos_snapped,
553 box3d_get_proj_corner (box, id)[(movement & Box3D::Y) ? Proj::X : Proj::Y],
554 (movement & Box3D::Y) ? Proj::X : Proj::Y));
555 bool corner0_move_x = !(id & Box3D::X) && (movement & Box3D::X);
556 bool corner0_move_y = !(id & Box3D::Y) && (movement & Box3D::Y);
557 bool corner7_move_x = (id & Box3D::X) && (movement & Box3D::X);
558 bool corner7_move_y = (id & Box3D::Y) && (movement & Box3D::Y);
559 // normalizing pt_proj is essential because we want to mingle affine coordinates
560 pt_proj.normalize();
561 box->orig_corner0 = Proj::Pt3 (corner0_move_x ? pt_proj[Proj::X] : box->orig_corner0[Proj::X],
562 corner0_move_y ? pt_proj[Proj::Y] : box->orig_corner0[Proj::Y],
563 (id & Box3D::Z) ? box->orig_corner0[Proj::Z] : pt_proj[Proj::Z],
564 1.0);
565 box->orig_corner7 = Proj::Pt3 (corner7_move_x ? pt_proj[Proj::X] : box->orig_corner7[Proj::X],
566 corner7_move_y ? pt_proj[Proj::Y] : box->orig_corner7[Proj::Y],
567 (id & Box3D::Z) ? pt_proj[Proj::Z] : box->orig_corner7[Proj::Z],
568 1.0);
569 }
570 // FIXME: Should we update the box here? If so, how?
571 }
573 void box3d_set_center (SPBox3D *box, Geom::Point const &new_pos, Geom::Point const &old_pos, const Box3D::Axis movement, bool constrained) {
574 g_return_if_fail ((movement != Box3D::NONE) && (movement != Box3D::XYZ));
576 box->orig_corner0.normalize();
577 box->orig_corner7.normalize();
579 Persp3D *persp = box3d_get_perspective(box);
580 if (!(movement & Box3D::Z)) {
581 double coord = (box->orig_corner0[Proj::Z] + box->orig_corner7[Proj::Z]) / 2;
582 double radx = (box->orig_corner7[Proj::X] - box->orig_corner0[Proj::X]) / 2;
583 double rady = (box->orig_corner7[Proj::Y] - box->orig_corner0[Proj::Y]) / 2;
585 Proj::Pt3 pt_proj (persp->perspective_impl->tmat.preimage (new_pos, coord, Proj::Z));
586 if (constrained) {
587 Proj::Pt3 old_pos_proj (persp->perspective_impl->tmat.preimage (old_pos, coord, Proj::Z));
588 old_pos_proj.normalize();
589 pt_proj = box3d_snap (box, -1, pt_proj, old_pos_proj);
590 }
591 // normalizing pt_proj is essential because we want to mingle affine coordinates
592 pt_proj.normalize();
593 box->orig_corner0 = Proj::Pt3 ((movement & Box3D::X) ? pt_proj[Proj::X] - radx : box->orig_corner0[Proj::X],
594 (movement & Box3D::Y) ? pt_proj[Proj::Y] - rady : box->orig_corner0[Proj::Y],
595 box->orig_corner0[Proj::Z],
596 1.0);
597 box->orig_corner7 = Proj::Pt3 ((movement & Box3D::X) ? pt_proj[Proj::X] + radx : box->orig_corner7[Proj::X],
598 (movement & Box3D::Y) ? pt_proj[Proj::Y] + rady : box->orig_corner7[Proj::Y],
599 box->orig_corner7[Proj::Z],
600 1.0);
601 } else {
602 double coord = (box->orig_corner0[Proj::X] + box->orig_corner7[Proj::X]) / 2;
603 double radz = (box->orig_corner7[Proj::Z] - box->orig_corner0[Proj::Z]) / 2;
605 Box3D::PerspectiveLine pl(old_pos, Proj::Z, persp);
606 Geom::Point new_pos_snapped(pl.closest_to(new_pos));
607 Proj::Pt3 pt_proj (persp->perspective_impl->tmat.preimage (new_pos_snapped, coord, Proj::X));
609 /* normalizing pt_proj is essential because we want to mingle affine coordinates */
610 pt_proj.normalize();
611 box->orig_corner0 = Proj::Pt3 (box->orig_corner0[Proj::X],
612 box->orig_corner0[Proj::Y],
613 pt_proj[Proj::Z] - radz,
614 1.0);
615 box->orig_corner7 = Proj::Pt3 (box->orig_corner7[Proj::X],
616 box->orig_corner7[Proj::Y],
617 pt_proj[Proj::Z] + radz,
618 1.0);
619 }
620 }
622 /*
623 * Manipulates corner1 through corner4 to contain the indices of the corners
624 * from which the perspective lines in the direction of 'axis' emerge
625 */
626 void box3d_corners_for_PLs (const SPBox3D * box, Proj::Axis axis,
627 Geom::Point &corner1, Geom::Point &corner2, Geom::Point &corner3, Geom::Point &corner4)
628 {
629 Persp3D *persp = box3d_get_perspective(box);
630 g_return_if_fail (persp);
631 Persp3DImpl *persp_impl = persp->perspective_impl;
632 //box->orig_corner0.normalize();
633 //box->orig_corner7.normalize();
634 double coord = (box->orig_corner0[axis] > box->orig_corner7[axis]) ?
635 box->orig_corner0[axis] :
636 box->orig_corner7[axis];
638 Proj::Pt3 c1, c2, c3, c4;
639 // FIXME: This can certainly be done more elegantly/efficiently than by a case-by-case analysis.
640 switch (axis) {
641 case Proj::X:
642 c1 = Proj::Pt3 (coord, box->orig_corner0[Proj::Y], box->orig_corner0[Proj::Z], 1.0);
643 c2 = Proj::Pt3 (coord, box->orig_corner7[Proj::Y], box->orig_corner0[Proj::Z], 1.0);
644 c3 = Proj::Pt3 (coord, box->orig_corner7[Proj::Y], box->orig_corner7[Proj::Z], 1.0);
645 c4 = Proj::Pt3 (coord, box->orig_corner0[Proj::Y], box->orig_corner7[Proj::Z], 1.0);
646 break;
647 case Proj::Y:
648 c1 = Proj::Pt3 (box->orig_corner0[Proj::X], coord, box->orig_corner0[Proj::Z], 1.0);
649 c2 = Proj::Pt3 (box->orig_corner7[Proj::X], coord, box->orig_corner0[Proj::Z], 1.0);
650 c3 = Proj::Pt3 (box->orig_corner7[Proj::X], coord, box->orig_corner7[Proj::Z], 1.0);
651 c4 = Proj::Pt3 (box->orig_corner0[Proj::X], coord, box->orig_corner7[Proj::Z], 1.0);
652 break;
653 case Proj::Z:
654 c1 = Proj::Pt3 (box->orig_corner7[Proj::X], box->orig_corner7[Proj::Y], coord, 1.0);
655 c2 = Proj::Pt3 (box->orig_corner7[Proj::X], box->orig_corner0[Proj::Y], coord, 1.0);
656 c3 = Proj::Pt3 (box->orig_corner0[Proj::X], box->orig_corner0[Proj::Y], coord, 1.0);
657 c4 = Proj::Pt3 (box->orig_corner0[Proj::X], box->orig_corner7[Proj::Y], coord, 1.0);
658 break;
659 default:
660 return;
661 }
662 corner1 = persp_impl->tmat.image(c1).affine();
663 corner2 = persp_impl->tmat.image(c2).affine();
664 corner3 = persp_impl->tmat.image(c3).affine();
665 corner4 = persp_impl->tmat.image(c4).affine();
666 }
668 /* Auxiliary function: Checks whether the half-line from A to B crosses the line segment joining C and D */
669 static bool
670 box3d_half_line_crosses_joining_line (Geom::Point const &A, Geom::Point const &B,
671 Geom::Point const &C, Geom::Point const &D) {
672 Geom::Point n0 = (B - A).ccw();
673 double d0 = dot(n0,A);
675 Geom::Point n1 = (D - C).ccw();
676 double d1 = dot(n1,C);
678 Geom::Line lineAB(A,B);
679 Geom::Line lineCD(C,D);
681 Geom::OptCrossing inters = Geom::OptCrossing(); // empty by default
682 try
683 {
684 inters = Geom::intersection(lineAB, lineCD);
685 }
686 catch (Geom::InfiniteSolutions e)
687 {
688 // We're probably dealing with parallel lines, so they don't really cross
689 return false;
690 }
692 if (!inters) {
693 return false;
694 }
696 Geom::Point E = lineAB.pointAt((*inters).ta); // the point of intersection
698 if ((dot(C,n0) < d0) == (dot(D,n0) < d0)) {
699 // C and D lie on the same side of the line AB
700 return false;
701 }
702 if ((dot(A,n1) < d1) != (dot(B,n1) < d1)) {
703 // A and B lie on different sides of the line CD
704 return true;
705 } else if (Geom::distance(E,A) < Geom::distance(E,B)) {
706 // The line CD passes on the "wrong" side of A
707 return false;
708 }
710 // The line CD passes on the "correct" side of A
711 return true;
712 }
714 static bool
715 box3d_XY_axes_are_swapped (SPBox3D *box) {
716 Persp3D *persp = box3d_get_perspective(box);
717 g_return_val_if_fail(persp, false);
718 Box3D::PerspectiveLine l1(box3d_get_corner_screen(box, 3, false), Proj::X, persp);
719 Box3D::PerspectiveLine l2(box3d_get_corner_screen(box, 3, false), Proj::Y, persp);
720 Geom::Point v1(l1.direction());
721 Geom::Point v2(l2.direction());
722 v1.normalize();
723 v2.normalize();
725 return (v1[Geom::X]*v2[Geom::Y] - v1[Geom::Y]*v2[Geom::X] > 0);
726 }
728 static inline void
729 box3d_aux_set_z_orders (int z_orders[6], int a, int b, int c, int d, int e, int f) {
730 z_orders[0] = a;
731 z_orders[1] = b;
732 z_orders[2] = c;
733 z_orders[3] = d;
734 z_orders[4] = e;
735 z_orders[5] = f;
736 }
738 static inline void
739 box3d_swap_z_orders (int z_orders[6]) {
740 int tmp;
741 for (int i = 0; i < 3; ++i) {
742 tmp = z_orders[i];
743 z_orders[i] = z_orders[5-i];
744 z_orders[5-i] = tmp;
745 }
746 }
748 /*
749 * In standard perspective we have:
750 * 2 = front face
751 * 1 = top face
752 * 0 = left face
753 * 3 = right face
754 * 4 = bottom face
755 * 5 = rear face
756 */
758 /* All VPs infinite */
759 static void
760 box3d_set_new_z_orders_case0 (SPBox3D *box, int z_orders[6], Box3D::Axis central_axis) {
761 Persp3D *persp = box3d_get_perspective(box);
762 Geom::Point xdir(persp3d_get_infinite_dir(persp, Proj::X));
763 Geom::Point ydir(persp3d_get_infinite_dir(persp, Proj::Y));
764 Geom::Point zdir(persp3d_get_infinite_dir(persp, Proj::Z));
766 bool swapped = box3d_XY_axes_are_swapped(box);
768 switch(central_axis) {
769 case Box3D::X:
770 if (!swapped) {
771 box3d_aux_set_z_orders (z_orders, 2, 0, 4, 1, 3, 5);
772 } else {
773 box3d_aux_set_z_orders (z_orders, 3, 1, 5, 2, 4, 0);
774 }
775 break;
776 case Box3D::Y:
777 if (!swapped) {
778 box3d_aux_set_z_orders (z_orders, 2, 3, 1, 4, 0, 5);
779 } else {
780 box3d_aux_set_z_orders (z_orders, 5, 0, 4, 1, 3, 2);
781 }
782 break;
783 case Box3D::Z:
784 if (!swapped) {
785 box3d_aux_set_z_orders (z_orders, 2, 0, 1, 4, 3, 5);
786 } else {
787 box3d_aux_set_z_orders (z_orders, 5, 3, 4, 1, 0, 2);
788 }
789 break;
790 case Box3D::NONE:
791 if (!swapped) {
792 box3d_aux_set_z_orders (z_orders, 2, 3, 4, 1, 0, 5);
793 } else {
794 box3d_aux_set_z_orders (z_orders, 5, 0, 1, 4, 3, 2);
795 }
796 break;
797 default:
798 g_assert_not_reached();
799 break;
800 }
801 }
803 /* Precisely one finite VP */
804 static void
805 box3d_set_new_z_orders_case1 (SPBox3D *box, int z_orders[6], Box3D::Axis central_axis, Box3D::Axis fin_axis) {
806 Persp3D *persp = box3d_get_perspective(box);
807 Geom::Point vp(persp3d_get_VP(persp, Box3D::toProj(fin_axis)).affine());
809 // note: in some of the case distinctions below we rely upon the fact that oaxis1 and oaxis2 are ordered
810 Box3D::Axis oaxis1 = Box3D::get_remaining_axes(fin_axis).first;
811 Box3D::Axis oaxis2 = Box3D::get_remaining_axes(fin_axis).second;
812 int inside1 = 0;
813 int inside2 = 0;
814 inside1 = box3d_pt_lies_in_PL_sector (box, vp, 3, 3 ^ oaxis2, oaxis1);
815 inside2 = box3d_pt_lies_in_PL_sector (box, vp, 3, 3 ^ oaxis1, oaxis2);
817 bool swapped = box3d_XY_axes_are_swapped(box);
819 switch(central_axis) {
820 case Box3D::X:
821 if (!swapped) {
822 box3d_aux_set_z_orders (z_orders, 2, 4, 0, 1, 3, 5);
823 } else {
824 box3d_aux_set_z_orders (z_orders, 5, 3, 1, 0, 2, 4);
825 }
826 break;
827 case Box3D::Y:
828 if (inside2 > 0) {
829 box3d_aux_set_z_orders (z_orders, 1, 2, 3, 0, 5, 4);
830 } else if (inside2 < 0) {
831 box3d_aux_set_z_orders (z_orders, 2, 3, 1, 4, 0, 5);
832 } else {
833 if (!swapped) {
834 box3d_aux_set_z_orders (z_orders, 2, 3, 1, 5, 0, 4);
835 } else {
836 box3d_aux_set_z_orders (z_orders, 5, 0, 4, 1, 3, 2);
837 }
838 }
839 break;
840 case Box3D::Z:
841 if (inside2) {
842 if (!swapped) {
843 box3d_aux_set_z_orders (z_orders, 2, 1, 3, 0, 4, 5);
844 } else {
845 box3d_aux_set_z_orders (z_orders, 5, 3, 4, 0, 1, 2);
846 }
847 } else if (inside1) {
848 if (!swapped) {
849 box3d_aux_set_z_orders (z_orders, 2, 0, 1, 4, 3, 5);
850 } else {
851 box3d_aux_set_z_orders (z_orders, 5, 3, 4, 1, 0, 2);
852 }
853 } else {
854 // "regular" case
855 if (!swapped) {
856 box3d_aux_set_z_orders (z_orders, 0, 1, 2, 5, 4, 3);
857 } else {
858 box3d_aux_set_z_orders (z_orders, 5, 3, 4, 0, 2, 1);
859 }
860 }
861 break;
862 case Box3D::NONE:
863 if (!swapped) {
864 box3d_aux_set_z_orders (z_orders, 2, 3, 4, 5, 0, 1);
865 } else {
866 box3d_aux_set_z_orders (z_orders, 5, 0, 1, 3, 2, 4);
867 }
868 break;
869 default:
870 g_assert_not_reached();
871 }
872 }
874 /* Precisely 2 finite VPs */
875 static void
876 box3d_set_new_z_orders_case2 (SPBox3D *box, int z_orders[6], Box3D::Axis central_axis, Box3D::Axis /*infinite_axis*/) {
877 Persp3D *persp = box3d_get_perspective(box);
879 Geom::Point c3(box3d_get_corner_screen(box, 3, false));
880 Geom::Point xdir(persp3d_get_PL_dir_from_pt(persp, c3, Proj::X));
881 Geom::Point ydir(persp3d_get_PL_dir_from_pt(persp, c3, Proj::Y));
882 Geom::Point zdir(persp3d_get_PL_dir_from_pt(persp, c3, Proj::Z));
884 bool swapped = box3d_XY_axes_are_swapped(box);
886 int insidexy = box3d_VP_lies_in_PL_sector (box, Proj::X, 3, 3 ^ Box3D::Z, Box3D::Y);
887 //int insidexz = box3d_VP_lies_in_PL_sector (box, Proj::X, 3, 3 ^ Box3D::Y, Box3D::Z);
889 int insideyx = box3d_VP_lies_in_PL_sector (box, Proj::Y, 3, 3 ^ Box3D::Z, Box3D::X);
890 int insideyz = box3d_VP_lies_in_PL_sector (box, Proj::Y, 3, 3 ^ Box3D::X, Box3D::Z);
892 //int insidezx = box3d_VP_lies_in_PL_sector (box, Proj::Z, 3, 3 ^ Box3D::Y, Box3D::X);
893 int insidezy = box3d_VP_lies_in_PL_sector (box, Proj::Z, 3, 3 ^ Box3D::X, Box3D::Y);
895 switch(central_axis) {
896 case Box3D::X:
897 if (!swapped) {
898 if (insidezy == -1) {
899 box3d_aux_set_z_orders (z_orders, 2, 4, 0, 1, 3, 5);
900 } else if (insidexy == 1) {
901 box3d_aux_set_z_orders (z_orders, 2, 4, 0, 5, 1, 3);
902 } else {
903 box3d_aux_set_z_orders (z_orders, 2, 4, 0, 1, 3, 5);
904 }
905 } else {
906 if (insideyz == -1) {
907 box3d_aux_set_z_orders (z_orders, 3, 1, 5, 0, 2, 4);
908 } else {
909 if (!swapped) {
910 box3d_aux_set_z_orders (z_orders, 3, 1, 5, 2, 4, 0);
911 } else {
912 if (insidexy == 0) {
913 box3d_aux_set_z_orders (z_orders, 3, 5, 1, 0, 2, 4);
914 } else {
915 box3d_aux_set_z_orders (z_orders, 3, 1, 5, 0, 2, 4);
916 }
917 }
918 }
919 }
920 break;
921 case Box3D::Y:
922 if (!swapped) {
923 if (insideyz == 1) {
924 box3d_aux_set_z_orders (z_orders, 2, 3, 1, 0, 5, 4);
925 } else {
926 box3d_aux_set_z_orders (z_orders, 2, 3, 1, 5, 0, 4);
927 }
928 } else {
929 if (insideyx == 1) {
930 box3d_aux_set_z_orders (z_orders, 4, 0, 5, 1, 3, 2);
931 } else {
932 box3d_aux_set_z_orders (z_orders, 5, 0, 4, 1, 3, 2);
933 }
934 }
935 break;
936 case Box3D::Z:
937 if (!swapped) {
938 if (insidezy == 1) {
939 box3d_aux_set_z_orders (z_orders, 2, 1, 0, 4, 3, 5);
940 } else if (insidexy == -1) {
941 box3d_aux_set_z_orders (z_orders, 2, 1, 0, 5, 4, 3);
942 } else {
943 box3d_aux_set_z_orders (z_orders, 2, 0, 1, 5, 3, 4);
944 }
945 } else {
946 box3d_aux_set_z_orders (z_orders, 3, 4, 5, 1, 0, 2);
947 }
948 break;
949 case Box3D::NONE:
950 if (!swapped) {
951 box3d_aux_set_z_orders (z_orders, 2, 3, 4, 1, 0, 5);
952 } else {
953 box3d_aux_set_z_orders (z_orders, 5, 0, 1, 4, 3, 2);
954 }
955 break;
956 default:
957 g_assert_not_reached();
958 break;
959 }
960 }
962 /*
963 * It can happen that during dragging the box is everted.
964 * In this case the opposite sides in this direction need to be swapped
965 */
966 static Box3D::Axis
967 box3d_everted_directions (SPBox3D *box) {
968 Box3D::Axis ev = Box3D::NONE;
970 box->orig_corner0.normalize();
971 box->orig_corner7.normalize();
973 if (box->orig_corner0[Proj::X] < box->orig_corner7[Proj::X])
974 ev = (Box3D::Axis) (ev ^ Box3D::X);
975 if (box->orig_corner0[Proj::Y] < box->orig_corner7[Proj::Y])
976 ev = (Box3D::Axis) (ev ^ Box3D::Y);
977 if (box->orig_corner0[Proj::Z] > box->orig_corner7[Proj::Z]) // FIXME: Remove the need to distinguish signs among the cases
978 ev = (Box3D::Axis) (ev ^ Box3D::Z);
980 return ev;
981 }
983 static void
984 box3d_swap_sides(int z_orders[6], Box3D::Axis axis) {
985 int pos1 = -1;
986 int pos2 = -1;
988 for (int i = 0; i < 6; ++i) {
989 if (!(Box3D::int_to_face(z_orders[i]) & axis)) {
990 if (pos1 == -1) {
991 pos1 = i;
992 } else {
993 pos2 = i;
994 break;
995 }
996 }
997 }
999 int tmp = z_orders[pos1];
1000 z_orders[pos1] = z_orders[pos2];
1001 z_orders[pos2] = tmp;
1002 }
1005 bool
1006 box3d_recompute_z_orders (SPBox3D *box) {
1007 Persp3D *persp = box3d_get_perspective(box);
1009 if (!persp)
1010 return false;
1012 int z_orders[6];
1014 Geom::Point c3(box3d_get_corner_screen(box, 3, false));
1016 // determine directions from corner3 to the VPs
1017 int num_finite = 0;
1018 Box3D::Axis axis_finite = Box3D::NONE;
1019 Box3D::Axis axis_infinite = Box3D::NONE;
1020 Geom::Point dirs[3];
1021 for (int i = 0; i < 3; ++i) {
1022 dirs[i] = persp3d_get_PL_dir_from_pt(persp, c3, Box3D::toProj(Box3D::axes[i]));
1023 if (persp3d_VP_is_finite(persp->perspective_impl, Proj::axes[i])) {
1024 num_finite++;
1025 axis_finite = Box3D::axes[i];
1026 } else {
1027 axis_infinite = Box3D::axes[i];
1028 }
1029 }
1031 // determine the "central" axis (if there is one)
1032 Box3D::Axis central_axis = Box3D::NONE;
1033 if(Box3D::lies_in_sector(dirs[0], dirs[1], dirs[2])) {
1034 central_axis = Box3D::Z;
1035 } else if(Box3D::lies_in_sector(dirs[1], dirs[2], dirs[0])) {
1036 central_axis = Box3D::X;
1037 } else if(Box3D::lies_in_sector(dirs[2], dirs[0], dirs[1])) {
1038 central_axis = Box3D::Y;
1039 }
1041 switch (num_finite) {
1042 case 0:
1043 // TODO: Remark: In this case (and maybe one of the others, too) the z-orders for all boxes
1044 // coincide, hence only need to be computed once in a more central location.
1045 box3d_set_new_z_orders_case0(box, z_orders, central_axis);
1046 break;
1047 case 1:
1048 box3d_set_new_z_orders_case1(box, z_orders, central_axis, axis_finite);
1049 break;
1050 case 2:
1051 case 3:
1052 box3d_set_new_z_orders_case2(box, z_orders, central_axis, axis_infinite);
1053 break;
1054 default:
1055 /*
1056 * For each VP F, check wether the half-line from the corner3 to F crosses the line segment
1057 * joining the other two VPs. If this is the case, it determines the "central" corner from
1058 * which the visible sides can be deduced. Otherwise, corner3 is the central corner.
1059 */
1060 // FIXME: We should eliminate the use of Geom::Point altogether
1061 Box3D::Axis central_axis = Box3D::NONE;
1062 Geom::Point vp_x = persp3d_get_VP(persp, Proj::X).affine();
1063 Geom::Point vp_y = persp3d_get_VP(persp, Proj::Y).affine();
1064 Geom::Point vp_z = persp3d_get_VP(persp, Proj::Z).affine();
1065 Geom::Point vpx(vp_x[Geom::X], vp_x[Geom::Y]);
1066 Geom::Point vpy(vp_y[Geom::X], vp_y[Geom::Y]);
1067 Geom::Point vpz(vp_z[Geom::X], vp_z[Geom::Y]);
1069 Geom::Point c3 = box3d_get_corner_screen(box, 3, false);
1070 Geom::Point corner3(c3[Geom::X], c3[Geom::Y]);
1072 if (box3d_half_line_crosses_joining_line (corner3, vpx, vpy, vpz)) {
1073 central_axis = Box3D::X;
1074 } else if (box3d_half_line_crosses_joining_line (corner3, vpy, vpz, vpx)) {
1075 central_axis = Box3D::Y;
1076 } else if (box3d_half_line_crosses_joining_line (corner3, vpz, vpx, vpy)) {
1077 central_axis = Box3D::Z;
1078 }
1080 unsigned int central_corner = 3 ^ central_axis;
1081 if (central_axis == Box3D::Z) {
1082 central_corner = central_corner ^ Box3D::XYZ;
1083 }
1084 if (box3d_XY_axes_are_swapped(box)) {
1085 central_corner = central_corner ^ Box3D::XYZ;
1086 }
1088 Geom::Point c1(box3d_get_corner_screen(box, 1, false));
1089 Geom::Point c2(box3d_get_corner_screen(box, 2, false));
1090 Geom::Point c7(box3d_get_corner_screen(box, 7, false));
1092 Geom::Point corner1(c1[Geom::X], c1[Geom::Y]);
1093 Geom::Point corner2(c2[Geom::X], c2[Geom::Y]);
1094 Geom::Point corner7(c7[Geom::X], c7[Geom::Y]);
1095 // FIXME: At present we don't use the information about central_corner computed above.
1096 switch (central_axis) {
1097 case Box3D::Y:
1098 if (!box3d_half_line_crosses_joining_line(vpz, vpy, corner3, corner2)) {
1099 box3d_aux_set_z_orders (z_orders, 2, 3, 1, 5, 0, 4);
1100 } else {
1101 // degenerate case
1102 box3d_aux_set_z_orders (z_orders, 2, 1, 3, 0, 5, 4);
1103 }
1104 break;
1106 case Box3D::Z:
1107 if (box3d_half_line_crosses_joining_line(vpx, vpz, corner3, corner1)) {
1108 // degenerate case
1109 box3d_aux_set_z_orders (z_orders, 2, 0, 1, 4, 3, 5);
1110 } else if (box3d_half_line_crosses_joining_line(vpx, vpy, corner3, corner7)) {
1111 // degenerate case
1112 box3d_aux_set_z_orders (z_orders, 2, 1, 0, 5, 3, 4);
1113 } else {
1114 box3d_aux_set_z_orders (z_orders, 2, 1, 0, 3, 4, 5);
1115 }
1116 break;
1118 case Box3D::X:
1119 if (box3d_half_line_crosses_joining_line(vpz, vpx, corner3, corner1)) {
1120 // degenerate case
1121 box3d_aux_set_z_orders (z_orders, 2, 1, 0, 4, 5, 3);
1122 } else {
1123 box3d_aux_set_z_orders (z_orders, 2, 4, 0, 5, 1, 3);
1124 }
1125 break;
1127 case Box3D::NONE:
1128 box3d_aux_set_z_orders (z_orders, 2, 3, 4, 1, 0, 5);
1129 break;
1131 default:
1132 g_assert_not_reached();
1133 break;
1134 } // end default case
1135 }
1137 // TODO: If there are still errors in z-orders of everted boxes, we need to choose a variable corner
1138 // instead of the hard-coded corner #3 in the computations above
1139 Box3D::Axis ev = box3d_everted_directions(box);
1140 for (int i = 0; i < 3; ++i) {
1141 if (ev & Box3D::axes[i]) {
1142 box3d_swap_sides(z_orders, Box3D::axes[i]);
1143 }
1144 }
1146 // Check whether anything actually changed
1147 for (int i = 0; i < 6; ++i) {
1148 if (box->z_orders[i] != z_orders[i]) {
1149 for (int j = i; j < 6; ++j) {
1150 box->z_orders[j] = z_orders[j];
1151 }
1152 return true;
1153 }
1154 }
1155 return false;
1156 }
1158 static std::map<int, Box3DSide *> box3d_get_sides(SPBox3D *box)
1159 {
1160 std::map<int, Box3DSide *> sides;
1161 for ( SPObject *side = box->firstChild(); side; side = side->getNext() ) {
1162 if (SP_IS_BOX3D_SIDE(side)){
1163 Box3DSide *bside = SP_BOX3D_SIDE(side);
1164 sides[Box3D::face_to_int(bside->getFaceId())] = bside;
1165 }
1166 }
1167 sides.erase(-1);
1168 return sides;
1169 }
1172 // TODO: Check whether the box is everted in any direction and swap the sides opposite to this direction
1173 void
1174 box3d_set_z_orders (SPBox3D *box) {
1175 // For efficiency reasons, we only set the new z-orders if something really changed
1176 if (box3d_recompute_z_orders (box)) {
1177 std::map<int, Box3DSide *> sides = box3d_get_sides(box);
1178 std::map<int, Box3DSide *>::iterator side;
1179 for (unsigned int i = 0; i < 6; ++i) {
1180 side = sides.find(box->z_orders[i]);
1181 if (side != sides.end()) {
1182 SP_ITEM((*side).second)->lowerToBottom();
1183 }
1184 }
1185 }
1186 }
1188 /*
1189 * Auxiliary function for z-order recomputing:
1190 * Determines whether \a pt lies in the sector formed by the two PLs from the corners with IDs
1191 * \a i21 and \a id2 to the VP in direction \a axis. If the VP is infinite, we say that \a pt
1192 * lies in the sector if it lies between the two (parallel) PLs.
1193 * \ret * 0 if \a pt doesn't lie in the sector
1194 * * 1 if \a pt lies in the sector and either VP is finite of VP is infinite and the direction
1195 * from the edge between the two corners to \a pt points towards the VP
1196 * * -1 otherwise
1197 */
1198 // TODO: Maybe it would be useful to have a similar method for projective points pt because then we
1199 // can use it for VPs and perhaps merge the case distinctions during z-order recomputation.
1200 int
1201 box3d_pt_lies_in_PL_sector (SPBox3D const *box, Geom::Point const &pt, int id1, int id2, Box3D::Axis axis) {
1202 Persp3D *persp = box3d_get_perspective(box);
1204 // the two corners
1205 Geom::Point c1(box3d_get_corner_screen(box, id1, false));
1206 Geom::Point c2(box3d_get_corner_screen(box, id2, false));
1208 int ret = 0;
1209 if (persp3d_VP_is_finite(persp->perspective_impl, Box3D::toProj(axis))) {
1210 Geom::Point vp(persp3d_get_VP(persp, Box3D::toProj(axis)).affine());
1211 Geom::Point v1(c1 - vp);
1212 Geom::Point v2(c2 - vp);
1213 Geom::Point w(pt - vp);
1214 ret = static_cast<int>(Box3D::lies_in_sector(v1, v2, w));
1215 } else {
1216 Box3D::PerspectiveLine pl1(c1, Box3D::toProj(axis), persp);
1217 Box3D::PerspectiveLine pl2(c2, Box3D::toProj(axis), persp);
1218 if (pl1.lie_on_same_side(pt, c2) && pl2.lie_on_same_side(pt, c1)) {
1219 // test whether pt lies "towards" or "away from" the VP
1220 Box3D::Line edge(c1,c2);
1221 Geom::Point c3(box3d_get_corner_screen(box, id1 ^ axis, false));
1222 if (edge.lie_on_same_side(pt, c3)) {
1223 ret = 1;
1224 } else {
1225 ret = -1;
1226 }
1227 }
1228 }
1229 return ret;
1230 }
1232 int
1233 box3d_VP_lies_in_PL_sector (SPBox3D const *box, Proj::Axis vpdir, int id1, int id2, Box3D::Axis axis) {
1234 Persp3D *persp = box3d_get_perspective(box);
1236 if (!persp3d_VP_is_finite(persp->perspective_impl, vpdir)) {
1237 return 0;
1238 } else {
1239 return box3d_pt_lies_in_PL_sector(box, persp3d_get_VP(persp, vpdir).affine(), id1, id2, axis);
1240 }
1241 }
1243 /* swap the coordinates of corner0 and corner7 along the specified axis */
1244 static void
1245 box3d_swap_coords(SPBox3D *box, Proj::Axis axis, bool smaller = true) {
1246 box->orig_corner0.normalize();
1247 box->orig_corner7.normalize();
1248 if ((box->orig_corner0[axis] < box->orig_corner7[axis]) != smaller) {
1249 double tmp = box->orig_corner0[axis];
1250 box->orig_corner0[axis] = box->orig_corner7[axis];
1251 box->orig_corner7[axis] = tmp;
1252 }
1253 // Should we also swap the coordinates of save_corner0 and save_corner7?
1254 }
1256 /* ensure that the coordinates of corner0 and corner7 are in the correct order (to prevent everted boxes) */
1257 void
1258 box3d_relabel_corners(SPBox3D *box) {
1259 box3d_swap_coords(box, Proj::X, false);
1260 box3d_swap_coords(box, Proj::Y, false);
1261 box3d_swap_coords(box, Proj::Z, true);
1262 }
1264 static void
1265 box3d_check_for_swapped_coords(SPBox3D *box, Proj::Axis axis, bool smaller) {
1266 box->orig_corner0.normalize();
1267 box->orig_corner7.normalize();
1269 if ((box->orig_corner0[axis] < box->orig_corner7[axis]) != smaller) {
1270 box->swapped = (Box3D::Axis) (box->swapped | Proj::toAffine(axis));
1271 } else {
1272 box->swapped = (Box3D::Axis) (box->swapped & ~Proj::toAffine(axis));
1273 }
1274 }
1276 static void
1277 box3d_exchange_coords(SPBox3D *box) {
1278 box->orig_corner0.normalize();
1279 box->orig_corner7.normalize();
1281 for (int i = 0; i < 3; ++i) {
1282 if (box->swapped & Box3D::axes[i]) {
1283 double tmp = box->orig_corner0[i];
1284 box->orig_corner0[i] = box->orig_corner7[i];
1285 box->orig_corner7[i] = tmp;
1286 }
1287 }
1288 }
1290 void
1291 box3d_check_for_swapped_coords(SPBox3D *box) {
1292 box3d_check_for_swapped_coords(box, Proj::X, false);
1293 box3d_check_for_swapped_coords(box, Proj::Y, false);
1294 box3d_check_for_swapped_coords(box, Proj::Z, true);
1296 box3d_exchange_coords(box);
1297 }
1299 static void box3d_extract_boxes_rec(SPObject *obj, std::list<SPBox3D *> &boxes) {
1300 if (SP_IS_BOX3D(obj)) {
1301 boxes.push_back(SP_BOX3D(obj));
1302 } else if (SP_IS_GROUP(obj)) {
1303 for ( SPObject *child = obj->firstChild(); child; child = child->getNext() ) {
1304 box3d_extract_boxes_rec(child, boxes);
1305 }
1306 }
1307 }
1309 std::list<SPBox3D *>
1310 box3d_extract_boxes(SPObject *obj) {
1311 std::list<SPBox3D *> boxes;
1312 box3d_extract_boxes_rec(obj, boxes);
1313 return boxes;
1314 }
1316 Persp3D *
1317 box3d_get_perspective(SPBox3D const *box) {
1318 return box->persp_ref->getObject();
1319 }
1321 void
1322 box3d_switch_perspectives(SPBox3D *box, Persp3D *old_persp, Persp3D *new_persp, bool recompute_corners) {
1323 if (recompute_corners) {
1324 box->orig_corner0.normalize();
1325 box->orig_corner7.normalize();
1326 double z0 = box->orig_corner0[Proj::Z];
1327 double z7 = box->orig_corner7[Proj::Z];
1328 Geom::Point corner0_screen = box3d_get_corner_screen(box, 0, false);
1329 Geom::Point corner7_screen = box3d_get_corner_screen(box, 7, false);
1331 box->orig_corner0 = new_persp->perspective_impl->tmat.preimage(corner0_screen, z0, Proj::Z);
1332 box->orig_corner7 = new_persp->perspective_impl->tmat.preimage(corner7_screen, z7, Proj::Z);
1333 }
1335 persp3d_remove_box (old_persp, box);
1336 persp3d_add_box (new_persp, box);
1338 Glib::ustring href = "#";
1339 href += new_persp->getId();
1340 box->setAttribute("inkscape:perspectiveID", href.c_str());
1341 }
1343 /* Converts the 3D box to an ordinary SPGroup, adds it to the XML tree at the same position as
1344 the original box and deletes the latter */
1345 SPGroup *box3d_convert_to_group(SPBox3D *box)
1346 {
1347 SPDocument *doc = SP_OBJECT_DOCUMENT(box);
1348 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
1350 // remember position of the box
1351 int pos = box->getPosition();
1353 // remember important attributes
1354 gchar const *id = box->getAttribute("id");
1355 gchar const *style = box->getAttribute("style");
1356 gchar const *mask = box->getAttribute("mask");
1357 gchar const *clip_path = box->getAttribute("clip-path");
1359 // create a new group and add the sides (converted to ordinary paths) as its children
1360 Inkscape::XML::Node *grepr = xml_doc->createElement("svg:g");
1362 for ( SPObject *child = box->firstChild(); child; child = child->getNext() ) {
1363 if (SP_IS_BOX3D_SIDE(child)) {
1364 Inkscape::XML::Node *repr = box3d_side_convert_to_path(SP_BOX3D_SIDE(child));
1365 grepr->appendChild(repr);
1366 } else {
1367 g_warning("Non-side item encountered as child of a 3D box.");
1368 }
1369 }
1371 // add the new group to the box's parent and set remembered position
1372 SPObject *parent = SP_OBJECT_PARENT(box);
1373 parent->appendChild(grepr);
1374 grepr->setPosition(pos);
1375 grepr->setAttribute("style", style);
1376 if (mask)
1377 grepr->setAttribute("mask", mask);
1378 if (clip_path)
1379 grepr->setAttribute("clip-path", clip_path);
1381 SP_OBJECT(box)->deleteObject(true);
1383 grepr->setAttribute("id", id);
1385 return SP_GROUP(doc->getObjectByRepr(grepr));
1386 }
1388 static inline void
1389 box3d_push_back_corner_pair(SPBox3D *box, std::list<std::pair<Geom::Point, Geom::Point> > &pts, int c1, int c2) {
1390 pts.push_back(std::make_pair(box3d_get_corner_screen(box, c1, false),
1391 box3d_get_corner_screen(box, c2, false)));
1392 }
1394 void
1395 box3d_convert_to_guides(SPItem *item) {
1396 SPBox3D *box = SP_BOX3D(item);
1397 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1399 if (!prefs->getBool("/tools/shapes/3dbox/convertguides", true)) {
1400 box->convert_to_guides();
1401 return;
1402 }
1404 std::list<std::pair<Geom::Point, Geom::Point> > pts;
1406 /* perspective lines in X direction */
1407 box3d_push_back_corner_pair(box, pts, 0, 1);
1408 box3d_push_back_corner_pair(box, pts, 2, 3);
1409 box3d_push_back_corner_pair(box, pts, 4, 5);
1410 box3d_push_back_corner_pair(box, pts, 6, 7);
1412 /* perspective lines in Y direction */
1413 box3d_push_back_corner_pair(box, pts, 0, 2);
1414 box3d_push_back_corner_pair(box, pts, 1, 3);
1415 box3d_push_back_corner_pair(box, pts, 4, 6);
1416 box3d_push_back_corner_pair(box, pts, 5, 7);
1418 /* perspective lines in Z direction */
1419 box3d_push_back_corner_pair(box, pts, 0, 4);
1420 box3d_push_back_corner_pair(box, pts, 1, 5);
1421 box3d_push_back_corner_pair(box, pts, 2, 6);
1422 box3d_push_back_corner_pair(box, pts, 3, 7);
1424 sp_guide_pt_pairs_to_guides(inkscape_active_desktop(), pts);
1425 }
1427 /*
1428 Local Variables:
1429 mode:c++
1430 c-file-style:"stroustrup"
1431 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1432 indent-tabs-mode:nil
1433 fill-column:99
1434 End:
1435 */
1436 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :