From fbdfd8486b6e079ebac503d948d77131bffaa600 Mon Sep 17 00:00:00 2001 From: cilix42 Date: Fri, 17 Aug 2007 16:22:23 +0000 Subject: [PATCH] Draw perspective lines for infinite VPs, too (they are updated during scrolling or zooming); don't create knots for infinite VPs on the canvas --- src/axis-manip.h | 2 + src/display/sp-canvas.cpp | 11 +++++ src/line-geometry.cpp | 82 ++++++++++++++++++++++++++++++++++ src/line-geometry.h | 9 +++- src/perspective-line.cpp | 18 ++++++++ src/perspective-line.h | 5 ++- src/perspective3d.cpp | 3 ++ src/vanishing-point.cpp | 93 +++++++++++++++++++++++++++------------ 8 files changed, 191 insertions(+), 32 deletions(-) diff --git a/src/axis-manip.h b/src/axis-manip.h index 8574bf3ff..027d17ea5 100644 --- a/src/axis-manip.h +++ b/src/axis-manip.h @@ -17,6 +17,8 @@ namespace Box3D { +const double epsilon = 1e-6; + // The X-/Y-/Z-axis corresponds to the first/second/third digit // in binary representation, respectively. enum Axis { diff --git a/src/display/sp-canvas.cpp b/src/display/sp-canvas.cpp index fef2fcc0c..c53b1e61b 100644 --- a/src/display/sp-canvas.cpp +++ b/src/display/sp-canvas.cpp @@ -34,6 +34,8 @@ #include #include #include "prefs-utils.h" +#include "box3d-context.h" +#include "inkscape.h" // Tiles are a way to minimize the number of redraws, eliminating too small redraws. // The canvas stores a 2D array of ints, each representing a TILE_SIZExTILE_SIZE pixels tile. @@ -2193,6 +2195,15 @@ sp_canvas_scroll_to (SPCanvas *canvas, double cx, double cy, unsigned int clear, } else { // scrolling as part of zoom; do nothing here - the next do_update will perform full redraw } + + /* update perspective lines if we are in the 3D box tool (so that infinite ones are shown correctly) */ + SPEventContext *ec = inkscape_active_event_context(); + if (SP_IS_3DBOX_CONTEXT (ec)) { + // We could avoid redraw during panning by checking the status of is_scrolling, but this is + // neither faster nor does it get rid of artefacts, so we update PLs unconditionally + SP3DBoxContext *bc = SP_3DBOX_CONTEXT (ec); + bc->_vpdrag->updateLines(); + } } /** diff --git a/src/line-geometry.cpp b/src/line-geometry.cpp index d36b1b63d..68741d8a7 100644 --- a/src/line-geometry.cpp +++ b/src/line-geometry.cpp @@ -75,6 +75,88 @@ NR::Point Line::closest_to(NR::Point const &pt) return *result; } +inline static double determinant (NR::Point const &a, NR::Point const &b) +{ + return (a[NR::X] * b[NR::Y] - a[NR::Y] * b[NR::X]); +} + +/* The coordinates of w with respect to the basis {v1, v2} */ +std::pair coordinates (NR::Point const &v1, NR::Point const &v2, NR::Point const &w) +{ + double det = determinant (v1, v2);; + if (fabs (det) < epsilon) { + g_warning ("Vectors do not form a basis.\n"); + return std::make_pair (0.0, 0.0); + } + + double lambda1 = determinant (w, v2) / det; + double lambda2 = determinant (v1, w) / det; + return std::make_pair (lambda1, lambda2); +} + +/* whether w lies inside the sector spanned by v1 and v2 */ +bool lies_in_sector (NR::Point const &v1, NR::Point const &v2, NR::Point const &w) +{ + std::pair coords = coordinates (v1, v2, w); + return (coords.first >= 0 and coords.second >= 0); +} + +static double pos_angle (NR::Point A, NR::Point B) +{ + return fabs (NR::atan2 (A) - NR::atan2 (B)); +} + +/* + * Returns the two corners of the quadrangle A, B, C, D spanning the edge that is hit by a semiline + * starting at pt and going into direction dir. + * If none of the sides is hit, it returns a pair containing two identical points. + */ +std::pair +side_of_intersection (NR::Point const &A, NR::Point const &B, NR::Point const &C, NR::Point const &D, + NR::Point const &pt, NR::Point const &dir) +{ + NR::Point dir_A (A - pt); + NR::Point dir_B (B - pt); + NR::Point dir_C (C - pt); + NR::Point dir_D (D - pt); + + std::pair result; + double angle = -1; + double tmp_angle; + + if (lies_in_sector (dir_A, dir_B, dir)) { + result = std::make_pair (A, B); + angle = pos_angle (dir_A, dir_B); + } + if (lies_in_sector (dir_B, dir_C, dir)) { + tmp_angle = pos_angle (dir_B, dir_C); + if (tmp_angle > angle) { + angle = tmp_angle; + result = std::make_pair (B, C); + } + } + if (lies_in_sector (dir_C, dir_D, dir)) { + tmp_angle = pos_angle (dir_C, dir_D); + if (tmp_angle > angle) { + angle = tmp_angle; + result = std::make_pair (C, D); + } + } + if (lies_in_sector (dir_D, dir_A, dir)) { + tmp_angle = pos_angle (dir_D, dir_A); + if (tmp_angle > angle) { + angle = tmp_angle; + result = std::make_pair (D, A); + } + } + if (angle == -1) { + // no intersection found; return a pair containing two identical points + return std::make_pair (A, A); + } else { + return result; + } +} + void create_canvas_point(NR::Point const &pos, double size, guint32 rgba) { SPDesktop *desktop = inkscape_active_desktop(); diff --git a/src/line-geometry.h b/src/line-geometry.h index b1eae366c..7e731d4bc 100644 --- a/src/line-geometry.h +++ b/src/line-geometry.h @@ -35,14 +35,19 @@ public: NR::Point closest_to(NR::Point const &pt); // returns the point on the line closest to pt friend inline std::ostream &operator<< (std::ostream &out_file, const Line &in_line); - -private: +//private: NR::Point pt; NR::Point v_dir; NR::Point normal; NR::Coord d0; }; +std::pair coordinates (NR::Point const &v1, NR::Point const &v2, NR::Point const &w); +bool lies_in_sector (NR::Point const &v1, NR::Point const &v2, NR::Point const &w); +std::pair side_of_intersection (NR::Point const &A, NR::Point const &B, + NR::Point const &C, NR::Point const &D, + NR::Point const &pt, NR::Point const &dir); + /*** For testing purposes: Draw a knot/node of specified size and color at the given position ***/ void create_canvas_point(NR::Point const &pos, double size = 4.0, guint32 rgba = 0xff00007f); diff --git a/src/perspective-line.cpp b/src/perspective-line.cpp index e5596cc1b..9ee2d3578 100644 --- a/src/perspective-line.cpp +++ b/src/perspective-line.cpp @@ -12,6 +12,7 @@ */ #include "perspective-line.h" +#include "desktop.h" namespace Box3D { @@ -50,6 +51,23 @@ NR::Point PerspectiveLine::meet(Line const &line) return *intersect(line); // works since intersect() does not return NR::Nothing() } +NR::Maybe PerspectiveLine::intersection_with_viewbox (SPDesktop *desktop) +{ + NR::Rect vb = desktop->get_display_area(); + /* remaining viewbox corners */ + NR::Point ul (vb.min()[NR::X], vb.max()[NR::Y]); + NR::Point lr (vb.max()[NR::X], vb.min()[NR::Y]); + + std::pair e = side_of_intersection (vb.min(), lr, vb.max(), ul, this->pt, this->v_dir); + if (e.first == e.second) { + // perspective line lies outside the canvas + return NR::Nothing(); + } + + Line line (e.first, e.second); + return this->intersect (line); +} + } // namespace Box3D /* diff --git a/src/perspective-line.h b/src/perspective-line.h index 8f218803b..cf8f2ba54 100644 --- a/src/perspective-line.h +++ b/src/perspective-line.h @@ -17,6 +17,8 @@ #include "box3d-context.h" #include +class SPDesktop; + namespace Box3D { class PerspectiveLine : public Box3D::Line { @@ -30,7 +32,8 @@ public: PerspectiveLine (NR::Point const &pt, Box3D::Axis const axis, Perspective3D *perspective); NR::Maybe intersect (Line const &line); // FIXME: Can we make this return only a NR::Point to remove the extra method meet()? NR::Point meet (Line const &line); - + NR::Maybe intersection_with_viewbox (SPDesktop *desktop); + private: Box3D::Axis vp_dir; // direction of the associated VP Perspective3D *persp; diff --git a/src/perspective3d.cpp b/src/perspective3d.cpp index 0d4877f37..c9c56e78d 100644 --- a/src/perspective3d.cpp +++ b/src/perspective3d.cpp @@ -310,6 +310,9 @@ Perspective3D::toggle_boxes (Box3D::Axis axis) sp_3dbox_reshape_after_VP_toggling (SP_3DBOX (i->data), axis); } update_box_reprs(); + + SP3DBoxContext *bc = SP_3DBOX_CONTEXT (inkscape_active_event_context()); + bc->_vpdrag->updateDraggers (); } void diff --git a/src/vanishing-point.cpp b/src/vanishing-point.cpp index ac7300b15..2426c9954 100644 --- a/src/vanishing-point.cpp +++ b/src/vanishing-point.cpp @@ -381,31 +381,33 @@ VPDragger::VPDragger(VPDrag *parent, NR::Point p, VanishingPoint *vp) this->point = p; this->point_original = p; - // create the knot - this->knot = sp_knot_new (inkscape_active_desktop(), NULL); - this->knot->setMode(SP_KNOT_MODE_XOR); - this->knot->setFill(VP_KNOT_COLOR_NORMAL, VP_KNOT_COLOR_NORMAL, VP_KNOT_COLOR_NORMAL); - this->knot->setStroke(0x000000ff, 0x000000ff, 0x000000ff); - sp_knot_update_ctrl(this->knot); - - // move knot to the given point - sp_knot_set_position (this->knot, &this->point, SP_KNOT_STATE_NORMAL); - sp_knot_show (this->knot); - - // connect knot's signals - g_signal_connect (G_OBJECT (this->knot), "moved", G_CALLBACK (vp_knot_moved_handler), this); - /*** - g_signal_connect (G_OBJECT (this->knot), "clicked", G_CALLBACK (vp_knot_clicked_handler), this); - ***/ - g_signal_connect (G_OBJECT (this->knot), "grabbed", G_CALLBACK (vp_knot_grabbed_handler), this); - g_signal_connect (G_OBJECT (this->knot), "ungrabbed", G_CALLBACK (vp_knot_ungrabbed_handler), this); - /*** - g_signal_connect (G_OBJECT (this->knot), "doubleclicked", G_CALLBACK (vp_knot_doubleclicked_handler), this); - ***/ - - // add the initial VP (which may be NULL!) - this->addVP (vp); - //updateKnotShape(); + if (vp->is_finite()) { + // create the knot + this->knot = sp_knot_new (inkscape_active_desktop(), NULL); + this->knot->setMode(SP_KNOT_MODE_XOR); + this->knot->setFill(VP_KNOT_COLOR_NORMAL, VP_KNOT_COLOR_NORMAL, VP_KNOT_COLOR_NORMAL); + this->knot->setStroke(0x000000ff, 0x000000ff, 0x000000ff); + sp_knot_update_ctrl(this->knot); + + // move knot to the given point + sp_knot_set_position (this->knot, &this->point, SP_KNOT_STATE_NORMAL); + sp_knot_show (this->knot); + + // connect knot's signals + g_signal_connect (G_OBJECT (this->knot), "moved", G_CALLBACK (vp_knot_moved_handler), this); + /*** + g_signal_connect (G_OBJECT (this->knot), "clicked", G_CALLBACK (vp_knot_clicked_handler), this); + ***/ + g_signal_connect (G_OBJECT (this->knot), "grabbed", G_CALLBACK (vp_knot_grabbed_handler), this); + g_signal_connect (G_OBJECT (this->knot), "ungrabbed", G_CALLBACK (vp_knot_ungrabbed_handler), this); + /*** + g_signal_connect (G_OBJECT (this->knot), "doubleclicked", G_CALLBACK (vp_knot_doubleclicked_handler), this); + ***/ + + // add the initial VP (which may be NULL!) + this->addVP (vp); + //updateKnotShape(); + } } VPDragger::~VPDragger() @@ -483,8 +485,8 @@ VPDragger::addVP (VanishingPoint *vp) if (vp == NULL) { return; } - if (g_slist_find (this->vps, vp)) { - // don't add the same VP twice + if (!vp->is_finite() || g_slist_find (this->vps, vp)) { + // don't add infinite VPs, and don't add the same VP twice return; } @@ -773,6 +775,7 @@ VPDrag::drawLinesForFace (const SP3DBox *box, Box3D::Axis axis) //, guint corner VanishingPoint *vp = document->get_persp_of_box (box)->get_vanishing_point (axis); if (vp->is_finite()) { + // draw perspective lines for finite VPs NR::Point pt = vp->get_pos(); if (this->front_or_rear_lines & 0x1) { // draw 'front' perspective lines @@ -785,8 +788,36 @@ VPDrag::drawLinesForFace (const SP3DBox *box, Box3D::Axis axis) //, guint corner this->addLine (corner4, pt, color); } } else { - // TODO: Draw infinite PLs - //g_warning ("Perspective lines for infinite vanishing points are not supported yet.\n"); + // draw perspective lines for infinite VPs + NR::Maybe pt1, pt2, pt3, pt4; + Box3D::Perspective3D *persp = this->document->get_persp_of_box (box); + SPDesktop *desktop = inkscape_active_desktop (); // FIXME: Store the desktop in VPDrag + Box3D::PerspectiveLine pl (corner1, axis, persp); + pt1 = pl.intersection_with_viewbox(desktop); + + pl = Box3D::PerspectiveLine (corner2, axis, persp); + pt2 = pl.intersection_with_viewbox(desktop); + + pl = Box3D::PerspectiveLine (corner3, axis, persp); + pt3 = pl.intersection_with_viewbox(desktop); + + pl = Box3D::PerspectiveLine (corner4, axis, persp); + pt4 = pl.intersection_with_viewbox(desktop); + + if (!pt1 || !pt2 || !pt3 || !pt4) { + // some perspective lines s are outside the canvas; currently we don't draw any of them + return; + } + if (this->front_or_rear_lines & 0x1) { + // draw 'front' perspective lines + this->addLine (corner1, *pt1, color); + this->addLine (corner2, *pt2, color); + } + if (this->front_or_rear_lines & 0x2) { + // draw 'rear' perspective lines + this->addLine (corner3, *pt3, color); + this->addLine (corner4, *pt4, color); + } } } @@ -830,6 +861,10 @@ VPDrag::addDragger (VanishingPoint *vp) g_print ("Warning: The VP in addDragger is already NULL. Aborting.\n)"); g_assert (vp != NULL); } + if (!vp->is_finite()) { + // don't create draggers for infinite vanishing points + return; + } NR::Point p = vp->get_pos(); for (GList *i = this->draggers; i != NULL; i = i->next) { -- 2.30.2