diff options
| author | Maximilian Albert <maximilian.albert@gmail.com> | 2007-12-13 09:45:27 +0000 |
|---|---|---|
| committer | cilix42 <cilix42@users.sourceforge.net> | 2007-12-13 09:45:27 +0000 |
| commit | cae2409c94b11d17643f7c19829e2653d759ff8e (patch) | |
| tree | a8399ab9b3e8ff2570a92bef06e63f2307fef592 /src | |
| parent | libgdl: avoid setting a negative preferred height for dock items, (diff) | |
| download | inkscape-cae2409c94b11d17643f7c19829e2653d759ff8e.tar.gz inkscape-cae2409c94b11d17643f7c19829e2653d759ff8e.zip | |
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.
(bzr r4224)
Diffstat (limited to 'src')
51 files changed, 4774 insertions, 3225 deletions
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 <gtk/gtk.h> #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<Box3D::Perspective3D *> 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 <sigc++/sigc++.h> #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 <Anhalter42@gmx.de> - * - * 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<NR::Point> 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<NR::Point> ur = first_line.intersect(second_line); - - Box3D::PerspectiveLine third_line (corners[0], dir2); - Box3D::PerspectiveLine fourth_line (corners[2], dir1); - NR::Maybe<NR::Point> 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<NR::Point> 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<NR::Point> 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 <Anhalter42@gmx.de> - * - * 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<NR::Point> 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 <Anhalter42@gmx.de> + * + * 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<Proj::Pt3> 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("<b>Star</b> with %d vertex", + "<b>Star</b> with %d vertices", + star->sides), star->sides); + else + return g_strdup_printf (ngettext("<b>Polygon</b> with %d vertex", + "<b>Polygon</b> 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<Proj::Pt3> 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<Proj::Pt3> +box3d_side_corners (Box3DSide *side) { + std::vector<Proj::Pt3> 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 <Anhalter42@gmx.de> + * + * 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 <box3d> implementation * * Authors: + * Maximilian Albert <Anhalter42@gmx.de> * Lauris Kaplinski <lauris@kaplinski.com> * bulia byak <buliabyak@users.sf.net> - * Maximilian Albert <Anhalter42@gmx.de> * * Copyright (C) 2007 Authors * Copyright (C) 1999-2002 Lauris Kaplinski @@ -17,91 +17,104 @@ #include <glibmm/i18n.h> #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<gdouble, gdouble> 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<NR::Point> cen = sp_3dbox_get_center (box); - if (cen) { - box->old_center = *cen; - } - box->old_corner2 = box->corners[2]; - box->old_corner1 = box->corners[1]; - box->old_corner0 = box->corners[0]; - box->old_corner3 = box->corners[3]; - box->old_corner5 = box->corners[5]; - 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<NR::Point> cen = sp_3dbox_get_center (box); - if (cen) { - box->old_center = *cen; - } - box->old_corner2 = box->corners[2]; - box->old_corner1 = box->corners[1]; - box->old_corner0 = box->corners[0]; - box->old_corner3 = box->corners[3]; - box->old_corner5 = box->corners[5]; - 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(_("<b>3D Box</b>")); } -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<gint> const &list1, std::vector<gint> 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<gint> const &list1, std::vector<gint> const &list2) -{ - std::vector<gint> diff1; - std::vector<gint> diff2; - std::set_difference (list1.begin(), list1.end(), list2.begin(), list2.end(), - std::insert_iterator<std::vector<gint> >(diff1, diff1.begin())); - std::set_difference (list2.begin(), list2.end(), list1.begin(), list1.end(), - std::insert_iterator<std::vector<gint> >(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<gint> sp_3dbox_adjacent_faces_of_edge (guint corner1, guint corner2) { - std::vector<gint> 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<gint> sp_3dbox_faces_meeting_in_corner (guint corner) { - std::vector<gint> 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<gint> const &faces, std::vector<gint> &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<gint> &on_hull, std::vector<gint> &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<gint> on_hull; - std::vector<gint> inside_hull; - std::vector<gint> 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<gint> 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<NR::Point> 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<Box3D::Axis, Box3D::Axis> 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<NR::Point> -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<NR::Point> -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<NR::Point> 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<gdouble, gdouble> -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<NR::Point, NR::Point> -sp_3dbox_new_midpoints (Box3D::Perspective3D *persp, Box3D::Axis axis, NR::Point const &M0, NR::Point const &M, NR::Point const &A, NR::Point const &B) -{ - double cr1 = Box3D::cross_ratio (*persp->get_vanishing_point (axis), M0, M, A); - double cr2 = Box3D::cross_ratio (*persp->get_vanishing_point (axis), M, B, M0); - if (fabs (cr1 - 1) < Box3D::epsilon) { - // FIXME: cr == 1 is a degenerate case; how should we deal with it? - return std::make_pair (NR::Point (0,0), NR::Point (0,0)); - } - if (cr1 == NR_HUGE) { - return std::make_pair (A, B); +static std::map<int, Box3DSide *> +box3d_get_sides (SPBox3D *box) { + std::map<int, Box3DSide *> 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<NR::Point, NR::Point> 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<NR::Point, NR::Point> new_midpts = sp_3dbox_new_midpoints (persp, Box3D::X, M0, M1, A0, B0); - NR::Point A1 (new_midpts.first); - NR::Point B1 (new_midpts.second); - - /* ... and then along the Y-axis */ - Box3D::PerspectiveLine pline4 (box->old_corner1, Box3D::X, persp); - Box3D::PerspectiveLine pline5 (box->old_corner3, Box3D::X, persp); - Box3D::PerspectiveLine aux_line3 (M1, Box3D::Y, persp); - NR::Point C1 = aux_line3.meet (pline4); - NR::Point D1 = aux_line3.meet (pline5); - - Box3D::PerspectiveLine aux_line4 (new_center, Box3D::Z, persp); - NR::Point M2 = aux_line4.meet (aux_line3); - - std::pair<NR::Point, NR::Point> other_new_midpts = sp_3dbox_new_midpoints (persp, Box3D::Y, M1, M2, C1, D1); - NR::Point C2 (other_new_midpts.first); - NR::Point D2 (other_new_midpts.second); - - Box3D::PerspectiveLine plXC (C2, Box3D::X, persp); - Box3D::PerspectiveLine plXD (D2, Box3D::X, persp); - Box3D::PerspectiveLine plYA (A1, Box3D::Y, persp); - Box3D::PerspectiveLine plYB (B1, Box3D::Y, persp); - - NR::Point new_corner2 (plXD.meet (plYA)); - NR::Point new_corner1 (plXC.meet (plYB)); - NR::Point tmp_corner1 (pline4.meet (plYB)); - Box3D::PerspectiveLine pline6 (box->old_corner5, Box3D::X, persp); - Box3D::PerspectiveLine pline7 (tmp_corner1, Box3D::Z, persp); - NR::Point tmp_corner5 (pline6.meet (pline7)); - - Box3D::PerspectiveLine pline8 (tmp_corner5, Box3D::Y, persp); - Box3D::PerspectiveLine pline9 (new_corner1, Box3D::Z, persp); - NR::Point new_corner5 (pline8.meet (pline9)); - - sp_3dbox_set_shape_from_points (box, new_corner2, new_corner1, new_corner5); -} - -void sp_3dbox_update_perspective_lines() -{ - SPEventContext *ec = inkscape_active_event_context(); - 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<int, Box3DSide *> sides = box3d_get_sides(box); + std::map<int, Box3DSide *>::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<int>(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<gdouble, gdouble> 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 <box3d> 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<gint> 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<NR::Point> sp_3dbox_get_center (SP3DBox *box); -NR::Maybe<NR::Point> sp_3dbox_get_midpoint_between_corners (SP3DBox *box, guint id_corner1, guint id_corner2); -void sp_3dbox_recompute_XY_corners_from_new_center (SP3DBox *box, NR::Point const new_center); -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 <glibmm/ustring.h> #include "verbs.h" #include <vector> +#include <set> 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<Persp3D *> 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<ReleaseEvent> 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<NR::Point> 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 <NR::Point, NR::Point> 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<NR::Point> 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<NR::Point, NR::Point> side_of_intersection (NR::Point const &A, NR::Po NR::Point const &C, NR::Point const &D, NR::Point const &pt, NR::Point const &dir); -double cross_ratio (NR::Point const &A, NR::Point const &B, NR::Point const &C, NR::Point const &D); -double cross_ratio (VanishingPoint const &V, NR::Point const &B, NR::Point const &C, NR::Point const &D); -NR::Point fourth_pt_with_given_cross_ratio (NR::Point const &A, NR::Point const &C, NR::Point const &D, double gamma); - -/*** For testing purposes: Draw a knot/node of specified size and color at the given position ***/ +/*** 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 <libnr/nr-scale-ops.h> - #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<NR::Point> center = sp_3dbox_get_center(SP_3DBOX(item)); - if (!center) return NR::Point (0, 0); - NR::Matrix const i2d (sp_item_i2d_affine (item)); - return (*center) * i2d; + 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 <b>Shift</b> along the Z axis; with <b>Ctrl</b> 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 <b>Shift</b> along the Z axis; with <b>Ctrl</b> 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 <b>Shift</b> along the Z axis; with <b>Ctrl</b> 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 <b>Shift</b> along the Z axis; with <b>Ctrl</b> 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 <b>Shift</b> in X/Y direction; with <b>Ctrl</b> to constrain to the directions of edges or diagonals")); - sp_knot_holder_add(knot_holder, sp_3dbox_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 <b>Shift</b> in X/Y direction; with <b>Ctrl</b> to constrain to the directions of edges or diagonals")); - sp_knot_holder_add(knot_holder, sp_3dbox_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 <b>Shift</b> in X/Y direction; with <b>Ctrl</b> to constrain to the directions of edges or diagonals")); - sp_knot_holder_add(knot_holder, sp_3dbox_knot7_set, sp_3dbox_knot7_get, NULL, + sp_knot_holder_add(knot_holder, box3d_knot7_set, box3d_knot7_get, box3d_knot7_click, _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; with <b>Ctrl</b> to constrain to the directions of edges or diagonals")); // center dragging - sp_knot_holder_add_full(knot_holder, sp_3dbox_knot_center_set, sp_3dbox_knot_center_get, NULL, + sp_knot_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 <sigc++/sigc++.h> + +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 <Anhalter42@gmx.de> + * + * 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 <glibmm/i18n.h> + +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<Persp3D *> p, Proj::Axis axis) { + for (std::set<Persp3D *>::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<SPBox3D *>::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<SPBox3D *>::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<SPBox3D *>::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<SPBox3D *>::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<SPBox3D *>::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<SPBox3D *> +persp3d_list_of_boxes(Persp3D *persp) { + std::list<SPBox3D *> bx_lst; + for (std::vector<SPBox3D *>::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<SPBox3D *>::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<SPBox3D *> 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<SPBox3D *>::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 *> +persp3d_currently_selected (Box3DContext *bc) { + Inkscape::Selection *selection = sp_desktop_selection (bc->desktop); + + std::set<Persp3D *> 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<SPBox3D *>::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 <Anhalter42@gmx.de> + * + * 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 <set> +#include <vector> +#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<SPBox3D *> 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<Persp3D *>, 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<SPBox3D *> 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 *> 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<NR::Point> PerspectiveLine::intersect (Line const &line) -{ - NR::Maybe<NR::Point> 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<NR::Point> 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 <NR::Point, NR::Point> 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 <glib.h> 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<NR::Point> intersect (Line const &line); // FIXME: Can we make this return only a NR::Point to remove the extra method meet()? - NR::Point meet (Line const &line); - NR::Point pt_with_given_cross_ratio (NR::Point const &C, NR::Point const &D, double gamma); - NR::Maybe<NR::Point> intersection_with_viewbox (SPDesktop *desktop); + 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 <Anhalter42@gmx.de> - * - * 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 <iostream> -#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<NR::Point> 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 <Anhalter42@gmx.de> - * - * 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 <glib.h> - -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 <Anhalter42@gmx.de> + * + * 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 <Anhalter42@gmx.de> + * + * 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 <gtk/gtk.h> + +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 <gdk/gdkkeysyms.h> @@ -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<SPObject *>(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<SPObject *>(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 <Anhalter42@gmx.de> + * + * Copyright (C) 2007 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <iostream> +#include <iomanip> +#include <vector> +#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 <int S, int T> +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 <int S, int T> +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 <int S, int T> +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 <int S, int U, int T> +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 <int S, int T> +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<vector<double> > 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 <int S, int T> +static int find_pivot(const double A[S][T], unsigned int i, std::vector<int> 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 <int S, int T> +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 <int S, int T> +static std::vector<int> gauss_jordan (double A[S][T], int avoid_col = -1) { + std::vector<int> 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<S,T>(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<S,T> (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 <int S, int T> +static double projectify (std::vector<int> 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 <int S, int T> 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<S,T>(A,B); + SysEq::copy_mat<S,T>(A,B); + std::vector<int> cols = gauss_jordan<S,T>(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<int>::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<S,T>(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<S,T>(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, _("<b>Drag</b> to create a rectangle. <b>Drag controls</b> to round corners and resize. <b>Click</b> 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, _("<b>Drag</b> to create a 3D box. <b>Drag controls</b> to resize in perspective. <b>Click</b> to select (with <b>Ctrl+Alt</b> 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 <Anhalter42@gmx.de> + * + * Copyright (C) 2007 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "transf_mat_3x4.h" +#include <gtk/gtk.h> +#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 <Anhalter42@gmx.de> + * + * 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<VanishingPoint>::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<VanishingPoint*, less_ptr> sel_vps = dragger->VPsOfSelectedBoxes(); + /** + g_print ("===== VPs of selected boxes: ===========================\n"); + for (std::set<VanishingPoint*, less_ptr>::iterator i = sel_vps.begin(); i != sel_vps.end(); ++i) { + (*i)->printPt(); + } + g_print ("========================================================\n"); + **/ + + std::list<SPBox3D *> sel_boxes; + for (std::set<VanishingPoint*, less_ptr>::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<SPBox3D *> bx_lst = persp3d_list_of_boxes(old_persp); + for (std::list<SPBox3D *>::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<VanishingPoint>::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<VanishingPoint>::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<Axis, Axis> 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<VanishingPoint>::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<SPBox3D *> +VanishingPoint::selectedBoxes(Inkscape::Selection *sel) { + std::list<SPBox3D *> 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("<b>Finite</b> vanishing point shared by <b>%d</b> box", - "<b>Finite</b> vanishing point shared by <b>%d</b> boxes; drag with <b>Shift</b> 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("<b>Infinite</b> vanishing point shared by <b>%d</b> box", - "<b>Infinite</b> vanishing point shared by <b>%d</b> boxes; drag with <b>Shift</b> 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("<b>Finite</b> vanishing point shared by <b>%d</b> box", + "<b>Finite</b> vanishing point shared by <b>%d</b> boxes; drag with <b>Shift</b> 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("<b>Infinite</b> vanishing point shared by <b>%d</b> box", + "<b>Infinite</b> vanishing point shared by <b>%d</b> boxes; drag with <b>Shift</b> 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 <b>%d</b> vanishing points ", length); char *desc2 = g_strdup_printf (ngettext("shared by <b>%d</b> box; drag with <b>Shift</b> to separate selected box(es)", "shared by <b>%d</b> boxes; drag with <b>Shift</b> 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<VanishingPoint>::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<VanishingPoint>::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<VanishingPoint*, less_ptr> +VPDragger::VPsOfSelectedBoxes() { + std::set<VanishingPoint*, less_ptr> 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<VanishingPoint>::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<VanishingPoint>::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<VanishingPoint>::iterator i = vps.begin(); i != vps.end(); ++i) { + persp1 = (*i).get_perspective(); + for (std::list<VanishingPoint>::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<VanishingPoint>::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<VanishingPoint>::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<VanishingPoint>::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<VanishingPoint>::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<VanishingPoint>::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<VanishingPoint>::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<VanishingPoint>::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<NR::Point> 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<VanishingPoint>::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 <set> #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<SPBox3D *> 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<VanishingPoint *, VanishingPoint *, bool> { + 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<VanishingPoint> 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<VanishingPoint*, less_ptr> 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); |
