From 8aa06fb3d6caf7e626b281b7b880d678d7297e80 Mon Sep 17 00:00:00 2001 From: Martin Felis Date: Fri, 28 May 2021 16:45:41 +0200 Subject: [PATCH] Hull builder can now build hulls including various validations. --- src/sconvcol.h | 161 +++++++++++++++++++++++++++++------------ tests/sconvcolTests.cc | 109 ++++++++++++++++++++++++---- 2 files changed, 209 insertions(+), 61 deletions(-) diff --git a/src/sconvcol.h b/src/sconvcol.h index db7e45a..883706c 100644 --- a/src/sconvcol.h +++ b/src/sconvcol.h @@ -9,6 +9,10 @@ extern "C" { #include #include "vectorial/simd4x4f.h" +8 +inline bool sch_simd4f_equal(simd4f a, simd4f b) { + return (simd4f_get_x(simd4f_length4_squared(simd4f_sub(a, b))) == 0.f); +} typedef struct sch_edge sch_edge; typedef struct sch_vert sch_vert; @@ -35,10 +39,12 @@ struct sch_face { }; struct sch_hull { - int num_faces; sch_face* faces; sch_edge* edges; sch_vert* vertices; + int num_faces; + int num_edges; + int num_vertices; }; struct sch_plane { @@ -64,7 +70,10 @@ struct sch_face_query { enum SchHullResult { SchHullResultOK = 0, - SchHullResultConcaveVertex = -1 + SchHullResultConcaveVertex = -1, + SchHullResultWrongWinding = -2, + SchHullResultInvalidTwinEdges = -3, + SchHullResultOpenHull = -4 }; typedef enum SchHullResult SchHullResult; @@ -98,21 +107,20 @@ void sch_edge_get_dir(sch_edge* edge, simd4f* dir); void sch_hull_free_memory (sch_hull* hull); -void sch_hull_get_plane(const sch_hull* hull, int index, sch_plane* out_plane); +void sch_hull_get_plane(const sch_hull* hull, const int index, sch_plane* out_plane); + +sch_edge* sch_hull_find_edge (const sch_hull* hull, const simd4f v0, const simd4f v1); void sch_hull_get_support(const sch_hull* hull, simd4f n, simd4f* out_vert); -int sch_hull_is_vertex_concave(const sch_hull* hull, simd4f p); +int sch_hull_is_vertex_concave(const sch_hull* hull, const simd4f p); + +int sch_hull_is_closed (const sch_hull* hull); + +int sch_hull_connect_face_edges(const sch_hull* hull, int face_index); void sch_create_face(int num_vert, simd4f* vertices, sch_face* out_face); -SchHullResult sch_create_hull(int num_faces, sch_face* faces, sch_hull* out_hull); - -void sch_query_face_dir( - const sch_hull* hull_A, - const sch_hull* hull_B, - sch_face_query* out_query); - // // sconvcol Implementation // @@ -162,7 +170,12 @@ void sch_builder_reset(sch_hull_builder* builder) { void sch_builder_face_begin(sch_hull_builder* builder) { int face_index = builder->num_faces; - builder->face_vert_idx_start[face_index] = builder->num_vertices; + if (builder->num_faces == 0) { + builder->face_vert_idx_start[face_index] = 0; + } else { + builder->face_vert_idx_start[face_index] = builder->face_vert_idx_end[face_index - 1] + 1; + } + builder->face_vert_idx_end[face_index] = -1; } @@ -171,37 +184,41 @@ void sch_builder_face_end(sch_hull_builder* builder) { builder->num_faces++; } void sch_builder_allocate_memory (sch_hull_builder* builder, sch_hull* out_hull) { out_hull->faces = (sch_face*)malloc(sizeof(sch_face) * builder->num_faces); + out_hull->num_vertices = builder->num_vertices; out_hull->vertices = (sch_vert*)malloc(sizeof(sch_vert) * builder->num_vertices); - int num_edges = 1; + int num_edges = 0; for (int face_index = 0; face_index < builder->num_faces; face_index++) { int start_idx = builder->face_vert_idx_start[face_index]; int end_idx = builder->face_vert_idx_end[face_index]; int edge_count = end_idx - start_idx; - num_edges += edge_count; + num_edges += edge_count + 1; } + out_hull->num_edges = num_edges; out_hull->edges = (sch_edge*)malloc(sizeof(sch_edge) * num_edges); } void sch_hull_free_memory (sch_hull* hull) { free (hull->faces); hull->faces = NULL; + hull->num_faces = 0; free (hull->vertices); hull->vertices = NULL; + hull->num_vertices = 0; free (hull->edges); hull->edges = NULL; + hull->num_edges = 0; } SchHullResult sch_builder_create_hull(sch_hull_builder* builder, sch_hull* out_hull) { sch_builder_allocate_memory(builder, out_hull); + out_hull->num_faces = 0; int hull_edge_idx = 0; - out_hull->num_faces = 0; - for (int face_index = 0; face_index < builder->num_faces; face_index++) { sch_face* face = &out_hull->faces[face_index]; @@ -242,10 +259,18 @@ SchHullResult sch_builder_create_hull(sch_hull_builder* builder, sch_hull* out_h prev_edge = edge; } + int edge_add_result = sch_hull_connect_face_edges (out_hull, face_index); + if (edge_add_result != SchHullResultOK) { + sch_hull_free_memory(out_hull); + return edge_add_result; + } + out_hull->num_faces = face_index + 1; } - return SchHullResultOK; + assert (hull_edge_idx == out_hull->num_edges); + + return sch_hull_is_closed (out_hull); } void sch_builder_face_vertex(sch_hull_builder* builder, simd4f vertex) { @@ -277,7 +302,7 @@ void sch_builder_face_vertex(sch_hull_builder* builder, simd4f vertex) { builder->face_vert_idx[face_end_idx] = vert_index; } -void sch_hull_get_plane(const sch_hull* hull, int index, sch_plane* out_plane) { +void sch_hull_get_plane(const sch_hull* hull, const int index, sch_plane* out_plane) { assert(hull != NULL); assert(index >= 0 && index < hull->num_faces); assert(out_plane != NULL); @@ -295,7 +320,24 @@ void sch_hull_get_plane(const sch_hull* hull, int index, sch_plane* out_plane) { out_plane->n = simd4f_cross3(dir0, dir1); } -int sch_hull_is_vertex_concave(const sch_hull* hull, simd4f v) { +sch_edge* sch_hull_find_edge (const sch_hull* hull, const simd4f v0, const simd4f v1) { + for (int fi = 0; fi < hull->num_faces; fi++) { + sch_face* face = &hull->faces[fi]; + sch_edge* edge0 = face->edge; + sch_edge* edge = edge0; + do { + if (sch_simd4f_equal(edge->vert->p, v0) + && sch_simd4f_equal(edge->next->vert->p, v1)) { + return edge; + } + edge = edge->next; + } while (edge != edge0); + } + + return NULL; +} + +int sch_hull_is_vertex_concave(const sch_hull* hull, const simd4f v) { sch_plane plane; for (int i = 0; i < hull->num_faces; i++) { sch_hull_get_plane(hull, i, &plane); @@ -306,40 +348,63 @@ int sch_hull_is_vertex_concave(const sch_hull* hull, simd4f v) { return 0; } -sch_hull* sch_create_box() { - simd4f vertices_pos_z[4] = { - simd4f_create(-0.5f, -0.5f, 0.5f, 0.f), - simd4f_create(0.5f, -0.5f, 0.5f, 0.f), - simd4f_create(0.5f, 0.5f, 0.5f, 0.f), - simd4f_create(-0.5f, 0.5f, 0.5f, 0.f)}; - sch_face face_pos_z = {0}; - sch_create_face(4, vertices_pos_z, &face_pos_z); +int sch_hull_connect_face_edges(const sch_hull* hull, int new_face_index) { + sch_face* new_face = &hull->faces[new_face_index]; - simd4f vertices_neg_z[4] = { - simd4f_create(-0.5f, -0.5f, -0.5f, 0.f), - simd4f_create(0.5f, -0.5f, -0.5f, 0.f), - simd4f_create(0.5f, 0.5f, -0.5f, 0.f), - simd4f_create(-0.5f, 0.5f, -0.5f, 0.f)}; - sch_face face_neg_z = {0}; - sch_create_face(4, vertices_neg_z, &face_neg_z); + sch_edge* new_face_edge0 = new_face->edge; + sch_edge* new_face_edge = new_face_edge0; - simd4f vertices_pos_x[4] = { - simd4f_create(0.5f, -0.5f, 0.5f, 0.f), - simd4f_create(0.5f, -0.5f, -0.5f, 0.f), - simd4f_create(0.5f, 0.5f, -0.5f, 0.f), - simd4f_create(0.5f, 0.5f, 0.5f, 0.f)}; + do { + sch_vert* new_edge_v0 = new_face_edge->vert; + sch_vert* new_edge_v1 = new_face_edge->next->vert; - sch_face face_pos_x = {0}; - sch_create_face(4, vertices_pos_x, &face_pos_x); + for (int fi = 0; fi < hull->num_faces; fi++) { + if (fi == new_face_index) { + continue; + } - simd4f vertices_neg_x[4] = { - simd4f_create(-0.5f, -0.5f, 0.5f, 0.f), - simd4f_create(-0.5f, -0.5f, -0.5f, 0.f), - simd4f_create(-0.5f, 0.5f, -0.5f, 0.f), - simd4f_create(-0.5f, 0.5f, 0.5f, 0.f)}; + sch_face* face = &hull->faces[fi]; + sch_edge* face_edge0 = face->edge; + sch_edge* face_edge = face_edge0; - sch_face face_neg_x = {0}; - sch_create_face(4, vertices_neg_x, &face_neg_x); + do { + sch_vert* edge_v0 = face_edge->vert; + sch_vert* edge_v1 = face_edge->next->vert; + + if (sch_simd4f_equal(new_edge_v0->p, edge_v0->p) + && sch_simd4f_equal(new_edge_v1->p, edge_v1->p)) { + return SchHullResultWrongWinding; + } else if (sch_simd4f_equal(new_edge_v0->p, edge_v1->p) + && sch_simd4f_equal(new_edge_v1->p, edge_v0->p)) { + if (new_face_edge->twin == NULL && face_edge->twin == NULL) { + new_face_edge->twin = face_edge; + face_edge->twin = new_face_edge; + } else if (new_face_edge->twin == face_edge->twin) { + // nothing to do + } else if ((new_face_edge->twin == NULL && face_edge->twin != NULL) + || (new_face_edge->twin != NULL && face_edge->twin == NULL)) { + return SchHullResultInvalidTwinEdges; + } + } + + face_edge = face_edge->next; + } while (face_edge != face_edge0); + } + + new_face_edge = new_face_edge->next; + } while (new_face_edge != new_face_edge0); + + return SchHullResultOK; +} + +int sch_hull_is_closed (const sch_hull* hull) { + for (int ei = 0; ei < hull->num_edges; ei++) { + if (hull->edges[ei].twin == NULL) { + return SchHullResultOpenHull; + } + } + + return SchHullResultOK; } #endif /* SCONVCOL_IMPLEMENTATION */ diff --git a/tests/sconvcolTests.cc b/tests/sconvcolTests.cc index 43c30a7..4521f8a 100644 --- a/tests/sconvcolTests.cc +++ b/tests/sconvcolTests.cc @@ -3,10 +3,6 @@ #include "catch.hpp" #include "sconvcol.h" -bool simd4f_equal(simd4f a, simd4f b) { - return (simd4f_get_x(simd4f_length4_squared(simd4f_sub(a, b))) == 0.f); -} - using namespace std; TEST_CASE("Plane Distance", "[sconvcol]") { @@ -70,12 +66,13 @@ TEST_CASE("HullBuilder", "[sconvcol]") { GIVEN("An empty builder") { sch_builder_reset(&builder); + simd4f vertex0 = simd4f_create(0.f, 0.f, 0.f, 1.f); + simd4f vertex1 = simd4f_create(1.f, 0.f, 0.f, 1.f); + simd4f vertex2 = simd4f_create(0.5f, 1.f, 0.f, 1.f); + WHEN("adding two vertices ") { - simd4f vertex0 = simd4f_create(0.f, 0.f, 0.f, 1.f); sch_builder_face_begin(&builder); sch_builder_face_vertex(&builder, vertex0); - - simd4f vertex1 = simd4f_create(1.f, 0.f, 0.f, 1.f); sch_builder_face_vertex(&builder, vertex1); WHEN("ending the face") { @@ -89,7 +86,6 @@ TEST_CASE("HullBuilder", "[sconvcol]") { } WHEN("creating a triangle") { - simd4f vertex2 = simd4f_create(0.5f, 1.f, 0.f, 1.f); sch_builder_face_vertex(&builder, vertex2); sch_builder_face_end(&builder); @@ -105,7 +101,7 @@ TEST_CASE("HullBuilder", "[sconvcol]") { SchHullResult result = sch_builder_create_hull(&builder, &hull); THEN("verify triangle hull") { - REQUIRE(result == SchHullResultOK); + REQUIRE(result == SchHullResultOpenHull); REQUIRE(hull.num_faces == 1); sch_face* face = &hull.faces[0]; @@ -113,9 +109,9 @@ TEST_CASE("HullBuilder", "[sconvcol]") { sch_vert* hull_vert1 = face->edge->next->vert; sch_vert* hull_vert2 = face->edge->next->next->vert; - REQUIRE(simd4f_equal(hull_vert0->p, vertex0)); - REQUIRE(simd4f_equal(hull_vert1->p, vertex1)); - REQUIRE(simd4f_equal(hull_vert2->p, vertex2)); + REQUIRE(sch_simd4f_equal(hull_vert0->p, vertex0)); + REQUIRE(sch_simd4f_equal(hull_vert1->p, vertex1)); + REQUIRE(sch_simd4f_equal(hull_vert2->p, vertex2)); REQUIRE(face->edge->next->next->next == face->edge); } @@ -145,12 +141,99 @@ TEST_CASE("HullBuilder", "[sconvcol]") { } THEN("hull creation fails") { - SchHullResult result = sch_builder_create_hull(&builder, &hull); + sch_hull concave_hull; + SchHullResult result = sch_builder_create_hull(&builder, &concave_hull); + REQUIRE(result == SchHullResultConcaveVertex); + + sch_hull_free_memory(&concave_hull); } sch_hull_free_memory(&hull); } + + WHEN("adding adjacent clockwise winded face") { + simd4f vertex4 = simd4f_create(0.5f, -0.25f, 0, 1.f); + + sch_builder_face_begin(&builder); + sch_builder_face_vertex(&builder, vertex0); + sch_builder_face_vertex(&builder, vertex1); + sch_builder_face_vertex(&builder, vertex4); + sch_builder_face_end(&builder); + + THEN("hull creation fails") { + sch_hull hull; + SchHullResult result = sch_builder_create_hull(&builder, &hull); + + REQUIRE(result == SchHullResultWrongWinding); + + sch_hull_free_memory(&hull); + } + } + + WHEN("adding adjacent counter clockwise winded face") { + simd4f vertex4 = simd4f_create(0.5f, -0.25f, 0, 1.f); + + sch_builder_face_begin(&builder); + sch_builder_face_vertex(&builder, vertex0); + sch_builder_face_vertex(&builder, vertex4); + sch_builder_face_vertex(&builder, vertex1); + sch_builder_face_end(&builder); + + THEN("edge v0-v1 has twin edge v1-v0") { + sch_hull hull; + SchHullResult result = sch_builder_create_hull(&builder, &hull); + + REQUIRE(result == SchHullResultOpenHull); + + sch_edge* edge_v0_v1 = sch_hull_find_edge(&hull, vertex0, vertex1); + sch_edge* edge_v1_v0 = sch_hull_find_edge(&hull, vertex1, vertex0); + + REQUIRE (edge_v0_v1 != NULL); + REQUIRE (edge_v1_v0->twin == edge_v0_v1); + + sch_hull_free_memory(&hull); + } + } + } + } + + WHEN("creating a tetrahedron ") { + simd4f vertex3 = simd4f_create (0.5f, 0.f, -0.5f, 1.f); + + // +z facing triangle + sch_builder_face_begin(&builder); + sch_builder_face_vertex(&builder, vertex0); + sch_builder_face_vertex(&builder, vertex1); + sch_builder_face_vertex(&builder, vertex2); + sch_builder_face_end(&builder); + + // +x,-z facing triangle + sch_builder_face_begin(&builder); + sch_builder_face_vertex(&builder, vertex1); + sch_builder_face_vertex(&builder, vertex3); + sch_builder_face_vertex(&builder, vertex2); + sch_builder_face_end(&builder); + + // -x,-z facing triangle + sch_builder_face_begin(&builder); + sch_builder_face_vertex(&builder, vertex0); + sch_builder_face_vertex(&builder, vertex2); + sch_builder_face_vertex(&builder, vertex3); + sch_builder_face_end(&builder); + + // -y facing triangle + sch_builder_face_begin(&builder); + sch_builder_face_vertex(&builder, vertex0); + sch_builder_face_vertex(&builder, vertex3); + sch_builder_face_vertex(&builder, vertex1); + sch_builder_face_end(&builder); + + THEN("results in a closed hull") { + sch_hull hull; + SchHullResult result = sch_builder_create_hull(&builder, &hull); + REQUIRE (result == SchHullResultOK); + sch_hull_free_memory(&hull); } } }