#define __VANISHING_POINT_C__

/*
 * Vanishing point for 3D perspectives
 *
 * Authors:
 *   bulia byak <buliabyak@users.sf.net>
 *   Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
 *   Maximilian Albert <Anhalter42@gmx.de>
 *
 * Copyright (C) 2005-2007 authors
 *
 * Released under GNU GPL, read the file 'COPYING' for more information
 */

#include <glibmm/i18n.h>

#include "vanishing-point.h"
#include "desktop-handles.h"
#include "desktop.h"
#include "event-context.h"
#include "xml/repr.h"
#include "perspective-line.h"
#include "shape-editor.h"

namespace Box3D {

#define VP_KNOT_COLOR_NORMAL 0xffffff00
#define VP_KNOT_COLOR_SELECTED 0x0000ff00

#define VP_LINE_COLOR_FILL 0x0000ff7f
#define VP_LINE_COLOR_STROKE_X 0xff00007f
#define VP_LINE_COLOR_STROKE_Y 0x0000ff7f
#define VP_LINE_COLOR_STROKE_Z 0xffff007f

// screen pixels between knots when they snap:
#define SNAP_DIST 5

// absolute distance between gradient points for them to become a single dragger when the drag is created:
#define MERGE_DIST 0.1

// knot shapes corresponding to GrPointType enum
SPKnotShapeType vp_knot_shapes [] = {
        SP_KNOT_SHAPE_SQUARE, // VP_FINITE
        SP_KNOT_SHAPE_CIRCLE  //VP_INFINITE
};

static void
vp_drag_sel_changed(Inkscape::Selection */*selection*/, gpointer data)
{
    VPDrag *drag = (VPDrag *) data;
    drag->updateDraggers();
    drag->updateLines();
    drag->updateBoxReprs();
}

static void
vp_drag_sel_modified (Inkscape::Selection */*selection*/, guint /*flags*/, gpointer data)
{
    VPDrag *drag = (VPDrag *) data;
    drag->updateLines ();
    //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)
{
    for (std::list<VanishingPoint>::iterator i = dr1->vps.begin(); i != dr1->vps.end(); ++i) {
        if (dr2->hasPerspective ((*i).get_perspective())) {
            return true;
        }
    }
    return false;
}

static void
vp_knot_moved_handler (SPKnot */*knot*/, Geom::Point const *ppointer, guint state, gpointer data)
{
    VPDragger *dragger = (VPDragger *) data;
    VPDrag *drag = dragger->parent;

    Geom::Point p = *ppointer;

    // 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 */
        if (dragger->numberOfBoxes() > 1) { // FIXME: Don't do anything if *all* boxes of a VP are selected
            std::set<VanishingPoint*, less_ptr> sel_vps = dragger->VPsOfSelectedBoxes();

            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->perspective_impl);

                /* ... 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) {
                    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 **/
                        box3d_switch_perspectives(*i, old_persp, new_persp);
                    }
                }
            }
            // 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) {
            VPDragger *d_new = (VPDragger *) di->data;
            if ((d_new != dragger) && (Geom::L2 (d_new->point - p) < snap_dist)) {
                if (have_VPs_of_same_perspective (dragger, d_new)) {
                    // this would result in degenerate boxes, which we disallow for the time being
                    continue;
                }

                // 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 ...
                d_new->vps.merge(dragger->vps);

                // ... delete old dragger ...
                drag->draggers = g_list_remove (drag->draggers, dragger);
                delete dragger;
                dragger = NULL;

                // ... and merge any duplicate perspectives
                d_new->mergePerspectives();

                // TODO: Update the new merged dragger
                d_new->updateTip();

                d_new->parent->updateBoxDisplays (); // FIXME: Only update boxes in current dragger!
                d_new->updateZOrders ();

                drag->updateLines ();

                // TODO: Undo machinery; this doesn't work yet because perspectives must be created and
                //       deleted according to changes in the svg representation, not based on any user input
                //       as is currently the case.

                sp_document_done (sp_desktop_document (inkscape_active_desktop()), SP_VERB_CONTEXT_3DBOX,
                                  _("Merge vanishing points"));

                return;
            }
        }
    }


    dragger->point = p; // FIXME: Brauchen wir dragger->point überhaupt?

    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();

    dragger->dragging_started = true;
}

void
vp_knot_grabbed_handler (SPKnot */*knot*/, unsigned int /*state*/, gpointer data)
{
    VPDragger *dragger = (VPDragger *) data;
    VPDrag *drag = dragger->parent;

    drag->dragging = true;
}

static void
vp_knot_ungrabbed_handler (SPKnot *knot, guint /*state*/, gpointer data)
{
    VPDragger *dragger = (VPDragger *) data;

    dragger->point_original = dragger->point = 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->parent->updateLines ();
    dragger->parent->updateBoxHandles ();

    // TODO: Update box's paths and svg representation

    dragger->parent->dragging = false;

    // TODO: Undo machinery!!
    g_return_if_fail (dragger->parent);
    g_return_if_fail (dragger->parent->document);
    sp_document_done(dragger->parent->document, SP_VERB_CONTEXT_3DBOX,
                     _("3D box: Move vanishing point"));
}

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->perspective_impl->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, Geom::Point p, VanishingPoint &vp)
{
    this->parent = parent;

    this->point = p;
    this->point_original = p;

    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);
        this->knot->setFill(VP_KNOT_COLOR_NORMAL, VP_KNOT_COLOR_NORMAL, VP_KNOT_COLOR_NORMAL);
        this->knot->setStroke(0x000000ff, 0x000000ff, 0x000000ff);
        sp_knot_update_ctrl(this->knot);

        // move knot to the given point
        sp_knot_set_position (this->knot, this->point, SP_KNOT_STATE_NORMAL);
        sp_knot_show (this->knot);

        // 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), "grabbed", G_CALLBACK (vp_knot_grabbed_handler), this);
        g_signal_connect (G_OBJECT (this->knot), "ungrabbed", G_CALLBACK (vp_knot_ungrabbed_handler), this);

        // add the initial VP (which may be NULL!)
        this->addVP (vp);
    }
}

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_grabbed_handler), this);
    g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_ungrabbed_handler), this);
    /* unref should call destroy */
    g_object_unref (G_OBJECT (this->knot));
}

/**
Updates the statusbar tip of the dragger knot, based on its draggables
 */
void
VPDragger::updateTip ()
{
    if (this->knot && this->knot->tip) {
        g_free (this->knot->tip);
        this->knot->tip = NULL;
    }

    guint num = this->numberOfBoxes();
    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 = 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)",
                                                num),
                                       num);
        this->knot->tip = g_strconcat(desc1, desc2, NULL);
        g_free (desc1);
        g_free (desc2);
    }
}

/**
 * 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, bool update_pos)
{
    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;
    }

    if (update_pos) {
        vp.set_pos (this->point);
    }
    this->vps.push_front (vp);

    this->updateTip();
}

void
VPDragger::removeVP (VanishingPoint const &vp)
{
    std::list<VanishingPoint>::iterator i = std::find (this->vps.begin(), this->vps.end(), vp);
    if (i != this->vps.end()) {
        this->vps.erase (i);
    }
    this->updateTip();
}

VanishingPoint *
VPDragger::findVPWithBox (SPBox3D *box) {
    for (std::list<VanishingPoint>::iterator vp = vps.begin(); vp != vps.end(); ++vp) {
        if ((*vp).hasBox(box)) {
            return &(*vp);
        }
    }
    return NULL;
}

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 sel_vps;
}

guint
VPDragger::numberOfBoxes ()
{
    guint num = 0;
    for (std::list<VanishingPoint>::iterator vp = vps.begin(); vp != vps.end(); ++vp) {
        num += (*vp).numberOfBoxes();
    }
    return num;
}

bool
VPDragger::hasPerspective (const Persp3D *persp)
{
    for (std::list<VanishingPoint>::iterator i = vps.begin(); i != vps.end(); ++i) {
        if (persp3d_perspectives_coincide(persp, (*i).get_perspective())) {
            return true;
        }
    }
    return false;
}

void
VPDragger::mergePerspectives ()
{
    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::updateBoxDisplays ()
{
    for (std::list<VanishingPoint>::iterator i = this->vps.begin(); i != this->vps.end(); ++i) {
        (*i).updateBoxDisplays();
    }
}

void
VPDragger::updateVPs (Geom::Point const &pt)
{
    for (std::list<VanishingPoint>::iterator i = this->vps.begin(); i != this->vps.end(); ++i) {
        (*i).set_pos (pt);
    }
}

void
VPDragger::updateZOrders ()
{
    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[Geom::X], point[Geom::Y]);
    for (std::list<VanishingPoint>::iterator i = this->vps.begin(); i != this->vps.end(); ++i) {
        g_print ("    VP %s\n", (*i).axisString());
    }
}

VPDrag::VPDrag (SPDocument *document)
{
    this->document = document;
    this->selection = sp_desktop_selection(inkscape_active_desktop());

    this->draggers = NULL;
    this->lines = NULL;
    this->show_lines = true;
    this->front_or_rear_lines = 0x1;

    this->dragging = false;

    this->sel_changed_connection = this->selection->connectChanged(
        sigc::bind (
            sigc::ptr_fun(&vp_drag_sel_changed),
            (gpointer)this )

        );
    this->sel_modified_connection = this->selection->connectModified(
        sigc::bind(
            sigc::ptr_fun(&vp_drag_sel_modified),
            (gpointer)this )
        );

    this->updateDraggers ();
    this->updateLines ();
}

VPDrag::~VPDrag()
{
    this->sel_changed_connection.disconnect();
    this->sel_modified_connection.disconnect();

    for (GList *l = this->draggers; l != NULL; l = l->next) {
        delete ((VPDragger *) l->data);
    }
    g_list_free (this->draggers);
    this->draggers = NULL;

    for (GSList const *i = this->lines; i != NULL; i = i->next) {
        gtk_object_destroy( GTK_OBJECT (i->data));
    }
    g_slist_free (this->lines);
    this->lines = NULL;
}

/**
 * Select the dragger that has the given VP.
 */
VPDragger *
VPDrag::getDraggerFor (VanishingPoint const &vp)
{
    for (GList const* i = this->draggers; i != NULL; i = i->next) {
        VPDragger *dragger = (VPDragger *) i->data;
        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 (*j == vp) {
                return (dragger);
            }
        }
    }
    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
 */
void
VPDrag::updateDraggers ()
{
    if (this->dragging)
        return;
    // delete old draggers
    for (GList const* i = this->draggers; i != NULL; i = i->next) {
        delete ((VPDragger *) i->data);
    }
    g_list_free (this->draggers);
    this->draggers = NULL;

    g_return_if_fail (this->selection != NULL);

    for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
        SPItem *item = SP_ITEM(i->data);
        if (!SP_IS_BOX3D (item)) continue;
        SPBox3D *box = SP_BOX3D (item);

        VanishingPoint vp;
        for (int i = 0; i < 3; ++i) {
            vp.set(box3d_get_perspective(box), Proj::axes[i]);
            addDragger (vp);
        }
    }
}

/**
Regenerates the lines list from the current selection; is called on each move
of a dragger, so that lines are always in sync with the actual perspective
*/
void
VPDrag::updateLines ()
{
    // delete old lines
    for (GSList const *i = this->lines; i != NULL; i = i->next) {
        gtk_object_destroy( GTK_OBJECT (i->data));
    }
    g_slist_free (this->lines);
    this->lines = NULL;

    // do nothing if perspective lines are currently disabled
    if (this->show_lines == 0) return;

    g_return_if_fail (this->selection != NULL);

    for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
        if (!SP_IS_BOX3D(i->data)) continue;
        SPBox3D *box = SP_BOX3D (i->data);

        this->drawLinesForFace (box, Proj::X);
        this->drawLinesForFace (box, Proj::Y);
        this->drawLinesForFace (box, Proj::Z);
    }
}

void
VPDrag::updateBoxHandles ()
{
    // FIXME: Is there a way to update the knots without accessing the
    //        (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;
    }

    SPEventContext *ec = inkscape_active_event_context();
    g_assert (ec != NULL);
    if (ec->shape_editor != NULL) {
        ec->shape_editor->update_knotholder();
    }
}

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 SPBox3D *box, Proj::Axis axis) //, guint corner1, guint corner2, guint corner3, guint corner4)
{
    guint color;
    switch (axis) {
        // TODO: Make color selectable by user
        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();
    }

    Geom::Point corner1, corner2, corner3, corner4;
    box3d_corners_for_PLs (box, axis, corner1, corner2, corner3, corner4);

    g_return_if_fail (box3d_get_perspective(box));
    Proj::Pt2 vp = persp3d_get_VP (box3d_get_perspective(box), axis);
    if (vp.is_finite()) {
        // draw perspective lines for finite VPs
        Geom::Point pt = vp.affine();
        if (this->front_or_rear_lines & 0x1) {
            // draw 'front' perspective lines
            this->addLine (corner1, pt, color);
            this->addLine (corner2, pt, color);
        }
        if (this->front_or_rear_lines & 0x2) {
            // draw 'rear' perspective lines
            this->addLine (corner3, pt, color);
            this->addLine (corner4, pt, color);
        }
    } else {
        // draw perspective lines for infinite VPs
        boost::optional<Geom::Point> pt1, pt2, pt3, pt4;
        Persp3D *persp = box3d_get_perspective(box);
        SPDesktop *desktop = inkscape_active_desktop (); // FIXME: Store the desktop in VPDrag
        Box3D::PerspectiveLine pl (corner1, axis, persp);
        pt1 = pl.intersection_with_viewbox(desktop);

        pl = Box3D::PerspectiveLine (corner2, axis, persp);
        pt2 = pl.intersection_with_viewbox(desktop);

        pl = Box3D::PerspectiveLine (corner3, axis, persp);
        pt3 = pl.intersection_with_viewbox(desktop);

        pl = Box3D::PerspectiveLine (corner4, axis, persp);
        pt4 = pl.intersection_with_viewbox(desktop);

        if (!pt1 || !pt2 || !pt3 || !pt4) {
            // some perspective lines s are outside the canvas; currently we don't draw any of them
            return;
        }
        if (this->front_or_rear_lines & 0x1) {
            // draw 'front' perspective lines
            this->addLine (corner1, *pt1, color);
            this->addLine (corner2, *pt2, color);
        }
        if (this->front_or_rear_lines & 0x2) {
            // draw 'rear' perspective lines
            this->addLine (corner3, *pt3, color);
            this->addLine (corner4, *pt4, color);
        }
    }
}

/**
 * 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)
{
    if (!vp.is_finite()) {
        // don't create draggers for infinite vanishing points
        return;
    }
    Geom::Point p = vp.get_pos();

    for (GList *i = this->draggers; i != NULL; i = i->next) {
        VPDragger *dragger = (VPDragger *) i->data;
        if (Geom::L2 (dragger->point - p) < MERGE_DIST) {
            // distance is small, merge this draggable into dragger, no need to create new dragger
            dragger->addVP (vp);
            return;
        }
    }

    VPDragger *new_dragger = new VPDragger(this, p, vp);
    // fixme: draggers should be added AFTER the last one: this way tabbing through them will be from begin to end.
    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
 */
void
VPDrag::addLine (Geom::Point p1, Geom::Point p2, guint32 rgba)
{
    SPCanvasItem *line = sp_canvas_item_new(sp_desktop_controls(inkscape_active_desktop()), SP_TYPE_CTRLLINE, NULL);
    sp_ctrlline_set_coords(SP_CTRLLINE(line), p1, p2);
    if (rgba != VP_LINE_COLOR_FILL) // fill is the default, so don't set color for it to speed up redraw
        sp_ctrlline_set_rgba32 (SP_CTRLLINE(line), rgba);
    sp_canvas_item_show (line);
    this->lines = g_slist_append (this->lines, line);
}

} // namespace Box3D

/*
  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 :
