Skip to content

Commit

Permalink
Restructured the bounding box generation (#46)
Browse files Browse the repository at this point in the history
* Restructured the code for handling the bounding box generation
Added unit tests for the point pruning functions

* Win32 compile fix
  • Loading branch information
JCash committed Nov 9, 2019
1 parent f5e9cf2 commit fc9ed1c
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 54 deletions.
100 changes: 54 additions & 46 deletions src/jc_voronoi.h
Expand Up @@ -1234,7 +1234,7 @@ static inline void jcv_rect_inflate(jcv_rect* rect, jcv_real amount)
rect->max.y += amount;
}

static int jcv_prune_duplicates(jcv_context_internal* internal, const jcv_rect* rect)
static int jcv_prune_duplicates(jcv_context_internal* internal, jcv_rect* rect)
{
int num_sites = internal->numsites;
jcv_site* sites = internal->sites;
Expand All @@ -1257,25 +1257,16 @@ static int jcv_prune_duplicates(jcv_context_internal* internal, const jcv_rect*

sites[i - offset] = sites[i];

if( rect == 0 )
{
jcv_rect_union(&r, &s->p);
}
jcv_rect_union(&r, &s->p);
}

if( rect == 0 )
{
jcv_rect_round(&r);
jcv_rect_inflate(&r, 10);
internal->rect = r;
} else {
internal->rect = *rect;
internal->numsites -= offset;
if (rect) {
*rect = r;
}

return offset;
}

static int jcv_prune_not_in_shape(jcv_context_internal* internal, const jcv_rect* rect)
static int jcv_prune_not_in_shape(jcv_context_internal* internal, jcv_rect* rect)
{
int num_sites = internal->numsites;
jcv_site* sites = internal->sites;
Expand All @@ -1297,20 +1288,17 @@ static int jcv_prune_not_in_shape(jcv_context_internal* internal, const jcv_rect

sites[i - offset] = sites[i];

if( rect == 0 )
{
jcv_rect_union(&r, &s->p);
}
jcv_rect_union(&r, &s->p);
}
internal->numsites -= offset;
if (rect) {
*rect = r;
}

return offset;
}

void jcv_diagram_generate_useralloc( int num_points, const jcv_point* points, const jcv_rect* rect, const jcv_clipper* clipper, void* userallocctx, FJCVAllocFn allocfn, FJCVFreeFn freefn, jcv_diagram* d )
static jcv_context_internal* jcv_alloc_internal(int num_points, void* userallocctx, FJCVAllocFn allocfn, FJCVFreeFn freefn)
{
if( d->internal )
jcv_diagram_free( d );

// Interesting limits from Euler's equation
// Slide 81: https://courses.cs.washington.edu/courses/csep521/01au/lectures/lecture10slides.pdf
// Page 3: https://sites.cs.ucsb.edu/~suri/cs235/Voronoi.pdf
Expand All @@ -1332,16 +1320,6 @@ void jcv_diagram_generate_useralloc( int num_points, const jcv_point* points, co
internal->alloc = allocfn;
internal->free = freefn;

internal->beachline_start = jcv_halfedge_new(internal, 0, 0);
internal->beachline_end = jcv_halfedge_new(internal, 0, 0);

internal->beachline_start->left = 0;
internal->beachline_start->right = internal->beachline_end;
internal->beachline_end->left = internal->beachline_start;
internal->beachline_end->right = 0;

internal->last_inserted = 0;

internal->sites = (jcv_site*) mem;
mem += sitessize;

Expand All @@ -1352,6 +1330,27 @@ void jcv_diagram_generate_useralloc( int num_points, const jcv_point* points, co
tmp.charp = mem;
internal->eventmem = tmp.voidpp;

return internal;
}

void jcv_diagram_generate_useralloc(int num_points, const jcv_point* points, const jcv_rect* rect, const jcv_clipper* clipper, void* userallocctx, FJCVAllocFn allocfn, FJCVFreeFn freefn, jcv_diagram* d)
{
if( d->internal )
jcv_diagram_free( d );

jcv_context_internal* internal = jcv_alloc_internal(num_points, userallocctx, allocfn, freefn);

internal->beachline_start = jcv_halfedge_new(internal, 0, 0);
internal->beachline_end = jcv_halfedge_new(internal, 0, 0);

internal->beachline_start->left = 0;
internal->beachline_start->right = internal->beachline_end;
internal->beachline_end->left = internal->beachline_start;
internal->beachline_end->right = 0;

internal->last_inserted = 0;

int max_num_events = num_points*2; // beachline can have max 2*n-5 parabolas
jcv_pq_create(internal->eventqueue, max_num_events, (void**)internal->eventmem);

internal->numsites = num_points;
Expand All @@ -1375,29 +1374,38 @@ void jcv_diagram_generate_useralloc( int num_points, const jcv_point* points, co
}
internal->clipper = *clipper;

int offset = jcv_prune_duplicates(internal, rect);
num_points -= offset;
jcv_rect tmp_rect;
tmp_rect.min.x = tmp_rect.min.y = JCV_FLT_MAX;
tmp_rect.max.x = tmp_rect.max.y = -JCV_FLT_MAX;
jcv_prune_duplicates(internal, &tmp_rect);

// Prune using the test second
if (internal->clipper.test_fn)
{
internal->clipper.min = internal->rect.min;
internal->clipper.max = internal->rect.max;
// e.g. used by the box clipper in the test_fn
internal->clipper.min = rect ? rect->min : tmp_rect.min;
internal->clipper.max = rect ? rect->max : tmp_rect.max;

jcv_prune_not_in_shape(internal, &tmp_rect);

offset = jcv_prune_not_in_shape(internal, rect);
num_points -= offset;
// The pruning might have made the bounding box smaller
if (!rect) {
// In the case of all sites being all on a horizontal or vertical line, the
// rect area will be zero, and the diagram generation will most likely fail
jcv_rect_round(&tmp_rect);
jcv_rect_inflate(&tmp_rect, 10);

internal->clipper.min = internal->rect.min;
internal->clipper.max = internal->rect.max;
internal->clipper.min = tmp_rect.min;
internal->clipper.max = tmp_rect.max;
}
}

internal->rect = rect ? *rect : tmp_rect;

d->min = internal->rect.min;
d->max = internal->rect.max;
d->numsites = internal->numsites;
d->internal = internal;
d->numsites = num_points;

internal->numsites = num_points;
internal->currentsite = 0;

internal->bottomsite = jcv_nextsite(internal);

Expand Down
15 changes: 10 additions & 5 deletions test/jc_test.h
Expand Up @@ -142,8 +142,8 @@ extern jc_test_state jc_test_global_state;

#define TEST(_TEST_) { #_TEST_, JC_TEST_CAST(jc_test_func, (_TEST_)) },

extern void jc_test_run_test_fixture(jc_test_fixture* fixture);
extern void jc_test_run_all_tests(jc_test_state* state);
extern int jc_test_run_test_fixture(jc_test_fixture* fixture);
extern int jc_test_run_all_tests(jc_test_state* state);
extern void jc_test_assert(jc_test_fixture* fixture, bool cond, const char* msg);
extern unsigned long long jc_test_get_time(void);

Expand Down Expand Up @@ -232,7 +232,7 @@ static void jc_test_report_time(unsigned long long t) // Micro seconds
JC_TEST_PRINTF("%g %s", t / 1000000.0, "s");
}

void jc_test_run_test_fixture(jc_test_fixture* fixture)
int jc_test_run_test_fixture(jc_test_fixture* fixture)
{
jc_test_global_state.current_fixture = fixture;

Expand Down Expand Up @@ -311,16 +311,19 @@ void jc_test_run_test_fixture(jc_test_fixture* fixture)
JC_TEST_PRINTF("\n");

jc_test_global_state.current_fixture = 0;

return fixture->stats.num_fail;
}

void jc_test_run_all_tests(jc_test_state* state)
int jc_test_run_all_tests(jc_test_state* state)
{
int num_fail = 0;
state->stats.totaltime = 0;
for( int i = 0; i < state->num_fixtures; ++i )
{
if( state->fixtures[i] )
{
jc_test_run_test_fixture( state->fixtures[i] );
num_fail += jc_test_run_test_fixture( state->fixtures[i] );

state->stats.num_assertions += state->fixtures[i]->stats.num_assertions;
state->stats.num_pass += state->fixtures[i]->stats.num_pass;
Expand All @@ -337,6 +340,8 @@ void jc_test_run_all_tests(jc_test_state* state)
JC_TEST_PRINTF("%d tests passed, and %d tests %sFAILED%s\n", state->stats.num_pass, state->stats.num_fail, JC_TEST_CLR_RED, JC_TEST_CLR_DEFAULT);
else
JC_TEST_PRINTF("%d tests %sPASSED%s\n", state->stats.num_pass, JC_TEST_CLR_GREEN, JC_TEST_CLR_DEFAULT);

return num_fail;
}

#if defined(_MSC_VER)
Expand Down
108 changes: 105 additions & 3 deletions test/test.c
Expand Up @@ -34,7 +34,9 @@ static void test_setup(Context* ctx)

static void test_teardown(Context* ctx)
{
jcv_diagram_free(&ctx->diagram);
if (ctx->diagram.internal) {
jcv_diagram_free(&ctx->diagram);
}
}

static void debug_points(int num, const jcv_point* points)
Expand Down Expand Up @@ -217,6 +219,105 @@ static void voronoi_test_culling(Context* ctx)
check_edges( sites[0].edges, 4, expected_edges_0, expected_neighbors_0 );
}


static jcv_context_internal* setup_test_context_internal(int num_points, jcv_point* points, void* ctx)
{
jcv_context_internal* internal = jcv_alloc_internal(num_points, ctx, jcv_alloc_fn, jcv_free_fn);
internal->numsites = num_points;
jcv_site* sites = internal->sites;

for( int i = 0; i < num_points; ++i )
{
sites[i].p = points[i];
sites[i].edges = 0;
sites[i].index = i;
}
qsort(sites, (size_t)num_points, sizeof(jcv_site), jcv_point_cmp);

return internal;
}

static void setup_clip_shape_box(jcv_context_internal* internal, jcv_rect rect)
{
jcv_clipper box_clipper;
box_clipper.test_fn = jcv_boxshape_test;
box_clipper.clip_fn = jcv_boxshape_clip;
box_clipper.fill_fn = jcv_boxshape_fillgaps;
internal->clipper = box_clipper;

internal->clipper.min = rect.min;
internal->clipper.max = rect.max;
}

static void teardown_test_context_internal(jcv_context_internal* internal)
{
jcv_free_fn(0, internal->mem);
}

static void voronoi_test_prune_duplicates(Context* ctx)
{
(void)ctx;
jcv_point duplicate = {1,2};
jcv_point points[] = { {1,2}, {2,2}, {1,2}, {3,3}};
int num_points = sizeof(points) / sizeof(points[0]);

jcv_context_internal* internal = setup_test_context_internal(num_points, points, 0);
ASSERT_EQ( 4, internal->numsites );

jcv_rect rect;
int num_removed = jcv_prune_duplicates(internal, &rect);

ASSERT_EQ( 1, num_removed );
ASSERT_EQ( 3, internal->numsites );

int count = 0;
for (int i = 0; i < internal->numsites; ++i)
{
if (internal->sites[i].p.x == duplicate.x &&
internal->sites[i].p.y == duplicate.y) {
count++;
}
}
ASSERT_EQ( 1, count );

ASSERT_EQ( 1, rect.min.x );
ASSERT_EQ( 2, rect.min.y );
ASSERT_EQ( 3, rect.max.x );
ASSERT_EQ( 3, rect.max.y );

teardown_test_context_internal(internal);
}

static void voronoi_test_prune_not_in_shape(Context* ctx)
{
(void)ctx;
jcv_point points[] = { {0,0}, {1,9}, {2,8}, {5,5}, {8,2}, {9,1}, {10,10}};
int num_points = sizeof(points) / sizeof(points[0]);

jcv_context_internal* internal = setup_test_context_internal(num_points, points, 0);
ASSERT_EQ( num_points, internal->numsites );

jcv_rect smaller_rect;
smaller_rect.min.x = 2;
smaller_rect.min.y = 2;
smaller_rect.max.x = 8;
smaller_rect.max.y = 8;
setup_clip_shape_box(internal, smaller_rect);

jcv_rect rect;
int num_removed = jcv_prune_not_in_shape(internal, &rect);

ASSERT_EQ( 4, num_removed );
ASSERT_EQ( 3, internal->numsites );

ASSERT_EQ( 2, rect.min.x );
ASSERT_EQ( 2, rect.min.y );
ASSERT_EQ( 8, rect.max.x );
ASSERT_EQ( 8, rect.max.y );

teardown_test_context_internal(internal);
}

// for debugging
// static void write_points(const char* name, int num_points, const jcv_point* points)
// {
Expand Down Expand Up @@ -438,6 +539,8 @@ static void voronoi_test_issue38_numsites_equals_one_assert(Context* ctx)
}

TEST_BEGIN(voronoi_test, voronoi_main_setup, voronoi_main_teardown, test_setup, test_teardown)
TEST(voronoi_test_prune_duplicates)
TEST(voronoi_test_prune_not_in_shape)
TEST(voronoi_test_parallel_horiz_2)
TEST(voronoi_test_parallel_vert_2)
TEST(voronoi_test_one_site)
Expand All @@ -461,6 +564,5 @@ int main(int argc, const char** argv)
(void)debug_edges;
(void)debug_points;

RUN_ALL();
return 0;
return RUN_ALL();
}

0 comments on commit fc9ed1c

Please sign in to comment.