From 5341d52bd89ffc1587300ac4210afb64661af05d Mon Sep 17 00:00:00 2001 From: cilix42 Date: Fri, 17 Aug 2007 18:42:23 +0000 Subject: [PATCH] Enable center-dragging of boxes ('in perspective') within the XY-plane --- src/box3d.cpp | 150 ++++++++++++++++++++++++++++++++++----- src/box3d.h | 11 +++ src/line-geometry.cpp | 65 +++++++++++++++++ src/line-geometry.h | 17 ++++- src/object-edit.cpp | 21 ++++++ src/perspective-line.cpp | 17 +++++ src/perspective-line.h | 1 + 7 files changed, 263 insertions(+), 19 deletions(-) diff --git a/src/box3d.cpp b/src/box3d.cpp index dad0ae88c..b0195cdb8 100644 --- a/src/box3d.cpp +++ b/src/box3d.cpp @@ -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 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 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 @@ -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 +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 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 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(); diff --git a/src/box3d.h b/src/box3d.h index ed563a42e..a47b06663 100644 --- a/src/box3d.h +++ b/src/box3d.h @@ -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 sp_3dbox_get_center (SP3DBox *box); NR::Maybe 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); diff --git a/src/line-geometry.cpp b/src/line-geometry.cpp index 68741d8a7..d7b5fb2ec 100644 --- a/src/line-geometry.cpp +++ b/src/line-geometry.cpp @@ -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(); diff --git a/src/line-geometry.h b/src/line-geometry.h index 7e731d4bc..cc0c9aaf4 100644 --- a/src/line-geometry.h +++ b/src/line-geometry.h @@ -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 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); diff --git a/src/object-edit.cpp b/src/object-edit.cpp index 4467074d0..765f90303 100644 --- a/src/object-edit.cpp +++ b/src/object-edit.cpp @@ -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 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 Shift in X/Y direction; with Ctrl 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 Shift in X/Y direction; with Ctrl 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; diff --git a/src/perspective-line.cpp b/src/perspective-line.cpp index 9ee2d3578..90857e6d5 100644 --- a/src/perspective-line.cpp +++ b/src/perspective-line.cpp @@ -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 PerspectiveLine::intersection_with_viewbox (SPDesktop *desktop) { NR::Rect vb = desktop->get_display_area(); diff --git a/src/perspective-line.h b/src/perspective-line.h index cf8f2ba54..90104ffdf 100644 --- a/src/perspective-line.h +++ b/src/perspective-line.h @@ -32,6 +32,7 @@ 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::Point pt_with_given_cross_ratio (NR::Point const &C, NR::Point const &D, double gamma); NR::Maybe intersection_with_viewbox (SPDesktop *desktop); private: -- 2.30.2