Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
box3d.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * SVG <box3d> implementation
4 *
5 * Authors:
6 * Maximilian Albert <Anhalter42@gmx.de>
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * bulia byak <buliabyak@users.sf.net>
9 * Abhishek Sharma
10 * Jon A. Cruz <jon@joncruz.org>
11 *
12 * Copyright (C) 2007 Authors
13 * Copyright (C) 1999-2002 Lauris Kaplinski
14 * Copyright (C) 2000-2001 Ximian, Inc.
15 *
16 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
17 */
18
19#include "box3d.h"
20
21#include <glibmm/i18n.h>
22#include <2geom/line.h>
23
24#include "attributes.h"
25#include "xml/document.h"
26
27#include "bad-uri-exception.h"
28#include "box3d-side.h"
29#include "ui/tools/box3d-tool.h"
30#include "perspective-line.h"
31#include "persp3d-reference.h"
32#include "uri.h"
33#include "sp-guide.h"
34
35#include "desktop.h"
36
37static void box3d_ref_changed(SPObject *old_ref, SPObject *ref, SPBox3D *box);
38
39static gint counter = 0;
40
42 this->my_counter = 0;
43 this->swapped = Box3D::NONE;
44
45 this->persp_href = nullptr;
46 this->persp_ref = new Persp3DReference(this);
47
48 /* we initialize the z-orders to zero so that they are updated during dragging */
49 for (int & z_order : z_orders) {
50 z_order = 0;
51 }
52}
53
54SPBox3D::~SPBox3D() = default;
55
58
60
61 /* we initialize the z-orders to zero so that they are updated during dragging */
62 for (int & z_order : z_orders) {
63 z_order = 0;
64 }
65
66 // TODO: Create/link to the correct perspective
67
68 if ( document ) {
69 persp_ref->changedSignal().connect(sigc::bind(sigc::ptr_fun(box3d_ref_changed), this));
70
74 }
75}
76
78 SPBox3D* object = this;
79 SPBox3D *box = object;
80
81 if (box->persp_href) {
82 g_free(box->persp_href);
83 }
84
85 // We have to store this here because the Persp3DReference gets destroyed below, but we need to
86 // access it to call Persp3D::remove_box(), which cannot be called earlier because the reference
87 // needs to be destroyed first.
88 Persp3D *persp = box->get_perspective();
89
90 if (box->persp_ref) {
91 box->persp_ref->detach();
92 delete box->persp_ref;
93 box->persp_ref = nullptr;
94 }
95 if (persp) {
96 persp->remove_box (box);
97
98 if (persp->perspective_impl->boxes.empty()) {
99 SPDocument *doc = box->document;
101 }
102 }
103
105}
106
107void SPBox3D::set(SPAttr key, const gchar* value) {
108 SPBox3D* object = this;
109 SPBox3D *box = object;
110
111 switch (key) {
113 if ( value && box->persp_href && ( strcmp(value, box->persp_href) == 0 ) ) {
114 /* No change, do nothing. */
115 } else {
116 if (box->persp_href) {
117 g_free(box->persp_href);
118 box->persp_href = nullptr;
119 }
120 if (value) {
121 box->persp_href = g_strdup(value);
122
123 // Now do the attaching, which emits the changed signal.
124 try {
125 box->persp_ref->attach(Inkscape::URI(value));
126 } catch (Inkscape::BadURIException &e) {
127 g_warning("%s", e.what());
128 box->persp_ref->detach();
129 }
130 } else {
131 // Detach, which emits the changed signal.
132 box->persp_ref->detach();
133 }
134 }
135
136 // FIXME: Is the following update doubled by some call in either persp3d.cpp or vanishing_point_new.cpp?
137 box->position_set();
138 break;
140 if (value && strcmp(value, "0 : 0 : 0 : 0")) {
141 box->orig_corner0 = Proj::Pt3(value);
142 box->save_corner0 = box->orig_corner0;
143 box->position_set();
144 }
145 break;
147 if (value && strcmp(value, "0 : 0 : 0 : 0")) {
148 box->orig_corner7 = Proj::Pt3(value);
149 box->save_corner7 = box->orig_corner7;
150 box->position_set();
151 }
152 break;
153 default:
154 SPGroup::set(key, value);
155 break;
156 }
157}
158
162static void
164{
165 if (old_ref) {
166 auto oldPersp = cast<Persp3D>(old_ref);
167 if (oldPersp) {
168 oldPersp->remove_box(box);
169 }
170 }
171 auto persp = cast<Persp3D>(ref);
172 if ( persp && (ref != box) ) // FIXME: Comparisons sane?
173 {
174 persp->add_box(box);
175 }
176}
177
178void SPBox3D::update(SPCtx *ctx, guint flags) {
179 if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
180
181 /* FIXME?: Perhaps the display updates of box sides should be instantiated from here, but this
182 causes evil update loops so it's all done from SPBox3D::position_set, which is called from
183 various other places (like the handlers in shape-editor-knotholders.cpp, vanishing-point.cpp, etc. */
184
185 }
186
187 // Invoke parent method
188 SPGroup::update(ctx, flags);
189}
190
192 SPBox3D* object = this;
193 SPBox3D *box = object;
194
195 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
196 // this is where we end up when saving as plain SVG (also in other circumstances?)
197 // thus we don' set "sodipodi:type" so that the box is only saved as an ordinary svg:g
198 repr = xml_doc->createElement("svg:g");
199 }
200
201 if (flags & SP_OBJECT_WRITE_EXT) {
202
203 if (box->persp_href) {
204 repr->setAttribute("inkscape:perspectiveID", box->persp_href);
205 } else {
206 /* box is not yet linked to a perspective; use the document's current perspective */
207 SPDocument *doc = object->document;
208 if (box->persp_ref->getURI()) {
209 auto uri_string = box->persp_ref->getURI()->str();
210 repr->setAttributeOrRemoveIfEmpty("inkscape:perspectiveID", uri_string);
211 } else {
212 Glib::ustring href = "#";
213 href += doc->getCurrentPersp3D()->getId();
214 repr->setAttribute("inkscape:perspectiveID", href);
215 }
216 }
217
218 gchar *coordstr0 = box->orig_corner0.coord_string();
219 gchar *coordstr7 = box->orig_corner7.coord_string();
220 repr->setAttribute("inkscape:corner0", coordstr0);
221 repr->setAttribute("inkscape:corner7", coordstr7);
222 g_free(coordstr0);
223 g_free(coordstr7);
224
225 box->orig_corner0.normalize();
226 box->orig_corner7.normalize();
227
228 box->save_corner0 = box->orig_corner0;
229 box->save_corner7 = box->orig_corner7;
230 }
231
232 SPGroup::write(xml_doc, repr, flags);
233
234 return repr;
235}
236
238 return _("3D Box");
239}
240
242{
243 /* This draws the curve and calls requestDisplayUpdate() for each side (the latter is done in
244 Box3DSide::position_set() to avoid update conflicts with the parent box) */
245 for (auto& obj: this->children) {
246 auto side = cast<Box3DSide>(&obj);
247 if (side) {
248 side->position_set();
249 }
250 }
251}
252
254 // We don't apply the transform to the box directly but instead to its perspective (which is
255 // done in sp_selection_apply_affine). Here we only adjust strokes, patterns, etc.
256
257 Geom::Affine ret(Geom::Affine(xform).withoutTranslation());
258 gdouble const sw = hypot(ret[0], ret[1]);
259 gdouble const sh = hypot(ret[2], ret[3]);
260
261 for (auto& child: children) {
262 auto childitem = cast<SPItem>(&child);
263 if (childitem) {
264 // Adjust stroke width
265 childitem->adjust_stroke(sqrt(fabs(sw * sh)));
266
267 // Adjust pattern fill
268 childitem->adjust_pattern(xform);
269
270 // Adjust gradient fill
271 childitem->adjust_gradient(xform);
272 }
273 }
274
275 return Geom::identity();
276}
277
278static Proj::Pt3
279box3d_get_proj_corner (guint id, Proj::Pt3 const &c0, Proj::Pt3 const &c7) {
280 return Proj::Pt3 ((id & Box3D::X) ? c7[Proj::X] : c0[Proj::X],
281 (id & Box3D::Y) ? c7[Proj::Y] : c0[Proj::Y],
282 (id & Box3D::Z) ? c7[Proj::Z] : c0[Proj::Z],
283 1.0);
284}
285
286static Proj::Pt3
287box3d_get_proj_corner (SPBox3D const *box, guint id) {
288 return Proj::Pt3 ((id & Box3D::X) ? box->orig_corner7[Proj::X] : box->orig_corner0[Proj::X],
289 (id & Box3D::Y) ? box->orig_corner7[Proj::Y] : box->orig_corner0[Proj::Y],
290 (id & Box3D::Z) ? box->orig_corner7[Proj::Z] : box->orig_corner0[Proj::Z],
291 1.0);
292}
293
295SPBox3D::get_corner_screen (guint id, bool item_coords) const {
296 Proj::Pt3 proj_corner (box3d_get_proj_corner (this, id));
297 if (!this->get_perspective()) {
299 }
300 Geom::Affine const i2d(this->i2dt_affine ());
301 if (item_coords) {
302 return this->get_perspective()->perspective_impl->tmat.image(proj_corner).affine() * i2d.inverse();
303 } else {
304 return this->get_perspective()->perspective_impl->tmat.image(proj_corner).affine();
305 }
306}
307
310 this->orig_corner0.normalize();
311 this->orig_corner7.normalize();
312 return Proj::Pt3 ((this->orig_corner0[Proj::X] + this->orig_corner7[Proj::X]) / 2,
313 (this->orig_corner0[Proj::Y] + this->orig_corner7[Proj::Y]) / 2,
314 (this->orig_corner0[Proj::Z] + this->orig_corner7[Proj::Z]) / 2,
315 1.0);
316}
317
320 Proj::Pt3 proj_center (this->get_proj_center ());
321 if (!this->get_perspective()) {
323 }
324 Geom::Affine const i2d( this->i2dt_affine() );
325 return this->get_perspective()->perspective_impl->tmat.image(proj_center).affine() * i2d.inverse();
326}
327
328/*
329 * To keep the snappoint from jumping randomly between the two lines when the mouse pointer is close to
330 * their intersection, we remember the last snapped line and keep snapping to this specific line as long
331 * as the distance from the intersection to the mouse pointer is less than remember_snap_threshold.
332 */
333
334// Should we make the threshold settable in the preferences?
335static double remember_snap_threshold = 30;
336static guint remember_snap_index = 0;
337
338// constant for sizing the array of points to be considered:
339static const int MAX_POINT_COUNT = 4;
340
341static Proj::Pt3
342box3d_snap (SPBox3D *box, int id, Proj::Pt3 const &pt_proj, Proj::Pt3 const &start_pt) {
343 double z_coord = start_pt[Proj::Z];
344 double diff_x = box->save_corner7[Proj::X] - box->save_corner0[Proj::X];
345 double diff_y = box->save_corner7[Proj::Y] - box->save_corner0[Proj::Y];
346 double x_coord = start_pt[Proj::X];
347 double y_coord = start_pt[Proj::Y];
348 Proj::Pt3 A_proj (x_coord, y_coord, z_coord, 1.0);
349 Proj::Pt3 B_proj (x_coord + diff_x, y_coord, z_coord, 1.0);
350 Proj::Pt3 C_proj (x_coord + diff_x, y_coord + diff_y, z_coord, 1.0);
351 Proj::Pt3 D_proj (x_coord, y_coord + diff_y, z_coord, 1.0);
352 Proj::Pt3 E_proj (x_coord - diff_x, y_coord + diff_y, z_coord, 1.0);
353
354 auto persp_impl = box->get_perspective()->perspective_impl.get();
355 Geom::Point A = persp_impl->tmat.image(A_proj).affine();
356 Geom::Point B = persp_impl->tmat.image(B_proj).affine();
357 Geom::Point C = persp_impl->tmat.image(C_proj).affine();
358 Geom::Point D = persp_impl->tmat.image(D_proj).affine();
359 Geom::Point E = persp_impl->tmat.image(E_proj).affine();
360 Geom::Point pt = persp_impl->tmat.image(pt_proj).affine();
361
362 // TODO: Replace these lines between corners with lines from a corner to a vanishing point
363 // (this might help to prevent rounding errors if the box is small)
364 Box3D::Line pl1(A, B);
365 Box3D::Line pl2(A, D);
366 Box3D::Line diag1(A, (id == -1 || (!(id & Box3D::X) == !(id & Box3D::Y))) ? C : E);
367 Box3D::Line diag2(A, E); // diag2 is only taken into account if id equals -1, i.e., if we are snapping the center
368
369 int num_snap_lines = (id != -1) ? 3 : 4;
371
372 snap_pts[0] = pl1.closest_to (pt);
373 snap_pts[1] = pl2.closest_to (pt);
374 snap_pts[2] = diag1.closest_to (pt);
375 if (id == -1) {
376 snap_pts[3] = diag2.closest_to (pt);
377 }
378
379 gdouble const zoom = SP_ACTIVE_DESKTOP->current_zoom();
380
381 // determine the distances to all potential snapping points
382 double snap_dists[MAX_POINT_COUNT];
383 for (int i = 0; i < num_snap_lines; ++i) {
384 snap_dists[i] = Geom::L2 (snap_pts[i] - pt) * zoom;
385 }
386
387 // while we are within a given tolerance of the starting point,
388 // keep snapping to the same point to avoid jumping
389 bool within_tolerance = true;
390 for (int i = 0; i < num_snap_lines; ++i) {
391 if (snap_dists[i] > remember_snap_threshold) {
392 within_tolerance = false;
393 break;
394 }
395 }
396
397 // find the closest snapping point
398 int snap_index = -1;
399 double snap_dist = Geom::infinity();
400 for (int i = 0; i < num_snap_lines; ++i) {
401 if (snap_dists[i] < snap_dist) {
402 snap_index = i;
403 snap_dist = snap_dists[i];
404 }
405 }
406
407 // snap to the closest point (or the previously remembered one
408 // if we are within tolerance of the starting point)
410 if (within_tolerance) {
411 result = snap_pts[remember_snap_index];
412 } else {
413 remember_snap_index = snap_index;
414 result = snap_pts[snap_index];
415 }
416 return box->get_perspective()->perspective_impl->tmat.preimage (result, z_coord, Proj::Z);
417}
418
420{
421 SPBox3D *box3d = nullptr;
422 Inkscape::XML::Document *xml_doc = parent->document->getReprDoc();
423 Inkscape::XML::Node *repr = xml_doc->createElement("svg:g");
424 repr->setAttribute("sodipodi:type", "inkscape:box3d");
425 box3d = reinterpret_cast<SPBox3D *>(parent->appendChildRepr(repr));
426 return box3d;
427}
428
429void
430SPBox3D::set_corner (const guint id, Geom::Point const &new_pos, const Box3D::Axis movement, bool constrained) {
431 g_return_if_fail ((movement != Box3D::NONE) && (movement != Box3D::XYZ));
432
433 this->orig_corner0.normalize();
434 this->orig_corner7.normalize();
435
436 /* update corners 0 and 7 according to which handle was moved and to the axes of movement */
437 if (!(movement & Box3D::Z)) {
438 auto persp_impl = get_perspective()->perspective_impl.get();
439 Proj::Pt3 pt_proj (persp_impl->tmat.preimage (new_pos, (id < 4) ? this->orig_corner0[Proj::Z] :
440 this->orig_corner7[Proj::Z], Proj::Z));
441 if (constrained) {
442 pt_proj = box3d_snap (this, id, pt_proj, box3d_get_proj_corner (id, this->save_corner0, this->save_corner7));
443 }
444
445 // normalizing pt_proj is essential because we want to mingle affine coordinates
446 pt_proj.normalize();
447 this->orig_corner0 = Proj::Pt3 ((id & Box3D::X) ? this->save_corner0[Proj::X] : pt_proj[Proj::X],
448 (id & Box3D::Y) ? this->save_corner0[Proj::Y] : pt_proj[Proj::Y],
449 this->save_corner0[Proj::Z],
450 1.0);
451 this->orig_corner7 = Proj::Pt3 ((id & Box3D::X) ? pt_proj[Proj::X] : this->save_corner7[Proj::X],
452 (id & Box3D::Y) ? pt_proj[Proj::Y] : this->save_corner7[Proj::Y],
453 this->save_corner7[Proj::Z],
454 1.0);
455 } else {
456 Persp3D *persp = this->get_perspective();
457 auto persp_impl = persp->perspective_impl.get();
458 Box3D::PerspectiveLine pl(persp_impl->tmat.image(
459 box3d_get_proj_corner (id, this->save_corner0, this->save_corner7)).affine(),
460 Proj::Z, persp);
461 Geom::Point new_pos_snapped(pl.closest_to(new_pos));
462 Proj::Pt3 pt_proj (persp_impl->tmat.preimage (new_pos_snapped,
463 box3d_get_proj_corner (this, id)[(movement & Box3D::Y) ? Proj::X : Proj::Y],
464 (movement & Box3D::Y) ? Proj::X : Proj::Y));
465 bool corner0_move_x = !(id & Box3D::X) && (movement & Box3D::X);
466 bool corner0_move_y = !(id & Box3D::Y) && (movement & Box3D::Y);
467 bool corner7_move_x = (id & Box3D::X) && (movement & Box3D::X);
468 bool corner7_move_y = (id & Box3D::Y) && (movement & Box3D::Y);
469 // normalizing pt_proj is essential because we want to mingle affine coordinates
470 pt_proj.normalize();
471 this->orig_corner0 = Proj::Pt3 (corner0_move_x ? pt_proj[Proj::X] : this->orig_corner0[Proj::X],
472 corner0_move_y ? pt_proj[Proj::Y] : this->orig_corner0[Proj::Y],
473 (id & Box3D::Z) ? this->orig_corner0[Proj::Z] : pt_proj[Proj::Z],
474 1.0);
475 this->orig_corner7 = Proj::Pt3 (corner7_move_x ? pt_proj[Proj::X] : this->orig_corner7[Proj::X],
476 corner7_move_y ? pt_proj[Proj::Y] : this->orig_corner7[Proj::Y],
477 (id & Box3D::Z) ? pt_proj[Proj::Z] : this->orig_corner7[Proj::Z],
478 1.0);
479 }
480 // FIXME: Should we update the box here? If so, how?
481}
482
483void SPBox3D::set_center (Geom::Point const &new_pos, Geom::Point const &old_pos, const Box3D::Axis movement, bool constrained) {
484 g_return_if_fail ((movement != Box3D::NONE) && (movement != Box3D::XYZ));
485
486 this->orig_corner0.normalize();
487 this->orig_corner7.normalize();
488
489 Persp3D *persp = this->get_perspective();
490 if (!(movement & Box3D::Z)) {
491 double coord = (this->orig_corner0[Proj::Z] + this->orig_corner7[Proj::Z]) / 2;
492 double radx = (this->orig_corner7[Proj::X] - this->orig_corner0[Proj::X]) / 2;
493 double rady = (this->orig_corner7[Proj::Y] - this->orig_corner0[Proj::Y]) / 2;
494
495 Proj::Pt3 pt_proj (persp->perspective_impl->tmat.preimage (new_pos, coord, Proj::Z));
496 if (constrained) {
497 Proj::Pt3 old_pos_proj (persp->perspective_impl->tmat.preimage (old_pos, coord, Proj::Z));
498 old_pos_proj.normalize();
499 pt_proj = box3d_snap (this, -1, pt_proj, old_pos_proj);
500 }
501 // normalizing pt_proj is essential because we want to mingle affine coordinates
502 pt_proj.normalize();
503 this->orig_corner0 = Proj::Pt3 ((movement & Box3D::X) ? pt_proj[Proj::X] - radx : this->orig_corner0[Proj::X],
504 (movement & Box3D::Y) ? pt_proj[Proj::Y] - rady : this->orig_corner0[Proj::Y],
505 this->orig_corner0[Proj::Z],
506 1.0);
507 this->orig_corner7 = Proj::Pt3 ((movement & Box3D::X) ? pt_proj[Proj::X] + radx : this->orig_corner7[Proj::X],
508 (movement & Box3D::Y) ? pt_proj[Proj::Y] + rady : this->orig_corner7[Proj::Y],
509 this->orig_corner7[Proj::Z],
510 1.0);
511 } else {
512 double coord = (this->orig_corner0[Proj::X] + this->orig_corner7[Proj::X]) / 2;
513 double radz = (this->orig_corner7[Proj::Z] - this->orig_corner0[Proj::Z]) / 2;
514
515 Box3D::PerspectiveLine pl(old_pos, Proj::Z, persp);
516 Geom::Point new_pos_snapped(pl.closest_to(new_pos));
517 Proj::Pt3 pt_proj (persp->perspective_impl->tmat.preimage (new_pos_snapped, coord, Proj::X));
518
519 /* normalizing pt_proj is essential because we want to mingle affine coordinates */
520 pt_proj.normalize();
522 this->orig_corner0[Proj::Y],
523 pt_proj[Proj::Z] - radz,
524 1.0);
525 this->orig_corner7 = Proj::Pt3 (this->orig_corner7[Proj::X],
526 this->orig_corner7[Proj::Y],
527 pt_proj[Proj::Z] + radz,
528 1.0);
529 }
530}
531
532/*
533 * Manipulates corner1 through corner4 to contain the indices of the corners
534 * from which the perspective lines in the direction of 'axis' emerge
535 */
537 Geom::Point &corner1, Geom::Point &corner2, Geom::Point &corner3, Geom::Point &corner4) const
538{
539 Persp3D *persp = this->get_perspective();
540 g_return_if_fail (persp);
541 auto persp_impl = persp->perspective_impl.get();
542 //this->orig_corner0.normalize();
543 //this->orig_corner7.normalize();
544 double coord = (this->orig_corner0[axis] > this->orig_corner7[axis]) ?
545 this->orig_corner0[axis] :
546 this->orig_corner7[axis];
547
548 Proj::Pt3 c1, c2, c3, c4;
549 // FIXME: This can certainly be done more elegantly/efficiently than by a case-by-case analysis.
550 switch (axis) {
551 case Proj::X:
552 c1 = Proj::Pt3 (coord, this->orig_corner0[Proj::Y], this->orig_corner0[Proj::Z], 1.0);
553 c2 = Proj::Pt3 (coord, this->orig_corner7[Proj::Y], this->orig_corner0[Proj::Z], 1.0);
554 c3 = Proj::Pt3 (coord, this->orig_corner7[Proj::Y], this->orig_corner7[Proj::Z], 1.0);
555 c4 = Proj::Pt3 (coord, this->orig_corner0[Proj::Y], this->orig_corner7[Proj::Z], 1.0);
556 break;
557 case Proj::Y:
558 c1 = Proj::Pt3 (this->orig_corner0[Proj::X], coord, this->orig_corner0[Proj::Z], 1.0);
559 c2 = Proj::Pt3 (this->orig_corner7[Proj::X], coord, this->orig_corner0[Proj::Z], 1.0);
560 c3 = Proj::Pt3 (this->orig_corner7[Proj::X], coord, this->orig_corner7[Proj::Z], 1.0);
561 c4 = Proj::Pt3 (this->orig_corner0[Proj::X], coord, this->orig_corner7[Proj::Z], 1.0);
562 break;
563 case Proj::Z:
564 c1 = Proj::Pt3 (this->orig_corner7[Proj::X], this->orig_corner7[Proj::Y], coord, 1.0);
565 c2 = Proj::Pt3 (this->orig_corner7[Proj::X], this->orig_corner0[Proj::Y], coord, 1.0);
566 c3 = Proj::Pt3 (this->orig_corner0[Proj::X], this->orig_corner0[Proj::Y], coord, 1.0);
567 c4 = Proj::Pt3 (this->orig_corner0[Proj::X], this->orig_corner7[Proj::Y], coord, 1.0);
568 break;
569 default:
570 return;
571 }
572 corner1 = persp_impl->tmat.image(c1).affine();
573 corner2 = persp_impl->tmat.image(c2).affine();
574 corner3 = persp_impl->tmat.image(c3).affine();
575 corner4 = persp_impl->tmat.image(c4).affine();
576}
577
578/* Auxiliary function: Checks whether the half-line from A to B crosses the line segment joining C and D */
579static bool
581 Geom::Point const &C, Geom::Point const &D) {
582 Geom::Point n0 = (B - A).ccw();
583 double d0 = dot(n0,A);
584
585 Geom::Point n1 = (D - C).ccw();
586 double d1 = dot(n1,C);
587
588 Geom::Line lineAB(A,B);
589 Geom::Line lineCD(C,D);
590
591 Geom::OptCrossing inters = Geom::OptCrossing(); // empty by default
592 try
593 {
594 inters = Geom::intersection(lineAB, lineCD);
595 }
596 catch (Geom::InfiniteSolutions& e)
597 {
598 // We're probably dealing with parallel lines, so they don't really cross
599 return false;
600 }
601
602 if (!inters) {
603 return false;
604 }
605
606 Geom::Point E = lineAB.pointAt((*inters).ta); // the point of intersection
607
608 if ((dot(C,n0) < d0) == (dot(D,n0) < d0)) {
609 // C and D lie on the same side of the line AB
610 return false;
611 }
612 if ((dot(A,n1) < d1) != (dot(B,n1) < d1)) {
613 // A and B lie on different sides of the line CD
614 return true;
615 } else if (Geom::distance(E,A) < Geom::distance(E,B)) {
616 // The line CD passes on the "wrong" side of A
617 return false;
618 }
619
620 // The line CD passes on the "correct" side of A
621 return true;
622}
623
624static bool
626 Persp3D *persp = box->get_perspective();
627 g_return_val_if_fail(persp, false);
628 Box3D::PerspectiveLine l1(box->get_corner_screen(3, false), Proj::X, persp);
629 Box3D::PerspectiveLine l2(box->get_corner_screen(3, false), Proj::Y, persp);
630 Geom::Point v1(l1.direction());
631 Geom::Point v2(l2.direction());
632 v1.normalize();
633 v2.normalize();
634
635 return (v1[Geom::X]*v2[Geom::Y] - v1[Geom::Y]*v2[Geom::X] > 0);
636}
637
638static inline void
639box3d_aux_set_z_orders (int z_orders[6], int a, int b, int c, int d, int e, int f) {
640 // TODO add function argument: SPDocument *doc = box->document
641 auto doc = SP_ACTIVE_DOCUMENT;
642
643 if (doc->is_yaxisdown()) {
644 std::swap(a, f);
645 std::swap(b, e);
646 std::swap(c, d);
647 }
648
649 z_orders[0] = a;
650 z_orders[1] = b;
651 z_orders[2] = c;
652 z_orders[3] = d;
653 z_orders[4] = e;
654 z_orders[5] = f;
655}
656
657
658/*
659 * In standard perspective we have:
660 * 2 = front face
661 * 1 = top face
662 * 0 = left face
663 * 3 = right face
664 * 4 = bottom face
665 * 5 = rear face
666 */
667
668/* All VPs infinite */
669static void
670box3d_set_new_z_orders_case0 (SPBox3D *box, int z_orders[6], Box3D::Axis central_axis) {
671 bool swapped = box3d_XY_axes_are_swapped(box);
672
673 switch(central_axis) {
674 case Box3D::X:
675 if (!swapped) {
676 box3d_aux_set_z_orders (z_orders, 2, 0, 4, 1, 3, 5);
677 } else {
678 box3d_aux_set_z_orders (z_orders, 3, 1, 5, 2, 4, 0);
679 }
680 break;
681 case Box3D::Y:
682 if (!swapped) {
683 box3d_aux_set_z_orders (z_orders, 2, 3, 1, 4, 0, 5);
684 } else {
685 box3d_aux_set_z_orders (z_orders, 5, 0, 4, 1, 3, 2);
686 }
687 break;
688 case Box3D::Z:
689 if (!swapped) {
690 box3d_aux_set_z_orders (z_orders, 2, 0, 1, 4, 3, 5);
691 } else {
692 box3d_aux_set_z_orders (z_orders, 5, 3, 4, 1, 0, 2);
693 }
694 break;
695 case Box3D::NONE:
696 if (!swapped) {
697 box3d_aux_set_z_orders (z_orders, 2, 3, 4, 1, 0, 5);
698 } else {
699 box3d_aux_set_z_orders (z_orders, 5, 0, 1, 4, 3, 2);
700 }
701 break;
702 default:
703 g_assert_not_reached();
704 break;
705 }
706}
707
708/* Precisely one finite VP */
709static void
710box3d_set_new_z_orders_case1 (SPBox3D *box, int z_orders[6], Box3D::Axis central_axis, Box3D::Axis fin_axis) {
711 Persp3D *persp = box->get_perspective();
712 Geom::Point vp(persp->get_VP(Box3D::toProj(fin_axis)).affine());
713
714 // note: in some of the case distinctions below we rely upon the fact that oaxis1 and oaxis2 are ordered
715 Box3D::Axis oaxis1 = Box3D::get_remaining_axes(fin_axis).first;
716 Box3D::Axis oaxis2 = Box3D::get_remaining_axes(fin_axis).second;
717 int inside1 = 0;
718 int inside2 = 0;
719 inside1 = box->pt_lies_in_PL_sector (vp, 3, 3 ^ oaxis2, oaxis1);
720 inside2 = box->pt_lies_in_PL_sector (vp, 3, 3 ^ oaxis1, oaxis2);
721
722 bool swapped = box3d_XY_axes_are_swapped(box);
723
724 switch(central_axis) {
725 case Box3D::X:
726 if (!swapped) {
727 box3d_aux_set_z_orders (z_orders, 2, 4, 0, 1, 3, 5);
728 } else {
729 box3d_aux_set_z_orders (z_orders, 5, 3, 1, 0, 2, 4);
730 }
731 break;
732 case Box3D::Y:
733 if (inside2 > 0) {
734 box3d_aux_set_z_orders (z_orders, 1, 2, 3, 0, 5, 4);
735 } else if (inside2 < 0) {
736 box3d_aux_set_z_orders (z_orders, 2, 3, 1, 4, 0, 5);
737 } else {
738 if (!swapped) {
739 box3d_aux_set_z_orders (z_orders, 2, 3, 1, 5, 0, 4);
740 } else {
741 box3d_aux_set_z_orders (z_orders, 5, 0, 4, 1, 3, 2);
742 }
743 }
744 break;
745 case Box3D::Z:
746 if (inside2) {
747 if (!swapped) {
748 box3d_aux_set_z_orders (z_orders, 2, 1, 3, 0, 4, 5);
749 } else {
750 box3d_aux_set_z_orders (z_orders, 5, 3, 4, 0, 1, 2);
751 }
752 } else if (inside1) {
753 if (!swapped) {
754 box3d_aux_set_z_orders (z_orders, 2, 0, 1, 4, 3, 5);
755 } else {
756 box3d_aux_set_z_orders (z_orders, 5, 3, 4, 1, 0, 2);
757 }
758 } else {
759 // "regular" case
760 if (!swapped) {
761 box3d_aux_set_z_orders (z_orders, 0, 1, 2, 5, 4, 3);
762 } else {
763 box3d_aux_set_z_orders (z_orders, 5, 3, 4, 0, 2, 1);
764 }
765 }
766 break;
767 case Box3D::NONE:
768 if (!swapped) {
769 box3d_aux_set_z_orders (z_orders, 2, 3, 4, 5, 0, 1);
770 } else {
771 box3d_aux_set_z_orders (z_orders, 5, 0, 1, 3, 2, 4);
772 }
773 break;
774 default:
775 g_assert_not_reached();
776 }
777}
778
779/* Precisely 2 finite VPs */
780static void
781box3d_set_new_z_orders_case2 (SPBox3D *box, int z_orders[6], Box3D::Axis central_axis, Box3D::Axis /*infinite_axis*/) {
782 bool swapped = box3d_XY_axes_are_swapped(box);
783
784 int insidexy = box->VP_lies_in_PL_sector (Proj::X, 3, 3 ^ Box3D::Z, Box3D::Y);
785 //int insidexz = box->VP_lies_in_PL_sector (Proj::X, 3, 3 ^ Box3D::Y, Box3D::Z);
786
787 int insideyx = box->VP_lies_in_PL_sector (Proj::Y, 3, 3 ^ Box3D::Z, Box3D::X);
788 int insideyz = box->VP_lies_in_PL_sector (Proj::Y, 3, 3 ^ Box3D::X, Box3D::Z);
789
790 //int insidezx = box->VP_lies_in_PL_sector (Proj::Z, 3, 3 ^ Box3D::Y, Box3D::X);
791 int insidezy = box->VP_lies_in_PL_sector (Proj::Z, 3, 3 ^ Box3D::X, Box3D::Y);
792
793 switch(central_axis) {
794 case Box3D::X:
795 if (!swapped) {
796 if (insidezy == -1) {
797 box3d_aux_set_z_orders (z_orders, 2, 4, 0, 1, 3, 5);
798 } else if (insidexy == 1) {
799 box3d_aux_set_z_orders (z_orders, 2, 4, 0, 5, 1, 3);
800 } else {
801 box3d_aux_set_z_orders (z_orders, 2, 4, 0, 1, 3, 5);
802 }
803 } else {
804 if (insideyz == -1) {
805 box3d_aux_set_z_orders (z_orders, 3, 1, 5, 0, 2, 4);
806 } else {
807 if (!swapped) {
808 box3d_aux_set_z_orders (z_orders, 3, 1, 5, 2, 4, 0);
809 } else {
810 if (insidexy == 0) {
811 box3d_aux_set_z_orders (z_orders, 3, 5, 1, 0, 2, 4);
812 } else {
813 box3d_aux_set_z_orders (z_orders, 3, 1, 5, 0, 2, 4);
814 }
815 }
816 }
817 }
818 break;
819 case Box3D::Y:
820 if (!swapped) {
821 if (insideyz == 1) {
822 box3d_aux_set_z_orders (z_orders, 2, 3, 1, 0, 5, 4);
823 } else {
824 box3d_aux_set_z_orders (z_orders, 2, 3, 1, 5, 0, 4);
825 }
826 } else {
827 if (insideyx == 1) {
828 box3d_aux_set_z_orders (z_orders, 4, 0, 5, 1, 3, 2);
829 } else {
830 box3d_aux_set_z_orders (z_orders, 5, 0, 4, 1, 3, 2);
831 }
832 }
833 break;
834 case Box3D::Z:
835 if (!swapped) {
836 if (insidezy == 1) {
837 box3d_aux_set_z_orders (z_orders, 2, 1, 0, 4, 3, 5);
838 } else if (insidexy == -1) {
839 box3d_aux_set_z_orders (z_orders, 2, 1, 0, 5, 4, 3);
840 } else {
841 box3d_aux_set_z_orders (z_orders, 2, 0, 1, 5, 3, 4);
842 }
843 } else {
844 box3d_aux_set_z_orders (z_orders, 3, 4, 5, 1, 0, 2);
845 }
846 break;
847 case Box3D::NONE:
848 if (!swapped) {
849 box3d_aux_set_z_orders (z_orders, 2, 3, 4, 1, 0, 5);
850 } else {
851 box3d_aux_set_z_orders (z_orders, 5, 0, 1, 4, 3, 2);
852 }
853 break;
854 default:
855 g_assert_not_reached();
856 break;
857 }
858}
859
860/*
861 * It can happen that during dragging the box is everted.
862 * In this case the opposite sides in this direction need to be swapped
863 */
864static Box3D::Axis
867
868 box->orig_corner0.normalize();
869 box->orig_corner7.normalize();
870
871 if (box->orig_corner0[Proj::X] < box->orig_corner7[Proj::X])
872 ev = (Box3D::Axis) (ev ^ Box3D::X);
873 if (box->orig_corner0[Proj::Y] < box->orig_corner7[Proj::Y])
874 ev = (Box3D::Axis) (ev ^ Box3D::Y);
875 if (box->orig_corner0[Proj::Z] > box->orig_corner7[Proj::Z]) // FIXME: Remove the need to distinguish signs among the cases
876 ev = (Box3D::Axis) (ev ^ Box3D::Z);
877
878 return ev;
879}
880
881static void
882box3d_swap_sides(int z_orders[6], Box3D::Axis axis) {
883 int pos1 = -1;
884 int pos2 = -1;
885
886 for (int i = 0; i < 6; ++i) {
887 if (!(Box3D::int_to_face(z_orders[i]).first == axis)) {
888 if (pos1 == -1) {
889 pos1 = i;
890 } else {
891 pos2 = i;
892 break;
893 }
894 }
895 }
896
897 if ((pos1 != -1) && (pos2 != -1)){
898 int tmp = z_orders[pos1];
899 z_orders[pos1] = z_orders[pos2];
900 z_orders[pos2] = tmp;
901 }
902}
903
904
905bool
907 Persp3D *persp = this->get_perspective();
908
909 if (!persp)
910 return false;
911
912 int z_orders[6];
913
914 Geom::Point c3(this->get_corner_screen(3, false));
915
916 // determine directions from corner3 to the VPs
917 int num_finite = 0;
918 Box3D::Axis axis_finite = Box3D::NONE;
919 Box3D::Axis axis_infinite = Box3D::NONE;
920 Geom::Point dirs[3];
921 for (int i = 0; i < 3; ++i) {
922 dirs[i] = persp->get_PL_dir_from_pt(c3, Box3D::toProj(Box3D::axes[i]));
923 if (Persp3D::VP_is_finite(persp->perspective_impl.get(), Proj::axes[i])) {
924 num_finite++;
925 axis_finite = Box3D::axes[i];
926 } else {
927 axis_infinite = Box3D::axes[i];
928 }
929 }
930
931 // determine the "central" axis (if there is one)
932 Box3D::Axis central_axis = Box3D::NONE;
933 if(Box3D::lies_in_sector(dirs[0], dirs[1], dirs[2])) {
934 central_axis = Box3D::Z;
935 } else if(Box3D::lies_in_sector(dirs[1], dirs[2], dirs[0])) {
936 central_axis = Box3D::X;
937 } else if(Box3D::lies_in_sector(dirs[2], dirs[0], dirs[1])) {
938 central_axis = Box3D::Y;
939 }
940
941 switch (num_finite) {
942 case 0:
943 // TODO: Remark: In this case (and maybe one of the others, too) the z-orders for all boxes
944 // coincide, hence only need to be computed once in a more central location.
945 box3d_set_new_z_orders_case0(this, z_orders, central_axis);
946 break;
947 case 1:
948 box3d_set_new_z_orders_case1(this, z_orders, central_axis, axis_finite);
949 break;
950 case 2:
951 case 3:
952 box3d_set_new_z_orders_case2(this, z_orders, central_axis, axis_infinite);
953 break;
954 default:
955 /*
956 * For each VP F, check whether the half-line from the corner3 to F crosses the line segment
957 * joining the other two VPs. If this is the case, it determines the "central" corner from
958 * which the visible sides can be deduced. Otherwise, corner3 is the central corner.
959 */
960 // FIXME: We should eliminate the use of Geom::Point altogether
961 Box3D::Axis central_axis = Box3D::NONE;
962 Geom::Point vp_x = persp->get_VP(Proj::X).affine();
963 Geom::Point vp_y = persp->get_VP(Proj::Y).affine();
964 Geom::Point vp_z = persp->get_VP(Proj::Z).affine();
965 Geom::Point vpx(vp_x[Geom::X], vp_x[Geom::Y]);
966 Geom::Point vpy(vp_y[Geom::X], vp_y[Geom::Y]);
967 Geom::Point vpz(vp_z[Geom::X], vp_z[Geom::Y]);
968
969 Geom::Point c3 = this->get_corner_screen(3, false);
970 Geom::Point corner3(c3[Geom::X], c3[Geom::Y]);
971
972 if (box3d_half_line_crosses_joining_line (corner3, vpx, vpy, vpz)) {
973 central_axis = Box3D::X;
974 } else if (box3d_half_line_crosses_joining_line (corner3, vpy, vpz, vpx)) {
975 central_axis = Box3D::Y;
976 } else if (box3d_half_line_crosses_joining_line (corner3, vpz, vpx, vpy)) {
977 central_axis = Box3D::Z;
978 }
979
980 // FIXME: At present, this is not used. Why is it calculated?
981 /*
982 unsigned int central_corner = 3 ^ central_axis;
983 if (central_axis == Box3D::Z) {
984 central_corner = central_corner ^ Box3D::XYZ;
985 }
986 if (box3d_XY_axes_are_swapped(this)) {
987 central_corner = central_corner ^ Box3D::XYZ;
988 }
989 */
990
991 Geom::Point c1(this->get_corner_screen(1, false));
992 Geom::Point c2(this->get_corner_screen(2, false));
993 Geom::Point c7(this->get_corner_screen(7, false));
994
995 Geom::Point corner1(c1[Geom::X], c1[Geom::Y]);
996 Geom::Point corner2(c2[Geom::X], c2[Geom::Y]);
997 Geom::Point corner7(c7[Geom::X], c7[Geom::Y]);
998 // FIXME: At present we don't use the information about central_corner computed above.
999 switch (central_axis) {
1000 case Box3D::Y:
1001 if (!box3d_half_line_crosses_joining_line(vpz, vpy, corner3, corner2)) {
1002 box3d_aux_set_z_orders (z_orders, 2, 3, 1, 5, 0, 4);
1003 } else {
1004 // degenerate case
1005 box3d_aux_set_z_orders (z_orders, 2, 1, 3, 0, 5, 4);
1006 }
1007 break;
1008
1009 case Box3D::Z:
1010 if (box3d_half_line_crosses_joining_line(vpx, vpz, corner3, corner1)) {
1011 // degenerate case
1012 box3d_aux_set_z_orders (z_orders, 2, 0, 1, 4, 3, 5);
1013 } else if (box3d_half_line_crosses_joining_line(vpx, vpy, corner3, corner7)) {
1014 // degenerate case
1015 box3d_aux_set_z_orders (z_orders, 2, 1, 0, 5, 3, 4);
1016 } else {
1017 box3d_aux_set_z_orders (z_orders, 2, 1, 0, 3, 4, 5);
1018 }
1019 break;
1020
1021 case Box3D::X:
1022 if (box3d_half_line_crosses_joining_line(vpz, vpx, corner3, corner1)) {
1023 // degenerate case
1024 box3d_aux_set_z_orders (z_orders, 2, 1, 0, 4, 5, 3);
1025 } else {
1026 box3d_aux_set_z_orders (z_orders, 2, 4, 0, 5, 1, 3);
1027 }
1028 break;
1029
1030 case Box3D::NONE:
1031 box3d_aux_set_z_orders (z_orders, 2, 3, 4, 1, 0, 5);
1032 break;
1033
1034 default:
1035 g_assert_not_reached();
1036 break;
1037 } // end default case
1038 }
1039
1040 // TODO: If there are still errors in z-orders of everted boxes, we need to choose a variable corner
1041 // instead of the hard-coded corner #3 in the computations above
1043 for (auto & axe : Box3D::axes) {
1044 if (ev & axe) {
1046 }
1047 }
1048
1049 // Check whether anything actually changed
1050 for (int i = 0; i < 6; ++i) {
1051 if (this->z_orders[i] != z_orders[i]) {
1052 for (int j = i; j < 6; ++j) {
1053 this->z_orders[j] = z_orders[j];
1054 }
1055 return true;
1056 }
1057 }
1058 return false;
1059}
1060
1061static std::map<int, Box3DSide *> box3d_get_sides(SPBox3D *box)
1062{
1063 std::map<int, Box3DSide *> sides;
1064 for (auto& obj: box->children) {
1065 auto side = cast<Box3DSide>(&obj);
1066 if (side) {
1067 sides[Box3D::face_to_int(side->getFaceId())] = side;
1068 }
1069 }
1070 sides.erase(-1);
1071 return sides;
1072}
1073
1074
1075// TODO: Check whether the box is everted in any direction and swap the sides opposite to this direction
1076void
1078 // For efficiency reasons, we only set the new z-orders if something really changed
1079 if (this->recompute_z_orders ()) {
1080 std::map<int, Box3DSide *> sides = box3d_get_sides(this);
1081 std::map<int, Box3DSide *>::iterator side;
1082 for (int z_order : this->z_orders) {
1083 side = sides.find(z_order);
1084 if (side != sides.end()) {
1085 ((*side).second)->lowerToBottom();
1086 }
1087 }
1088 }
1089}
1090
1091/*
1092 * Auxiliary function for z-order recomputing:
1093 * Determines whether \a pt lies in the sector formed by the two PLs from the corners with IDs
1094 * \a i21 and \a id2 to the VP in direction \a axis. If the VP is infinite, we say that \a pt
1095 * lies in the sector if it lies between the two (parallel) PLs.
1096 * \ret * 0 if \a pt doesn't lie in the sector
1097 * * 1 if \a pt lies in the sector and either VP is finite of VP is infinite and the direction
1098 * from the edge between the two corners to \a pt points towards the VP
1099 * * -1 otherwise
1100 */
1101// TODO: Maybe it would be useful to have a similar method for projective points pt because then we
1102// can use it for VPs and perhaps merge the case distinctions during z-order recomputation.
1103int
1104SPBox3D::pt_lies_in_PL_sector (Geom::Point const &pt, int id1, int id2, Box3D::Axis axis) const {
1105 Persp3D *persp = this->get_perspective();
1106
1107 // the two corners
1108 Geom::Point c1(this->get_corner_screen(id1, false));
1109 Geom::Point c2(this->get_corner_screen(id2, false));
1110
1111 int ret = 0;
1112 if (Persp3D::VP_is_finite(persp->perspective_impl.get(), Box3D::toProj(axis))) {
1113 Geom::Point vp(persp->get_VP(Box3D::toProj(axis)).affine());
1114 Geom::Point v1(c1 - vp);
1115 Geom::Point v2(c2 - vp);
1116 Geom::Point w(pt - vp);
1117 ret = static_cast<int>(Box3D::lies_in_sector(v1, v2, w));
1118 } else {
1119 Box3D::PerspectiveLine pl1(c1, Box3D::toProj(axis), persp);
1120 Box3D::PerspectiveLine pl2(c2, Box3D::toProj(axis), persp);
1121 if (pl1.lie_on_same_side(pt, c2) && pl2.lie_on_same_side(pt, c1)) {
1122 // test whether pt lies "towards" or "away from" the VP
1123 Box3D::Line edge(c1,c2);
1124 Geom::Point c3(this->get_corner_screen(id1 ^ axis, false));
1125 if (edge.lie_on_same_side(pt, c3)) {
1126 ret = 1;
1127 } else {
1128 ret = -1;
1129 }
1130 }
1131 }
1132 return ret;
1133}
1134
1135int
1136SPBox3D::VP_lies_in_PL_sector (Proj::Axis vpdir, int id1, int id2, Box3D::Axis axis) const {
1137 Persp3D *persp = this->get_perspective();
1138
1139 if (!Persp3D::VP_is_finite(persp->perspective_impl.get(), vpdir)) {
1140 return 0;
1141 } else {
1142 return this->pt_lies_in_PL_sector(persp->get_VP(vpdir).affine(), id1, id2, axis);
1143 }
1144}
1145
1146/* swap the coordinates of corner0 and corner7 along the specified axis */
1147static void
1148box3d_swap_coords(SPBox3D *box, Proj::Axis axis, bool smaller = true) {
1149 box->orig_corner0.normalize();
1150 box->orig_corner7.normalize();
1151 if ((box->orig_corner0[axis] < box->orig_corner7[axis]) != smaller) {
1152 double tmp = box->orig_corner0[axis];
1153 box->orig_corner0[axis] = box->orig_corner7[axis];
1154 box->orig_corner7[axis] = tmp;
1155 }
1156 // Should we also swap the coordinates of save_corner0 and save_corner7?
1157}
1158
1159/* ensure that the coordinates of corner0 and corner7 are in the correct order (to prevent everted boxes) */
1160void
1162 box3d_swap_coords(this, Proj::X, false);
1163 box3d_swap_coords(this, Proj::Y, false);
1164 box3d_swap_coords(this, Proj::Z, true);
1165}
1166
1167static void
1169 box->orig_corner0.normalize();
1170 box->orig_corner7.normalize();
1171
1172 if ((box->orig_corner0[axis] < box->orig_corner7[axis]) != smaller) {
1173 box->swapped = (Box3D::Axis) (box->swapped | Proj::toAffine(axis));
1174 } else {
1175 box->swapped = (Box3D::Axis) (box->swapped & ~Proj::toAffine(axis));
1176 }
1177}
1178
1179static void
1181 box->orig_corner0.normalize();
1182 box->orig_corner7.normalize();
1183
1184 for (int i = 0; i < 3; ++i) {
1185 if (box->swapped & Box3D::axes[i]) {
1186 double tmp = box->orig_corner0[i];
1187 box->orig_corner0[i] = box->orig_corner7[i];
1188 box->orig_corner7[i] = tmp;
1189 }
1190 }
1191}
1192
1193void
1201
1202static void box3d_extract_boxes_rec(SPObject *obj, std::list<SPBox3D *> &boxes) {
1203 auto box = cast<SPBox3D>(obj);
1204 if (box) {
1205 boxes.push_back(box);
1206 } else if (is<SPGroup>(obj)) {
1207 for (auto& child: obj->children) {
1209 }
1210 }
1211}
1212
1213std::list<SPBox3D *>
1215 std::list<SPBox3D *> boxes;
1216 box3d_extract_boxes_rec(obj, boxes);
1217 return boxes;
1218}
1219
1220Persp3D *
1222 if(this->persp_ref) {
1223 return this->persp_ref->getObject();
1224 }
1225 return nullptr;
1226}
1227
1228void
1229SPBox3D::switch_perspectives(Persp3D *old_persp, Persp3D *new_persp, bool recompute_corners) {
1230 if (recompute_corners) {
1231 this->orig_corner0.normalize();
1232 this->orig_corner7.normalize();
1233 double z0 = this->orig_corner0[Proj::Z];
1234 double z7 = this->orig_corner7[Proj::Z];
1235 Geom::Point corner0_screen = this->get_corner_screen(0, false);
1236 Geom::Point corner7_screen = this->get_corner_screen(7, false);
1237
1238 this->orig_corner0 = new_persp->perspective_impl->tmat.preimage(corner0_screen, z0, Proj::Z);
1239 this->orig_corner7 = new_persp->perspective_impl->tmat.preimage(corner7_screen, z7, Proj::Z);
1240 }
1241
1242 old_persp->remove_box (this);
1243 new_persp->add_box (this);
1244
1245 Glib::ustring href = "#";
1246 href += new_persp->getId();
1247 this->setAttribute("inkscape:perspectiveID", href);
1248}
1249
1250/* Converts the 3D box to an ordinary SPGroup, adds it to the XML tree at the same position as
1251 the original box and deletes the latter */
1253{
1254 SPDocument *doc = this->document;
1255 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
1256
1257 // remember position of the box
1258 int pos = this->getPosition();
1259
1260 // remember important attributes
1261 gchar const *id = this->getAttribute("id");
1262 gchar const *style = this->getAttribute("style");
1263 gchar const *mask = this->getAttribute("mask");
1264 gchar const *clip_path = this->getAttribute("clip-path");
1265
1266 // create a new group and add the sides (converted to ordinary paths) as its children
1267 Inkscape::XML::Node *grepr = xml_doc->createElement("svg:g");
1268
1269 for (auto& obj: this->children) {
1270 auto side = cast<Box3DSide>(&obj);
1271 if (side) {
1272 Inkscape::XML::Node *repr = side->convert_to_path();
1273 grepr->appendChild(repr);
1274 } else {
1275 g_warning("Non-side item encountered as child of a 3D box.");
1276 }
1277 }
1278
1279 // add the new group to the box's parent and set remembered position
1280 SPObject *parent = this->parent;
1281 parent->appendChild(grepr);
1282 grepr->setPosition(pos);
1283 grepr->setAttributeOrRemoveIfEmpty("style", style);
1284 grepr->setAttributeOrRemoveIfEmpty("mask", mask);
1285 grepr->setAttributeOrRemoveIfEmpty("clip-path", clip_path);
1286
1287 this->deleteObject(true);
1288
1289 grepr->setAttribute("id", id);
1290
1291 auto group = cast<SPGroup>(doc->getObjectByRepr(grepr));
1292 g_assert(group != nullptr);
1293 return group;
1294}
1295
1296const char *SPBox3D::displayName() const {
1297 return _("3D Box");
1298}
1299
1300gchar *SPBox3D::description() const {
1301 // We could put more details about the 3d box here
1302 return g_strdup("");
1303}
1304
1305static inline void
1306box3d_push_back_corner_pair(SPBox3D const *box, std::list<std::pair<Geom::Point, Geom::Point> > &pts, int c1, int c2) {
1307 pts.emplace_back(box->get_corner_screen(c1, false),
1308 box->get_corner_screen(c2, false));
1309}
1310
1313
1314 if (!prefs->getBool("/tools/shapes/3dbox/convertguides", true)) {
1315 this->convert_to_guides();
1316 return;
1317 }
1318
1319 std::list<std::pair<Geom::Point, Geom::Point> > pts;
1320
1321 /* perspective lines in X direction */
1322 box3d_push_back_corner_pair(this, pts, 0, 1);
1323 box3d_push_back_corner_pair(this, pts, 2, 3);
1324 box3d_push_back_corner_pair(this, pts, 4, 5);
1325 box3d_push_back_corner_pair(this, pts, 6, 7);
1326
1327 /* perspective lines in Y direction */
1328 box3d_push_back_corner_pair(this, pts, 0, 2);
1329 box3d_push_back_corner_pair(this, pts, 1, 3);
1330 box3d_push_back_corner_pair(this, pts, 4, 6);
1331 box3d_push_back_corner_pair(this, pts, 5, 7);
1332
1333 /* perspective lines in Z direction */
1334 box3d_push_back_corner_pair(this, pts, 0, 4);
1335 box3d_push_back_corner_pair(this, pts, 1, 5);
1336 box3d_push_back_corner_pair(this, pts, 2, 6);
1337 box3d_push_back_corner_pair(this, pts, 3, 7);
1338
1340}
1341
1342/*
1343 Local Variables:
1344 mode:c++
1345 c-file-style:"stroustrup"
1346 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1347 indent-tabs-mode:nil
1348 fill-column:99
1349 End:
1350*/
1351// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
Lookup dictionary for attributes/properties.
SPAttr
Definition attributes.h:27
@ INKSCAPE_BOX3D_CORNER7
@ INKSCAPE_BOX3D_PERSPECTIVE_ID
@ INKSCAPE_BOX3D_CORNER0
TODO: insert short description here.
bool lie_on_same_side(Geom::Point const &A, Geom::Point const &B)
Geom::Point direction()
Geom::Point closest_to(Geom::Point const &pt)
3x3 matrix representing an affine transformation.
Definition affine.h:70
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
Infinite line on a plane.
Definition line.h:53
Point pointAt(Coord t) const
Definition line.h:231
Two-dimensional point that doubles as a vector.
Definition point.h:66
void normalize()
Normalize the vector representing the point.
Definition point.cpp:96
Preference storage class.
Definition preferences.h:66
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
static Preferences * get()
Access the singleton Preferences object.
URI const * getURI() const
Returns a pointer to a URI containing the currently attached URI, or NULL if no URI is currently atta...
void detach()
Detaches from the currently attached URI target, if any; the current referrent is signaled as NULL.
sigc::signal< void(SPObject *, SPObject *)> changedSignal()
Accessor for the referrent change notification signal; this signal is emitted whenever the URIReferen...
void attach(URI const &uri)
Attaches to a URI, relative to the specified document.
Represents an URI as per RFC 2396.
Definition uri.h:36
std::string str(char const *baseuri=nullptr) const
Return the string representation of this URI.
Definition uri.cpp:281
Interface for refcounted XML nodes.
Definition node.h:80
virtual void setPosition(int pos)=0
Set the position of this node in parent's child order.
virtual void appendChild(Node *child)=0
Append a node as the last child of this node.
void setAttributeOrRemoveIfEmpty(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:167
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
virtual Document * document()=0
Get the node's associated document.
Persp3D * getObject() const
void add_box(SPBox3D *box)
Definition persp3d.cpp:397
static Persp3D * document_first_persp(SPDocument *document)
Definition persp3d.cpp:248
Geom::Point get_PL_dir_from_pt(Geom::Point const &pt, Proj::Axis axis) const
Definition persp3d.cpp:309
std::unique_ptr< Persp3DImpl > perspective_impl
Definition persp3d.h:65
Proj::Pt2 get_VP(Proj::Axis axis) const
Definition persp3d.h:83
static bool VP_is_finite(Persp3DImpl *persp_impl, Proj::Axis axis)
Definition persp3d.cpp:339
void remove_box(SPBox3D *box)
Definition persp3d.cpp:410
Geom::Point affine()
Definition proj_pt.cpp:51
char * coord_string()
Definition proj_pt.cpp:101
void normalize()
Definition proj_pt.cpp:91
void update(SPCtx *ctx, unsigned int flags) override
Definition box3d.cpp:178
Proj::Pt3 save_corner7
Definition box3d.h:45
void release() override
Definition box3d.cpp:77
virtual const char * display_name()
Definition box3d.cpp:237
void set_corner(unsigned int id, Geom::Point const &new_pos, Box3D::Axis movement, bool constrained)
Definition box3d.cpp:430
char * description() const override
Definition box3d.cpp:1300
static SPBox3D * createBox3D(SPItem *parent)
Create a SPBox3D and append it to the parent.
Definition box3d.cpp:419
Geom::Point get_center_screen()
Definition box3d.cpp:319
SPBox3D()
Definition box3d.cpp:41
Proj::Pt3 orig_corner0
Definition box3d.h:41
void check_for_swapped_coords()
Definition box3d.cpp:1194
int my_counter
Definition box3d.h:49
int z_orders[6]
Definition box3d.h:36
char * persp_href
Definition box3d.h:38
int VP_lies_in_PL_sector(Proj::Axis vpdir, int id1, int id2, Box3D::Axis axis) const
Definition box3d.cpp:1136
bool recompute_z_orders()
Definition box3d.cpp:906
~SPBox3D() override
static std::list< SPBox3D * > extract_boxes(SPObject *obj)
Definition box3d.cpp:1214
void set_z_orders()
Definition box3d.cpp:1077
Geom::Affine set_transform(Geom::Affine const &transform) override
Definition box3d.cpp:253
Persp3DReference * persp_ref
Definition box3d.h:39
int pt_lies_in_PL_sector(Geom::Point const &pt, int id1, int id2, Box3D::Axis axis) const
Definition box3d.cpp:1104
Persp3D * get_perspective() const
Definition box3d.cpp:1221
void corners_for_PLs(Proj::Axis axis, Geom::Point &corner1, Geom::Point &corner2, Geom::Point &corner3, Geom::Point &corner4) const
Definition box3d.cpp:536
void position_set()
Definition box3d.cpp:241
Geom::Point get_corner_screen(unsigned int id, bool item_coords=true) const
Definition box3d.cpp:295
Inkscape::XML::Node * write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags) override
Definition box3d.cpp:191
Box3D::Axis swapped
Definition box3d.h:47
void set_center(Geom::Point const &new_pos, Geom::Point const &old_pos, Box3D::Axis movement, bool constrained)
Definition box3d.cpp:483
void convert_to_guides() const override
Definition box3d.cpp:1311
SPGroup * convert_to_group()
Definition box3d.cpp:1252
void build(SPDocument *document, Inkscape::XML::Node *repr) override
Definition box3d.cpp:56
void set(SPAttr key, char const *value) override
Definition box3d.cpp:107
Proj::Pt3 save_corner0
Definition box3d.h:44
void switch_perspectives(Persp3D *old_persp, Persp3D *new_persp, bool recompute_corners=false)
Definition box3d.cpp:1229
Proj::Pt3 orig_corner7
Definition box3d.h:42
Proj::Pt3 get_proj_center()
Definition box3d.cpp:309
const char * displayName() const override
The item's type name as a translated human string.
Definition box3d.cpp:1296
void relabel_corners()
Definition box3d.cpp:1161
Typed SVG document implementation.
Definition document.h:103
void setCurrentPersp3D(Persp3D *const persp)
Definition document.cpp:268
Inkscape::XML::Document * getReprDoc()
Our Inkscape::XML::Document.
Definition document.h:213
SPObject * getObjectByRepr(Inkscape::XML::Node *repr) const
Persp3D * getCurrentPersp3D()
Definition document.cpp:253
void release() override
Inkscape::XML::Node * write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags) override
void set(SPAttr key, char const *value) override
void build(SPDocument *document, Inkscape::XML::Node *repr) override
void update(SPCtx *ctx, unsigned int flags) override
Base class for visual SVG elements.
Definition sp-item.h:109
Geom::Affine i2dt_affine() const
Returns the transformation from item to desktop coords.
Definition sp-item.cpp:1821
void lowerToBottom()
Definition sp-item.cpp:458
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
Inkscape::XML::Node * repr
Definition sp-object.h:193
void appendChild(Inkscape::XML::Node *child)
void setAttribute(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
SPDocument * document
Definition sp-object.h:188
char const * getId() const
Returns the objects current ID string.
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
unsigned getPosition()
void readAttr(char const *key)
Read value of key attribute from XML node into object.
void deleteObject(bool propagate, bool propagate_descendants)
Deletes an object, unparenting it from its parent.
char const * getAttribute(char const *name) const
ChildrenList children
Definition sp-object.h:907
Linear z0(0.5, 1.)
const double w
Definition conic-4.cpp:19
Css & result
double c[8][4]
Editable view implementation.
static char const *const parent
Definition dir-util.cpp:70
constexpr Coord infinity()
Get a value representing infinity.
Definition coord.h:88
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Infinite straight line.
Proj::Axis toProj(Box3D::Axis axis)
Definition axis-manip.h:95
std::pair< Box3D::Axis, Box3D::FrontOrRear > int_to_face(unsigned id)
Definition axis-manip.h:169
int face_to_int(unsigned int face_id)
Definition axis-manip.h:149
std::pair< Axis, Axis > get_remaining_axes(Axis axis)
bool lies_in_sector(Geom::Point const &v1, Geom::Point const &v2, Geom::Point const &w)
Axis axes[3]
Angle distance(Angle const &a, Angle const &b)
Definition angle.h:163
std::optional< Crossing > OptCrossing
Definition crossing.h:64
OptCrossing intersection(Ray const &r1, Line const &l2)
Definition line.h:545
Affine identity()
Create an identity matrix.
Definition affine.h:210
SBasis L2(D2< SBasis > const &a, unsigned k)
Definition d2-sbasis.cpp:42
Box3D::Axis toAffine(Proj::Axis axis)
Definition axis-manip.h:119
Axis axes[4]
static cairo_user_data_key_t key
static std::map< int, Box3DSide * > box3d_get_sides(SPBox3D *box)
Definition box3d.cpp:1061
static double remember_snap_threshold
Definition box3d.cpp:335
static guint remember_snap_index
Definition box3d.cpp:336
static const int MAX_POINT_COUNT
Definition box3d.cpp:339
static void box3d_swap_coords(SPBox3D *box, Proj::Axis axis, bool smaller=true)
Definition box3d.cpp:1148
static void box3d_push_back_corner_pair(SPBox3D const *box, std::list< std::pair< Geom::Point, Geom::Point > > &pts, int c1, int c2)
Definition box3d.cpp:1306
static void box3d_check_for_swapped_coords(SPBox3D *box, Proj::Axis axis, bool smaller)
Definition box3d.cpp:1168
static Proj::Pt3 box3d_snap(SPBox3D *box, int id, Proj::Pt3 const &pt_proj, Proj::Pt3 const &start_pt)
Definition box3d.cpp:342
static void box3d_aux_set_z_orders(int z_orders[6], int a, int b, int c, int d, int e, int f)
Definition box3d.cpp:639
static void box3d_set_new_z_orders_case0(SPBox3D *box, int z_orders[6], Box3D::Axis central_axis)
Definition box3d.cpp:670
static gint counter
Definition box3d.cpp:39
static void box3d_ref_changed(SPObject *old_ref, SPObject *ref, SPBox3D *box)
Gets called when (re)attached to another perspective.
Definition box3d.cpp:163
static Box3D::Axis box3d_everted_directions(SPBox3D *box)
Definition box3d.cpp:865
static void box3d_exchange_coords(SPBox3D *box)
Definition box3d.cpp:1180
static void box3d_set_new_z_orders_case2(SPBox3D *box, int z_orders[6], Box3D::Axis central_axis, Box3D::Axis)
Definition box3d.cpp:781
static Proj::Pt3 box3d_get_proj_corner(guint id, Proj::Pt3 const &c0, Proj::Pt3 const &c7)
Definition box3d.cpp:279
static void box3d_extract_boxes_rec(SPObject *obj, std::list< SPBox3D * > &boxes)
Definition box3d.cpp:1202
static void box3d_swap_sides(int z_orders[6], Box3D::Axis axis)
Definition box3d.cpp:882
static bool box3d_XY_axes_are_swapped(SPBox3D *box)
Definition box3d.cpp:625
static bool box3d_half_line_crosses_joining_line(Geom::Point const &A, Geom::Point const &B, Geom::Point const &C, Geom::Point const &D)
Definition box3d.cpp:580
static void box3d_set_new_z_orders_case1(SPBox3D *box, int z_orders[6], Box3D::Axis central_axis, Box3D::Axis fin_axis)
Definition box3d.cpp:710
Ocnode * child[8]
Definition quantize.cpp:33
Ocnode ** ref
Definition quantize.cpp:32
unsigned n1
void sp_guide_pt_pairs_to_guides(SPDocument *doc, std::list< std::pair< Geom::Point, Geom::Point > > &pts)
Definition sp-guide.cpp:268
SPGuide – a guideline.
Interface for XML documents.
Definition document.h:43
virtual Node * createElement(char const *name)=0
Unused.
Definition sp-object.h:94
void dot(Cairo::RefPtr< Cairo::Context > &cr, double x, double y)
Interface for XML documents.