/* new_virtual_edge: * Create and return a new virtual edge e attached to orig. * ED_to_orig(e) = orig * ED_to_virt(orig) = e if e is the first virtual edge attached. * orig might be an input edge, reverse of an input edge, or virtual edge */ edge_t *new_virtual_edge(node_t * u, node_t * v, edge_t * orig) { edge_t *e; e = NEW(edge_t); e->tail = u; e->head = v; ED_edge_type(e) = VIRTUAL; if (orig) { e->id = orig->id; ED_count(e) = ED_count(orig); ED_xpenalty(e) = ED_xpenalty(orig); ED_weight(e) = ED_weight(orig); ED_minlen(e) = ED_minlen(orig); if (e->tail == orig->tail) ED_tail_port(e) = ED_tail_port(orig); else if (e->tail == orig->head) ED_tail_port(e) = ED_head_port(orig); if (e->head == orig->head) ED_head_port(e) = ED_head_port(orig); else if (e->head == orig->tail) ED_head_port(e) = ED_tail_port(orig); if (ED_to_virt(orig) == NULL) ED_to_virt(orig) = e; ED_to_orig(e) = orig; } else ED_minlen(e) = ED_count(e) = ED_xpenalty(e) = ED_weight(e) = 1; return e; }
void merge_oneway(edge_t *e, edge_t *rep) { if (rep == ED_to_virt(e)) {agerr(AGWARN, "merge_oneway glitch\n"); return;} assert(ED_to_virt(e) == NULL); ED_to_virt(e) = rep; basic_merge(e,rep); }
void makeStraightEdge(graph_t * g, edge_t * e, int et, splineInfo* sinfo) { edge_t *e0; edge_t** edges; edge_t* elist[MAX_EDGE]; int i, e_cnt; e_cnt = 1; e0 = e; while ((e0 = ED_to_virt(e0))) e_cnt++; if (e_cnt <= MAX_EDGE) edges = elist; else edges = N_NEW(e_cnt,edge_t*); e0 = e; for (i = 0; i < e_cnt; i++) { edges[i] = e0; e0 = ED_to_virt(e0); } makeStraightEdges (g, edges, e_cnt, et, sinfo); if (e_cnt > MAX_EDGE) free (edges); }
void delete_flat_edge(edge_t * e) { assert(e != NULL); if (ED_to_orig(e) && ED_to_virt(ED_to_orig(e)) == e) ED_to_virt(ED_to_orig(e)) = NULL; zapinlist(&(ND_flat_out(e->tail)), e); zapinlist(&(ND_flat_in(e->head)), e); }
void class1(graph_t * g) { node_t *n, *t, *h; edge_t *e, *rep; mark_clusters(g); for (n = agfstnode(g); n; n = agnxtnode(g, n)) { for (e = agfstout(g, n); e; e = agnxtout(g, e)) { /* skip edges already processed */ if (ED_to_virt(e)) continue; /* skip edges that we want to ignore in this phase */ if (nonconstraint_edge(e)) continue; t = UF_find(agtail(e)); h = UF_find(aghead(e)); /* skip self, flat, and intra-cluster edges */ if (t == h) continue; /* inter-cluster edges require special treatment */ if (ND_clust(t) || ND_clust(h)) { interclust1(g, agtail(e), aghead(e), e); continue; } if ((rep = find_fast_edge(t, h))) merge_oneway(e, rep); else virtual_edge(t, h, e); #ifdef NOTDEF if ((t == agtail(e)) && (h == aghead(e))) { if (rep = find_fast_edge(t, h)) merge_oneway(e, rep); else virtual_edge(t, h, e); } else { f = agfindedge(g, t, h); if (f && (ED_to_virt(f) == NULL)) rep = virtual_edge(t, h, f); else rep = find_fast_edge(t, h); if (rep) merge_oneway(e, rep); else virtual_edge(t, h, e); } #endif } } }
void mark_lowclusters(Agraph_t * root) { Agnode_t *n, *vn; Agedge_t *orig, *e; /* first, zap any previous cluster labelings */ for (n = agfstnode(root); n; n = agnxtnode(root, n)) { ND_clust(n) = NULL; for (orig = agfstout(root, n); orig; orig = agnxtout(root, orig)) { if ((e = ED_to_virt(orig))) { #ifndef WITH_CGRAPH while (e && (vn = e->head)->u.node_type == VIRTUAL) { #else /* WITH_CGRAPH */ while (e && (ND_node_type(vn = aghead(e))) == VIRTUAL) { #endif /* WITH_CGRAPH */ ND_clust(vn) = NULL; e = ND_out(aghead(e)).list[0]; } } } } /* do the recursion */ mark_lowcluster_basic(root); } static void mark_lowcluster_basic(Agraph_t * g) { Agraph_t *clust; Agnode_t *n, *vn; Agedge_t *orig, *e; int c; for (c = 1; c <= GD_n_cluster(g); c++) { clust = GD_clust(g)[c]; mark_lowcluster_basic(clust); } /* see what belongs to this graph that wasn't already marked */ for (n = agfstnode(g); n; n = agnxtnode(g, n)) { if (ND_clust(n) == NULL) ND_clust(n) = g; for (orig = agfstout(g, n); orig; orig = agnxtout(g, orig)) { if ((e = ED_to_virt(orig))) { #ifndef WITH_CGRAPH while (e && (vn = e->head)->u.node_type == VIRTUAL) { #else /* WITH_CGRAPH */ while (e && (ND_node_type(vn = aghead(e))) == VIRTUAL) { #endif /* WITH_CGRAPH */ if (ND_clust(vn) == NULL) ND_clust(vn) = g; e = ND_out(aghead(e)).list[0]; } } } } }
static void rebuild_vlists(graph_t * g) { int c, i, r, maxi; node_t *n, *lead; edge_t *e, *rep; for (r = GD_minrank(g); r <= GD_maxrank(g); r++) GD_rankleader(g)[r] = NULL; for (n = agfstnode(g); n; n = agnxtnode(g, n)) { infuse(g, n); for (e = agfstout(g, n); e; e = agnxtout(g, e)) { for (rep = e; ED_to_virt(rep); rep = ED_to_virt(rep)); while (ND_rank(rep->head) < ND_rank(e->head)) { infuse(g, rep->head); rep = ND_out(rep->head).list[0]; } } } for (r = GD_minrank(g); r <= GD_maxrank(g); r++) { lead = GD_rankleader(g)[r]; if (ND_rank(g->root)[r].v[ND_order(lead)] != lead) abort(); GD_rank(g)[r].v = ND_rank(g->root)[r].v + GD_rankleader(g)[r]->u.order; maxi = -1; for (i = 0; i < GD_rank(g)[r].n; i++) { if ((n = GD_rank(g)[r].v[i]) == NULL) break; if (ND_node_type(n) == NORMAL) { if (agcontains(g, n)) maxi = i; else break; } else { edge_t *e; for (e = ND_in(n).list[0]; e && ED_to_orig(e); e = ED_to_orig(e)); if (e && (agcontains(g, e->tail)) && agcontains(g, e->head)) maxi = i; } } if (maxi == -1) agerr(AGWARN, "degenerate concentrated rank %s,%d\n", g->name, r); GD_rank(g)[r].n = maxi + 1; } for (c = 1; c <= GD_n_cluster(g); c++) rebuild_vlists(GD_clust(g)[c]); }
/* makeSelfArcs: * Generate loops. We use the library routine makeSelfEdge * which also places the labels. * We have to handle port labels here. * as well as update the bbox from edge labels. */ void makeSelfArcs(path * P, edge_t * e, int stepx) { int cnt = ED_count(e); if ((cnt == 1) || Concentrate) { edge_t *edges1[1]; edges1[0] = e; makeSelfEdge(P, edges1, 0, 1, stepx, stepx, &sinfo); if (ED_label(e)) updateBB(agraphof(agtail(e)), ED_label(e)); makePortLabels(e); } else { int i; edge_t **edges = N_GNEW(cnt, edge_t *); for (i = 0; i < cnt; i++) { edges[i] = e; e = ED_to_virt(e); } makeSelfEdge(P, edges, 0, cnt, stepx, stepx, &sinfo); for (i = 0; i < cnt; i++) { e = edges[i]; if (ED_label(e)) updateBB(agraphof(agtail(e)), ED_label(e)); makePortLabels(e); } free(edges); } }
static void freeDerivedGraph(graph_t * g, graph_t ** cc) { graph_t *cg; node_t *dn; node_t *dnxt; edge_t *e; while ((cg = *cc++)) { freeGData(cg); agdelrec(cg, "Agraphinfo_t"); } if (PORTS(g)) free(PORTS(g)); freeGData(g); agdelrec(g, "Agraphinfo_t"); for (dn = agfstnode(g); dn; dn = dnxt) { dnxt = agnxtnode(g, dn); for (e = agfstout(g, dn); e; e = agnxtout(g, e)) { free (ED_to_virt(e)); agdelrec(e, "Agedgeinfo_t"); } freeDeriveNode(dn); } agclose(g); }
static void cleanup1(graph_t * g) { node_t *n; edge_t *e, *f; int c; for (c = 0; c < GD_comp(g).size; c++) { GD_nlist(g) = GD_comp(g).list[c]; for (n = GD_nlist(g); n; n = ND_next(n)) { renewlist(&ND_in(n)); renewlist(&ND_out(n)); ND_mark(n) = FALSE; } } for (n = agfstnode(g); n; n = agnxtnode(g, n)) { for (e = agfstout(g, n); e; e = agnxtout(g, e)) { f = ED_to_virt(e); /* Null out any other references to f to make sure we don't * handle it a second time. For example, parallel multiedges * share a virtual edge. */ if (f && (e == ED_to_orig(f))) { edge_t *e1, *f1; node_t *n1; for (n1 = agfstnode(g); n1; n1 = agnxtnode(g, n1)) { for (e1 = agfstout(g, n1); e1; e1 = agnxtout(g, e1)) { if (e != e1) { f1 = ED_to_virt(e1); if (f1 && (f == f1)) { ED_to_virt(e1) = NULL; } } } } free(f->base.data); free(f); } ED_to_virt(e) = NULL; } } free(GD_comp(g).list); GD_comp(g).list = NULL; GD_comp(g).size = 0; }
void unmerge_oneway(edge_t *e) { edge_t *rep,*nextrep; for (rep = ED_to_virt(e); rep; rep = nextrep) { unrep(rep,e); nextrep = ED_to_virt(rep); if (ED_count(rep) == 0) safe_delete_fast_edge(rep); /* free(rep)? */ /* unmerge from a virtual edge chain */ while ((ED_edge_type(rep) == VIRTUAL) && (ND_node_type(rep->head) == VIRTUAL) && (ND_out(rep->head).size == 1)) { rep = ND_out(rep->head).list[0]; unrep(rep,e); } } ED_to_virt(e) = NULL; }
void basic_merge(edge_t *e, edge_t *rep) { if (ED_minlen(rep) < ED_minlen(e)) ED_minlen(rep) = ED_minlen(e); while (rep) { ED_count(rep) += ED_count(e); ED_xpenalty(rep) += ED_xpenalty(e); ED_weight(rep) += ED_weight(e); rep = ED_to_virt(rep); } }
static void make_interclust_chain(graph_t * g, node_t * from, node_t * to, edge_t * orig) { int newtype; node_t *u, *v; u = map_interclust_node(from); v = map_interclust_node(to); if ((u == from) && (v == to)) newtype = VIRTUAL; else newtype = CLUSTER_EDGE; map_path(u, v, orig, ED_to_virt(orig), newtype); }
/* dumpE: */ void dumpE(graph_t * g, int derived) { Agnode_t *n; Agedge_t *e; Agedge_t **ep; Agedge_t *el; int i; int deg; prIndent(); fprintf(stderr, "Graph %s : %d nodes %d edges\n", g->name, agnnodes(g), agnedges(g)); for (n = agfstnode(g); n; n = agnxtnode(g, n)) { deg = 0; for (e = agfstout(g, n); e; e = agnxtout(g, e)) { deg++; prIndent(); fprintf(stderr, " %s -- %s\n", e->tail->name, e->head->name); if (derived) { for (i = 0, ep = (Agedge_t **) ED_to_virt(e); i < ED_count(e); i++, ep++) { el = *ep; prIndent(); fprintf(stderr, " %s -- %s\n", el->tail->name, el->head->name); } } } if (deg == 0) { /* no out edges */ if (!agfstin(g, n)) /* no in edges */ fprintf(stderr, " %s\n", n->name); } } if (!derived) { bport_t *pp; if ((pp = PORTS(g))) { int sz = NPORTS(g); fprintf(stderr, " %d ports\n", sz); while (pp->e) { fprintf(stderr, " %s : %s -- %s\n", pp->n->name, pp->e->tail->name, pp->e->head->name); pp++; } } } }
/* makeStraightEdge: * * FIX: handle ports on boundary? */ void makeStraightEdge(graph_t * g, edge_t * e, int et, splineInfo* sinfo) { pointf dumb[4]; node_t *n = agtail(e); node_t *head = aghead(e); int e_cnt = ED_count(e); int curved = (et == ET_CURVED); pointf perp; pointf del; edge_t *e0; int i, j, xstep, dx; double l_perp; pointf dumber[4]; pointf p, q; p = dumb[1] = dumb[0] = add_pointf(ND_coord(n), ED_tail_port(e).p); q = dumb[2] = dumb[3] = add_pointf(ND_coord(head), ED_head_port(e).p); if ((e_cnt == 1) || Concentrate) { if (curved) bend(dumb,get_centroid(g)); clip_and_install(e, aghead(e), dumb, 4, sinfo); addEdgeLabels(g, e, p, q); return; } e0 = e; if (APPROXEQPT(dumb[0], dumb[3], MILLIPOINT)) { /* degenerate case */ dumb[1] = dumb[0]; dumb[2] = dumb[3]; del.x = 0; del.y = 0; } else { perp.x = dumb[0].y - dumb[3].y; perp.y = dumb[3].x - dumb[0].x; l_perp = LEN(perp.x, perp.y); xstep = GD_nodesep(g->root); dx = xstep * (e_cnt - 1) / 2; dumb[1].x = dumb[0].x + (dx * perp.x) / l_perp; dumb[1].y = dumb[0].y + (dx * perp.y) / l_perp; dumb[2].x = dumb[3].x + (dx * perp.x) / l_perp; dumb[2].y = dumb[3].y + (dx * perp.y) / l_perp; del.x = -xstep * perp.x / l_perp; del.y = -xstep * perp.y / l_perp; } for (i = 0; i < e_cnt; i++) { if (aghead(e0) == head) { p = dumb[0]; q = dumb[3]; for (j = 0; j < 4; j++) { dumber[j] = dumb[j]; } } else { p = dumb[3]; q = dumb[0]; for (j = 0; j < 4; j++) { dumber[3 - j] = dumb[j]; } } if (et == ET_PLINE) { Ppoint_t pts[4]; Ppolyline_t spl, line; line.pn = 4; line.ps = pts; for (j=0; j < 4; j++) { pts[j] = dumber[j]; } make_polyline (line, &spl); clip_and_install(e0, aghead(e0), spl.ps, spl.pn, sinfo); } else clip_and_install(e0, aghead(e0), dumber, 4, sinfo); addEdgeLabels(g, e0, p, q); e0 = ED_to_virt(e0); dumb[1].x += del.x; dumb[1].y += del.y; dumb[2].x += del.x; dumb[2].y += del.y; } }
/* this function marks every node in <g> with its top-level cluster under <g> */ void mark_clusters(graph_t * g) { int c; node_t *n, *nn, *vn; edge_t *orig, *e; graph_t *clust; /* remove sub-clusters below this level */ for (n = agfstnode(g); n; n = agnxtnode(g, n)) { if (ND_ranktype(n) == CLUSTER) UF_singleton(n); ND_clust(n) = NULL; } for (c = 1; c <= GD_n_cluster(g); c++) { clust = GD_clust(g)[c]; for (n = agfstnode(clust); n; n = nn) { nn = agnxtnode(clust,n); if (ND_ranktype(n) != NORMAL) { agerr(AGWARN, "%s was already in a rankset, deleted from cluster %s\n", agnameof(n), agnameof(g)); agdelete(clust,n); continue; } UF_setname(n, GD_leader(clust)); ND_clust(n) = clust; ND_ranktype(n) = CLUSTER; /* here we mark the vnodes of edges in the cluster */ for (orig = agfstout(clust, n); orig; orig = agnxtout(clust, orig)) { if ((e = ED_to_virt(orig))) { #ifndef WITH_CGRAPH while (e && (vn = e->head)->u.node_type == VIRTUAL) { #else /* WITH_CGRAPH */ while (e && ND_node_type(vn =aghead(e)) == VIRTUAL) { #endif /* WITH_CGRAPH */ ND_clust(vn) = clust; e = ND_out(aghead(e)).list[0]; /* trouble if concentrators and clusters are mixed */ } } } } } } void build_skeleton(graph_t * g, graph_t * subg) { int r; node_t *v, *prev, *rl; edge_t *e; prev = NULL; GD_rankleader(subg) = N_NEW(GD_maxrank(subg) + 2, node_t *); for (r = GD_minrank(subg); r <= GD_maxrank(subg); r++) { v = GD_rankleader(subg)[r] = virtual_node(g); ND_rank(v) = r; ND_ranktype(v) = CLUSTER; ND_clust(v) = subg; if (prev) { e = virtual_edge(prev, v, NULL); ED_xpenalty(e) *= CL_CROSS; } prev = v; } /* set the counts on virtual edges of the cluster skeleton */ for (v = agfstnode(subg); v; v = agnxtnode(subg, v)) { rl = GD_rankleader(subg)[ND_rank(v)]; ND_UF_size(rl)++; for (e = agfstout(subg, v); e; e = agnxtout(subg, e)) { for (r = ND_rank(agtail(e)); r < ND_rank(aghead(e)); r++) { ED_count(ND_out(rl).list[0])++; } } } for (r = GD_minrank(subg); r <= GD_maxrank(subg); r++) { rl = GD_rankleader(subg)[r]; if (ND_UF_size(rl) > 1) ND_UF_size(rl)--; } }
/* * attach and install edges between clusters. * essentially, class2() for interclust edges. */ void interclexp(graph_t * subg) { graph_t *g; node_t *n; edge_t *e, *prev, *next; g = agroot(subg); for (n = agfstnode(subg); n; n = agnxtnode(subg, n)) { /* N.B. n may be in a sub-cluster of subg */ prev = NULL; for (e = agfstedge(agroot(subg), n); e; e = next) { next = agnxtedge(agroot(subg), e, n); if (agcontains(subg, e)) continue; #ifdef WITH_CGRAPH /* canonicalize edge */ e = AGMKOUT(e); #endif /* short/flat multi edges */ if (mergeable(prev, e)) { if (ND_rank(agtail(e)) == ND_rank(aghead(e))) ED_to_virt(e) = prev; else ED_to_virt(e) = NULL; if (ED_to_virt(prev) == NULL) continue; /* internal edge */ merge_chain(subg, e, ED_to_virt(prev), FALSE); safe_other_edge(e); continue; } /* flat edges */ if (ND_rank(agtail(e)) == ND_rank(aghead(e))) { edge_t* fe; if ((fe = find_flat_edge(agtail(e), aghead(e))) == NULL) { flat_edge(g, e); prev = e; } else if (e != fe) { safe_other_edge(e); if (!ED_to_virt(e)) merge_oneway(e, fe); } continue; } /* This assertion is still valid if the new ranking is not used */ #ifndef WITH_CGRAPH assert(ED_to_virt(e) != NULL); #endif /* forward edges */ if (ND_rank(aghead(e)) > ND_rank(agtail(e))) { make_interclust_chain(g, agtail(e), aghead(e), e); prev = e; continue; } /* backward edges */ else { /* I think that make_interclust_chain should create call other_edge(e) anyway if (agcontains(subg,agtail(e)) && agfindedge(subg->root,aghead(e),agtail(e))) other_edge(e); */ make_interclust_chain(g, aghead(e), agtail(e), e); prev = e; } } } }
static void map_path(node_t * from, node_t * to, edge_t * orig, edge_t * ve, int type) { int r; node_t *u, *v; edge_t *e; assert(ND_rank(from) < ND_rank(to)); if ((agtail(ve) == from) && (aghead(ve) == to)) return; if (ED_count(ve) > 1) { ED_to_virt(orig) = NULL; if (ND_rank(to) - ND_rank(from) == 1) { if ((e = find_fast_edge(from, to)) && (ports_eq(orig, e))) { merge_oneway(orig, e); if ((ND_node_type(from) == NORMAL) && (ND_node_type(to) == NORMAL)) other_edge(orig); return; } } u = from; for (r = ND_rank(from); r < ND_rank(to); r++) { if (r < ND_rank(to) - 1) v = clone_vn(agraphof(from), aghead(ve)); else v = to; e = virtual_edge(u, v, orig); ED_edge_type(e) = type; u = v; ED_count(ve)--; ve = ND_out(aghead(ve)).list[0]; } } else { if (ND_rank(to) - ND_rank(from) == 1) { if ((ve = find_fast_edge(from, to)) && (ports_eq(orig, ve))) { /*ED_to_orig(ve) = orig; */ ED_to_virt(orig) = ve; ED_edge_type(ve) = type; ED_count(ve)++; if ((ND_node_type(from) == NORMAL) && (ND_node_type(to) == NORMAL)) other_edge(orig); } else { ED_to_virt(orig) = NULL; ve = virtual_edge(from, to, orig); ED_edge_type(ve) = type; } } if (ND_rank(to) - ND_rank(from) > 1) { e = ve; if (agtail(ve) != from) { ED_to_virt(orig) = NULL; e = ED_to_virt(orig) = virtual_edge(from, aghead(ve), orig); delete_fast_edge(ve); } else e = ve; while (ND_rank(aghead(e)) != ND_rank(to)) e = ND_out(aghead(e)).list[0]; if (aghead(e) != to) { ve = e; e = virtual_edge(agtail(e), to, orig); ED_edge_type(e) = type; delete_fast_edge(ve); } } } }
/* genroute: * Generate splines for e and cohorts. * Edges go from s to t. * Return 0 on success. */ static int genroute(graph_t* g, tripoly_t * trip, int s, int t, edge_t * e, int doPolyline) { pointf eps[2]; Pvector_t evs[2]; pointf **cpts; /* lists of control points */ Ppoly_t poly; Ppolyline_t pl, spl; int i, j; Ppolyline_t mmpl; Pedge_t *medges = N_GNEW(trip->poly.pn, Pedge_t); int pn; int mult = ED_count(e); node_t* head = aghead(e); eps[0].x = trip->poly.ps[s].x, eps[0].y = trip->poly.ps[s].y; eps[1].x = trip->poly.ps[t].x, eps[1].y = trip->poly.ps[t].y; Pshortestpath(&(trip->poly), eps, &pl); if (pl.pn == 2) { makeStraightEdge(agraphof(head), e, doPolyline); return 0; } evs[0].x = evs[0].y = 0; evs[1].x = evs[1].y = 0; if ((mult == 1) || Concentrate) { poly = trip->poly; for (j = 0; j < poly.pn; j++) { medges[j].a = poly.ps[j]; medges[j].b = poly.ps[(j + 1) % poly.pn]; } tweakPath (poly, s, t, pl); Proutespline(medges, poly.pn, pl, evs, &spl); finishEdge (g, e, spl, aghead(e) != head, eps[0], eps[1]); free(medges); return 0; } pn = 2 * (pl.pn - 1); cpts = N_NEW(pl.pn - 2, pointf *); for (i = 0; i < pl.pn - 2; i++) { cpts[i] = mkCtrlPts(t, mult+1, pl.ps[i], pl.ps[i + 1], pl.ps[i + 2], trip); if (!cpts[i]) { agerr(AGWARN, "Could not create control points for multiple spline for edge (%s,%s)\n", agnameof(agtail(e)), agnameof(aghead(e))); return 1; } } poly.ps = N_GNEW(pn, pointf); poly.pn = pn; for (i = 0; i < mult; i++) { poly.ps[0] = eps[0]; for (j = 1; j < pl.pn - 1; j++) { poly.ps[j] = cpts[j - 1][i]; } poly.ps[pl.pn - 1] = eps[1]; for (j = 1; j < pl.pn - 1; j++) { poly.ps[pn - j] = cpts[j - 1][i + 1]; } Pshortestpath(&poly, eps, &mmpl); if (doPolyline) { make_polyline (mmpl, &spl); } else { for (j = 0; j < poly.pn; j++) { medges[j].a = poly.ps[j]; medges[j].b = poly.ps[(j + 1) % poly.pn]; } tweakPath (poly, 0, pl.pn-1, mmpl); Proutespline(medges, poly.pn, mmpl, evs, &spl); } finishEdge (g, e, spl, aghead(e) != head, eps[0], eps[1]); e = ED_to_virt(e); } for (i = 0; i < pl.pn - 2; i++) free(cpts[i]); free(cpts); free(medges); free(poly.ps); return 0; }