The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "ppport.h"

/* aesthetic 'min edge intersect' - makes crossed edges uncross */

#include "aesth.h"

/* this doesn't make any sense except in 2D */
#define DIMENSIONS	2

typedef struct private {
    aglo_point mid1, mid2;
    aglo_point delta;
    aglo_point force_delta;
    aglo_real data[1];
} *private;

declare_aesth(min_edge_intersect);

define_setup(min_edge_intersect) {
    private private;

    if (state->dimensions != DIMENSIONS) 
        croak("MinEdgeIntersect only works in %d dimensions, not %"UVuf, 
              (int) DIMENSIONS, (UV) state->dimensions);

    Newc(__LINE__, private, sizeof(struct private) + (4*DIMENSIONS-1) * sizeof(aglo_real), char, struct private);
    private->delta       = &private->data[DIMENSIONS * 0];
    private->force_delta = &private->data[DIMENSIONS * 1];
    private->mid1        = &private->data[DIMENSIONS * 2];
    private->mid2        = &private->data[DIMENSIONS * 3];
    return private;
}

define_cleanup(min_edge_intersect) {
    Safefree(private);
    return;
}

static int ccw(aglo_real x0, aglo_real y0,
               aglo_real x1, aglo_real y1,
               aglo_real x2, aglo_real y2) {
    aglo_real dx1, dx2, dy1, dy2;
    dx1 = x1 - x0; dy1 = y1 - y0;
    dx2 = x2 - x0; dy2 = y2 - y0;
    if (dx1*dy2 > dy1*dx2) return +1;
    if (dx1*dy2 < dy1*dx2) return -1;
    if (dx1*dx2 < 0 || dy1*dy2 < 0) return -1;
    if (dx1*dx1+dy1*dy1 < dx2*dx2+dy2*dy2) return +1;
    return 0;
}

static int intersect(aglo_real x11, aglo_real y11,
                     aglo_real x12, aglo_real y12,	/* line 1 */
                     aglo_real x21, aglo_real y21,
                     aglo_real x22, aglo_real y22) {	/* line 2 */
    return 
        ccw(x11, y11, x12, y12, x21, y21) *
        ccw(x11, y11, x12, y12, x22, y22) <= 0 &&
        ccw(x21, y21, x22, y22, x11, y11) *
        ccw(x21, y21, x22, y22, x12, y12) <= 0;
}

static void ae_intersection(aglo_state state,
                            aglo_gradient gradient,
                            void *private,
                            aglo_vertex p11, aglo_vertex p12,
                            aglo_vertex p21, aglo_vertex p22) {
    if (intersect(state->point[p11][0], state->point[p11][1],
                  state->point[p12][0], state->point[p12][1],
                  state->point[p21][0], state->point[p21][1],
                  state->point[p22][0], state->point[p22][1])) {
        aglo_point mid1 = PRIVATE->mid1;
        aglo_point mid2 = PRIVATE->mid2;
        aglo_point delta = PRIVATE->delta;
        aglo_point force_delta = PRIVATE->force_delta;
        aglo_real mag;

        aglo_point_midpoint(DIMENSIONS, mid1, state->point[p11], state->point[p12]);
        aglo_point_midpoint(DIMENSIONS, mid2, state->point[p21], state->point[p22]);
        aglo_point_sub(DIMENSIONS, delta, mid2, mid1);
        mag = aglo_point_mag(DIMENSIONS, delta);
        mag = fmax(mag, 1e-8);  /* avoid div by 0 */
        aglo_point_scalar_mult(DIMENSIONS, force_delta, 1/mag, delta);

        aglo_point_dec(DIMENSIONS, &gradient[p11*DIMENSIONS], force_delta);
        aglo_point_dec(DIMENSIONS, &gradient[p12*DIMENSIONS], force_delta);
        aglo_point_inc(DIMENSIONS, &gradient[p21*DIMENSIONS], force_delta);
        aglo_point_inc(DIMENSIONS, &gradient[p22*DIMENSIONS], force_delta);
    }
}

define_aesth(min_edge_intersect) {
    aglo_unsigned i, j;
    aglo_edge_record p, q;
    aglo_graph graph = state->graph;

    /* all 4 endpoints are distinct */
    /* i p */
    /* i j q */
    for (i=0;i<graph->vertices;i++)
        for (p=graph->edge_table[i];p;p=p->next)
            if (i < p->tail)
                for (j=i+1;j<graph->vertices;j++)
                    if (j != p->tail)
                        for (q=graph->edge_table[j];q;q=q->next)
                            if (j < q->tail && p->tail != q->tail)
                                ae_intersection(state, gradient, private,
                                                i, p->tail,
                                                j, q->tail);
}

MODULE = Graph::Layout::Aesthetic::Force::MinEdgeIntersect	PACKAGE = Graph::Layout::Aesthetic::Force::MinEdgeIntersect
PROTOTYPES: ENABLE

SV *
new(const char *class)
  PREINIT:
    aglo_force force;
  CODE:
    New(__LINE__, force, 1, struct aglo_force);
    force->aesth_gradient = ae_min_edge_intersect;
    force->aesth_setup	 = ae_setup_min_edge_intersect;
    force->aesth_cleanup  = ae_cleanup_min_edge_intersect;
    force->user_data = force->private_data = NULL;
    RETVAL = NEWSV(1, 0);
    sv_setref_pv(RETVAL, class, (void*) force);
  OUTPUT:
    RETVAL