improve edge combine alg to avoid crossing paths
This commit is contained in:
parent
3f5da909b6
commit
f9359459fe
9 changed files with 286 additions and 273 deletions
|
|
@ -38,11 +38,6 @@ typedef enum {
|
|||
* Sphere with radius 1, centered a [0,0,0] */
|
||||
CP_CSG3_SPHERE = CP_CSG3_TYPE + 1,
|
||||
|
||||
/**
|
||||
* Cylinder with length 1, radius 1, centered at [0,0,0], along z-axis.
|
||||
* The top radius can be set (so this also implements cone and frustrum). */
|
||||
CP_CSG3_CYL,
|
||||
|
||||
/**
|
||||
* Polyhedron */
|
||||
CP_CSG3_POLY,
|
||||
|
|
|
|||
|
|
@ -285,21 +285,6 @@ static inline cp_ring_t *cp_ring_next(
|
|||
return __cp_ring_get_buddy(b, __cp_ring_ref(b,a));
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a node is a mirror.
|
||||
*
|
||||
* Note that all singleton and pair nodes are ends. Only
|
||||
* in rings of length 3 or more mirrors and non-mirrors can
|
||||
* be distinguished.
|
||||
*
|
||||
* Runtime: O(1).
|
||||
*/
|
||||
static inline bool cp_ring_is_mirr(
|
||||
cp_ring_t const *a)
|
||||
{
|
||||
return a->n[0] == a->n[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next node after the edge a-b.
|
||||
*
|
||||
|
|
@ -489,12 +474,27 @@ static inline bool cp_ring_is_pair(
|
|||
}
|
||||
|
||||
/**
|
||||
* Whether a ring is an end (including singletons).
|
||||
* Whether a node is part of a pair.
|
||||
*
|
||||
* Runtime: O(1)
|
||||
*/
|
||||
static inline bool cp_ring_is_moiety(
|
||||
cp_ring_t *a)
|
||||
{
|
||||
return cp_ring_is_pair(a, a->n[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a ring is an end or mirror (including singletons).
|
||||
*
|
||||
* Note that all singleton and pair nodes are ends. Only
|
||||
* in rings of length 3 or more mirrors and non-mirrors can
|
||||
* be distinguished.
|
||||
*
|
||||
* Runtime: O(1)
|
||||
*/
|
||||
static inline bool cp_ring_is_end(
|
||||
cp_ring_t *a)
|
||||
cp_ring_t const *a)
|
||||
{
|
||||
return (a->n[0] == a->n[1]);
|
||||
}
|
||||
|
|
|
|||
475
src/csg2-bool.c
475
src/csg2-bool.c
|
|
@ -104,8 +104,6 @@ struct event {
|
|||
cp_dict_t node_q;
|
||||
/** Node for storing in ctxt::end */
|
||||
cp_dict_t node_end;
|
||||
/** Node for storing in ctxt::poly */
|
||||
cp_list_t node_poly;
|
||||
};
|
||||
|
||||
cp_loc_t loc;
|
||||
|
|
@ -214,6 +212,10 @@ typedef struct {
|
|||
/** Whether to output all points or to drop those of adjacent collinear
|
||||
* lines. */
|
||||
bool all_points;
|
||||
|
||||
/**
|
||||
* Temporary array for processing vertices when connecting polygon chains */
|
||||
v_event_p_t vert;
|
||||
} ctxt_t;
|
||||
|
||||
/**
|
||||
|
|
@ -396,27 +398,16 @@ static void debug_print_s(
|
|||
}
|
||||
|
||||
/* chain */
|
||||
cp_printf(cp_debug_ps, "4 setlinewidth\n");
|
||||
cp_printf(cp_debug_ps, "2 setlinewidth\n");
|
||||
i = 0;
|
||||
for (cp_dict_each(_e, c->end)) {
|
||||
cp_printf(cp_debug_ps, "1 %g 0 setrgbcolor\n", three_steps(i) * 0.6);
|
||||
cp_printf(cp_debug_ps, "0 %g 0.8 setrgbcolor\n", three_steps(i));
|
||||
event_t *e0 = CP_BOX_OF(_e, event_t, node_end);
|
||||
cp_debug_ps_dot(CP_PS_XY(e0->p->v.coord), 4);
|
||||
debug_print_chain(e0, cp_debug_ps_page_cnt);
|
||||
i++;
|
||||
}
|
||||
|
||||
/* poly */
|
||||
cp_printf(cp_debug_ps, "2 setlinewidth\n");
|
||||
i = 0;
|
||||
for (cp_list_each(_e, &c->poly)) {
|
||||
cp_printf(cp_debug_ps, "0 %g 0.8 setrgbcolor\n", three_steps(i));
|
||||
event_t *e0 = CP_BOX_OF(_e, event_t, node_poly);
|
||||
cp_debug_ps_dot(CP_PS_XY(e0->p->v.coord), 4);
|
||||
debug_print_chain(e0, ~cp_debug_ps_page_cnt);
|
||||
i++;
|
||||
}
|
||||
|
||||
/* end page */
|
||||
cp_ps_page_end(cp_debug_ps);
|
||||
}
|
||||
|
|
@ -837,47 +828,206 @@ static int pt_cmp_end_d(
|
|||
}
|
||||
|
||||
/**
|
||||
* Try to insert a node into a the polygon chain end store.
|
||||
* If a duplicate is found, extract and return it instead of inserting e.
|
||||
* Insert a vertex into the node_end structure. Duplicates are OK
|
||||
* and will be handled later.
|
||||
*/
|
||||
static event_t *chain_insert_or_extract(
|
||||
static void end_insert(
|
||||
ctxt_t *c,
|
||||
event_t *e)
|
||||
{
|
||||
LOG("insert %s\n", ev_str(e));
|
||||
cp_dict_t *_r = cp_dict_insert(&e->node_end, &c->end, pt_cmp_end_d, NULL, 0);
|
||||
if (_r == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
cp_dict_remove(_r, &c->end);
|
||||
return CP_BOX_OF(_r, event_t, node_end);
|
||||
(void)cp_dict_insert(&e->node_end, &c->end, pt_cmp_end_d, NULL, +1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect an edge e to a polygon point o1 that may already be
|
||||
* connected to more points.
|
||||
* Add an edge to the output edge. Only right events are added.
|
||||
*/
|
||||
static void chain_join(
|
||||
event_t *o1,
|
||||
event_t *e)
|
||||
{
|
||||
LOG("join %s with %s\n", ev_str(o1), ev_str(e));
|
||||
assert(cp_ring_is_end(&o1->node_chain));
|
||||
assert(cp_ring_is_end(&e->node_chain));
|
||||
cp_ring_join(&o1->node_chain, &e->node_chain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert into polygon output list */
|
||||
static void poly_add(
|
||||
static void chain_add(
|
||||
ctxt_t *c,
|
||||
event_t *e)
|
||||
{
|
||||
LOG("poly %s\n", ev_str(e));
|
||||
LOG("out: %s (%p)\n", ev_str(e), e);
|
||||
|
||||
event_t *o= e->other;
|
||||
|
||||
/* the event should left and neither point should be s or q */
|
||||
assert(!e->left);
|
||||
assert(pt_cmp(e->p, o->p) >= 0);
|
||||
assert(!cp_dict_is_member(&e->node_s));
|
||||
assert(!cp_dict_is_member(&e->node_q));
|
||||
assert(!cp_dict_is_member(&e->node_end));
|
||||
cp_list_init(&e->node_poly);
|
||||
cp_list_insert(&c->poly, &e->node_poly);
|
||||
assert(!cp_dict_is_member(&o->node_s));
|
||||
assert(!cp_dict_is_member(&o->node_q));
|
||||
|
||||
/*
|
||||
* This algorithm combines output edges into a polygon ring. Because
|
||||
* we can have multiple edges meeting in a single point, we cannot
|
||||
* directly connect points as they come it; in some case, this would
|
||||
* create crossing paths, which we cannot have.
|
||||
*
|
||||
* Instead, we first add all points (both ends of each edge) to a
|
||||
* set ordered by point coordinates (c->end using node_end). Left
|
||||
* and right vertices of each inserted edge are left singletons
|
||||
* (wrt. node_chain), i.e., the edges are defined by ->other,
|
||||
* and the next edge is found via a pair in (node_chain).
|
||||
* Identical points are in no particular order (we could sort them
|
||||
* now already, but we do not need the order for most of the point
|
||||
* pair, so comparing would be a waste at this point. The data
|
||||
* structure will, in the end, have an even number of vertices at
|
||||
* each point coordinate. Usually, it will have 2 unless vertices
|
||||
* coincide.
|
||||
*
|
||||
* When everything is inserted, we iterate the c->end data
|
||||
* structure and take out groups of equal points. If there are 2,
|
||||
* they are connected into a chain. For more than 2, the points
|
||||
* are sorted by absolute angle so that there is no edge between
|
||||
* adjacent vertices. Sorted this way, they can be connected
|
||||
* again.
|
||||
*
|
||||
* This second step will notice collapses of edges in the form
|
||||
* a-b-a, because the angle of the two a-b edges is equal. Both
|
||||
* vertices of these edges are removed from the data structures.
|
||||
* (It may be that the countervertex is the same edge, as in a-b-c,
|
||||
* but there may also be two distinct vertices stemming from longer
|
||||
* collapsed chains, e.g. in a-b-c-b-a.)
|
||||
*
|
||||
* In the last step, polygons are reconstructed from the chains
|
||||
* (in node_chain), each polygon is found by iterating
|
||||
* c->end (in node_end) again, marking what was already extracted.
|
||||
*
|
||||
* In total, this takes O(n log n) time with n edges found by the
|
||||
* algorithm.
|
||||
*/
|
||||
|
||||
/* make a singleton of the two end points */
|
||||
cp_ring_init(&e->node_chain);
|
||||
cp_ring_init(&o->node_chain);
|
||||
|
||||
/* insert into c->end */
|
||||
end_insert(c, e);
|
||||
end_insert(c, o);
|
||||
}
|
||||
|
||||
static void chain_merge(
|
||||
ctxt_t *c,
|
||||
event_t *e1,
|
||||
event_t *e2)
|
||||
{
|
||||
assert(e1->p == e2->p);
|
||||
e1->p->path_cnt++;
|
||||
LOG("chain_merge: %s -- %s -- %s\n",
|
||||
pt_str(e1->other->p),
|
||||
pt_str(e1->p),
|
||||
pt_str(e2->other->p));
|
||||
|
||||
cp_ring_pair(&e1->node_chain, &e2->node_chain);
|
||||
|
||||
debug_print_s(c, "join", e2, e1, NULL);
|
||||
}
|
||||
|
||||
static cp_angle_t ev_atan2(
|
||||
event_t *e)
|
||||
{
|
||||
/* We swap x and y in atan2 so that the touching end between -pi and +pi is
|
||||
* in the vertical, not horizontal. This will produce more start/ends,
|
||||
* heuristically, compared to bends, which seems good for the triangulation
|
||||
* algorithm. */
|
||||
cp_angle_t a = atan2(
|
||||
e->p->v.coord.x - e->other->p->v.coord.x,
|
||||
e->p->v.coord.y - e->other->p->v.coord.y);
|
||||
|
||||
/* identify -pi with +pi so that the angles are ordered equally.
|
||||
* Map -PI and -PI to -PI (not +PI), because in vertical lines, the
|
||||
* lower node compares smaller than the upper one, and so vertical+to_the_right
|
||||
* is not a start, but a bend, which is more brittle in triangulation. Try to
|
||||
* avoid those kinds of edges in conflicting situations.
|
||||
*/
|
||||
if (cp_eq(a, +CP_PI) || cp_eq(a, -CP_PI)) {
|
||||
a = -CP_PI;
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
static int cmp_by_atan2(
|
||||
event_t * const *a,
|
||||
event_t * const *b,
|
||||
void *u __unused)
|
||||
{
|
||||
return cp_cmp(ev_atan2(*a), ev_atan2(*b));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle same point vertices */
|
||||
static void chain_flush_vertex(
|
||||
ctxt_t *c)
|
||||
{
|
||||
LOG("BEGIN: flush_vertex: %"_Pz"u points\n", c->vert.size);
|
||||
assert(c->vert.size > 0);
|
||||
assert(((c->vert.size & 1) == 0) && "Odd number of edges meet in one point");
|
||||
|
||||
/* sort by atan2() if we have more than 2 vertices */
|
||||
if (c->vert.size > 2) {
|
||||
cp_v_qsort(&c->vert, 0, ~(size_t)0, cmp_by_atan2, NULL);
|
||||
}
|
||||
|
||||
/* remove adjacent equal angles (both of the entries) */
|
||||
size_t o = 0;
|
||||
for (cp_v_each(i, &c->vert)) {
|
||||
event_t *e = cp_v_nth(&c->vert, i);
|
||||
/* equal to predecessor? => skip */
|
||||
if ((i > 0) &&
|
||||
(e->other->p == cp_v_nth(&c->vert, i-1)->other->p))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
/* equal to successor? => skip */
|
||||
if ((i < (c->vert.size - 1)) &&
|
||||
(e->other->p == cp_v_nth(&c->vert, i+1)->other->p))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
/* not equal: keep */
|
||||
cp_v_nth(&c->vert, o) = e;
|
||||
o++;
|
||||
}
|
||||
c->vert.size = o;
|
||||
|
||||
/* join remaining edges in pairs */
|
||||
assert(((c->vert.size & 1) == 0) && "Odd number of edges meet in one point");
|
||||
for (size_t i = 0; i < c->vert.size; i += 2) {
|
||||
event_t *e1 = cp_v_nth(&c->vert, i);
|
||||
event_t *e2 = cp_v_nth(&c->vert, i+1);
|
||||
chain_merge(c, e1, e2);
|
||||
}
|
||||
LOG("END: flush_vertex\n");
|
||||
|
||||
/* sweep */
|
||||
cp_v_clear(&c->vert, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine longer chains from c->end structure
|
||||
*/
|
||||
static void chain_combine(
|
||||
ctxt_t *c)
|
||||
{
|
||||
LOG("BEGIN: chain_combine\n");
|
||||
/* init */
|
||||
cp_v_clear(&c->vert, 8); /* FIXME: temporary: should be in pool */
|
||||
|
||||
/* iterate c->end for same points */
|
||||
for (cp_dict_each(_e, c->end)) {
|
||||
event_t *e = CP_BOX_OF(_e, event_t, node_end);
|
||||
if ((c->vert.size > 0) && (cp_v_last(&c->vert)->p != e->p)) {
|
||||
chain_flush_vertex(c);
|
||||
}
|
||||
cp_v_push(&c->vert, e);
|
||||
}
|
||||
if (c->vert.size > 0) {
|
||||
chain_flush_vertex(c);
|
||||
}
|
||||
LOG("END: chain_combine\n");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -886,20 +1036,14 @@ static void poly_add(
|
|||
static void path_add_point(
|
||||
cp_csg2_poly_t *r,
|
||||
cp_csg2_path_t *p,
|
||||
event_t *e)
|
||||
point_t *q)
|
||||
{
|
||||
assert(!cp_ring_is_end(&e->node_chain) && "Polygon chain is too short or misformed");
|
||||
|
||||
/* mark event used in polygon */
|
||||
assert(!e->used);
|
||||
e->used = true;
|
||||
|
||||
/* possibly allocate a point */
|
||||
size_t idx = e->p->idx;
|
||||
size_t idx = q->idx;
|
||||
if (idx == CP_SIZE_MAX) {
|
||||
cp_vec2_loc_t *v = cp_v_push0(&r->point);
|
||||
e->p->idx = idx = cp_v_idx(&r->point, v);
|
||||
*v = e->p->v;
|
||||
q->idx = idx = cp_v_idx(&r->point, v);
|
||||
*v = q->v;
|
||||
}
|
||||
assert(idx < r->point.size);
|
||||
|
||||
|
|
@ -915,48 +1059,31 @@ static bool path_add_point3(
|
|||
event_t *cur,
|
||||
event_t *next)
|
||||
{
|
||||
LOG("point3: %p: (%s) -- %s -- (%s)\n",
|
||||
cur, pt_str(prev->p), pt_str(cur->p), pt_str(next->p));
|
||||
/* mark event used in polygon */
|
||||
assert(!cur->used);
|
||||
cur->used = true;
|
||||
|
||||
if (c->all_points ||
|
||||
(cur->p->path_cnt > 1) ||
|
||||
!cp_vec2_in_line(&prev->p->v.coord, &cur->p->v.coord, &next->p->v.coord))
|
||||
{
|
||||
assert(!cp_vec2_eq(&prev->p->v.coord, &cur->p->v.coord));
|
||||
assert(!cp_vec2_eq(&next->p->v.coord, &cur->p->v.coord));
|
||||
path_add_point(r, p, cur);
|
||||
path_add_point(r, p, cur->p);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cut a path like a-b-a-c to just a-c.
|
||||
* This may happen at small scales.
|
||||
*/
|
||||
static bool chain_cut_backforth(
|
||||
event_t *a,
|
||||
event_t **_b)
|
||||
static event_t *chain_other(event_t *e)
|
||||
{
|
||||
event_t *b = *_b;
|
||||
if (cp_ring_is_pair(&a->node_chain, &b->node_chain)) {
|
||||
return false;
|
||||
}
|
||||
event_t *x = CP_BOX0_OF(cp_ring_next(&a->node_chain, &b->node_chain), event_t, node_chain);
|
||||
if (a->p != x->p) {
|
||||
return false;
|
||||
}
|
||||
assert(!cp_ring_is_end(&b->node_chain));
|
||||
assert(!cp_ring_is_end(&x->node_chain));
|
||||
assert(b->p->path_cnt > 0);
|
||||
b->p->path_cnt--;
|
||||
assert(x->p->path_cnt > 0);
|
||||
x->p->path_cnt--;
|
||||
|
||||
*_b = CP_BOX0_OF(cp_ring_next(&b->node_chain, &x->node_chain), event_t, node_chain);
|
||||
|
||||
cp_ring_remove(&b->node_chain);
|
||||
cp_ring_remove(&x->node_chain);
|
||||
|
||||
return true;
|
||||
assert(cp_ring_is_moiety(&e->node_chain));
|
||||
event_t *o = CP_BOX_OF(cp_ring_step(&e->node_chain, 0), event_t, node_chain);
|
||||
assert(e->p == o->p);
|
||||
return o;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -964,49 +1091,47 @@ static bool chain_cut_backforth(
|
|||
static void path_make(
|
||||
ctxt_t *c,
|
||||
cp_csg2_poly_t *r,
|
||||
cp_csg2_path_t *p,
|
||||
event_t *e0)
|
||||
{
|
||||
assert(p->point_idx.size == 0);
|
||||
event_t *ex = CP_BOX_OF(cp_ring_step(&e0->node_chain, 0), event_t, node_chain);
|
||||
event_t *e1 = CP_BOX_OF(cp_ring_step(&e0->node_chain, 1), event_t, node_chain);
|
||||
|
||||
/* make it so that e1 equals e0->other, and ex is the other end */
|
||||
assert((e1->p == e0->other->p) || (ex->p == e0->other->p));
|
||||
if (ex->p == e0->other->p) {
|
||||
/* for some reason, none of my tests triggers this, but I cannot see
|
||||
* why it couldn't happen */
|
||||
CP_SWAP(&e1, &ex);
|
||||
/* start at unused left points */
|
||||
if (!e0->left || e0->used || chain_other(e0)->used) {
|
||||
return;
|
||||
}
|
||||
assert(e1->p == e0->other->p);
|
||||
|
||||
/* Four cases that collapse to two (no need to check whether e1 or ex is above):
|
||||
* If e0-e1 is below e0-ex, and e0->in.below==0, then move along e0->e1.
|
||||
* If e0-e1 is above e0-ex, and e0->in.below==1, then move along e1->e0.
|
||||
* If e0-e1 is below e0-ex, and e0->in.below==1, then move along e1->e0.
|
||||
* If e0-e1 is above e0-ex, and e0->in.below==0, then move along e0->e1.
|
||||
*/
|
||||
if (e0->in.below) {
|
||||
event_t *e1 = e0->other;
|
||||
assert(!e1->left);
|
||||
/* e0 is a left edge, i.e., we have an orientation like this: e0--e1 */
|
||||
|
||||
/* Make it so that in e0--e1, 'inside' is below. */
|
||||
if (!e1->in.below) {
|
||||
CP_SWAP(&e0, &e1);
|
||||
}
|
||||
|
||||
/* eliminate back-and-forth points, which can happen at small dimensions */
|
||||
while(chain_cut_backforth(e0, &e1)) {}
|
||||
while(chain_cut_backforth(e1, &e0)) {}
|
||||
|
||||
/* handle triples in order to add points that are not collinear */
|
||||
/* Keep chain_other(ex)->other == ey by moving to other edge at e0->p. */
|
||||
e0 = chain_other(e0);
|
||||
event_t *ea = e0;
|
||||
event_t *eb = e1;
|
||||
for (cp_ring_each(_ec, &e0->node_chain, &e1->node_chain)) {
|
||||
event_t *ec = CP_BOX_OF(_ec, event_t, node_chain);
|
||||
while (chain_cut_backforth(eb, &ec)) {
|
||||
_ec = &ec->node_chain;
|
||||
}
|
||||
event_t *ec = chain_other(e1)->other;
|
||||
assert(chain_other(ea)->other == eb);
|
||||
assert(chain_other(eb)->other == ec);
|
||||
if (ea == ec) {
|
||||
/* 2 nodes only. There cannot be longer chains that collapse
|
||||
* into a line, because these are filtered out by chain_combine(). */
|
||||
return;
|
||||
}
|
||||
|
||||
/* make a new path */
|
||||
cp_csg2_path_t *p = cp_v_push0(&r->path);
|
||||
|
||||
/* add points, removing collinear ones (if requested) */
|
||||
do {
|
||||
if (path_add_point3(c, r, p, ea, eb, ec)) {
|
||||
ea = eb;
|
||||
}
|
||||
eb = ec;
|
||||
}
|
||||
ec = chain_other(eb)->other;
|
||||
} while (ec != e0);
|
||||
LOG("iter ends\n");
|
||||
if (path_add_point3(c, r, p, ea, eb, e0)) {
|
||||
ea = eb;
|
||||
}
|
||||
|
|
@ -1024,126 +1149,19 @@ static void poly_make(
|
|||
{
|
||||
CP_COPY_N_ZERO(r, obj, t->obj);
|
||||
|
||||
assert((c->end == NULL) && "Some poly chains are still open");
|
||||
|
||||
for (cp_list_each(_e, &c->poly)) {
|
||||
event_t *e = CP_BOX_OF(_e, event_t, node_poly);
|
||||
if (!e->used && !cp_ring_is_singleton(&e->node_chain)) {
|
||||
cp_csg2_path_t *p = cp_v_push0(&r->path);
|
||||
path_make(c, r, p, e);
|
||||
/* iterate all points again */
|
||||
for (cp_dict_each(_e, c->end)) {
|
||||
event_t *e = CP_BOX_OF(_e, event_t, node_end);
|
||||
/* only start a poly at left nodes to get the orientation right (e->in.below). */
|
||||
/* only start at unused points */
|
||||
if (e->left && !e->used) {
|
||||
LOG("BEGIN: poly: %s\n", pt_str(e->p));
|
||||
path_make(c, r, e);
|
||||
LOG("END: poly\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add an edge to the output edge. Only right events are added.
|
||||
*/
|
||||
static void chain_add(
|
||||
ctxt_t *c,
|
||||
event_t *e)
|
||||
{
|
||||
LOG("out: %s (%p)\n", ev_str(e), e);
|
||||
|
||||
/* the event should left and neither point should be s or q */
|
||||
assert(!e->left);
|
||||
assert(pt_cmp(e->p, e->other->p) >= 0);
|
||||
assert(!cp_dict_is_member(&e->node_s));
|
||||
assert(!cp_dict_is_member(&e->node_q));
|
||||
assert(!cp_dict_is_member(&e->other->node_s));
|
||||
assert(!cp_dict_is_member(&e->other->node_q));
|
||||
|
||||
cp_ring_init(&e->node_chain);
|
||||
cp_ring_init(&e->other->node_chain);
|
||||
|
||||
/*
|
||||
* This algorithm combines output edges into a polygon ring. We
|
||||
* know that the events come in from left (bottom) to right (top),
|
||||
* i.e., we have a definitive direction. Only right points are
|
||||
* added.
|
||||
*
|
||||
* Edges are inserted by their next connection point that will
|
||||
* come in into the c->end using node_end. Partial polygon chains
|
||||
* consisting of more than one point will have both ends in that
|
||||
* set.
|
||||
*
|
||||
* The first edge of a new polygon is added by its left point,
|
||||
* because we know that the next connection will be to that
|
||||
* point. Once an edge is connected, its right point will be
|
||||
* inserted because its left point is already connected, so it cannot
|
||||
* be connected again.
|
||||
*
|
||||
* A new edge first searches c->end by its left point to find a place to
|
||||
* attach. If found, that point is extracted from c->end, connected to
|
||||
* the new edge using (using node_chain), and the new edge is inserted
|
||||
* by its right point, waiting for another edge to connect.
|
||||
*
|
||||
* If another point with the same coordinates is found when trying
|
||||
* to insert an edge, that node is extracted from c->end, the two
|
||||
* ends are connected and the new edge is not inserted because
|
||||
* both ends are connected. This may or may not close a polygon
|
||||
* complete.
|
||||
*
|
||||
* To gather polygons, once an edge connects two ends, it is inserted
|
||||
* into the list c->poly using the node node_poly. Polygons may have
|
||||
* multiple nodes in this list, but an O(n) search is necessary to
|
||||
* output them anyway, so this does not hurt.
|
||||
*
|
||||
* For connecting nodes, the ring data structure is used so that
|
||||
* the order by which chains are linked does not matter -- we
|
||||
* might otherwise end up trying to connect chains of opposing
|
||||
* direction. Rings handle this, plus our implementation supports
|
||||
* 'end' nodes, which our list implementation does not.
|
||||
*
|
||||
* In total, this takes no extra space except for c->end and c->poly,
|
||||
* and takes O(n log n) time with n edges found by the algorithm.
|
||||
*/
|
||||
|
||||
/* Find the left point in the end array. Note: we search by
|
||||
* 'e->other->p', while we will insert by 'e->p'. */
|
||||
assert(e->other->left);
|
||||
event_t *o1 = chain_insert_or_extract(c, e->other);
|
||||
event_t *o2 = chain_insert_or_extract(c, e);
|
||||
|
||||
switch ((o1 != NULL) | ((o2 != NULL) << 1)) {
|
||||
case 0: /* none found: new chain */
|
||||
/* connect left and right point to make initial pair */
|
||||
e->p->path_cnt++;
|
||||
e->other->p->path_cnt++;
|
||||
chain_join(e, e->other);
|
||||
assert(cp_ring_is_pair(&e->node_chain, &e->other->node_chain));
|
||||
break;
|
||||
|
||||
case 3: /* both found: closed */
|
||||
/* close chain */
|
||||
chain_join(o1, o2);
|
||||
/* On small scales, points may collapse in unfortunate ways, creating
|
||||
* degenerate polygons. This filters out a-b* rings. */
|
||||
if (!cp_ring_is_pair(&o1->node_chain, &o2->node_chain)) {
|
||||
assert(!cp_ring_is_end(&o1->node_chain));
|
||||
assert(!cp_ring_is_end(&o2->node_chain));
|
||||
/* put in poly list */
|
||||
poly_add(c, o2);
|
||||
assert(o1 != o2->other);
|
||||
}
|
||||
break;
|
||||
|
||||
case 1: /* o1 found, o2 not found: connect */
|
||||
e->p->path_cnt++;
|
||||
chain_join(o1, e);
|
||||
assert(!cp_ring_is_end(&o1->node_chain));
|
||||
assert( cp_ring_is_end(&e->node_chain));
|
||||
break;
|
||||
|
||||
case 2: /* o2 found, o1 not found: connect */
|
||||
e->other->p->path_cnt++;
|
||||
chain_join(o2, e->other);
|
||||
assert(!cp_ring_is_end(&o2->node_chain));
|
||||
assert( cp_ring_is_end(&e->other->node_chain));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void intersection_add_ev(
|
||||
event_t **sev,
|
||||
size_t *sev_cnt,
|
||||
|
|
@ -1975,6 +1993,7 @@ static void cp_csg2_op_poly(
|
|||
}
|
||||
}
|
||||
|
||||
chain_combine(&c);
|
||||
poly_make(o, &c, r->data[0]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -742,9 +742,6 @@ static bool csg2_add_layer_stack(
|
|||
csg2_add_layer_sphere(r->opt, z, &l->root->add, cp_csg3_cast(cp_csg3_sphere_t, d));
|
||||
break;
|
||||
|
||||
case CP_CSG3_CYL:
|
||||
CP_NYI("cylinder");
|
||||
|
||||
case CP_CSG3_POLY:
|
||||
csg2_add_layer_poly(r->opt, pool, z, &l->root->add,
|
||||
cp_csg3_cast(cp_csg3_poly_t, d));
|
||||
|
|
|
|||
|
|
@ -111,7 +111,6 @@ static cp_csg2_t *csg2_tree_from_csg3(
|
|||
{
|
||||
switch (d->type) {
|
||||
case CP_CSG3_SPHERE:
|
||||
case CP_CSG3_CYL:
|
||||
case CP_CSG3_POLY:
|
||||
case CP_CSG2_POLY:
|
||||
return csg2_tree_from_csg3_obj(s, d);
|
||||
|
|
|
|||
22
src/csg3.c
22
src/csg3.c
|
|
@ -840,7 +840,7 @@ static bool faces_n_edges_from_tower(
|
|||
unsigned tri_side,
|
||||
bool may_need_tri)
|
||||
{
|
||||
/* FIXME:
|
||||
/*
|
||||
* To cope with non-convex bottom and top:
|
||||
*
|
||||
* * Check here whether top/bottom will be non-convex.
|
||||
|
|
@ -873,7 +873,7 @@ static bool faces_n_edges_from_tower(
|
|||
}
|
||||
|
||||
/* check whether rev was passed correctly */
|
||||
cp_v_size3_t tri = {0};
|
||||
cp_v_size3_t tri = {0}; /* FIXME: temporary should be in pool */
|
||||
if (need_tri) {
|
||||
cp_vec2_arr_ref_t a2;
|
||||
cp_vec2_arr_ref_from_a_vec3_loc_xy(&a2, &o->point);
|
||||
|
|
@ -913,6 +913,9 @@ static bool faces_n_edges_from_tower(
|
|||
face_from_tri_or_poly(&k, o, &tri, loc, fn, rev, true);
|
||||
}
|
||||
|
||||
/* sweep */
|
||||
cp_v_fini(&tri);
|
||||
|
||||
/* sides */
|
||||
cp_csg3_face_t *f;
|
||||
for (cp_size_each(i, fnz, 1, !has_top)) {
|
||||
|
|
@ -1106,7 +1109,7 @@ static bool csg3_make_polyhedron_face(
|
|||
|
||||
if (need_tri != 0) {
|
||||
/* construct from triangles */
|
||||
cp_v_size3_t tri = {0};
|
||||
cp_v_size3_t tri = {0}; /* FIXME: temporary should be in pool */
|
||||
cp_vec2_arr_ref_t a2;
|
||||
cp_vec2_arr_ref_from_a_vec3_loc_ref(&a2, &s->points, &sf->points, (need_tri == 2));
|
||||
if (!cp_csg2_tri_vec2_arr_ref(&tri, c->tmp, c->err, s->loc, &a2, sf->points.size)) {
|
||||
|
|
@ -1138,6 +1141,9 @@ static bool csg3_make_polyhedron_face(
|
|||
}
|
||||
face_basics(cf, rev ^ rev2, sf->loc);
|
||||
}
|
||||
|
||||
/* sweep */
|
||||
cp_v_fini(&tri);
|
||||
}
|
||||
else {
|
||||
/* construct from convex face */
|
||||
|
|
@ -1182,7 +1188,6 @@ static bool csg3_from_polyhedron(
|
|||
s->faces.size);
|
||||
}
|
||||
|
||||
/* FIXME: possibly concave faces */
|
||||
cp_csg3_poly_t *o = cp_csg3_new_obj(*o, s->loc, m->gc);
|
||||
cp_v_push(r, cp_obj(o));
|
||||
|
||||
|
|
@ -1670,7 +1675,7 @@ static bool csg3_from_linext(
|
|||
/* construct a separate tree for the children */
|
||||
ctxt_t c2 = *c;
|
||||
c2.context = IN2D;
|
||||
cp_v_obj_p_t rc = {0};
|
||||
cp_v_obj_p_t rc = {0}; /* FIXME: temporary should be in pool */
|
||||
|
||||
/* start with fresh matrix in 2D space */
|
||||
mat_ctxt_t mn = *mo;
|
||||
|
|
@ -1681,7 +1686,10 @@ static bool csg3_from_linext(
|
|||
|
||||
/* get polygon */
|
||||
cp_csg2_poly_t *p = cp_csg2_flatten(c->opt, c->tmp, &rc);
|
||||
|
||||
/* sweep */
|
||||
cp_pool_clear(c->tmp);
|
||||
cp_v_fini(&rc);
|
||||
|
||||
/* empty? */
|
||||
if ((p == NULL) || (p->path.size == 0)) {
|
||||
|
|
@ -2038,10 +2046,6 @@ static void get_bb_csg3(
|
|||
get_bb_sphere(bb, cp_csg3_cast(cp_csg3_sphere_t, r));
|
||||
return;
|
||||
|
||||
case CP_CSG3_CYL:
|
||||
/* return get_bb_cyl(bb, &r->cyl); FIXME: continue */
|
||||
return;
|
||||
|
||||
case CP_CSG3_POLY:
|
||||
get_bb_poly(bb, cp_csg3_cast(cp_csg3_poly_t, r));
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ static void show(cp_ring_t *base, cp_ring_t *a, cp_ring_t *b)
|
|||
fprintf(stderr, "%"_Pz"u--", CP_PTRDIFF(b, base));
|
||||
for (cp_ring_each(n,a,b)) {
|
||||
fprintf(stderr, "%"_Pz"u--", CP_PTRDIFF(n, base));
|
||||
if (cp_ring_is_mirr(n)) {
|
||||
if (cp_ring_is_end(n)) {
|
||||
fprintf(stderr, "|");
|
||||
}
|
||||
}
|
||||
|
|
@ -214,11 +214,11 @@ extern void cp_ring_test(void)
|
|||
TEST_VOID(cp_ring_join(&n[2], &n[4]));
|
||||
show(&n[0], &n[0], &n[1]);
|
||||
TEST_ORDER5_MIRR(&n[0], &n[1], &n[2], &n[4], &n[3]);
|
||||
TEST_EQ(cp_ring_is_mirr(&n[0]), true);
|
||||
TEST_EQ(cp_ring_is_mirr(&n[1]), false);
|
||||
TEST_EQ(cp_ring_is_mirr(&n[2]), false);
|
||||
TEST_EQ(cp_ring_is_mirr(&n[4]), false);
|
||||
TEST_EQ(cp_ring_is_mirr(&n[3]), true);
|
||||
TEST_EQ(cp_ring_is_end(&n[0]), true);
|
||||
TEST_EQ(cp_ring_is_end(&n[1]), false);
|
||||
TEST_EQ(cp_ring_is_end(&n[2]), false);
|
||||
TEST_EQ(cp_ring_is_end(&n[4]), false);
|
||||
TEST_EQ(cp_ring_is_end(&n[3]), true);
|
||||
|
||||
TEST_VOID(cp_ring_cut(&n[4], &n[3]));
|
||||
show(&n[0], &n[0], &n[1]);
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ extern void cp_ring_rewire(
|
|||
* u-v
|
||||
*/
|
||||
|
||||
if (cp_ring_is_mirr(b)) {
|
||||
if (cp_ring_is_end(b)) {
|
||||
CP_SWAP(&a, &b);
|
||||
CP_SWAP(&u, &v);
|
||||
}
|
||||
|
|
|
|||
5
test.mk
5
test.mk
|
|
@ -166,11 +166,10 @@ TEST_STL.scad := \
|
|||
scad-test/test30a.scad \
|
||||
scad-test/test13c.scad \
|
||||
scad-test/test13c2.scad \
|
||||
scad-test/test13b.scad
|
||||
scad-test/test13b.scad \
|
||||
scad-test/chain1.scad
|
||||
|
||||
FAIL_STL.scad := \
|
||||
scad-test/chain1.scad \
|
||||
|
||||
FAIL_JS.scad := \
|
||||
scad-test/chain1.scad \
|
||||
scad-test/linext5.scad
|
||||
|
|
|
|||
Loading…
Reference in a new issue