/* * COPYRIGHT * * cschem - modular/flexible schematics editor - libcschem (core library) * Copyright (C) 2018 Tibor 'Igor2' Palinkas * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version.* * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 31 Milk Street, # 960789 Boston, MA 02196 USA * * Contact: * Project page: http://repo.hu/projects/sch-rnd * contact lead developer: http://www.repo.hu/projects/sch-rnd/contact.html * mailing list: http://www.repo.hu/projects/sch-rnd/contact.html */ #include "config.h" #include #include #include #include #include "event.h" #include "concrete.h" #include "cnc_obj.h" #include "cnc_any_obj.h" #include "cnc_arc.h" #include "operation.h" #include "op_common.h" #include "gengeo2d/box.h" #include "gengeo2d/xform.h" #include "undo.h" #include "rotate.h" #include "cnc_poly.h" csch_cpoly_t *csch_cpoly_alloc(csch_sheet_t *sheet, csch_cgrp_t *parent, csch_oid_t oid) { csch_cpoly_t *poly; poly = htip_get(&parent->id2obj, oid); if (poly != NULL) return NULL; poly = calloc(sizeof(csch_cpoly_t), 1); csch_cobj_init(&poly->hdr, sheet, parent, oid, CSCH_CTYPE_POLY); return poly; } csch_cpoly_t *csch_cpoly_dup(csch_sheet_t *sheet, csch_cgrp_t *parent, const csch_cpoly_t *src, int keep_id) { csch_cpoly_t *dst = csch_cpoly_alloc(sheet, parent, CSCH_KEEP_OID(parent, src->hdr.oid)); dst->has_stroke = src->has_stroke; dst->has_fill = src->has_fill; csch_chdr_copy_meta4dup(&dst->hdr, &src->hdr); csch_vtcoutline_enlarge(&dst->outline, src->outline.used); memcpy(dst->outline.array, src->outline.array, src->outline.used * sizeof(csch_coutline_t)); dst->outline.used = src->outline.used; return dst; } void csch_cpoly_free(csch_cpoly_t *poly) { csch_cobj_uninit(&poly->hdr); csch_vtcoutline_uninit(&poly->outline); free(poly); } csch_cpoly_t *csch_cpoly_get(csch_sheet_t *sheet, csch_cgrp_t *grp, csch_oid_t oid) { csch_cpoly_t *poly = htip_get(&grp->id2obj, oid); if ((poly != NULL) && (poly->hdr.type != CSCH_CTYPE_POLY)) return NULL; return poly; } void csch_cpoly_center_bbox(csch_sheet_t *sheet, const csch_cpoly_t *poly, csch_rtree_box_t *dst) { long n; csch_coutline_t *o; csch_rtree_box_t tmp; csch_bbox_reset(dst); for(n = 0, o = poly->outline.array; n < poly->outline.used; n++, o++) { switch(o->hdr.type) { case CSCH_CTYPE_LINE: csch_line_center_bbox(sheet, &o->line, &tmp); break; case CSCH_CTYPE_ARC: csch_arc_center_bbox(sheet, &o->arc, &tmp); break; default: break; /* already warned */ } csch_bbox_bump(dst, x, tmp.x1); csch_bbox_bump(dst, x, tmp.x2); csch_bbox_bump(dst, y, tmp.y1); csch_bbox_bump(dst, y, tmp.y2); } } void csch_poly_update(csch_sheet_t *sheet, csch_cpoly_t *poly, int do_xform) { long n; csch_coutline_t *o; csch_cobj_update_pen(&poly->hdr); poly->area = -1; if (do_xform) { for(n = 0, o = poly->outline.array; n < poly->outline.used; n++, o++) { csch_ctype_t type_save = o->hdr.type; o->hdr = poly->hdr; o->hdr.type = type_save; switch(o->hdr.type) { case CSCH_CTYPE_LINE: csch_line_update_xform(sheet, &o->line); break; case CSCH_CTYPE_ARC: csch_arc_update_xform(sheet, &o->arc); break; default: rnd_message(RND_MSG_ERROR, "Polygon #%ld contains an invalid object in its contour\n", poly->hdr.oid); break; } } } csch_cobj_rtree_del(sheet, &poly->hdr); csch_obj_bbox_reset(poly); for(n = 0, o = poly->outline.array; n < poly->outline.used; n++, o++) { switch(o->hdr.type) { case CSCH_CTYPE_LINE: csch_line_update_bbox(sheet, &o->line); break; case CSCH_CTYPE_ARC: csch_arc_update_bbox(sheet, &o->arc); break; default: break; /* already warned above */ } csch_bbox_bump(&poly->hdr.bbox, x, o->hdr.bbox.x1); csch_bbox_bump(&poly->hdr.bbox, x, o->hdr.bbox.x2); csch_bbox_bump(&poly->hdr.bbox, y, o->hdr.bbox.y1); csch_bbox_bump(&poly->hdr.bbox, y, o->hdr.bbox.y2); } csch_cobj_rtree_add(sheet, &poly->hdr); csch_cobj_bbox_changed(sheet, &poly->hdr); } int csch_cpoly_has_fill(csch_cpoly_t *poly) { return poly->has_fill; } static csch_chdr_t *poly_create(csch_sheet_t *sheet, csch_cgrp_t *parent) { csch_cpoly_t *poly = csch_cpoly_alloc(sheet, parent, csch_oid_new(sheet, parent)); if (poly == NULL) return NULL; return &poly->hdr; } static void poly_remove_alloc(csch_undo_remove_t *slot) { } static void poly_remove_redo(csch_undo_remove_t *slot) { csch_cnc_common_remove_redo(slot, CSCH_REM_FROM_RTREE | CSCH_REM_FROM_PARENT | CSCH_REM_DEL_EMPTY_PARENT); } static void poly_remove_undo(csch_undo_remove_t *slot) { csch_cnc_common_remove_undo(slot, CSCH_REM_FROM_RTREE | CSCH_REM_FROM_PARENT | CSCH_REM_DEL_EMPTY_PARENT); } /* obj_idx is at most 2 items long but can be NULL to skip the search */ RND_INLINE int poly_isc_with_box_(csch_chdr_t *obj, csch_rtree_box_t *box, long *obj_idx) { csch_cpoly_t *poly = (csch_cpoly_t *)obj; g2d_box_t gbox, pbox, obox; long n; int found = 0; csch_coutline_t *o; gbox.p1.x = box->x1; gbox.p1.y = box->y1; gbox.p2.x = box->x2; gbox.p2.y = box->y2; pbox.p1.x = poly->hdr.bbox.x1; pbox.p1.y = poly->hdr.bbox.y1; pbox.p2.x = poly->hdr.bbox.x2; pbox.p2.y = poly->hdr.bbox.y2; /* poly fully within the box (cheapest) */ if (g2d_box_in_box(&gbox, &pbox)) return 1; /* expect reasonably small polygons; typical use case ranges from triangle to octagon. Linear search will be good enough for this */ for(n = 0, o = poly->outline.array; n < poly->outline.used; n++, o++) { /* optimization: don't go and check the object if its bbox doesn't intersect with target bbox */ obox.p1.x = o->hdr.bbox.x1; obox.p1.y = o->hdr.bbox.y1; obox.p2.x = o->hdr.bbox.x2; obox.p2.y = o->hdr.bbox.y2; if (!g2d_isc_box_box(&gbox, &obox)) continue; if (csch_isc_with_box(&o->hdr, box)) { if (obj_idx != NULL) { obj_idx[found] = n; found++; if (found == 2) return 2; } else return 1; } } return found; } static int poly_isc_with_box(csch_chdr_t *obj, csch_rtree_box_t *box) { return poly_isc_with_box_(obj, box, NULL); } int csch_poly_contour_objs_at(csch_cpoly_t *poly, csch_coord_t x, csch_coord_t y, csch_coord_t r, long out[2]) { csch_rtree_box_t box; long obj_idx[2]; int len; box.x1 = x - r; box.y1 = y - r; box.x2 = x + r; box.y2 = y + r; len = poly_isc_with_box_(&poly->hdr, &box, obj_idx); switch(len) { case 0: out[0] = out[1] = -1; return 0; case 1: out[0] = obj_idx[0]; out[1] = -1; return 1; case 2: /* need to sort objects */ if (obj_idx[1] == obj_idx[0]+1) { out[0] = obj_idx[0]; out[1] = obj_idx[1]; return 2; } else if ((obj_idx[0] == 0) && (obj_idx[1] == poly->outline.used - 1)) { out[0] = obj_idx[1]; out[1] = obj_idx[0]; return 2; } else { /* self-isect poly? */ out[0] = obj_idx[0]; out[1] = -1; return 1; } break; } assert(!"internal error poly_isc_with_box_()"); return 0; } int csch_poly_contour_obj_ends_at(csch_cpoly_t *poly, long idx, csch_coord_t x, csch_coord_t y) { csch_coutline_t *o; csch_coord_t ex, ey; if ((idx < 0) || (idx >= poly->outline.used)) return 0; o = &poly->outline.array[idx]; switch(o->hdr.type) { case CSCH_CTYPE_LINE: if ((o->line.inst.c.p1.x == x) && (o->line.inst.c.p1.y == y)) return 1; if ((o->line.inst.c.p2.x == x) && (o->line.inst.c.p2.y == y)) return 1; return 0; case CSCH_CTYPE_ARC: if ((csch_arc_get_endxy(&o->arc, 0, &ex, &ey) == 0) && (ex == x) && (ey == y)) return 1; if ((csch_arc_get_endxy(&o->arc, 1, &ex, &ey) == 0) && (ex == x) && (ey == y)) return 1; return 0; default: break; /* Invalid object in polygon, already warned on create/load */ } return 0; } static void poly_outline_move(csch_vtcoutline_t *outline, csch_coord_t dx, csch_coord_t dy) { csch_coutline_t *o; long n; for(n = 0, o = outline->array; n < outline->used; n++,o++) { switch(o->hdr.type) { case CSCH_CTYPE_LINE: o->line.spec.p1.x += dx; o->line.spec.p1.y += dy; o->line.spec.p2.x += dx; o->line.spec.p2.y += dy; break; case CSCH_CTYPE_ARC: o->arc.spec.c.x += dx; o->arc.spec.c.y += dy; break; default: break; /* Invalid object in polygon, already warned on create/load */ } } } static void poly_move(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t dx, csch_coord_t dy, int undoable) { csch_cpoly_t *poly = (csch_cpoly_t *)obj; csch_vtcoutline_t *outline; void *cookie; outline = csch_cpoly_modify_geo_begin(sheet, poly, &cookie, undoable); poly_outline_move(outline, dx, dy); csch_cpoly_modify_geo_end(sheet, poly, &cookie); } static void poly_copy(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t dx, csch_coord_t dy, int undoable) { csch_cpoly_t *poly = csch_cpoly_dup(sheet, obj->parent, (csch_cpoly_t *)obj, 0); if (poly != NULL) { poly_outline_move(&poly->outline, dx, dy); csch_poly_update(sheet, poly, 1); csch_op_inserted(sheet, obj->parent, &poly->hdr); } } static void poly_outline_rotate(csch_vtcoutline_t *outline, csch_coord_t rcx, csch_coord_t rcy, double da) { csch_coutline_t *o; double rad = -da / RND_RAD_TO_DEG; double cs = cos(rad), sn = sin(rad); long n; for(n = 0, o = outline->array; n < outline->used; n++,o++) { switch(o->hdr.type) { case CSCH_CTYPE_LINE: csch_rotate_pt(&o->line.spec.p1.x, &o->line.spec.p1.y, rcx, rcy, cs, sn); csch_rotate_pt(&o->line.spec.p2.x, &o->line.spec.p2.y, rcx, rcy, cs, sn); break; case CSCH_CTYPE_ARC: csch_arc_rotate_(&o->arc, rcx, rcy, da, cs, sn, &o->arc.spec.c.x, &o->arc.spec.c.y, &o->arc.spec.start); break; default: break; /* Invalid object in polygon, already warned on create/load */ } } } static void poly_rotate(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t rcx, csch_coord_t rcy, double da, int undoable) { csch_cpoly_t *poly = (csch_cpoly_t *)obj; csch_vtcoutline_t *outline; void *cookie; outline = csch_cpoly_modify_geo_begin(sheet, poly, &cookie, undoable); poly_outline_rotate(outline, rcx, rcy, da); csch_cpoly_modify_geo_end(sheet, poly, &cookie); } static void poly_outline_rotate90(csch_vtcoutline_t *outline, csch_coord_t rcx, csch_coord_t rcy, int steps) { csch_coutline_t *o; long n; for(n = 0, o = outline->array; n < outline->used; n++,o++) { switch(o->hdr.type) { case CSCH_CTYPE_LINE: csch_rotate90_pt(&o->line.spec.p1.x, &o->line.spec.p1.y, rcx, rcy, steps); csch_rotate90_pt(&o->line.spec.p2.x, &o->line.spec.p2.y, rcx, rcy, steps); break; case CSCH_CTYPE_ARC: csch_arc_rotate90_(&o->arc, rcx, rcy, steps, &o->arc.spec.c.x, &o->arc.spec.c.y, &o->arc.spec.start); break; default: break; /* Invalid object in polygon, already warned on create/load */ } } } static void poly_rotate90(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t rcx, csch_coord_t rcy, int steps, int undoable) { csch_cpoly_t *poly = (csch_cpoly_t *)obj; csch_vtcoutline_t *outline; void *cookie; outline = csch_cpoly_modify_geo_begin(sheet, poly, &cookie, undoable); poly_outline_rotate90(outline, rcx, rcy, steps); csch_cpoly_modify_geo_end(sheet, poly, &cookie); } static void poly_outline_mirror(csch_vtcoutline_t *outline, csch_coord_t mcx, csch_coord_t mcy, int mirx, int miry) { csch_coutline_t *o; long n; for(n = 0, o = outline->array; n < outline->used; n++,o++) { switch(o->hdr.type) { case CSCH_CTYPE_LINE: csch_mirror_pt(&o->line.spec.p1.x, &o->line.spec.p1.y, mcx, mcy, mirx, miry); csch_mirror_pt(&o->line.spec.p2.x, &o->line.spec.p2.y, mcx, mcy, mirx, miry); break; case CSCH_CTYPE_ARC: csch_arc_mirror_(&o->arc, mcx, mcy, mirx, miry, &o->arc.spec.c.x, &o->arc.spec.c.y, &o->arc.spec.start, &o->arc.spec.delta); break; default: break; /* Invalid object in polygon, already warned on create/load */ } } } static void poly_mirror(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t mcx, csch_coord_t mcy, int mirx, int miry, int undoable) { csch_cpoly_t *poly = (csch_cpoly_t *)obj; csch_vtcoutline_t *outline; void *cookie; outline = csch_cpoly_modify_geo_begin(sheet, poly, &cookie, undoable); poly_outline_mirror(outline, mcx, mcy, mirx, miry); csch_cpoly_modify_geo_end(sheet, poly, &cookie); } static void poly_outline_inst2spec(csch_sheet_t *sheet, csch_vtcoutline_t *dst, const csch_vtcoutline_t *src) { const csch_coutline_t *s; csch_coutline_t *d; long n; for(n = 0, s = src->array, d = dst->array; n < src->used; n++,s++,d++) { switch(s->hdr.type) { case CSCH_CTYPE_LINE: d->line.spec = s->line.inst.c; break; case CSCH_CTYPE_ARC: d->arc.spec = s->arc.inst.c; d->arc.svalid = d->arc.evalid = 0; break; default: break; /* Invalid object in polygon, already warned on create/load */ } } } static void poly_inst2spec(csch_sheet_t *sheet, csch_chdr_t *obj, const csch_chdr_t *in, int undoable) { csch_cpoly_t *dst = (csch_cpoly_t *)obj; const csch_cpoly_t *src = (csch_cpoly_t *)in; csch_vtcoutline_t *outline; void *cookie; outline = csch_cpoly_modify_geo_begin(sheet, dst, &cookie, undoable); poly_outline_inst2spec(sheet, outline, &src->outline); csch_cpoly_modify_geo_end(sheet, dst, &cookie); } const csch_ops_t csch_ops_poly = { poly_create, poly_remove_alloc, poly_remove_redo, poly_remove_undo, poly_isc_with_box, poly_move, poly_copy, poly_rotate, poly_rotate90, poly_mirror, poly_inst2spec }; RND_INLINE unsigned csch_contour_hash(const csch_chdr_t *obj) { switch(obj->type) { case CSCH_CTYPE_LINE: return csch_line_hash_((const csch_line_t *)obj, 0, 1); case CSCH_CTYPE_ARC: return csch_arc_hash_((const csch_arc_t *)obj, 0, 1); default: break; } return 0; } RND_INLINE int csch_contour_keyeq(const csch_chdr_t *p1, const csch_chdr_t *p2) { if (p1->type != p2->type) return 0; switch(p1->type) { case CSCH_CTYPE_LINE: return csch_line_keyeq_((const csch_line_t *)p1, (const csch_line_t *)p2, 0, 1); case CSCH_CTYPE_ARC: return csch_arc_keyeq_((const csch_arc_t *)p1, (const csch_arc_t *)p2, 0, 1); default: break; } return 1; } unsigned csch_cpoly_hash(const csch_cpoly_t *poly, csch_hash_ignore_t ignore) { unsigned res = csch_chdr_hash(&poly->hdr); long n; res ^= ((unsigned)poly->has_stroke) << 13; res ^= ((unsigned)poly->has_fill) << 15; res ^= ((unsigned)poly->outline.used) << 3; if (!(ignore & CSCH_HIGN_FLOATER_GEO) || !poly->hdr.floater) for(n = 0; n < poly->outline.used; n++) res ^= csch_contour_hash(&poly->outline.array[n].hdr); return res; } int csch_cpoly_keyeq(const csch_cpoly_t *p1, const csch_cpoly_t *p2, csch_hash_ignore_t ignore) { long n; if (!csch_chdr_eq(&p1->hdr, &p2->hdr)) return 0; if (p1->has_stroke != p2->has_stroke) return 0; if (p1->has_fill != p2->has_fill) return 0; if (p1->outline.used != p2->outline.used) return 0; if (!(ignore & CSCH_HIGN_FLOATER_GEO) || !p1->hdr.floater || !p2->hdr.floater) for(n = 0; n < p1->outline.used; n++) if (!csch_contour_keyeq(&p1->outline.array[n].hdr, &p2->outline.array[n].hdr)) return 0; return 1; } /*** Modify ***/ typedef struct { csch_cpoly_t *poly; /* it is safe to save the object pointer because it is persistent (through the removed object list) */ csch_vtcoutline_t outline; unsigned swap_outline:1; unsigned has_stroke:1; unsigned has_fill:1; unsigned swap_has:1; } undo_poly_modify_t; static int undo_poly_modify_swap(void *udata) { undo_poly_modify_t *u = udata; csch_cobj_redraw(u->poly); if (u->swap_has) { csch_sheet_t *sheet = u->poly->hdr.sheet; /* if has_fill changes the fill layer rtree needs to be updated accordingly */ if (u->poly->has_fill && !u->has_fill) csch_rtree_delete(&sheet->dsply_fill[u->poly->hdr.dsply], &u->poly->hdr, &u->poly->hdr.bbox); else if (!u->poly->has_fill && u->has_fill) csch_rtree_insert(&sheet->dsply_fill[u->poly->hdr.dsply], &u->poly->hdr, &u->poly->hdr.bbox); rnd_swap(int, u->has_stroke, u->poly->has_stroke); rnd_swap(int, u->has_fill, u->poly->has_fill); } if (u->swap_outline) { rnd_swap(csch_vtcoutline_t, u->outline, u->poly->outline); csch_poly_update(u->poly->hdr.sheet, u->poly, 1); } csch_op_geo_edited(u->poly->hdr.sheet, &u->poly->hdr); return 0; } static void undo_poly_modify_print(void *udata, char *dst, size_t dst_len) { undo_poly_modify_t *u = udata; rnd_snprintf(dst, dst_len, "poly modification has_stroke %d <-> %d, has_fill %d <-> %d", u->poly->has_stroke, u->has_stroke, u->poly->has_fill, u->has_fill); } static const char core_cpoly_cookie[] = "libcschem/core/cnc_poly.c"; static const uundo_oper_t undo_poly_modify = { core_cpoly_cookie, NULL, undo_poly_modify_swap, undo_poly_modify_swap, undo_poly_modify_print }; void csch_cpoly_modify(csch_sheet_t *sheet, csch_cpoly_t *poly, int *has_stroke, int *has_fill, int undoable) { undo_poly_modify_t utmp, *u = &utmp; if (undoable) u = uundo_append(&sheet->undo, &undo_poly_modify, sizeof(undo_poly_modify_t)); u->poly = poly; u->has_stroke = CSCH_UNDO_MODIFY(poly, has_stroke); u->has_fill = CSCH_UNDO_MODIFY(poly, has_fill); u->swap_has = 1; u->swap_outline = 0; undo_poly_modify_swap(u); if (undoable) csch_undo_inc_serial(sheet); } static char not_undoable[] = "not undoable"; csch_vtcoutline_t *csch_cpoly_modify_geo_begin(csch_sheet_t *sheet, csch_cpoly_t *poly, void **cookie, int undoable) { undo_poly_modify_t utmp, *u = &utmp; long len; if (undoable) u = uundo_append(&sheet->undo, &undo_poly_modify, sizeof(undo_poly_modify_t)); u->poly = poly; u->swap_has = 0; u->swap_outline = 1; if (undoable) { /* clone the outline */ u->outline.alloced = poly->outline.used; u->outline.used = poly->outline.used; len = sizeof(csch_coutline_t) * u->outline.alloced; u->outline.array = malloc(len); memcpy(u->outline.array, poly->outline.array, len); *cookie = u; return &u->outline; } *cookie = (void *)not_undoable; return &poly->outline; } void csch_cpoly_modify_geo_end(csch_sheet_t *sheet, csch_cpoly_t *poly, void **cookie) { undo_poly_modify_t *u = *cookie; if (u == (void *)not_undoable) { csch_poly_update(sheet, poly, 1); csch_op_geo_edited(sheet, &poly->hdr); } else { undo_poly_modify_swap(u); csch_undo_inc_serial(sheet); } *cookie = NULL; }