star-cpr

Clip 2D meshes with 2D polygons
git clone git://git.meso-star.fr/star-cpr.git
Log | Files | Refs | README | LICENSE

commit 818e40e24d75578d1aeb2153aa5e804bb9018836
parent 8fcfd5fddd719b84f952f749a535a76f7c0f670e
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Tue, 23 Aug 2016 14:44:07 +0200

Begin the implementation of the cpr_mesh_clip function

Setup the Weiler & Atherton clipping algorithm. Build the clip &
candidate polygon node lists and link them at edge intersections.

Diffstat:
Msrc/cpr_mesh.c | 492+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 485 insertions(+), 7 deletions(-)

diff --git a/src/cpr_mesh.c b/src/cpr_mesh.c @@ -15,11 +15,52 @@ #include "cpr.h" +#include <rsys/double2.h> #include <rsys/dynamic_array_double.h> #include <rsys/dynamic_array_size_t.h> +#include <rsys/hash_table.h> #include <rsys/mem_allocator.h> #include <rsys/ref_count.h> +struct edge { + size_t v0; + size_t v1; +}; + +static INLINE char +edge_eq(const struct edge* a, const struct edge* b) +{ + return a->v0 == b->v0 && a->v1 == b->v1; +} + +#define HTABLE_NAME edge +#define HTABLE_DATA size_t /* Index of the firs node */ +#define HTABLE_KEY struct edge +#define HTABLE_KEY_FUNCTOR_EQ edge_eq +#include <rsys/hash_table.h> + +struct node { + size_t next; + size_t prev; + size_t link; + size_t id; + double t; +}; + +#define DARRAY_NAME node +#define DARRAY_DATA struct node +#include <rsys/dynamic_array.h> + +struct polygon { + struct darray_double coords; +}; + +struct hit { + double pos[2]; + double t0; /* Parametric intersection on 1st edge */ + double t1; /* Parametric intersection on 2nd edge */ +}; + struct cpr_mesh { struct darray_double coords; struct darray_size_t indices; @@ -31,6 +72,393 @@ struct cpr_mesh { /******************************************************************************* * Helper function ******************************************************************************/ +static INLINE void +polygon_init(struct mem_allocator* allocator, struct polygon* poly) +{ + ASSERT(allocator && poly); + darray_double_init(allocator, &poly->coords); +} + +static INLINE void +polygon_release(struct polygon* poly) +{ + ASSERT(poly); + darray_double_release(&poly->coords); +} + +static res_T +polygon_setup(struct polygon* poly, const struct cpr_polygon* desc) +{ + size_t ivert; + res_T res = RES_OK; + ASSERT(poly && desc); + + if(!desc->get_position && !desc->nvertices) { + res = RES_BAD_ARG; + goto error; + } + + res = darray_double_resize(&poly->coords, desc->nvertices * 2/*#coords*/); + if(res != RES_OK) goto error; + + FOR_EACH(ivert, 0, desc->nvertices) { + double* pos = darray_double_data_get(&poly->coords) + ivert*2; + desc->get_position(ivert, pos, desc->context); + } + +exit: + return res; +error: + darray_double_clear(&poly->coords); + goto exit; +} + +static res_T +polygon_setup_nodes(struct polygon* poly, struct darray_node* nodes) +{ + size_t nverts; + size_t ivert; + res_T res = RES_OK; + ASSERT(poly && nodes); + + darray_node_clear(nodes); + nverts = darray_double_size_get(&poly->coords)/2/*#coords*/; + + res = darray_node_resize(nodes, nverts); + if(res != RES_OK) goto error; + + FOR_EACH(ivert, 0, nverts) { + struct node* node = darray_node_data_get(nodes) + ivert; + node->next = (ivert == nverts - 1) ? 0 : ivert + 1; + node->prev = (ivert == 0) ? nverts - 1 : ivert - 1; + node->link = SIZE_MAX; + node->id = ivert; + node->t = -1.0; + } + +exit: + return res; +error: + darray_node_clear(nodes); + goto exit; +} + +static res_T +mesh_setup_edge_nodes + (struct cpr_mesh* mesh, + struct darray_node* nodes, + struct htable_edge* edges) +{ + size_t itri, ntris; + res_T res = RES_OK; + ASSERT(mesh && nodes); + + darray_node_clear(nodes); + htable_edge_clear(edges); + + CPR(mesh_get_triangles_count(mesh, &ntris)); + + FOR_EACH(itri, 0, ntris) { + struct edge edge; + size_t iedge; + size_t ids[3]; + + CPR(mesh_get_indices(mesh, itri, ids)); + FOR_EACH(iedge, 0, 3) { + struct node* node; + size_t inode; + + edge.v0 = ids[iedge]; + edge.v1 = ids[(iedge + 1)%3]; + if(edge.v0 > edge.v1) SWAP(size_t, edge.v0, edge.v1); + + if(htable_edge_find(edges, &edge)) continue; + + inode = darray_node_size_get(nodes); + + res = htable_edge_set(edges, &edge, &inode); + if(res != RES_OK) goto error; + res = darray_node_resize(nodes, inode + 2); + if(res != RES_OK) goto error; + + node = darray_node_data_get(nodes) + inode; + node[0].next = inode + 1; + node[0].prev = SIZE_MAX; + node[0].link = SIZE_MAX; + node[0].id = edge.v0; + node[0].t = -1.0; + + node[1].next = SIZE_MAX; + node[1].prev = inode; + node[1].link = SIZE_MAX; + node[1].id = edge.v1; + node[1].t = -1.0; + } + } + +exit: + return res; +error: + darray_node_clear(nodes); + goto exit; +} + +static int +intersect_edges + (double edge0[2][2], + double edge1[2][2], + struct hit* hit) +{ + double org0[2], org1[2]; + double dir0[2], dir1[2]; + double tmp[2]; + double a, b; + int i; + + /* Express the edges as org<0|1> + t<0|1>*dir<0|1> */ + d2_set(org0, edge0[0]); + d2_set(org1, edge1[0]); + d2_sub(dir0, edge0[1], edge0[0]); + d2_sub(dir1, edge1[1], edge1[0]); + + /* Solve org0 + t0*dir0 = org1 + t1*dir1 + * (org0 + t0*dir0) x dir1 = (org1 + t1*dir1) x dir1 + * org0 x dir1 + t0*(dir0 x dir1) = org1 x dir1 + * t0 = (org1 - org0) x dir1 / (dir0 x dir1) */ + d2_sub(tmp, org1, org0); + b = d2_cross(dir0, dir1); + if(b == 0) return 0; /* Edges are colinear => i.e. no intersection */ + a = d2_cross(tmp, dir1); + hit->t0 = a / b; + + /* Compute the hit position */ + d2_muld(hit->pos, dir0, hit->t0); + d2_add(hit->pos, org0, hit->pos); + + /* Compute the paremtric hit coordinate on the edge1 */ + i = dir1[0] != 0 ? 0 : 1; + hit->t1 = (hit->pos[i] - org1[i]) / dir1[i]; + +#ifndef NDEBUG + d2_muld(tmp, dir1, hit->t1); + d2_add(tmp, org1, tmp); + ASSERT(d2_eq_eps(hit->pos, tmp, 1.e-6)); +#endif + + /* Ensure that the hit lie in the edge boundaries */ + return hit->t0 >= 0.0 && hit->t0 <= 1.0 && hit->t1 >= 0.0 && hit->t1 <= 1.0; +} + +static res_T +compute_hit_nodes + (struct darray_node* mesh_nodes, + struct darray_double* mesh_coords, + struct darray_node* poly_nodes, + struct darray_double* poly_coords) +{ + size_t nmesh_edges, npoly_edges; + size_t imesh_edge, ipoly_edge; + res_T res = RES_OK; + ASSERT(mesh_nodes && mesh_coords && poly_nodes && poly_coords); + + nmesh_edges = darray_node_size_get(mesh_nodes)/2; + npoly_edges = darray_node_size_get(poly_nodes); + + FOR_EACH(imesh_edge, 0, nmesh_edges) { + struct node* mesh_node = darray_node_data_get(mesh_nodes) + imesh_edge; + double mesh_edge[2][2]; + + /* Fetch the coordinates of the edge0 vertices */ + d2_set(mesh_edge[0], darray_double_data_get(mesh_coords) + mesh_node[0].id*2); + d2_set(mesh_edge[1], darray_double_data_get(mesh_coords) + mesh_node[1].id*2); + + FOR_EACH(ipoly_edge, 0, npoly_edges) { + const size_t ipoly_node0 = ipoly_edge; + const size_t ipoly_node1 = (ipoly_edge + 1) % npoly_edges; + struct node* poly_node0; + struct node* poly_node1; + struct node* node; + struct node* mesh_hit_node; + struct node* poly_hit_node; + struct hit hit; + size_t inode; + size_t imesh_hit_node, ipoly_hit_node; + size_t imesh_hit_pos, ipoly_hit_pos; + double poly_edge[2][2]; + + poly_node0 = darray_node_data_get(poly_nodes) + ipoly_node0; + poly_node1 = darray_node_data_get(poly_nodes) + ipoly_node1; + + /* Fetch the coordinates of the edge1 vertices */ + d2_set(poly_edge[0], darray_double_data_get(poly_coords) + poly_node0->id*2); + d2_set(poly_edge[1], darray_double_data_get(poly_coords) + poly_node1->id*2); + + /* Find the intersection between the 2 edges */ + if(!intersect_edges(mesh_edge, poly_edge, &hit)) + continue; + + /* Allocate the hit node in the mesh node list */ + imesh_hit_node = darray_node_size_get(mesh_nodes); + res = darray_node_resize(mesh_nodes, imesh_hit_node + 1); + if(res != RES_OK) goto error; + mesh_hit_node = darray_node_data_get(mesh_nodes) + imesh_hit_node; + + /* Allocate the hit node in the polygon node list */ + ipoly_hit_node = darray_node_size_get(poly_nodes); + res = darray_node_resize(poly_nodes, ipoly_hit_node + 1); + if(res != RES_OK) goto error; + poly_hit_node = darray_node_data_get(poly_nodes) + ipoly_hit_node; + + /* Register the hit position in the mesh vertex buffer */ + imesh_hit_pos = darray_double_size_get(mesh_coords); + res = darray_double_resize(mesh_coords, imesh_hit_pos + 2); + if(res != RES_OK) goto error; + d2_set(darray_double_data_get(mesh_coords) + imesh_hit_pos, hit.pos); + imesh_hit_pos /= 2/*#coords per vertex*/; + + /* Register the hit position in the vertex buffer 1 */ + ipoly_hit_pos = darray_double_size_get(poly_coords); + res = darray_double_resize(poly_coords, ipoly_hit_pos + 2); + if(res != RES_OK) goto error; + d2_set(darray_double_data_get(poly_coords) + ipoly_hit_pos, hit.pos); + ipoly_hit_pos /= 2/*#coords per vertex*/; + + /* Insert the hit node in the mesh node list */ + mesh_node = darray_node_data_get(mesh_nodes) + imesh_edge*2; + node = darray_node_data_get(mesh_nodes) + mesh_node[0].next; + while(node->link != SIZE_MAX && node->t <= hit.t0) { + node = darray_node_data_get(mesh_nodes) + node->next; + } + inode = (size_t)(node - darray_node_data_get(mesh_nodes)); + mesh_hit_node->next = inode; + mesh_hit_node->prev = node->prev; + mesh_hit_node->link = ipoly_hit_node; /* Hit id in the poly node list */ + mesh_hit_node->id = imesh_hit_pos; + mesh_hit_node->t = hit.t0; + darray_node_data_get(mesh_nodes)[node->prev].next = imesh_hit_node; + node->prev = imesh_hit_node; + + /* Insert the hit node in the poly node list 1 */ + poly_node0 = darray_node_data_get(poly_nodes) + ipoly_node0; + node = darray_node_data_get(poly_nodes) + poly_node0->next; + while(node->link != SIZE_MAX && node->t <= hit.t1) { + node = darray_node_data_get(poly_nodes) + node->next; + } + inode = (size_t)(node - darray_node_data_get(poly_nodes)); + poly_hit_node->next = inode; + poly_hit_node->prev = node->prev; + poly_hit_node->link = imesh_hit_node; /* Hit id in the mesh node list */ + poly_hit_node->id = ipoly_hit_pos; + poly_hit_node->t = hit.t1; + darray_node_data_get(poly_nodes)[node->prev].next = ipoly_hit_node; + node->prev = ipoly_hit_node; + } + } + +exit: + return res; +error: + goto exit; +} + +static void +vertices_setup_inside_polygon_flag + (const struct darray_double* coords, + const struct darray_node* poly_nodes, + const struct darray_double* poly_coords, + char* is_inside) +{ + size_t ivert, nverts; + size_t iedge, nedges; + ASSERT(coords && poly_nodes && poly_coords && is_inside); + + nverts = darray_double_size_get(coords) / 2/*#coords per vertex*/; + nedges = darray_node_size_get(poly_nodes); + + FOR_EACH(ivert, 0, nverts) { + const double* v = darray_double_cdata_get(coords) + ivert*2; + char inside = 1; + for(iedge = 0; inside && iedge < nedges; ++iedge) { + const struct node* node0 = darray_node_cdata_get(poly_nodes) + iedge; + const struct node* node1 = darray_node_cdata_get(poly_nodes) + node0->next; + const double* c0 = darray_double_cdata_get(poly_coords) + node0->id*2; + const double* c1 = darray_double_cdata_get(poly_coords) + node1->id*2; + double e0[2], e1[2]; + d2_sub(e0, c0, v); + d2_sub(e1, c1, v); + inside = d2_cross(e1, e0) < 0; + } + is_inside[ivert] = inside; + } +} + +static res_T +mesh_setup_candidate_nodes + (const struct cpr_mesh* mesh, + const size_t itri, + struct htable_edge* edges, + const struct darray_node* mesh_nodes, + struct darray_node* poly_nodes, + struct darray_node* cand_nodes) +{ + const struct node* msh_nodes; + size_t ids[3]; + size_t iedge; + size_t icand_node_last; + res_T res = RES_OK; + + ASSERT(mesh_nodes && cand_nodes); + ASSERT(itri < darray_node_size_get(mesh_nodes) / + (3/*#edge per triangle*/*2/*#node per edge*/)); + + darray_node_clear(cand_nodes); + CPR(mesh_get_indices(mesh, itri, ids)); + + msh_nodes = darray_node_cdata_get(mesh_nodes); + + FOR_EACH(iedge, 0, 3) { + struct edge edge; + size_t imesh_node; + int flip_edge; + + edge.v0 = ids[iedge], edge.v1 = ids[(iedge+1)%3]; + if((flip_edge = edge.v0 > edge.v1)) SWAP(size_t, edge.v0, edge.v1); + + imesh_node = *htable_edge_find(edges, &edge) + (size_t)flip_edge; + + do { + struct node* cand_node; + const size_t icand_node = darray_node_size_get(cand_nodes); + + res = darray_node_resize(cand_nodes, icand_node + 1); + if(res != RES_OK) goto error; + cand_node = darray_node_data_get(cand_nodes) + icand_node; + + cand_node->prev = icand_node - 1; + cand_node->next = icand_node + 1; + cand_node->link = msh_nodes[imesh_node].link; + cand_node->id = msh_nodes[imesh_node].id; + cand_node->t = msh_nodes[imesh_node].t; + if(cand_node->link) { + darray_node_data_get(poly_nodes)[cand_node->link].link = + icand_node; + } + } while(flip_edge + ? msh_nodes[msh_nodes[imesh_node].prev].prev != SIZE_MAX + : msh_nodes[msh_nodes[imesh_node].next].next != SIZE_MAX); + + icand_node_last = darray_node_size_get(cand_nodes) - 1; + darray_node_data_get(cand_nodes)[0].prev = icand_node_last; + darray_node_data_get(cand_nodes)[icand_node_last].next = 0; + } + +exit: + return res; +error: + darray_node_clear(cand_nodes); + goto exit; +} + static void mesh_release(ref_T* ref) { @@ -130,8 +558,8 @@ cpr_mesh_setup_indexed_vertices ids = darray_size_t_data_get(&mesh->indices); FOR_EACH(i, 0, ntris) { get_indices(i, ids +i*3, data); - if(ids[i*3+0] >= nverts - || ids[i*3+1] >= nverts + if(ids[i*3+0] >= nverts + || ids[i*3+1] >= nverts || ids[i*3+2] >= nverts) { res = RES_BAD_ARG; goto error; @@ -178,7 +606,7 @@ cpr_mesh_get_indices ids[1] = darray_size_t_cdata_get(&mesh->indices)[itri*3 + 1]; ids[2] = darray_size_t_cdata_get(&mesh->indices)[itri*3 + 2]; return RES_OK; -} +} res_T cpr_mesh_get_position @@ -196,9 +624,59 @@ cpr_mesh_get_position res_T cpr_mesh_clip(struct cpr_mesh* mesh, struct cpr_polygon* polygon) { - if(!mesh || !polygon) return RES_BAD_ARG; - FATAL("Not implemented yet!\n"); - /* TODO */ - return RES_OK; + struct darray_node mesh_nodes; + struct darray_node poly_nodes; + struct darray_node cand_nodes; /* Candidate */ + struct darray_char is_inside; + struct htable_edge edges; + struct polygon poly; + size_t itri, ntris; + res_T res = RES_OK; + + darray_node_init(mesh->allocator, &mesh_nodes); + darray_node_init(mesh->allocator, &poly_nodes); + darray_node_init(mesh->allocator, &cand_nodes); + darray_char_init(mesh->allocator, &is_inside); + htable_edge_init(mesh->allocator, &edges); + polygon_init(mesh->allocator, &poly); + + if(!mesh || !polygon) { + res = RES_BAD_ARG; + goto error; + } + + res = polygon_setup(&poly, polygon); + if(res != RES_OK) goto error; + res = polygon_setup_nodes(&poly, &poly_nodes); + if(res != RES_OK) goto error; + vertices_setup_inside_polygon_flag(&mesh->coords, &poly_nodes, + &poly.coords, darray_char_data_get(&is_inside)); + + res = mesh_setup_edge_nodes(mesh, &mesh_nodes, &edges); + if(res != RES_OK) goto error; + res = compute_hit_nodes + (&mesh_nodes, &mesh->coords, &poly_nodes, &poly.coords); + if(res != RES_OK) goto error; + + CPR(mesh_get_triangles_count(mesh, &ntris)); + FOR_EACH(itri, 0, ntris) { + res = mesh_setup_candidate_nodes + (mesh, itri, &edges, &mesh_nodes, &poly_nodes, &cand_nodes); + if(res != RES_OK) goto error; + + /*TODO clip(&cand_nodes, &poly_nodes);*/ + } + +exit: + darray_node_release(&mesh_nodes); + darray_node_release(&poly_nodes); + darray_node_release(&cand_nodes); + darray_char_release(&is_inside); + htable_edge_release(&edges); + polygon_release(&poly); + return res; + +error: + goto exit; }