From 36f768de5093f93de10e3b516a2b6f8b74f41513 Mon Sep 17 00:00:00 2001 From: cilix42 Date: Mon, 6 Aug 2007 08:01:03 +0000 Subject: [PATCH] Draw perspective lines; provide shortcuts to toggle their visibility and the corners where they are attached --- src/box3d-context.cpp | 60 +++++++++++++++++- src/box3d.cpp | 88 ++++++++++++++++++++++++++ src/box3d.h | 8 +++ src/object-edit.cpp | 2 + src/perspective3d.cpp | 8 --- src/vanishing-point.cpp | 133 ++++++++++++++++++++++++++++++++++++++-- src/vanishing-point.h | 12 +++- 7 files changed, 291 insertions(+), 20 deletions(-) diff --git a/src/box3d-context.cpp b/src/box3d-context.cpp index 3096ad179..a753f5f50 100644 --- a/src/box3d-context.cpp +++ b/src/box3d-context.cpp @@ -440,13 +440,67 @@ static gint sp_3dbox_context_root_handler(SPEventContext *event_context, GdkEven ret = true; break; + case GDK_L: + case GDK_l: + bc->_vpdrag->show_lines = !bc->_vpdrag->show_lines; + bc->_vpdrag->updateLines(); + ret = true; + break; + + case GDK_A: + case GDK_a: + if (MOD__CTRL) break; // Don't catch Ctrl+A ("select all") + if (bc->_vpdrag->show_lines) { + bc->_vpdrag->front_or_rear_lines = bc->_vpdrag->front_or_rear_lines ^ 0x2; // toggle rear PLs + } + bc->_vpdrag->updateLines(); + ret = true; + break; + case GDK_x: case GDK_X: - if (MOD__ALT_ONLY) { - // desktop->setToolboxFocusTo ("altx-rect"); - ret = TRUE; + { + if (MOD__CTRL) break; // Don't catch Ctrl+X ('cut') and Ctrl+Shift+X ('open XML editor') + // FIXME: Shouldn't we access _vpdrag->selection instead? + Inkscape::Selection *selection = sp_desktop_selection (bc->_vpdrag->desktop); + for (GSList const *i = selection->itemList(); i != NULL; i = i->next) { + if (!SP_IS_3DBOX (i->data)) continue; + sp_3dbox_switch_front_face (SP_3DBOX (i->data), Box3D::X); + } + bc->_vpdrag->updateLines(); + ret = true; + break; + } + + case GDK_y: + case GDK_Y: + { + if (MOD__CTRL) break; // Don't catch Ctrl+Y ("redo") + // FIXME: Shouldn't we access _vpdrag->selection instead? + Inkscape::Selection *selection = sp_desktop_selection (bc->_vpdrag->desktop); + for (GSList const *i = selection->itemList(); i != NULL; i = i->next) { + if (!SP_IS_3DBOX (i->data)) continue; + sp_3dbox_switch_front_face (SP_3DBOX (i->data), Box3D::Y); + } + bc->_vpdrag->updateLines(); + ret = true; + break; + } + + case GDK_z: + case GDK_Z: + { + if (MOD__CTRL) break; // Don't catch Ctrl+Z ("undo") + // FIXME: Shouldn't we access _vpdrag->selection instead? + Inkscape::Selection *selection = sp_desktop_selection (bc->_vpdrag->desktop); + for (GSList const *i = selection->itemList(); i != NULL; i = i->next) { + if (!SP_IS_3DBOX (i->data)) continue; + sp_3dbox_switch_front_face (SP_3DBOX (i->data), Box3D::Z); } + bc->_vpdrag->updateLines(); + ret = true; break; + } case GDK_Escape: sp_desktop_selection(desktop)->clear(); diff --git a/src/box3d.cpp b/src/box3d.cpp index 455a7889c..e4bda5d9c 100644 --- a/src/box3d.cpp +++ b/src/box3d.cpp @@ -103,6 +103,8 @@ sp_3dbox_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr box->my_counter = counter++; + box->front_bits = 0x0; + if (repr->attribute ("inkscape:perspective") == NULL) { // we are creating a new box; link it to the current perspective Box3D::Perspective3D::current_perspective->add_box (box); @@ -295,6 +297,15 @@ void sp_3dbox_set_ratios (SP3DBox *box, Box3D::Axis axes) } } +void +sp_3dbox_switch_front_face (SP3DBox *box, Box3D::Axis axis) +{ + if (Box3D::get_persp_of_box (box)->get_vanishing_point (axis)->is_finite()) { + box->front_bits = box->front_bits ^ axis; + } +} + + void sp_3dbox_position_set (SP3DBoxContext &bc) { @@ -566,6 +577,83 @@ sp_3dbox_get_svg_descr_of_persp (Box3D::Perspective3D *persp) return g_strdup(os.str().c_str()); } +void sp_3dbox_update_perspective_lines() +{ + SPEventContext *ec = inkscape_active_event_context(); + if (!SP_IS_3DBOX_CONTEXT (ec)) + return; + + SP_3DBOX_CONTEXT (ec)->_vpdrag->updateLines(); +} + +/* + * Manipulates corner1 through corner4 to contain the indices of the corners + * from which the perspective lines in the direction of 'axis' emerge + */ +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) +{ + // along which axis to switch when takint + Box3D::Axis switch_axis; + if (axis == Box3D::X || axis == Box3D::Y) { + switch_axis = (box->front_bits & axis) ? Box3D::Z : Box3D::NONE; + } else { + switch_axis = (box->front_bits & axis) ? Box3D::X : Box3D::NONE; + } + + switch (axis) { + case Box3D::X: + corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR); + corner2 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR); + corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR); + corner4 = sp_3dbox_get_corner_along_edge (box, 6 ^ switch_axis, axis, Box3D::REAR); + break; + case Box3D::Y: + corner1 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR); + corner2 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR); + corner3 = sp_3dbox_get_corner_along_edge (box, 4 ^ switch_axis, axis, Box3D::REAR); + corner4 = sp_3dbox_get_corner_along_edge (box, 5 ^ switch_axis, axis, Box3D::REAR); + break; + case Box3D::Z: + corner1 = sp_3dbox_get_corner_along_edge (box, 1 ^ switch_axis, axis, Box3D::REAR); + corner2 = sp_3dbox_get_corner_along_edge (box, 3 ^ switch_axis, axis, Box3D::REAR); + corner3 = sp_3dbox_get_corner_along_edge (box, 0 ^ switch_axis, axis, Box3D::REAR); + corner4 = sp_3dbox_get_corner_along_edge (box, 2 ^ switch_axis, axis, Box3D::REAR); + break; + } +} + +/** + * Returns the id of the corner on the edge along 'axis' and passing through 'corner' that + * lies on the front/rear face in this direction. + */ +guint +sp_3dbox_get_corner_id_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos) +{ + guint result; + guint other_corner = corner ^ axis; + Box3D::VanishingPoint *vp = Box3D::get_persp_of_box (box)->get_vanishing_point(axis); + if (vp->is_finite()) { + result = ( NR::L2 (vp->get_pos() - box->corners[corner]) + < NR::L2 (vp->get_pos() - box->corners[other_corner]) ? other_corner : corner); + } else { + // clear the axis bit and switch to the appropriate corner along axis, depending on the value of front_bits + result = ((corner & (0xF ^ axis)) ^ (box->front_bits & axis)); + } + + if (rel_pos == Box3D::FRONT) { + return result; + } else { + return result ^ axis; + } +} + +NR::Point +sp_3dbox_get_corner_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos) +{ + return box->corners[sp_3dbox_get_corner_id_along_edge (box, corner, axis, rel_pos)]; +} + // auxiliary functions static void sp_3dbox_update_corner_with_value_from_svg (SPObject *object, guint corner_id, const gchar *value) diff --git a/src/box3d.h b/src/box3d.h index d72c6eb9c..ebf2a68bb 100644 --- a/src/box3d.h +++ b/src/box3d.h @@ -43,6 +43,8 @@ struct SP3DBox : public SPGroup { double ratio_y; double ratio_z; + guint front_bits; /* used internally to determine which of two parallel faces is supposed to be the front face */ + gint my_counter; // for testing only }; @@ -58,11 +60,17 @@ void sp_3dbox_recompute_corners (SP3DBox *box, NR::Point const pt1, NR::Point co void sp_3dbox_update_curves (SP3DBox *box); void sp_3dbox_link_to_existing_paths (SP3DBox *box, Inkscape::XML::Node *repr); void sp_3dbox_set_ratios (SP3DBox *box, Box3D::Axis axes = Box3D::XYZ); +void sp_3dbox_switch_front_face (SP3DBox *box, Box3D::Axis axis); void sp_3dbox_move_corner_in_XY_plane (SP3DBox *box, guint id, NR::Point pt, Box3D::Axis axes = Box3D::XY); void sp_3dbox_move_corner_in_Z_direction (SP3DBox *box, guint id, NR::Point pt, bool constrained = true); 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_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); +guint sp_3dbox_get_corner_id_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos); +NR::Point sp_3dbox_get_corner_along_edge (const SP3DBox *box, guint corner, Box3D::Axis axis, Box3D::FrontOrRear rel_pos); + gchar * sp_3dbox_get_svg_descr_of_persp (Box3D::Perspective3D *persp); inline NR::Point sp_3dbox_get_corner (SP3DBox *box, guint id) { return box->corners[id]; } diff --git a/src/object-edit.cpp b/src/object-edit.cpp index e8390da20..f9e5c64fb 100644 --- a/src/object-edit.cpp +++ b/src/object-edit.cpp @@ -547,6 +547,7 @@ static void sp_3dbox_knot_set(SPItem *item, guint knot_id, Box3D::Axis direction } sp_3dbox_update_curves (box); sp_3dbox_set_ratios (box); + sp_3dbox_update_perspective_lines (); } static NR::Point sp_3dbox_knot_get(SPItem *item, guint knot_id) @@ -617,6 +618,7 @@ static void sp_3dbox_knot_set_uniformly(SPItem *item, guint knot_id, Box3D::Axis } sp_3dbox_update_curves (box); sp_3dbox_set_ratios (box); + sp_3dbox_update_perspective_lines (); } static void sp_3dbox_knot0_set_uniformly(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) diff --git a/src/perspective3d.cpp b/src/perspective3d.cpp index 213744937..d37c9f3a0 100644 --- a/src/perspective3d.cpp +++ b/src/perspective3d.cpp @@ -19,7 +19,6 @@ // can probably be removed later #include "inkscape.h" -#include "knotholder.h" namespace Box3D { @@ -312,13 +311,6 @@ Perspective3D::reshape_boxes (Box3D::Axis axes) } sp_3dbox_set_shape (box, true); - // FIXME: Is there a way update the knots without accessing the - // statically linked function knotholder_update_knots? - SPEventContext *ec = inkscape_active_event_context(); - g_assert (ec != NULL); - if (ec->shape_knot_holder != NULL) { - knotholder_update_knots(ec->shape_knot_holder, (SPItem *) box); - } } } diff --git a/src/vanishing-point.cpp b/src/vanishing-point.cpp index fa77d755f..ea3626dac 100644 --- a/src/vanishing-point.cpp +++ b/src/vanishing-point.cpp @@ -19,6 +19,8 @@ #include "desktop-handles.h" #include "box3d.h" +#include "knotholder.h" // FIXME: can we avoid direct access to knotholder_update_knots? + namespace Box3D { #define VP_KNOT_COLOR_NORMAL 0xffffff00 @@ -131,7 +133,7 @@ vp_drag_sel_changed(Inkscape::Selection *selection, gpointer data) { VPDrag *drag = (VPDrag *) data; drag->updateDraggers (); - //drag->updateLines (); + drag->updateLines (); } static void @@ -145,7 +147,7 @@ vp_drag_sel_modified (Inkscape::Selection *selection, guint flags, gpointer data drag->updateDraggers (); } ***/ - //drag->updateLines (); + drag->updateLines (); } // auxiliary function @@ -225,9 +227,11 @@ vp_knot_moved_handler (SPKnot *knot, NR::Point const *ppointer, guint state, gpo //d_new->updateKnotShape (); //d_new->updateTip (); - d_new->reshapeBoxes (p, Box3D::XYZ); + d_new->reshapeBoxes (d_new->point, Box3D::XYZ); d_new->updateBoxReprs (); + drag->updateLines (); + // TODO: Undo machinery; this doesn't work yet because perspectives must be created and // deleted according to changes in the svg representation, not based on any user input // as is currently the case. @@ -245,7 +249,7 @@ vp_knot_moved_handler (SPKnot *knot, NR::Point const *ppointer, guint state, gpo dragger->reshapeBoxes (p, Box3D::XYZ); dragger->updateBoxReprs (); - //dragger->parent->updateLines (); + drag->updateLines (); //drag->local_change = false; } @@ -530,6 +534,7 @@ VPDragger::reshapeBoxes (NR::Point const &p, Box3D::Axis axes) Box3D::Axis axis = persp->get_axis_of_VP (vp); get_persp_of_VP (vp)->reshape_boxes (axis); // FIXME: we should only update the direction of the VP } + parent->updateBoxHandles(); } void @@ -543,9 +548,16 @@ VPDragger::updateBoxReprs () VPDrag::VPDrag (SPDesktop *desktop) { this->desktop = desktop; - this->draggers = NULL; this->selection = sp_desktop_selection(desktop); + this->draggers = NULL; + this->lines = NULL; + this->show_lines = true; + this->front_or_rear_lines = 0x1; + + //this->selected = NULL; + this->local_change = false; + this->sel_changed_connection = this->selection->connectChanged( sigc::bind ( sigc::ptr_fun(&vp_drag_sel_changed), @@ -559,7 +571,7 @@ VPDrag::VPDrag (SPDesktop *desktop) ); this->updateDraggers (); - //this->updateLines (); + this->updateLines (); } VPDrag::~VPDrag() @@ -572,6 +584,12 @@ VPDrag::~VPDrag() } g_list_free (this->draggers); this->draggers = NULL; + + for (GSList const *i = this->lines; i != NULL; i = i->next) { + gtk_object_destroy( GTK_OBJECT (i->data)); + } + g_slist_free (this->lines); + this->lines = NULL; } /** @@ -636,6 +654,95 @@ VPDrag::updateDraggers () } } +/** +Regenerates the lines list from the current selection; is called on each move +of a dragger, so that lines are always in sync with the actual perspective +*/ +void +VPDrag::updateLines () +{ + // delete old lines + for (GSList const *i = this->lines; i != NULL; i = i->next) { + gtk_object_destroy( GTK_OBJECT (i->data)); + } + g_slist_free (this->lines); + this->lines = NULL; + + // do nothing if perspective lines are currently disabled + if (this->show_lines == 0) return; + + g_return_if_fail (this->selection != NULL); + + for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) { + if (!SP_IS_3DBOX(i->data)) continue; + SP3DBox *box = SP_3DBOX (i->data); + + this->drawLinesForFace (box, Box3D::X); + this->drawLinesForFace (box, Box3D::Y); + this->drawLinesForFace (box, Box3D::Z); + } +} + +void +VPDrag::updateBoxHandles () +{ + // FIXME: Is there a way to update the knots without accessing the + // statically linked function knotholder_update_knots? + + GSList *sel = (GSList *) selection->itemList(); + if (g_slist_length (sel) > 1) { + // Currently we only show handles if a single box is selected + return; + } + + if (!SP_IS_3DBOX (sel->data)) + return; + + SPEventContext *ec = inkscape_active_event_context(); + g_assert (ec != NULL); + if (ec->shape_knot_holder != NULL) { + knotholder_update_knots(ec->shape_knot_holder, (SPItem *) sel->data); + } +} + +/** + * Depending on the value of all_lines, draw the front and/or rear perspective lines starting from the given corners. + */ +void +VPDrag::drawLinesForFace (const SP3DBox *box, Box3D::Axis axis) //, guint corner1, guint corner2, guint corner3, guint corner4) +{ + guint color; + switch (axis) { + // TODO: Make color selectable by user + case Box3D::X: color = VP_LINE_COLOR_STROKE_X; break; + case Box3D::Y: color = VP_LINE_COLOR_STROKE_Y; break; + case Box3D::Z: color = VP_LINE_COLOR_STROKE_Z; break; + default: g_assert_not_reached(); + } + + NR::Point corner1, corner2, corner3, corner4; + sp_3dbox_corners_for_perspective_lines (box, axis, corner1, corner2, corner3, corner4); + + //VanishingPoint *vp = box->perspective->get_vanishing_point (axis); + VanishingPoint *vp = Box3D::get_persp_of_box (box)->get_vanishing_point (axis); + if (vp->is_finite()) { + NR::Point pt = vp->get_pos(); + if (this->front_or_rear_lines & 0x1) { + // draw 'front' perspective lines + this->addLine (corner1, pt, color); + this->addLine (corner2, pt, color); + } + if (this->front_or_rear_lines & 0x2) { + // draw 'rear' perspective lines + this->addLine (corner3, pt, color); + this->addLine (corner4, pt, color); + } + } else { + // TODO: Draw infinite PLs + g_warning ("Perspective lines for infinite vanishing points are not supported yet.\n"); + } + +} /** * Returns true if all boxes that are linked to a VP in the dragger are selected @@ -693,6 +800,20 @@ VPDrag::addDragger (VanishingPoint *vp) this->draggers = g_list_append (this->draggers, new_dragger); } +/** +Create a line from p1 to p2 and add it to the lines list + */ +void +VPDrag::addLine (NR::Point p1, NR::Point p2, guint32 rgba) +{ + SPCanvasItem *line = sp_canvas_item_new(sp_desktop_controls(this->desktop), SP_TYPE_CTRLLINE, NULL); + sp_ctrlline_set_coords(SP_CTRLLINE(line), p1, p2); + if (rgba != VP_LINE_COLOR_FILL) // fill is the default, so don't set color for it to speed up redraw + sp_ctrlline_set_rgba32 (SP_CTRLLINE(line), rgba); + sp_canvas_item_show (line); + this->lines = g_slist_append (this->lines, line); +} + } // namespace Box3D /* diff --git a/src/vanishing-point.h b/src/vanishing-point.h index 76299541a..98788f512 100644 --- a/src/vanishing-point.h +++ b/src/vanishing-point.h @@ -118,10 +118,16 @@ public: SPDesktop *desktop; GList *draggers; - //GSList *lines; + GSList *lines; void updateDraggers (); - //void updateLines (); + void updateLines (); + void updateBoxHandles (); + void drawLinesForFace (const SP3DBox *box, Box3D::Axis axis); //, guint corner1, guint corner2, guint corner3, guint corner4); + bool show_lines; /* whether perspective lines are drawn at all */ + guint front_or_rear_lines; /* whether we draw perspective lines from all corners or only the + front/rear corners (indicated by the first/second bit, respectively */ + inline bool hasEmptySelection() { return this->selection->isEmpty(); } bool allBoxesAreSelected (VPDragger *dragger); @@ -134,7 +140,7 @@ public: private: //void deselect_all(); - //void addLine (NR::Point p1, NR::Point p2, guint32 rgba); + void addLine (NR::Point p1, NR::Point p2, guint32 rgba); Inkscape::Selection *selection; sigc::connection sel_changed_connection; -- 2.30.2