Code

Display CMS adjustment per-desktop view
[inkscape.git] / src / perspective3d.cpp
index 1ad910bfad249cd102f31bd320f4380ec2bbcda6..489e88dfc859d46786ec28cb4872b229b2f90fca 100644 (file)
  * 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 "desktop.h"
+#include "perspective3d.h"
+#include "desktop-handles.h"
 
 // can probably be removed later
 #include "inkscape.h"
 
 namespace Box3D {
 
-Perspective3D *
-get_persp_of_box (const SP3DBox *box)
-{
-    SPDesktop *desktop = inkscape_active_desktop(); // Should we pass the desktop as an argument?
-    for (GSList *p = desktop->perspectives; p != NULL; p = p->next) {
-        if (((Perspective3D *) p->data)->has_box (box))
-            return (Perspective3D *) p->data;
-    }
-    g_warning ("Stray 3D box!\n");
-    g_assert_not_reached();
-}
+gint Perspective3D::counter = 0;
 
 /**
  * Computes the intersection of the two perspective lines from pt1 and pt2 to the respective
@@ -57,39 +49,114 @@ 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)
-    : desktop (NULL),
-      vp_x (pt_x),
-      vp_y (pt_y),
-      vp_z (pt_z),
-      boxes (NULL)
+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 ()
 {
-    g_assert (desktop != NULL);
-    desktop->remove_perspective (this);
+    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)
 {
-    // FIXME: Also handle value 'NONE' in switch
     switch (dir) {
         case X:
-            return &vp_x;
+            return vp_x;
             break;
         case Y:
-            return &vp_y;
+            return vp_y;
             break;
         case Z:
-            return &vp_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;
     }
 }
@@ -99,20 +166,91 @@ Perspective3D::set_vanishing_point (Box3D::Axis const dir, VanishingPoint const
 {
     switch (dir) {
         case X:
-            vp_x = pt;
+            (*vp_x) = pt;
             break;
         case Y:
-            vp_y = pt;
+            (*vp_y) = pt;
             break;
         case Z:
-            vp_z = pt;
+            (*vp_z) = pt;
             break;
-        case NONE:
+        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)
 {
@@ -134,13 +272,175 @@ Perspective3D::remove_box (const SP3DBox *box)
 }
 
 bool
-Perspective3D::has_box (const SP3DBox *box)
+Perspective3D::has_box (const SP3DBox *box) const
 {
     return (g_slist_find (this->boxes, box) != NULL);
 }
 
-} // namespace Box3D 
+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++