Code

Fundamentally reworked version of the 3D box tool (among many other things, this...
authorcilix42 <cilix42@users.sourceforge.net>
Thu, 13 Dec 2007 09:45:27 +0000 (09:45 +0000)
committercilix42 <cilix42@users.sourceforge.net>
Thu, 13 Dec 2007 09:45:27 +0000 (09:45 +0000)
51 files changed:
src/Makefile_insert
src/arc-context.cpp
src/attributes-test.h
src/attributes.cpp
src/attributes.h
src/axis-manip.cpp
src/axis-manip.h
src/box3d-context.cpp
src/box3d-context.h
src/box3d-face.cpp [deleted file]
src/box3d-face.h [deleted file]
src/box3d-side.cpp [new file with mode: 0644]
src/box3d-side.h [new file with mode: 0644]
src/box3d.cpp
src/box3d.h
src/desktop-style.cpp
src/display/sp-canvas.cpp
src/document.cpp
src/document.h
src/gc-anchored.cpp
src/knotholder.cpp
src/knotholder.h
src/line-geometry.cpp
src/line-geometry.h
src/object-edit.cpp
src/pencil-context.cpp
src/persp3d-reference.cpp [new file with mode: 0644]
src/persp3d-reference.h [new file with mode: 0644]
src/persp3d.cpp [new file with mode: 0644]
src/persp3d.h [new file with mode: 0644]
src/perspective-line.cpp
src/perspective-line.h
src/perspective3d.cpp [deleted file]
src/perspective3d.h [deleted file]
src/proj_pt.cpp [new file with mode: 0644]
src/proj_pt.h [new file with mode: 0644]
src/rect-context.cpp
src/select-context.cpp
src/selection-describer.cpp
src/sp-ellipse.cpp
src/sp-item-group.cpp
src/sp-object-repr.cpp
src/sp-rect.cpp
src/syseq.h [new file with mode: 0644]
src/tools-switch.cpp
src/transf_mat_3x4.cpp [new file with mode: 0644]
src/transf_mat_3x4.h [new file with mode: 0644]
src/vanishing-point.cpp
src/vanishing-point.h
src/verbs.h
src/widgets/toolbox.cpp

index ff597b816fa4b1225ed88b555b356ad2c1c2c966..695d87f8f6b000f644ace91891648e0c2ee458da 100644 (file)
@@ -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 \
index c2ec30a64a61a50a298c6e292e75181e2fc61373..82c00fd05f05fe7ea2d028af895076ff6cefdfa0 100644 (file)
@@ -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],
index 57eb03eb5ae77a78e109e89867c28d3a5a84e3fb..384c0ca373f333f3a05192df9e39fe6acde70626 100644 (file)
@@ -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},
index 3777c68a1b2d67e92cb7024f527b756c39df8c9a..8232065fc665bdb346dfb5b89e9521f154c46545 100644 (file)
@@ -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"},
index 0962827f8dacbf0e6fb4ec1f4fc82add8f398f25..33e06089351237909ed41d97eb48c0367bb9b1b9 100644 (file)
@@ -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,
index 7539f2324e3118f23cb44e308efb878fd0c1ba66..1eed56439eadc39d0a756a2c7533116b181028e1 100644 (file)
 
 #include "axis-manip.h"
 
+namespace Proj {
+
+Axis axes[4]   = { X,  Y,  Z, W };
+
+} // namespace Proj
+
+
 namespace Box3D {
 
 Axis axes[3]   = { X,  Y,  Z };
index 4ebdb5aab342139b96cac6a9bc43a55675d1ce96..e5cc963ba2614d95f406dad307a24c34c9dfb706 100644 (file)
 #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
index 7ceed0acab2eb8a35fe9731a699469e7622aa833..d74b0e7d1c8c88d29d52f89eaccc6427c03a6f27 100644 (file)
@@ -1,4 +1,4 @@
-#define __SP_3DBOX_CONTEXT_C__
+#define __SP_BOX3D_CONTEXT_C__
 
 /*
  * 3D box drawing context
 #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);
 
index 33176ae84565316464af1292e75803e24375acca..1817aa180ff251256e1b0d584aa1f4813c2fc303 100644 (file)
@@ -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
 
 #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 (file)
index a1b7ae8..0000000
+++ /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 (file)
index 9281d45..0000000
+++ /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 (file)
index 0000000..ee449be
--- /dev/null
@@ -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 (file)
index 0000000..e915408
--- /dev/null
@@ -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 :
index ff00a795c57505d50c8475fa83ed835cd16e4740..c9f3bb7d296b63b2533f1cdad606838d2caf921e 100644 (file)
@@ -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
 
 #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);
 }
 
 /*
index 1e567ded95d2e1751d73cda2e8fb7bff9d9691f1..c676696e99e978b32caba2f7b58f45a55fab1837 100644 (file)
@@ -1,5 +1,5 @@
-#ifndef __SP_3DBOX_H__
-#define __SP_3DBOX_H__
+#ifndef __SP_BOX3D_H__
+#define __SP_BOX3D_H__
 
 /*
  * SVG <box3d> implementation
  * 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 :
index 236130173fec7f4536e6429f23c2f540ddcd920a..eaa4ee6a7a5be8473f13d636a40e9e6867e3d59d 100644 (file)
@@ -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);
index efc4fd112a34fe4257aee30d8d4403453de65263..6fca902cef03c95bd07a9b8e73539a99179a324b 100644 (file)
@@ -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();
     }
 }
index 0207fe5979c3e6c5824b7dbddac303e935b05117..4b71576096b3ce85f633fa0b7dd76053d980f2c3 100644 (file)
 #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
index e1b405f1879fba37c0ce84ad9bb41fab9f81021c..6e2693aedc53fc827360b2dba15a48f01fdd1223 100644 (file)
@@ -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 :
index 3f4cfc12dcf8cbe1a3022882495dbfe451f480f5..91055c968259493cd9ca562c6b9207bf07308b51 100644 (file)
@@ -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;
index 3c61e980be70222842975495220a309ccde7424d..161e779d1baf97b7134dec2375baeae60c80667b 100644 (file)
@@ -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;
index 18b6c4165241b38fa6de694a62a238bb2d95f871..fd09c7b23fb4ee2802fffd181087e0718a7405f9 100644 (file)
@@ -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())
index 872e9ed6b7f58ba47d9f6160ac168d299c09a064..d050ec458c266605d4142a65e09e4593080508f5 100644 (file)
 
 #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)
index e678c40312a958dfc6e97c16c910578720b84719..5e3152c03c97e44e9f4be6b32cdf559af56db5d6 100644 (file)
@@ -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);
 
 
index 1ef02672f4c04b222350b25f42691d8ec58301be..9c8db0936210a1130434fcc5dbe9867c1d18c7af 100644 (file)
@@ -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 */
 
 /*
index 32ea8aafa36adb200e7198c0884d890589d832d5..1ee39d5309f9427ccc14007eb6a1fb3b1e7783b5 100644 (file)
@@ -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 (file)
index 0000000..cbd855c
--- /dev/null
@@ -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 (file)
index 0000000..43b0e82
--- /dev/null
@@ -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 (file)
index 0000000..3dafba3
--- /dev/null
@@ -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 (file)
index 0000000..934136b
--- /dev/null
@@ -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 :
index 90857e6d5ecd10fac2ee887f5db8dfda20068e66..051a1d94acb6d2cbcf7d53f24138560aac27aa44 100644 (file)
  */
 
 #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 
index 90104ffdf963953e86d58efb690e6a896d9efc48..e0235aafc72f830f31512b1f3c86437879e33ea3 100644 (file)
@@ -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 (file)
index 489e88d..0000000
+++ /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 (file)
index caf503e..0000000
+++ /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 (file)
index 0000000..d7906a4
--- /dev/null
@@ -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 (file)
index 0000000..30f375a
--- /dev/null
@@ -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 :
index d069e052c977b7b9ce28e6248cce6046384cdea7..3efc8159611d9a7e142e58b2b54a28e5aa19fa61 100644 (file)
@@ -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
index 2eff4297f3f78d6c7b28313c4aa733f82b8ebe17..cdf63785c2f33536b9b5acb715ddfec454ca2635 100644 (file)
@@ -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 {
index 01aab97b759f29efa95aa3d7401ea400cad58d65..1debd73e15e95206674d275db6b5b5fbfcf2940b 100644 (file)
@@ -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"); }
index 7a1b0f33e0ef41cc118a6a9d862ed873263a2340..81a103cd7881faca80e9fee2723f2a336df651c8 100644 (file)
@@ -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);
 
index cabc7b26a754e173cb6b9c2dee5eb37443e58052..410a7b37e4c898dc0ba1cce4378e1c2498ff6a39 100644 (file)
@@ -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));
index 9338f101996372d0eb811f27f672e3ebe17dd955..5fa36d7b589884692431abe5fafe082ca66aab07 100644 (file)
@@ -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[] = {
index d9caebd5a00eaeae4464aec2536b30deaf79c57a..e5f1da05d24eb735e527ef04bfee4e60da0b57e0 100644 (file)
@@ -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 (file)
index 0000000..7075bd4
--- /dev/null
@@ -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 :
index 1bc83d7a2bc09cdfde14eedc6ffbcd5b6ba80892..e4692d91b85e878242d2b51f0e4117816f36a5b2 100644 (file)
@@ -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 (file)
index 0000000..a624fb1
--- /dev/null
@@ -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 (file)
index 0000000..549db1c
--- /dev/null
@@ -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 :
index 6f72a962140accd9ae0ee5c22845df39c6ac6772..0a8336102c290feaa5ac46f0c42e32c4563684ad 100644 (file)
 
 #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++
index 3dde393855d99362e04aa55b45949e925ff009c2..47c11be186df74643e348ef43ac47ecb8152e3ea 100644 (file)
 #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 */
 
 /*
index be9405f9bac09e4cd4235094acfdbb37f07d0fd8..a9abc02ed613543ab0a2ff57f7ac4f9af91d4c26 100644 (file)
@@ -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,
index 47cb78cb22d21ad91f6f158c5688f584bda645a2..9e9686e77763d36ce92d10c1428563c7bd63bcec 100644 (file)
@@ -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);