Code

Enable center-dragging of boxes ('in perspective') within the XY-plane
authorcilix42 <cilix42@users.sourceforge.net>
Fri, 17 Aug 2007 18:42:23 +0000 (18:42 +0000)
committercilix42 <cilix42@users.sourceforge.net>
Fri, 17 Aug 2007 18:42:23 +0000 (18:42 +0000)
src/box3d.cpp
src/box3d.h
src/line-geometry.cpp
src/line-geometry.h
src/object-edit.cpp
src/perspective-line.cpp
src/perspective-line.h

index dad0ae88cd7df11059a199153ae126dae9a9f9cb..b0195cdb8918d76c1fdd612d64023ff225f31ce5 100644 (file)
@@ -19,6 +19,7 @@
 #include "attributes.h"
 #include "svg/stringstream.h"
 #include "box3d.h"
+#include "desktop-handles.h"
 
 static void sp_3dbox_class_init(SP3DBoxClass *klass);
 static void sp_3dbox_init(SP3DBox *box3d);
@@ -169,6 +170,17 @@ sp_3dbox_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr
     sp_3dbox_link_to_existing_paths (box, repr);
 
     sp_3dbox_set_ratios (box, Box3D::XYZ);
+
+    // Store the center (if it already exists) and certain corners for later use during center-dragging
+    NR::Maybe<NR::Point> cen = sp_3dbox_get_center (box);
+    if (cen) {
+        box->old_center = *cen;
+    }
+    box->old_corner2 = box->corners[2];
+    box->old_corner1 = box->corners[1];
+    box->old_corner0 = box->corners[0];
+    box->old_corner3 = box->corners[3];
+    box->old_corner5 = box->corners[5];
 }
 
 static void
@@ -269,6 +281,17 @@ static Inkscape::XML::Node *sp_3dbox_write(SPObject *object, Inkscape::XML::Node
         sp_3dbox_set_ratios (box);
 
         g_free ((void *) str);
+
+        /* store center and construction-corners for later use during center-dragging */
+        NR::Maybe<NR::Point> cen = sp_3dbox_get_center (box);
+        if (cen) {
+            box->old_center = *cen;
+        }
+        box->old_corner2 = box->corners[2];
+        box->old_corner1 = box->corners[1];
+        box->old_corner0 = box->corners[0];
+        box->old_corner3 = box->corners[3];
+        box->old_corner5 = box->corners[5];
     }
 
     if (((SPObjectClass *) (parent_class))->write) {
@@ -337,37 +360,45 @@ sp_3dbox_position_set (SP3DBoxContext &bc)
     ***/
 }
 
-//static
+static void
+sp_3dbox_set_shape_from_points (SP3DBox *box, NR::Point const &cornerA, NR::Point const &cornerB, NR::Point const &cornerC)
+{
+    sp_3dbox_recompute_corners (box, cornerA, cornerB, cornerC);
+
+    // FIXME: How to handle other contexts???
+    // FIXME: Is tools_isactive(..) more recommended to check for the current context/tool?
+    if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
+        return;
+    SP3DBoxContext *bc = SP_3DBOX_CONTEXT(inkscape_active_event_context());
+
+    if (bc->extruded) {
+        box->faces[0]->set_corners (box->corners[0], box->corners[4], box->corners[6], box->corners[2]);
+        box->faces[1]->set_corners (box->corners[1], box->corners[5], box->corners[7], box->corners[3]);
+        box->faces[2]->set_corners (box->corners[0], box->corners[1], box->corners[5], box->corners[4]);
+        box->faces[3]->set_corners (box->corners[2], box->corners[3], box->corners[7], box->corners[6]);
+        box->faces[5]->set_corners (box->corners[4], box->corners[5], box->corners[7], box->corners[6]);
+    }
+    box->faces[4]->set_corners (box->corners[0], box->corners[1], box->corners[3], box->corners[2]);
+
+    sp_3dbox_update_curves (box);
+}
+
 void
 // FIXME: Note that this is _not_ the virtual set_shape() method inherited from SPShape,
 //        since SP3DBox is inherited from SPGroup. The following method is "artificially"
 //        called from sp_3dbox_update().
 //sp_3dbox_set_shape(SPShape *shape)
-sp_3dbox_set_shape(SP3DBox *box3d, bool use_previous_corners)
+sp_3dbox_set_shape(SP3DBox *box, bool use_previous_corners)
 {
-    // FIXME: How to handle other contexts???
-    // FIXME: Is tools_isactive(..) more recommended to check for the current context/tool?
     if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context()))
         return;
     SP3DBoxContext *bc = SP_3DBOX_CONTEXT(inkscape_active_event_context());
 
-    /* Only update the curves during dragging; setting the svg representations 
-       is expensive and only done once at the end */
     if (!use_previous_corners) {
-        sp_3dbox_recompute_corners (box3d, bc->drag_origin, bc->drag_ptB, bc->drag_ptC);
+        sp_3dbox_set_shape_from_points (box, bc->drag_origin, bc->drag_ptB, bc->drag_ptC);
     } else {
-        sp_3dbox_recompute_corners (box3d, box3d->corners[2], box3d->corners[1], box3d->corners[5]);
-    }
-    if (bc->extruded) {
-        box3d->faces[0]->set_corners (box3d->corners[0], box3d->corners[4], box3d->corners[6], box3d->corners[2]);
-        box3d->faces[1]->set_corners (box3d->corners[1], box3d->corners[5], box3d->corners[7], box3d->corners[3]);
-        box3d->faces[2]->set_corners (box3d->corners[0], box3d->corners[1], box3d->corners[5], box3d->corners[4]);
-        box3d->faces[3]->set_corners (box3d->corners[2], box3d->corners[3], box3d->corners[7], box3d->corners[6]);
-        box3d->faces[5]->set_corners (box3d->corners[4], box3d->corners[5], box3d->corners[7], box3d->corners[6]);
+        sp_3dbox_set_shape_from_points (box, box->corners[2], box->corners[1], box->corners[5]);
     }
-    box3d->faces[4]->set_corners (box3d->corners[0], box3d->corners[1], box3d->corners[3], box3d->corners[2]);
-
-    sp_3dbox_update_curves (box3d);
 }
 
 
@@ -633,6 +664,13 @@ sp_3dbox_get_center (SP3DBox *box)
     return sp_3dbox_get_midpoint_between_corners (box, 0, 7);
 }
 
+NR::Point
+sp_3dbox_get_midpoint_in_axis_direction (NR::Point const &C, NR::Point const &D, Box3D::Axis axis, Box3D::Perspective3D *persp)
+{
+    Box3D::PerspectiveLine pl (D, axis, persp);
+    return pl.pt_with_given_cross_ratio (C, D, -1.0);
+}
+
 // TODO: The following function can probably be rewritten in a much more elegant and robust way
 //        by using projective coordinates for all points and using the cross ratio.
 NR::Maybe<NR::Point>
@@ -733,6 +771,82 @@ sp_3dbox_get_svg_descr_of_persp (Box3D::Perspective3D *persp)
     return g_strdup(os.str().c_str());
 }
 
+// auxiliary function
+static std::pair<NR::Point, NR::Point>
+sp_3dbox_new_midpoints (Box3D::Perspective3D *persp, Box3D::Axis axis, NR::Point const &M0, NR::Point const &M, NR::Point const &A, NR::Point const &B)
+{
+    double cr1 = Box3D::cross_ratio (*persp->get_vanishing_point (axis), M0, M, A);
+    double cr2 = Box3D::cross_ratio (*persp->get_vanishing_point (axis), M, B, M0);
+    if (fabs (cr1 - 1) < Box3D::epsilon) {
+        // FIXME: cr == 1 is a degenerate case; how should we deal with it?
+        return std::make_pair (NR::Point (0,0), NR::Point (0,0));
+    }
+    Box3D::PerspectiveLine pl (M0, axis, persp);
+    NR::Point B_new = pl.pt_with_given_cross_ratio (M0, M, cr1 / (cr1 - 1));
+    NR::Point A_new = pl.pt_with_given_cross_ratio (M0, M, 1 - cr2);
+    return std::make_pair (A_new, B_new);
+}
+
+void sp_3dbox_recompute_XY_corners_from_new_center (SP3DBox *box, NR::Point const new_center)
+{
+    // TODO: Clean this function up
+
+    Box3D::Perspective3D *persp = sp_desktop_document (inkscape_active_desktop())->get_persp_of_box (box);
+    NR::Point old_center = box->old_center;
+
+    NR::Point A0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner2, box->old_corner0, Box3D::Y, persp));
+    NR::Point B0 (sp_3dbox_get_midpoint_in_axis_direction (box->old_corner1, box->old_corner3, Box3D::Y, persp));
+
+    /* we first move the box along the X-axis ... */
+    Box3D::PerspectiveLine aux_line1 (old_center, Box3D::X, persp);
+    Box3D::PerspectiveLine aux_line2 (new_center, Box3D::Y, persp);
+    NR::Point Z1 = aux_line1.meet (aux_line2);
+
+    Box3D::PerspectiveLine ref_line (B0, Box3D::X, persp);
+    Box3D::PerspectiveLine pline2 (old_center, Box3D::Z, persp);
+    Box3D::PerspectiveLine pline3 (Z1, Box3D::Z, persp);
+    NR::Point M0 = ref_line.meet (pline2);
+    NR::Point M1 = ref_line.meet (pline3);
+
+    std::pair<NR::Point, NR::Point> new_midpts = sp_3dbox_new_midpoints (persp, Box3D::X, M0, M1, A0, B0);
+    NR::Point A1 (new_midpts.first);
+    NR::Point B1 (new_midpts.second);
+
+    /* ... and then along the Y-axis */
+    Box3D::PerspectiveLine pline4 (box->old_corner1, Box3D::X, persp);
+    Box3D::PerspectiveLine pline5 (box->old_corner3, Box3D::X, persp);
+    Box3D::PerspectiveLine aux_line3 (M1, Box3D::Y, persp);
+    NR::Point C1 = aux_line3.meet (pline4);
+    NR::Point D1 = aux_line3.meet (pline5);
+
+    Box3D::PerspectiveLine aux_line4 (new_center, Box3D::Z, persp);
+    NR::Point M2 = aux_line4.meet (aux_line3);
+
+    Box3D::VanishingPoint *vp_y = persp->get_vanishing_point (Box3D::Y);
+    std::pair<NR::Point, NR::Point> other_new_midpts = sp_3dbox_new_midpoints (persp, Box3D::Y, M1, M2, C1, D1);
+    NR::Point C2 (other_new_midpts.first);
+    NR::Point D2 (other_new_midpts.second);
+
+    Box3D::PerspectiveLine plXC (C2, Box3D::X, persp);
+    Box3D::PerspectiveLine plXD (D2, Box3D::X, persp);
+    Box3D::PerspectiveLine plYA (A1, Box3D::Y, persp);
+    Box3D::PerspectiveLine plYB (B1, Box3D::Y, persp);
+
+    NR::Point new_corner2 (plXD.meet (plYA));
+    NR::Point new_corner1 (plXC.meet (plYB));
+
+    NR::Point tmp_corner1 (pline4.meet (plYB));
+    Box3D::PerspectiveLine pline6 (box->old_corner5, Box3D::X, persp);
+    Box3D::PerspectiveLine pline7 (tmp_corner1, Box3D::Z, persp);
+    NR::Point tmp_corner5 (pline6.meet (pline7));
+
+    Box3D::PerspectiveLine pline8 (tmp_corner5, Box3D::Y, persp);
+    Box3D::PerspectiveLine pline9 (new_corner1, Box3D::Z, persp);
+    NR::Point new_corner5 (pline8.meet (pline9));
+
+    sp_3dbox_set_shape_from_points (box, new_corner2, new_corner1, new_corner5);
+}
+
 void sp_3dbox_update_perspective_lines()
 {
     SPEventContext *ec = inkscape_active_event_context();
index ed563a42e4444fe1e861435d50dc22a4c68579ae..a47b066634b43b494c27bb6e40d8c4b7013e5d8d 100644 (file)
@@ -46,6 +46,15 @@ struct SP3DBox : public SPGroup {
 
     guint front_bits; /* used internally to determine which of two parallel faces is supposed to be the front face */
 
+    // FIXME: If we only allow a single box to be dragged at a time then we can save memory by storing
+    //        the old positions centrally in SP3DBoxContext (instead of in each box separately)
+    NR::Point old_center;
+    NR::Point old_corner2;
+    NR::Point old_corner1;
+    NR::Point old_corner0;
+    NR::Point old_corner3;
+    NR::Point old_corner5;
+
     gint my_counter; // for testing only
 };
 
@@ -70,6 +79,8 @@ void sp_3dbox_move_corner_in_Z_direction (SP3DBox *box, guint id, NR::Point pt,
 void sp_3dbox_reshape_after_VP_toggling (SP3DBox *box, Box3D::Axis axis);
 NR::Maybe<NR::Point> sp_3dbox_get_center (SP3DBox *box);
 NR::Maybe<NR::Point> sp_3dbox_get_midpoint_between_corners (SP3DBox *box, guint id_corner1, guint id_corner2);
+void sp_3dbox_recompute_XY_corners_from_new_center (SP3DBox *box, NR::Point const new_center);
+NR::Point sp_3dbox_get_midpoint_in_axis_direction (NR::Point const &C, NR::Point const &D, Box3D::Axis axis, Box3D::Perspective3D *persp);
 
 void sp_3dbox_update_perspective_lines();
 void sp_3dbox_corners_for_perspective_lines (const SP3DBox * box, Box3D::Axis axis, NR::Point &corner1, NR::Point &corner2, NR::Point &corner3, NR::Point &corner4);
index 68741d8a707c5ab5694017c34606f266081c92b8..d7b5fb2ecd9bc66022916c2d7c8298f6c8c07a0e 100644 (file)
@@ -75,6 +75,20 @@ NR::Point Line::closest_to(NR::Point const &pt)
     return *result;
 }
 
+double Line::lambda (NR::Point const pt)
+{
+    double sign = (NR::dot (pt - this->pt, this->v_dir) > 0) ? 1.0 : -1.0;
+    double lambda = sign * NR::L2 (pt - this->pt);
+    // FIXME: It may speed things up (but how much?) if we assume that
+    //        pt lies on the line and thus skip the following test
+    NR::Point test = point_from_lambda (lambda);
+    if (!pts_coincide (pt, test)) {
+        g_warning ("Point does not lie on line.\n");
+        return 0;
+    }
+    return lambda;
+}
+
 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]);
@@ -157,6 +171,57 @@ side_of_intersection (NR::Point const &A, NR::Point const &B, NR::Point const &C
     }
 }
 
+double cross_ratio (NR::Point const &A, NR::Point const &B, NR::Point const &C, NR::Point const &D)
+{
+    Line line (A, D);
+    double lambda_A = line.lambda (A);
+    double lambda_B = line.lambda (B);
+    double lambda_C = line.lambda (C);
+    double lambda_D = line.lambda (D);
+
+    if (fabs (lambda_D - lambda_A) < epsilon || fabs (lambda_C - lambda_B) < epsilon) {
+        // FIXME: What should we return if the cross ratio can't be computed?
+        return 0;
+        //return NR_HUGE;
+    }
+    return (((lambda_C - lambda_A) / (lambda_D - lambda_A)) * ((lambda_D - lambda_B) / (lambda_C - lambda_B)));
+}
+
+double cross_ratio (VanishingPoint const &V, NR::Point const &B, NR::Point const &C, NR::Point const &D)
+{
+    if (V.is_finite()) {
+        return cross_ratio (V.get_pos(), B, C, D);
+    } else {
+        Line line (B, D);
+        double lambda_B = line.lambda (B);
+        double lambda_C = line.lambda (C);
+        double lambda_D = line.lambda (D);
+
+        if (fabs (lambda_C - lambda_B) < epsilon) {
+            // FIXME: What should we return if the cross ratio can't be computed?
+            return 0;
+            //return NR_HUGE;
+        }
+        return (lambda_D - lambda_B) / (lambda_C - lambda_B);
+    }
+}
+
+NR::Point fourth_pt_with_given_cross_ratio (NR::Point const &A, NR::Point const &C, NR::Point const &D, double gamma)
+{
+    Line line (A, D);
+    double lambda_A = line.lambda (A);
+    double lambda_C = line.lambda (C);
+    double lambda_D = line.lambda (D);
+
+    double beta = (lambda_C - lambda_A) / (lambda_D - lambda_A);
+    if (fabs (beta - gamma) < epsilon) {
+        // FIXME: How to handle the case when the point can't be computed?
+        // g_warning ("Cannot compute point with given cross ratio.\n");
+        return NR::Point (0.0, 0.0);
+    }
+    return line.point_from_lambda ((beta * lambda_D - gamma * lambda_C) / (beta - gamma));
+}
+
 void create_canvas_point(NR::Point const &pos, double size, guint32 rgba)
 {
     SPDesktop *desktop = inkscape_active_desktop();
index 7e731d4bc60b79421e1617b32486942457190ae4..cc0c9aaf4dff86988a8d177c8381bfd0e74e7ee2 100644 (file)
@@ -35,7 +35,18 @@ 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:
+    friend NR::Point fourth_pt_with_given_cross_ratio (NR::Point const &A, NR::Point const &C, NR::Point const &D, double gamma);
+
+    double lambda (NR::Point const pt);
+    inline NR::Point point_from_lambda (double const lambda) { 
+        return (pt + lambda * NR::unit_vector (v_dir)); }
+
+protected:
+    inline static bool pts_coincide (NR::Point const pt1, NR::Point const pt2)
+    {
+        return (NR::L2 (pt2 - pt1) < epsilon);
+    }
+
     NR::Point pt;
     NR::Point v_dir;
     NR::Point normal;
@@ -48,6 +59,10 @@ std::pair<NR::Point, NR::Point> side_of_intersection (NR::Point const &A, NR::Po
                                                       NR::Point const &C, NR::Point const &D,
                                                       NR::Point const &pt, NR::Point const &dir);
 
+double cross_ratio (NR::Point const &A, NR::Point const &B, NR::Point const &C, NR::Point const &D);
+double cross_ratio (VanishingPoint const &V, NR::Point const &B, NR::Point const &C, NR::Point const &D);
+NR::Point fourth_pt_with_given_cross_ratio (NR::Point const &A, NR::Point const &C, NR::Point const &D, double gamma);
+
 /*** 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);
 
index 4467074d0b7229c9dc87ef385d66035e077af23d..765f903037ec1ea6a1135df2f301458b559515e7 100644 (file)
@@ -648,6 +648,22 @@ static void sp_3dbox_knot_set(SPItem *item, guint knot_id, NR::Point const &new_
     sp_3dbox_set_z_orders (box);
 }
 
+static void sp_3dbox_knot_center_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state)
+{
+    NR::Matrix const i2d (sp_item_i2d_affine (item));
+    sp_3dbox_recompute_XY_corners_from_new_center (SP_3DBOX (item), new_pos * i2d);
+
+    sp_3dbox_update_curves (SP_3DBOX(item));
+}
+
+static NR::Point sp_3dbox_knot_center_get(SPItem *item)
+{
+    NR::Maybe<NR::Point> center = sp_3dbox_get_center(SP_3DBOX(item));
+    if (!center) return NR::Point (0, 0);
+    NR::Matrix const i2d (sp_item_i2d_affine (item));
+    return (*center) * i2d;
+}
+
 static void sp_3dbox_knot0_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state)
 {
     sp_3dbox_knot_set(item, 0, new_pos, origin, state);
@@ -752,6 +768,11 @@ sp_3dbox_knot_holder(SPItem *item, SPDesktop *desktop)
                        _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
     sp_knot_holder_add(knot_holder, sp_3dbox_knot7_set, sp_3dbox_knot7_get, NULL,
                        _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
+
+    // center dragging
+    sp_knot_holder_add_full(knot_holder, sp_3dbox_knot_center_set, sp_3dbox_knot_center_get, NULL,
+                            SP_KNOT_SHAPE_CROSS, SP_KNOT_MODE_XOR,_("Move the box in perspective."));
+
     sp_pat_knot_holder(item, knot_holder);
 
     return knot_holder;
index 9ee2d3578664005ae10042d948a95a2e6cce2a92..90857e6d5ecd10fac2ee887f5db8dfda20068e66 100644 (file)
@@ -51,6 +51,23 @@ NR::Point PerspectiveLine::meet(Line const &line)
     return *intersect(line); // works since intersect() does not return NR::Nothing()
 }
 
+NR::Point PerspectiveLine::pt_with_given_cross_ratio (NR::Point const &C, NR::Point const &D, double gamma)
+{
+    if (persp->get_vanishing_point (vp_dir)->is_finite()) {
+        NR::Point V (*persp->get_vanishing_point (vp_dir));
+        return fourth_pt_with_given_cross_ratio (V, C, D, gamma);
+    } else {
+        if (fabs (gamma - 1) < epsilon) {
+            g_warning ("Cannot compute point with given cross ratio.\n");
+            return NR::Point (0.0, 0.0);
+        }
+        Line line (C, D);
+        double lambda_C = line.lambda (C);
+        double lambda_D = line.lambda (D);
+        return line.point_from_lambda ((lambda_D - gamma * lambda_C) / (1 - gamma));
+    }
+}
+
 NR::Maybe<NR::Point> PerspectiveLine::intersection_with_viewbox (SPDesktop *desktop)
 {
     NR::Rect vb = desktop->get_display_area();
index cf8f2ba542c2f4004150faf57bb3f316574c0671..90104ffdf963953e86d58efb690e6a896d9efc48 100644 (file)
@@ -32,6 +32,7 @@ public:
     PerspectiveLine (NR::Point const &pt, Box3D::Axis const axis, Perspective3D *perspective);
     NR::Maybe<NR::Point> 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::Point pt_with_given_cross_ratio (NR::Point const &C, NR::Point const &D, double gamma);
     NR::Maybe<NR::Point> intersection_with_viewbox (SPDesktop *desktop);
 
 private: