From: cilix42 Date: Thu, 13 Dec 2007 09:45:27 +0000 (+0000) Subject: Fundamentally reworked version of the 3D box tool (among many other things, this... X-Git-Url: https://git.tokkee.org/?a=commitdiff_plain;h=4358ff6156766a315e38e72a5c3c83d6d5f7486b;p=inkscape.git Fundamentally reworked version of the 3D box tool (among many other things, this fixes bugs #168900 and #168868). See mailing list for details. Sorry for this single large commit but it was unfeasible to keep the history. --- diff --git a/src/Makefile_insert b/src/Makefile_insert index ff597b816..695d87f8f 100644 --- a/src/Makefile_insert +++ b/src/Makefile_insert @@ -44,7 +44,7 @@ libinkpre_a_SOURCES = \ bad-uri-exception.h \ box3d.cpp box3d.h \ box3d-context.cpp box3d-context.h \ - box3d-face.cpp box3d-face.h \ + box3d-side.cpp box3d-side.h \ brokenimage.xpm \ color-rgba.h \ color-profile.cpp color-profile.h \ @@ -116,8 +116,9 @@ libinkpre_a_SOURCES = \ pen-context.h \ pencil-context.cpp \ pencil-context.h \ + persp3d.cpp persp3d.h \ + persp3d-reference.cpp persp3d-reference.h \ perspective-line.cpp perspective-line.h \ - perspective3d.cpp perspective3d.h \ preferences.cpp preferences.h \ preferences-skeleton.h \ menus-skeleton.h \ @@ -128,6 +129,7 @@ libinkpre_a_SOURCES = \ print.cpp print.h \ profile-manager.cpp \ profile-manager.h \ + proj_pt.cpp proj_pt.h \ rect-context.cpp rect-context.h \ require-config.h \ rubberband.cpp rubberband.h \ @@ -145,6 +147,8 @@ libinkpre_a_SOURCES = \ snapped-line.cpp snapped-line.h \ snapped-point.cpp snapped-point.h \ snapper.cpp snapper.h \ + syseq.h \ + transf_mat_3x4.cpp transf_mat_3x4.h \ line-snapper.cpp line-snapper.h \ guide-snapper.cpp guide-snapper.h \ object-snapper.cpp object-snapper.h \ diff --git a/src/arc-context.cpp b/src/arc-context.cpp index c2ec30a64..82c00fd05 100644 --- a/src/arc-context.cpp +++ b/src/arc-context.cpp @@ -433,7 +433,38 @@ static void sp_arc_drag(SPArcContext *ac, NR::Point pt, guint state) sp_canvas_force_full_redraw_after_interruptions(desktop->canvas, 5); } - NR::Rect const r = Inkscape::snap_rectangular_box(desktop, ac->item, pt, ac->center, state); + bool ctrl_save = false; + if ((state & GDK_MOD1_MASK) && (state & GDK_CONTROL_MASK) && !(state & GDK_SHIFT_MASK)) { + // if Alt is pressed without Shift in addition to Control, temporarily drop the CONTROL mask + // so that the ellipse is not constrained to integer ratios + ctrl_save = true; + state = state ^ GDK_CONTROL_MASK; + } + NR::Rect r = Inkscape::snap_rectangular_box(desktop, ac->item, pt, ac->center, state); + if (ctrl_save) { + state = state ^ GDK_CONTROL_MASK; + } + + NR::Point dir = r.dimensions() / 2; + if (state & GDK_MOD1_MASK) { + /* With Alt let the ellipse pass through the mouse pointer */ + NR::Point c = r.midpoint(); + if (!ctrl_save) { + if (fabs(dir[NR::X]) > 1E-6 && fabs(dir[NR::Y]) > 1E-6) { + NR::Matrix const i2d (sp_item_i2d_affine (ac->item)); + NR::Point new_dir = pt * i2d - c; + new_dir[NR::X] *= dir[NR::Y] / dir[NR::X]; + double lambda = NR::L2(new_dir) / dir[NR::Y]; + r = NR::Rect (c - lambda*dir, c + lambda*dir); + } + } else { + /* with Alt+Ctrl (without Shift) we generate a perfect circle + with diameter click point <--> mouse pointer */ + double l = NR::L2 (dir); + NR::Point d = NR::Point (l, l); + r = NR::Rect (c - d, c + d); + } + } sp_arc_position_set(SP_ARC(ac->item), r.midpoint()[NR::X], r.midpoint()[NR::Y], diff --git a/src/attributes-test.h b/src/attributes-test.h index 57eb03eb5..384c0ca37 100644 --- a/src/attributes-test.h +++ b/src/attributes-test.h @@ -370,6 +370,15 @@ struct {char const *attr; bool supported;} const all_attrs[] = { {"sodipodi:cy", true}, {"sodipodi:rx", true}, {"sodipodi:ry", true}, + {"inkscape:perspectiveID", true}, + {"inkscape:corner0", true}, + {"inkscape:corner7", true}, + {"inkscape:box3dsidetype", true}, + {"inkscape:persp3d", true}, + {"inkscape:vp_x", true}, + {"inkscape:vp_y", true}, + {"inkscape:vp_z", true}, + {"inkscape:persp3d-origin", true}, {"sodipodi:start", true}, {"sodipodi:end", true}, {"sodipodi:open", true}, diff --git a/src/attributes.cpp b/src/attributes.cpp index 3777c68a1..8232065fc 100644 --- a/src/attributes.cpp +++ b/src/attributes.cpp @@ -118,13 +118,18 @@ static SPStyleProp const props[] = { /* SPRect */ {SP_ATTR_RX, "rx"}, {SP_ATTR_RY, "ry"}, - /* SP3DBox */ - {SP_ATTR_INKSCAPE_3DBOX, "inkscape:3dbox"}, - {SP_ATTR_INKSCAPE_3DBOX_CORNER_A, "inkscape:box3dcornerA"}, // "upper left front" corner - {SP_ATTR_INKSCAPE_3DBOX_CORNER_B, "inkscape:box3dcornerB"}, // "lower right front" corner - {SP_ATTR_INKSCAPE_3DBOX_CORNER_C, "inkscape:box3dcornerC"}, // "lower right rear" corner - {SP_ATTR_INKSCAPE_3DBOX_PERSPECTIVE, "inkscape:perspective"}, - {SP_ATTR_INKSCAPE_3DBOX_FACE, "inkscape:box3dface"}, + /* Box3D */ + {SP_ATTR_INKSCAPE_BOX3D_PERSPECTIVE_ID, "inkscape:perspectiveID"}, + {SP_ATTR_INKSCAPE_BOX3D_CORNER0, "inkscape:corner0"}, + {SP_ATTR_INKSCAPE_BOX3D_CORNER7, "inkscape:corner7"}, + /* Box3DSide */ + {SP_ATTR_INKSCAPE_BOX3D_SIDE_TYPE, "inkscape:box3dsidetype"}, // XYfront, etc. + /* Persp3D */ + {SP_ATTR_INKSCAPE_PERSP3D, "inkscape:persp3d"}, + {SP_ATTR_INKSCAPE_PERSP3D_VP_X, "inkscape:vp_x"}, + {SP_ATTR_INKSCAPE_PERSP3D_VP_Y, "inkscape:vp_y"}, + {SP_ATTR_INKSCAPE_PERSP3D_VP_Z, "inkscape:vp_z"}, + {SP_ATTR_INKSCAPE_PERSP3D_ORIGIN, "inkscape:persp3d-origin"}, /* SPEllipse */ {SP_ATTR_R, "r"}, {SP_ATTR_CX, "cx"}, diff --git a/src/attributes.h b/src/attributes.h index 0962827f8..33e060893 100644 --- a/src/attributes.h +++ b/src/attributes.h @@ -118,13 +118,18 @@ enum SPAttributeEnum { /* SPRect */ SP_ATTR_RX, SP_ATTR_RY, - /* SP3DBox */ - SP_ATTR_INKSCAPE_3DBOX, - SP_ATTR_INKSCAPE_3DBOX_CORNER_A, // "upper left front" corner - SP_ATTR_INKSCAPE_3DBOX_CORNER_B, // "lower right front" corner - SP_ATTR_INKSCAPE_3DBOX_CORNER_C, // "lower right rear" corner - SP_ATTR_INKSCAPE_3DBOX_PERSPECTIVE, - SP_ATTR_INKSCAPE_3DBOX_FACE, + /* Box3D */ + SP_ATTR_INKSCAPE_BOX3D_PERSPECTIVE_ID, + SP_ATTR_INKSCAPE_BOX3D_CORNER0, // "upper left front" corner (as a point in 3-space) + SP_ATTR_INKSCAPE_BOX3D_CORNER7, // "lower right rear" corner (as a point in 3-space) + /* Box3DSide */ + SP_ATTR_INKSCAPE_BOX3D_SIDE_TYPE, + /* Persp3D */ + SP_ATTR_INKSCAPE_PERSP3D, + SP_ATTR_INKSCAPE_PERSP3D_VP_X, + SP_ATTR_INKSCAPE_PERSP3D_VP_Y, + SP_ATTR_INKSCAPE_PERSP3D_VP_Z, + SP_ATTR_INKSCAPE_PERSP3D_ORIGIN, /* SPEllipse */ SP_ATTR_R, SP_ATTR_CX, diff --git a/src/axis-manip.cpp b/src/axis-manip.cpp index 7539f2324..1eed56439 100644 --- a/src/axis-manip.cpp +++ b/src/axis-manip.cpp @@ -13,6 +13,13 @@ #include "axis-manip.h" +namespace Proj { + +Axis axes[4] = { X, Y, Z, W }; + +} // namespace Proj + + namespace Box3D { Axis axes[3] = { X, Y, Z }; diff --git a/src/axis-manip.h b/src/axis-manip.h index 4ebdb5aab..e5cc963ba 100644 --- a/src/axis-manip.h +++ b/src/axis-manip.h @@ -15,6 +15,48 @@ #include #include "libnr/nr-point.h" +namespace Proj { + +enum VPState { + FINITE = 0, + INFINITE +}; + +// The X-/Y-/Z-axis corresponds to the first/second/third digit +// in binary representation, respectively. +enum Axis { + X = 0, + Y = 1, + Z = 2, + W = 3, + NONE +}; + +extern Axis axes[4]; + +inline gchar * string_from_axis (Proj::Axis axis) { + switch (axis) { + case X: + return "X"; + break; + case Y: + return "Y"; + break; + case Z: + return "Z"; + break; + case W: + return "W"; + break; + case NONE: + return "NONE"; + break; + } + return ""; +} + +} // namespace Proj + namespace Box3D { const double epsilon = 1e-6; @@ -39,15 +81,74 @@ enum FrontOrRear { // find a better name REAR = 8 }; +// converts X, Y, Z respectively to 0, 1, 2 (for use as array indices, e.g) +inline int axis_to_int(Box3D::Axis axis) { + switch (axis) { + case Box3D::X: + return 0; + break; + case Box3D::Y: + return 1; + break; + case Box3D::Z: + return 2; + break; + case Box3D::NONE: + return -1; + break; + default: + g_assert_not_reached(); + } +} + +inline Proj::Axis toProj(Box3D::Axis axis) { + switch (axis) { + case Box3D::X: + return Proj::X; + case Box3D::Y: + return Proj::Y; + case Box3D::Z: + return Proj::Z; + case Box3D::NONE: + return Proj::NONE; + default: + g_assert_not_reached(); + } +} + extern Axis axes[3]; extern Axis planes[3]; extern FrontOrRear face_positions [2]; +} // namespace Box3D + +namespace Proj { + +inline Box3D::Axis toAffine(Proj::Axis axis) { + switch (axis) { + case Proj::X: + return Box3D::X; + case Proj::Y: + return Box3D::Y; + case Proj::Z: + return Box3D::Z; + case Proj::NONE: + return Box3D::NONE; + default: + g_assert_not_reached(); + } +} + +} // namespace Proj + +namespace Box3D { + // Given a bit sequence that unambiguously specifies the face of a 3D box, // return a number between 0 and 5 corresponding to that particular face // (which is normally used to index an array). Return -1 if the bit sequence // does not specify a face. A face can either be given by its plane (e.g, XY) // or by the axis that is orthogonal to it (e.g., Z). +/*** inline gint face_to_int (guint face_id) { switch (face_id) { case 1: return 0; @@ -67,10 +168,75 @@ inline gint face_to_int (guint face_id) { default: return -1; } } +***/ + +/*** +inline gint int_to_face (guint id) { + switch (id) { + case 0: return 6; + case 1: return 14; + case 2: return 5; + case 3: return 13; + case 4: return 3; + case 5: return 11; + + default: return -1; + } +} +***/ + +/* + * New version: + * Identify the axes X, Y, Z with the numbers 0, 1, 2. + * A box's face is identified by the axis perpendicular to it. + * For a rear face, add 3. + */ +// Given a bit sequence that unambiguously specifies the face of a 3D box, +// return a number between 0 and 5 corresponding to that particular face +// (which is normally used to index an array). Return -1 if the bit sequence +// does not specify a face. A face can either be given by its plane (e.g, XY) +// or by the axis that is orthogonal to it (e.g., Z). +inline gint face_to_int (guint face_id) { + switch (face_id) { + case 1: return 0; + case 2: return 1; + case 4: return 2; + case 3: return 2; + case 5: return 1; + case 6: return 0; + case 9: return 3; + case 10: return 4; + case 12: return 5; + case 11: return 5; + case 13: return 4; + case 14: return 3; + + default: return -1; + } +} + +inline gint int_to_face (guint id) { + switch (id) { + case 0: return Box3D::YZ ^ Box3D::FRONT; + case 1: return Box3D::XZ ^ Box3D::FRONT; + case 2: return Box3D::XY ^ Box3D::FRONT; + case 3: return Box3D::YZ ^ Box3D::REAR; + case 4: return Box3D::XZ ^ Box3D::REAR; + case 5: return Box3D::XY ^ Box3D::REAR; + } + return Box3D::NONE; // should not be reached +} + +inline bool is_face_id (guint face_id) { + return !((face_id & 0x7) == 0x7); +} + +/** inline gint opposite_face (guint face_id) { - return face_id + ((face_id % 2 == 0) ? 1 : -1); + return face_id + (((face_id % 2) == 0) ? 1 : -1); } +**/ inline guint number_of_axis_directions (Box3D::Axis axis) { guint num = 0; @@ -90,6 +256,7 @@ inline bool is_single_axis_direction (Box3D::Axis dir) { return (!(dir & (dir - 1)) && dir); } +/*** // Warning: We don't check that axis really unambiguously specifies a plane. // Make sure this is the case when calling this function. inline gint face_containing_corner (Box3D::Axis axis, guint corner) { @@ -98,7 +265,7 @@ inline gint face_containing_corner (Box3D::Axis axis, guint corner) { } return face_to_int (axis ^ ((corner & axis) ? Box3D::REAR : Box3D::FRONT)); } - +***/ /** * Given two axis directions out of {X, Y, Z} or the corresponding plane, return the remaining one diff --git a/src/box3d-context.cpp b/src/box3d-context.cpp index 7ceed0aca..d74b0e7d1 100644 --- a/src/box3d-context.cpp +++ b/src/box3d-context.cpp @@ -1,4 +1,4 @@ -#define __SP_3DBOX_CONTEXT_C__ +#define __SP_BOX3D_CONTEXT_C__ /* * 3D box drawing context @@ -39,58 +39,63 @@ #include "xml/node-event-vector.h" #include "prefs-utils.h" #include "context-fns.h" +#include "inkscape.h" +#include "desktop-style.h" +#include "transf_mat_3x4.h" +#include "perspective-line.h" +#include "persp3d.h" +#include "box3d-side.h" +#include "document-private.h" // for debugging (see case GDK_P) +#include "line-geometry.h" -static void sp_3dbox_context_class_init(SP3DBoxContextClass *klass); -static void sp_3dbox_context_init(SP3DBoxContext *box3d_context); -static void sp_3dbox_context_dispose(GObject *object); +static void sp_box3d_context_class_init(Box3DContextClass *klass); +static void sp_box3d_context_init(Box3DContext *box3d_context); +static void sp_box3d_context_dispose(GObject *object); -static void sp_3dbox_context_setup(SPEventContext *ec); -static void sp_3dbox_context_set(SPEventContext *ec, gchar const *key, gchar const *val); +static void sp_box3d_context_setup(SPEventContext *ec); -static gint sp_3dbox_context_root_handler(SPEventContext *event_context, GdkEvent *event); -static gint sp_3dbox_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event); +static gint sp_box3d_context_root_handler(SPEventContext *event_context, GdkEvent *event); +static gint sp_box3d_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event); -static void sp_3dbox_drag(SP3DBoxContext &bc, guint state); -static void sp_3dbox_finish(SP3DBoxContext *bc); +static void sp_box3d_drag(Box3DContext &bc, guint state); +static void sp_box3d_finish(Box3DContext *bc); static SPEventContextClass *parent_class; - -GtkType sp_3dbox_context_get_type() +GtkType sp_box3d_context_get_type() { static GType type = 0; if (!type) { GTypeInfo info = { - sizeof(SP3DBoxContextClass), + sizeof(Box3DContextClass), NULL, NULL, - (GClassInitFunc) sp_3dbox_context_class_init, + (GClassInitFunc) sp_box3d_context_class_init, NULL, NULL, - sizeof(SP3DBoxContext), + sizeof(Box3DContext), 4, - (GInstanceInitFunc) sp_3dbox_context_init, + (GInstanceInitFunc) sp_box3d_context_init, NULL, /* value_table */ }; - type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SP3DBoxContext", &info, (GTypeFlags) 0); + type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "Box3DContext", &info, (GTypeFlags) 0); } return type; } -static void sp_3dbox_context_class_init(SP3DBoxContextClass *klass) +static void sp_box3d_context_class_init(Box3DContextClass *klass) { GObjectClass *object_class = (GObjectClass *) klass; SPEventContextClass *event_context_class = (SPEventContextClass *) klass; parent_class = (SPEventContextClass *) g_type_class_peek_parent(klass); - object_class->dispose = sp_3dbox_context_dispose; + object_class->dispose = sp_box3d_context_dispose; - event_context_class->setup = sp_3dbox_context_setup; - event_context_class->set = sp_3dbox_context_set; - event_context_class->root_handler = sp_3dbox_context_root_handler; - event_context_class->item_handler = sp_3dbox_context_item_handler; + event_context_class->setup = sp_box3d_context_setup; + event_context_class->root_handler = sp_box3d_context_root_handler; + event_context_class->item_handler = sp_box3d_context_item_handler; } -static void sp_3dbox_context_init(SP3DBoxContext *box3d_context) +static void sp_box3d_context_init(Box3DContext *box3d_context) { SPEventContext *event_context = SP_EVENT_CONTEXT(box3d_context); @@ -116,9 +121,9 @@ static void sp_3dbox_context_init(SP3DBoxContext *box3d_context) new (&box3d_context->sel_changed_connection) sigc::connection(); } -static void sp_3dbox_context_dispose(GObject *object) +static void sp_box3d_context_dispose(GObject *object) { - SP3DBoxContext *bc = SP_3DBOX_CONTEXT(object); + Box3DContext *bc = SP_BOX3D_CONTEXT(object); SPEventContext *ec = SP_EVENT_CONTEXT(object); ec->enableGrDrag(false); @@ -131,7 +136,7 @@ static void sp_3dbox_context_dispose(GObject *object) /* fixme: This is necessary because we do not grab */ if (bc->item) { - sp_3dbox_finish(bc); + sp_box3d_finish(bc); } if (ec->shape_knot_holder) { @@ -164,9 +169,9 @@ static Inkscape::XML::NodeEventVector ec_shape_repr_events = { \brief Callback that processes the "changed" signal on the selection; destroys old and creates new knotholder */ -void sp_3dbox_context_selection_changed(Inkscape::Selection *selection, gpointer data) +static void sp_box3d_context_selection_changed(Inkscape::Selection *selection, gpointer data) { - SP3DBoxContext *bc = SP_3DBOX_CONTEXT(data); + Box3DContext *bc = SP_BOX3D_CONTEXT(data); SPEventContext *ec = SP_EVENT_CONTEXT(bc); if (ec->shape_knot_holder) { // destroy knotholder @@ -180,6 +185,10 @@ void sp_3dbox_context_selection_changed(Inkscape::Selection *selection, gpointer ec->shape_repr = 0; } + SPDocument *doc = sp_desktop_document(bc->desktop); + doc->persps_sel.clear(); + doc->persps_sel = persp3d_currently_selected(bc); + SPItem *item = selection->singleItem(); if (item) { ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop); @@ -189,28 +198,17 @@ void sp_3dbox_context_selection_changed(Inkscape::Selection *selection, gpointer Inkscape::GC::anchor(shape_repr); sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec); } - if (SP_IS_3DBOX (item)) { - bc->_vpdrag->document->current_perspective = bc->_vpdrag->document->get_persp_of_box (SP_3DBOX (item)); - } - } else { - /* If several boxes sharing the same perspective are selected, - we can still set the current selection accordingly */ - std::set perspectives; - for (GSList *i = (GSList *) selection->itemList(); i != NULL; i = i->next) { - if (SP_IS_3DBOX (i->data)) { - perspectives.insert (bc->_vpdrag->document->get_persp_of_box (SP_3DBOX (i->data))); - } - } - if (perspectives.size() == 1) { - bc->_vpdrag->document->current_perspective = *(perspectives.begin()); - } - // TODO: What to do if several boxes with different perspectives are selected? + } + + if (doc->persps_sel.size() == 1) { + // selecting a single box changes the current perspective + doc->current_persp3d = *(doc->persps_sel.begin()); } } -static void sp_3dbox_context_setup(SPEventContext *ec) +static void sp_box3d_context_setup(SPEventContext *ec) { - SP3DBoxContext *bc = SP_3DBOX_CONTEXT(ec); + Box3DContext *bc = SP_BOX3D_CONTEXT(ec); if (((SPEventContextClass *) parent_class)->setup) { ((SPEventContextClass *) parent_class)->setup(ec); @@ -229,7 +227,7 @@ static void sp_3dbox_context_setup(SPEventContext *ec) bc->sel_changed_connection.disconnect(); bc->sel_changed_connection = sp_desktop_selection(ec->desktop)->connectChanged( - sigc::bind(sigc::ptr_fun(&sp_3dbox_context_selection_changed), (gpointer)bc) + sigc::bind(sigc::ptr_fun(&sp_box3d_context_selection_changed), (gpointer)bc) ); bc->_vpdrag = new Box3D::VPDrag(sp_desktop_document (ec->desktop)); @@ -245,26 +243,7 @@ static void sp_3dbox_context_setup(SPEventContext *ec) bc->_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack()); } -static void sp_3dbox_context_set(SPEventContext */*ec*/, gchar const */*key*/, gchar const */*val*/) -{ - //SP3DBoxContext *bc = SP_3DBOX_CONTEXT(ec); - - /* fixme: Proper error handling for non-numeric data. Use a locale-independent function like - * g_ascii_strtod (or a thin wrapper that does the right thing for invalid values inf/nan). */ - /** - if ( strcmp(key, "rx") == 0 ) { - bc->rx = ( val - ? g_ascii_strtod (val, NULL) - : 0.0 ); - } else if ( strcmp(key, "ry") == 0 ) { - bc->ry = ( val - ? g_ascii_strtod (val, NULL) - : 0.0 ); - } - **/ -} - -static gint sp_3dbox_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event) +static gint sp_box3d_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event) { SPDesktop *desktop = event_context->desktop; @@ -289,7 +268,7 @@ static gint sp_3dbox_context_item_handler(SPEventContext *event_context, SPItem return ret; } -static gint sp_3dbox_context_root_handler(SPEventContext *event_context, GdkEvent *event) +static gint sp_box3d_context_root_handler(SPEventContext *event_context, GdkEvent *event) { static bool dragging; @@ -297,7 +276,9 @@ static gint sp_3dbox_context_root_handler(SPEventContext *event_context, GdkEven Inkscape::Selection *selection = sp_desktop_selection (desktop); int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12); - SP3DBoxContext *bc = SP_3DBOX_CONTEXT(event_context); + Box3DContext *bc = SP_BOX3D_CONTEXT(event_context); + g_assert (SP_ACTIVE_DOCUMENT->current_persp3d); + Persp3D *cur_persp = SP_ACTIVE_DOCUMENT->current_persp3d; event_context->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); @@ -313,17 +294,24 @@ static gint sp_3dbox_context_root_handler(SPEventContext *event_context, GdkEven event_context->yp = (gint) button_w[NR::Y]; event_context->within_tolerance = true; - // remember clicked item, disregarding groups, honoring Alt + // remember clicked item, *not* disregarding groups (since a 3D box is a group), honoring Alt event_context->item_to_select = sp_event_context_find_item (desktop, button_w, event->button.state & GDK_MOD1_MASK, event->button.state & GDK_CONTROL_MASK); dragging = true; - /* Position center */ + /* */ NR::Point const button_dt(desktop->w2d(button_w)); bc->drag_origin = button_dt; bc->drag_ptB = button_dt; bc->drag_ptC = button_dt; + /* Projective preimages of clicked point under current perspective */ + bc->drag_origin_proj = cur_persp->tmat.preimage (button_dt, 0, Proj::Z); + bc->drag_ptB_proj = bc->drag_origin_proj; + bc->drag_ptC_proj = bc->drag_origin_proj; + bc->drag_ptC_proj.normalize(); + bc->drag_ptC_proj[Proj::Z] = 0.25; + /* Snap center */ SnapManager const &m = desktop->namedview->snap_manager; bc->center = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, @@ -362,39 +350,52 @@ static gint sp_3dbox_context_root_handler(SPEventContext *event_context, GdkEven bc->ctrl_dragged = event->motion.state & GDK_CONTROL_MASK; if (event->motion.state & GDK_SHIFT_MASK && !bc->extruded && bc->item) { - /* once shift is pressed, set bc->extruded (no need to create further faces; - all of them are already created in sp_3dbox_init); since we made the rear face - invisible in the beginning to avoid "flashing", we must set its correct style now */ + // once shift is pressed, set bc->extruded bc->extruded = true; - SP_3DBOX (bc->item)->faces[5]->set_style (NULL, true); } if (!bc->extruded) { bc->drag_ptB = motion_dt; bc->drag_ptC = motion_dt; + + bc->drag_ptB_proj = cur_persp->tmat.preimage (motion_dt, 0, Proj::Z); + bc->drag_ptC_proj = bc->drag_ptB_proj; + bc->drag_ptC_proj.normalize(); + bc->drag_ptC_proj[Proj::Z] = 0.25; } else { // Without Ctrl, motion of the extruded corner is constrained to the // perspective line from drag_ptB to vanishing point Y. if (!bc->ctrl_dragged) { - bc->drag_ptC = Box3D::perspective_line_snap (bc->drag_ptB, Box3D::Z, motion_dt, bc->_vpdrag->document->current_perspective); + /* snapping */ + Box3D::PerspectiveLine pline (bc->drag_ptB, Proj::Z, SP_ACTIVE_DOCUMENT->current_persp3d); + bc->drag_ptC = pline.closest_to (motion_dt); + + bc->drag_ptB_proj.normalize(); + bc->drag_ptC_proj = cur_persp->tmat.preimage (bc->drag_ptC, bc->drag_ptB_proj[Proj::X], Proj::X); } else { bc->drag_ptC = motion_dt; + + bc->drag_ptB_proj.normalize(); + bc->drag_ptC_proj = cur_persp->tmat.preimage (motion_dt, bc->drag_ptB_proj[Proj::X], Proj::X); } bc->drag_ptC = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, bc->drag_ptC, bc->item).getPoint(); if (bc->ctrl_dragged) { - Box3D::PerspectiveLine pl1 (NR::Point (event_context->xp, event_context->yp), Box3D::Y, bc->_vpdrag->document->current_perspective); - Box3D::PerspectiveLine pl2 (bc->drag_ptB, Box3D::X, bc->_vpdrag->document->current_perspective); - NR::Point corner1 = pl1.meet(pl2); + g_print ("TODO: What should happen here?\n"); + // Update bc->drag_ptB in case we are ctrl-dragging + /*** + Box3D::PerspectiveLine pl1 (NR::Point (event_context->xp, event_context->yp), Box3D::Y, bc->_vpdrag->document->current_perspective); + Box3D::PerspectiveLine pl2 (bc->drag_ptB, Box3D::X, bc->_vpdrag->document->current_perspective); + NR::Point corner1 = pl1.meet(pl2); - Box3D::PerspectiveLine pl3 (corner1, Box3D::X, bc->_vpdrag->document->current_perspective); - Box3D::PerspectiveLine pl4 (bc->drag_ptC, Box3D::Z, bc->_vpdrag->document->current_perspective); - bc->drag_ptB = pl3.meet(pl4); + Box3D::PerspectiveLine pl3 (corner1, Box3D::X, bc->_vpdrag->document->current_perspective); + Box3D::PerspectiveLine pl4 (bc->drag_ptC, Box3D::Z, bc->_vpdrag->document->current_perspective); + bc->drag_ptB = pl3.meet(pl4); + ***/ } } - - - sp_3dbox_drag(*bc, event->motion.state); - + + sp_box3d_drag(*bc, event->motion.state); + ret = TRUE; } break; @@ -405,7 +406,7 @@ static gint sp_3dbox_context_root_handler(SPEventContext *event_context, GdkEven if (!event_context->within_tolerance) { // we've been dragging, finish the box - sp_3dbox_finish(bc); + sp_box3d_finish(bc); } else if (event_context->item_to_select) { // no dragging, select clicked item if any if (event->button.state & GDK_SHIFT_MASK) { @@ -453,78 +454,100 @@ static gint sp_3dbox_context_root_handler(SPEventContext *event_context, GdkEven break; case GDK_bracketright: - inkscape_active_document()->current_perspective->rotate (Box3D::X, -180/snaps, MOD__ALT); + persp3d_rotate_VP (inkscape_active_document()->current_persp3d, Proj::X, -180/snaps, MOD__ALT); ret = true; break; case GDK_bracketleft: - inkscape_active_document()->current_perspective->rotate (Box3D::X, 180/snaps, MOD__ALT); + persp3d_rotate_VP (inkscape_active_document()->current_persp3d, Proj::X, 180/snaps, MOD__ALT); ret = true; break; case GDK_parenright: - inkscape_active_document()->current_perspective->rotate (Box3D::Y, -180/snaps, MOD__ALT); + persp3d_rotate_VP (inkscape_active_document()->current_persp3d, Proj::Y, -180/snaps, MOD__ALT); ret = true; break; case GDK_parenleft: - inkscape_active_document()->current_perspective->rotate (Box3D::Y, 180/snaps, MOD__ALT); + persp3d_rotate_VP (inkscape_active_document()->current_persp3d, Proj::Y, 180/snaps, MOD__ALT); ret = true; break; case GDK_braceright: - inkscape_active_document()->current_perspective->rotate (Box3D::Z, -180/snaps, MOD__ALT); + persp3d_rotate_VP (inkscape_active_document()->current_persp3d, Proj::Z, -180/snaps, MOD__ALT); ret = true; break; case GDK_braceleft: - inkscape_active_document()->current_perspective->rotate (Box3D::Z, 180/snaps, MOD__ALT); + persp3d_rotate_VP (inkscape_active_document()->current_persp3d, Proj::Z, 180/snaps, MOD__ALT); ret = true; break; - case GDK_I: - Box3D::Perspective3D::print_debugging_info(); + case GDK_O: + Box3D::create_canvas_point(persp3d_get_VP(inkscape_active_document()->current_persp3d, Proj::W).affine(), + 6, 0xff00ff00); ret = true; break; - case GDK_L: - if (MOD__CTRL) break; // Don't catch Shift+Ctrl+L (Layers dialog) - bc->_vpdrag->show_lines = !bc->_vpdrag->show_lines; - bc->_vpdrag->updateLines(); + case GDK_I: + if (MOD__ALT) { + persp3d_print_debugging_info_all (inkscape_active_document()); + } else { + persp3d_print_debugging_info (inkscape_active_document()->current_persp3d); + } ret = true; break; - 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 + case GDK_P: + { + if (MOD__SHIFT && MOD__CTRL) break; // Don't catch Shift+Ctrl+P (Preferences dialog) + SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(SP_ACTIVE_DOCUMENT); + g_print ("=== Persp3D Objects: ==============================\n"); + for (SPObject *i = sp_object_first_child(SP_OBJECT(defs)); i != NULL; i = SP_OBJECT_NEXT(i) ) { + g_print ("Object encountered\n"); + if (SP_IS_PERSP3D(i)) { + //g_print ("Encountered a Persp3D in defs\n"); + SP_PERSP3D(i)->tmat.print(); + g_print ("\n"); + g_print ("Computing preimage of point (300, 400)\n"); + SP_PERSP3D(i)->tmat.preimage (NR::Point (300, 400), 0, Proj::Z); + g_print ("Computing preimage of point (200, 500)\n"); + SP_PERSP3D(i)->tmat.preimage (NR::Point (200, 500), 0, Proj::Z); + } } - bc->_vpdrag->updateLines(); + g_print ("===================================================\n"); + ret = true; break; + } + + case GDK_V: + if (bc->_vpdrag) { + bc->_vpdrag->printDraggers(); + ret = true; + } else { + g_print ("No VPDrag in Box3DContext.\n"); + } + break; + case GDK_x: + if (MOD__ALT_ONLY) { + desktop->setToolboxFocusTo ("altx-box3d"); + ret = TRUE; + } + break; case GDK_X: - { if (MOD__CTRL) break; // Don't catch Ctrl+X ('cut') and Ctrl+Shift+X ('open XML editor') - Inkscape::Selection *selection = sp_desktop_selection (inkscape_active_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(); + persp3d_toggle_VPs(persp3d_currently_selected(bc), Proj::X); + bc->_vpdrag->updateLines(); // FIXME: Shouldn't this be done automatically? ret = true; break; - } - + case GDK_Y: { if (MOD__CTRL) break; // Don't catch Ctrl+Y ("redo") - Inkscape::Selection *selection = sp_desktop_selection (inkscape_active_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(); + persp3d_toggle_VPs(persp3d_currently_selected(bc), Proj::Y); + bc->_vpdrag->updateLines(); // FIXME: Shouldn't this be done automatically? ret = true; break; } @@ -532,12 +555,8 @@ static gint sp_3dbox_context_root_handler(SPEventContext *event_context, GdkEven case GDK_Z: { if (MOD__CTRL) break; // Don't catch Ctrl+Z ("undo") - Inkscape::Selection *selection = sp_desktop_selection (inkscape_active_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(); + persp3d_toggle_VPs(persp3d_currently_selected(bc), Proj::Z); + bc->_vpdrag->updateLines(); // FIXME: Shouldn't this be done automatically? ret = true; break; } @@ -554,7 +573,7 @@ static gint sp_3dbox_context_root_handler(SPEventContext *event_context, GdkEven dragging = false; if (!event_context->within_tolerance) { // we've been dragging, finish the box - sp_3dbox_finish(bc); + sp_box3d_finish(bc); } // do not return true, so that space would work switching to selector } @@ -593,7 +612,7 @@ static gint sp_3dbox_context_root_handler(SPEventContext *event_context, GdkEven return ret; } -static void sp_3dbox_drag(SP3DBoxContext &bc, guint state) +static void sp_box3d_drag(Box3DContext &bc, guint state) { SPDesktop *desktop = SP_EVENT_CONTEXT(&bc)->desktop; @@ -606,48 +625,55 @@ static void sp_3dbox_drag(SP3DBoxContext &bc, guint state) /* Create object */ Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_EVENT_CONTEXT_DOCUMENT(&bc)); Inkscape::XML::Node *repr = xml_doc->createElement("svg:g"); - repr->setAttribute("sodipodi:type", "inkscape:3dbox"); + repr->setAttribute("sodipodi:type", "inkscape:box3d"); /* Set style */ sp_desktop_apply_style_tool (desktop, repr, "tools.shapes.3dbox", false); bc.item = (SPItem *) desktop->currentLayer()->appendChildRepr(repr); Inkscape::GC::release(repr); - bc.item->transform = SP_ITEM(desktop->currentRoot())->getRelativeTransform(desktop->currentLayer()); - - /* Hook paths to the faces of the box (applies last used style if necessary) */ + /**** bc.item->transform = SP_ITEM(desktop->currentRoot())->getRelativeTransform(desktop->currentLayer()); ****/ + Inkscape::XML::Node *repr_side; for (int i = 0; i < 6; ++i) { - SP_3DBOX(bc.item)->faces[i]->hook_path_to_3dbox(); + repr_side = xml_doc->createElement("svg:path"); + repr_side->setAttribute("sodipodi:type", "inkscape:box3dside"); + repr->addChild(repr_side, NULL); + + Box3DSide *side = SP_BOX3D_SIDE(inkscape_active_document()->getObjectByRepr (repr_side)); + + guint desc = Box3D::int_to_face(i); + + Box3D::Axis plane = (Box3D::Axis) (desc & 0x7); + plane = (Box3D::is_plane(plane) ? plane : Box3D::orth_plane_or_axis(plane)); + side->dir1 = Box3D::extract_first_axis_direction(plane); + side->dir2 = Box3D::extract_second_axis_direction(plane); + side->front_or_rear = (Box3D::FrontOrRear) (desc & 0x8); + + SP_OBJECT(side)->updateRepr(); // calls box3d_side_write() and updates, e.g., the axes string description } - // make rear face invisible in the beginning to avoid "flashing" - SP_3DBOX (bc.item)->faces[5]->set_style (NULL, false); + box3d_set_z_orders(SP_BOX3D(bc.item)); bc.item->updateRepr(); - sp_3dbox_set_z_orders_in_the_first_place (SP_3DBOX (bc.item)); // TODO: It would be nice to show the VPs during dragging, but since there is no selection // at this point (only after finishing the box), we must do this "manually" - bc._vpdrag->updateDraggers(); + /**** bc._vpdrag->updateDraggers(); ****/ sp_canvas_force_full_redraw_after_interruptions(desktop->canvas, 5); } - // FIXME: remove these extra points - NR::Point pt = bc.drag_ptB; - NR::Point shift_pt = bc.drag_ptC; + g_assert(bc.item); - NR::Rect r; - if (!(state & GDK_SHIFT_MASK)) { - r = Inkscape::snap_rectangular_box(desktop, bc.item, pt, bc.center, state); - } else { - r = Inkscape::snap_rectangular_box(desktop, bc.item, shift_pt, bc.center, state); - } + SPBox3D *box = SP_BOX3D(bc.item); + + box->orig_corner0 = bc.drag_origin_proj; + box->orig_corner7 = bc.drag_ptC_proj; - SPEventContext *ec = SP_EVENT_CONTEXT(&bc); - NR::Point origin_w(ec->xp, ec->yp); - NR::Point origin(desktop->w2d(origin_w)); - sp_3dbox_position_set(bc); - sp_3dbox_set_z_orders_in_the_first_place (SP_3DBOX (bc.item)); + /* we need to call this from here (instead of from box3d_position_set(), for example) + because z-order setting must not interfere with display updates during undo/redo */ + box3d_set_z_orders (box); + + box3d_position_set(SP_BOX3D(bc.item)); // status text //GString *Ax = SP_PX_TO_METRIC_STRING(origin[NR::X], desktop->namedview->getDefaultMetric()); @@ -657,17 +683,23 @@ static void sp_3dbox_drag(SP3DBoxContext &bc, guint state) //g_string_free(Ay, FALSE); } -static void sp_3dbox_finish(SP3DBoxContext *bc) +static void sp_box3d_finish(Box3DContext *bc) { bc->_message_context->clear(); + g_assert (SP_ACTIVE_DOCUMENT->current_persp3d); + //Persp3D *cur_persp = SP_ACTIVE_DOCUMENT->current_persp3d; if ( bc->item != NULL ) { - SPDesktop * desktop; + SPDesktop * desktop = SP_EVENT_CONTEXT_DESKTOP(bc); + + SPBox3D *box = SP_BOX3D(bc->item); + + box->orig_corner0 = bc->drag_origin_proj; + box->orig_corner7 = bc->drag_ptC_proj; - desktop = SP_EVENT_CONTEXT_DESKTOP(bc); + box->updateRepr(); - SP_OBJECT(bc->item)->updateRepr(); - sp_3dbox_set_ratios(SP_3DBOX(bc->item)); + box3d_relabel_corners(box); sp_canvas_end_forced_full_redraws(desktop->canvas); diff --git a/src/box3d-context.h b/src/box3d-context.h index 33176ae84..1817aa180 100644 --- a/src/box3d-context.h +++ b/src/box3d-context.h @@ -1,5 +1,5 @@ -#ifndef __SP_3DBOX_CONTEXT_H__ -#define __SP_3DBOX_CONTEXT_H__ +#ifndef __SP_BOX3D_CONTEXT_H__ +#define __SP_BOX3D_CONTEXT_H__ /* * 3D box drawing context @@ -17,22 +17,23 @@ #include #include "event-context.h" -#include "perspective3d.h" +#include "proj_pt.h" +#include "vanishing-point.h" struct SPKnotHolder; -#define SP_TYPE_3DBOX_CONTEXT (sp_3dbox_context_get_type ()) -#define SP_3DBOX_CONTEXT(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_3DBOX_CONTEXT, SP3DBoxContext)) -#define SP_3DBOX_CONTEXT_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), SP_TYPE_3DBOX_CONTEXT, SP3DBoxContextClass)) -#define SP_IS_3DBOX_CONTEXT(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_3DBOX_CONTEXT)) -#define SP_IS_3DBOX_CONTEXT_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), SP_TYPE_3DBOX_CONTEXT)) +#define SP_TYPE_BOX3D_CONTEXT (sp_box3d_context_get_type ()) +#define SP_BOX3D_CONTEXT(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_BOX3D_CONTEXT, Box3DContext)) +#define SP_BOX3D_CONTEXT_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), SP_TYPE_BOX3D_CONTEXT, Box3DContextClass)) +#define SP_IS_BOX3D_CONTEXT(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_BOX3D_CONTEXT)) +#define SP_IS_BOX3D_CONTEXT_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), SP_TYPE_BOX3D_CONTEXT)) -class SP3DBoxContext; -class SP3DBoxContextClass; +class Box3DContext; +class Box3DContextClass; -struct SP3DBoxContext : public SPEventContext { - SPItem *item; - NR::Point center; +struct Box3DContext : public SPEventContext { + SPItem *item; + NR::Point center; /** * save three corners while dragging: @@ -45,22 +46,38 @@ struct SP3DBoxContext : public SPEventContext { NR::Point drag_origin; NR::Point drag_ptB; NR::Point drag_ptC; + + Proj::Pt3 drag_origin_proj; + Proj::Pt3 drag_ptB_proj; + Proj::Pt3 drag_ptC_proj; + bool ctrl_dragged; /* whether we are ctrl-dragging */ bool extruded; /* whether shift-dragging already occured (i.e. the box is already extruded) */ Box3D::VPDrag * _vpdrag; - sigc::connection sel_changed_connection; + sigc::connection sel_changed_connection; - Inkscape::MessageContext *_message_context; + Inkscape::MessageContext *_message_context; }; -struct SP3DBoxContextClass { - SPEventContextClass parent_class; +struct Box3DContextClass { + SPEventContextClass parent_class; }; /* Standard Gtk function */ -GtkType sp_3dbox_context_get_type (void); +GtkType sp_box3d_context_get_type (void); -#endif +#endif /* __SP_BOX3D_CONTEXT_H__ */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/box3d-face.cpp b/src/box3d-face.cpp deleted file mode 100644 index a1b7ae863..000000000 --- a/src/box3d-face.cpp +++ /dev/null @@ -1,294 +0,0 @@ -#define __SP_3DBOX_FACE_C__ - -/* - * Face of a 3D box ('perspectivic rectangle') - * - * Authors: - * Maximilian Albert - * - * Copyright (C) 2007 authors - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include "svg/svg.h" -#include "box3d-face.h" -#include "prefs-utils.h" - -// FIXME: It's quite redundant to pass the box plus the corners plus the axes. At least the corners can -// theoretically be reconstructed from the box and the axes, but in order to do this we need -// access to box->corners, which is not possible if we only have a forward declaration of SP3DBox -// in box3d-face.h. (But we can't include box3d.h itself because the latter already includes -// box3d-face.h). -Box3DFace::Box3DFace(SP3DBox *box, NR::Point &A, NR::Point &B, NR::Point &C, NR::Point &D, - Box3D::Axis plane, Box3D::FrontOrRear rel_pos) - : front_or_rear (rel_pos), path (NULL), parent_box3d (box) - { - dir1 = extract_first_axis_direction (plane); - dir2 = extract_second_axis_direction (plane); - /* - Box3D::Axis axis = (rel_pos == Box3D::FRONT ? Box3D::NONE : Box3D::third_axis_direction (plane)); - set_corners (box->corners[axis], - box->corners[axis ^ dir1], - box->corners[axis ^ dir1 ^ dir2], - box->corners[axis ^ dir2]); - */ - set_corners (A, B, C, D); -} - -Box3DFace::~Box3DFace() -{ - for (int i = 0; i < 4; ++i) { - if (this->corners[i]) { - //delete this->corners[i]; - this->corners[i] = NULL; - } - } -} - -void Box3DFace::set_corners(NR::Point &A, NR::Point &B, NR::Point &C, NR::Point &D) -{ - corners[0] = &A; - corners[1] = &B; - corners[2] = &C; - corners[3] = &D; -} - -/*** -void Box3DFace::set_shape(NR::Point const ul, NR::Point const lr, - Box3D::Axis const dir1, Box3D::Axis const dir2, - unsigned int shift_count, NR::Maybe pt_align, bool align_along_PL) -{ - corners[0] = ul; - if (!pt_align) { - corners[2] = lr; - } else { - if (align_along_PL) { - Box3D::Axis dir3 = Box3D::third_axis_direction (dir1, dir2); - Box3D::Line line1(*Box3D::Perspective3D::current_perspective->get_vanishing_point(dir1), lr); - Box3D::Line line2(*pt_align, *Box3D::Perspective3D::current_perspective->get_vanishing_point(dir3)); - corners[2] = *line1.intersect(line2); - } else { - corners[2] = Box3D::Line(*pt_align, *Box3D::Perspective3D::current_perspective->get_vanishing_point(dir1)).closest_to(lr); - } - } - - Box3D::PerspectiveLine first_line (corners[0], dir1); - Box3D::PerspectiveLine second_line (corners[2], dir2); - NR::Maybe ur = first_line.intersect(second_line); - - Box3D::PerspectiveLine third_line (corners[0], dir2); - Box3D::PerspectiveLine fourth_line (corners[2], dir1); - NR::Maybe ll = third_line.intersect(fourth_line); - - // FIXME: How to handle the case if one of the intersections doesn't exist? - // Maybe set them equal to the corresponding VPs? - if (!ur) ur = NR::Point(0.0, 0.0); - if (!ll) ll = NR::Point(0.0, 0.0); - - corners[1] = *ll; - corners[3] = *ur; - - this->dir1 = dir1; - this->dir2 = dir2; - - // FIXME: Can be made more concise - NR::Point tmp_pt; - for (unsigned int i=0; i < shift_count; i++) { - tmp_pt = corners[3]; - corners[1] = corners[0]; - corners[2] = corners[1]; - corners[3] = corners[2]; - corners[0] = tmp_pt; - } -} -***/ - -Box3DFace::Box3DFace(Box3DFace const &box3dface) -{ - for (int i = 0; i < 4; ++i) { - this->corners[i] = box3dface.corners[i]; - } - this->dir1 = box3dface.dir1; - this->dir2 = box3dface.dir2; -} - -/** - * Construct a 3D box face with opposite corners A and C whose sides are directed - * along axis1 and axis2. The corners have the following order: - * - * A = corners[0] --> along axis1 --> B = corners[1] --> along axis2 --> C = corners[2] - * --> along axis1 --> D = corners[3] --> along axis2 --> D = corners[0]. - * - * Note that several other functions rely on this precise order. - */ -/*** -void -Box3DFace::set_face (NR::Point const A, NR::Point const C, Box3D::Axis const axis1, Box3D::Axis const axis2) -{ - *corners[0] = A; - *corners[2] = C; - if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context())) - return; - SP3DBoxContext *bc = SP_3DBOX_CONTEXT(inkscape_active_event_context()); - - Box3D::PerspectiveLine line1 (A, axis1, Box3D::Perspective3D::current_perspective); - Box3D::PerspectiveLine line2 (C, axis2, Box3D::Perspective3D::current_perspective); - NR::Maybe B = line1.intersect(line2); - - Box3D::PerspectiveLine line3 (*corners[0], axis2, Box3D::Perspective3D::current_perspective); - Box3D::PerspectiveLine line4 (*corners[2], axis1, Box3D::Perspective3D::current_perspective); - NR::Maybe D = line3.intersect(line4); - - // FIXME: How to handle the case if one of the intersections doesn't exist? - // Maybe set them equal to the corresponding VPs? - if (!D) D = NR::Point(0.0, 0.0); - if (!B) B = NR::Point(0.0, 0.0); - - *corners[1] = *B; - *corners[3] = *D; - - this->dir1 = axis1; - this->dir2 = axis2; -} -***/ - -NR::Point Box3DFace::operator[](unsigned int i) -{ - return *corners[i % 4]; -} - - - -/** - * Append the curve's path as a child to the given 3D box (since SP3DBox - * is derived from SPGroup, so we can append children to its svg representation) - */ -void Box3DFace::hook_path_to_3dbox(SPPath * existing_path) -{ - if (this->path) { - //g_print ("Path already exists. Returning ...\n"); - return; - } - - if (existing_path != NULL) { - // no need to create a new path - this->path = existing_path; - return; - } - - /* create new path for face */ - Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(SP_OBJECT(parent_box3d))); - - Inkscape::XML::Node *repr_face = xml_doc->createElement("svg:path"); - repr_face->setAttribute("inkscape:box3dface", this->axes_string()); - this->path = SP_PATH(SP_OBJECT(parent_box3d)->appendChildRepr(repr_face)); - Inkscape::GC::release(repr_face); - - /* set the correct style */ - this->set_style (repr_face); -} - -void Box3DFace::set_style(Inkscape::XML::Node *repr_face, bool extruded) -{ - if (repr_face == NULL) { - repr_face = SP_OBJECT_REPR (this->path); - } - - if (!extruded && !strcmp (axes_string (), "XYrear")) { - // to avoid "flashing" during the initial dragging process, we make the rear face invisible in this case - repr_face->setAttribute("style", "fill:none"); - return; - } - - gchar *descr = g_strconcat ("desktop.", axes_string (), NULL); - const gchar * cur_style = prefs_get_string_attribute(descr, "style"); - g_free (descr); - - SPDesktop *desktop = inkscape_active_desktop(); - bool use_current = prefs_get_int_attribute("tools.shapes.3dbox", "usecurrent", 0); - if (use_current && cur_style !=NULL) { - /* use last used style */ - repr_face->setAttribute("style", cur_style); - } else { - /* use default style */ - GString *pstring = g_string_new(""); - g_string_printf (pstring, "tools.shapes.3dbox.%s", axes_string()); - sp_desktop_apply_style_tool (desktop, repr_face, pstring->str, false); - } -} - -/** - * Write the path's "d" attribute to the SVG representation. - */ -void Box3DFace::set_path_repr() -{ - NR::Matrix const i2d (sp_item_i2d_affine (SP_ITEM (this->parent_box3d))); - SPCurve * curve = sp_curve_new(); - sp_curve_moveto (curve, ((*corners[0]) * i2d)[NR::X], ((*corners[0]) * i2d)[NR::Y]); - sp_curve_lineto (curve, ((*corners[1]) * i2d)[NR::X], ((*corners[1]) * i2d)[NR::Y]); - sp_curve_lineto (curve, ((*corners[2]) * i2d)[NR::X], ((*corners[2]) * i2d)[NR::Y]); - sp_curve_lineto (curve, ((*corners[3]) * i2d)[NR::X], ((*corners[3]) * i2d)[NR::Y]); - sp_curve_closepath (curve); - SP_OBJECT(this->path)->repr->setAttribute("d", sp_svg_write_path (SP_CURVE_BPATH(curve))); -} - -void Box3DFace::set_curve() -{ - if (this->path == NULL) { - return; - } - NR::Matrix const i2d (sp_item_i2d_affine (SP_ITEM (this->parent_box3d))); - SPCurve *curve = sp_curve_new(); - sp_curve_moveto(curve, (*corners[0]) * i2d); - sp_curve_lineto(curve, (*corners[1]) * i2d); - sp_curve_lineto(curve, (*corners[2]) * i2d); - sp_curve_lineto(curve, (*corners[3]) * i2d); - sp_curve_closepath(curve); - sp_shape_set_curve(SP_SHAPE(this->path), curve, true); - sp_curve_unref(curve); -} - -gchar * Box3DFace::axes_string() -{ - GString *pstring = g_string_new(""); - g_string_printf (pstring, "%s", Box3D::string_from_axes ((Box3D::Axis) (dir1 ^ dir2))); - switch ((Box3D::Axis) (dir1 ^ dir2)) { - case Box3D::XY: - g_string_append_printf (pstring, (front_or_rear == Box3D::FRONT) ? "front" : "rear"); - break; - case Box3D::XZ: - g_string_append_printf (pstring, (front_or_rear == Box3D::FRONT) ? "top" : "bottom"); - break; - case Box3D::YZ: - g_string_append_printf (pstring, (front_or_rear == Box3D::FRONT) ? "right" : "left"); - break; - default: - break; - } - return pstring->str; -} - -gint Box3DFace::descr_to_id (gchar const *descr) -{ - if (!strcmp (descr, "XYrear")) { return 5; } - if (!strcmp (descr, "XYfront")) { return 4; } - if (!strcmp (descr, "XZbottom")) { return 3; } - if (!strcmp (descr, "XZtop")) { return 2; } - if (!strcmp (descr, "YZleft")) { return 1; } - if (!strcmp (descr, "YZright")) { return 0; } - - g_warning ("Invalid description of 3D box face.\n"); - return -1; -} - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/box3d-face.h b/src/box3d-face.h deleted file mode 100644 index 9281d458c..000000000 --- a/src/box3d-face.h +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef __SP_3DBOX_FACE_H__ -#define __SP_3DBOX_FACE_H__ - -/* - * Face of a 3D box ('perspectivic rectangle') - * - * Authors: - * Maximilian Albert - * - * Copyright (C) 2007 Authors - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include "perspective-line.h" -#include "display/curve.h" -#include "sp-path.h" -#include "sp-object.h" -#include "inkscape.h" -#include "desktop-style.h" -#include "desktop.h" -#include "xml/document.h" - -class SP3DBox; - -class Box3DFace { -public: - Box3DFace(SP3DBox *box, NR::Point &A, NR::Point &B, NR::Point &C, NR::Point &D, - Box3D::Axis plane, Box3D::FrontOrRear rel_pos); - Box3DFace(Box3DFace const &box3dface); - virtual ~Box3DFace(); - - NR::Point operator[](unsigned int i); - void draw(SP3DBox *box3d, SPCurve *c); - - /*** - void set_shape(NR::Point const ul, NR::Point const lr, - Box3D::Axis const dir1, Box3D::Axis const dir2, - unsigned int shift_count = 0, NR::Maybe pt_align = NR::Nothing(), - bool align_along_PL = false); - ***/ - void set_corners (NR::Point &A, NR::Point &B, NR::Point &C, NR::Point &D); - //void set_face (NR::Point const A, NR::Point const C, Box3D::Axis const dir1, Box3D::Axis const dir2); - - void hook_path_to_3dbox(SPPath * existing_path = NULL); - void set_style(Inkscape::XML::Node *repr_face = NULL, bool extruded = true); - void set_path_repr(); - void set_curve(); - inline void lower_to_bottom() { SP_ITEM (path)->lowerToBottom(); } - inline void raise_to_top() { SP_ITEM (path)->raiseToTop(); } - gchar * axes_string(); - gchar * svg_repr_string(); - static gint descr_to_id (gchar const *descr); - -private: - NR::Point *corners[4]; - - Box3D::Axis dir1; - Box3D::Axis dir2; - - Box3D::FrontOrRear front_or_rear; - - SPPath *path; - SP3DBox *parent_box3d; -}; - -#endif diff --git a/src/box3d-side.cpp b/src/box3d-side.cpp new file mode 100644 index 000000000..ee449be47 --- /dev/null +++ b/src/box3d-side.cpp @@ -0,0 +1,424 @@ +#define __BOX3D_SIDE_C__ + +/* + * 3D box face implementation + * + * Authors: + * Maximilian Albert + * + * Copyright (C) 2007 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "box3d-side.h" +#include "document.h" +#include "xml/document.h" +#include "xml/repr.h" +#include "display/curve.h" +#include "svg/svg.h" +#include "attributes.h" +#include "inkscape.h" +#include "persp3d.h" +#include "box3d-context.h" +#include "prefs-utils.h" +#include "desktop-style.h" +#include "box3d.h" + +static void box3d_side_class_init (Box3DSideClass *klass); +static void box3d_side_init (Box3DSide *side); + +static void box3d_side_build (SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static Inkscape::XML::Node *box3d_side_write (SPObject *object, Inkscape::XML::Node *repr, guint flags); +static void box3d_side_set (SPObject *object, unsigned int key, const gchar *value); +static void box3d_side_update (SPObject *object, SPCtx *ctx, guint flags); + +//static gchar * box3d_side_description (SPItem * item); +//static void box3d_side_snappoints(SPItem const *item, SnapPointsIter p); + +//static void box3d_side_set_shape (SPShape *shape); +//static void box3d_side_update_patheffect (SPShape *shape, bool write); + +static void box3d_side_apply_style (Box3DSide *side); +static Proj::Pt3 box3d_side_corner (Box3DSide *side, guint index); +static std::vector box3d_side_corners (Box3DSide *side); +static gint box3d_side_descr_to_id (gchar const *descr); + +static SPShapeClass *parent_class; + +GType +box3d_side_get_type (void) +{ + static GType type = 0; + + if (!type) { + GTypeInfo info = { + sizeof (Box3DSideClass), + NULL, NULL, + (GClassInitFunc) box3d_side_class_init, + NULL, NULL, + sizeof (Box3DSide), + 16, + (GInstanceInitFunc) box3d_side_init, + NULL, /* value_table */ + }; + type = g_type_register_static (SP_TYPE_SHAPE, "Box3DSide", &info, (GTypeFlags)0); + } + return type; +} + +static void +box3d_side_class_init (Box3DSideClass *klass) +{ + GObjectClass * gobject_class; + SPObjectClass * sp_object_class; + SPItemClass * item_class; + SPPathClass * path_class; + SPShapeClass * shape_class; + + gobject_class = (GObjectClass *) klass; + sp_object_class = (SPObjectClass *) klass; + item_class = (SPItemClass *) klass; + path_class = (SPPathClass *) klass; + shape_class = (SPShapeClass *) klass; + + parent_class = (SPShapeClass *)g_type_class_ref (SP_TYPE_SHAPE); + + sp_object_class->build = box3d_side_build; + sp_object_class->write = box3d_side_write; + sp_object_class->set = box3d_side_set; + sp_object_class->update = box3d_side_update; + + //item_class->description = box3d_side_description; + //item_class->snappoints = box3d_side_snappoints; + + shape_class->set_shape = box3d_side_set_shape; + //shape_class->update_patheffect = box3d_side_update_patheffect; +} + +static void +box3d_side_init (Box3DSide * side) +{ + side->dir1 = Box3D::NONE; + side->dir2 = Box3D::NONE; + side->front_or_rear = Box3D::FRONT; +} + +static void +box3d_side_build (SPObject * object, SPDocument * document, Inkscape::XML::Node * repr) +{ + if (((SPObjectClass *) parent_class)->build) + ((SPObjectClass *) parent_class)->build (object, document, repr); + + sp_object_read_attr (object, "inkscape:box3dsidetype"); +} + +static Inkscape::XML::Node * +box3d_side_write (SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + Box3DSide *side = SP_BOX3D_SIDE (object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + g_print ("Do we ever end up here?\n"); + Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object)); + repr = xml_doc->createElement("svg:path"); + repr->setAttribute("sodipodi:type", "inkscape:box3dside"); // FIXME: Does this double the + } + + if (flags & SP_OBJECT_WRITE_EXT) { + sp_repr_set_int(repr, "inkscape:box3dsidetype", side->dir1 ^ side->dir2 ^ side->front_or_rear); + } + + sp_shape_set_shape ((SPShape *) object); // FIXME: necessary? YES! + + /* Duplicate the path */ + SPCurve *curve = ((SPShape *) object)->curve; + //Nulls might be possible if this called iteratively + if ( !curve ) { + return NULL; + } + NArtBpath *bpath = SP_CURVE_BPATH(curve); + if ( !bpath ) { + return NULL; + } + char *d = sp_svg_write_path ( bpath ); + repr->setAttribute("d", d); + g_free (d); + + box3d_side_apply_style (side); + + if (((SPObjectClass *) (parent_class))->write) + ((SPObjectClass *) (parent_class))->write (object, repr, flags); + + return repr; +} + +static void +box3d_side_set (SPObject *object, unsigned int key, const gchar *value) +{ + Box3DSide *side = SP_BOX3D_SIDE (object); + + // TODO: In case the box was recreated (by undo, e.g.) we need to recreate the path + // (along with other info?) from the parent box. + + /* fixme: we should really collect updates */ + switch (key) { + case SP_ATTR_INKSCAPE_BOX3D_SIDE_TYPE: + if (value) { + guint desc = atoi (value); + + if (!Box3D::is_face_id(desc)) { + g_print ("desc is not a face id: =%s=\n", value); + } + g_return_if_fail (Box3D::is_face_id (desc)); + Box3D::Axis plane = (Box3D::Axis) (desc & 0x7); + plane = (Box3D::is_plane(plane) ? plane : Box3D::orth_plane_or_axis(plane)); + side->dir1 = Box3D::extract_first_axis_direction(plane); + side->dir2 = Box3D::extract_second_axis_direction(plane); + side->front_or_rear = (Box3D::FrontOrRear) (desc & 0x8); + + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } + break; + default: + if (((SPObjectClass *) parent_class)->set) + ((SPObjectClass *) parent_class)->set (object, key, value); + break; + } +} + +static void +box3d_side_update (SPObject *object, SPCtx *ctx, guint flags) +{ + //g_print ("box3d_side_update\n"); + if (flags & (SP_OBJECT_MODIFIED_FLAG | + //SP_OBJECT_CHILD_MODIFIED_FLAG | + SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + /*** + g_print ("\n\nIn box3d_side_update: "); + if (flags & SP_OBJECT_MODIFIED_FLAG) g_print ("SP_OBJECT_MODIFIED_FLAG "); + if (flags & SP_OBJECT_CHILD_MODIFIED_FLAG) g_print ("SP_OBJECT_CHILD_MODIFIED_FLAG "); + if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) g_print ("SP_OBJECT_STYLE_MODIFIED_FLAG "); + if (flags & SP_OBJECT_VIEWPORT_MODIFIED_FLAG) g_print ("SP_OBJECT_VIEWPORT_MODIFIED_FLAG "); + g_print ("\n"); + ***/ + sp_shape_set_shape ((SPShape *) object); + } + + if (((SPObjectClass *) parent_class)->update) + ((SPObjectClass *) parent_class)->update (object, ctx, flags); +} + +/*** +static void +box3d_side_update_patheffect(SPShape *shape, bool write) +{ + box3d_side_set_shape(shape); + + if (write) { + Inkscape::XML::Node *repr = SP_OBJECT_REPR(shape); + if ( shape->curve != NULL ) { + NArtBpath *abp = sp_curve_first_bpath(shape->curve); + if (abp) { + gchar *str = sp_svg_write_path(abp); + repr->setAttribute("d", str); + g_free(str); + } else { + repr->setAttribute("d", ""); + } + } else { + repr->setAttribute("d", NULL); + } + } + + ((SPObject *)shape)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} +***/ + +/*** +static gchar * +box3d_side_description (SPItem *item) +{ + Box3DSide *side = SP_BOX3D_SIDE (item); + + // while there will never be less than 3 vertices, we still need to + // make calls to ngettext because the pluralization may be different + // for various numbers >=3. The singular form is used as the index. + if (side->flatsided == false ) + return g_strdup_printf (ngettext("Star with %d vertex", + "Star with %d vertices", + star->sides), star->sides); + else + return g_strdup_printf (ngettext("Polygon with %d vertex", + "Polygon with %d vertices", + star->sides), star->sides); +} +***/ + +void +box3d_side_position_set (Box3DSide *side) { + box3d_side_set_shape (SP_SHAPE (side)); + + /* This call is responsible for live update of the sides during the initial drag */ + SP_OBJECT(side)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +void +box3d_side_set_shape (SPShape *shape) +{ + //g_print ("box3d_side_set_shape\n"); + Box3DSide *side = SP_BOX3D_SIDE (shape); + if (!SP_OBJECT_DOCUMENT(side)->root) { + // avoid a warning caused by sp_document_height() (which is called from sp_item_i2d_affine() below) + // when reading a file containing 3D boxes + return; + } + + if (!SP_IS_BOX3D(SP_OBJECT(side)->parent)) { + g_warning ("Parent of 3D box side is not a 3D box.\n"); + /** + g_print ("Removing the inkscape:box3dside attribute and returning from box3d_side_set_shape().\n"); + SP_OBJECT_REPR (shape)->setAttribute("sodipodi:type", NULL); + SP_OBJECT_REPR (shape)->setAttribute("inkscape:box3dside", NULL); + **/ + return; + } + + Inkscape::XML::Node *repr = SP_OBJECT_REPR (shape); + Persp3D *persp = box3d_side_perspective(side); + //g_return_if_fail (persp != NULL); + if (!persp) { + //g_warning ("persp != NULL in box3d_side_set_shape failed!\n"); + //persp = SP_OBJECT_DOCUMENT(side)->current_persp3d; + return; + } + + SPCurve *c = sp_curve_new (); + // TODO: Draw the correct quadrangle here + // To do this, determine the perspective of the box, the orientation of the side (e.g., XY-FRONT) + // compute the coordinates of the corners in P^3, project them onto the canvas, and draw the + // resulting path. + + std::vector corners = box3d_side_corners (side); + + NR::Matrix const i2d (sp_item_i2d_affine (SP_ITEM(shape))); + + // FIXME: This can better be implemented by using box3d_get_corner + sp_curve_moveto (c, persp->tmat.image(corners[0]).affine() * i2d); + sp_curve_lineto (c, persp->tmat.image(corners[1]).affine() * i2d); + sp_curve_lineto (c, persp->tmat.image(corners[2]).affine() * i2d); + sp_curve_lineto (c, persp->tmat.image(corners[3]).affine() * i2d); + + sp_curve_closepath (c); + //sp_shape_perform_path_effect(c, SP_SHAPE (side)); + sp_shape_set_curve_insync (SP_SHAPE (side), c, TRUE); + sp_curve_unref (c); +} + +static void +//box3d_side_apply_style (SPBox3D *box, bool extruded) { +box3d_side_apply_style (Box3DSide *side) { + Inkscape::XML::Node *repr_face = SP_OBJECT_REPR(SP_OBJECT(side)); + + /** + if (!extruded && !strcmp (box3d_side_axes_string (), "XYrear")) { + // to avoid "flashing" during the initial dragging process, we make the rear face invisible in this case + repr_face->setAttribute("style", "fill:none"); + return; + } + **/ + + gchar *descr = g_strconcat ("desktop.", box3d_side_axes_string (side), NULL); + const gchar * cur_style = prefs_get_string_attribute(descr, "style"); + g_free (descr); + + SPDesktop *desktop = inkscape_active_desktop(); + bool use_current = prefs_get_int_attribute("tools.shapes.3dbox", "usecurrent", 0); + if (use_current && cur_style !=NULL) { + /* use last used style */ + repr_face->setAttribute("style", cur_style); + } else { + /* use default style */ + GString *pstring = g_string_new(""); + g_string_printf (pstring, "tools.shapes.3dbox.%s", box3d_side_axes_string(side)); + sp_desktop_apply_style_tool (desktop, repr_face, pstring->str, false); + } +} + +gchar * +box3d_side_axes_string(Box3DSide *side) +{ + GString *pstring = g_string_new(""); + g_string_printf (pstring, "%s", Box3D::string_from_axes ((Box3D::Axis) (side->dir1 ^ side->dir2))); + switch ((Box3D::Axis) (side->dir1 ^ side->dir2)) { + case Box3D::XY: + g_string_append_printf (pstring, (side->front_or_rear == Box3D::FRONT) ? "front" : "rear"); + break; + case Box3D::XZ: + g_string_append_printf (pstring, (side->front_or_rear == Box3D::FRONT) ? "top" : "bottom"); + break; + case Box3D::YZ: + g_string_append_printf (pstring, (side->front_or_rear == Box3D::FRONT) ? "right" : "left"); + break; + default: + break; + } + return pstring->str; +} + +static Proj::Pt3 +box3d_side_corner (Box3DSide *side, guint index) { + SPBox3D *box = SP_BOX3D(SP_OBJECT_PARENT(side)); + return Proj::Pt3 ((index & 0x1) ? box->orig_corner7[Proj::X] : box->orig_corner0[Proj::X], + (index & 0x2) ? box->orig_corner7[Proj::Y] : box->orig_corner0[Proj::Y], + (index & 0x4) ? box->orig_corner7[Proj::Z] : box->orig_corner0[Proj::Z], + 1.0); +} + +static std::vector +box3d_side_corners (Box3DSide *side) { + std::vector corners; + Box3D::Axis orth = Box3D::third_axis_direction (side->dir1, side->dir2); + unsigned int i0 = (side->front_or_rear ? orth : 0); + unsigned int i1 = i0 ^ side->dir1; + unsigned int i2 = i0 ^ side->dir1 ^ side->dir2; + unsigned int i3 = i0 ^ side->dir2; + + corners.push_back (box3d_side_corner (side, i0)); + corners.push_back (box3d_side_corner (side, i1)); + corners.push_back (box3d_side_corner (side, i2)); + corners.push_back (box3d_side_corner (side, i3)); + return corners; +} + +static gint +box3d_side_descr_to_id (gchar const *descr) +{ + if (!strcmp (descr, "XYrear")) { return 5; } + if (!strcmp (descr, "XYfront")) { return 2; } + if (!strcmp (descr, "XZbottom")) { return 1; } + if (!strcmp (descr, "XZtop")) { return 4; } + if (!strcmp (descr, "YZleft")) { return 3; } + if (!strcmp (descr, "YZright")) { return 0; } + + g_warning ("Invalid description of 3D box face.\n"); + g_print (" (description is: %s)\n", descr); + return -1; +} + +Persp3D * +box3d_side_perspective(Box3DSide *side) { + return SP_BOX3D(SP_OBJECT(side)->parent)->persp_ref->getObject(); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/box3d-side.h b/src/box3d-side.h new file mode 100644 index 000000000..e9154087f --- /dev/null +++ b/src/box3d-side.h @@ -0,0 +1,59 @@ +#ifndef __BOX3D_SIDE_H__ +#define __BOX3D_SIDE_H__ + +/* + * 3D box face implementation + * + * Authors: + * Maximilian Albert + * + * Copyright (C) 2007 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-polygon.h" +#include "axis-manip.h" + +#define SP_TYPE_BOX3D_SIDE (box3d_side_get_type ()) +#define SP_BOX3D_SIDE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_BOX3D_SIDE, Box3DSide)) +#define SP_BOX3D_SIDE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_BOX3D_SIDE, Box3DSideClass)) +#define SP_IS_BOX3D_SIDE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_BOX3D_SIDE)) +#define SP_IS_BOX3D_SIDE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_BOX3D_SIDE)) + +class SPBox3D; +class Box3DSide; +class Box3DSideClass; +class Persp3D; + +// FIXME: Would it be better to inherit from SPPath instead? +struct Box3DSide : public SPPolygon { + Box3D::Axis dir1; + Box3D::Axis dir2; + Box3D::FrontOrRear front_or_rear; +}; + +struct Box3DSideClass { + SPPolygonClass parent_class; +}; + +GType box3d_side_get_type (void); + +//void sp_box3d_side_position_set (Box3DSide *side, NR::Point corner1, NR::Point corner2); +void box3d_side_set_shape (SPShape *shape); +void box3d_side_position_set (Box3DSide *side); // FIXME: Replace this by box3d_side_set_shape?? +gchar *box3d_side_axes_string(Box3DSide *side); +Persp3D *box3d_side_perspective(Box3DSide *side); + +#endif /* __BOX3D_SIDE_H__ */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/box3d.cpp b/src/box3d.cpp index ff00a795c..c9f3bb7d2 100644 --- a/src/box3d.cpp +++ b/src/box3d.cpp @@ -1,12 +1,12 @@ -#define __SP_3DBOX_C__ +#define __SP_BOX3D_C__ /* * SVG implementation * * Authors: + * Maximilian Albert * Lauris Kaplinski * bulia byak - * Maximilian Albert * * Copyright (C) 2007 Authors * Copyright (C) 1999-2002 Lauris Kaplinski @@ -17,91 +17,104 @@ #include #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); - -static void sp_3dbox_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); -static void sp_3dbox_release (SPObject *object); -static void sp_3dbox_set(SPObject *object, unsigned int key, const gchar *value); -static void sp_3dbox_update(SPObject *object, SPCtx *ctx, guint flags); -static Inkscape::XML::Node *sp_3dbox_write(SPObject *object, Inkscape::XML::Node *repr, guint flags); - -static gchar *sp_3dbox_description(SPItem *item); - -//static void sp_3dbox_set_shape(SPShape *shape); -//static void sp_3dbox_set_shape(SP3DBox *box3d); +#include "xml/document.h" +#include "xml/repr.h" -static void sp_3dbox_update_corner_with_value_from_svg (SPObject *object, guint corner_id, const gchar *value); -static void sp_3dbox_update_perspective (Box3D::Perspective3D *persp, const gchar *value); -static gchar * sp_3dbox_get_corner_coords_string (SP3DBox *box, guint id); -static std::pair sp_3dbox_get_coord_pair_from_string (const gchar *); -static gchar * sp_3dbox_get_perspective_string (SP3DBox *box); +#include "box3d.h" +#include "box3d-side.h" +#include "box3d-context.h" +#include "proj_pt.h" +#include "transf_mat_3x4.h" +#include "perspective-line.h" +#include "inkscape.h" +#include "persp3d.h" +#include "line-geometry.h" +#include "persp3d-reference.h" +#include "uri.h" +#include "2geom/geom.h" + +#include "desktop.h" +#include "macros.h" + +static void box3d_class_init(SPBox3DClass *klass); +static void box3d_init(SPBox3D *box3d); + +static void box3d_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static void box3d_release(SPObject *object); +static void box3d_set(SPObject *object, unsigned int key, const gchar *value); +static void box3d_update(SPObject *object, SPCtx *ctx, guint flags); +static Inkscape::XML::Node *box3d_write(SPObject *object, Inkscape::XML::Node *repr, guint flags); + +static gchar *box3d_description(SPItem *item); +static NR::Matrix box3d_set_transform(SPItem *item, NR::Matrix const &xform); + +static void box3d_ref_changed(SPObject *old_ref, SPObject *ref, SPBox3D *box); +static void box3d_ref_modified(SPObject *href, guint flags, SPBox3D *box); +//static void box3d_ref_changed(SPObject *old_ref, SPObject *ref, Persp3D *persp); +//static void box3d_ref_modified(SPObject *href, guint flags, Persp3D *persp); static SPGroupClass *parent_class; static gint counter = 0; GType -sp_3dbox_get_type(void) +box3d_get_type(void) { static GType type = 0; if (!type) { GTypeInfo info = { - sizeof(SP3DBoxClass), + sizeof(SPBox3DClass), NULL, /* base_init */ NULL, /* base_finalize */ - (GClassInitFunc) sp_3dbox_class_init, + (GClassInitFunc) box3d_class_init, NULL, /* class_finalize */ NULL, /* class_data */ - sizeof(SP3DBox), + sizeof(SPBox3D), 16, /* n_preallocs */ - (GInstanceInitFunc) sp_3dbox_init, + (GInstanceInitFunc) box3d_init, NULL, /* value_table */ }; - type = g_type_register_static(SP_TYPE_GROUP, "SP3DBox", &info, (GTypeFlags) 0); + type = g_type_register_static(SP_TYPE_GROUP, "SPBox3D", &info, (GTypeFlags) 0); } return type; } static void -sp_3dbox_class_init(SP3DBoxClass *klass) +box3d_class_init(SPBox3DClass *klass) { SPObjectClass *sp_object_class = (SPObjectClass *) klass; SPItemClass *item_class = (SPItemClass *) klass; parent_class = (SPGroupClass *) g_type_class_ref(SP_TYPE_GROUP); - sp_object_class->build = sp_3dbox_build; - sp_object_class->set = sp_3dbox_set; - sp_object_class->write = sp_3dbox_write; - sp_object_class->update = sp_3dbox_update; - sp_object_class->release = sp_3dbox_release; + sp_object_class->build = box3d_build; + sp_object_class->release = box3d_release; + sp_object_class->set = box3d_set; + sp_object_class->write = box3d_write; + sp_object_class->update = box3d_update; - item_class->description = sp_3dbox_description; + item_class->description = box3d_description; + item_class->set_transform = box3d_set_transform; } static void -sp_3dbox_init(SP3DBox *box) +box3d_init(SPBox3D *box) { - for (int i = 0; i < 8; ++i) box->corners[i] = NR::Point(0,0); - for (int i = 0; i < 6; ++i) box->faces[i] = NULL; + box->persp_href = NULL; + box->persp_ref = new Persp3DReference(SP_OBJECT(box)); + new (&box->modified_connection) sigc::connection(); } static void -sp_3dbox_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +box3d_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) { if (((SPObjectClass *) (parent_class))->build) { ((SPObjectClass *) (parent_class))->build(object, document, repr); } - SP3DBox *box = SP_3DBOX (object); - + SPBox3D *box = SP_BOX3D (object); box->my_counter = counter++; /* we initialize the z-orders to zero so that they are updated during dragging */ @@ -109,197 +122,199 @@ sp_3dbox_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr box->z_orders[i] = 0; } - box->front_bits = 0x0; + // TODO: Create/link to the correct perspective - - if (repr->attribute ("inkscape:perspective") == NULL) { - // we are creating a new box; link it to the current perspective - document->current_perspective->add_box (box); - } else { - // create a new perspective that we can compare with existing ones - Box3D::Perspective3D *persp = new Box3D::Perspective3D (Box3D::VanishingPoint (0,0), - Box3D::VanishingPoint (0,0), - Box3D::VanishingPoint (0,0), - document); - sp_3dbox_update_perspective (persp, repr->attribute ("inkscape:perspective")); - Box3D::Perspective3D *comp = document->find_perspective (persp); - if (comp == NULL) { - // perspective doesn't exist yet - document->add_perspective (persp); - persp->add_box (box); - } else { - // link the box to the existing perspective and delete the temporary one - comp->add_box (box); - delete persp; - //g_assert (Box3D::get_persp_of_box (box) == comp); - - // FIXME: If the paths of the box's faces do not correspond to the svg representation of the perspective - // the box is shown with a "wrong" initial shape that is only corrected after dragging. - // Should we "repair" this by updating the paths at the end of sp_3dbox_build()? - // Maybe it would be better to simply destroy and rebuild them in sp_3dbox_link_to_existing_paths(). - } + SPDocument *doc = SP_OBJECT_DOCUMENT(box); + if (!doc) { + g_print ("No document for the box!!!!\n"); + return; } - - sp_object_read_attr(object, "inkscape:box3dcornerA"); - sp_object_read_attr(object, "inkscape:box3dcornerB"); - sp_object_read_attr(object, "inkscape:box3dcornerC"); - - // TODO: We create all faces in the beginning, but only the non-degenerate ones - // should be written to the svg representation later in sp_3dbox_write. - Box3D::Axis cur_plane, axis, dir1, dir2; - Box3D::FrontOrRear cur_pos; - for (int i = 0; i < 3; ++i) { - for (int j = 0; j < 2; ++j) { - cur_plane = Box3D::planes[i]; - cur_pos = Box3D::face_positions[j]; - // FIXME: The following code could theoretically be moved to - // the constructor of Box3DFace (but see the comment there). - axis = (cur_pos == Box3D::FRONT ? Box3D::NONE : Box3D::third_axis_direction (cur_plane)); - dir1 = extract_first_axis_direction (cur_plane); - dir2 = extract_second_axis_direction (cur_plane); - - box->faces[Box3D::face_to_int(cur_plane ^ cur_pos)] = - new Box3DFace (box, box->corners[axis], box->corners[axis ^ dir1], - box->corners[axis ^ dir1 ^ dir2], box->corners[axis ^ dir2], - cur_plane, cur_pos); - } + /** + if (!box->persp3d) { + g_print ("Box seems to be newly created since no perspective is referenced yet. We reference the current perspective.\n"); + box->persp3d = doc->current_persp3d; } + **/ - // Check whether the paths of the faces of the box need to be linked to existing paths in the - // document (e.g., after a 'redo' operation or after opening a file) and do so if necessary. - sp_3dbox_link_to_existing_paths (box, repr); - - sp_3dbox_set_ratios (box, Box3D::XYZ); + box->persp_ref->changedSignal().connect(sigc::bind(sigc::ptr_fun(box3d_ref_changed), box)); - // 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]; - box->old_corner7 = box->corners[7]; + sp_object_read_attr(object, "inkscape:perspectiveID"); + sp_object_read_attr(object, "inkscape:corner0"); + sp_object_read_attr(object, "inkscape:corner7"); } +/** + * Virtual release of SPBox3D members before destruction. + */ static void -sp_3dbox_release (SPObject *object) +box3d_release(SPObject *object) { - SP3DBox *box = SP_3DBOX(object); - for (int i = 0; i < 6; ++i) { - if (box->faces[i]) { - delete box->faces[i]; // FIXME: Anything else to do? Do we need to clean up the face first? - } - } + SPBox3D *box = (SPBox3D *) object; + + if (box->persp_href) { + g_free(box->persp_href); + } + if (box->persp_ref) { + box->persp_ref->detach(); + delete box->persp_ref; + box->persp_ref = NULL; + } - // FIXME: We do not duplicate perspectives if they are the same for several boxes. - // Thus, don't delete the perspective when deleting a box but rather unlink the box from it. - SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->remove_box (box); + box->modified_connection.disconnect(); + box->modified_connection.~connection(); - if (((SPObjectClass *) parent_class)->release) { - ((SPObjectClass *) parent_class)->release (object); - } + //persp3d_remove_box (box->persp_ref->getObject(), box); + + if (((SPObjectClass *) parent_class)->release) + ((SPObjectClass *) parent_class)->release(object); } -static void sp_3dbox_set(SPObject *object, unsigned int key, const gchar *value) +static void +box3d_set(SPObject *object, unsigned int key, const gchar *value) { + SPBox3D *box = SP_BOX3D(object); + switch (key) { - case SP_ATTR_INKSCAPE_3DBOX_CORNER_A: - sp_3dbox_update_corner_with_value_from_svg (object, 2, value); - break; - case SP_ATTR_INKSCAPE_3DBOX_CORNER_B: - sp_3dbox_update_corner_with_value_from_svg (object, 1, value); + case SP_ATTR_INKSCAPE_BOX3D_PERSPECTIVE_ID: + if ( value && box->persp_href && ( strcmp(value, box->persp_href) == 0 ) ) { + /* No change, do nothing. */ + } else { + if (box->persp_href) { + g_free(box->persp_href); + box->persp_href = NULL; + } + if (value) { + box->persp_href = g_strdup(value); + + // Now do the attaching, which emits the changed signal. + try { + box->persp_ref->attach(Inkscape::URI(value)); + } catch (Inkscape::BadURIException &e) { + g_warning("%s", e.what()); + box->persp_ref->detach(); + } + } else { + // Detach, which emits the changed signal. + box->persp_ref->detach(); + // TODO: Clean this up (also w.r.t the surrounding if construct) + /*** + g_print ("No perspective given. Attaching to current perspective instead.\n"); + g_free(box->persp_href); + Inkscape::XML::Node *repr = SP_OBJECT_REPR(inkscape_active_document()->current_persp3d); + box->persp_href = g_strdup(repr->attribute("id")); + box->persp_ref->attach(Inkscape::URI(box->persp_href)); + ***/ + } + } + + // FIXME: Is the following update doubled by some call in either persp3d.cpp or vanishing_point_new.cpp? + box3d_position_set(box); break; - case SP_ATTR_INKSCAPE_3DBOX_CORNER_C: - sp_3dbox_update_corner_with_value_from_svg (object, 5, value); + case SP_ATTR_INKSCAPE_BOX3D_CORNER0: + if (value && strcmp(value, "0 : 0 : 0 : 0")) { + box->orig_corner0 = Proj::Pt3(value); + box->save_corner0 = box->orig_corner0; + box3d_position_set(box); + } break; - case SP_ATTR_INKSCAPE_3DBOX_PERSPECTIVE: - { - SP3DBox *box = SP_3DBOX (object); - sp_3dbox_update_perspective (SP_OBJECT_DOCUMENT (object)->get_persp_of_box (box), value); + case SP_ATTR_INKSCAPE_BOX3D_CORNER7: + if (value && strcmp(value, "0 : 0 : 0 : 0")) { + box->orig_corner7 = Proj::Pt3(value); + box->save_corner7 = box->orig_corner7; + box3d_position_set(box); + } break; - } default: if (((SPObjectClass *) (parent_class))->set) { ((SPObjectClass *) (parent_class))->set(object, key, value); } break; } + //object->updateRepr(); // This ensures correct update of the box after undo/redo. FIXME: Why is this not present in sp-rect.cpp and similar files? } +/** + * Gets called when (re)attached to another perspective. + */ static void -sp_3dbox_update(SPObject *object, SPCtx *ctx, guint flags) +box3d_ref_changed(SPObject *old_ref, SPObject *ref, SPBox3D *box) +{ + if (old_ref) { + sp_signal_disconnect_by_data(old_ref, box); + persp3d_remove_box (SP_PERSP3D(old_ref), box); + } + if ( SP_IS_PERSP3D(ref) && ref != box ) // FIXME: Comparisons sane? + { + box->modified_connection.disconnect(); + box->modified_connection = ref->connectModified(sigc::bind(sigc::ptr_fun(&box3d_ref_modified), box)); + box3d_ref_modified(ref, 0, box); + persp3d_add_box (SP_PERSP3D(ref), box); + } +} + +static void +box3d_update(SPObject *object, SPCtx *ctx, guint flags) { if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { - SP3DBox *box = SP_3DBOX(object); - Inkscape::XML::Node *repr = SP_OBJECT_REPR(object); - sp_3dbox_link_to_existing_paths (box, repr); - SPEventContext *ec = inkscape_active_event_context(); - if (SP_IS_3DBOX_CONTEXT (ec)) { - SP_3DBOX_CONTEXT (ec)->_vpdrag->updateDraggers(); - // FIXME: Should we update the corners here, too? Maybe this is the reason why the handles - // are off after an undo/redo! On the other hand, if we do so we get warnings about - // updates occuring while other updats are in progress ... - } + + /* FIXME?: Perhaps the display updates of box sides should be instantiated from here, but this + causes evil update loops so it's all done from box3d_position_set, which is called from + various other places (like the handlers in object-edit.cpp, vanishing-point.cpp, etc. */ + } - /* Invoke parent method */ + // Invoke parent method if (((SPObjectClass *) (parent_class))->update) ((SPObjectClass *) (parent_class))->update(object, ctx, flags); } -static Inkscape::XML::Node *sp_3dbox_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) + +static Inkscape::XML::Node *box3d_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) { - SP3DBox *box = SP_3DBOX(object); - // 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 repr; + SPBox3D *box = SP_BOX3D(object); if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + g_print ("Do we ever end up here?\n"); Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object)); repr = xml_doc->createElement("svg:g"); - repr->setAttribute("sodipodi:type", "inkscape:3dbox"); - /* Hook paths to the faces of the box */ - for (int i = 0; i < 6; ++i) { - box->faces[i]->hook_path_to_3dbox(); - } - } - - for (int i = 0; i < 6; ++i) { - box->faces[i]->set_path_repr(); + repr->setAttribute("sodipodi:type", "inkscape:box3d"); } if (flags & SP_OBJECT_WRITE_EXT) { - gchar *str; - str = sp_3dbox_get_corner_coords_string (box, 2); - repr->setAttribute("inkscape:box3dcornerA", str); - - str = sp_3dbox_get_corner_coords_string (box, 1); - repr->setAttribute("inkscape:box3dcornerB", str); - str = sp_3dbox_get_corner_coords_string (box, 5); - repr->setAttribute("inkscape:box3dcornerC", str); + if (box->persp_href) { + repr->setAttribute("inkscape:perspectiveID", box->persp_href); + } else { + /* box is not yet linked to a perspective; use the document's current perspective */ + SPDocument *doc = inkscape_active_document(); + if (box->persp_ref->getURI()) { + gchar *uri_string = box->persp_ref->getURI()->toString(); + repr->setAttribute("inkscape:perspectiveID", uri_string); + g_free(uri_string); + } else if (doc) { + //persp3d_add_box (doc->current_persp3d, box); + Inkscape::XML::Node *persp_repr = SP_OBJECT_REPR(doc->current_persp3d); + const gchar *persp_id = persp_repr->attribute("id"); + gchar *href = g_strdup_printf("#%s", persp_id); + repr->setAttribute("inkscape:perspectiveID", href); + g_free(href); + } else { + g_print ("No active document while creating perspective!!!\n"); + } + } - str = sp_3dbox_get_perspective_string (box); - repr->setAttribute("inkscape:perspective", str); - sp_3dbox_set_ratios (box); + gchar *coordstr0 = box->orig_corner0.coord_string(); + gchar *coordstr7 = box->orig_corner7.coord_string(); + repr->setAttribute("inkscape:corner0", coordstr0); + repr->setAttribute("inkscape:corner7", coordstr7); + g_free(coordstr0); + g_free(coordstr7); - g_free ((void *) str); + box->orig_corner0.normalize(); + box->orig_corner7.normalize(); - /* 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]; - box->old_corner7 = box->corners[7]; + box->save_corner0 = box->orig_corner0; + box->save_corner7 = box->orig_corner7; } if (((SPObjectClass *) (parent_class))->write) { @@ -310,988 +325,1003 @@ static Inkscape::XML::Node *sp_3dbox_write(SPObject *object, Inkscape::XML::Node } static gchar * -sp_3dbox_description(SPItem *item) +box3d_description(SPItem *item) { - g_return_val_if_fail(SP_IS_3DBOX(item), NULL); + g_return_val_if_fail(SP_IS_BOX3D(item), NULL); return g_strdup(_("3D Box")); } -void sp_3dbox_set_ratios (SP3DBox *box, Box3D::Axis axes) +void +box3d_position_set (SPBox3D *box) { - Box3D::Perspective3D *persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box); - NR::Point pt; - - if (axes & Box3D::X) { - pt = persp->get_vanishing_point (Box3D::X)->get_pos(); - box->ratio_x = NR::L2 (pt - box->corners[2]) / NR::L2 (pt - box->corners[3]); - } - - if (axes & Box3D::Y) { - pt = persp->get_vanishing_point (Box3D::Y)->get_pos(); - box->ratio_y = NR::L2 (pt - box->corners[2]) / NR::L2 (pt - box->corners[0]); - } - - if (axes & Box3D::Z) { - pt = persp->get_vanishing_point (Box3D::Z)->get_pos(); - box->ratio_z = NR::L2 (pt - box->corners[4]) / NR::L2 (pt - box->corners[0]); + /* This draws the curve and calls requestDisplayUpdate() for each side (the latter is done in + box3d_side_position_set() to avoid update conflicts with the parent box) */ + for (SPObject *child = sp_object_first_child(SP_OBJECT (box)); child != NULL; child = SP_OBJECT_NEXT(child) ) { + box3d_side_position_set (SP_BOX3D_SIDE (child)); } } -void -sp_3dbox_switch_front_face (SP3DBox *box, Box3D::Axis axis) +static NR::Matrix +box3d_set_transform(SPItem *item, NR::Matrix const &xform) { - if (SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point (axis)->is_finite()) { - box->front_bits = box->front_bits ^ axis; - } -} + SPBox3D *box = SP_BOX3D(item); + Persp3D *persp = box->persp_ref->getObject(); -void -sp_3dbox_position_set (SP3DBoxContext &bc) -{ - SP3DBox *box3d = SP_3DBOX(bc.item); + persp3d_apply_affine_transformation(persp, xform); // also triggers repr updates - sp_3dbox_set_shape(box3d); + /*** + // FIXME: We somehow have to apply the transformation to strokes, patterns, and gradients. How? + NR::Matrix ret(NR::transform(xform)); + gdouble const sw = hypot(ret[0], ret[1]); + gdouble const sh = hypot(ret[2], ret[3]); - // FIXME: Why does the following call not automatically update the children - // of box3d (which is an SPGroup, which should do this)? - //SP_OBJECT(box3d)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + SPItem *sideitem = NULL; + for (SPObject *side = sp_object_first_child(box); side != NULL; side = SP_OBJECT_NEXT(side)) { + sideitem = SP_ITEM(side); - /** - SP_OBJECT(box3d->path_face1)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - SP_OBJECT(box3d->path_face2)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - SP_OBJECT(box3d->path_face3)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - SP_OBJECT(box3d->path_face4)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - SP_OBJECT(box3d->path_face5)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - SP_OBJECT(box3d->path_face6)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - ***/ -} + // Adjust stroke width + sp_item_adjust_stroke(sideitem, sqrt(fabs(sw * sh))); -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); + // Adjust pattern fill + sp_item_adjust_pattern(sideitem, xform); - // 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]); + // Adjust gradient fill + sp_item_adjust_gradient(sideitem, xform); } - box->faces[4]->set_corners (box->corners[0], box->corners[1], box->corners[3], box->corners[2]); + ***/ - sp_3dbox_update_curves (box); + return NR::identity(); } -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 *box, bool use_previous_corners) -{ - if (!SP_IS_3DBOX_CONTEXT(inkscape_active_event_context())) - return; - SP3DBoxContext *bc = SP_3DBOX_CONTEXT(inkscape_active_event_context()); - if (!use_previous_corners) { - sp_3dbox_set_shape_from_points (box, bc->drag_origin, bc->drag_ptB, bc->drag_ptC); - } else { - sp_3dbox_set_shape_from_points (box, box->corners[2], box->corners[1], box->corners[5]); - } +/** + * Gets called when persp(?) repr contents change: i.e. parameter change. + */ +static void +box3d_ref_modified(SPObject *href, guint flags, SPBox3D *box) +{ + /*** + g_print ("FIXME: box3d_ref_modified was called. What should we do?\n"); + g_print ("Here is at least the the href's id: %s\n", SP_OBJECT_REPR(href)->attribute("id")); + g_print (" ... and the box's, too: %s\n", SP_OBJECT_REPR(box)->attribute("id")); + ***/ + } +Proj::Pt3 +box3d_get_proj_corner (guint id, Proj::Pt3 const &c0, Proj::Pt3 const &c7) { + return Proj::Pt3 ((id & Box3D::X) ? c7[Proj::X] : c0[Proj::X], + (id & Box3D::Y) ? c7[Proj::Y] : c0[Proj::Y], + (id & Box3D::Z) ? c7[Proj::Z] : c0[Proj::Z], + 1.0); +} -void sp_3dbox_recompute_corners (SP3DBox *box, NR::Point const A, NR::Point const B, NR::Point const C) -{ - sp_3dbox_move_corner_in_XY_plane (box, 2, A); - sp_3dbox_move_corner_in_XY_plane (box, 1, B); - sp_3dbox_move_corner_in_Z_direction (box, 5, C); +Proj::Pt3 +box3d_get_proj_corner (SPBox3D const *box, guint id) { + return Proj::Pt3 ((id & Box3D::X) ? box->orig_corner7[Proj::X] : box->orig_corner0[Proj::X], + (id & Box3D::Y) ? box->orig_corner7[Proj::Y] : box->orig_corner0[Proj::Y], + (id & Box3D::Z) ? box->orig_corner7[Proj::Z] : box->orig_corner0[Proj::Z], + 1.0); } -inline static double -normalized_angle (double angle) { - if (angle < -M_PI) { - return angle + 2*M_PI; - } else if (angle > M_PI) { - return angle - 2*M_PI; +NR::Point +box3d_get_corner_screen (SPBox3D const *box, guint id) { + Proj::Pt3 proj_corner (box3d_get_proj_corner (box, id)); + if (!box->persp_ref->getObject()) { + //g_print ("No perspective present in box!! Should we simply use the currently active perspective?\n"); + return NR::Point (NR_HUGE, NR_HUGE); } - return angle; + return box->persp_ref->getObject()->tmat.image(proj_corner).affine(); } -static gdouble -sp_3dbox_corner_angle_to_VP (SP3DBox *box, Box3D::Axis axis, guint extreme_corner) -{ - Box3D::VanishingPoint *vp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)->get_vanishing_point (axis); - NR::Point dir; +Proj::Pt3 +box3d_get_proj_center (SPBox3D *box) { + box->orig_corner0.normalize(); + box->orig_corner7.normalize(); + return Proj::Pt3 ((box->orig_corner0[Proj::X] + box->orig_corner7[Proj::X]) / 2, + (box->orig_corner0[Proj::Y] + box->orig_corner7[Proj::Y]) / 2, + (box->orig_corner0[Proj::Z] + box->orig_corner7[Proj::Z]) / 2, + 1.0); +} - if (vp->is_finite()) { - dir = NR::unit_vector (vp->get_pos() - box->corners[extreme_corner]); - } else { - dir = NR::unit_vector (vp->v_dir); +NR::Point +box3d_get_center_screen (SPBox3D *box) { + Proj::Pt3 proj_center (box3d_get_proj_center (box)); + if (!box->persp_ref->getObject()) { + //g_print ("No perspective present in box!! Should we simply use the currently active perspective?\n"); + return NR::Point (NR_HUGE, NR_HUGE); } - - return atan2 (dir[NR::Y], dir[NR::X]); + return box->persp_ref->getObject()->tmat.image(proj_center).affine(); } +/* + * To keep the snappoint from jumping randomly between the two lines when the mouse pointer is close to + * their intersection, we remember the last snapped line and keep snapping to this specific line as long + * as the distance from the intersection to the mouse pointer is less than remember_snap_threshold. + */ -bool sp_3dbox_recompute_z_orders (SP3DBox *box) -{ - gint new_z_orders[6]; - - // TODO: Determine the front corner depending on the distance from VPs and/or the user presets - guint front_corner = sp_3dbox_get_front_corner_id (box); - - gdouble dir_1x = sp_3dbox_corner_angle_to_VP (box, Box3D::X, front_corner); - gdouble dir_3x = sp_3dbox_corner_angle_to_VP (box, Box3D::X, front_corner ^ Box3D::Y); +// Should we make the threshold settable in the preferences? +static double remember_snap_threshold = 30; +//static guint remember_snap_index = 0; +static guint remember_snap_index_center = 0; + +static Proj::Pt3 +box3d_snap (SPBox3D *box, int id, Proj::Pt3 const &pt_proj, Proj::Pt3 const &start_pt) { + double z_coord = start_pt[Proj::Z]; + double diff_x = box->save_corner7[Proj::X] - box->save_corner0[Proj::X]; + double diff_y = box->save_corner7[Proj::Y] - box->save_corner0[Proj::Y]; + double x_coord = start_pt[Proj::X]; + double y_coord = start_pt[Proj::Y]; + Proj::Pt3 A_proj (x_coord, y_coord, z_coord, 1.0); + Proj::Pt3 B_proj (x_coord + diff_x, y_coord, z_coord, 1.0); + Proj::Pt3 C_proj (x_coord + diff_x, y_coord + diff_y, z_coord, 1.0); + Proj::Pt3 D_proj (x_coord, y_coord + diff_y, z_coord, 1.0); + Proj::Pt3 E_proj (x_coord - diff_x, y_coord + diff_y, z_coord, 1.0); + + NR::Point A = box->persp_ref->getObject()->tmat.image(A_proj).affine(); + NR::Point B = box->persp_ref->getObject()->tmat.image(B_proj).affine(); + NR::Point C = box->persp_ref->getObject()->tmat.image(C_proj).affine(); + NR::Point D = box->persp_ref->getObject()->tmat.image(D_proj).affine(); + NR::Point E = box->persp_ref->getObject()->tmat.image(E_proj).affine(); + NR::Point pt = box->persp_ref->getObject()->tmat.image(pt_proj).affine(); + + // TODO: Replace these lines between corners with lines from a corner to a vanishing point + // (this might help to prevent rounding errors if the box is small) + Box3D::Line pl1(A, B); + Box3D::Line pl2(A, D); + Box3D::Line diag1(A, (id == -1 || (!(id & Box3D::X) == !(id & Box3D::Y))) ? C : E); + Box3D::Line diag2(A, E); // diag2 is only taken into account if id equals -1, i.e., if we are snapping the center + + int num_snap_lines = (id != -1) ? 3 : 4; + NR::Point snap_pts[num_snap_lines]; + + snap_pts[0] = pl1.closest_to (pt); + snap_pts[1] = pl2.closest_to (pt); + snap_pts[2] = diag1.closest_to (pt); + if (id == -1) { + snap_pts[3] = diag2.closest_to (pt); + } - gdouble dir_1y = sp_3dbox_corner_angle_to_VP (box, Box3D::Y, front_corner); - //gdouble dir_0y = sp_3dbox_corner_angle_to_VP (box, Box3D::Y, front_corner ^ Box3D::X); + gdouble const zoom = inkscape_active_desktop()->current_zoom(); - gdouble dir_1z = sp_3dbox_corner_angle_to_VP (box, Box3D::Z, front_corner); - gdouble dir_3z = sp_3dbox_corner_angle_to_VP (box, Box3D::Z, front_corner ^ Box3D::Y); + // determine the distances to all potential snapping points + double snap_dists[num_snap_lines]; + for (int i = 0; i < num_snap_lines; ++i) { + snap_dists[i] = NR::L2 (snap_pts[i] - pt) * zoom; + } - // Still not perfect, but only fails in some rather degenerate cases. - // I suspect that there is a more elegant model, though. :) - new_z_orders[0] = Box3D::face_containing_corner (Box3D::XY, front_corner); - if (normalized_angle (dir_1y - dir_1z) > 0) { - new_z_orders[1] = Box3D::face_containing_corner (Box3D::YZ, front_corner); - if (normalized_angle (dir_1x - dir_1z) > 0) { - new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner ^ Box3D::Y); - } else { - new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner); - } - } else { - if (normalized_angle (dir_3x - dir_3z) > 0) { - new_z_orders[1] = Box3D::face_containing_corner (Box3D::XZ, front_corner ^ Box3D::Y); - new_z_orders[2] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X); - } else { - if (normalized_angle (dir_1x - dir_1z) > 0) { - new_z_orders[1] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X); - new_z_orders[2] = Box3D::face_containing_corner (Box3D::XZ, front_corner); - } else { - new_z_orders[1] = Box3D::face_containing_corner (Box3D::XZ, front_corner); - new_z_orders[2] = Box3D::face_containing_corner (Box3D::YZ, front_corner ^ Box3D::X); - } + // while we are within a given tolerance of the starting point, + // keep snapping to the same point to avoid jumping + bool within_tolerance = true; + for (int i = 0; i < num_snap_lines; ++i) { + if (snap_dists[i] > remember_snap_threshold) { + within_tolerance = false; + break; } } - new_z_orders[3] = Box3D::opposite_face (new_z_orders[2]); - new_z_orders[4] = Box3D::opposite_face (new_z_orders[1]); - new_z_orders[5] = Box3D::opposite_face (new_z_orders[0]); - - /* We only need to look for changes among the topmost three faces because the order - of the other ones is just inverted. */ - if ((box->z_orders[0] != new_z_orders[0]) || - (box->z_orders[1] != new_z_orders[1]) || - (box->z_orders[2] != new_z_orders[2])) - { - for (int i = 0; i < 6; ++i) { - box->z_orders[i] = new_z_orders[i]; + // find the closest snapping point + int snap_index = -1; + double snap_dist = NR_HUGE; + for (int i = 0; i < num_snap_lines; ++i) { + if (snap_dists[i] < snap_dist) { + snap_index = i; + snap_dist = snap_dists[i]; } - return true; } - return false; -} - -// convenience -static bool sp_3dbox_is_subset_or_superset (std::vector const &list1, std::vector const &list2) -{ - return (std::includes (list1.begin(), list1.end(), list2.begin(), list2.end()) || - std::includes (list2.begin(), list2.end(), list1.begin(), list1.end())); + // snap to the closest point (or the previously remembered one + // if we are within tolerance of the starting point) + NR::Point result; + if (within_tolerance) { + result = snap_pts[remember_snap_index_center]; + } else { + remember_snap_index_center = snap_index; + result = snap_pts[snap_index]; + } + return box->persp_ref->getObject()->tmat.preimage (result, z_coord, Proj::Z); } -static bool sp_3dbox_differ_by_opposite_faces (std::vector const &list1, std::vector const &list2) -{ - std::vector diff1; - std::vector diff2; - std::set_difference (list1.begin(), list1.end(), list2.begin(), list2.end(), - std::insert_iterator >(diff1, diff1.begin())); - std::set_difference (list2.begin(), list2.end(), list1.begin(), list1.end(), - std::insert_iterator >(diff2, diff2.begin())); - - if (diff1.size() == 3 || diff1.size() != diff2.size()) - return false; - - for (guint i = 0; i < diff1.size(); ++i) { - if (std::find (diff2.begin(), diff2.end(), Box3D::opposite_face (diff1[i])) == diff2.end()) { - return false; +void +box3d_set_corner (SPBox3D *box, const guint id, NR::Point const &new_pos, const Box3D::Axis movement, bool constrained) { + g_return_if_fail ((movement != Box3D::NONE) && (movement != Box3D::XYZ)); + + box->orig_corner0.normalize(); + box->orig_corner7.normalize(); + + /* update corners 0 and 7 according to which handle was moved and to the axes of movement */ + if (!(movement & Box3D::Z)) { + Proj::Pt3 pt_proj (box->persp_ref->getObject()->tmat.preimage (new_pos, (id < 4) ? box->orig_corner0[Proj::Z] : + box->orig_corner7[Proj::Z], + Proj::Z)); + if (constrained) { + pt_proj = box3d_snap (box, id, pt_proj, box3d_get_proj_corner (id, box->save_corner0, box->save_corner7)); } - } - return true; -} -static gint -sp_3dbox_face_containing_diagonal_corners (guint corner1, guint corner2) -{ - Box3D::Axis plane = (Box3D::Axis) (corner1 ^ corner2); - if (!Box3D::is_plane (plane)) { - g_warning ("Corners %d and %d should span a plane.\n", corner1, corner2); - return 0; + // normalizing pt_proj is essential because we want to mingle affine coordinates + pt_proj.normalize(); + box->orig_corner0 = Proj::Pt3 ((id & Box3D::X) ? box->save_corner0[Proj::X] : pt_proj[Proj::X], + (id & Box3D::Y) ? box->save_corner0[Proj::Y] : pt_proj[Proj::Y], + box->save_corner0[Proj::Z], + 1.0); + box->orig_corner7 = Proj::Pt3 ((id & Box3D::X) ? pt_proj[Proj::X] : box->save_corner7[Proj::X], + (id & Box3D::Y) ? pt_proj[Proj::Y] : box->save_corner7[Proj::Y], + box->save_corner7[Proj::Z], + 1.0); + } else { + Box3D::PerspectiveLine pl(box->persp_ref->getObject()->tmat.image( + box3d_get_proj_corner (id, box->save_corner0, box->save_corner7)).affine(), + Proj::Z, box->persp_ref->getObject()); + NR::Point new_pos_snapped(pl.closest_to(new_pos)); + Proj::Pt3 pt_proj (box->persp_ref->getObject()-> + tmat.preimage (new_pos_snapped, + box3d_get_proj_corner (box, id)[(movement & Box3D::Y) ? Proj::X : Proj::Y], + (movement & Box3D::Y) ? Proj::X : Proj::Y)); + bool corner0_move_x = !(id & Box3D::X) && (movement & Box3D::X); + bool corner0_move_y = !(id & Box3D::Y) && (movement & Box3D::Y); + bool corner7_move_x = (id & Box3D::X) && (movement & Box3D::X); + bool corner7_move_y = (id & Box3D::Y) && (movement & Box3D::Y); + // normalizing pt_proj is essential because we want to mingle affine coordinates + pt_proj.normalize(); + box->orig_corner0 = Proj::Pt3 (corner0_move_x ? pt_proj[Proj::X] : box->orig_corner0[Proj::X], + corner0_move_y ? pt_proj[Proj::Y] : box->orig_corner0[Proj::Y], + (id & Box3D::Z) ? box->orig_corner0[Proj::Z] : pt_proj[Proj::Z], + 1.0); + box->orig_corner7 = Proj::Pt3 (corner7_move_x ? pt_proj[Proj::X] : box->orig_corner7[Proj::X], + corner7_move_y ? pt_proj[Proj::Y] : box->orig_corner7[Proj::Y], + (id & Box3D::Z) ? pt_proj[Proj::Z] : box->orig_corner7[Proj::Z], + 1.0); } - - return Box3D::face_containing_corner (plane, corner1); + // FIXME: Should we update the box here? If so, how? } -static std::vector sp_3dbox_adjacent_faces_of_edge (guint corner1, guint corner2) { - std::vector adj_faces; - Box3D::Axis edge = (Box3D::Axis) (corner1 ^ corner2); - if (!Box3D::is_single_axis_direction (edge)) { - return adj_faces; - } - - Box3D::Axis plane = Box3D::orth_plane_or_axis (edge); - Box3D::Axis axis1 = Box3D::extract_first_axis_direction (plane); - Box3D::Axis axis2 = Box3D::extract_second_axis_direction (plane); - adj_faces.push_back (Box3D::face_containing_corner ((Box3D::Axis) (edge ^ axis1), corner1)); - adj_faces.push_back (Box3D::face_containing_corner ((Box3D::Axis) (edge ^ axis2), corner1)); - return adj_faces; -} +void box3d_set_center (SPBox3D *box, NR::Point const &new_pos, NR::Point const &old_pos, const Box3D::Axis movement, bool constrained) { + g_return_if_fail ((movement != Box3D::NONE) && (movement != Box3D::XYZ)); -static std::vector sp_3dbox_faces_meeting_in_corner (guint corner) { - std::vector faces; - for (int i = 0; i < 3; ++i) { - faces.push_back (sp_3dbox_face_containing_diagonal_corners (corner, corner ^ Box3D::planes[i])); - } - return faces; -} + if (!(movement & Box3D::Z)) { + double coord = (box->orig_corner0[Proj::Z] + box->orig_corner7[Proj::Z]) / 2; + double radx = (box->orig_corner7[Proj::X] - box->orig_corner0[Proj::X]) / 2; + double rady = (box->orig_corner7[Proj::Y] - box->orig_corner0[Proj::Y]) / 2; -static void sp_3dbox_remaining_faces (std::vector const &faces, std::vector &rem_faces) -{ - rem_faces.clear(); - for (gint i = 0; i < 6; ++i) { - if (std::find (faces.begin(), faces.end(), i) == faces.end()) { - rem_faces.push_back (i); + Proj::Pt3 pt_proj (box->persp_ref->getObject()->tmat.preimage (new_pos, coord, Proj::Z)); + if (constrained) { + Proj::Pt3 old_pos_proj (box->persp_ref->getObject()->tmat.preimage (old_pos, coord, Proj::Z)); + pt_proj = box3d_snap (box, -1, pt_proj, old_pos_proj); } + // normalizing pt_proj is essential because we want to mingle affine coordinates + pt_proj.normalize(); + box->orig_corner0 = Proj::Pt3 ((movement & Box3D::X) ? pt_proj[Proj::X] - radx : box->orig_corner0[Proj::X], + (movement & Box3D::Y) ? pt_proj[Proj::Y] - rady : box->orig_corner0[Proj::Y], + box->orig_corner0[Proj::Z], + 1.0); + box->orig_corner7 = Proj::Pt3 ((movement & Box3D::X) ? pt_proj[Proj::X] + radx : box->orig_corner7[Proj::X], + (movement & Box3D::Y) ? pt_proj[Proj::Y] + rady : box->orig_corner7[Proj::Y], + box->orig_corner7[Proj::Z], + 1.0); + } else { + double coord = (box->orig_corner0[Proj::X] + box->orig_corner7[Proj::X]) / 2; + double radz = (box->orig_corner7[Proj::Z] - box->orig_corner0[Proj::Z]) / 2; + + Box3D::PerspectiveLine pl(old_pos, Proj::Z, box->persp_ref->getObject()); + NR::Point new_pos_snapped(pl.closest_to(new_pos)); + Proj::Pt3 pt_proj (box->persp_ref->getObject()->tmat.preimage (new_pos_snapped, coord, Proj::X)); + + /* normalizing pt_proj is essential because we want to mingle affine coordinates */ + pt_proj.normalize(); + box->orig_corner0 = Proj::Pt3 (box->orig_corner0[Proj::X], + box->orig_corner0[Proj::Y], + pt_proj[Proj::Z] - radz, + 1.0); + box->orig_corner7 = Proj::Pt3 (box->orig_corner7[Proj::X], + box->orig_corner7[Proj::Y], + pt_proj[Proj::Z] + radz, + 1.0); } } /* - * Given two adjacent edges (\a c2,\a c1) and (\a c2, \a c3) of \a box (with common corner \a c2), - * check whether both lie on the convex hull of the point configuration given by \a box's corners. + * Manipulates corner1 through corner4 to contain the indices of the corners + * from which the perspective lines in the direction of 'axis' emerge */ -static bool -sp_3dbox_is_border_edge_pair (SP3DBox *box, guint const c1, guint const c2, guint const c3) +void box3d_corners_for_PLs (const SPBox3D * box, Proj::Axis axis, + NR::Point &corner1, NR::Point &corner2, NR::Point &corner3, NR::Point &corner4) { - Box3D::Axis edge21 = (Box3D::Axis) (c2 ^ c1); - Box3D::Axis edge23 = (Box3D::Axis) (c2 ^ c3); - Box3D::Axis rear_axis = Box3D::orth_plane_or_axis ((Box3D::Axis) (edge21 ^ edge23)); - - NR::Point corner2 = box->corners[c2]; - NR::Point dir21 = box->corners[c1] - corner2; - NR::Point dir23 = box->corners[c3] - corner2; - - if (!Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ edge21 ^ edge23] - corner2) || - !Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ rear_axis] - corner2) || - !Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ rear_axis ^ edge21] - corner2) || - !Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ rear_axis ^ edge21 ^ edge23] - corner2) || - !Box3D::lies_in_sector (dir21, dir23, box->corners[c2 ^ rear_axis ^ edge23] - corner2)) { - // corner triple c1, c2, c3 doesn't bound the convex hull - return false; + g_return_if_fail (box->persp_ref->getObject()); + //box->orig_corner0.normalize(); + //box->orig_corner7.normalize(); + double coord = (box->orig_corner0[axis] > box->orig_corner7[axis]) ? + box->orig_corner0[axis] : + box->orig_corner7[axis]; + + Proj::Pt3 c1, c2, c3, c4; + // FIXME: This can certainly be done more elegantly/efficiently than by a case-by-case analysis. + switch (axis) { + case Proj::X: + c1 = Proj::Pt3 (coord, box->orig_corner0[Proj::Y], box->orig_corner0[Proj::Z], 1.0); + c2 = Proj::Pt3 (coord, box->orig_corner7[Proj::Y], box->orig_corner0[Proj::Z], 1.0); + c3 = Proj::Pt3 (coord, box->orig_corner7[Proj::Y], box->orig_corner7[Proj::Z], 1.0); + c4 = Proj::Pt3 (coord, box->orig_corner0[Proj::Y], box->orig_corner7[Proj::Z], 1.0); + break; + case Proj::Y: + c1 = Proj::Pt3 (box->orig_corner0[Proj::X], coord, box->orig_corner0[Proj::Z], 1.0); + c2 = Proj::Pt3 (box->orig_corner7[Proj::X], coord, box->orig_corner0[Proj::Z], 1.0); + c3 = Proj::Pt3 (box->orig_corner7[Proj::X], coord, box->orig_corner7[Proj::Z], 1.0); + c4 = Proj::Pt3 (box->orig_corner0[Proj::X], coord, box->orig_corner7[Proj::Z], 1.0); + break; + case Proj::Z: + c1 = Proj::Pt3 (box->orig_corner7[Proj::X], box->orig_corner7[Proj::Y], coord, 1.0); + c2 = Proj::Pt3 (box->orig_corner7[Proj::X], box->orig_corner0[Proj::Y], coord, 1.0); + c3 = Proj::Pt3 (box->orig_corner0[Proj::X], box->orig_corner0[Proj::Y], coord, 1.0); + c4 = Proj::Pt3 (box->orig_corner0[Proj::X], box->orig_corner7[Proj::Y], coord, 1.0); + break; + default: + return; } - // corner triple c1, c2, c3 bounds the convex hull - return true; + corner1 = box->persp_ref->getObject()->tmat.image(c1).affine(); + corner2 = box->persp_ref->getObject()->tmat.image(c2).affine(); + corner3 = box->persp_ref->getObject()->tmat.image(c3).affine(); + corner4 = box->persp_ref->getObject()->tmat.image(c4).affine(); } -/* - * Test whether there are any adjacent corners of \a corner (i.e., connected with it along one of the axes) - * such that the corresponding edges bound the convex hull of the box (as a point configuration in the plane) - * If this is the case, return the corresponding two adjacent corners; otherwise return (-1, -1). - */ -static Box3D::Axis -sp_3dbox_axis_pair_bounding_convex_hull (SP3DBox *box, guint corner) - { - guint adj1 = corner ^ Box3D::X; - guint adj2 = corner ^ Box3D::Y; - guint adj3 = corner ^ Box3D::Z; - - if (sp_3dbox_is_border_edge_pair (box, adj1, corner, adj2)) { - return Box3D::XY; +/* Auxiliary function: Checks whether the half-line from A to B crosses the line segment joining C and D */ +static bool +box3d_half_line_crosses_joining_line (Geom::Point const &A, Geom::Point const &B, + Geom::Point const &C, Geom::Point const &D) { + Geom::Point E; // the point of intersection + Geom::Point n0 = (B - A).ccw(); + double d0 = dot(n0,A); + + Geom::Point n1 = (D - C).ccw(); + double d1 = dot(n1,C); + Geom::IntersectorKind intersects = Geom::line_intersection(n0, d0, n1, d1, E); + if (intersects == Geom::coincident || intersects == Geom::parallel) { + return false; } - if (sp_3dbox_is_border_edge_pair (box, adj1, corner, adj3)) { - return Box3D::XZ; + + if ((dot(C,n0) < d0) == (dot(D,n0) < d0)) { + // C and D lie on the same side of the line AB + return false; } - if (sp_3dbox_is_border_edge_pair (box, adj2, corner, adj3)) { - return Box3D::YZ; + if ((dot(A,n1) < d1) != (dot(B,n1) < d1)) { + // A and B lie on different sides of the line CD + return true; + } else if (Geom::distance(E,A) < Geom::distance(E,B)) { + // The line CD passes on the "wrong" side of A + return false; } - return Box3D::NONE; + + // The line CD passes on the "correct" side of A + return true; } -// inside_hull is modified 'in place' by the following function -static void sp_3dbox_corner_configuration (SP3DBox *box, std::vector &on_hull, std::vector &inside_hull) -{ - for (int i = 0; i < 8; ++i) { - Box3D::Axis bounding_edges = sp_3dbox_axis_pair_bounding_convex_hull (box, i); - if (bounding_edges != Box3D::NONE) { - on_hull.push_back (i); - } else { - inside_hull.push_back (i); - } +static bool +box3d_XY_axes_are_swapped (SPBox3D *box) { + Persp3D *persp = box->persp_ref->getObject(); + g_return_val_if_fail(persp, false); + Box3D::PerspectiveLine l1(box3d_get_corner_screen(box, 3), Proj::X, persp); + Box3D::PerspectiveLine l2(box3d_get_corner_screen(box, 3), Proj::Y, persp); + NR::Point v1(l1.direction()); + NR::Point v2(l2.direction()); + v1.normalize(); + v2.normalize(); + + return (v1[NR::X]*v2[NR::Y] - v1[NR::Y]*v2[NR::X] > 0); +} + +static inline void +box3d_aux_set_z_orders (int z_orders[6], int a, int b, int c, int d, int e, int f) { + z_orders[0] = a; + z_orders[1] = b; + z_orders[2] = c; + z_orders[3] = d; + z_orders[4] = e; + z_orders[5] = f; +} + +static inline void +box3d_swap_z_orders (int z_orders[6]) { + int tmp; + for (int i = 0; i < 3; ++i) { + tmp = z_orders[i]; + z_orders[i] = z_orders[5-i]; + z_orders[5-i] = tmp; } } -/* returns true if there was a change in the z-orders (which triggers an update of the repr) */ -static bool sp_3dbox_recompute_z_orders_by_corner_configuration (SP3DBox *box) -{ - gint new_z_orders[6]; - Box3D::Axis front_rear_axis = Box3D::Z; +/* + * In der Standard-Perspektive: + * 2 = vorne + * 1 = oben + * 0 = links + * 3 = rechts + * 4 = unten + * 5 = hinten + */ - std::vector on_hull; - std::vector inside_hull; - std::vector visible_faces; +/* All VPs infinite */ +static void +box3d_set_new_z_orders_case0 (SPBox3D *box, int z_orders[6], Box3D::Axis central_axis) { + Persp3D *persp = box->persp_ref->getObject(); + NR::Point xdir(persp3d_get_infinite_dir(persp, Proj::X)); + NR::Point ydir(persp3d_get_infinite_dir(persp, Proj::Y)); + NR::Point zdir(persp3d_get_infinite_dir(persp, Proj::Z)); - sp_3dbox_corner_configuration (box, on_hull, inside_hull); + bool swapped = box3d_XY_axes_are_swapped(box); - switch (on_hull.size()) { - case 4: - { - // the following works because on_hull is sorted - gint front_face = sp_3dbox_face_containing_diagonal_corners (on_hull[0], on_hull[3]); - visible_faces.push_back (front_face); + //g_print ("3 infinite VPs; "); + switch(central_axis) { + case Box3D::X: + if (!swapped) { + //g_print ("central axis X (case a)"); + box3d_aux_set_z_orders (z_orders, 2, 0, 4, 1, 3, 5); + } else { + //g_print ("central axis X (case b)"); + box3d_aux_set_z_orders (z_orders, 3, 1, 5, 2, 4, 0); } break; - - case 6: - { - guint c1 = inside_hull[0] ^ Box3D::XYZ; - guint c2 = inside_hull[1] ^ Box3D::XYZ; - Box3D::Axis edge = (Box3D::Axis) (c1 ^ c2); - if (Box3D::is_single_axis_direction (edge)) { - visible_faces = sp_3dbox_adjacent_faces_of_edge (c1, c2); - } else if (c1 == c2 ^ Box3D::XYZ) { - guint c_cmp = sp_3dbox_get_corner_id_along_edge (box, 0, front_rear_axis, Box3D::FRONT); - guint visible_front_corner = (((c_cmp & front_rear_axis) == (c1 & front_rear_axis)) ? c1 : c2); - visible_faces = sp_3dbox_faces_meeting_in_corner (visible_front_corner); + case Box3D::Y: + if (!swapped) { + //g_print ("central axis Y (case a)"); + box3d_aux_set_z_orders (z_orders, 2, 3, 1, 4, 0, 5); } else { - /* Under what conditions do we end up here? Can we safely ignore this case? */ - return false; + //g_print ("central axis Y (case b)"); + box3d_aux_set_z_orders (z_orders, 5, 0, 4, 1, 3, 2); } break; - } - - default: - /* Under what conditions do we end up here? Can we safely ignore this case? */ - return false; - } - - /* catch weird corner configurations; these should be theoretically impossible, but maybe - occur in (almost) degenerate cases due to rounding errors, for example */ - if (std::find (visible_faces.begin(), visible_faces.end(), -1) != visible_faces.end()) { - return false; - } - - /* sort the list of visible faces for later use (although it may be already sorted anyway) */ - std::sort (visible_faces.begin(), visible_faces.end()); - - std::vector invisible_faces; - sp_3dbox_remaining_faces (visible_faces, invisible_faces); - - - if (!sp_3dbox_is_subset_or_superset (visible_faces, box->currently_visible_faces) && - !sp_3dbox_differ_by_opposite_faces (visible_faces, box->currently_visible_faces)) { - std::swap (visible_faces, invisible_faces); - if (!sp_3dbox_is_subset_or_superset (visible_faces, box->currently_visible_faces) && - !sp_3dbox_differ_by_opposite_faces (visible_faces, box->currently_visible_faces)) { - /* Hopefully this case is only caused by rounding errors or something similar; - does it need further investigation? */ - return false; - } - } - - box->currently_visible_faces = visible_faces; - - // set new z-orders according to the visible/invisible faces - guint vis_size = visible_faces.size(); - for (guint i = 0; i < vis_size; ++i) { - new_z_orders[i] = visible_faces[i]; - } - for (guint i = 0; i < invisible_faces.size(); ++i) { - new_z_orders[vis_size + i] = invisible_faces[i]; - } - - // test whether any z-orders actually changed and indicate this in the return status - for (int i = 0; i < 6; ++i) { - if (box->z_orders[i] != new_z_orders[i]) { - // we update the z-orders starting from the index where the change occurs - for (int j = i; j < 6; ++j) { - box->z_orders[j] = new_z_orders[j]; + case Box3D::Z: + if (!swapped) { + //g_print ("central axis Z (case a)"); + box3d_aux_set_z_orders (z_orders, 2, 0, 1, 4, 3, 5); + } else { + //g_print ("central axis Z (case b)"); + box3d_aux_set_z_orders (z_orders, 5, 3, 4, 1, 0, 2); } - return true; - } - } - return false; -} - -// FIXME: Can we unify this and the next function for setting the z-orders? -void sp_3dbox_set_z_orders_in_the_first_place (SP3DBox *box) -{ - // For efficiency reasons, we only set the new z-orders if something really changed - if (sp_3dbox_recompute_z_orders (box)) { - box->faces[box->z_orders[0]]->lower_to_bottom (); - box->faces[box->z_orders[1]]->lower_to_bottom (); - box->faces[box->z_orders[2]]->lower_to_bottom (); - box->faces[box->z_orders[3]]->lower_to_bottom (); - box->faces[box->z_orders[4]]->lower_to_bottom (); - box->faces[box->z_orders[5]]->lower_to_bottom (); - } -} - -void sp_3dbox_set_z_orders_later_on (SP3DBox *box) -{ - // For efficiency reasons, we only set the new z-orders if something really changed - if (sp_3dbox_recompute_z_orders_by_corner_configuration (box)) { - box->faces[box->z_orders[0]]->lower_to_bottom (); - box->faces[box->z_orders[1]]->lower_to_bottom (); - box->faces[box->z_orders[2]]->lower_to_bottom (); - box->faces[box->z_orders[3]]->lower_to_bottom (); - box->faces[box->z_orders[4]]->lower_to_bottom (); - box->faces[box->z_orders[5]]->lower_to_bottom (); + break; + case Box3D::NONE: + if (!swapped) { + //g_print ("central axis NONE (case a)"); + box3d_aux_set_z_orders (z_orders, 2, 3, 4, 1, 0, 5); + } else { + //g_print ("central axis NONE (case b)"); + box3d_aux_set_z_orders (z_orders, 5, 0, 1, 4, 3, 2); + } + break; + default: + g_assert_not_reached(); + break; } -} - -void -sp_3dbox_update_curves (SP3DBox *box) { - for (int i = 0; i < 6; ++i) { - if (box->faces[i]) box->faces[i]->set_curve(); + /** + if (swapped) { + g_print ("; swapped"); } + g_print ("\n"); + **/ } -/** - * In some situations (e.g., after cloning boxes, undo & redo, or reading boxes from a file) there are - * paths already present in the document which correspond to the faces of newly created boxes, but their - * 'path' members don't link to them yet. The following function corrects this if necessary. - */ -void -sp_3dbox_link_to_existing_paths (SP3DBox *box, Inkscape::XML::Node *repr) { - // TODO: We should probably destroy the existing paths and recreate them because we don't know - // precisely which path corresponds to which face. Does this make a difference? - // In sp_3dbox_write we write the correct paths anyway, don't we? But we could get into - // trouble at a later stage when we only write single faces for degenerate boxes. - - SPDocument *document = SP_OBJECT_DOCUMENT(box); - guint face_id = 0; - - for (Inkscape::XML::Node *i = sp_repr_children(repr); i != NULL; i = sp_repr_next(i)) { - if (face_id > 5) { - g_warning ("SVG representation of 3D boxes must contain 6 paths or less.\n"); +/* Precisely one finite VP */ +static void +box3d_set_new_z_orders_case1 (SPBox3D *box, int z_orders[6], Box3D::Axis central_axis, Box3D::Axis fin_axis) { + Persp3D *persp = box->persp_ref->getObject(); + NR::Point vp(persp3d_get_VP(persp, Box3D::toProj(fin_axis)).affine()); + + // note: in some of the case distinctions below we rely upon the fact that oaxis1 and oaxis2 are ordered + Box3D::Axis oaxis1 = Box3D::get_remaining_axes(fin_axis).first; + Box3D::Axis oaxis2 = Box3D::get_remaining_axes(fin_axis).second; + //g_print ("oaxis1 = %s, oaxis2 = %s\n", Box3D::string_from_axes(oaxis1), Box3D::string_from_axes(oaxis2)); + int inside1 = 0; + int inside2 = 0; + inside1 = box3d_pt_lies_in_PL_sector (box, vp, 3, 3 ^ oaxis2, oaxis1); + inside2 = box3d_pt_lies_in_PL_sector (box, vp, 3, 3 ^ oaxis1, oaxis2); + //g_print ("inside1 = %d, inside2 = %d\n", inside1, inside2); + + bool swapped = box3d_XY_axes_are_swapped(box); + + //g_print ("2 infinite VPs; "); + //g_print ("finite axis: %s; ", Box3D::string_from_axes(fin_axis)); + switch(central_axis) { + case Box3D::X: + if (!swapped) { + //g_print ("central axis X (case a)"); + box3d_aux_set_z_orders (z_orders, 2, 4, 0, 1, 3, 5); + } else { + //if (inside2) { + //g_print ("central axis X (case b)"); + box3d_aux_set_z_orders (z_orders, 5, 3, 1, 0, 2, 4); + //} else { + //g_print ("central axis X (case c)"); + //box3d_aux_set_z_orders (z_orders, 5, 3, 1, 2, 0, 4); + //} + } break; - } - - SPObject *face_object = document->getObjectByRepr((Inkscape::XML::Node *) i); - if (!SP_IS_PATH(face_object)) { - g_warning ("SVG representation of 3D boxes should only contain paths.\n"); - continue; - } - // TODO: Currently we don't check whether all paths are being linked to different faces. - // This is no problem with valid SVG files. It may lead to crashes, however, - // in case a file is corrupt (e.g., two or more faces have identical descriptions). - gint id = Box3DFace::descr_to_id (i->attribute ("inkscape:box3dface")); - box->faces[id]->hook_path_to_3dbox(SP_PATH(face_object)); - ++face_id; + case Box3D::Y: + if (inside2 > 0) { + //g_print ("central axis Y (case a)"); + box3d_aux_set_z_orders (z_orders, 1, 2, 3, 0, 5, 4); + } else if (inside2 < 0) { + //g_print ("central axis Y (case b)"); + box3d_aux_set_z_orders (z_orders, 2, 3, 1, 4, 0, 5); + } else { + if (!swapped) { + //g_print ("central axis Y (case c1)"); + box3d_aux_set_z_orders (z_orders, 2, 3, 1, 5, 0, 4); + } else { + //g_print ("central axis Y (case c2)"); + box3d_aux_set_z_orders (z_orders, 5, 0, 4, 1, 3, 2); + } + } + break; + case Box3D::Z: + if (inside2) { + if (!swapped) { + //g_print ("central axis Z (case a1)"); + box3d_aux_set_z_orders (z_orders, 2, 1, 3, 0, 4, 5); + } else { + //g_print ("central axis Z (case a2)"); + box3d_aux_set_z_orders (z_orders, 5, 3, 4, 0, 1, 2); + } + } else if (inside1) { + if (!swapped) { + //g_print ("central axis Z (case b1)"); + box3d_aux_set_z_orders (z_orders, 2, 0, 1, 4, 3, 5); + } else { + //g_print ("central axis Z (case b2)"); + box3d_aux_set_z_orders (z_orders, 5, 3, 4, 1, 0, 2); + //box3d_aux_set_z_orders (z_orders, 5, 3, 0, 1, 2, 4); + } + } else { + // "regular" case + if (!swapped) { + //g_print ("central axis Z (case c1)"); + box3d_aux_set_z_orders (z_orders, 0, 1, 2, 5, 4, 3); + } else { + //g_print ("central axis Z (case c2)"); + box3d_aux_set_z_orders (z_orders, 5, 3, 4, 0, 2, 1); + //box3d_aux_set_z_orders (z_orders, 5, 3, 4, 0, 2, 1); + } + } + break; + case Box3D::NONE: + if (!swapped) { + //g_print ("central axis NONE (case a)"); + box3d_aux_set_z_orders (z_orders, 2, 3, 4, 5, 0, 1); + } else { + //g_print ("central axis NONE (case b)"); + box3d_aux_set_z_orders (z_orders, 5, 0, 1, 3, 2, 4); + //box3d_aux_set_z_orders (z_orders, 2, 3, 4, 1, 0, 5); + } + break; + default: + g_assert_not_reached(); } - if (face_id < 6) { - //g_warning ("SVG representation of 3D boxes should contain exactly 6 paths (degenerate boxes are not yet supported).\n"); - // TODO: Check whether it is safe to add the remaining paths to the box and do so in case it is. - // (But we also land here for newly created boxes where we shouldn't add any paths because - // This is done in sp_3dbox_write later on. + /** + if (swapped) { + g_print ("; swapped"); } + g_print ("\n"); + **/ } -void -sp_3dbox_reshape_after_VP_rotation (SP3DBox *box, Box3D::Axis axis) -{ - Box3D::Perspective3D *persp = inkscape_active_document()->get_persp_of_box (box); - Box3D::VanishingPoint *vp = persp->get_vanishing_point (axis); - - guint c1 = (axis == Box3D::Z) ? 1 : sp_3dbox_get_front_corner_id (box); // hack - guint c2 = c1 ^ axis; - NR::Point v = box->corners[c1] - box->corners[c2]; - double dist = NR::L2 (v) * ((NR::dot (v, vp->v_dir) < 0) ? 1 : -1); // "directed" distance - - Box3D::PerspectiveLine pline (box->corners[c1], axis, persp); - NR::Point pt = pline.point_from_lambda (dist); - - sp_3dbox_move_corner_in_Z_direction (box, c2, pt, axis == Box3D::Z); -} - -void -sp_3dbox_move_corner_in_XY_plane (SP3DBox *box, guint id, NR::Point pt, Box3D::Axis axes) -{ - Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box); - - NR::Point A (box->corners[id ^ Box3D::XY]); - if (Box3D::is_single_axis_direction (axes)) { - pt = Box3D::PerspectiveLine (box->corners[id], axes, persp).closest_to(pt); - } +/* Precisely 2 finite VPs */ +static void +box3d_set_new_z_orders_case2 (SPBox3D *box, int z_orders[6], Box3D::Axis central_axis, Box3D::Axis infinite_axis) { + Persp3D *persp = box->persp_ref->getObject(); - /* set the 'front' corners */ - box->corners[id] = pt; + NR::Point c3(box3d_get_corner_screen(box, 3)); + NR::Point xdir(persp3d_get_PL_dir_from_pt(persp, c3, Proj::X)); + NR::Point ydir(persp3d_get_PL_dir_from_pt(persp, c3, Proj::Y)); + NR::Point zdir(persp3d_get_PL_dir_from_pt(persp, c3, Proj::Z)); - Box3D::PerspectiveLine pl_one (A, Box3D::Y, persp); - Box3D::PerspectiveLine pl_two (pt, Box3D::X, persp); - box->corners[id ^ Box3D::X] = pl_one.meet(pl_two); + bool swapped = box3d_XY_axes_are_swapped(box); - pl_one = Box3D::PerspectiveLine (A, Box3D::X, persp); - pl_two = Box3D::PerspectiveLine (pt, Box3D::Y, persp); - box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two); + int insidexy = box3d_VP_lies_in_PL_sector (box, Proj::X, 3, 3 ^ Box3D::Z, Box3D::Y); + int insidexz = box3d_VP_lies_in_PL_sector (box, Proj::X, 3, 3 ^ Box3D::Y, Box3D::Z); - /* set the 'rear' corners */ - NR::Point B (box->corners[id ^ Box3D::XYZ]); + int insideyx = box3d_VP_lies_in_PL_sector (box, Proj::Y, 3, 3 ^ Box3D::Z, Box3D::X); + int insideyz = box3d_VP_lies_in_PL_sector (box, Proj::Y, 3, 3 ^ Box3D::X, Box3D::Z); - pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Z, persp); - pl_two = Box3D::PerspectiveLine (B, Box3D::Y, persp); - box->corners[id ^ Box3D::XZ] = pl_one.meet(pl_two); + int insidezx = box3d_VP_lies_in_PL_sector (box, Proj::Z, 3, 3 ^ Box3D::Y, Box3D::X); + int insidezy = box3d_VP_lies_in_PL_sector (box, Proj::Z, 3, 3 ^ Box3D::X, Box3D::Y); - pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XZ], Box3D::X, persp); - pl_two = Box3D::PerspectiveLine (pt, Box3D::Z, persp); - box->corners[id ^ Box3D::Z] = pl_one.meet(pl_two); + //g_print ("Insides: xy = %d, xz = %d, yx = %d, yz = %d, zx = %d, zy = %d\n", + // insidexy, insidexz, insideyx, insideyz, insidezx, insidezy); - pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::Z], Box3D::Y, persp); - pl_two = Box3D::PerspectiveLine (B, Box3D::X, persp); - box->corners[id ^ Box3D::YZ] = pl_one.meet(pl_two); - + //g_print ("1 infinite VP; "); + switch(central_axis) { + case Box3D::X: + if (!swapped) { + if (insidezy == -1) { + //g_print ("central axis X (case a1)"); + box3d_aux_set_z_orders (z_orders, 2, 4, 0, 1, 3, 5); + } else if (insidexy == 1) { + //g_print ("central axis X (case a2)"); + box3d_aux_set_z_orders (z_orders, 2, 4, 0, 5, 1, 3); + } else { + //g_print ("central axis X (case a3)"); + box3d_aux_set_z_orders (z_orders, 2, 4, 0, 1, 3, 5); + } + } else { + if (insideyz == -1) { + //g_print ("central axis X (case b1)"); + box3d_aux_set_z_orders (z_orders, 3, 1, 5, 0, 2, 4); + } else { + if (!swapped) { + //g_print ("central axis X (case b2)"); + box3d_aux_set_z_orders (z_orders, 3, 1, 5, 2, 4, 0); + } else { + //g_print ("central axis X (case b3)"); + box3d_aux_set_z_orders (z_orders, 1, 3, 5, 0, 2, 4); + } + } + } + break; + case Box3D::Y: + if (!swapped) { + if (insideyz == 1) { + //g_print ("central axis Y (case a1)"); + box3d_aux_set_z_orders (z_orders, 2, 3, 1, 0, 5, 4); + } else { + //g_print ("central axis Y (case a2)"); + box3d_aux_set_z_orders (z_orders, 2, 3, 1, 5, 0, 4); + } + } else { + //g_print ("central axis Y (case b)"); + box3d_aux_set_z_orders (z_orders, 5, 0, 4, 1, 3, 2); + } + break; + case Box3D::Z: + if (!swapped) { + if (insidezy == 1) { + //g_print ("central axis Z (case a1)"); + box3d_aux_set_z_orders (z_orders, 2, 1, 0, 4, 3, 5); + } else if (insidexy == -1) { + //g_print ("central axis Z (case a2)"); + box3d_aux_set_z_orders (z_orders, 2, 1, 0, 5, 4, 3); + } else { + //g_print ("central axis Z (case a3)"); + box3d_aux_set_z_orders (z_orders, 2, 0, 1, 5, 3, 4); + } + } else { + //g_print ("central axis Z (case b)"); + box3d_aux_set_z_orders (z_orders, 5, 3, 4, 1, 0, 2); + } + break; + case Box3D::NONE: + if (!swapped) { + //g_print ("central axis NONE (case a)"); + box3d_aux_set_z_orders (z_orders, 2, 3, 4, 1, 0, 5); + } else { + //g_print ("central axis NONE (case b)"); + box3d_aux_set_z_orders (z_orders, 5, 0, 1, 4, 3, 2); + } + break; + default: + g_assert_not_reached(); + break; + } + /** + if (swapped) { + g_print ("; swapped"); + } + g_print ("\n"); + **/ } -void -sp_3dbox_move_corner_in_Z_direction (SP3DBox *box, guint id, NR::Point pt, bool constrained) -{ - if (!constrained) sp_3dbox_move_corner_in_XY_plane (box, id, pt, Box3D::XY); - - Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box); - - /* set the four corners of the face containing corners[id] */ - box->corners[id] = Box3D::PerspectiveLine (box->corners[id], Box3D::Z, persp).closest_to(pt); +/* + * It can happen that during dragging the box is everted. + * In this case the opposite sides in this direction need to be swapped + */ +static Box3D::Axis +box3d_everted_directions (SPBox3D *box) { + Box3D::Axis ev = Box3D::NONE; - Box3D::PerspectiveLine pl_one (box->corners[id], Box3D::X, persp); - Box3D::PerspectiveLine pl_two (box->corners[id ^ Box3D::XZ], Box3D::Z, persp); - box->corners[id ^ Box3D::X] = pl_one.meet(pl_two); + box->orig_corner0.normalize(); + box->orig_corner7.normalize(); - pl_one = Box3D::PerspectiveLine (box->corners[id ^ Box3D::X], Box3D::Y, persp); - pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::XYZ], Box3D::Z, persp); - box->corners[id ^ Box3D::XY] = pl_one.meet(pl_two); + if (box->orig_corner0[Proj::X] < box->orig_corner7[Proj::X]) + ev = (Box3D::Axis) (ev ^ Box3D::X); + if (box->orig_corner0[Proj::Y] < box->orig_corner7[Proj::Y]) + ev = (Box3D::Axis) (ev ^ Box3D::Y); + if (box->orig_corner0[Proj::Z] > box->orig_corner7[Proj::Z]) // FIXME: Remove the need to distinguish signs among the cases + ev = (Box3D::Axis) (ev ^ Box3D::Z); - pl_one = Box3D::PerspectiveLine (box->corners[id], Box3D::Y, persp); - pl_two = Box3D::PerspectiveLine (box->corners[id ^ Box3D::YZ], Box3D::Z, persp); - box->corners[id ^ Box3D::Y] = pl_one.meet(pl_two); + return ev; } static void -sp_3dbox_reshape_edge_after_VP_toggling (SP3DBox *box, const guint corner, const Box3D::Axis axis, Box3D::Perspective3D *persp) -{ - /* Hmm, perhaps we should simply use one of the corners as the pivot point. - But this way we minimize the amount of reshaping. - On second thought, we need to find a way to ensure that all boxes sharing the same - perspective are updated consistently _as a group_. That is, they should also retain - their relative positions towards each other. */ - NR::Maybe pt = sp_3dbox_get_midpoint_between_corners (box, corner, corner ^ axis); - g_return_if_fail (pt); - - Box3D::Axis axis2 = ((axis == Box3D::Y) ? Box3D::X : Box3D::Y); - - Box3D::PerspectiveLine line1 (box->corners[corner], axis2, persp); - Box3D::PerspectiveLine line2 (box->corners[corner ^ axis], axis2, persp); - - Box3D::PerspectiveLine line3 (*pt, axis, persp); - - NR::Point new_corner1 = line1.meet (line3); - NR::Point new_corner2 = line2.meet (line3); - - box->corners[corner] = new_corner1; - box->corners[corner ^ axis] = new_corner2; -} +box3d_swap_sides(int z_orders[6], Box3D::Axis axis) { + int pos1 = -1; + int pos2 = -1; -void -sp_3dbox_reshape_after_VP_toggling (SP3DBox *box, Box3D::Axis axis) -{ - Box3D::Perspective3D *persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box); - std::pair dirs = Box3D::get_remaining_axes (axis); - - sp_3dbox_reshape_edge_after_VP_toggling (box, 0, axis, persp); - sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first, axis, persp); - sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.first ^ dirs.second, axis, persp); - sp_3dbox_reshape_edge_after_VP_toggling (box, 0 ^ dirs.second, axis, persp); -} - -NR::Maybe -sp_3dbox_get_center (SP3DBox *box) -{ - return sp_3dbox_get_midpoint_between_corners (box, 0, 7); -} + for (int i = 0; i < 6; ++i) { + if (!(Box3D::int_to_face(z_orders[i]) & axis)) { + if (pos1 == -1) { + pos1 = i; + } else { + pos2 = i; + break; + } + } + } -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); + int tmp = z_orders[pos1]; + z_orders[pos1] = z_orders[pos2]; + z_orders[pos2] = tmp; } -// 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 -sp_3dbox_get_midpoint_between_corners (SP3DBox *box, guint id_corner1, guint id_corner2) -{ - Box3D::Axis corner_axes = (Box3D::Axis) (id_corner1 ^ id_corner2); - // Is all this sufficiently precise also for degenerate cases? - if (sp_3dbox_corners_are_adjacent (id_corner1, id_corner2)) { - Box3D::Axis orth_dir = get_perpendicular_axis_direction (corner_axes); +bool +box3d_recompute_z_orders (SPBox3D *box) { + Persp3D *persp = box->persp_ref->getObject(); - Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2 ^ orth_dir]); - Box3D::Line diag2 (box->corners[id_corner1 ^ orth_dir], box->corners[id_corner2]); - NR::Maybe adjacent_face_center = diag1.intersect(diag2); + //g_return_val_if_fail(persp, false); + if (!persp) + return false; - if (!adjacent_face_center) return NR::Nothing(); + int z_orders[6]; - Box3D::Perspective3D * persp = SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box); + NR::Point c3(box3d_get_corner_screen(box, 3)); - Box3D::PerspectiveLine pl (*adjacent_face_center, orth_dir, persp); - return pl.intersect(Box3D::PerspectiveLine(box->corners[id_corner1], corner_axes, persp)); - } else { - Box3D::Axis dir = Box3D::extract_first_axis_direction (corner_axes); - Box3D::Line diag1 (box->corners[id_corner1], box->corners[id_corner2]); - Box3D::Line diag2 (box->corners[id_corner1 ^ dir], box->corners[id_corner2 ^ dir]); - return diag1.intersect(diag2); + // determine directions from corner3 to the VPs + int num_finite = 0; + Box3D::Axis axis_finite = Box3D::NONE; + Box3D::Axis axis_infinite = Box3D::NONE; + NR::Point dirs[3]; + for (int i = 0; i < 3; ++i) { + dirs[i] = persp3d_get_PL_dir_from_pt(persp, c3, Box3D::toProj(Box3D::axes[i])); + if (persp3d_VP_is_finite(persp, Proj::axes[i])) { + num_finite++; + axis_finite = Box3D::axes[i]; + } else { + axis_infinite = Box3D::axes[i]; + } } -} - -static gchar * -sp_3dbox_get_corner_coords_string (SP3DBox *box, guint id) -{ - id = id % 8; - Inkscape::SVGOStringStream os; - os << box->corners[id][NR::X] << "," << box->corners[id][NR::Y]; - return g_strdup(os.str().c_str()); -} -static std::pair -sp_3dbox_get_coord_pair_from_string (const gchar *coords) -{ - gchar **coordpair = g_strsplit( coords, ",", 0); - // We might as well rely on g_ascii_strtod to convert the NULL pointer to 0.0, - // but we include the following test anyway - if (coordpair[0] == NULL || coordpair[1] == NULL) { - g_strfreev (coordpair); - g_warning ("Coordinate conversion failed.\n"); - return std::make_pair(0.0, 0.0); + // determine the "central" axis (if there is one) + Box3D::Axis central_axis = Box3D::NONE; + if(Box3D::lies_in_sector(dirs[0], dirs[1], dirs[2])) { + central_axis = Box3D::Z; + } else if(Box3D::lies_in_sector(dirs[1], dirs[2], dirs[0])) { + central_axis = Box3D::X; + } else if(Box3D::lies_in_sector(dirs[2], dirs[0], dirs[1])) { + central_axis = Box3D::Y; } - gdouble coord1 = g_ascii_strtod(coordpair[0], NULL); - gdouble coord2 = g_ascii_strtod(coordpair[1], NULL); - g_strfreev (coordpair); + switch (num_finite) { + case 0: + // TODO: Remark: In this case (and maybe one of the others, too) the z-orders for all boxes + // coincide, hence only need to be computed once in a more central location. + box3d_set_new_z_orders_case0(box, z_orders, central_axis); + break; + case 1: + box3d_set_new_z_orders_case1(box, z_orders, central_axis, axis_finite); + break; + case 2: + case 3: + box3d_set_new_z_orders_case2(box, z_orders, central_axis, axis_infinite); + break; + default: + /* + * For each VP F, check wether the half-line from the corner3 to F crosses the line segment + * joining the other two VPs. If this is the case, it determines the "central" corner from + * which the visible sides can be deduced. Otherwise, corner3 is the central corner. + */ + // FIXME: We should eliminate the use of NR::Point altogether + Box3D::Axis central_axis = Box3D::NONE; + NR::Point vp_x = persp3d_get_VP(persp, Proj::X).affine(); + NR::Point vp_y = persp3d_get_VP(persp, Proj::Y).affine(); + NR::Point vp_z = persp3d_get_VP(persp, Proj::Z).affine(); + Geom::Point vpx(vp_x[NR::X], vp_x[NR::Y]); + Geom::Point vpy(vp_y[NR::X], vp_y[NR::Y]); + Geom::Point vpz(vp_z[NR::X], vp_z[NR::Y]); + + NR::Point c3 = box3d_get_corner_screen(box, 3); + Geom::Point corner3(c3[NR::X], c3[NR::Y]); + + if (box3d_half_line_crosses_joining_line (corner3, vpx, vpy, vpz)) { + central_axis = Box3D::X; + } else if (box3d_half_line_crosses_joining_line (corner3, vpy, vpz, vpx)) { + central_axis = Box3D::Y; + } else if (box3d_half_line_crosses_joining_line (corner3, vpz, vpx, vpy)) { + central_axis = Box3D::Z; + } + //g_print ("Crossing: %s\n", Box3D::string_from_axes(central_axis)); - return std::make_pair(coord1, coord2); -} + unsigned int central_corner = 3 ^ central_axis; + if (central_axis == Box3D::Z) { + central_corner = central_corner ^ Box3D::XYZ; + } + if (box3d_XY_axes_are_swapped(box)) { + //g_print ("Axes X and Y are swapped\n"); + central_corner = central_corner ^ Box3D::XYZ; + } -static gchar * -sp_3dbox_get_perspective_string (SP3DBox *box) -{ - - return sp_3dbox_get_svg_descr_of_persp (SP_OBJECT_DOCUMENT (G_OBJECT (box))->get_persp_of_box (box)); -} - -gchar * -sp_3dbox_get_svg_descr_of_persp (Box3D::Perspective3D *persp) -{ - // FIXME: We should move this code to perspective3d.cpp, but this yields compiler errors. Why? - Inkscape::SVGOStringStream os; - - Box3D::VanishingPoint vp = *(persp->get_vanishing_point (Box3D::X)); - os << vp[NR::X] << "," << vp[NR::Y] << ","; - os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ","; - if (vp.is_finite()) { - os << "finite,"; - } else { - os << "infinite,"; + NR::Point c1(box3d_get_corner_screen(box, 1)); + NR::Point c2(box3d_get_corner_screen(box, 2)); + NR::Point c7(box3d_get_corner_screen(box, 7)); + + Geom::Point corner1(c1[NR::X], c1[NR::Y]); + Geom::Point corner2(c2[NR::X], c2[NR::Y]); + Geom::Point corner7(c7[NR::X], c7[NR::Y]); + // FIXME: At present we don't use the information about central_corner computed above. + switch (central_axis) { + case Box3D::Y: + if (!box3d_half_line_crosses_joining_line(vpz, vpy, corner3, corner2)) { + box3d_aux_set_z_orders (z_orders, 2, 3, 1, 5, 0, 4); + } else { + // degenerate case + //g_print ("Degenerate case #1\n"); + box3d_aux_set_z_orders (z_orders, 2, 1, 3, 0, 5, 4); + } + break; + + case Box3D::Z: + if (box3d_half_line_crosses_joining_line(vpx, vpz, corner3, corner1)) { + // degenerate case + //g_print ("Degenerate case #2\n"); + box3d_aux_set_z_orders (z_orders, 2, 0, 1, 4, 3, 5); + } else if (box3d_half_line_crosses_joining_line(vpx, vpy, corner3, corner7)) { + // degenerate case + //g_print ("Degenerate case #3\n"); + box3d_aux_set_z_orders (z_orders, 2, 1, 0, 5, 3, 4); + } else { + box3d_aux_set_z_orders (z_orders, 2, 1, 0, 3, 4, 5); + } + break; + + case Box3D::X: + if (box3d_half_line_crosses_joining_line(vpz, vpx, corner3, corner1)) { + // degenerate case + //g_print ("Degenerate case #4\n"); + box3d_aux_set_z_orders (z_orders, 2, 1, 0, 4, 5, 3); + } else { + box3d_aux_set_z_orders (z_orders, 2, 4, 0, 5, 1, 3); + } + break; + + case Box3D::NONE: + box3d_aux_set_z_orders (z_orders, 2, 3, 4, 1, 0, 5); + break; + + default: + g_assert_not_reached(); + break; + } // end default case } - vp = *(persp->get_vanishing_point (Box3D::Y)); - os << vp[NR::X] << "," << vp[NR::Y] << ","; - os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ","; - if (vp.is_finite()) { - os << "finite,"; - } else { - os << "infinite,"; + // TODO: If there are still errors in z-orders of everted boxes, we need to choose a variable corner + // instead of the hard-coded corner #3 in the computations above + Box3D::Axis ev = box3d_everted_directions(box); + for (int i = 0; i < 3; ++i) { + if (ev & Box3D::axes[i]) { + box3d_swap_sides(z_orders, Box3D::axes[i]); + } } - vp = *(persp->get_vanishing_point (Box3D::Z)); - os << vp[NR::X] << "," << vp[NR::Y] << ","; - os << vp.v_dir[NR::X] << "," << vp.v_dir[NR::Y] << ","; - if (vp.is_finite()) { - os << "finite"; - } else { - os << "infinite"; + // Check whether anything actually changed + for (int i = 0; i < 6; ++i) { + if (box->z_orders[i] != z_orders[i]) { + for (int j = i; j < 6; ++j) { + box->z_orders[j] = z_orders[j]; + } + return true; + } } - - return g_strdup(os.str().c_str()); + return false; } -// 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)); - } - if (cr1 == NR_HUGE) { - return std::make_pair (A, B); +static std::map +box3d_get_sides (SPBox3D *box) { + std::map sides; + for (SPObject *side = sp_object_first_child(box); side != NULL; side = SP_OBJECT_NEXT(side)) { + sides[Box3D::face_to_int(sp_repr_get_int_attribute(SP_OBJECT_REPR(side), + "inkscape:box3dsidetype", -1))] = SP_BOX3D_SIDE(side); } - 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); + sides.erase(-1); + return sides; } -void sp_3dbox_recompute_Z_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; - - Box3D::PerspectiveLine aux_line1 (old_center, Box3D::Z, persp); - Box3D::PerspectiveLine aux_line2 (new_center, Box3D::Y, persp); - NR::Point Z1 = aux_line1.meet (aux_line2); - - 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_corner7, box->old_corner5, Box3D::Y, persp)); - Box3D::PerspectiveLine aux_line3 (A0, Box3D::X, persp); - Box3D::PerspectiveLine aux_line4 (B0, Box3D::X, persp); - - NR::Point C0 = aux_line3.meet (aux_line1); - NR::Point D0 = aux_line4.meet (aux_line1); - - std::pair new_midpts = sp_3dbox_new_midpoints (persp, Box3D::Z, old_center, Z1, C0, D0); - NR::Point C1 (new_midpts.first); - NR::Point D1 (new_midpts.second); - Box3D::PerspectiveLine aux_line5 (C1, Box3D::X, persp); - Box3D::PerspectiveLine aux_line6 (D1, Box3D::X, persp); - - Box3D::PerspectiveLine aux_line7 (A0, Box3D::Z, persp); - Box3D::PerspectiveLine aux_line8 (B0, Box3D::Z, persp); - - NR::Point A1 = aux_line5.meet (aux_line7); - NR::Point B1 = aux_line6.meet (aux_line8); - - Box3D::PerspectiveLine aux_line9 (box->old_corner2, Box3D::Z, persp); - Box3D::PerspectiveLine aux_line10 (box->old_corner5, Box3D::Z, persp); - - Box3D::PerspectiveLine aux_line11 (A1, Box3D::Y, persp); - Box3D::PerspectiveLine aux_line12 (B1, Box3D::Y, persp); - - NR::Point new_corner2 = aux_line9.meet (aux_line11); - NR::Point new_corner5 = aux_line10.meet (aux_line12); - - Box3D::PerspectiveLine aux_line13 (A1, Box3D::X, persp); - NR::Point E1 = aux_line13.meet (aux_line8); - Box3D::PerspectiveLine aux_line14 (E1, Box3D::Y, persp); - - NR::Point new_corner1 = aux_line10.meet (aux_line14); - - sp_3dbox_set_shape_from_points (box, new_corner2, new_corner1, new_corner5); -} - -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); - - 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(); - if (!SP_IS_3DBOX_CONTEXT (ec)) - return; - - SP_3DBOX_CONTEXT (ec)->_vpdrag->updateLines(); +// TODO: Check whether the box is everted in any direction and swap the sides opposite to this direction +void +box3d_set_z_orders (SPBox3D *box) { + // For efficiency reasons, we only set the new z-orders if something really changed + if (box3d_recompute_z_orders (box)) { + std::map sides = box3d_get_sides(box); + std::map::iterator side; + for (unsigned int i = 0; i < 6; ++i) { + side = sides.find(box->z_orders[i]); + if (side != sides.end()) { + SP_ITEM((*side).second)->lowerToBottom(); + } + } + /** + g_print ("Resetting z-orders: "); + for (int i = 0; i < 6; ++i) { + g_print ("%d ", box->z_orders[i]); + } + g_print ("\n"); + **/ + } } /* - * Manipulates corner1 through corner4 to contain the indices of the corners - * from which the perspective lines in the direction of 'axis' emerge + * Auxiliary function for z-order recomputing: + * Determines whether \a pt lies in the sector formed by the two PLs from the corners with IDs + * \a i21 and \a id2 to the VP in direction \a axis. If the VP is infinite, we say that \a pt + * lies in the sector if it lies between the two (parallel) PLs. + * \ret * 0 if \a pt doesn't lie in the sector + * * 1 if \a pt lies in the sector and either VP is finite of VP is infinite and the direction + * from the edge between the two corners to \a pt points towards the VP + * * -1 otherwise */ -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; +// TODO: Maybe it would be useful to have a similar method for projective points pt because then we +// can use it for VPs and perhaps merge the case distinctions during z-order recomputation. +int +box3d_pt_lies_in_PL_sector (SPBox3D const *box, NR::Point const &pt, int id1, int id2, Box3D::Axis axis) { + Persp3D *persp = box->persp_ref->getObject(); + + // the two corners + NR::Point c1(box3d_get_corner_screen(box, id1)); + NR::Point c2(box3d_get_corner_screen(box, id2)); + + int ret = 0; + if (persp3d_VP_is_finite(persp, Box3D::toProj(axis))) { + NR::Point vp(persp3d_get_VP(persp, Box3D::toProj(axis)).affine()); + NR::Point v1(c1 - vp); + NR::Point v2(c2 - vp); + NR::Point w(pt - vp); + ret = static_cast(Box3D::lies_in_sector(v1, v2, w)); + //g_print ("Case 0 - returning %d\n", ret); } else { - switch_axis = (box->front_bits & axis) ? Box3D::X : Box3D::NONE; + Box3D::PerspectiveLine pl1(c1, Box3D::toProj(axis), persp); + Box3D::PerspectiveLine pl2(c2, Box3D::toProj(axis), persp); + if (pl1.lie_on_same_side(pt, c2) && pl2.lie_on_same_side(pt, c1)) { + // test whether pt lies "towards" or "away from" the VP + Box3D::Line edge(c1,c2); + NR::Point c3(box3d_get_corner_screen(box, id1 ^ axis)); + if (edge.lie_on_same_side(pt, c3)) { + ret = 1; + } else { + ret = -1; + } + } + //g_print ("Case 1 - returning %d\n", ret); } - - 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; - default: - // do nothing - break; - } + return ret; } -/** - * 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 = SP_OBJECT_DOCUMENT (G_OBJECT (box))->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)); - } +int +box3d_VP_lies_in_PL_sector (SPBox3D const *box, Proj::Axis vpdir, int id1, int id2, Box3D::Axis axis) { + Persp3D *persp = box->persp_ref->getObject(); - if (rel_pos == Box3D::FRONT) { - return result; + if (!persp3d_VP_is_finite(persp, vpdir)) { + return 0; } else { - return result ^ axis; + return box3d_pt_lies_in_PL_sector(box, persp3d_get_VP(persp, vpdir).affine(), id1, id2, 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)]; -} - -guint -sp_3dbox_get_front_corner_id (const SP3DBox *box) -{ - guint front_corner = 1; // this could in fact be any corner, but we choose the one that is normally in front - front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::X, Box3D::FRONT); - front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Y, Box3D::FRONT); - front_corner = sp_3dbox_get_corner_id_along_edge (box, front_corner, Box3D::Z, Box3D::FRONT); - return front_corner; -} - -// auxiliary functions +/* swap the coordinates of corner0 and corner7 along the specified axis */ static void -sp_3dbox_update_corner_with_value_from_svg (SPObject *object, guint corner_id, const gchar *value) -{ - if (value == NULL) return; - SP3DBox *box = SP_3DBOX(object); - - std::pair coord_pair = sp_3dbox_get_coord_pair_from_string (value); - box->corners[corner_id] = NR::Point (coord_pair.first, coord_pair.second); - sp_3dbox_recompute_corners (box, box->corners[2], box->corners[1], box->corners[5]); - object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); -} - -static void -sp_3dbox_update_perspective (Box3D::Perspective3D *persp, const gchar *value) -{ - // WARNING! This function changes the perspective associated to 'box'. Since there may be - // many other boxes linked to the same perspective, their perspective is also changed. - // If this behaviour is not desired in all cases, we need a different function. - if (value == NULL) return; - - gchar **vps = g_strsplit( value, ",", 0); - for (int i = 0; i < 15; ++i) { - if (vps[i] == NULL) { - g_warning ("Malformed svg attribute 'perspective'\n"); - return; - } +box3d_swap_coords(SPBox3D *box, Proj::Axis axis, bool smaller = true) { + box->orig_corner0.normalize(); + box->orig_corner7.normalize(); + if ((box->orig_corner0[axis] < box->orig_corner7[axis]) != smaller) { + double tmp = box->orig_corner0[axis]; + box->orig_corner0[axis] = box->orig_corner7[axis]; + box->orig_corner7[axis] = tmp; } + // FIXME: Should we also swap the coordinates of save_corner0 and save_corner7? +} - persp->set_vanishing_point (Box3D::X, g_ascii_strtod (vps[0], NULL), g_ascii_strtod (vps[1], NULL), - g_ascii_strtod (vps[2], NULL), g_ascii_strtod (vps[3], NULL), - strcmp (vps[4], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE); - persp->set_vanishing_point (Box3D::Y, g_ascii_strtod (vps[5], NULL), g_ascii_strtod (vps[6], NULL), - g_ascii_strtod (vps[7], NULL), g_ascii_strtod (vps[8], NULL), - strcmp (vps[9], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE); - persp->set_vanishing_point (Box3D::Z, g_ascii_strtod (vps[10], NULL), g_ascii_strtod (vps[11], NULL), - g_ascii_strtod (vps[12], NULL), g_ascii_strtod (vps[13], NULL), - strcmp (vps[14], "finite") == 0 ? Box3D::VP_FINITE : Box3D::VP_INFINITE); - - // update the other boxes linked to the same perspective - persp->reshape_boxes (Box3D::XYZ); +/* ensure that the coordinates of corner0 and corner7 are in the correct order (to prevent everted boxes) */ +void +box3d_relabel_corners(SPBox3D *box) { + box3d_swap_coords(box, Proj::X, false); + box3d_swap_coords(box, Proj::Y, false); + box3d_swap_coords(box, Proj::Z, true); } /* diff --git a/src/box3d.h b/src/box3d.h index 1e567ded9..c676696e9 100644 --- a/src/box3d.h +++ b/src/box3d.h @@ -1,5 +1,5 @@ -#ifndef __SP_3DBOX_H__ -#define __SP_3DBOX_H__ +#ifndef __SP_BOX3D_H__ +#define __SP_BOX3D_H__ /* * SVG implementation @@ -15,90 +15,71 @@ * Released under GNU GPL, read the file 'COPYING' for more information */ -#include "inkscape.h" -#include "perspective-line.h" - #include "sp-item-group.h" -#include "sp-path.h" -#include "xml/document.h" -#include "xml/repr.h" -#include "line-geometry.h" -#include "box3d-face.h" - +#include "proj_pt.h" +#include "axis-manip.h" -#define SP_TYPE_3DBOX (sp_3dbox_get_type ()) -#define SP_3DBOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_3DBOX, SP3DBox)) -#define SP_3DBOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_3DBOX, SP3DBoxClass)) -#define SP_IS_3DBOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_3DBOX)) -#define SP_IS_3DBOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_3DBOX)) +#define SP_TYPE_BOX3D (box3d_get_type ()) +#define SP_BOX3D(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_BOX3D, SPBox3D)) +#define SP_BOX3D_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_BOX3D, Box3DClass)) +#define SP_IS_BOX3D(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_BOX3D)) +#define SP_IS_BOX3D_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_BOX3D)) +class Box3DSide; +class Persp3D; +class Persp3DReference; -struct SP3DBox : public SPGroup { - NR::Point corners[8]; - Box3DFace *faces[6]; +struct SPBox3D : public SPGroup { gint z_orders[6]; // z_orders[i] holds the ID of the face at position #i in the group (from top to bottom) - std::vector currently_visible_faces; + gchar *persp_href; + Persp3DReference *persp_ref; - // TODO: Keeping/updating the ratios works reasonably well but is still an ad hoc implementation. - // Use a mathematically correct model to update the boxes. - double ratio_x; - double ratio_y; - double ratio_z; + sigc::connection modified_connection; - guint front_bits; /* used internally to determine which of two parallel faces is supposed to be the front face */ + Proj::Pt3 orig_corner0; + Proj::Pt3 orig_corner7; - // 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) - // Also, it may be better not to store the old corners but rather the old lines to which we want to snap - NR::Point old_center; - NR::Point old_corner2; - NR::Point old_corner1; - NR::Point old_corner0; - NR::Point old_corner3; - NR::Point old_corner5; - NR::Point old_corner7; + Proj::Pt3 save_corner0; + Proj::Pt3 save_corner7; - gint my_counter; // for testing only + gint my_counter; // for debugging only }; -struct SP3DBoxClass { - SPGroupClass parent_class; +struct SPBox3DClass { + SPGroupClass parent_class; }; -GType sp_3dbox_get_type (void); - -void sp_3dbox_position_set (SP3DBoxContext &bc); -void sp_3dbox_set_shape(SP3DBox *box3d, bool use_previous_corners = false); -void sp_3dbox_recompute_corners (SP3DBox *box, NR::Point const pt1, NR::Point const pt2, NR::Point const pt3); -void sp_3dbox_set_z_orders_in_the_first_place (SP3DBox *box); -void sp_3dbox_set_z_orders_later_on (SP3DBox *box); -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_reshape_after_VP_rotation (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); -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); -void sp_3dbox_recompute_Z_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); -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); -guint sp_3dbox_get_front_corner_id (const SP3DBox *box); - - -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]; } -inline bool sp_3dbox_corners_are_adjacent (guint id_corner1, guint id_corner2) { - return Box3D::is_single_axis_direction ((Box3D::Axis) (id_corner1 ^ id_corner2)); -} - -#endif +GType box3d_get_type (void); + +void box3d_position_set (SPBox3D *box); +Proj::Pt3 box3d_get_proj_corner (SPBox3D const *box, guint id); +NR::Point box3d_get_corner_screen (SPBox3D const *box, guint id); +Proj::Pt3 box3d_get_proj_center (SPBox3D *box); +NR::Point box3d_get_center_screen (SPBox3D *box); + +void box3d_set_corner (SPBox3D *box, guint id, NR::Point const &new_pos, Box3D::Axis movement, bool constrained); +void box3d_set_center (SPBox3D *box, NR::Point const &new_pos, NR::Point const &old_pos, Box3D::Axis movement, bool constrained); +void box3d_corners_for_PLs (const SPBox3D * box, Proj::Axis axis, NR::Point &corner1, NR::Point &corner2, NR::Point &corner3, NR::Point &corner4); +bool box3d_recompute_z_orders (SPBox3D *box); +void box3d_set_z_orders (SPBox3D *box); + +int box3d_pt_lies_in_PL_sector (SPBox3D const *box, NR::Point const &pt, int id1, int id2, Box3D::Axis axis); +int box3d_VP_lies_in_PL_sector (SPBox3D const *box, Proj::Axis vpdir, int id1, int id2, Box3D::Axis axis); + +/* ensures that the coordinates of corner0 and corner7 are in the correct order (to prevent everted boxes) */ +void box3d_relabel_corners(SPBox3D *box); + + +#endif /* __SP_BOX3D_H__ */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/desktop-style.cpp b/src/desktop-style.cpp index 236130173..eaa4ee6a7 100644 --- a/src/desktop-style.cpp +++ b/src/desktop-style.cpp @@ -41,6 +41,7 @@ #include "desktop-style.h" #include "svg/svg-icc-color.h" +#include "box3d-side.h" /** * Set color on selection on desktop. @@ -165,9 +166,11 @@ sp_desktop_set_style(SPDesktop *desktop, SPCSSAttr *css, bool change, bool write sp_repr_css_change(inkscape_get_repr(INKSCAPE, "desktop"), css_write, "style"); for (const GSList *i = desktop->selection->itemList(); i != NULL; i = i->next) { /* last used styles for 3D box faces are stored separately */ - if (SP_IS_PATH (i->data)) { - const char * descr = SP_OBJECT_REPR (G_OBJECT (i->data))->attribute ("inkscape:box3dface"); + if (SP_IS_BOX3D_SIDE (i->data)) { + //const char * descr = SP_OBJECT_REPR (G_OBJECT (i->data))->attribute ("inkscape:box3dside"); + const char * descr = box3d_side_axes_string(SP_BOX3D_SIDE(i->data)); if (descr != NULL) { + g_print ("################ Box3DSide description found.\n"); gchar *style_grp = g_strconcat ("desktop.", descr, NULL); sp_repr_css_change(inkscape_get_repr(INKSCAPE, style_grp), css_write, "style"); g_free (style_grp); diff --git a/src/display/sp-canvas.cpp b/src/display/sp-canvas.cpp index efc4fd112..6fca902ce 100644 --- a/src/display/sp-canvas.cpp +++ b/src/display/sp-canvas.cpp @@ -2118,10 +2118,10 @@ sp_canvas_scroll_to (SPCanvas *canvas, double cx, double cy, unsigned int clear, /* 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)) { + if (SP_IS_BOX3D_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); + Box3DContext *bc = SP_BOX3D_CONTEXT (ec); bc->_vpdrag->updateLines(); } } diff --git a/src/document.cpp b/src/document.cpp index 0207fe597..4b7157609 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -53,13 +53,15 @@ #include "libavoid/router.h" #include "libnr/nr-rect.h" #include "sp-item-group.h" -#include "perspective3d.h" #include "profile-manager.h" +#include "persp3d.h" #include "display/nr-arena-item.h" #include "dialogs/rdf.h" +#include "transf_mat_3x4.h" + #define A4_WIDTH_STR "210mm" #define A4_HEIGHT_STR "297mm" @@ -100,21 +102,6 @@ SPDocument::SPDocument() { perspectives = NULL; - /* Create an initial perspective, make it current and append it to the list of existing perspectives */ - current_perspective = new Box3D::Perspective3D ( - // VP in x-direction - Box3D::VanishingPoint( NR::Point(-50.0, 600.0), - NR::Point( -1.0, 0.0), Box3D::VP_FINITE), - // VP in y-direction - Box3D::VanishingPoint( NR::Point(500.0,1000.0), - NR::Point( 0.0, 1.0), Box3D::VP_INFINITE), - // VP in z-direction - Box3D::VanishingPoint( NR::Point(700.0, 600.0), - NR::Point(sqrt(3.0),1.0), Box3D::VP_FINITE), - this); - - add_perspective (current_perspective); - p = new SPDocumentPrivate(); p->serial = next_serial++; @@ -213,65 +200,26 @@ SPDocument::~SPDocument() { //delete this->_whiteboard_session_manager; - current_perspective = NULL; - // TODO: Do we have to delete the perspectives? - /*** - for (GSList *i = perspectives; i != NULL; ++i) { - delete ((Box3D::Perspective3D *) i->data); - } - g_slist_free (perspectives); - ***/ } -void SPDocument::add_perspective (Box3D::Perspective3D * const persp) +void SPDocument::add_persp3d (Persp3D * const persp) { - // FIXME: Should we handle the case that the perspectives have equal VPs but are not identical? - // If so, we need to take care of relinking the boxes, etc. - if (persp == NULL || g_slist_find (perspectives, persp)) return; - perspectives = g_slist_prepend (perspectives, persp); -} - -void SPDocument::remove_perspective (Box3D::Perspective3D * const persp) -{ - if (persp == NULL || !g_slist_find (perspectives, persp)) return; - perspectives = g_slist_remove (perspectives, persp); -} - -// find an existing perspective whose VPs are equal to those of persp -Box3D::Perspective3D * SPDocument::find_perspective (const Box3D::Perspective3D * persp) -{ - for (GSList *p = perspectives; p != NULL; p = p->next) { - if (*((Box3D::Perspective3D *) p->data) == *persp) { - return ((Box3D::Perspective3D *) p->data); + SPDefs *defs = SP_ROOT(this->root)->defs; + for (SPObject *i = sp_object_first_child(SP_OBJECT(defs)); i != NULL; i = SP_OBJECT_NEXT(i) ) { + if (SP_IS_PERSP3D(i)) { + g_print ("Encountered a Persp3D in defs\n"); } } - return NULL; // perspective was not found -} -Box3D::Perspective3D * SPDocument::get_persp_of_box (const SP3DBox *box) -{ - for (GSList *p = perspectives; p != NULL; p = p->next) { - if (((Box3D::Perspective3D *) p->data)->has_box (box)) - return (Box3D::Perspective3D *) p->data; - } - g_warning ("Stray 3D box!\n"); - g_assert_not_reached(); + g_print ("Adding Persp3D to defs\n"); + persp3d_create_xml_element (this); } -Box3D::Perspective3D * SPDocument::get_persp_of_VP (const Box3D::VanishingPoint *vp) +void SPDocument::remove_persp3d (Persp3D * const persp) { - Box3D::Perspective3D *persp; - for (GSList *p = perspectives; p != NULL; p = p->next) { - persp = (Box3D::Perspective3D *) p->data; - // we compare the pointers, not the position/state of the VPs; is this correct? - if (persp->get_vanishing_point (Box3D::X) == vp || - persp->get_vanishing_point (Box3D::Y) == vp || - persp->get_vanishing_point (Box3D::Z) == vp) - return persp; - } - - g_warning ("Stray vanishing point!\n"); - g_assert_not_reached(); + // TODO: Delete the repr, maybe perform a check if any boxes are still linked to the perspective. + // Anything else? + g_print ("Please implement deletion of perspectives here.\n"); } unsigned long SPDocument::serial() const { @@ -397,6 +345,59 @@ sp_document_create(Inkscape::XML::Document *rdoc, inkscape_ref(); } + /* Create an initial perspective, make it current and append it to the list of existing perspectives */ + /*** + document->current_perspective = new Box3D::Perspective3D ( + // VP in x-direction + Box3D::VanishingPoint( NR::Point(-50.0, 600.0), + NR::Point( -1.0, 0.0), Box3D::VP_FINITE), + // VP in y-direction + Box3D::VanishingPoint( NR::Point(500.0,1000.0), + NR::Point( 0.0, 1.0), Box3D::VP_INFINITE), + // VP in z-direction + Box3D::VanishingPoint( NR::Point(700.0, 600.0), + NR::Point(sqrt(3.0),1.0), Box3D::VP_FINITE), + document); + + document->add_perspective (document->current_perspective); + ***/ + + // Remark: Here, we used to create a "currentpersp3d" element in the document defs. + // But this is probably a bad idea since we need to adapt it for every change of selection, which will + // completely clutter the undo history. Maybe rather save it to prefs on exit and re-read it on startup? + + Proj::Pt2 proj_vp_x = Proj::Pt2 (-50.0, 600.0, 1.0); + Proj::Pt2 proj_vp_y = Proj::Pt2 ( 0.0,1000.0, 0.0); + Proj::Pt2 proj_vp_z = Proj::Pt2 (700.0, 600.0, 1.0); + Proj::Pt2 proj_origin = Proj::Pt2 (300.0, 400.0, 1.0); + + document->current_persp3d = (Persp3D *) persp3d_create_xml_element (document); + Inkscape::XML::Node *repr = SP_OBJECT_REPR(document->current_persp3d); + + gchar *str = NULL; + str = proj_vp_x.coord_string(); + repr->setAttribute("inkscape:vp_x", str); + g_free (str); + str = proj_vp_y.coord_string(); + repr->setAttribute("inkscape:vp_y", str); + g_free (str); + str = proj_vp_z.coord_string(); + repr->setAttribute("inkscape:vp_z", str); + g_free (str); + str = proj_origin.coord_string(); + repr->setAttribute("inkscape:persp3d-origin", str); + g_free (str); + Inkscape::GC::release(repr); + + /*** + document->current_persp3d = (Persp3D *) sp_object_get_child_by_repr (SP_OBJECT(defs), repr); + g_assert (document->current_persp3d != NULL); + persp3d_update_with_point (document->current_persp3d, Proj::X, proj_vp_x); + persp3d_update_with_point (document->current_persp3d, Proj::Y, proj_vp_y); + persp3d_update_with_point (document->current_persp3d, Proj::Z, proj_vp_z); + persp3d_update_with_point (document->current_persp3d, Proj::W, proj_origin); + ***/ + sp_document_set_undo_sensitive(document, true); // reset undo key when selection changes, so that same-key actions on different objects are not coalesced diff --git a/src/document.h b/src/document.h index e1b405f18..6e2693aed 100644 --- a/src/document.h +++ b/src/document.h @@ -29,6 +29,7 @@ #include #include "verbs.h" #include +#include namespace Avoid { class Router; @@ -53,10 +54,10 @@ namespace Inkscape { } class SP3DBox; +class Persp3D; -namespace Box3D { - class Perspective3D; - class VanishingPoint; +namespace Proj { + class TransfMat3x4; } class SPDocumentPrivate; @@ -103,17 +104,12 @@ struct SPDocument : public Inkscape::GC::Managed<>, Avoid::Router *router; GSList *perspectives; - Box3D::Perspective3D *current_perspective; - // FIXME: Perspectives should be linked to the list of existing ones automatically in the constructor - // and removed in the destructor! - void add_perspective (Box3D::Perspective3D * const persp); - void remove_perspective (Box3D::Perspective3D * const persp); - /* find an existing perspective whose VPs are equal to those of persp */ - Box3D::Perspective3D * find_perspective (const Box3D::Perspective3D * persp); + Persp3D *current_persp3d; // "currently active" perspective (e.g., newly created boxes are attached to this one) + std::set persps_sel; // perspectives associated to currently selected boxes - Box3D::Perspective3D * get_persp_of_box (const SP3DBox *box); - Box3D::Perspective3D * get_persp_of_VP (const Box3D::VanishingPoint *vp); + void add_persp3d (Persp3D * const persp); + void remove_persp3d (Persp3D * const persp); sigc::connection connectModified(ModifiedSignal::slot_type slot); sigc::connection connectURISet(URISetSignal::slot_type slot); @@ -266,3 +262,14 @@ void sp_document_resized_signal_emit (SPDocument *doc, gdouble width, gdouble he unsigned int vacuum_document (SPDocument *document); #endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/gc-anchored.cpp b/src/gc-anchored.cpp index 3f4cfc12d..91055c968 100644 --- a/src/gc-anchored.cpp +++ b/src/gc-anchored.cpp @@ -72,6 +72,7 @@ void Anchored::anchor() const { void Anchored::release() const { Debug::EventTracker tracker(this); + g_return_if_fail(_anchor); if (!--_anchor->refcount) { _free_anchor(_anchor); _anchor = NULL; diff --git a/src/knotholder.cpp b/src/knotholder.cpp index 3c61e980b..161e779d1 100644 --- a/src/knotholder.cpp +++ b/src/knotholder.cpp @@ -238,7 +238,7 @@ static void knot_clicked_handler(SPKnot *knot, guint state, gpointer data) if (SP_IS_RECT(item)) object_verb = SP_VERB_CONTEXT_RECT; - else if (SP_IS_3DBOX(item)) + else if (SP_IS_BOX3D(item)) object_verb = SP_VERB_CONTEXT_3DBOX; else if (SP_IS_GENERICELLIPSE(item)) object_verb = SP_VERB_CONTEXT_ARC; @@ -293,7 +293,7 @@ static void knot_ungrabbed_handler(SPKnot */*knot*/, unsigned int /*state*/, SPK if (SP_IS_RECT(object)) object_verb = SP_VERB_CONTEXT_RECT; - else if (SP_IS_3DBOX(object)) + else if (SP_IS_BOX3D(object)) object_verb = SP_VERB_CONTEXT_3DBOX; else if (SP_IS_GENERICELLIPSE(object)) object_verb = SP_VERB_CONTEXT_ARC; diff --git a/src/knotholder.h b/src/knotholder.h index 18b6c4165..fd09c7b23 100644 --- a/src/knotholder.h +++ b/src/knotholder.h @@ -68,7 +68,7 @@ void sp_knot_holder_add_full(SPKnotHolder *knot_holder, GType sp_knot_holder_get_type(); -// For testing. What is the right way to update the knots from Perspective3D::reshape_boxes() ? +// FIXME: This is an ugly hack! What is the right way to update the knots from VPDrag::updateBoxHandles() ? void knotholder_update_knots(SPKnotHolder *knot_holder, SPItem *item); #define SP_TYPE_KNOT_HOLDER (sp_knot_holder_get_type()) diff --git a/src/line-geometry.cpp b/src/line-geometry.cpp index 872e9ed6b..d050ec458 100644 --- a/src/line-geometry.cpp +++ b/src/line-geometry.cpp @@ -13,11 +13,11 @@ #include "line-geometry.h" #include "inkscape.h" +#include "desktop.h" #include "desktop-style.h" #include "desktop-handles.h" #include "display/sp-canvas.h" #include "display/sodipodi-ctrl.h" -//#include "display/curve.cpp" namespace Box3D { @@ -178,57 +178,21 @@ 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) +NR::Maybe Line::intersection_with_viewbox (SPDesktop *desktop) { - 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) { - // We return NR_HUGE so that we can catch this case in the calling functions - return NR_HUGE; + 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(); } - 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 { - if (B == D) { - // catch this case so that the line BD below is non-degenerate - return 0; - } - 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) { - // We return NR_HUGE so that we can catch this case in the calling functions - 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)); + Line line (e.first, e.second); + return this->intersect (line); } void create_canvas_point(NR::Point const &pos, double size, guint32 rgba) diff --git a/src/line-geometry.h b/src/line-geometry.h index e678c4031..5e3152c03 100644 --- a/src/line-geometry.h +++ b/src/line-geometry.h @@ -17,7 +17,7 @@ #include "libnr/nr-maybe.h" #include "glib.h" #include "display/sp-ctrlline.h" -#include "vanishing-point.h" +#include "axis-manip.h" // FIXME: This is only for Box3D::epsilon; move that to a better location #include "document.h" #include "ui/view/view.h" @@ -36,7 +36,13 @@ 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); - friend NR::Point fourth_pt_with_given_cross_ratio (NR::Point const &A, NR::Point const &C, NR::Point const &D, double gamma); + NR::Maybe intersection_with_viewbox (SPDesktop *desktop); + inline bool lie_on_same_side (NR::Point const &A, NR::Point const &B) { + /* If A is a point in the plane and n is the normal vector of the line then + the sign of dot(A, n) specifies the half-plane in which A lies. + Thus A and B lie on the same side if the dot products have equal sign. */ + return ((NR::dot(A, normal) - d0) * (NR::dot(B, normal) - d0)) > 0; + } double lambda (NR::Point const pt); inline NR::Point point_from_lambda (double const lambda) { @@ -66,14 +72,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 ***/ +/*** For debugging 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); -/*** For testing purposes: Draw a line between the specified points ***/ +/*** For debugging purposes: Draw a line between the specified points ***/ void create_canvas_line(NR::Point const &p1, NR::Point const &p2, guint32 rgba = 0xff00007f); diff --git a/src/object-edit.cpp b/src/object-edit.cpp index 1ef02672f..9c8db0936 100644 --- a/src/object-edit.cpp +++ b/src/object-edit.cpp @@ -42,7 +42,6 @@ #include - #include "xml/repr.h" #include "isnan.h" @@ -50,8 +49,7 @@ #define sp_round(v,m) (((v) < 0.0) ? ((ceil((v) / (m) - 0.5)) * (m)) : ((floor((v) / (m) + 0.5)) * (m))) static SPKnotHolder *sp_rect_knot_holder(SPItem *item, SPDesktop *desktop); -//static -SPKnotHolder *sp_3dbox_knot_holder(SPItem *item, SPDesktop *desktop); +static SPKnotHolder *box3d_knot_holder(SPItem *item, SPDesktop *desktop); static SPKnotHolder *sp_arc_knot_holder(SPItem *item, SPDesktop *desktop); static SPKnotHolder *sp_star_knot_holder(SPItem *item, SPDesktop *desktop); static SPKnotHolder *sp_spiral_knot_holder(SPItem *item, SPDesktop *desktop); @@ -65,8 +63,8 @@ sp_item_knot_holder(SPItem *item, SPDesktop *desktop) { if (SP_IS_RECT(item)) { return sp_rect_knot_holder(item, desktop); - } else if (SP_IS_3DBOX(item)) { - return sp_3dbox_knot_holder(item, desktop); + } else if (SP_IS_BOX3D(item)) { + return box3d_knot_holder(item, desktop); } else if (SP_IS_ARC(item)) { return sp_arc_knot_holder(item, desktop); } else if (SP_IS_STAR(item)) { @@ -528,326 +526,204 @@ static SPKnotHolder *sp_rect_knot_holder(SPItem *item, SPDesktop *desktop) return knot_holder; } -/* 3D Box */ +/* Box3D (= the new 3D box structure) */ -static inline Box3D::Axis movement_axis_of_3dbox_corner (guint corner, guint state) +static NR::Point box3d_knot_get(SPItem *item, guint knot_id) { - // this function has the purpose to simplify a change in the resizing behaviour of boxes - switch (corner) { - case 0: - case 1: - case 2: - case 3: - return ((state & GDK_SHIFT_MASK) ? Box3D::Z : Box3D::XY); - case 4: - case 5: - case 6: - case 7: - return ((state & GDK_SHIFT_MASK) ? Box3D::XY : Box3D::Z); - } - return Box3D::NONE; -} - -/* - * To keep the snappoint from jumping randomly between the two lines when the mouse pointer is close to - * their intersection, we remember the last snapped line and keep snapping to this specific line as long - * as the distance from the intersection to the mouse pointer is less than remember_snap_threshold. - */ + g_assert(item != NULL); + SPBox3D *box = SP_BOX3D(item); -// Should we make the threshold settable in the preferences? -static double remember_snap_threshold = 30; -static guint remember_snap_index = 0; -static guint remember_snap_index_center = 0; + NR::Matrix const i2d (sp_item_i2d_affine (item)); + return box3d_get_corner_screen(box, knot_id) * i2d; +} -static NR::Point snap_knot_position_3dbox (SP3DBox *box, guint corner, Box3D::Axis direction, NR::Point const &origin, NR::Point const &p, guint /*state*/) +static void box3d_knot_set(SPItem *item, guint knot_id, NR::Point const &new_pos, NR::Point const &origin, guint state) { - SPDesktop * desktop = inkscape_active_desktop(); - Box3D::Perspective3D *persp = sp_desktop_document (desktop)->get_persp_of_box (box); - - if (is_single_axis_direction (direction)) return p; - - Box3D::Axis axis1 = Box3D::extract_first_axis_direction (direction); - Box3D::Axis axis2 = Box3D::extract_second_axis_direction (direction); - - NR::Matrix const i2d (sp_item_i2d_affine (SP_ITEM (box))); - NR::Point origin_dt = origin * i2d; - NR::Point p_dt = p * i2d; - - Box3D::PerspectiveLine pl1 (origin_dt, axis1, persp); - Box3D::PerspectiveLine pl2 (origin_dt, axis2, persp); - Box3D::Line diag1 (origin_dt, box->corners[corner ^ Box3D::XY]); - - int num_snap_lines = 3; - NR::Point snap_pts[num_snap_lines]; - - snap_pts[0] = pl1.closest_to (p_dt); - snap_pts[1] = pl2.closest_to (p_dt); - snap_pts[2] = diag1.closest_to (p_dt); - - gdouble const zoom = desktop->current_zoom(); - - double snap_dists[num_snap_lines]; - - for (int i = 0; i < num_snap_lines; ++i) { - snap_dists[i] = NR::L2 (snap_pts[i] - p_dt) * zoom; - } - - bool within_tolerance = true; - for (int i = 0; i < num_snap_lines; ++i) { - if (snap_dists[i] > remember_snap_threshold) { - within_tolerance = false; - break; - } - } - - int snap_index = -1; - double snap_dist = NR_HUGE; - for (int i = 0; i < num_snap_lines; ++i) { - if (snap_dists[i] < snap_dist) { - snap_index = i; - snap_dist = snap_dists[i]; - } - } + g_assert(item != NULL); + SPBox3D *box = SP_BOX3D(item); + NR::Matrix const i2d (sp_item_i2d_affine (item)); - if (within_tolerance) { - return snap_pts[remember_snap_index] * i2d.inverse(); + Box3D::Axis movement; + if ((knot_id < 4) != (state & GDK_SHIFT_MASK)) { + movement = Box3D::XY; } else { - remember_snap_index = snap_index; - return snap_pts[snap_index] * i2d.inverse(); + movement = Box3D::Z; } + + box3d_set_corner (box, knot_id, new_pos * i2d, movement, (state & GDK_CONTROL_MASK)); + box3d_set_z_orders(box); + box3d_position_set(box); } -static NR::Point snap_center_position_3dbox (SP3DBox *box, NR::Point const &origin, NR::Point const &p) +static NR::Point box3d_knot_center_get (SPItem *item) { - SPDesktop * desktop = inkscape_active_desktop(); - Box3D::Perspective3D *persp = sp_desktop_document (desktop)->get_persp_of_box (box); - - Box3D::Axis axis1 = Box3D::X; - Box3D::Axis axis2 = Box3D::Y; - - NR::Matrix const i2d (sp_item_i2d_affine (SP_ITEM (box))); - NR::Point origin_dt = origin * i2d; - NR::Point p_dt = p * i2d; - - Box3D::PerspectiveLine pl1 (origin_dt, axis1, persp); - Box3D::PerspectiveLine pl2 (origin_dt, axis2, persp); - NR::Point midpt1 = sp_3dbox_get_midpoint_in_axis_direction (box->old_corner1, box->old_corner5, Box3D::Z, persp); - NR::Point midpt2 = sp_3dbox_get_midpoint_in_axis_direction (box->old_corner3, box->old_corner7, Box3D::Z, persp); - Box3D::Line diag1 (origin_dt, midpt1); - Box3D::Line diag2 (origin_dt, midpt2); - - int num_snap_lines = 4; - NR::Point snap_pts[num_snap_lines]; - - // should we snap to the closest point or to the projection along perspective lines? - snap_pts[0] = pl1.closest_to (p_dt); - snap_pts[1] = pl2.closest_to (p_dt); - snap_pts[2] = diag1.closest_to (p_dt); - snap_pts[3] = diag2.closest_to (p_dt); - - gdouble const zoom = desktop->current_zoom(); - - double snap_dists[num_snap_lines]; - - for (int i = 0; i < num_snap_lines; ++i) { - snap_dists[i] = NR::L2 (snap_pts[i] - p_dt) * zoom; - } + NR::Matrix const i2d (sp_item_i2d_affine (item)); + return box3d_get_center_screen (SP_BOX3D(item)) * i2d; +} - bool within_tolerance = true; - for (int i = 0; i < num_snap_lines; ++i) { - if (snap_dists[i] > remember_snap_threshold) { - within_tolerance = false; - break; - } - } +static void box3d_knot_center_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) +{ + SPBox3D *box = SP_BOX3D(item); + NR::Matrix const i2d (sp_item_i2d_affine (item)); - int snap_index = -1; - double snap_dist = NR_HUGE; - for (int i = 0; i < num_snap_lines; ++i) { - if (snap_dists[i] < snap_dist) { - snap_index = i; - snap_dist = snap_dists[i]; - } - } + box3d_set_center (SP_BOX3D(item), new_pos * i2d, origin * i2d, !(state & GDK_SHIFT_MASK) ? Box3D::XY : Box3D::Z, + state & GDK_CONTROL_MASK); - if (within_tolerance) { - return snap_pts[remember_snap_index_center] * i2d.inverse(); - } else { - remember_snap_index_center = snap_index; - return snap_pts[snap_index] * i2d.inverse(); - } + box3d_set_z_orders(box); + box3d_position_set(box); } -static NR::Point sp_3dbox_knot_get(SPItem *item, guint knot_id) +static void box3d_knot0_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) { - g_assert(item != NULL); - SP3DBox *box = SP_3DBOX(item); - - NR::Matrix const i2d (sp_item_i2d_affine (item)); - return sp_3dbox_get_corner(box, knot_id) * i2d; + box3d_knot_set(item, 0, new_pos, origin, state); } -static void sp_3dbox_knot_set(SPItem *item, guint knot_id, NR::Point const &new_pos, NR::Point const &origin, guint state) +static void box3d_knot1_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) { - g_assert(item != NULL); - SP3DBox *box = SP_3DBOX(item); + box3d_knot_set(item, 1, new_pos, origin, state); +} - NR::Matrix const i2d (sp_item_i2d_affine (item)); - Box3D::Axis direction = movement_axis_of_3dbox_corner (knot_id, state); - if ((state & GDK_CONTROL_MASK) && !is_single_axis_direction (direction)) { - // snap if Ctrl is pressed and movement isn't already constrained to a single axis - NR::Point const s = snap_knot_position_3dbox (box, knot_id, direction, origin, new_pos, state); - sp_3dbox_move_corner_in_Z_direction (box, knot_id, s * i2d, false); - } else { - if (direction == Box3D::Z) { - sp_3dbox_move_corner_in_Z_direction (box, knot_id, new_pos * i2d, true); - } else { - sp_3dbox_move_corner_in_Z_direction (box, knot_id, new_pos * i2d, false); - } - } - sp_3dbox_update_curves (box); - sp_3dbox_set_ratios (box); - sp_3dbox_update_perspective_lines (); - sp_3dbox_set_z_orders_later_on (box); +static void box3d_knot2_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) +{ + box3d_knot_set(item, 2, new_pos, origin, state); } -static void sp_3dbox_knot_center_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) +static void box3d_knot3_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) { - SP3DBox *box = SP_3DBOX(item); + box3d_knot_set(item, 3, new_pos, origin, state); +} - NR::Matrix const i2d (sp_item_i2d_affine (item)); - NR::Point new_pt (new_pos); +static void box3d_knot4_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) +{ + box3d_knot_set(item, 4, new_pos, origin, state); +} - if ((state & GDK_CONTROL_MASK) && !(state & GDK_SHIFT_MASK)) { - // snap if Ctrl is pressed and movement isn't already constrained to a single axis - new_pt = snap_center_position_3dbox (box, origin, new_pos); - } +static void box3d_knot5_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) +{ + box3d_knot_set(item, 5, new_pos, origin, state); +} - if (state & GDK_SHIFT_MASK) { - sp_3dbox_recompute_Z_corners_from_new_center (box, new_pt * i2d); - } else { - sp_3dbox_recompute_XY_corners_from_new_center (box, new_pt * i2d); - } +static void box3d_knot6_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) +{ + box3d_knot_set(item, 6, new_pos, origin, state); +} - sp_3dbox_update_curves (box); - sp_3dbox_set_z_orders_later_on (box); +static void box3d_knot7_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) +{ + box3d_knot_set(item, 7, new_pos, origin, state); } -static NR::Point sp_3dbox_knot_center_get(SPItem *item) +static NR::Point box3d_knot0_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; + return box3d_knot_get(item, 0); } -static void sp_3dbox_knot0_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) +static NR::Point box3d_knot1_get(SPItem *item) { - sp_3dbox_knot_set(item, 0, new_pos, origin, state); + return box3d_knot_get(item, 1); } -static void sp_3dbox_knot1_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) +static NR::Point box3d_knot2_get(SPItem *item) { - sp_3dbox_knot_set(item, 1, new_pos, origin, state); + return box3d_knot_get(item, 2); } -static void sp_3dbox_knot2_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) +static NR::Point box3d_knot3_get(SPItem *item) { - sp_3dbox_knot_set(item, 2, new_pos, origin, state); + return box3d_knot_get(item, 3); } -static void sp_3dbox_knot3_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) +static NR::Point box3d_knot4_get(SPItem *item) { - sp_3dbox_knot_set(item, 3, new_pos, origin, state); + return box3d_knot_get(item, 4); } -static void sp_3dbox_knot4_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) +static NR::Point box3d_knot5_get(SPItem *item) { - sp_3dbox_knot_set(item, 4, new_pos, origin, state); + return box3d_knot_get(item, 5); } -static void sp_3dbox_knot5_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) +static NR::Point box3d_knot6_get(SPItem *item) { - sp_3dbox_knot_set(item, 5, new_pos, origin, state); + return box3d_knot_get(item, 6); } -static void sp_3dbox_knot6_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) +static NR::Point box3d_knot7_get(SPItem *item) { - sp_3dbox_knot_set(item, 6, new_pos, origin, state); + return box3d_knot_get(item, 7); } -static void sp_3dbox_knot7_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state) +static void box3d_knot_click(SPItem *item, guint state, guint id) { - sp_3dbox_knot_set(item, 7, new_pos, origin, state); + g_print ("Corner %d was clicked\n", id); } -static NR::Point sp_3dbox_knot0_get(SPItem *item) +static void box3d_knot0_click(SPItem *item, guint state) { - return sp_3dbox_knot_get(item, 0); + box3d_knot_click(item, state, 0); } -static NR::Point sp_3dbox_knot1_get(SPItem *item) +static void box3d_knot1_click(SPItem *item, guint state) { - return sp_3dbox_knot_get(item, 1); + box3d_knot_click(item, state, 1); } -static NR::Point sp_3dbox_knot2_get(SPItem *item) +static void box3d_knot2_click(SPItem *item, guint state) { - return sp_3dbox_knot_get(item, 2); + box3d_knot_click(item, state, 2); } -static NR::Point sp_3dbox_knot3_get(SPItem *item) +static void box3d_knot3_click(SPItem *item, guint state) { - return sp_3dbox_knot_get(item, 3); + box3d_knot_click(item, state, 3); } -static NR::Point sp_3dbox_knot4_get(SPItem *item) +static void box3d_knot4_click(SPItem *item, guint state) { - return sp_3dbox_knot_get(item, 4); + box3d_knot_click(item, state, 4); } -static NR::Point sp_3dbox_knot5_get(SPItem *item) +static void box3d_knot5_click(SPItem *item, guint state) { - return sp_3dbox_knot_get(item, 5); + box3d_knot_click(item, state, 5); } -static NR::Point sp_3dbox_knot6_get(SPItem *item) +static void box3d_knot6_click(SPItem *item, guint state) { - return sp_3dbox_knot_get(item, 6); + box3d_knot_click(item, state, 6); } -static NR::Point sp_3dbox_knot7_get(SPItem *item) +static void box3d_knot7_click(SPItem *item, guint state) { - return sp_3dbox_knot_get(item, 7); + box3d_knot_click(item, state, 7); } -//static SPKnotHolder * -sp_3dbox_knot_holder(SPItem *item, SPDesktop *desktop) +box3d_knot_holder(SPItem *item, SPDesktop *desktop) { g_assert(item != NULL); SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL); - sp_knot_holder_add(knot_holder, sp_3dbox_knot0_set, sp_3dbox_knot0_get, NULL, + sp_knot_holder_add(knot_holder, box3d_knot0_set, box3d_knot0_get, box3d_knot0_click, _("Resize box in X/Y direction; with Shift along the Z axis; with Ctrl to constrain to the directions of edges or diagonals")); - sp_knot_holder_add(knot_holder, sp_3dbox_knot1_set, sp_3dbox_knot1_get, NULL, + sp_knot_holder_add(knot_holder, box3d_knot1_set, box3d_knot1_get, box3d_knot1_click, _("Resize box in X/Y direction; with Shift along the Z axis; with Ctrl to constrain to the directions of edges or diagonals")); - sp_knot_holder_add(knot_holder, sp_3dbox_knot2_set, sp_3dbox_knot2_get, NULL, + sp_knot_holder_add(knot_holder, box3d_knot2_set, box3d_knot2_get, box3d_knot2_click, _("Resize box in X/Y direction; with Shift along the Z axis; with Ctrl to constrain to the directions of edges or diagonals")); - sp_knot_holder_add(knot_holder, sp_3dbox_knot3_set, sp_3dbox_knot3_get, NULL, + sp_knot_holder_add(knot_holder, box3d_knot3_set, box3d_knot3_get, box3d_knot3_click, _("Resize box in X/Y direction; with Shift along the Z axis; with Ctrl to constrain to the directions of edges or diagonals")); - sp_knot_holder_add(knot_holder, sp_3dbox_knot4_set, sp_3dbox_knot4_get, NULL, + sp_knot_holder_add(knot_holder, box3d_knot4_set, box3d_knot4_get, box3d_knot4_click, _("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_knot5_set, sp_3dbox_knot5_get, NULL, + sp_knot_holder_add(knot_holder, box3d_knot5_set, box3d_knot5_get, box3d_knot5_click, _("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_knot6_set, sp_3dbox_knot6_get, NULL, + sp_knot_holder_add(knot_holder, box3d_knot6_set, box3d_knot6_get, box3d_knot6_click, _("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, + sp_knot_holder_add(knot_holder, box3d_knot7_set, box3d_knot7_get, box3d_knot7_click, _("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_holder_add_full(knot_holder, box3d_knot_center_set, box3d_knot_center_get, NULL, SP_KNOT_SHAPE_CROSS, SP_KNOT_MODE_XOR,_("Move the box in perspective.")); sp_pat_knot_holder(item, knot_holder); @@ -855,6 +731,8 @@ sp_3dbox_knot_holder(SPItem *item, SPDesktop *desktop) return knot_holder; } + + /* SPArc */ /* diff --git a/src/pencil-context.cpp b/src/pencil-context.cpp index 32ea8aafa..1ee39d530 100644 --- a/src/pencil-context.cpp +++ b/src/pencil-context.cpp @@ -36,6 +36,9 @@ #include "libnr/n-art-bpath.h" #include "context-fns.h" #include "sp-namedview.h" +#include "xml/repr.h" +#include "document.h" +#include "desktop-style.h" static void sp_pencil_context_class_init(SPPencilContextClass *klass); static void sp_pencil_context_init(SPPencilContext *pc); @@ -223,6 +226,30 @@ pencil_handle_button_press(SPPencilContext *const pc, GdkEventButton const &beve break; default: /* Set first point of sequence */ + if (bevent.state & GDK_CONTROL_MASK) { + /* Create object */ + Inkscape::XML::Document *xml_doc = sp_document_repr_doc(desktop->doc()); + Inkscape::XML::Node *repr = xml_doc->createElement("svg:path"); + repr->setAttribute("sodipodi:type", "arc"); + SPItem *item = SP_ITEM(desktop->currentLayer()->appendChildRepr(repr)); + Inkscape::GC::release(repr); + NR::Matrix const i2d (sp_item_i2d_affine (item)); + NR::Point pp = p * i2d; + sp_repr_set_svg_double (repr, "sodipodi:cx", pp[NR::X]); + sp_repr_set_svg_double (repr, "sodipodi:cy", pp[NR::Y]); + sp_repr_set_int (repr, "sodipodi:rx", 10); + sp_repr_set_int (repr, "sodipodi:ry", 10); + + /* Set style */ + sp_desktop_apply_style_tool(desktop, repr, "tools.shapes.arc", false); + + item->updateRepr(); + desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating single point")); + sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_PENCIL, _("Create single point")); + ret = true; + break; + + } if (anchor) { p = anchor->dp; desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Continuing selected path")); @@ -255,6 +282,11 @@ pencil_handle_button_press(SPPencilContext *const pc, GdkEventButton const &beve static gint pencil_handle_motion_notify(SPPencilContext *const pc, GdkEventMotion const &mevent) { + if (mevent.state & GDK_CONTROL_MASK) { + // mouse was accidentally moved during Ctrl+click; + // ignore the motion and create a single point + return TRUE; + } gint ret = FALSE; SPDesktop *const dt = pc->desktop; @@ -359,7 +391,10 @@ pencil_handle_button_release(SPPencilContext *const pc, GdkEventButton const &re case SP_PENCIL_CONTEXT_IDLE: /* Releasing button in idle mode means single click */ /* We have already set up start point/anchor in button_press */ - pc->state = SP_PENCIL_CONTEXT_ADDLINE; + if (!(revent.state & GDK_CONTROL_MASK)) { + // Ctrl+click creates a single point so only set context in ADDLINE mode when Ctrl isn't pressed + pc->state = SP_PENCIL_CONTEXT_ADDLINE; + } ret = TRUE; break; case SP_PENCIL_CONTEXT_ADDLINE: diff --git a/src/persp3d-reference.cpp b/src/persp3d-reference.cpp new file mode 100644 index 000000000..cbd855c31 --- /dev/null +++ b/src/persp3d-reference.cpp @@ -0,0 +1,175 @@ +/* + * The reference corresponding to the inkscape:perspectiveID attribute + * + * Copyright (C) 2007 Johan Engelen + * Copyright (C) 2007 Maximilian Albert + * + * Released under GNU GPL, read the file 'COPYING' for more information. + */ + +#include "persp3d-reference.h" +#include "persp3d.h" +#include "uri.h" + +// for testing: +#include "xml/repr.h" +#include "box3d.h" + +static void persp3dreference_href_changed(SPObject *old_ref, SPObject *ref, Persp3DReference *persp3dref); +static void persp3dreference_delete_self(SPObject *deleted, Persp3DReference *persp3dref); +static void persp3dreference_source_modified(SPObject *iSource, guint flags, Persp3DReference *persp3dref); + +Persp3DReference::Persp3DReference(SPObject* i_owner) : URIReference(i_owner) +{ + owner=i_owner; + /** + if (owner) { + g_print ("Owner of newly created Persp3DReference is box #%d ", SP_BOX3D(owner)->my_counter); + g_print ("(no ID yet because we are calling from box3d_init()...\n"); + } + **/ + persp_href = NULL; + persp_repr = NULL; + persp = NULL; + _changed_connection = changedSignal().connect(sigc::bind(sigc::ptr_fun(persp3dreference_href_changed), this)); // listening to myself, this should be virtual instead +} + +Persp3DReference::~Persp3DReference(void) +{ + _changed_connection.disconnect(); // to do before unlinking + + quit_listening(); + unlink(); +} + +bool +Persp3DReference::_acceptObject(SPObject *obj) const +{ + return SP_IS_PERSP3D(obj); + /* effic: Don't bother making this an inline function: _acceptObject is a virtual function, + typically called from a context where the runtime type is not known at compile time. */ +} + +/*** +void +Persp3DReference::link(char *to) +{ + if ( to == NULL ) { + quit_listening(); + unlink(); + } else { + if ( !persp_href || ( strcmp(to, persp_href) != 0 ) ) { + g_free(persp_href); + persp_href = g_strdup(to); + try { + attach(Inkscape::URI(to)); + } catch (Inkscape::BadURIException &e) { + // TODO: Proper error handling as per + // http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing. + // + g_warning("%s", e.what()); + detach(); + } + } + } +} +***/ + +void +Persp3DReference::unlink(void) +{ + g_free(persp_href); + persp_href = NULL; + detach(); +} + +void +Persp3DReference::start_listening(Persp3D* to) +{ + if ( to == NULL ) { + return; + } + persp = to; + persp_repr = SP_OBJECT_REPR(to); + _delete_connection = to->connectDelete(sigc::bind(sigc::ptr_fun(&persp3dreference_delete_self), this)); + _modified_connection = to->connectModified(sigc::bind<2>(sigc::ptr_fun(&persp3dreference_source_modified), this)); + //box3d_start_listening_to_persp_change (SP_BOX3D(this->owner), to); +} + +void +Persp3DReference::quit_listening(void) +{ + if ( persp == NULL ) { + return; + } + _modified_connection.disconnect(); + _delete_connection.disconnect(); + persp_repr = NULL; + persp = NULL; +} + +static void +persp3dreference_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, Persp3DReference *persp3dref) +{ + //g_print ("persp3dreference_href_changed:\n"); + persp3dref->quit_listening(); + /** + if (SP_IS_PERSP3D(persp3dref->getObject())){ + g_print ("referenced object is a perspective\n"); + } else { + g_print ("referenced object is NOT a perspective!!!!\n"); + } + **/ + Persp3D *refobj = SP_PERSP3D(persp3dref->getObject()); + if ( refobj ) { + persp3dref->start_listening(refobj); + //g_print (" start listening to %s\n", SP_OBJECT_REPR(refobj)->attribute("id")); + } + + /** + if (persp3dref->owner) { + g_print ("Requesting display update of owner box #%d (%s) from persp3dreference_href_changed()\n", + SP_BOX3D(persp3dref->owner)->my_counter, + SP_OBJECT_REPR(persp3dref->owner)->attribute("id")); + } + **/ + persp3dref->owner->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +static void +persp3dreference_delete_self(SPObject */*deleted*/, Persp3DReference *persp3dref) +{ + g_print ("persp3dreference_delete_self; FIXME: Can we leave this to the parent URIReference?\n"); + if (persp3dref->owner) { + g_print ("Deleting box #%d (%s) (?) from Persp3DReference\n", + SP_BOX3D(persp3dref->owner)->my_counter, + SP_OBJECT_REPR(persp3dref->owner)->attribute("id")); + } + persp3dref->owner->deleteObject(); +} + +static void +persp3dreference_source_modified(SPObject *iSource, guint flags, Persp3DReference *persp3dref) +{ + /** + g_print ("persp3dreference_source_modified; FIXME: Can we leave this to the parent URIReference?\n"); + if (persp3dref->owner) { + g_print ("Requesting display update of box #%d (%s) from persp3dreference_source_modified\n", + SP_BOX3D(persp3dref->owner)->my_counter, + SP_OBJECT_REPR(persp3dref->owner)->attribute("id")); + } + **/ + persp3dref->owner->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/persp3d-reference.h b/src/persp3d-reference.h new file mode 100644 index 000000000..43b0e82b1 --- /dev/null +++ b/src/persp3d-reference.h @@ -0,0 +1,66 @@ +#ifndef SEEN_PERSP3D_REFERENCE_H +#define SEEN_PERSP3D_REFERENCE_H + +/* + * The reference corresponding to the inkscape:perspectiveID attribute + * + * Copyright (C) 2007 Johan Engelen + * Copyright (C) 2007 Maximilian Albert + * + * Released under GNU GPL, read the file 'COPYING' for more information. + */ + +#include "uri-references.h" +#include + +class SPObject; +class Persp3D; + +namespace Inkscape { +namespace XML { +struct Node; +} +} + +class Persp3DReference : public Inkscape::URIReference { +public: + Persp3DReference(SPObject *obj); + ~Persp3DReference(); + + Persp3D *getObject() const { + return (Persp3D *)URIReference::getObject(); + } + + SPObject *owner; + + // concerning the Persp3D (we only use SPBox3D) that is refered to: + gchar *persp_href; + Inkscape::XML::Node *persp_repr; + Persp3D *persp; + + sigc::connection _changed_connection; + sigc::connection _modified_connection; + sigc::connection _delete_connection; + + void link(char* to); + void unlink(void); + void start_listening(Persp3D* to); + void quit_listening(void); + +protected: + virtual bool _acceptObject(SPObject *obj) const; +}; + + +#endif /* !SEEN_PERSP3D_REFERENCE_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/persp3d.cpp b/src/persp3d.cpp new file mode 100644 index 000000000..3dafba30d --- /dev/null +++ b/src/persp3d.cpp @@ -0,0 +1,546 @@ +#define __PERSP3D_C__ + +/* + * Class modelling a 3D perspective as an SPObject + * + * Authors: + * Maximilian Albert + * + * Copyright (C) 2007 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "persp3d.h" +#include "perspective-line.h" +#include "attributes.h" +#include "document-private.h" +#include "vanishing-point.h" +#include "box3d-context.h" +#include "box3d.h" +#include "xml/document.h" +#include "xml/node-event-vector.h" +#include "desktop-handles.h" +#include + +static void persp3d_class_init(Persp3DClass *klass); +static void persp3d_init(Persp3D *stop); + +static void persp3d_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static void persp3d_release(SPObject *object); +static void persp3d_set(SPObject *object, unsigned key, gchar const *value); +static void persp3d_update(SPObject *object, SPCtx *ctx, guint flags); +static Inkscape::XML::Node *persp3d_write(SPObject *object, Inkscape::XML::Node *repr, guint flags); + +static void persp3d_on_repr_attr_changed (Inkscape::XML::Node * repr, const gchar *key, const gchar *oldval, const gchar *newval, bool is_interactive, void * data); + +static SPObjectClass *persp3d_parent_class; + +static int global_counter = 0; + +/** + * Registers Persp3d class and returns its type. + */ +GType +persp3d_get_type() +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(Persp3DClass), + NULL, NULL, + (GClassInitFunc) persp3d_class_init, + NULL, NULL, + sizeof(Persp3D), + 16, + (GInstanceInitFunc) persp3d_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_OBJECT, "Persp3D", &info, (GTypeFlags)0); + } + return type; +} + +static Inkscape::XML::NodeEventVector const persp3d_repr_events = { + NULL, /* child_added */ + NULL, /* child_removed */ + persp3d_on_repr_attr_changed, + NULL, /* content_changed */ + NULL /* order_changed */ +}; + +/** + * Callback to initialize Persp3D vtable. + */ +static void persp3d_class_init(Persp3DClass *klass) +{ + SPObjectClass *sp_object_class = (SPObjectClass *) klass; + + persp3d_parent_class = (SPObjectClass *) g_type_class_ref(SP_TYPE_OBJECT); + + sp_object_class->build = persp3d_build; + sp_object_class->release = persp3d_release; + sp_object_class->set = persp3d_set; + sp_object_class->update = persp3d_update; + sp_object_class->write = persp3d_write; +} + +/** + * Callback to initialize Persp3D object. + */ +static void +persp3d_init(Persp3D *persp) +{ + persp->tmat = Proj::TransfMat3x4 (); + + //persp->boxes = NULL; + persp->document = NULL; + + persp->my_counter = global_counter++; +} + +/** + * Virtual build: set persp3d attributes from its associated XML node. + */ +static void persp3d_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + if (((SPObjectClass *) persp3d_parent_class)->build) + (* ((SPObjectClass *) persp3d_parent_class)->build)(object, document, repr); + + /* calls sp_object_set for the respective attributes */ + // The transformation matrix is updated according to the values we read for the VPs + sp_object_read_attr(object, "inkscape:vp_x"); + sp_object_read_attr(object, "inkscape:vp_y"); + sp_object_read_attr(object, "inkscape:vp_z"); + sp_object_read_attr(object, "inkscape:persp3d-origin"); + + if (repr) { + repr->addListener (&persp3d_repr_events, object); + } + + // FIXME: What precisely does this do and is it necessary for perspectives? + /* Register ourselves */ + //sp_document_add_resource(document, "persp3d", object); +} + +/** + * Virtual release of Persp3D members before destruction. + */ +static void persp3d_release(SPObject *object) { + //Persp3D *persp = (Persp3D *) object; + + // FIXME: What precisely does this do and is it necessary for perspectives? + /** + if (SP_OBJECT_DOCUMENT(object)) { + // Unregister ourselves + sp_document_remove_resource(SP_OBJECT_DOCUMENT(object), "persp3d", SP_OBJECT(object)); + } + **/ +} + + +/** + * Virtual set: set attribute to value. + */ +// FIXME: Currently we only read the finite positions of vanishing points; +// should we move VPs into their own repr (as it's done for SPStop, e.g.)? +static void +persp3d_set(SPObject *object, unsigned key, gchar const *value) +{ + Persp3D *persp = SP_PERSP3D (object); + + switch (key) { + case SP_ATTR_INKSCAPE_PERSP3D_VP_X: { + if (value) { + Proj::Pt2 new_image (value); + persp3d_update_with_point (persp, Proj::X, new_image); + } + break; + } + case SP_ATTR_INKSCAPE_PERSP3D_VP_Y: { + if (value) { + Proj::Pt2 new_image (value); + persp3d_update_with_point (persp, Proj::Y, new_image); + break; + } + } + case SP_ATTR_INKSCAPE_PERSP3D_VP_Z: { + if (value) { + Proj::Pt2 new_image (value); + persp3d_update_with_point (persp, Proj::Z, new_image); + break; + } + } + case SP_ATTR_INKSCAPE_PERSP3D_ORIGIN: { + if (value) { + Proj::Pt2 new_image (value); + persp3d_update_with_point (persp, Proj::W, new_image); + break; + } + } + default: { + if (((SPObjectClass *) persp3d_parent_class)->set) + (* ((SPObjectClass *) persp3d_parent_class)->set)(object, key, value); + break; + } + } + + // FIXME: Is this the right place for resetting the draggers? + SPEventContext *ec = inkscape_active_event_context(); + if (SP_IS_BOX3D_CONTEXT(ec)) { + Box3DContext *bc = SP_BOX3D_CONTEXT(ec); + bc->_vpdrag->updateDraggers(); + bc->_vpdrag->updateLines(); + bc->_vpdrag->updateBoxHandles(); + bc->_vpdrag->updateBoxReprs(); + } +} + +static void +persp3d_update(SPObject *object, SPCtx *ctx, guint flags) +{ + if (flags & SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG) { + + /* TODO: Should we update anything here? */ + + } + + if (((SPObjectClass *) persp3d_parent_class)->update) + ((SPObjectClass *) persp3d_parent_class)->update(object, ctx, flags); +} + +Persp3D * +persp3d_create_xml_element (SPDocument *document, Persp3D *dup) {// if dup is given, copy the attributes over + SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document); + Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document); + Inkscape::XML::Node *repr; + if (dup) { + repr = SP_OBJECT_REPR(dup)->duplicate (xml_doc); + } else { + repr = xml_doc->createElement("inkscape:perspective"); + repr->setAttribute("sodipodi:type", "inkscape:persp3d"); + } + + /* Append the new persp3d to defs */ + SP_OBJECT_REPR(defs)->addChild(repr, NULL); + Inkscape::GC::release(repr); + + return (Persp3D *) sp_object_get_child_by_repr (SP_OBJECT(defs), repr); +} + +/** + * Virtual write: write object attributes to repr. + */ +static Inkscape::XML::Node * +persp3d_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPDocument *document = SP_OBJECT_DOCUMENT(object); + Persp3D *persp = SP_PERSP3D(object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = SP_OBJECT_REPR(persp3d_create_xml_element (document)); + } + + if (flags & SP_OBJECT_WRITE_EXT) { + gchar *str = NULL; // FIXME: Should this be freed each time we set an attribute or only in the end or at all? + str = persp3d_pt_to_str (persp, Proj::X); + repr->setAttribute("inkscape:vp_x", str); + + str = persp3d_pt_to_str (persp, Proj::Y); + repr->setAttribute("inkscape:vp_y", str); + + str = persp3d_pt_to_str (persp, Proj::Z); + repr->setAttribute("inkscape:vp_z", str); + + str = persp3d_pt_to_str (persp, Proj::W); + repr->setAttribute("inkscape:persp3d-origin", str); + } + + if (((SPObjectClass *) persp3d_parent_class)->write) + (* ((SPObjectClass *) persp3d_parent_class)->write)(object, repr, flags); + + return repr; +} + +/* convenience wrapper around persp3d_get_finite_dir() and persp3d_get_infinite_dir() */ +NR::Point persp3d_get_PL_dir_from_pt (Persp3D *persp, NR::Point const &pt, Proj::Axis axis) { + if (persp3d_VP_is_finite(persp, axis)) { + return persp3d_get_finite_dir(persp, pt, axis); + } else { + return persp3d_get_infinite_dir(persp, axis); + } +} + +NR::Point +persp3d_get_finite_dir (Persp3D *persp, NR::Point const &pt, Proj::Axis axis) { + Box3D::PerspectiveLine pl(pt, axis, persp); + return pl.direction(); +} + +NR::Point +persp3d_get_infinite_dir (Persp3D *persp, Proj::Axis axis) { + Proj::Pt2 vp(persp3d_get_VP(persp, axis)); + if (vp[2] != 0.0) { + g_print ("VP should be infinite but is (%f : %f : %f)\n", vp[0], vp[1], vp[2]); + g_return_val_if_fail(vp[2] != 0.0, NR::Point(0.0, 0.0)); + } + return NR::Point(vp[0], vp[1]); +} + +double +persp3d_get_infinite_angle (Persp3D *persp, Proj::Axis axis) { + return persp->tmat.get_infinite_angle(axis); +} + +bool +persp3d_VP_is_finite (Persp3D *persp, Proj::Axis axis) { + return persp->tmat.has_finite_image(axis); +} + +void +persp3d_toggle_VP (Persp3D *persp, Proj::Axis axis, bool set_undo) { + persp->tmat.toggle_finite(axis); + // FIXME: Remove this repr update and rely on vp_drag_sel_modified() to do this for us + // On the other hand, vp_drag_sel_modified() would update all boxes; + // here we can confine ourselves to the boxes of this particular perspective. + persp3d_update_box_reprs (persp); + persp3d_update_z_orders (persp); + SP_OBJECT(persp)->updateRepr(SP_OBJECT_WRITE_EXT); + if (set_undo) { + sp_document_done(sp_desktop_document(inkscape_active_desktop()), SP_VERB_CONTEXT_3DBOX, + _("Toggle vanishing point")); + } +} + +/* toggle VPs for the same axis in all perspectives of a given list */ +void +persp3d_toggle_VPs (std::set p, Proj::Axis axis) { + for (std::set::iterator i = p.begin(); i != p.end(); ++i) { + persp3d_toggle_VP((*i), axis, false); + } + sp_document_done(sp_desktop_document(inkscape_active_desktop()), SP_VERB_CONTEXT_3DBOX, + _("Toggle multiple vanishing points")); +} + +void +persp3d_set_VP_state (Persp3D *persp, Proj::Axis axis, Proj::VPState state) { + if (persp3d_VP_is_finite(persp, axis) != (state == Proj::FINITE)) { + persp3d_toggle_VP(persp, axis); + } +} + +void +persp3d_rotate_VP (Persp3D *persp, Proj::Axis axis, double angle, bool alt_pressed) { // angle is in degrees + // FIXME: Most of this functionality should be moved to trans_mat_3x4.(h|cpp) + if (persp->tmat.has_finite_image(axis)) { + // don't rotate anything for finite VPs + return; + } + Proj::Pt2 v_dir_proj (persp->tmat.column(axis)); + NR::Point v_dir (v_dir_proj[0], v_dir_proj[1]); + double a = NR::atan2 (v_dir) * 180/M_PI; + a += alt_pressed ? 0.5 * ((angle > 0 ) - (angle < 0)) : angle; // the r.h.s. yields +/-0.5 or angle + persp->tmat.set_infinite_direction (axis, a); + + persp3d_update_box_reprs (persp); + persp3d_update_z_orders (persp); + SP_OBJECT(persp)->updateRepr(SP_OBJECT_WRITE_EXT); +} + +void +persp3d_update_with_point (Persp3D *persp, Proj::Axis const axis, Proj::Pt2 const &new_image) { + persp->tmat.set_image_pt (axis, new_image); +} + +void +persp3d_apply_affine_transformation (Persp3D *persp, NR::Matrix const &xform) { + persp->tmat *= xform; + persp3d_update_box_reprs(persp); +} + +gchar * +persp3d_pt_to_str (Persp3D *persp, Proj::Axis const axis) +{ + return persp->tmat.pt_to_str(axis); +} + +void +persp3d_add_box (Persp3D *persp, SPBox3D *box) { + if (!box) { + //g_warning ("Trying to add NULL box to perspective.\n"); + return; + } + if (std::find (persp->boxes.begin(), persp->boxes.end(), box) != persp->boxes.end()) { + //g_warning ("Attempting to add already existent box to perspective.\n"); + return; + } + persp->boxes.push_back(box); + //SP_OBJECT_REPR(box)->setAttribute("inkscape:perspectiveID", SP_OBJECT_REPR(persp)->attribute("id")); +} + +void +persp3d_remove_box (Persp3D *persp, SPBox3D *box) { + std::vector::iterator i = std::find (persp->boxes.begin(), persp->boxes.end(), box); + if (i != persp->boxes.end()) { + persp->boxes.erase(i); + } +} + +bool +persp3d_has_box (Persp3D *persp, SPBox3D *box) { + // FIXME: For some reason, std::find() does not seem to compare pointers "correctly" (or do we need to + // provide a proper comparison function?), so we manually traverse the list. + for (std::vector::iterator i = persp->boxes.begin(); i != persp->boxes.end(); ++i) { + if ((*i) == box) { + return true; + } + } + return false; +} + +void +persp3d_update_box_displays (Persp3D *persp) { + if (persp->boxes.empty()) + return; + //g_print ("Requesting display update for %d boxes in the perspective.\n", persp->boxes.size()); + for (std::vector::iterator i = persp->boxes.begin(); i != persp->boxes.end(); ++i) { + box3d_position_set(*i); + } +} + +void +persp3d_update_box_reprs (Persp3D *persp) { + if (persp->boxes.empty()) + return; + //g_print ("Requesting repr update for %d boxes in the perspective.\n", persp->boxes.size()); + for (std::vector::iterator i = persp->boxes.begin(); i != persp->boxes.end(); ++i) { + SP_OBJECT(*i)->updateRepr(SP_OBJECT_WRITE_EXT); + } +} + +void +persp3d_update_z_orders (Persp3D *persp) { + if (persp->boxes.empty()) + return; + for (std::vector::iterator i = persp->boxes.begin(); i != persp->boxes.end(); ++i) { + box3d_set_z_orders(*i); + } +} + +// FIXME: For some reason we seem to require a vector instead of a list in Persp3D, but in vp_knot_moved_handler() +// we need a list of boxes. If we can store a list in Persp3D right from the start, this function becomes +// obsolete. We should do this. +std::list +persp3d_list_of_boxes(Persp3D *persp) { + std::list bx_lst; + for (std::vector::iterator i = persp->boxes.begin(); i != persp->boxes.end(); ++i) { + bx_lst.push_back(*i); + } + return bx_lst; +} + +bool +persp3d_perspectives_coincide(const Persp3D *lhs, const Persp3D *rhs) +{ + return lhs->tmat == rhs->tmat; +} + +void +persp3d_absorb(Persp3D *persp1, Persp3D *persp2) { + /* double check if we are called in sane situations */ + g_return_if_fail (persp3d_perspectives_coincide(persp1, persp2) && persp1 != persp2); + + std::vector::iterator boxes; + + // Note: We first need to copy the boxes of persp2 into a separate list; + // otherwise the loop below gets confused when perspectives are reattached. + std::list boxes_of_persp2 = persp3d_list_of_boxes(persp2); + + Inkscape::XML::Node *persp_repr = SP_OBJECT_REPR(persp1); + const gchar *persp_id = persp_repr->attribute("id"); + gchar *href = g_strdup_printf("#%s", persp_id); + + for (std::list::iterator i = boxes_of_persp2.begin(); i != boxes_of_persp2.end(); ++i) { + SP_OBJECT_REPR(*i)->setAttribute("inkscape:perspectiveID", href); + } + g_free(href); + + persp1->boxes.insert(persp1->boxes.begin(), persp2->boxes.begin(), persp2->boxes.end()); +} + +static void +persp3d_on_repr_attr_changed ( Inkscape::XML::Node * repr, + const gchar *key, + const gchar *oldval, + const gchar *newval, + bool is_interactive, + void * data ) +{ + //g_print("persp3d_on_repr_attr_changed!!!! TODO: Do we need to trigger any further updates than the box reprs?"); + + if (!data) + return; + + Persp3D *persp = (Persp3D*) data; + persp3d_update_box_displays (persp); + + //lpeobj->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +/* returns a std::set() of all perspectives of the currently selected boxes */ +std::set +persp3d_currently_selected (Box3DContext *bc) { + Inkscape::Selection *selection = sp_desktop_selection (bc->desktop); + + std::set p; + for (GSList *i = (GSList *) selection->itemList(); i != NULL; i = i->next) { + if (SP_IS_BOX3D (i->data)) { + p.insert(SP_BOX3D(i->data)->persp_ref->getObject()); + } + } + return p; +} + +void +persp3d_print_debugging_info (Persp3D *persp) { + g_print ("=== Info for Persp3D %d ===\n", persp->my_counter); + gchar * cstr; + for (int i = 0; i < 4; ++i) { + cstr = persp3d_get_VP(persp, Proj::axes[i]).coord_string(); + g_print (" VP %s: %s\n", Proj::string_from_axis(Proj::axes[i]), cstr); + g_free(cstr); + } + cstr = persp3d_get_VP(persp, Proj::W).coord_string(); + g_print (" Origin: %s\n", cstr); + g_free(cstr); + + g_print (" Boxes: "); + for (std::vector::iterator i = persp->boxes.begin(); i != persp->boxes.end(); ++i) { + g_print ("%d (%d)", (*i)->my_counter, (*i)->persp_ref->getObject()->my_counter); + } + g_print ("\n"); + g_print ("========================\n"); +} + +void +persp3d_print_debugging_info_all(SPDocument *document) { + SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document); + Inkscape::XML::Node *repr; + for (SPObject *child = sp_object_first_child(defs); child != NULL; child = SP_OBJECT_NEXT(child) ) { + repr = SP_OBJECT_REPR(child); + if (SP_IS_PERSP3D(child)) { + persp3d_print_debugging_info(SP_PERSP3D(child)); + } + } +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/persp3d.h b/src/persp3d.h new file mode 100644 index 000000000..934136ba2 --- /dev/null +++ b/src/persp3d.h @@ -0,0 +1,94 @@ +#ifndef __PERSP3D_H__ +#define __PERSP3D_H__ + +/* + * Implementation of 3D perspectives as SPObjects + * + * Authors: + * Maximilian Albert + * + * Copyright (C) 2007 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define SP_TYPE_PERSP3D (persp3d_get_type ()) +#define SP_PERSP3D(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_PERSP3D, Persp3D)) +#define SP_PERSP3D_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_PERSP3D, Persp3DClass)) +#define SP_IS_PERSP3D(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_PERSP3D)) +#define SP_IS_PERSP3D_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_PERSP3D)) + +#include +#include +#include "sp-item.h" +#include "transf_mat_3x4.h" + +class SPDocument; +class SPBox3D; +class Box3DContext; + +struct Persp3D : public SPObject { + Proj::TransfMat3x4 tmat; + + // TODO: Also write the list of boxes into the xml repr and vice versa link boxes to their persp3d? + std::vector boxes; + SPDocument *document; // FIXME: should this rather be the SPDesktop? + + // for debugging only + int my_counter; +}; + +struct Persp3DClass { + SPItemClass parent_class; +}; + + +/* Standard GType function */ +GType persp3d_get_type (void); + +// FIXME: Make more of these inline! +inline Proj::Pt2 persp3d_get_VP (Persp3D *persp, Proj::Axis axis) { return persp->tmat.column(axis); } +NR::Point persp3d_get_PL_dir_from_pt (Persp3D *persp, NR::Point const &pt, Proj::Axis axis); // convenience wrapper around the following two +NR::Point persp3d_get_finite_dir (Persp3D *persp, NR::Point const &pt, Proj::Axis axis); +NR::Point persp3d_get_infinite_dir (Persp3D *persp, Proj::Axis axis); +double persp3d_get_infinite_angle (Persp3D *persp, Proj::Axis axis); +bool persp3d_VP_is_finite (Persp3D *persp, Proj::Axis axis); +void persp3d_toggle_VP (Persp3D *persp, Proj::Axis axis, bool set_undo = true); +void persp3d_toggle_VPs (std::set, Proj::Axis axis); +void persp3d_set_VP_state (Persp3D *persp, Proj::Axis axis, Proj::VPState state); +void persp3d_rotate_VP (Persp3D *persp, Proj::Axis axis, double angle, bool alt_pressed); // angle is in degrees +void persp3d_update_with_point (Persp3D *persp, Proj::Axis const axis, Proj::Pt2 const &new_image); +void persp3d_apply_affine_transformation (Persp3D *persp, NR::Matrix const &xform); +gchar * persp3d_pt_to_str (Persp3D *persp, Proj::Axis const axis); + +void persp3d_add_box (Persp3D *persp, SPBox3D *box); +void persp3d_remove_box (Persp3D *persp, SPBox3D *box); +bool persp3d_has_box (Persp3D *persp, SPBox3D *box); +void persp3d_update_box_displays (Persp3D *persp); +void persp3d_update_box_reprs (Persp3D *persp); +void persp3d_update_z_orders (Persp3D *persp); +inline unsigned int persp3d_num_boxes (Persp3D *persp) { return persp->boxes.size(); } +std::list persp3d_list_of_boxes(Persp3D *persp); + +bool persp3d_perspectives_coincide(const Persp3D *lhs, const Persp3D *rhs); +void persp3d_absorb(Persp3D *persp1, Persp3D *persp2); + +Persp3D * persp3d_create_xml_element (SPDocument *document, Persp3D *dup = NULL); + +std::set persp3d_currently_selected (Box3DContext *bc); + +void persp3d_print_debugging_info (Persp3D *persp); +void persp3d_print_debugging_info_all(SPDocument *doc); + +#endif /* __PERSP3D_H__ */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/perspective-line.cpp b/src/perspective-line.cpp index 90857e6d5..051a1d94a 100644 --- a/src/perspective-line.cpp +++ b/src/perspective-line.cpp @@ -12,77 +12,21 @@ */ #include "perspective-line.h" -#include "desktop.h" +#include "persp3d.h" namespace Box3D { -PerspectiveLine::PerspectiveLine (NR::Point const &pt, Box3D::Axis const axis, Perspective3D *perspective) : - Line (pt, *(perspective->get_vanishing_point(axis)), true) +PerspectiveLine::PerspectiveLine (NR::Point const &pt, Proj::Axis const axis, Persp3D *persp) : + Line (pt, persp3d_get_VP(persp, axis).affine(), true) { - g_assert (perspective != NULL); - g_assert (Box3D::is_single_axis_direction (axis)); + g_assert (persp != NULL); - if (perspective->get_vanishing_point(axis)->state == VP_INFINITE) { - this->set_direction(perspective->get_vanishing_point(axis)->v_dir); + if (!persp3d_get_VP(persp, axis).is_finite()) { + Proj::Pt2 vp(persp3d_get_VP(persp, axis)); + this->set_direction(NR::Point(vp[Proj::X], vp[Proj::Y])); } this->vp_dir = axis; - this->persp = perspective; -} - -// This function makes sure not to return NR::Nothing() -// FIXME: How to gracefully handle parallel lines? -NR::Maybe PerspectiveLine::intersect (Line const &line) -{ - NR::Maybe pt = this->Line::intersect(line); - if (!pt) { - Box3D::VanishingPoint vp = *(persp->get_vanishing_point(vp_dir)); - if (vp.state == VP_INFINITE) { - pt = vp; - } else { - pt = NR::Point (0.0, 0.0); // FIXME: Better solution needed - } - } - return pt; -} - -// FIXME: Do we really need two intersection methods? -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(); - /* 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); + this->persp = persp; } } // namespace Box3D diff --git a/src/perspective-line.h b/src/perspective-line.h index 90104ffdf..e0235aafc 100644 --- a/src/perspective-line.h +++ b/src/perspective-line.h @@ -12,9 +12,7 @@ #ifndef SEEN_PERSPECTIVE_LINE_H #define SEEN_PERSPECTIVE_LINE_H -#include "vanishing-point.h" #include "line-geometry.h" -#include "box3d-context.h" #include class SPDesktop; @@ -29,30 +27,17 @@ public: * PL runs through it; otherwise it has the direction specified by the v_dir vector * of the VP. */ - 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); + PerspectiveLine (NR::Point const &pt, Proj::Axis const axis, Persp3D *persp); private: - Box3D::Axis vp_dir; // direction of the associated VP - Perspective3D *persp; + Proj::Axis vp_dir; // direction of the associated VP + Persp3D *persp; }; } // namespace Box3D -/** A function to print out the VanishingPoint (prints the coordinates) **/ -/*** -inline std::ostream &operator<< (std::ostream &out_file, const VanishingPoint &vp) { - out_file << vp; - return out_file; -} -***/ - - #endif /* !SEEN_PERSPECTIVE_LINE_H */ /* diff --git a/src/perspective3d.cpp b/src/perspective3d.cpp deleted file mode 100644 index 489e88dfc..000000000 --- a/src/perspective3d.cpp +++ /dev/null @@ -1,453 +0,0 @@ -#define __PERSPECTIVE3D_C__ - -/* - * Class modelling a 3D perspective - * - * Authors: - * Maximilian Albert - * - * Copyright (C) 2007 authors - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include "box3d.h" -#include "box3d-context.h" -#include "perspective-line.h" -#include -#include "perspective3d.h" -#include "desktop-handles.h" - -// can probably be removed later -#include "inkscape.h" - -namespace Box3D { - -gint Perspective3D::counter = 0; - -/** - * Computes the intersection of the two perspective lines from pt1 and pt2 to the respective - * vanishing points in the given directions. - */ -// FIXME: This has been moved to a virtual method inside PerspectiveLine; can probably be purged -NR::Point -perspective_intersection (NR::Point pt1, Box3D::Axis dir1, NR::Point pt2, Box3D::Axis dir2, Perspective3D *persp) -{ - VanishingPoint const *vp1 = persp->get_vanishing_point(dir1); - VanishingPoint const *vp2 = persp->get_vanishing_point(dir2); - NR::Maybe meet = Line(pt1, *vp1).intersect(Line(pt2, *vp2)); - // FIXME: How to handle parallel lines (also depends on the type of the VPs)? - if (!meet) { meet = NR::Point (0.0, 0.0); } - return *meet; -} - -/** - * Find the point on the perspective line from line_pt to the - * vanishing point in direction dir that is closest to ext_pt. - */ -NR::Point -perspective_line_snap (NR::Point line_pt, Box3D::Axis dir, NR::Point ext_pt, Perspective3D *persp) -{ - return PerspectiveLine(line_pt, dir, persp).closest_to(ext_pt); -} - -Perspective3D::Perspective3D (VanishingPoint const &pt_x, VanishingPoint const &pt_y, VanishingPoint const &pt_z, SPDocument *doc) - : boxes (NULL), - document (doc) -{ - vp_x = new VanishingPoint (pt_x); - vp_y = new VanishingPoint (pt_y); - vp_z = new VanishingPoint (pt_z); - - my_counter = Perspective3D::counter++; - - if (document == NULL) { - g_warning ("What to do now?\n"); - } -} - -Perspective3D::Perspective3D (Perspective3D &other) - : boxes (NULL) // Should we add an option to copy the list of boxes? -{ - vp_x = new VanishingPoint (*other.vp_x); - vp_y = new VanishingPoint (*other.vp_y); - vp_z = new VanishingPoint (*other.vp_z); - - my_counter = Perspective3D::counter++; - - document = other.document; -} - -Perspective3D::~Perspective3D () -{ - if (document) { - document->remove_perspective (this); - } else { - g_warning ("No document found!\n"); - } - - // Remove the VPs from their draggers - SPEventContext *ec = inkscape_active_event_context(); - if (SP_IS_3DBOX_CONTEXT (ec)) { - SP3DBoxContext *bc = SP_3DBOX_CONTEXT (ec); - // we need to check if there are any draggers because the selection - // is temporarily empty during duplication of boxes, e.g. - if (bc->_vpdrag->draggers != NULL) { - /*** - g_assert (bc->_vpdrag->getDraggerFor (*vp_x) != NULL); - g_assert (bc->_vpdrag->getDraggerFor (*vp_y) != NULL); - g_assert (bc->_vpdrag->getDraggerFor (*vp_z) != NULL); - bc->_vpdrag->getDraggerFor (*vp_x)->removeVP (vp_x); - bc->_vpdrag->getDraggerFor (*vp_y)->removeVP (vp_y); - bc->_vpdrag->getDraggerFor (*vp_z)->removeVP (vp_z); - ***/ - // TODO: the temporary perspective created when building boxes is not linked to any dragger, hence - // we need to do the following checks. Maybe it would be better to not create a temporary - // perspective at all but simply compare the VPs manually in sp_3dbox_build. - VPDragger * dragger; - dragger = bc->_vpdrag->getDraggerFor (*vp_x); - if (dragger) - dragger->removeVP (vp_x); - dragger = bc->_vpdrag->getDraggerFor (*vp_y); - if (dragger) - dragger->removeVP (vp_y); - dragger = bc->_vpdrag->getDraggerFor (*vp_z); - if (dragger) - dragger->removeVP (vp_z); - } - } - - delete vp_x; - delete vp_y; - delete vp_z; - - g_slist_free (boxes); -} - -bool -Perspective3D::operator==(Perspective3D const &other) const -{ - // Two perspectives are equal iff their vanishing points coincide and have identical states - return (*vp_x == *other.vp_x && *vp_y == *other.vp_y && *vp_z == *other.vp_z); -} - -bool -Perspective3D::has_vanishing_point (VanishingPoint *vp) -{ - return (vp == vp_x || vp == vp_y || vp == vp_z); -} - -VanishingPoint * -Perspective3D::get_vanishing_point (Box3D::Axis const dir) -{ - switch (dir) { - case X: - return vp_x; - break; - case Y: - return vp_y; - break; - case Z: - return vp_z; - break; - case NONE: - g_warning ("Axis direction must be specified. As a workaround we return the VP in X direction.\n"); - return vp_x; - break; - default: - g_warning ("Single axis direction needed to determine corresponding vanishing point.\n"); - return get_vanishing_point (extract_first_axis_direction(dir)); - break; - } -} - -void -Perspective3D::set_vanishing_point (Box3D::Axis const dir, VanishingPoint const &pt) -{ - switch (dir) { - case X: - (*vp_x) = pt; - break; - case Y: - (*vp_y) = pt; - break; - case Z: - (*vp_z) = pt; - break; - default: - // no vanishing point to set - break; - } -} - -void -Perspective3D::set_infinite_direction (Box3D::Axis axis, NR::Point const dir) -{ - Box3D::Axis axis1 = Box3D::get_remaining_axes (axis).first; - Box3D::Axis axis2 = Box3D::get_remaining_axes (axis).second; - Box3D::VanishingPoint *vp1 = get_vanishing_point (axis1); - Box3D::VanishingPoint *vp2 = get_vanishing_point (axis2); - if (fabs (Box3D::determinant (vp1->v_dir, dir)) < Box3D::epsilon || - fabs (Box3D::determinant (vp2->v_dir, dir)) < Box3D::epsilon) { - // This is an ad-hoc correction; we should fix this more thoroughly - double a = NR::atan2 (dir) + 0.01; - this->set_infinite_direction (axis, NR::Point (cos (a), sin (a))); // we call this function again in case there is another conflict (which is unlikely, but possible) - return; - } - - get_vanishing_point (axis)->set_infinite_direction (dir); - for (GSList *i = this->boxes; i != NULL; i = i->next) { - sp_3dbox_reshape_after_VP_rotation (SP_3DBOX (i->data), axis); - sp_3dbox_set_z_orders_later_on (SP_3DBOX (i->data)); - } - update_box_reprs(); -} - -void -Perspective3D::rotate (Box3D::Axis const axis, double const angle, bool const alt_pressed) -{ - Box3D::VanishingPoint *vp = get_vanishing_point (axis); - if (!vp->is_finite()) { - //double add_value = angle; - double a = NR::atan2 (vp->v_dir) * 180/M_PI; - a += alt_pressed ? 0.5 * ((angle > 0 ) - (angle < 0)) : angle; // the r.h.s. yields +/-0.5 or angle - a *= M_PI/180; - this->set_infinite_direction (axis, NR::Point (cos (a), sin (a))); - } -} - -Axis -Perspective3D::get_axis_of_VP (VanishingPoint *vp) -{ - if (vp == vp_x) return X; - if (vp == vp_y) return Y; - if (vp == vp_z) return Z; - - g_warning ("Vanishing point not present in the perspective.\n"); - return NONE; -} - -void -Perspective3D::set_vanishing_point (Box3D::Axis const dir, gdouble pt_x, gdouble pt_y, gdouble dir_x, gdouble dir_y, VPState st) -{ - VanishingPoint *vp; - switch (dir) { - case X: - vp = vp_x; - break; - case Y: - vp = vp_y; - break; - case Z: - vp = vp_z; - break; - default: - // no vanishing point to set - return; - } - - vp->set_pos (pt_x, pt_y); - vp->v_dir = NR::Point (dir_x, dir_y); - vp->state = st; -} - -void -Perspective3D::add_box (SP3DBox *box) -{ - if (g_slist_find (this->boxes, box) != NULL) { - // Don't add the same box twice - g_warning ("Box already uses the current perspective. We don't add it again.\n"); - return; - } - this->boxes = g_slist_append (this->boxes, box); -} - -void -Perspective3D::remove_box (const SP3DBox *box) -{ - if (!g_slist_find (this->boxes, box)) { - g_warning ("Could not find box that is to be removed in the current perspective.\n"); - } - this->boxes = g_slist_remove (this->boxes, box); -} - -bool -Perspective3D::has_box (const SP3DBox *box) const -{ - return (g_slist_find (this->boxes, box) != NULL); -} - -bool -Perspective3D::all_boxes_occur_in_list (GSList *boxes_to_do) -{ - for (GSList *i = boxes; i != NULL; i = i->next) { - if (!g_slist_find (boxes_to_do, i->data)) { - return false; - } - } - return true; -} - -GSList * -Perspective3D::boxes_occurring_in_list (GSList * list_of_boxes) -{ - GSList * result = NULL; - for (GSList *i = list_of_boxes; i != NULL; i = i->next) { - if (this->has_box (SP_3DBOX (i->data))) { - result = g_slist_prepend (result, i->data); - } - } - // we reverse so as to retain the same order as in list_of_boxes - return g_slist_reverse (result); -} - -/** - * Update the shape of a box after a handle was dragged or a VP was changed, according to the stored ratios. - */ -void -Perspective3D::reshape_boxes (Box3D::Axis axes) -{ - // TODO: Leave the "correct" corner fixed according to which face is supposed to be on front. - NR::Point new_pt; - VanishingPoint *vp; - for (const GSList *i = this->boxes; i != NULL; i = i->next) { - SP3DBox *box = SP_3DBOX (i->data); - if (axes & Box3D::X) { - vp = this->get_vanishing_point (Box3D::X); - if (vp->is_finite()) { - new_pt = vp->get_pos() + box->ratio_x * (box->corners[3] - vp->get_pos()); - sp_3dbox_move_corner_in_XY_plane (box, 2, new_pt); - } - } - if (axes & Box3D::Y) { - vp = this->get_vanishing_point (Box3D::Y); - if (vp->is_finite()) { - new_pt = vp->get_pos() + box->ratio_y * (box->corners[0] - vp->get_pos()); - sp_3dbox_move_corner_in_XY_plane (box, 2, new_pt); - } - } - if (axes & Box3D::Z) { - vp = this->get_vanishing_point (Box3D::Z); - if (vp->is_finite()) { - new_pt = vp->get_pos() + box->ratio_z * (box->corners[0] - vp->get_pos()); - sp_3dbox_move_corner_in_Z_direction (box, 4, new_pt); - } - } - - sp_3dbox_set_shape (box, true); - } -} - -void -Perspective3D::toggle_boxes (Box3D::Axis axis) -{ - get_vanishing_point (axis)->toggle_parallel(); - for (GSList *i = this->boxes; i != NULL; i = i->next) { - 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 -Perspective3D::update_box_reprs () -{ - for (GSList *i = this->boxes; i != NULL; i = i->next) { - SP_OBJECT(SP_3DBOX (i->data))->updateRepr(SP_OBJECT_WRITE_EXT); - } -} - -void -Perspective3D::update_z_orders () -{ - for (GSList *i = this->boxes; i != NULL; i = i->next) { - sp_3dbox_set_z_orders_later_on (SP_3DBOX (i->data)); - } -} - -/* the direction from a point pt towards the specified vanishing point of the perspective */ -NR::Point -Perspective3D::direction (NR::Point pt, Box3D::Axis axis) -{ - Box3D::VanishingPoint *vp = this->get_vanishing_point (axis); - if (!vp->is_finite()) { - return vp->v_dir; - } - return (vp->get_pos() - pt); -} - -// swallow the list of boxes from the other perspective and delete it -void -Perspective3D::absorb (Perspective3D *other) -{ - g_return_if_fail (*this == *other); - - // FIXME: Is copying necessary? Is other->boxes invalidated when other is deleted below? - this->boxes = g_slist_concat (this->boxes, g_slist_copy (other->boxes)); - - // Should we delete the other perspective here or at the place from where absorb() is called? - delete other; - other = NULL; -} - -// FIXME: We get compiler errors when we try to move the code from sp_3dbox_get_perspective_string to this function -/*** -gchar * -Perspective3D::svg_string () -{ -} -***/ - -void -Perspective3D::print_debugging_info () -{ - g_print ("====================================================\n"); - for (GSList *i = sp_desktop_document (inkscape_active_desktop())->perspectives; i != NULL; i = i->next) { - Perspective3D *persp = (Perspective3D *) i->data; - g_print ("Perspective %d:\n", persp->my_counter); - - VanishingPoint * vp = persp->get_vanishing_point(Box3D::X); - g_print (" VP X: (%f,%f) ", (*vp)[NR::X], (*vp)[NR::Y]); - g_print ((vp->is_finite()) ? "(finite)\n" : "(infinite)\n"); - - vp = persp->get_vanishing_point(Box3D::Y); - g_print (" VP Y: (%f,%f) ", (*vp)[NR::X], (*vp)[NR::Y]); - g_print ((vp->is_finite()) ? "(finite)\n" : "(infinite)\n"); - - vp = persp->get_vanishing_point(Box3D::Z); - g_print (" VP Z: (%f,%f) ", (*vp)[NR::X], (*vp)[NR::Y]); - g_print ((vp->is_finite()) ? "(finite)\n" : "(infinite)\n"); - - g_print ("\nBoxes: "); - if (persp->boxes == NULL) { - g_print ("none"); - } else { - GSList *j; - for (j = persp->boxes; j != NULL; j = j->next) { - if (j->next == NULL) break; - g_print ("%d, ", SP_3DBOX (j->data)->my_counter); - } - if (j != NULL) { - g_print ("%d", SP_3DBOX (j->data)->my_counter); - } - g_print ("\n"); - } - g_print ("\n"); - } - g_print ("====================================================\n"); -} - -} // namespace Box3D - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/perspective3d.h b/src/perspective3d.h deleted file mode 100644 index caf503e0d..000000000 --- a/src/perspective3d.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Class modelling a 3D perspective - * - * Authors: - * Maximilian Albert - * - * Copyright (C) 2007 authors - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#ifndef SEEN_PERSPECTIVE3D_H -#define SEEN_PERSPECTIVE3D_H - -#include "vanishing-point.h" -#include "svg/stringstream.h" -#include - -class SP3DBox; - -namespace Box3D { - -class PerspectiveLine; - -class Perspective3D { -public: - Perspective3D(VanishingPoint const &pt_x, VanishingPoint const &pt_y, VanishingPoint const &pt_z, SPDocument *document); - Perspective3D(Perspective3D &other); - ~Perspective3D(); - - bool operator== (Perspective3D const &other) const; - - bool has_vanishing_point (VanishingPoint *vp); - VanishingPoint *get_vanishing_point (Box3D::Axis const dir); - Axis get_axis_of_VP (VanishingPoint *vp); - void set_vanishing_point (Box3D::Axis const dir, VanishingPoint const &pt); - void set_vanishing_point (Box3D::Axis const dir, gdouble pt_x, gdouble pt_y, gdouble dir_x, gdouble dir_y, VPState st); - void set_infinite_direction (Box3D::Axis axis, NR::Point const dir); - void rotate (Box3D::Axis const dir, double const angle, bool const alt_pressed = false); - void add_box (SP3DBox *box); - void remove_box (const SP3DBox *box); - bool has_box (const SP3DBox *box) const; - inline guint number_of_boxes () { return g_slist_length (boxes); } - void reshape_boxes (Box3D::Axis axes); - void toggle_boxes (Box3D::Axis axes); // update the shape of boxes after a VP's state was toggled - void update_box_reprs (); - void update_z_orders (); - - NR::Point direction (NR::Point pt, Box3D::Axis axis); - - /* convenience functions for interaction with dragging machinery: */ - bool all_boxes_occur_in_list (GSList *boxes_to_do); - GSList * boxes_occurring_in_list (GSList * list_of_boxes); - - void absorb (Perspective3D *other); // swallow the other perspective if both coincide - - static gint counter; // for testing only - gint my_counter; // for testing only - - static void print_debugging_info(); - static Perspective3D * current_perspective; - -private: - VanishingPoint *vp_x; - VanishingPoint *vp_y; - VanishingPoint *vp_z; - GSList * boxes; // holds a list of boxes sharing this specific perspective - SPDocument * document; -}; - -NR::Point perspective_intersection (NR::Point pt1, Box3D::Axis dir1, NR::Point pt2, Box3D::Axis dir2, Perspective3D *persp); -NR::Point perspective_line_snap (NR::Point pt, Box3D::Axis dir, NR::Point ext_pt, Perspective3D *persp); - -} // namespace Box3D - - -/** A function to print out the VanishingPoint (prints the coordinates) **/ -/*** -inline std::ostream &operator<< (std::ostream &out_file, const VanishingPoint &vp) { - out_file << vp; - return out_file; -} -***/ - - -#endif /* !SEEN_PERSPECTIVE3D_H */ - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/proj_pt.cpp b/src/proj_pt.cpp new file mode 100644 index 000000000..d7906a4e2 --- /dev/null +++ b/src/proj_pt.cpp @@ -0,0 +1,119 @@ +#define __PROJ_PT_C__ + +/* + * 3x4 transformation matrix to map points from projective 3-space into the projective plane + * + * Authors: + * Maximilian Albert + * + * Copyright (C) 2007 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "proj_pt.h" +#include "svg/stringstream.h" + +namespace Proj { + +Pt2::Pt2(const gchar *coord_str) { + if (!coord_str) { + pt[0] = 0.0; + pt[1] = 0.0; + pt[2] = 1.0; + g_warning ("Coordinate string is empty. Creating default Pt2\n"); + return; + } + gchar **coords = g_strsplit(coord_str, ":", 0); + if (coords[0] == NULL || coords[1] == NULL || coords[2] == NULL) { + g_strfreev (coords); + g_warning ("Malformed coordinate string.\n"); + return; + } + + pt[0] = g_ascii_strtod(coords[0], NULL); + pt[1] = g_ascii_strtod(coords[1], NULL); + pt[2] = g_ascii_strtod(coords[2], NULL); +} + +void +Pt2::normalize() { + if (fabs(pt[2]) < 1E-6 || pt[2] == 1.0) + return; + pt[0] /= pt[2]; + pt[1] /= pt[2]; + pt[2] = 1.0; +} + +NR::Point +Pt2::affine() { + if (fabs(pt[2]) < epsilon) { + return NR::Point (NR_HUGE, NR_HUGE); + } + return NR::Point (pt[0]/pt[2], pt[1]/pt[2]); +} + +gchar * +Pt2::coord_string() { + Inkscape::SVGOStringStream os; + os << pt[0] << " : " + << pt[1] << " : " + << pt[2]; + return g_strdup(os.str().c_str()); +} + +Pt3::Pt3(const gchar *coord_str) { + if (!coord_str) { + pt[0] = 0.0; + pt[1] = 0.0; + pt[2] = 0.0; + pt[3] = 1.0; + g_warning ("Coordinate string is empty. Creating default Pt2\n"); + return; + } + gchar **coords = g_strsplit(coord_str, ":", 0); + if (coords[0] == NULL || coords[1] == NULL || + coords[2] == NULL || coords[3] == NULL) { + g_strfreev (coords); + g_warning ("Malformed coordinate string.\n"); + return; + } + + pt[0] = g_ascii_strtod(coords[0], NULL); + pt[1] = g_ascii_strtod(coords[1], NULL); + pt[2] = g_ascii_strtod(coords[2], NULL); + pt[3] = g_ascii_strtod(coords[3], NULL); +} + +void +Pt3::normalize() { + if (fabs(pt[3]) < 1E-6 || pt[3] == 1.0) + return; + pt[0] /= pt[3]; + pt[1] /= pt[3]; + pt[2] /= pt[3]; + pt[3] = 1.0; +} + +gchar * +Pt3::coord_string() { + Inkscape::SVGOStringStream os; + os << pt[0] << " : " + << pt[1] << " : " + << pt[2] << " : " + << pt[3]; + return g_strdup(os.str().c_str()); +} + +} // namespace Proj + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/proj_pt.h b/src/proj_pt.h new file mode 100644 index 000000000..30f375aa5 --- /dev/null +++ b/src/proj_pt.h @@ -0,0 +1,172 @@ +#ifndef __PROJ_PT_H__ +#define __PROJ_PT_H__ + +/* + * 3x4 transformation matrix to map points from projective 3-space into the projective plane + * + * Authors: + * Maximilian Albert + * + * Copyright (C) 2007 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "libnr/nr-point.h" +#include "libnr/nr-values.h" +#include + +namespace Proj { + +const double epsilon = 1E-6; + +// TODO: Catch the case when the constructors are called with only zeros +class Pt2 { +public: + Pt2 () { pt[0] = 0; pt[1] = 0; pt[2] = 1.0; } // we default to (0 : 0 : 1) + Pt2 (double x, double y, double w) { pt[0] = x; pt[1] = y; pt[2] = w; } + Pt2 (NR::Point const &point) { pt[0] = point[NR::X]; pt[1] = point[NR::Y]; pt[2] = 1; } + Pt2 (const gchar *coord_str); + + inline double operator[] (unsigned int index) const { + if (index > 2) { return NR_HUGE; } + return pt[index]; + } + inline double &operator[] (unsigned int index) { + // FIXME: How should we handle wrong indices? + //if (index > 2) { return NR_HUGE; } + return pt[index]; + } + inline bool operator== (Pt2 &rhs) { + normalize(); + rhs.normalize(); + return (fabs(pt[0] - rhs.pt[0]) < epsilon && + fabs(pt[1] - rhs.pt[1]) < epsilon && + fabs(pt[2] - rhs.pt[2]) < epsilon); + } + inline bool operator!= (Pt2 &rhs) { + return !((*this) == rhs); + } + + /*** For convenience, we define addition/subtraction etc. as "affine" operators (i.e., + the result for finite points is the same as if the affine points were addes ***/ + inline Pt2 &operator+(Pt2 &rhs) const { + Pt2 *result = new Pt2 (*this); + result->normalize(); + rhs.normalize(); + for ( unsigned i = 0 ; i < 2 ; ++i ) { + result->pt[i] += rhs.pt[i]; + } + return *result; + } + + inline Pt2 &operator-(Pt2 &rhs) const { + Pt2 *result = new Pt2 (*this); + result->normalize(); + rhs.normalize(); + for ( unsigned i = 0 ; i < 2 ; ++i ) { + result->pt[i] -= rhs.pt[i]; + } + return *result; + } + + inline Pt2 &operator*(double const s) const { + Pt2 *result = new Pt2 (*this); + result->normalize(); + for ( unsigned i = 0 ; i < 2 ; ++i ) { + result->pt[i] *= s; + } + return *result; + } + + void normalize(); + NR::Point affine(); + inline bool is_finite() { return pt[2] != 0; } // FIXME: Should we allow for some tolerance? + gchar *coord_string(); + inline void print(gchar *s) const { g_print ("%s(%8.2f : %8.2f : %8.2f)\n", s, pt[0], pt[1], pt[2]); } + +private: + double pt[3]; +}; + + +class Pt3 { +public: + Pt3 () { pt[0] = 0; pt[1] = 0; pt[2] = 0; pt[3] = 1.0; } // we default to (0 : 0 : 0 : 1) + Pt3 (double x, double y, double z, double w) { pt[0] = x; pt[1] = y; pt[2] = z; pt[3] = w; } + Pt3 (const gchar *coord_str); + + inline bool operator== (Pt3 &rhs) { + normalize(); + rhs.normalize(); + return (fabs(pt[0] - rhs.pt[0]) < epsilon && + fabs(pt[1] - rhs.pt[1]) < epsilon && + fabs(pt[2] - rhs.pt[2]) < epsilon && + fabs(pt[3] - rhs.pt[3]) < epsilon); + } + + /*** For convenience, we define addition/subtraction etc. as "affine" operators (i.e., + the result for finite points is the same as if the affine points were addes ***/ + inline Pt3 &operator+(Pt3 &rhs) const { + Pt3 *result = new Pt3 (*this); + result->normalize(); + rhs.normalize(); + for ( unsigned i = 0 ; i < 3 ; ++i ) { + result->pt[i] += rhs.pt[i]; + } + return *result; + } + + inline Pt3 &operator-(Pt3 &rhs) const { + Pt3 *result = new Pt3 (*this); + result->normalize(); + rhs.normalize(); + for ( unsigned i = 0 ; i < 3 ; ++i ) { + result->pt[i] -= rhs.pt[i]; + } + return *result; + } + + inline Pt3 &operator*(double const s) const { + Pt3 *result = new Pt3 (*this); + result->normalize(); + for ( unsigned i = 0 ; i < 3 ; ++i ) { + result->pt[i] *= s; + } + return *result; + } + + inline double operator[] (unsigned int index) const { + if (index > 3) { return NR_HUGE; } + return pt[index]; + } + inline double &operator[] (unsigned int index) { + // FIXME: How should we handle wrong indices? + //if (index > 3) { return NR_HUGE; } + return pt[index]; + } + void normalize(); + inline bool is_finite() { return pt[3] != 0; } // FIXME: Should we allow for some tolerance? + gchar *coord_string(); + inline void print(gchar *s) const { + g_print ("%s(%8.2f : %8.2f : %8.2f : %8.2f)\n", s, pt[0], pt[1], pt[2], pt[3]); + } + +private: + double pt[4]; +}; + +} // namespace Proj + +#endif /* __PROJ_PT_H__ */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/rect-context.cpp b/src/rect-context.cpp index d069e052c..3efc81596 100644 --- a/src/rect-context.cpp +++ b/src/rect-context.cpp @@ -15,6 +15,7 @@ */ #include "config.h" +#include "inkscape.h" #include @@ -397,6 +398,19 @@ static gint sp_rect_context_root_handler(SPEventContext *event_context, GdkEvent } break; + case GDK_T: + { + Inkscape::Selection *selection = sp_desktop_selection (inkscape_active_desktop()); + SPItem *item = selection->singleItem(); + if (item && SP_IS_RECT (item)) { + g_print ("Scaling transformation matrix\n"); + SP_RECT (item)->transform = nr_matrix_set_scale(SP_RECT (item)->transform, 1.25, 1.5); + SP_OBJECT (item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } + ret = TRUE; + } + break; + case GDK_Escape: sp_desktop_selection(desktop)->clear(); //TODO: make dragging escapable by Esc diff --git a/src/select-context.cpp b/src/select-context.cpp index 2eff4297f..cdf63785c 100644 --- a/src/select-context.cpp +++ b/src/select-context.cpp @@ -38,6 +38,7 @@ #include "message-stack.h" #include "selection-describer.h" #include "seltrans.h" +#include "box3d.h" static void sp_select_context_class_init(SPSelectContextClass *klass); static void sp_select_context_init(SPSelectContext *select_context); @@ -412,7 +413,7 @@ sp_select_context_root_handler(SPEventContext *event_context, GdkEvent *event) if (event->button.button == 1) { if (!selection->isEmpty()) { SPItem *clicked_item = (SPItem *) selection->itemList()->data; - if (SP_IS_GROUP (clicked_item)) { // enter group + if (SP_IS_GROUP(clicked_item) && !SP_IS_BOX3D(clicked_item)) { // enter group if it's not a 3D box desktop->setCurrentLayer(reinterpret_cast(clicked_item)); sp_desktop_selection(desktop)->clear(); sc->dragging = false; @@ -861,7 +862,8 @@ sp_select_context_root_handler(SPEventContext *event_context, GdkEvent *event) if (MOD__CTRL_ONLY) { if (selection->singleItem()) { SPItem *clicked_item = selection->singleItem(); - if (SP_IS_GROUP (clicked_item)) { // enter group + if ( SP_IS_GROUP(clicked_item) && + !SP_IS_BOX3D(clicked_item)) { // enter group if it's not a 3D box desktop->setCurrentLayer(reinterpret_cast(clicked_item)); sp_desktop_selection(desktop)->clear(); } else { diff --git a/src/selection-describer.cpp b/src/selection-describer.cpp index 01aab97b7..1debd73e1 100644 --- a/src/selection-describer.cpp +++ b/src/selection-describer.cpp @@ -60,7 +60,7 @@ type2term(GType type) { return _("Polyline"); } if (type == SP_TYPE_RECT) { return _("Rectangle"); } - if (type == SP_TYPE_3DBOX) + if (type == SP_TYPE_BOX3D) { return _("3D Box"); } if (type == SP_TYPE_TEXT) { return _("Text"); } diff --git a/src/sp-ellipse.cpp b/src/sp-ellipse.cpp index 7a1b0f33e..81a103cd7 100644 --- a/src/sp-ellipse.cpp +++ b/src/sp-ellipse.cpp @@ -390,6 +390,7 @@ sp_ellipse_init(SPEllipse */*ellipse*/) static void sp_ellipse_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) { + g_print ("sp_ellipse_build\n"); if (((SPObjectClass *) ellipse_parent_class)->build) (* ((SPObjectClass *) ellipse_parent_class)->build) (object, document, repr); diff --git a/src/sp-item-group.cpp b/src/sp-item-group.cpp index cabc7b26a..410a7b37e 100644 --- a/src/sp-item-group.cpp +++ b/src/sp-item-group.cpp @@ -35,6 +35,8 @@ #include "sp-clippath.h" #include "sp-mask.h" #include "sp-path.h" +#include "box3d.h" +#include "box3d-side.h" static void sp_group_class_init (SPGroupClass *klass); static void sp_group_init (SPGroup *group); @@ -326,6 +328,11 @@ sp_item_group_ungroup (SPGroup *group, GSList **children, bool do_done) SPItem *pitem = SP_ITEM (SP_OBJECT_PARENT (gitem)); Inkscape::XML::Node *prepr = SP_OBJECT_REPR (pitem); + /* When ungrouping a 3D box, we must convert the sides to ordinary paths */ + if (SP_IS_BOX3D (gitem)) { + g_print ("============== Ungrouping a 3D box ===============\n"); + } + /* Step 1 - generate lists of children objects */ GSList *items = NULL; GSList *objects = NULL; @@ -335,6 +342,13 @@ sp_item_group_ungroup (SPGroup *group, GSList **children, bool do_done) SPItem *citem = SP_ITEM (child); + if (SP_IS_BOX3D_SIDE(child)) { + Inkscape::XML::Node *repr = SP_OBJECT_REPR(child); + // FIXME: This doesn't remove the attribute "inkscape:box3dsidetype". Why? + repr->setAttribute("inkscape:box3dsidetype", NULL); + repr->setAttribute("sodipodi:type", NULL); + } + /* Merging of style */ // this converts the gradient/pattern fill/stroke, if any, to userSpaceOnUse; we need to do // it here _before_ the new transform is set, so as to use the pre-transform bbox @@ -421,7 +435,7 @@ sp_item_group_ungroup (SPGroup *group, GSList **children, bool do_done) while (items) { Inkscape::XML::Node *repr = (Inkscape::XML::Node *) items->data; // add item - prepr->appendChild(repr); + prepr->appendChild(repr); // restore position; since the items list was prepended (i.e. reverse), we now add // all children at the same pos, which inverts the order once again repr->setPosition(pos > 0 ? pos : 0); @@ -608,14 +622,50 @@ void CGroup::onUpdate(SPCtx *ctx, unsigned int flags) { while (l) { SPObject *child = SP_OBJECT (l->data); l = g_slist_remove (l, child); + //g_print ("sp-item-group: onUpdate working on child with flags "); if (flags || (child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + /*** + g_print (" On parent object: "); + if (flags & SP_OBJECT_MODIFIED_FLAG) { + g_print("SP_OBJECT_MODIFIED_FLAG "); + } + if (flags & SP_OBJECT_CHILD_MODIFIED_FLAG) { + g_print("SP_OBJECT_CHILD_MODIFIED_FLAG "); + } + g_print ("\n"); + g_print (" On child object: "); + if (child->uflags & SP_OBJECT_MODIFIED_FLAG) { + g_print("SP_OBJECT_MODIFIED_FLAG "); + } + if (child->uflags & SP_OBJECT_CHILD_MODIFIED_FLAG) { + g_print("SP_OBJECT_CHILD_MODIFIED_FLAG "); + } + ***/ if (SP_IS_ITEM (child)) { SPItem const &chi = *SP_ITEM(child); cctx.i2doc = chi.transform * ictx->i2doc; cctx.i2vp = chi.transform * ictx->i2vp; + /** + g_print ("case 1\n"); + g_print ("\n On parent object: flags=%d ", flags); + g_print ("\n On child object: uflags=%d ", child->uflags); + if (flags & SP_OBJECT_MODIFIED_FLAG) g_print("SP_OBJECT_MODIFIED_FLAG "); + if (flags & SP_OBJECT_CHILD_MODIFIED_FLAG) g_print("SP_OBJECT_CHILD_MODIFIED_FLAG "); + g_print ("\n"); + if (child->uflags & SP_OBJECT_MODIFIED_FLAG) { + g_print("SP_OBJECT_MODIFIED_FLAG "); + } + if (child->uflags & SP_OBJECT_CHILD_MODIFIED_FLAG) { + g_print("SP_OBJECT_CHILD_MODIFIED_FLAG "); + } + g_print ("\n"); + **/ + //g_print ("Caution! The changed code applies! Does this change any behaviour?\n"); child->updateDisplay((SPCtx *)&cctx, flags); + //child->updateDisplay((SPCtx *)&cctx, child->uflags); } else { child->updateDisplay(ctx, flags); + //g_print ("case 2\n"); } } g_object_unref (G_OBJECT (child)); diff --git a/src/sp-object-repr.cpp b/src/sp-object-repr.cpp index 9338f1019..5fa36d7b5 100644 --- a/src/sp-object-repr.cpp +++ b/src/sp-object-repr.cpp @@ -22,6 +22,8 @@ #include "sp-radial-gradient-fns.h" #include "sp-rect.h" #include "box3d.h" +#include "box3d-side.h" +#include "persp3d.h" #include "sp-ellipse.h" #include "sp-star.h" #include "sp-stop-fns.h" @@ -186,8 +188,9 @@ populate_dtables() { "inkscape:offset", SP_TYPE_OFFSET }, { "spiral", SP_TYPE_SPIRAL }, { "star", SP_TYPE_STAR }, - { "inkscape:3dbox", SP_TYPE_3DBOX }//, - //{ "inkscape:3dboxface", SP_TYPE_3DBOX_FACE } + { "inkscape:box3d", SP_TYPE_BOX3D }, + { "inkscape:box3dside", SP_TYPE_BOX3D_SIDE }, + { "inkscape:persp3d", SP_TYPE_PERSP3D } }; NameTypeEntry const *const t2entries[] = { diff --git a/src/sp-rect.cpp b/src/sp-rect.cpp index d9caebd5a..e5f1da05d 100644 --- a/src/sp-rect.cpp +++ b/src/sp-rect.cpp @@ -349,6 +349,7 @@ sp_rect_set_ry(SPRect *rect, gboolean set, gdouble value) static NR::Matrix sp_rect_set_transform(SPItem *item, NR::Matrix const &xform) { + g_print ("sp_rect_set_transform\n"); SPRect *rect = SP_RECT(item); /* Calculate rect start in parent coords. */ diff --git a/src/syseq.h b/src/syseq.h new file mode 100644 index 000000000..7075bd47b --- /dev/null +++ b/src/syseq.h @@ -0,0 +1,328 @@ +#ifndef SEEN_SYSEQ_H +#define SEEN_SYSEQ_H + +/* + * Auxiliary routines to solve systems of linear equations in several variants and sizes. + * + * Authors: + * Maximilian Albert + * + * Copyright (C) 2007 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include +#include "math.h" + +namespace SysEq { + +enum SolutionKind { + unique = 0, + ambiguous, + no_solution, + solution_exists // FIXME: remove this; does not yield enough information +}; + +inline void explain(SolutionKind sol) { + switch (sol) { + case SysEq::unique: + std::cout << "unique" << std::endl; + break; + case SysEq::ambiguous: + std::cout << "ambiguous" << std::endl; + break; + case SysEq::no_solution: + std::cout << "no solution" << std::endl; + break; + case SysEq::solution_exists: + std::cout << "solution exists" << std::endl; + break; + } +} + +inline double +determinant3x3 (double A[3][3]) { + return (A[0][0]*A[1][1]*A[2][2] + + A[0][1]*A[1][2]*A[2][0] + + A[0][2]*A[1][0]*A[2][1] - + A[0][0]*A[1][2]*A[2][1] - + A[0][1]*A[1][0]*A[2][2] - + A[0][2]*A[1][1]*A[2][0]); +} + +/* Determinant of the 3x3 matrix having a, b, and c as columns */ +inline double +determinant3v (const double a[3], const double b[3], const double c[3]) { + return (a[0]*b[1]*c[2] + + a[1]*b[2]*c[0] + + a[2]*b[0]*c[1] - + a[0]*b[2]*c[1] - + a[1]*b[0]*c[2] - + a[2]*b[1]*c[0]); +} + +/* Fills the matrix A with random values between lower and upper */ +template +inline void fill_random (double A[S][T], double lower = 0.0, double upper = 1.0) { + srand(time(NULL)); + double range = upper - lower; + for (int i = 0; i < S; ++i) { + for (int j = 0; j < T; ++j) { + A[i][j] = range*(random()/(RAND_MAX + 1.0)) - lower; + } + } +} + +/* Copy the elements of A into B */ +template +inline void copy_mat(double A[S][T], double B[S][T]) { + for (int i = 0; i < S; ++i) { + for (int j = 0; j < T; ++j) { + B[i][j] = A[i][j]; + } + } +} + +template +inline void print_mat (const double A[S][T]) { + std::cout.setf(std::ios::left, std::ios::internal); + for (int i = 0; i < S; ++i) { + for (int j = 0; j < T; ++j) { + printf ("%8.2f ", A[i][j]); + } + std::cout << std::endl;; + } +} + +/* Multiplication of two matrices */ +template +inline void multiply(double A[S][U], double B[U][T], double res[S][T]) { + for (int i = 0; i < S; ++i) { + for (int j = 0; j < T; ++j) { + double sum = 0; + for (int k = 0; k < U; ++k) { + sum += A[i][k] * B[k][j]; + } + res[i][j] = sum; + } + } +} + +/* + * Multiplication of a matrix with a vector (for convenience, because with the previous + * multiplication function we would always have to write v[i][0] for elements of the vector. + */ +template +inline void multiply(double A[S][T], double v[T], double res[S]) { + for (int i = 0; i < S; ++i) { + double sum = 0; + for (int k = 0; k < T; ++k) { + sum += A[i][k] * v[k]; + } + res[i] = sum; + } +} + +// Remark: Since we are using templates, we cannot separate declarations from definitions (which would +// result in linker errors but have to include the definitions here for the following functions. +// FIXME: Maybe we should rework all this by using vector > structures for matrices +// instead of double[S][T]. This would allow us to avoid templates. Would the performance degrade? + +/* + * Find the element of maximal absolute value in row i that + * does not lie in one of the columns given in avoid_cols. + */ +template +static int find_pivot(const double A[S][T], unsigned int i, std::vector const &avoid_cols) { + if (i >= S) { + return -1; + } + int pos = -1; + double max = 0; + for (int j = 0; j < T; ++j) { + if (std::find(avoid_cols.begin(), avoid_cols.end(), j) != avoid_cols.end()) { + continue; // skip "forbidden" columns + } + if (fabs(A[i][j]) > max) { + pos = j; + max = fabs(A[i][j]); + } + } + return pos; +} + +/* + * Performs a single 'exchange step' in the Gauss-Jordan algorithm (i.e., swapping variables in the + * two vectors). + */ +template +static void gauss_jordan_step (double A[S][T], int row, int col) { + double piv = A[row][col]; // pivot element + /* adapt the entries of the matrix, first outside the pivot row/column */ + for (int k = 0; k < S; ++k) { + if (k == row) continue; + for (int l = 0; l < T; ++l) { + if (l == col) continue; + A[k][l] -= A[k][col] * A[row][l] / piv; + } + } + /* now adapt the pivot column ... */ + for (int k = 0; k < S; ++k) { + if (k == row) continue; + A[k][col] /= piv; + } + /* and the pivot row */ + for (int l = 0; l < T; ++l) { + if (l == col) continue; + A[row][l] /= -piv; + } + /* finally, set the element at the pivot position itself */ + A[row][col] = 1/piv; +} + +/* + * Perform Gauss-Jordan elimination on the matrix A, optionally avoiding a given column during pivot search + */ +template +static std::vector gauss_jordan (double A[S][T], int avoid_col = -1) { + std::vector cols_used; + if (avoid_col != -1) { + cols_used.push_back (avoid_col); + } + int col; + for (int i = 0; i < S; ++i) { + /* for each row find a pivot element of maximal absolute value, skipping the columns that were used before */ + col = find_pivot(A, i, cols_used); + cols_used.push_back(col); + if (col == -1) { + // no non-zero elements in the row + return cols_used; + } + + /* if pivot search was successful we can perform a Gauss-Jordan step */ + gauss_jordan_step (A, i, col); + } + if (avoid_col != -1) { + // since the columns that were used will be needed later on, we need to clean up the column vector + cols_used.erase(cols_used.begin()); + } + return cols_used; +} + +/* compute the modified value that x[index] needs to assume so that in the end we have x[index]/x[T-1] = val */ +template +static double projectify (std::vector const &cols, const double B[S][T], const double x[T], + const int index, const double val) { + double val_proj = 0.0; + if (index != -1) { + int c = -1; + for (int i = 0; i < S; ++i) { + if (cols[i] == T-1) { + c = i; + break; + } + } + if (c == -1) { + std::cout << "Something is wrong. Rethink!!" << std::endl; + return SysEq::no_solution; + } + + double sp = 0; + for (int j = 0; j < T; ++j) { + if (j == index) continue; + sp += B[c][j] * x[j]; + } + double mu = 1 - val * B[c][index]; + if (fabs(mu) < 1E-6) { + std::cout << "No solution since adapted value is too close to zero" << std::endl; + return SysEq::no_solution; + } + val_proj = sp*val/mu; + } else { + val_proj = val; // FIXME: Is this correct? + } + return val_proj; +} + +/** + * Solve the linear system of equations \a A * \a x = \a v where we additionally stipulate + * \a x[\a index] = \a val if \a index is not -1. The system is solved using Gauss-Jordan + * elimination so that we can gracefully handle the case that zero or infinitely many + * solutions exist. + * + * Since our application will be to finding preimages of projective mappings, we provide + * an additional argument \a proj. If this is true, we find a solution of + * \a x[\a index]/\a x[\T - 1] = \a val insted (i.e., we want the corresponding coordinate + * of the _affine image_ of the point with homogeneous coordinate vector \a x to be equal + * to \a val. + * + * Remark: We don't need this but it would be relatively simple to let the calling function + * prescripe the value of _multiple_ components of the solution vector instead of only a single one. + */ +template SolutionKind gaussjord_solve (double A[S][T], double x[T], double v[S], + int index = -1, double val = 0.0, bool proj = false) { + double B[S][T]; + //copy_mat(A,B); + SysEq::copy_mat(A,B); + std::vector cols = gauss_jordan(B, index); + if (std::find(cols.begin(), cols.end(), -1) != cols.end()) { + // pivot search failed for some row so the system is not solvable + return SysEq::no_solution; + } + + /* the vector x is filled with the coefficients of the desired solution vector at appropriate places; + * the other components are set to zero, and we additionally set x[index] = val if applicable + */ + std::vector::iterator k; + for (int j = 0; j < S; ++j) { + x[cols[j]] = v[j]; + } + for (int j = 0; j < T; ++j) { + k = std::find(cols.begin(), cols.end(), j); + if (k == cols.end()) { + x[j] = 0; + } + } + + // we need to adapt the value if we we are in the "projective case" (see above) + double val_new = (proj ? projectify(cols, B, x, index, val) : val); + + if (index != -1 && index >= 0 && index < T) { + // we want the specified coefficient of the solution vector to have a given value + x[index] = val_new; + } + + /* the final solution vector is now obtained as the product B*x, where B is the matrix + * obtained by Gauss-Jordan manipulation of A; we use w as an auxiliary vector and + * afterwards copy the result back to x + */ + double w[S]; + SysEq::multiply(B,x,w); + for (int j = 0; j < S; ++j) { + x[cols[j]] = w[j]; + } + + if (S + (index == -1 ? 0 : 1) == T) { + return SysEq::unique; + } else { + return SysEq::ambiguous; + } +} + +} // namespace SysEq + +#endif /* __SYSEQ_H__ */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/tools-switch.cpp b/src/tools-switch.cpp index 1bc83d7a2..e4692d91b 100644 --- a/src/tools-switch.cpp +++ b/src/tools-switch.cpp @@ -157,7 +157,7 @@ tools_switch(SPDesktop *dt, int num) dt->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Drag to create a rectangle. Drag controls to round corners and resize. Click to select.")); break; case TOOLS_SHAPES_3DBOX: - dt->set_event_context(SP_TYPE_3DBOX_CONTEXT, tool_names[num]); + dt->set_event_context(SP_TYPE_BOX3D_CONTEXT, tool_names[num]); dt->activate_guides(false); inkscape_eventcontext_set(sp_desktop_event_context(dt)); dt->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Drag to create a 3D box. Drag controls to resize in perspective. Click to select (with Ctrl+Alt for single faces).")); @@ -248,6 +248,8 @@ void tools_switch_by_item(SPDesktop *dt, SPItem *item) { if (SP_IS_RECT(item)) { tools_switch(dt, TOOLS_SHAPES_RECT); + } else if (SP_IS_BOX3D(item)) { + tools_switch(dt, TOOLS_SHAPES_3DBOX); } else if (SP_IS_GENERICELLIPSE(item)) { tools_switch(dt, TOOLS_SHAPES_ARC); } else if (SP_IS_STAR(item)) { diff --git a/src/transf_mat_3x4.cpp b/src/transf_mat_3x4.cpp new file mode 100644 index 000000000..a624fb163 --- /dev/null +++ b/src/transf_mat_3x4.cpp @@ -0,0 +1,200 @@ +#define SEEN_TRANSF_MAT_3x4_C + +/* + * 3x4 transformation matrix to map points from projective 3-space into the projective plane + * + * Authors: + * Maximilian Albert + * + * Copyright (C) 2007 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "transf_mat_3x4.h" +#include +#include "svg/stringstream.h" +#include "syseq.h" +#include "libnr/nr-matrix.h" +#include "document.h" +#include "inkscape.h" + +namespace Proj { + +TransfMat3x4::TransfMat3x4 () { + for (unsigned int i = 0; i < 3; ++i) { + for (unsigned int j = 0; j < 4; ++j) { + tmat[i][j] = (i == j ? 1 : 0); // or should we initialize all values with 0? does it matter at all? + } + } +} + +TransfMat3x4::TransfMat3x4 (Proj::Pt2 vp_x, Proj::Pt2 vp_y, Proj::Pt2 vp_z, Proj::Pt2 origin) { + for (unsigned int i = 0; i < 3; ++i) { + tmat[i][0] = vp_x[i]; + tmat[i][1] = vp_y[i]; + tmat[i][2] = vp_z[i]; + tmat[i][3] = origin[i]; + } +} + +TransfMat3x4::TransfMat3x4(TransfMat3x4 const &rhs) { + for (unsigned int i = 0; i < 3; ++i) { + for (unsigned int j = 0; j < 4; ++j) { + tmat[i][j] = rhs.tmat[i][j]; + } + } +} + +Pt2 +TransfMat3x4::column (Proj::Axis axis) const { + return Proj::Pt2 (tmat[0][axis], tmat[1][axis], tmat[2][axis]); +} + +Pt2 +TransfMat3x4::image (Pt3 const &point) { + double x = tmat[0][0] * point[0] + tmat[0][1] * point[1] + tmat[0][2] * point[2] + tmat[0][3] * point[3]; + double y = tmat[1][0] * point[0] + tmat[1][1] * point[1] + tmat[1][2] * point[2] + tmat[1][3] * point[3]; + double w = tmat[2][0] * point[0] + tmat[2][1] * point[1] + tmat[2][2] * point[2] + tmat[2][3] * point[3]; + + return Pt2 (x, y, w); +} + +Pt3 +TransfMat3x4::preimage (NR::Point const &pt, double coord, Proj::Axis axis) { + double x[4]; + double v[3]; + v[0] = pt[NR::X]; + v[1] = pt[NR::Y]; + v[2] = 1.0; + int index = (int) axis; + + SysEq::SolutionKind sol = SysEq::gaussjord_solve<3,4>(tmat, x, v, index, coord, true); + + if (sol != SysEq::unique) { + if (sol == SysEq::no_solution) { + g_print ("No solution. Please investigate.\n"); + } else { + g_print ("Infinitely many solutions. Please investigate.\n"); + } + } + return Pt3(x[0], x[1], x[2], x[3]); +} + +void +TransfMat3x4::set_image_pt (Proj::Axis axis, Proj::Pt2 const &pt) { + // FIXME: Do we need to adapt the coordinates in any way or can we just use them as they are? + for (int i = 0; i < 3; ++i) { + tmat[i][axis] = pt[i]; + } +} + +void +TransfMat3x4::toggle_finite (Proj::Axis axis) { + g_return_if_fail (axis != Proj::W); + if (has_finite_image(axis)) { + NR::Point dir (column(axis).affine()); + NR::Point origin (column(Proj::W).affine()); + dir -= origin; + set_column (axis, Proj::Pt2(dir[NR::X], dir[NR::Y], 0)); + } else { + Proj::Pt2 dir (column(axis)); + Proj::Pt2 origin (column(Proj::W).affine()); + dir = dir + origin; + dir[2] = 1.0; + set_column (axis, dir); + } +} + +gchar * +TransfMat3x4::pt_to_str (Proj::Axis axis) { + Inkscape::SVGOStringStream os; + os << tmat[0][axis] << " : " + << tmat[1][axis] << " : " + << tmat[2][axis]; + return g_strdup(os.str().c_str()); +} + +bool +TransfMat3x4::operator==(const TransfMat3x4 &rhs) const +{ + // Should we allow a certain tolerance or "normalize" the matrices first? + for (int i = 0; i < 3; ++i) { + Proj::Pt2 pt1 = column(Proj::axes[i]); + Proj::Pt2 pt2 = rhs.column(Proj::axes[i]); + if (pt1 != pt2) { + return false; + } + } + return true; +} + +/* multiply a projective matrix by an affine matrix */ +TransfMat3x4 +TransfMat3x4::operator*(NR::Matrix const &A) const { + TransfMat3x4 ret; + + // Is it safe to always use the currently active document? + double h = sp_document_height(inkscape_active_document()); + + /* + * Note: The strange multiplication involving the document height is due to the buggy + * intertwining of SVG and document coordinates. Essentially, what we do is first + * convert from "real-world" to SVG coordinates, then apply the transformation A + * (by multiplying with the NR::Matrix) and then convert back from SVG to real-world + * coordinates. Maybe there is even a more Inkscape-ish way to achieve this? + * Once Inkscape has gotton rid of the two different coordiate systems, we can change + * this function to an ordinary matrix multiplication. + */ + for (int j = 0; j < 4; ++j) { + ret.tmat[0][j] = A[0]*tmat[0][j] + A[2]*(h*tmat[2][j] - tmat[1][j]) + A[4]*tmat[2][j]; + ret.tmat[1][j] = A[1]*tmat[0][j] + A[3]*(h*tmat[2][j] - tmat[1][j]) + A[5]*tmat[2][j]; + ret.tmat[2][j] = tmat[2][j]; + + ret.tmat[1][j] = h*ret.tmat[2][j] - ret.tmat[1][j]; // switch back from SVG to desktop coordinates + } + + return ret; +} + +// FIXME: Shouldn't rather operator* call operator*= for efficiency? (Because in operator*= +// there is in principle no need to create a temporary object, which happens in the assignment) +TransfMat3x4 & +TransfMat3x4::operator*=(NR::Matrix const &A) { + *this = *this * A; + return *this; +} + + +void +TransfMat3x4::print () const { + g_print ("Transformation matrix:\n"); + for (int i = 0; i < 3; ++i) { + g_print (" "); + for (int j = 0; j < 4; ++j) { + g_print ("%8.2f ", tmat[i][j]); + } + g_print ("\n"); + } +} + +void +TransfMat3x4::normalize_column (Proj::Axis axis) { + Proj::Pt2 new_col(column(axis)); + new_col.normalize(); + set_image_pt(axis, new_col); +} + + +} // namespace Proj + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/transf_mat_3x4.h b/src/transf_mat_3x4.h new file mode 100644 index 000000000..549db1c93 --- /dev/null +++ b/src/transf_mat_3x4.h @@ -0,0 +1,80 @@ +#ifndef SEEN_TRANSF_MAT_3x4_H +#define SEEN_TRANSF_MAT_3x4_H + +/* + * 3x4 transformation matrix to map points from projective 3-space into the projective plane + * + * Authors: + * Maximilian Albert + * + * Copyright (C) 2007 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "proj_pt.h" +#include "axis-manip.h" +#include "libnr/nr-point-fns.h" + +namespace Proj { + +class TransfMat3x4 { +public: + TransfMat3x4(); + TransfMat3x4(Pt2 vp_x, Pt2 vp_y, Pt2 vp_z, Pt2 origin); + TransfMat3x4(TransfMat3x4 const &rhs); + Pt2 column (Proj::Axis axis) const; + Pt2 image (Pt3 const &point); + Pt3 preimage (NR::Point const &pt, double coord = 0, Axis = Z); + void set_image_pt (Proj::Axis axis, Proj::Pt2 const &pt); + void toggle_finite (Proj::Axis axis); + double get_infinite_angle (Proj::Axis axis) { + if (has_finite_image(axis)) { + return NR_HUGE; + } + Pt2 vp(column(axis)); + return NR::atan2(NR::Point(vp[0], vp[1])) * 180.0/M_PI; + } + void set_infinite_direction (Proj::Axis axis, double angle) { // angle is in degrees + g_return_if_fail(tmat[2][axis] == 0); // don't set directions for finite VPs + + double a = angle * M_PI/180; + NR::Point pt(tmat[0][axis], tmat[1][axis]); + double rad = NR::L2(pt); + set_image_pt(axis, Proj::Pt2(cos (a) * rad, sin (a) * rad, 0.0)); + } + inline bool has_finite_image (Proj::Axis axis) { return (tmat[2][axis] != 0.0); } + + gchar * pt_to_str (Proj::Axis axis); + + bool operator==(const TransfMat3x4 &rhs) const; + TransfMat3x4 operator*(NR::Matrix const &A) const; + TransfMat3x4 &operator*=(NR::Matrix const &A); + + void print() const; + +private: + // FIXME: Is changing a single column allowed when a projective coordinate system is specified!?!?! + void normalize_column (Proj::Axis axis); + inline void set_column (Proj::Axis axis, Proj::Pt2 pt) { + tmat[0][axis] = pt[0]; + tmat[1][axis] = pt[1]; + tmat[2][axis] = pt[2]; + } + double tmat[3][4]; +}; + +} // namespace Proj + +#endif /* __TRANSF_MAT_3x4_H__ */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/vanishing-point.cpp b/src/vanishing-point.cpp index 6f72a9621..0a8336102 100644 --- a/src/vanishing-point.cpp +++ b/src/vanishing-point.cpp @@ -17,7 +17,10 @@ #include "vanishing-point.h" #include "desktop-handles.h" -#include "box3d.h" +#include "desktop.h" +#include "event-context.h" +#include "xml/repr.h" +#include "perspective-line.h" #include "knotholder.h" // FIXME: can we avoid direct access to knotholder_update_knots? @@ -43,143 +46,30 @@ SPKnotShapeType vp_knot_shapes [] = { SP_KNOT_SHAPE_CIRCLE //VP_INFINITE }; -// FIXME: We should always require to have both the point (for finite VPs) -// and the direction (for infinite VPs) set. Otherwise toggling -// shows very unexpected behaviour. -// Later on we can maybe infer the infinite direction from the finite point -// and a suitable center of the scene. How to go in the other direction? -VanishingPoint::VanishingPoint(NR::Point const &pt, NR::Point const &inf_dir, VPState st) - : NR::Point (pt), state (st), v_dir (inf_dir) {} - -VanishingPoint::VanishingPoint(NR::Point const &pt) - : NR::Point (pt), state (VP_FINITE), v_dir (0.0, 0.0) {} - -VanishingPoint::VanishingPoint(NR::Point const &pt, NR::Point const &direction) - : NR::Point (pt), state (VP_INFINITE), v_dir (direction) {} - -VanishingPoint::VanishingPoint(NR::Coord x, NR::Coord y) - : NR::Point(x, y), state(VP_FINITE), v_dir(0.0, 0.0) {} - -VanishingPoint::VanishingPoint(NR::Coord dir_x, NR::Coord dir_y, VPState st) - : NR::Point(0.0, 0.0), state(st), v_dir(dir_x, dir_y) {} - -VanishingPoint::VanishingPoint(NR::Coord x, NR::Coord y, NR::Coord dir_x, NR::Coord dir_y) - : NR::Point(x, y), state(VP_INFINITE), v_dir(dir_x, dir_y) {} - -VanishingPoint::VanishingPoint(VanishingPoint const &rhs) : NR::Point (rhs) -{ - this->state = rhs.state; - //this->ref_pt = rhs.ref_pt; - this->v_dir = rhs.v_dir; -} - -VanishingPoint::~VanishingPoint () {} - -bool VanishingPoint::operator== (VanishingPoint const &other) -{ - // Should we compare the parent perspectives, too? Probably not. - if ((*this)[NR::X] == other[NR::X] && (*this)[NR::Y] == other[NR::Y] - && this->state == other.state && this->v_dir == other.v_dir) { - return true; - } - return false; -} - -bool VanishingPoint::is_finite() const -{ - return this->state == VP_FINITE; -} - -VPState VanishingPoint::toggle_parallel() -{ - if (this->state == VP_FINITE) { - this->state = VP_INFINITE; - } else { - this->state = VP_FINITE; - } - - return this->state; -} - -void VanishingPoint::draw(Box3D::Axis const axis) -{ - switch (axis) { - case X: - if (state == VP_FINITE) - create_canvas_point(*this, 6.0, 0xff000000); - else - create_canvas_point(*this, 6.0, 0xffffff00); - break; - case Y: - if (state == VP_FINITE) - create_canvas_point(*this, 6.0, 0x0000ff00); - else - create_canvas_point(*this, 6.0, 0xffffff00); - break; - case Z: - if (state == VP_FINITE) - create_canvas_point(*this, 6.0, 0x00770000); - else - create_canvas_point(*this, 6.0, 0xffffff00); - break; - default: - g_assert_not_reached(); - break; - } -} - static void -vp_drag_sel_changed(Inkscape::Selection */*selection*/, gpointer data) +vp_drag_sel_changed(Inkscape::Selection *selection, gpointer data) { VPDrag *drag = (VPDrag *) data; - drag->updateDraggers (); - drag->updateLines (); + drag->updateDraggers(); + drag->updateLines(); + drag->updateBoxReprs(); } static void -vp_drag_sel_modified (Inkscape::Selection */*selection*/, guint /*flags*/, gpointer data) +vp_drag_sel_modified (Inkscape::Selection *selection, guint flags, gpointer data) { VPDrag *drag = (VPDrag *) data; - /*** - if (drag->local_change) { - drag->local_change = false; - } else { - drag->updateDraggers (); - } - ***/ drag->updateLines (); -} - -// auxiliary function -static GSList * -eliminate_remaining_boxes_of_persp_starting_from_list_position (GSList *boxes_to_do, const SP3DBox *start_box, const Perspective3D *persp) -{ - GSList *i = g_slist_find (boxes_to_do, start_box); - g_return_val_if_fail (i != NULL, boxes_to_do); - - SP3DBox *box; - GSList *successor; - - i = i->next; - while (i != NULL) { - successor = i->next; - box = SP_3DBOX (i->data); - if (persp->has_box (box)) { - boxes_to_do = g_slist_remove (boxes_to_do, box); - } - i = successor; - } - - return boxes_to_do; + //drag->updateBoxReprs(); + drag->updateBoxHandles (); // FIXME: Only update the handles of boxes on this dragger (not on all) + drag->updateDraggers (); } static bool have_VPs_of_same_perspective (VPDragger *dr1, VPDragger *dr2) { - Perspective3D *persp; - for (GSList *i = dr1->vps; i != NULL; i = i->next) { - persp = dr1->parent->document->get_persp_of_VP ((VanishingPoint *) i->data); - if (dr2->hasPerspective (persp)) { + for (std::list::iterator i = dr1->vps.begin(); i != dr1->vps.end(); ++i) { + if (dr2->hasPerspective ((*i).get_perspective())) { return true; } } @@ -187,7 +77,7 @@ have_VPs_of_same_perspective (VPDragger *dr1, VPDragger *dr2) } static void -vp_knot_moved_handler (SPKnot */*knot*/, NR::Point const *ppointer, guint state, gpointer data) +vp_knot_moved_handler (SPKnot *knot, NR::Point const *ppointer, guint state, gpointer data) { VPDragger *dragger = (VPDragger *) data; VPDrag *drag = dragger->parent; @@ -197,6 +87,60 @@ vp_knot_moved_handler (SPKnot */*knot*/, NR::Point const *ppointer, guint state, // FIXME: take from prefs double snap_dist = SNAP_DIST / inkscape_active_desktop()->current_zoom(); + /* + * We use dragging_started to indicate if we have already checked for the need to split Draggers up. + * This only has the purpose of avoiding costly checks in the routine below. + */ + if (!dragger->dragging_started && (state & GDK_SHIFT_MASK)) { + /* with Shift; if there is more than one box linked to this VP + we need to split it and create a new perspective */ + //g_print ("Number of boxes in dragger: %d\n", dragger->numberOfBoxes()); + if (dragger->numberOfBoxes() > 1) { // FIXME: Don't do anything if *all* boxes of a VP are selected + //g_print ("We need to split the VPDragger\n"); + std::set sel_vps = dragger->VPsOfSelectedBoxes(); + /** + g_print ("===== VPs of selected boxes: ===========================\n"); + for (std::set::iterator i = sel_vps.begin(); i != sel_vps.end(); ++i) { + (*i)->printPt(); + } + g_print ("========================================================\n"); + **/ + + std::list sel_boxes; + for (std::set::iterator vp = sel_vps.begin(); vp != sel_vps.end(); ++vp) { + // for each VP that has selected boxes: + Persp3D *old_persp = (*vp)->get_perspective(); + sel_boxes = (*vp)->selectedBoxes(sp_desktop_selection(inkscape_active_desktop())); + + // we create a new perspective ... + Persp3D *new_persp = persp3d_create_xml_element (dragger->parent->document, old_persp); + + /* ... unlink the boxes from the old one and + FIXME: We need to unlink the _un_selected boxes of each VP so that + the correct boxes are kept with the VP being moved */ + std::list bx_lst = persp3d_list_of_boxes(old_persp); + for (std::list::iterator i = bx_lst.begin(); i != bx_lst.end(); ++i) { + //g_print ("Iterating over box #%d\n", (*i)->my_counter); + if (std::find(sel_boxes.begin(), sel_boxes.end(), *i) == sel_boxes.end()) { + /* if a box in the VP is unselected, move it to the + newly created perspective so that it doesn't get dragged **/ + //g_print (" switching box #%d to new perspective.\n", (*i)->my_counter); + persp3d_remove_box (old_persp, *i); + persp3d_add_box (new_persp, *i); + gchar *href = g_strdup_printf("#%s", SP_OBJECT_REPR(new_persp)->attribute("id")); + SP_OBJECT_REPR(*i)->setAttribute("inkscape:perspectiveID", href); + g_free(href); + } + } + } + // FIXME: Do we need to create a new dragger as well? + dragger->updateZOrders (); + sp_document_done (sp_desktop_document (inkscape_active_desktop()), SP_VERB_CONTEXT_3DBOX, + _("Split vanishing points")); + return; + } + } + if (!(state & GDK_SHIFT_MASK)) { // without Shift; see if we need to snap to another dragger for (GList *di = dragger->parent->draggers; di != NULL; di = di->next) { @@ -207,13 +151,14 @@ vp_knot_moved_handler (SPKnot */*knot*/, NR::Point const *ppointer, guint state, continue; } - // update positions ... - for (GSList *j = dragger->vps; j != NULL; j = j->next) { - ((VanishingPoint *) j->data)->set_pos (d_new->point); + // update positions ... (this is needed so that the perspectives are detected as identical) + // FIXME: This is called a bit too often, isn't it? + for (std::list::iterator j = dragger->vps.begin(); j != dragger->vps.end(); ++j) { + (*j).set_pos(d_new->point); } + // ... join lists of VPs ... - // FIXME: Do we have to copy the second list (i.e, is it invalidated when dragger is deleted below)? - d_new->vps = g_slist_concat (d_new->vps, g_slist_copy (dragger->vps)); + d_new->vps.merge(dragger->vps); // ... delete old dragger ... drag->draggers = g_list_remove (drag->draggers, dragger); @@ -222,13 +167,12 @@ vp_knot_moved_handler (SPKnot */*knot*/, NR::Point const *ppointer, guint state, // ... and merge any duplicate perspectives d_new->mergePerspectives(); - + // TODO: Update the new merged dragger //d_new->updateKnotShape (); - d_new->updateTip (); + d_new->updateTip(); - d_new->reshapeBoxes (d_new->point, Box3D::XYZ); - d_new->updateBoxReprs (); + d_new->parent->updateBoxDisplays (); // FIXME: Only update boxes in current dragger! d_new->updateZOrders (); drag->updateLines (); @@ -237,120 +181,50 @@ vp_knot_moved_handler (SPKnot */*knot*/, NR::Point const *ppointer, guint state, // deleted according to changes in the svg representation, not based on any user input // as is currently the case. - //sp_document_done (sp_desktop_document (drag->desktop), SP_VERB_CONTEXT_3DBOX, - // _("Merge vanishing points")); + sp_document_done (sp_desktop_document (inkscape_active_desktop()), SP_VERB_CONTEXT_3DBOX, + _("Merge vanishing points")); return; } } } - dragger->point = p; - dragger->reshapeBoxes (p, Box3D::XYZ); - dragger->updateBoxReprs (); - dragger->updateZOrders (); + dragger->point = p; // FIXME: Brauchen wir dragger->point überhaupt? - drag->updateLines (); + dragger->updateVPs(p); + dragger->updateBoxDisplays(); + dragger->parent->updateBoxHandles (); // FIXME: Only update the handles of boxes on this dragger (not on all) + dragger->updateZOrders(); + + drag->updateLines(); - //drag->local_change = false; + dragger->dragging_started = true; } -/*** +/* helpful for debugging */ static void vp_knot_clicked_handler(SPKnot *knot, guint state, gpointer data) { VPDragger *dragger = (VPDragger *) data; + g_print ("\nVPDragger contains the following VPs: "); + for (std::list::iterator i = dragger->vps.begin(); i != dragger->vps.end(); ++i) { + g_print("%d (%d) ", (*i).my_counter, (*i).get_perspective()->my_counter); + } + g_print("\n"); } -***/ void -vp_knot_grabbed_handler (SPKnot */*knot*/, unsigned int state, gpointer data) +vp_knot_grabbed_handler (SPKnot *knot, unsigned int state, gpointer data) { VPDragger *dragger = (VPDragger *) data; VPDrag *drag = dragger->parent; drag->dragging = true; - - //sp_canvas_force_full_redraw_after_interruptions(dragger->parent->desktop->canvas, 5); - - if ((state & GDK_SHIFT_MASK) && !drag->hasEmptySelection()) { // FIXME: Is the second check necessary? - - if (drag->allBoxesAreSelected (dragger)) { - // if all of the boxes linked to dragger are selected, we don't need to split it - return; - } - - // we are Shift-dragging; unsnap if we carry more than one VP - - // FIXME: Should we distinguish between the following cases: - // 1) there are several VPs in a dragger - // 2) there is only a single VP but several boxes linked to it - // ? - // Or should we simply unlink all selected boxes? Currently we do the latter. - if (dragger->numberOfBoxes() > 1) { - // create a new dragger - VPDragger *dr_new = new VPDragger (drag, dragger->point, NULL); - drag->draggers = g_list_prepend (drag->draggers, dr_new); - - // move all the VPs from dragger to dr_new - dr_new->vps = dragger->vps; - dragger->vps = NULL; - - /* now we move all selected boxes back to the current dragger (splitting perspectives - if they also have unselected boxes) so that they are further reshaped during dragging */ - - GSList *boxes_to_do = drag->selectedBoxesWithVPinDragger (dr_new); - - for (GSList *i = boxes_to_do; i != NULL; i = i->next) { - SP3DBox *box = SP_3DBOX (i->data); - Perspective3D *persp = drag->document->get_persp_of_box (box); - VanishingPoint *vp = dr_new->getVPofPerspective (persp); - if (vp == NULL) { - g_warning ("VP is NULL. We should be okay, though.\n"); - } - if (persp->all_boxes_occur_in_list (boxes_to_do)) { - // if all boxes of persp are selected, we can simply move the VP from dr_new back to dragger - dr_new->removeVP (vp); - dragger->addVP (vp); - - // some cleaning up for efficiency - boxes_to_do = eliminate_remaining_boxes_of_persp_starting_from_list_position (boxes_to_do, box, persp); - } else { - /* otherwise the unselected boxes need to stay linked to dr_new; thus we - create a new perspective and link the VPs to the correct draggers */ - Perspective3D *persp_new = new Perspective3D (*persp); - drag->document->add_perspective (persp_new); - - Axis vp_axis = persp->get_axis_of_VP (vp); - dragger->addVP (persp_new->get_vanishing_point (vp_axis)); - std::pair rem_axes = get_remaining_axes (vp_axis); - drag->addDragger (persp->get_vanishing_point (rem_axes.first)); - drag->addDragger (persp->get_vanishing_point (rem_axes.second)); - - // now we move the selected boxes from persp to persp_new - GSList * selected_boxes_of_perspective = persp->boxes_occurring_in_list (boxes_to_do); - for (GSList *j = selected_boxes_of_perspective; j != NULL; j = j->next) { - persp->remove_box (SP_3DBOX (j->data)); - persp_new->add_box (SP_3DBOX (j->data)); - } - - // cleaning up - boxes_to_do = eliminate_remaining_boxes_of_persp_starting_from_list_position (boxes_to_do, box, persp); - } - } - - // TODO: Something is still wrong with updating the boxes' representations after snapping - //dr_new->updateBoxReprs (); - - dragger->updateTip(); - dr_new->updateTip(); - } - } } static void -vp_knot_ungrabbed_handler (SPKnot *knot, guint /*state*/, gpointer data) +vp_knot_ungrabbed_handler (SPKnot *knot, guint state, gpointer data) { VPDragger *dragger = (VPDragger *) data; @@ -358,16 +232,18 @@ vp_knot_ungrabbed_handler (SPKnot *knot, guint /*state*/, gpointer data) dragger->point_original = dragger->point = knot->pos; - /*** - VanishingPoint *vp; - for (GSList *i = dragger->vps; i != NULL; i = i->next) { - vp = (VanishingPoint *) i->data; - vp->set_pos (knot->pos); + dragger->dragging_started = false; + + for (std::list::iterator i = dragger->vps.begin(); i != dragger->vps.end(); ++i) { + (*i).set_pos (knot->pos); + (*i).updateBoxReprs(); + (*i).updatePerspRepr(); } - ***/ dragger->parent->updateDraggers (); - dragger->updateBoxReprs (); + //dragger->updateBoxReprs (); + dragger->parent->updateLines (); + dragger->parent->updateBoxHandles (); // TODO: Update box's paths and svg representation @@ -380,16 +256,41 @@ vp_knot_ungrabbed_handler (SPKnot *knot, guint /*state*/, gpointer data) _("3D box: Move vanishing point")); } -VPDragger::VPDragger(VPDrag *parent, NR::Point p, VanishingPoint *vp) +unsigned int VanishingPoint::global_counter = 0; + +// FIXME: Rename to something more meaningful! +void +VanishingPoint::set_pos(Proj::Pt2 const &pt) { + g_return_if_fail (_persp); + _persp->tmat.set_image_pt (_axis, pt); +} + +std::list +VanishingPoint::selectedBoxes(Inkscape::Selection *sel) { + std::list sel_boxes; + for (GSList const* i = sel->itemList(); i != NULL; i = i->next) { + if (!SP_IS_BOX3D(i->data)) + continue; + SPBox3D *box = SP_BOX3D(i->data); + if (this->hasBox(box)) { + sel_boxes.push_back (box); + } + } + return sel_boxes; +} + +VPDragger::VPDragger(VPDrag *parent, NR::Point p, VanishingPoint &vp) { - this->vps = NULL; + //this->vps = NULL; this->parent = parent; this->point = p; this->point_original = p; - if (vp->is_finite()) { + this->dragging_started = false; + + if (vp.is_finite()) { // create the knot this->knot = sp_knot_new (inkscape_active_desktop(), NULL); this->knot->setMode(SP_KNOT_MODE_XOR); @@ -403,9 +304,7 @@ VPDragger::VPDragger(VPDrag *parent, NR::Point p, VanishingPoint *vp) // 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); /*** @@ -425,9 +324,7 @@ VPDragger::~VPDragger() // disconnect signals g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_moved_handler), this); - /*** g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_clicked_handler), this); - ***/ g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_grabbed_handler), this); g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_ungrabbed_handler), this); /*** @@ -437,8 +334,8 @@ VPDragger::~VPDragger() /* unref should call destroy */ g_object_unref (G_OBJECT (this->knot)); - g_slist_free (this->vps); - this->vps = NULL; + //g_slist_free (this->vps); + //this->vps = NULL; } /** @@ -453,26 +350,22 @@ VPDragger::updateTip () } guint num = this->numberOfBoxes(); - if (g_slist_length (this->vps) == 1) { - VanishingPoint *vp = (VanishingPoint *) this->vps->data; - switch (vp->state) { - case VP_FINITE: - this->knot->tip = g_strdup_printf (ngettext("Finite vanishing point shared by %d box", - "Finite vanishing point shared by %d boxes; drag with Shift to separate selected box(es)", - num), - num); - break; - case VP_INFINITE: - // This won't make sense any more when infinite VPs are not shown on the canvas, - // but currently we update the status message anyway - this->knot->tip = g_strdup_printf (ngettext("Infinite vanishing point shared by %d box", - "Infinite vanishing point shared by %d boxes; drag with Shift to separate selected box(es)", - num), - num); - break; + if (this->vps.size() == 1) { + if (this->vps.front().is_finite()) { + this->knot->tip = g_strdup_printf (ngettext("Finite vanishing point shared by %d box", + "Finite vanishing point shared by %d boxes; drag with Shift to separate selected box(es)", + num), + num); + } else { + // This won't make sense any more when infinite VPs are not shown on the canvas, + // but currently we update the status message anyway + this->knot->tip = g_strdup_printf (ngettext("Infinite vanishing point shared by %d box", + "Infinite vanishing point shared by %d boxes; drag with Shift to separate selected box(es)", + num), + num); } } else { - int length = g_slist_length (this->vps); + int length = this->vps.size(); char *desc1 = g_strdup_printf ("Collection of %d vanishing points ", length); char *desc2 = g_strdup_printf (ngettext("shared by %d box; drag with Shift to separate selected box(es)", "shared by %d boxes; drag with Shift to separate selected box(es)", @@ -485,76 +378,83 @@ VPDragger::updateTip () } /** - * Adds a vanishing point to the dragger (also updates the position) + * Adds a vanishing point to the dragger (also updates the position if necessary); + * the perspective is stored separately, too, for efficiency in updating boxes. */ void -VPDragger::addVP (VanishingPoint *vp) +VPDragger::addVP (VanishingPoint &vp, bool update_pos) { - if (vp == NULL) { - return; - } - if (!vp->is_finite() || g_slist_find (this->vps, vp)) { - // don't add infinite VPs, and don't add the same VP twice + //if (!vp.is_finite() || g_slist_find (this->vps, vp)) { + if (!vp.is_finite() || std::find (vps.begin(), vps.end(), vp) != vps.end()) { + // don't add infinite VPs; don't add the same VP twice return; } - vp->set_pos (this->point); - this->vps = g_slist_prepend (this->vps, vp); + if (update_pos) { + vp.set_pos (this->point); + } + //this->vps = g_slist_prepend (this->vps, vp); + this->vps.push_front (vp); + //this->persps.include (vp.get_perspective()); this->updateTip(); } void -VPDragger::removeVP (VanishingPoint *vp) +VPDragger::removeVP (VanishingPoint const &vp) { - if (vp == NULL) { - g_print ("NULL vanishing point will not be removed.\n"); - return; + std::list::iterator i = std::find (this->vps.begin(), this->vps.end(), vp); + if (i != this->vps.end()) { + this->vps.erase (i); } - g_assert (this->vps != NULL); - this->vps = g_slist_remove (this->vps, vp); - this->updateTip(); } -// returns the VP contained in the dragger that belongs to persp VanishingPoint * -VPDragger::getVPofPerspective (Perspective3D *persp) -{ - for (GSList *i = vps; i != NULL; i = i->next) { - if (persp->has_vanishing_point ((VanishingPoint *) i->data)) { - return ((VanishingPoint *) i->data); +VPDragger::findVPWithBox (SPBox3D *box) { + for (std::list::iterator vp = vps.begin(); vp != vps.end(); ++vp) { + if ((*vp).hasBox(box)) { + return &(*vp); } } return NULL; } -bool -VPDragger::hasBox(const SP3DBox *box) -{ - for (GSList *i = this->vps; i != NULL; i = i->next) { - if (parent->document->get_persp_of_VP ((VanishingPoint *) i->data)->has_box (box)) return true; +std::set +VPDragger::VPsOfSelectedBoxes() { + std::set sel_vps; + VanishingPoint *vp; + // FIXME: Should we take the selection from the parent VPDrag? I guess it shouldn't make a difference. + Inkscape::Selection *sel = sp_desktop_selection(inkscape_active_desktop()); + for (GSList const* i = sel->itemList(); i != NULL; i = i->next) { + if (!SP_IS_BOX3D(i->data)) + continue; + SPBox3D *box = SP_BOX3D(i->data); + vp = this->findVPWithBox(box); + if (vp) { + sel_vps.insert (vp); + } } - return false; + return sel_vps; } guint VPDragger::numberOfBoxes () { guint num = 0; - for (GSList *i = this->vps; i != NULL; i = i->next) { - num += parent->document->get_persp_of_VP ((VanishingPoint *) i->data)->number_of_boxes (); + for (std::list::iterator vp = vps.begin(); vp != vps.end(); ++vp) { + num += (*vp).numberOfBoxes(); } return num; } bool -VPDragger::hasPerspective (const Perspective3D *persp) +VPDragger::hasPerspective (const Persp3D *persp) { - for (GSList *i = this->vps; i != NULL; i = i->next) { - if (*persp == *parent->document->get_persp_of_VP ((VanishingPoint *) i->data)) { + for (std::list::iterator i = vps.begin(); i != vps.end(); ++i) { + if (persp3d_perspectives_coincide(persp, (*i).get_perspective())) { return true; - } + } } return false; } @@ -562,50 +462,56 @@ VPDragger::hasPerspective (const Perspective3D *persp) void VPDragger::mergePerspectives () { - Perspective3D *persp1, *persp2; - GSList * successor = NULL; - for (GSList *i = this->vps; i != NULL; i = i->next) { - persp1 = parent->document->get_persp_of_VP ((VanishingPoint *) i->data); - for (GSList *j = i->next; j != NULL; j = successor) { - // if the perspective is deleted, the VP is invalidated, too, so we must store its successor beforehand - successor = j->next; - persp2 = parent->document->get_persp_of_VP ((VanishingPoint *) j->data); - if (*persp1 == *persp2) { - persp1->absorb (persp2); // persp2 is deleted; hopefully this doesn't screw up the list of vanishing points and thus the loops + Persp3D *persp1, *persp2; + for (std::list::iterator i = vps.begin(); i != vps.end(); ++i) { + persp1 = (*i).get_perspective(); + for (std::list::iterator j = i; j != vps.end(); ++j) { + persp2 = (*j).get_perspective(); + if (persp1 == persp2) { + /* don't merge a perspective with itself */ + continue; + } + if (persp3d_perspectives_coincide(persp1,persp2)) { + /* if perspectives coincide but are not the same, merge them */ + persp3d_absorb(persp1, persp2); + + this->parent->swap_perspectives_of_VPs(persp2, persp1); + + SP_OBJECT(persp2)->deleteObject(false); } } } } void -VPDragger::reshapeBoxes (NR::Point const &p, Box3D::Axis /*axes*/) +VPDragger::updateBoxDisplays () { - Perspective3D *persp; - for (GSList const* i = this->vps; i != NULL; i = i->next) { - VanishingPoint *vp = (VanishingPoint *) i->data; - // TODO: We can extract the VP directly from the box's perspective. Is that vanishing point identical to 'vp'? - // Or is there duplicated information? If so, remove it and simplify the whole construction! - vp->set_pos(p); - persp = parent->document->get_persp_of_VP (vp); - Box3D::Axis axis = persp->get_axis_of_VP (vp); - parent->document->get_persp_of_VP (vp)->reshape_boxes (axis); // FIXME: we should only update the direction of the VP + for (std::list::iterator i = this->vps.begin(); i != this->vps.end(); ++i) { + (*i).updateBoxDisplays(); } - parent->updateBoxHandles(); } void -VPDragger::updateBoxReprs () +VPDragger::updateVPs (NR::Point const &pt) { - for (GSList *i = this->vps; i != NULL; i = i->next) { - parent->document->get_persp_of_VP ((VanishingPoint *) i->data)->update_box_reprs (); + for (std::list::iterator i = this->vps.begin(); i != this->vps.end(); ++i) { + (*i).set_pos (pt); } } void VPDragger::updateZOrders () { - for (GSList *i = this->vps; i != NULL; i = i->next) { - parent->document->get_persp_of_VP ((VanishingPoint *) i->data)->update_z_orders (); + for (std::list::iterator i = this->vps.begin(); i != this->vps.end(); ++i) { + persp3d_update_z_orders((*i).get_perspective()); + } +} + +void +VPDragger::printVPs() { + g_print ("VPDragger at position (%f, %f):\n", point[NR::X], point[NR::Y]); + for (std::list::iterator i = this->vps.begin(); i != this->vps.end(); ++i) { + g_print (" VP %s\n", (*i).axisString()); } } @@ -620,7 +526,6 @@ VPDrag::VPDrag (SPDocument *document) this->front_or_rear_lines = 0x1; //this->selected = NULL; - this->local_change = false; this->dragging = false; this->sel_changed_connection = this->selection->connectChanged( @@ -665,13 +570,9 @@ VPDrag::getDraggerFor (VanishingPoint const &vp) { for (GList const* i = this->draggers; i != NULL; i = i->next) { VPDragger *dragger = (VPDragger *) i->data; - for (GSList const* j = dragger->vps; j != NULL; j = j->next) { - VanishingPoint *vp2 = (VanishingPoint *) j->data; - g_assert (vp2 != NULL); - + for (std::list::iterator j = dragger->vps.begin(); j != dragger->vps.end(); ++j) { // TODO: Should we compare the pointers or the VPs themselves!?!?!?! - //if ((*vp2) == vp) { - if (vp2 == &vp) { + if (*j == vp) { return (dragger); } } @@ -679,6 +580,17 @@ VPDrag::getDraggerFor (VanishingPoint const &vp) return NULL; } +void +VPDrag::printDraggers () +{ + g_print ("=== VPDrag info: =================================\n"); + for (GList const* i = this->draggers; i != NULL; i = i->next) { + ((VPDragger *) i->data)->printVPs(); + g_print ("========\n"); + } + g_print ("=================================================\n"); +} + /** * Regenerates the draggers list from the current selection; is called when selection is changed or modified */ @@ -687,11 +599,6 @@ VPDrag::updateDraggers () { if (this->dragging) return; - /*** - while (selected) { - selected = g_list_remove(selected, selected->data); - } - ***/ // delete old draggers for (GList const* i = this->draggers; i != NULL; i = i->next) { delete ((VPDragger *) i->data); @@ -703,15 +610,14 @@ VPDrag::updateDraggers () for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) { SPItem *item = SP_ITEM(i->data); - //SPStyle *style = SP_OBJECT_STYLE (item); + if (!SP_IS_BOX3D (item)) continue; + SPBox3D *box = SP_BOX3D (item); - if (!SP_IS_3DBOX (item)) continue; - SP3DBox *box = SP_3DBOX (item); - - Box3D::Perspective3D *persp = document->get_persp_of_box (box); - addDragger (persp->get_vanishing_point(Box3D::X)); - addDragger (persp->get_vanishing_point(Box3D::Y)); - addDragger (persp->get_vanishing_point(Box3D::Z)); + VanishingPoint vp; + for (int i = 0; i < 3; ++i) { + vp.set (box->persp_ref->getObject(), Proj::axes[i]); + addDragger (vp); + } } } @@ -735,12 +641,12 @@ VPDrag::updateLines () 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); + if (!SP_IS_BOX3D(i->data)) continue; + SPBox3D *box = SP_BOX3D (i->data); - this->drawLinesForFace (box, Box3D::X); - this->drawLinesForFace (box, Box3D::Y); - this->drawLinesForFace (box, Box3D::Z); + this->drawLinesForFace (box, Proj::X); + this->drawLinesForFace (box, Proj::Y); + this->drawLinesForFace (box, Proj::Z); } } @@ -748,17 +654,17 @@ void VPDrag::updateBoxHandles () { // FIXME: Is there a way to update the knots without accessing the - // statically linked function knotholder_update_knots? + // (previously) statically linked function knotholder_update_knots? GSList *sel = (GSList *) selection->itemList(); + if (!sel) + return; // no selection + 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) { @@ -766,28 +672,52 @@ VPDrag::updateBoxHandles () } } +void +VPDrag::updateBoxReprs () +{ + for (GList *i = this->draggers; i != NULL; i = i->next) { + VPDragger *dragger = (VPDragger *) i->data; + for (std::list::iterator i = dragger->vps.begin(); i != dragger->vps.end(); ++i) { + (*i).updateBoxReprs(); + } + } +} + +void +VPDrag::updateBoxDisplays () +{ + for (GList *i = this->draggers; i != NULL; i = i->next) { + VPDragger *dragger = (VPDragger *) i->data; + for (std::list::iterator i = dragger->vps.begin(); i != dragger->vps.end(); ++i) { + (*i).updateBoxDisplays(); + } + } +} + + /** * 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) +VPDrag::drawLinesForFace (const SPBox3D *box, Proj::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; + case Proj::X: color = VP_LINE_COLOR_STROKE_X; break; + case Proj::Y: color = VP_LINE_COLOR_STROKE_Y; break; + case Proj::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); + box3d_corners_for_PLs (box, axis, corner1, corner2, corner3, corner4); - VanishingPoint *vp = document->get_persp_of_box (box)->get_vanishing_point (axis); - if (vp->is_finite()) { + g_return_if_fail (box->persp_ref->getObject()); + Proj::Pt2 vp = persp3d_get_VP (box->persp_ref->getObject(), axis); + if (vp.is_finite()) { // draw perspective lines for finite VPs - NR::Point pt = vp->get_pos(); + NR::Point pt = vp.affine(); if (this->front_or_rear_lines & 0x1) { // draw 'front' perspective lines this->addLine (corner1, pt, color); @@ -801,7 +731,7 @@ VPDrag::drawLinesForFace (const SP3DBox *box, Box3D::Axis axis) //, guint corner } else { // draw perspective lines for infinite VPs NR::Maybe pt1, pt2, pt3, pt4; - Box3D::Perspective3D *persp = this->document->get_persp_of_box (box); + Persp3D *persp = box->persp_ref->getObject(); SPDesktop *desktop = inkscape_active_desktop (); // FIXME: Store the desktop in VPDrag Box3D::PerspectiveLine pl (corner1, axis, persp); pt1 = pl.intersection_with_viewbox(desktop); @@ -830,53 +760,21 @@ VPDrag::drawLinesForFace (const SP3DBox *box, Box3D::Axis axis) //, guint corner this->addLine (corner4, *pt4, color); } } - -} - -/** - * Returns true if all boxes that are linked to a VP in the dragger are selected - */ -bool -VPDrag::allBoxesAreSelected (VPDragger *dragger) { - GSList *selected_boxes = (GSList *) dragger->parent->selection->itemList(); - for (GSList *i = dragger->vps; i != NULL; i = i->next) { - if (!document->get_persp_of_VP ((VanishingPoint *) i->data)->all_boxes_occur_in_list (selected_boxes)) { - return false; - } - } - return true; -} - -GSList * -VPDrag::selectedBoxesWithVPinDragger (VPDragger *dragger) -{ - GSList *sel_boxes = g_slist_copy ((GSList *) dragger->parent->selection->itemList()); - for (GSList const *i = sel_boxes; i != NULL; i = i->next) { - SP3DBox *box = SP_3DBOX (i->data); - if (!dragger->hasBox (box)) { - sel_boxes = g_slist_remove (sel_boxes, box); - } - } - return sel_boxes; } - /** * If there already exists a dragger within MERGE_DIST of p, add the VP to it; * otherwise create new dragger and add it to draggers list + * We also store the corresponding perspective in case it is not already present. */ void -VPDrag::addDragger (VanishingPoint *vp) +VPDrag::addDragger (VanishingPoint &vp) { - if (vp == NULL) { - g_print ("Warning: The VP in addDragger is already NULL. Aborting.\n)"); - g_assert (vp != NULL); - } - if (!vp->is_finite()) { + if (!vp.is_finite()) { // don't create draggers for infinite vanishing points return; } - NR::Point p = vp->get_pos(); + NR::Point p = vp.get_pos(); for (GList *i = this->draggers; i != NULL; i = i->next) { VPDragger *dragger = (VPDragger *) i->data; @@ -893,6 +791,20 @@ VPDrag::addDragger (VanishingPoint *vp) this->draggers = g_list_append (this->draggers, new_dragger); } +void +VPDrag::swap_perspectives_of_VPs(Persp3D *persp2, Persp3D *persp1) +{ + // iterate over all VP in all draggers and replace persp2 with persp1 + for (GList *i = this->draggers; i != NULL; i = i->next) { + for (std::list::iterator j = ((VPDragger *) (i->data))->vps.begin(); + j != ((VPDragger *) (i->data))->vps.end(); ++j) { + if ((*j).get_perspective() == persp2) { + (*j).set_perspective(persp1); + } + } + } +} + /** Create a line from p1 to p2 and add it to the lines list */ @@ -907,8 +819,8 @@ VPDrag::addLine (NR::Point p1, NR::Point p2, guint32 rgba) this->lines = g_slist_append (this->lines, line); } -} // namespace Box3D - +} // namespace Box3D + /* Local Variables: mode:c++ diff --git a/src/vanishing-point.h b/src/vanishing-point.h index 3dde39385..47c11be18 100644 --- a/src/vanishing-point.h +++ b/src/vanishing-point.h @@ -12,14 +12,19 @@ #ifndef SEEN_VANISHING_POINT_H #define SEEN_VANISHING_POINT_H +#include #include "libnr/nr-point.h" #include "knot.h" #include "selection.h" #include "axis-manip.h" +#include "inkscape.h" +#include "persp3d.h" +#include "box3d.h" +#include "persp3d-reference.h" #include "line-geometry.h" // TODO: Remove this include as soon as we don't need create_canvas_(point|line) any more. -class SP3DBox; +class SPBox3D; namespace Box3D { @@ -28,59 +33,98 @@ enum VPState { VP_INFINITE // perspective lines are parallel }; -// FIXME: Store the Axis of the VP inside the class -class VanishingPoint : public NR::Point { +/* VanishingPoint is a simple wrapper class to easily extract VP data from perspectives. + * A VanishingPoint represents a VP in a certain direction (X, Y, Z) of a single perspective. + * In particular, it can potentially have more than one box linked to it (although in facth they + * are rather linked to the parent perspective). + */ +// FIXME: Don't store the box in the VP but rather the perspective (and link the box to it)!! +class VanishingPoint { public: - inline VanishingPoint() : NR::Point() {}; - /*** - inline VanishingPoint(NR::Point const &pt, NR::Point const &ref = NR::Point(0,0)) - : NR::Point (pt), - ref_pt (ref), - v_dir (pt[NR::X] - ref[NR::X], pt[NR::Y] - ref[NR::Y]) {} - inline VanishingPoint(NR::Coord x, NR::Coord y, NR::Point const &ref = NR::Point(0,0)) - : NR::Point (x, y), - ref_pt (ref), - v_dir (x - ref[NR::X], y - ref[NR::Y]) {} - ***/ - VanishingPoint(NR::Point const &pt, NR::Point const &inf_dir, VPState st); - VanishingPoint(NR::Point const &pt); - VanishingPoint(NR::Point const &dir, VPState const state); - VanishingPoint(NR::Point const &pt, NR::Point const &direction); - VanishingPoint(NR::Coord x, NR::Coord y); - VanishingPoint(NR::Coord x, NR::Coord y, VPState const state); - VanishingPoint(NR::Coord x, NR::Coord y, NR::Coord dir_x, NR::Coord dir_y); - VanishingPoint(VanishingPoint const &rhs); - ~VanishingPoint(); - - bool operator== (VanishingPoint const &other); - - inline NR::Point get_pos() const { return NR::Point ((*this)[NR::X], (*this)[NR::Y]); } - inline double get_angle() const { return NR::atan2 (this->v_dir) * 180/M_PI; } // return angle of infinite direction is in degrees - inline void set_pos(NR::Point const &pt) { (*this)[NR::X] = pt[NR::X]; - (*this)[NR::Y] = pt[NR::Y]; } - inline void set_pos(const double pt_x, const double pt_y) { (*this)[NR::X] = pt_x; - (*this)[NR::Y] = pt_y; } - inline void set_infinite_direction (const NR::Point dir) { v_dir = dir; } - inline void set_infinite_direction (const double dir_x, const double dir_y) { v_dir = NR::Point (dir_x, dir_y); } - - bool is_finite() const; - VPState toggle_parallel(); - void draw(Box3D::Axis const axis); // Draws a point on the canvas if state == VP_FINITE - //inline VPState state() { return state; } - - VPState state; - //NR::Point ref_pt; // point of reference to compute the direction of parallel lines - NR::Point v_dir; // direction of perslective lines if the VP has state == VP_INFINITE - + VanishingPoint() : my_counter(VanishingPoint::global_counter++), _persp(NULL), _axis(Proj::NONE) {} + VanishingPoint(Persp3D *persp, Proj::Axis axis) : my_counter(VanishingPoint::global_counter++), _persp(persp), _axis(axis) {} + VanishingPoint(const VanishingPoint &other) : my_counter(VanishingPoint::global_counter++), _persp(other._persp), _axis(other._axis) {} + + inline VanishingPoint &operator=(VanishingPoint const &rhs) { + _persp = rhs._persp; + _axis = rhs._axis; + return *this; + } + inline bool operator==(VanishingPoint const &rhs) const { + /* vanishing points coincide if they belong to the same perspective */ + return (_persp == rhs._persp && _axis == rhs._axis); + } + + inline bool operator<(VanishingPoint const &rhs) const { + return my_counter < rhs.my_counter; + } + + inline void set(Persp3D *persp, Proj::Axis axis) { + _persp = persp; + _axis = axis; + } + void set_pos(Proj::Pt2 const &pt); + inline bool is_finite() const { + g_return_val_if_fail (_persp, false); + return persp3d_get_VP (_persp, _axis).is_finite(); + } + inline NR::Point get_pos() const { + g_return_val_if_fail (_persp, NR::Point (NR_HUGE, NR_HUGE)); + return persp3d_get_VP (_persp,_axis).affine(); + } + inline Persp3D * get_perspective() const { + return _persp; + } + inline Persp3D * set_perspective(Persp3D *persp) { + return _persp = persp; + } + + inline bool hasBox (SPBox3D *box) { + return persp3d_has_box(_persp, box); + } + inline unsigned int numberOfBoxes() const { + return persp3d_num_boxes(_persp); + } + + /* returns all selected boxes sharing this perspective */ + std::list selectedBoxes(Inkscape::Selection *sel); + + inline void updateBoxDisplays() const { + g_return_if_fail (_persp); + persp3d_update_box_displays(_persp); + } + inline void updateBoxReprs() const { + g_return_if_fail (_persp); + persp3d_update_box_reprs(_persp); + } + inline void updatePerspRepr() const { + g_return_if_fail (_persp); + SP_OBJECT(_persp)->updateRepr(SP_OBJECT_WRITE_EXT); + } + inline void printPt() const { + g_return_if_fail (_persp); + persp3d_get_VP (_persp, _axis).print(""); + } + inline gchar *axisString () { return Proj::string_from_axis (_axis); } + + unsigned int my_counter; + static unsigned int global_counter; // FIXME: Only to implement operator< so that we can merge lists. Do this in a better way!! private: + Persp3D *_persp; + Proj::Axis _axis; }; -class Perspective3D; class VPDrag; +struct less_ptr : public std::binary_function { + bool operator()(VanishingPoint *vp1, VanishingPoint *vp2) { + return GPOINTER_TO_INT(vp1) < GPOINTER_TO_INT(vp2); + } +}; + struct VPDragger { public: - VPDragger(VPDrag *parent, NR::Point p, VanishingPoint *vp); + VPDragger(VPDrag *parent, NR::Point p, VanishingPoint &vp); ~VPDragger(); VPDrag *parent; @@ -91,24 +135,27 @@ public: // position of the knot before it began to drag; updated when released NR::Point point_original; - GSList *vps; // the list of vanishing points + bool dragging_started; + + std::list vps; - void addVP(VanishingPoint *vp); - void removeVP(VanishingPoint *vp); - /* returns the VP of the dragger that belongs to the given perspective */ - VanishingPoint *getVPofPerspective (Perspective3D *persp); + void addVP(VanishingPoint &vp, bool update_pos = false); + void removeVP(const VanishingPoint &vp); void updateTip(); - bool hasBox (const SP3DBox *box); guint numberOfBoxes(); // the number of boxes linked to all VPs of the dragger + VanishingPoint *findVPWithBox(SPBox3D *box); + std::set VPsOfSelectedBoxes(); - bool hasPerspective (const Perspective3D *perps); - void mergePerspectives (); // remove duplicate perspectives + bool hasPerspective(const Persp3D *persp); + void mergePerspectives(); // remove duplicate perspectives - void reshapeBoxes(NR::Point const &p, Box3D::Axis axes); - void updateBoxReprs(); + void updateBoxDisplays(); + void updateVPs(NR::Point const &pt); void updateZOrders(); + + void printVPs(); }; struct VPDrag { @@ -118,19 +165,24 @@ public: VPDragger *getDraggerFor (VanishingPoint const &vp); - //void grabKnot (VanishingPoint const &vp, gint x, gint y, guint32 etime); - - bool local_change; bool dragging; SPDocument *document; GList *draggers; GSList *lines; + void printDraggers(); // convenience for debugging + /* + * FIXME: Should the following functions be merged? + * Also, they should make use of the info in a VanishingPoint structure (regarding boxes + * and perspectives) rather than each time iterating over the whole list of selected items? + */ void updateDraggers (); void updateLines (); void updateBoxHandles (); - void drawLinesForFace (const SP3DBox *box, Box3D::Axis axis); //, guint corner1, guint corner2, guint corner3, guint corner4); + void updateBoxReprs (); + void updateBoxDisplays (); + void drawLinesForFace (const SPBox3D *box, Proj::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 */ @@ -142,7 +194,9 @@ public: // FIXME: Should this be private? (It's the case with the corresponding function in gradient-drag.h) // But vp_knot_grabbed_handler - void addDragger (VanishingPoint *vp); + void addDragger (VanishingPoint &vp); + + void swap_perspectives_of_VPs(Persp3D *persp2, Persp3D *persp1); private: //void deselect_all(); @@ -157,15 +211,6 @@ private: } // namespace Box3D -/** A function to print out the VanishingPoint (prints the coordinates) **/ -/*** -inline std::ostream &operator<< (std::ostream &out_file, const VanishingPoint &vp) { - out_file << vp; - return out_file; -} -***/ - - #endif /* !SEEN_VANISHING_POINT_H */ /* diff --git a/src/verbs.h b/src/verbs.h index be9405f9b..a9abc02ed 100644 --- a/src/verbs.h +++ b/src/verbs.h @@ -143,6 +143,7 @@ enum { SP_VERB_CONTEXT_TWEAK, SP_VERB_CONTEXT_RECT, SP_VERB_CONTEXT_3DBOX, + //SP_VERB_CONTEXT_BOX3D, SP_VERB_CONTEXT_ARC, SP_VERB_CONTEXT_STAR, SP_VERB_CONTEXT_SPIRAL, @@ -161,6 +162,7 @@ enum { SP_VERB_CONTEXT_TWEAK_PREFS, SP_VERB_CONTEXT_RECT_PREFS, SP_VERB_CONTEXT_3DBOX_PREFS, + //SP_VERB_CONTEXT_BOX3D_PREFS, SP_VERB_CONTEXT_ARC_PREFS, SP_VERB_CONTEXT_STAR_PREFS, SP_VERB_CONTEXT_SPIRAL_PREFS, diff --git a/src/widgets/toolbox.cpp b/src/widgets/toolbox.cpp index 47cb78cb2..9e9686e77 100644 --- a/src/widgets/toolbox.cpp +++ b/src/widgets/toolbox.cpp @@ -105,7 +105,7 @@ static void sp_zoom_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainA static void sp_star_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder); static void sp_arc_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder); static void sp_rect_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder); -static void sp_3dbox_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder); +static void box3d_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder); static void sp_spiral_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder); static void sp_pencil_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder); static void sp_pen_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder); @@ -129,7 +129,7 @@ static struct { { "SPTweakContext", "tweak_tool", SP_VERB_CONTEXT_TWEAK, SP_VERB_CONTEXT_TWEAK_PREFS }, { "SPZoomContext", "zoom_tool", SP_VERB_CONTEXT_ZOOM, SP_VERB_CONTEXT_ZOOM_PREFS }, { "SPRectContext", "rect_tool", SP_VERB_CONTEXT_RECT, SP_VERB_CONTEXT_RECT_PREFS }, -// { "SP3DBoxContext", "3dbox_tool", SP_VERB_CONTEXT_3DBOX, SP_VERB_CONTEXT_3DBOX_PREFS }, + { "Box3DContext", "3dbox_tool", SP_VERB_CONTEXT_3DBOX, SP_VERB_CONTEXT_3DBOX_PREFS }, { "SPArcContext", "arc_tool", SP_VERB_CONTEXT_ARC, SP_VERB_CONTEXT_ARC_PREFS }, { "SPStarContext", "star_tool", SP_VERB_CONTEXT_STAR, SP_VERB_CONTEXT_STAR_PREFS }, { "SPSpiralContext", "spiral_tool", SP_VERB_CONTEXT_SPIRAL, SP_VERB_CONTEXT_SPIRAL_PREFS }, @@ -166,7 +166,7 @@ static struct { SP_VERB_CONTEXT_STAR_PREFS, "tools.shapes.star", _("Style of new stars")}, { "SPRectContext", "rect_toolbox", 0, sp_rect_toolbox_prep, "RectToolbar", SP_VERB_CONTEXT_RECT_PREFS, "tools.shapes.rect", _("Style of new rectangles")}, - { "SP3DBoxContext", "3dbox_toolbox", 0, sp_3dbox_toolbox_prep, "3DBoxToolbar", + { "Box3DContext", "3dbox_toolbox", 0, box3d_toolbox_prep, "3DBoxToolbar", SP_VERB_CONTEXT_3DBOX_PREFS, "tools.shapes.3dbox", _("Style of new 3D boxes")}, { "SPArcContext", "arc_toolbox", 0, sp_arc_toolbox_prep, "ArcToolbar", SP_VERB_CONTEXT_ARC_PREFS, "tools.shapes.arc", _("Style of new ellipses")}, @@ -2343,26 +2343,35 @@ static void sp_rect_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions //## 3D Box ## //######################## -static void sp_3dbox_toggle_vp_changed (GtkToggleAction */*act*/, GObject *dataKludge, Box3D::Axis axis) +static void box3d_toggle_vp_changed (GtkToggleAction *act, GObject *dataKludge, Proj::Axis axis) { SPDesktop *desktop = (SPDesktop *) g_object_get_data (dataKludge, "desktop"); SPDocument *document = sp_desktop_document (desktop); - Box3D::Perspective3D *persp = document->current_perspective; + // FIXME: Make sure document->current_persp3d is set correctly! + Persp3D *persp = document->current_persp3d; - g_return_if_fail (is_single_axis_direction (axis)); g_return_if_fail (persp); - persp->toggle_boxes (axis); + // quit if run by the attr_changed listener + if (g_object_get_data(dataKludge, "freeze")) { + return; + } + + // in turn, prevent listener from responding + g_object_set_data(dataKludge, "freeze", GINT_TO_POINTER(TRUE)); + + persp3d_set_VP_state(persp, axis, gtk_toggle_action_get_active(act) ? Proj::INFINITE : Proj::FINITE); - gchar *str; + // FIXME: Can we merge this functionality with the one in box3d_persp_tb_event_attr_changed()? + gchar *str; switch (axis) { - case Box3D::X: + case Proj::X: str = g_strdup ("box3d_angle_x_action"); break; - case Box3D::Y: + case Proj::Y: str = g_strdup ("box3d_angle_y_action"); break; - case Box3D::Z: + case Proj::Z: str = g_strdup ("box3d_angle_z_action"); break; default: @@ -2370,67 +2379,77 @@ static void sp_3dbox_toggle_vp_changed (GtkToggleAction */*act*/, GObject *dataK } GtkAction* angle_action = GTK_ACTION (g_object_get_data (dataKludge, str)); if (angle_action) { - gtk_action_set_sensitive (angle_action, !persp->get_vanishing_point (axis)->is_finite() ); + gtk_action_set_sensitive (angle_action, !persp3d_VP_is_finite(persp, axis)); } - // FIXME: Given how it is realized in the other tools, this is probably not the right way to do it, - // but without the if construct, we get continuous segfaults. Needs further investigation. - if (sp_document_get_undo_sensitive(sp_desktop_document(desktop))) { - sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_3DBOX, - _("3D Box: Change perspective")); - } + sp_document_maybe_done(sp_desktop_document(desktop), "toggle_vp", SP_VERB_CONTEXT_3DBOX, + _("3D Box: Toggle VP")); + //sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_3DBOX,_("3D Box: Toggle VP")); + + g_object_set_data(dataKludge, "freeze", GINT_TO_POINTER(FALSE)); } -static void sp_3dbox_toggle_vp_x_changed(GtkToggleAction *act, GObject *dataKludge) +static void box3d_toggle_vp_x_changed(GtkToggleAction *act, GObject *dataKludge) { - sp_3dbox_toggle_vp_changed (act, dataKludge, Box3D::X); + box3d_toggle_vp_changed (act, dataKludge, Proj::X); } -static void sp_3dbox_toggle_vp_y_changed(GtkToggleAction *act, GObject *dataKludge) +static void box3d_toggle_vp_y_changed(GtkToggleAction *act, GObject *dataKludge) { - sp_3dbox_toggle_vp_changed (act, dataKludge, Box3D::Y); + box3d_toggle_vp_changed (act, dataKludge, Proj::Y); } -static void sp_3dbox_toggle_vp_z_changed(GtkToggleAction *act, GObject *dataKludge) +static void box3d_toggle_vp_z_changed(GtkToggleAction *act, GObject *dataKludge) { - sp_3dbox_toggle_vp_changed (act, dataKludge, Box3D::Z); + box3d_toggle_vp_changed (act, dataKludge, Proj::Z); } -static void sp_3dbox_vp_angle_changed(GtkAdjustment *adj, GObject *dataKludge, Box3D::Axis axis ) +static void box3d_vp_angle_changed(GtkAdjustment *adj, GObject *dataKludge, Proj::Axis axis ) { SPDesktop *desktop = (SPDesktop *) g_object_get_data(dataKludge, "desktop"); - Box3D::Perspective3D *persp = sp_desktop_document (desktop)->current_perspective; + Persp3D *persp = sp_desktop_document (desktop)->current_persp3d; - if (persp) { - double angle = adj->value * M_PI/180; - persp->set_infinite_direction (axis, NR::Point (cos (angle), sin (angle))); + // quit if run by the attr_changed listener + if (g_object_get_data(dataKludge, "freeze")) { + return; + } - // FIXME: See comment above; without the if construct we get segfaults during undo. - if (sp_document_get_undo_sensitive(sp_desktop_document(desktop))) { - sp_document_maybe_done(sp_desktop_document(desktop), "perspectiveangle", SP_VERB_CONTEXT_3DBOX, - _("3D Box: Change perspective")); + // in turn, prevent listener from responding + g_object_set_data(dataKludge, "freeze", GINT_TO_POINTER(TRUE)); + + if (persp) { + double angle = adj->value; + // FIXME: Shouldn't we set the angle via the SVG attributes of the perspective instead of directly? + if (persp3d_VP_is_finite(persp, axis)) { + return; } + persp->tmat.set_infinite_direction (axis, angle); + persp3d_update_box_reprs (persp); + + sp_document_maybe_done(sp_desktop_document(desktop), "perspectiveangle", SP_VERB_CONTEXT_3DBOX, + _("3D Box: Change perspective")); } - //g_object_set_data(G_OBJECT(dataKludge), "freeze", GINT_TO_POINTER(FALSE)); + + g_object_set_data(dataKludge, "freeze", GINT_TO_POINTER(FALSE)); } -static void sp_3dbox_vpx_angle_changed(GtkAdjustment *adj, GObject *dataKludge ) +static void box3d_vpx_angle_changed(GtkAdjustment *adj, GObject *dataKludge ) { - sp_3dbox_vp_angle_changed (adj, dataKludge, Box3D::X); + box3d_vp_angle_changed (adj, dataKludge, Proj::X); } -static void sp_3dbox_vpy_angle_changed(GtkAdjustment *adj, GObject *dataKludge ) +static void box3d_vpy_angle_changed(GtkAdjustment *adj, GObject *dataKludge ) { - sp_3dbox_vp_angle_changed (adj, dataKludge, Box3D::Y); + box3d_vp_angle_changed (adj, dataKludge, Proj::Y); } -static void sp_3dbox_vpz_angle_changed(GtkAdjustment *adj, GObject *dataKludge ) +static void box3d_vpz_angle_changed(GtkAdjustment *adj, GObject *dataKludge ) { - sp_3dbox_vp_angle_changed (adj, dataKludge, Box3D::Z); + box3d_vp_angle_changed (adj, dataKludge, Proj::Z); } // normalize angle so that it lies in the interval [0,360] -static double sp_3dbox_normalize_angle (double a) { +static double box3d_normalize_angle (double a) { double angle = a + ((int) (a/360.0))*360; if (angle < 0) { angle += 360.0; @@ -2438,48 +2457,88 @@ static double sp_3dbox_normalize_angle (double a) { return angle; } -static void sp_3dbox_tb_event_attr_changed(Inkscape::XML::Node */*repr*/, gchar const *name, - gchar const */*old_value*/, gchar const */*new_value*/, - bool /*is_interactive*/, gpointer data) +static void box3d_persp_tb_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name, + gchar const *old_value, gchar const *new_value, + bool is_interactive, gpointer data) { GtkWidget *tbl = GTK_WIDGET(data); // FIXME: if we check for "freeze" as in other tools, no action is performed at all ... - /*** // quit if run by the _changed callbacks if (g_object_get_data(G_OBJECT(tbl), "freeze")) { - return; + //return; } // in turn, prevent callbacks from responding - g_object_set_data(G_OBJECT(tbl), "freeze", GINT_TO_POINTER(TRUE)); - ***/ + //g_object_set_data(G_OBJECT(tbl), "freeze", GINT_TO_POINTER(TRUE)); - if (!strcmp(name, "inkscape:perspective")) { - GtkAdjustment *adj = 0; - double angle; - SPDesktop *desktop = (SPDesktop *) g_object_get_data(G_OBJECT(tbl), "desktop"); - Box3D::Perspective3D *persp = sp_desktop_document (desktop)->current_perspective; + GtkAdjustment *adj = 0; + double angle; + SPDesktop *desktop = (SPDesktop *) g_object_get_data(G_OBJECT(tbl), "desktop"); + // FIXME: Get the persp from the box (should be the same, but ...) + Persp3D *persp = sp_desktop_document (desktop)->current_persp3d; + if (!strcmp(name, "inkscape:vp_x")) { + GtkAction* act = GTK_ACTION (gtk_object_get_data (GTK_OBJECT(tbl), "box3d_angle_x_action")); + GtkToggleAction* tact = GTK_TOGGLE_ACTION (gtk_object_get_data (GTK_OBJECT(tbl), "toggle_vp_x_action")); + if (!persp3d_VP_is_finite(persp, Proj::X)) { + gtk_action_set_sensitive(GTK_ACTION(act), TRUE); + gtk_toggle_action_set_active(tact, TRUE); + } else { + gtk_action_set_sensitive(GTK_ACTION(act), FALSE); + gtk_toggle_action_set_active(tact, FALSE); + } adj = GTK_ADJUSTMENT(gtk_object_get_data(GTK_OBJECT(tbl), "dir_vp_x")); - angle = sp_3dbox_normalize_angle (persp->get_vanishing_point (Box3D::X)->get_angle()); - gtk_adjustment_set_value(adj, angle); + angle = persp3d_get_infinite_angle(persp, Proj::X); + if (angle != NR_HUGE) { // FIXME: We should catch this error earlier (don't show the spinbutton at all) + gtk_adjustment_set_value(adj, box3d_normalize_angle(angle)); + } + } + + if (!strcmp(name, "inkscape:vp_y")) { + GtkAction* act = GTK_ACTION (gtk_object_get_data (GTK_OBJECT(tbl), "box3d_angle_y_action")); + GtkToggleAction* tact = GTK_TOGGLE_ACTION (gtk_object_get_data (GTK_OBJECT(tbl), "toggle_vp_y_action")); + if (!persp3d_VP_is_finite(persp, Proj::Y)) { + gtk_action_set_sensitive(GTK_ACTION(act), TRUE); + gtk_toggle_action_set_active(tact, TRUE); + } else { + gtk_action_set_sensitive(GTK_ACTION(act), FALSE); + gtk_toggle_action_set_active(tact, FALSE); + } adj = GTK_ADJUSTMENT(gtk_object_get_data(GTK_OBJECT(tbl), "dir_vp_y")); - angle = sp_3dbox_normalize_angle (persp->get_vanishing_point (Box3D::Y)->get_angle()); - gtk_adjustment_set_value(adj, angle); + angle = persp3d_get_infinite_angle(persp, Proj::Y); + if (angle != NR_HUGE) { // FIXME: We should catch this error earlier (don't show the spinbutton at all) + gtk_adjustment_set_value(adj, box3d_normalize_angle(angle)); + } + } + + if (!strcmp(name, "inkscape:vp_z")) { + GtkAction* act = GTK_ACTION (gtk_object_get_data (GTK_OBJECT(tbl), "box3d_angle_z_action")); + GtkToggleAction* tact = GTK_TOGGLE_ACTION (gtk_object_get_data (GTK_OBJECT(tbl), "toggle_vp_z_action")); + if (!persp3d_VP_is_finite(persp, Proj::Z)) { + gtk_action_set_sensitive(GTK_ACTION(act), TRUE); + gtk_toggle_action_set_active(tact, TRUE); + } else { + gtk_action_set_sensitive(GTK_ACTION(act), FALSE); + gtk_toggle_action_set_active(tact, FALSE); + } adj = GTK_ADJUSTMENT(gtk_object_get_data(GTK_OBJECT(tbl), "dir_vp_z")); - angle = sp_3dbox_normalize_angle (persp->get_vanishing_point (Box3D::Z)->get_angle()); - gtk_adjustment_set_value(adj, angle); + angle = persp3d_get_infinite_angle(persp, Proj::Z); + if (angle != NR_HUGE) { // FIXME: We should catch this error earlier (don't show the spinbutton at all) + gtk_adjustment_set_value(adj, box3d_normalize_angle(angle)); + } } + + //g_object_set_data(G_OBJECT(tbl), "freeze", GINT_TO_POINTER(FALSE)); } -static Inkscape::XML::NodeEventVector sp_3dbox_tb_repr_events = +static Inkscape::XML::NodeEventVector box3d_persp_tb_repr_events = { NULL, /* child_added */ NULL, /* child_removed */ - sp_3dbox_tb_event_attr_changed, + box3d_persp_tb_event_attr_changed, NULL, /* content_changed */ NULL /* order_changed */ }; @@ -2487,43 +2546,46 @@ static Inkscape::XML::NodeEventVector sp_3dbox_tb_repr_events = /** * \param selection Should not be NULL. */ +// FIXME: This should rather be put into persp3d-reference.cpp or something similar so that it reacts upon each +// Change of the perspective, and not of the current selection (but how to refer to the toolbar then?) static void -sp_3dbox_toolbox_selection_changed(Inkscape::Selection *selection, GObject *tbl) +box3d_toolbox_selection_changed(Inkscape::Selection *selection, GObject *tbl) { Inkscape::XML::Node *repr = NULL; purge_repr_listener(tbl, tbl); SPItem *item = selection->singleItem(); - if (item) { - repr = SP_OBJECT_REPR(item); + if (item && SP_IS_BOX3D(item)) { + //repr = SP_OBJECT_REPR(item); + repr = SP_OBJECT_REPR(SP_BOX3D(item)->persp_ref->getObject()); if (repr) { g_object_set_data(tbl, "repr", repr); Inkscape::GC::anchor(repr); - sp_repr_add_listener(repr, &sp_3dbox_tb_repr_events, tbl); - sp_repr_synthesize_events(repr, &sp_3dbox_tb_repr_events, tbl); + sp_repr_add_listener(repr, &box3d_persp_tb_repr_events, tbl); + sp_repr_synthesize_events(repr, &box3d_persp_tb_repr_events, tbl); } } } -static void sp_3dbox_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder) +static void box3d_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder) { EgeAdjustmentAction* eact = 0; SPDocument *document = sp_desktop_document (desktop); - Box3D::Perspective3D *persp = document->current_perspective; + Persp3D *persp = document->current_persp3d; bool toggled = false; /* angle of VP in X direction */ eact = create_adjustment_action("3DBoxPosAngleXAction", _("Angle X"), _("Angle X:"), _("Angle of infinite vanishing point in X direction"), - "tools.shapes.3dbox", "dir_vp_x", persp->get_vanishing_point (Box3D::X)->get_angle(), - GTK_WIDGET(desktop->canvas), NULL, holder, FALSE, NULL, - 0.0, 360.0, 1.0, 10.0, + "tools.shapes.3dbox", "dir_vp_x", persp3d_get_infinite_angle(persp, Proj::X), + GTK_WIDGET(desktop->canvas), NULL, holder, TRUE, "altx-box3d", + -360.0, 360.0, 1.0, 10.0, 0, 0, 0, // labels, values, G_N_ELEMENTS(labels), - sp_3dbox_vpx_angle_changed, + box3d_vpx_angle_changed, 0.1, 1); gtk_action_group_add_action(mainActions, GTK_ACTION(eact)); g_object_set_data(holder, "box3d_angle_x_action", eact); - if (!persp->get_vanishing_point (Box3D::X)->is_finite()) { + if (!persp3d_VP_is_finite(persp, Proj::X)) { gtk_action_set_sensitive(GTK_ACTION(eact), TRUE); } else { gtk_action_set_sensitive(GTK_ACTION(eact), FALSE); @@ -2533,30 +2595,28 @@ static void sp_3dbox_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainAction { InkToggleAction* act = ink_toggle_action_new("3DBoxVPXAction", _("Toggle VP in X direction"), - _("Toggle VP in X direction between 'finite' and 'infinite' (=parallel)"), + _("Toggle VP in X direction between 'finite' and 'infinite' (= parallel)"), "toggle_vp_x", Inkscape::ICON_SIZE_DECORATION); gtk_action_group_add_action(mainActions, GTK_ACTION(act)); - if (persp) { - toggled = !persp->get_vanishing_point(Box3D::X)->is_finite(); - } - gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act), toggled); + g_object_set_data(holder, "toggle_vp_x_action", act); + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act), !persp3d_VP_is_finite(persp, Proj::X)); /* we connect the signal after setting the state to avoid switching the state again */ - g_signal_connect_after(G_OBJECT(act), "toggled", G_CALLBACK(sp_3dbox_toggle_vp_x_changed), holder); + g_signal_connect_after(G_OBJECT(act), "toggled", G_CALLBACK(box3d_toggle_vp_x_changed), holder); } /* angle of VP in Y direction */ eact = create_adjustment_action("3DBoxPosAngleYAction", _("Angle Y"), _("Angle Y:"), _("Angle of infinite vanishing point in Y direction"), - "tools.shapes.3dbox", "dir_vp_y", persp->get_vanishing_point (Box3D::Y)->get_angle(), + "tools.shapes.3dbox", "dir_vp_y", persp3d_get_infinite_angle(persp, Proj::Y), GTK_WIDGET(desktop->canvas), NULL, holder, FALSE, NULL, - 0.0, 360.0, 1.0, 10.0, + -360.0, 360.0, 1.0, 10.0, 0, 0, 0, // labels, values, G_N_ELEMENTS(labels), - sp_3dbox_vpy_angle_changed, + box3d_vpy_angle_changed, 0.1, 1); gtk_action_group_add_action(mainActions, GTK_ACTION(eact)); g_object_set_data(holder, "box3d_angle_y_action", eact); - if (!persp->get_vanishing_point (Box3D::Y)->is_finite()) { + if (!persp3d_VP_is_finite(persp, Proj::Y)) { gtk_action_set_sensitive(GTK_ACTION(eact), TRUE); } else { gtk_action_set_sensitive(GTK_ACTION(eact), FALSE); @@ -2566,31 +2626,29 @@ static void sp_3dbox_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainAction { InkToggleAction* act = ink_toggle_action_new("3DBoxVPYAction", _("Toggle VP in Y direction"), - _("Toggle VP in Y direction between 'finite' and 'infinite' (=parallel)"), + _("Toggle VP in Y direction between 'finite' and 'infinite' (= parallel)"), "toggle_vp_y", Inkscape::ICON_SIZE_DECORATION); gtk_action_group_add_action(mainActions, GTK_ACTION(act)); - if (persp) { - toggled = !persp->get_vanishing_point(Box3D::Y)->is_finite(); - } - gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act), toggled); + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act), !persp3d_VP_is_finite(persp, Proj::Y)); + g_object_set_data(holder, "toggle_vp_y_action", act); /* we connect the signal after setting the state to avoid switching the state again */ - g_signal_connect_after(G_OBJECT(act), "toggled", G_CALLBACK(sp_3dbox_toggle_vp_y_changed), holder); + g_signal_connect_after(G_OBJECT(act), "toggled", G_CALLBACK(box3d_toggle_vp_y_changed), holder); } /* angle of VP in Z direction */ eact = create_adjustment_action("3DBoxPosAngleZAction", _("Angle Z"), _("Angle Z:"), _("Angle of infinite vanishing point in Z direction"), - "tools.shapes.3dbox", "dir_vp_z", persp->get_vanishing_point (Box3D::Z)->get_angle(), + "tools.shapes.3dbox", "dir_vp_z", persp3d_get_infinite_angle(persp, Proj::Z), GTK_WIDGET(desktop->canvas), NULL, holder, FALSE, NULL, - 0.0, 360.0, 1.0, 10.0, + -360.0, 360.0, 1.0, 10.0, 0, 0, 0, // labels, values, G_N_ELEMENTS(labels), - sp_3dbox_vpz_angle_changed, + box3d_vpz_angle_changed, 0.1, 1); gtk_action_group_add_action(mainActions, GTK_ACTION(eact)); g_object_set_data(holder, "box3d_angle_z_action", eact); - if (!persp->get_vanishing_point (Box3D::Z)->is_finite()) { + if (!persp3d_VP_is_finite(persp, Proj::Z)) { gtk_action_set_sensitive(GTK_ACTION(eact), TRUE); } else { gtk_action_set_sensitive(GTK_ACTION(eact), FALSE); @@ -2600,20 +2658,19 @@ static void sp_3dbox_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainAction { InkToggleAction* act = ink_toggle_action_new("3DBoxVPZAction", _("Toggle VP in Z direction"), - _("Toggle VP in Z direction between 'finite' and 'infinite' (=parallel)"), + _("Toggle VP in Z direction between 'finite' and 'infinite' (= parallel)"), "toggle_vp_z", Inkscape::ICON_SIZE_DECORATION); gtk_action_group_add_action(mainActions, GTK_ACTION(act)); - if (persp) { - toggled = !persp->get_vanishing_point(Box3D::Z)->is_finite(); - } + + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act), !persp3d_VP_is_finite(persp, Proj::Z)); + g_object_set_data(holder, "toggle_vp_z_action", act); /* we connect the signal after setting the state to avoid switching the state again */ - gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act), toggled); - g_signal_connect_after(G_OBJECT(act), "toggled", G_CALLBACK(sp_3dbox_toggle_vp_z_changed), holder); + g_signal_connect_after(G_OBJECT(act), "toggled", G_CALLBACK(box3d_toggle_vp_z_changed), holder); } sigc::connection *connection = new sigc::connection( - sp_desktop_selection(desktop)->connectChanged(sigc::bind(sigc::ptr_fun(sp_3dbox_toolbox_selection_changed), (GObject *)holder)) + sp_desktop_selection(desktop)->connectChanged(sigc::bind(sigc::ptr_fun(box3d_toolbox_selection_changed), (GObject *)holder)) ); g_signal_connect(holder, "destroy", G_CALLBACK(delete_connection), connection); g_signal_connect(holder, "destroy", G_CALLBACK(purge_repr_listener), holder);