/*******************************************************************************
* This file is part of SWIFT.
* Copyright (c) 2017 Bert Vandenbroucke (bert.vandenbroucke@gmail.com).
*
* This program 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 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*
******************************************************************************/
#ifndef SWIFT_VORONOIXD_ALGORITHM_H
#define SWIFT_VORONOIXD_ALGORITHM_H
#include "error.h"
#include "inline.h"
#include "minmax.h"
#include "voronoi2d_cell.h"
#include
#include
#include
/* Check if the number of vertices exceeds the maximal allowed number */
#define VORONOI_CHECK_SIZE() \
if (nvert > VORONOI2D_MAXNUMVERT) { \
error("Too many vertices!"); \
}
/* IDs used to keep track of cells neighbouring walls of the simulation box
This will only work if these IDs are never used for actual particles (which
in practice means you want to have less than 2^63-4 (~9e18) particles in your
simulation) */
#define VORONOI2D_BOX_LEFT 18446744073709551602llu
#define VORONOI2D_BOX_RIGHT 18446744073709551603llu
#define VORONOI2D_BOX_TOP 18446744073709551604llu
#define VORONOI2D_BOX_BOTTOM 18446744073709551605llu
#define VORONOI2D_TOLERANCE 1.e-6f
/**
* @brief Initialize a 2D Voronoi cell.
*
* @param cell 2D Voronoi cell to initialize.
* @param x Position of the generator of the cell.
* @param anchor Anchor of the simulation box containing all particles.
* @param side Side lengths of the simulation box containing all particles.
*/
__attribute__((always_inline)) INLINE void voronoi_cell_init(
struct voronoi_cell *cell, const double *x, const double *anchor,
const double *side) {
/* Set the position of the generator of the cell (for reference) */
cell->x[0] = x[0];
cell->x[1] = x[1];
/* Initialize the cell as a box with the same extents as the simulation box
(note: all vertex coordinates are relative w.r.t. the cell generator) */
cell->nvert = 4;
cell->vertices[0][0] = anchor[0] - cell->x[0];
cell->vertices[0][1] = anchor[1] - cell->x[1];
cell->vertices[1][0] = anchor[0] - cell->x[0];
cell->vertices[1][1] = anchor[1] + side[1] - cell->x[1];
cell->vertices[2][0] = anchor[0] + side[0] - cell->x[0];
cell->vertices[2][1] = anchor[1] + side[1] - cell->x[1];
cell->vertices[3][0] = anchor[0] + side[0] - cell->x[0];
cell->vertices[3][1] = anchor[1] - cell->x[1];
/* The neighbours are ordered such that neighbour i shares the face in between
vertices i and i+1 (with last vertex + 1 = first vertex)
We walk around the cell in clockwise direction */
cell->ngbs[0] = VORONOI2D_BOX_LEFT;
cell->ngbs[1] = VORONOI2D_BOX_TOP;
cell->ngbs[2] = VORONOI2D_BOX_RIGHT;
cell->ngbs[3] = VORONOI2D_BOX_BOTTOM;
/* These variables are initialized to zero, we will compute them after the
neighbour iteration has finished */
cell->volume = 0.0f;
cell->centroid[0] = 0.0f;
cell->centroid[1] = 0.0f;
}
/**
* @brief Interact a 2D Voronoi cell with a particle with given relative
* position and ID.
*
* @param cell 2D Voronoi cell.
* @param dx Relative position of the interacting generator w.r.t. the cell
* generator (in fact: dx = generator - neighbour).
* @param id ID of the interacting neighbour.
*/
__attribute__((always_inline)) INLINE void voronoi_cell_interact(
struct voronoi_cell *cell, const float *dx, unsigned long long id) {
/* variables used for geometrical tests */
float half_dx[2];
float r2;
/* variables used to store test results */
float test, b1, b2, a1, a2;
/* general loop index */
int i;
/* variables used to store indices of intersected edges */
int index_above1, index_above2;
int index_below1, index_below2;
/* variable used to store directionality in edge traversal */
int increment;
/* new number of vertices and new vertex coordinates */
int nvert;
float vertices[VORONOI2D_MAXNUMVERT][2];
unsigned long long ngbs[VORONOI2D_MAXNUMVERT];
/* The process of cutting the current cell with the midline of the generator
and the given relative neighbour position proceeds in two steps:
- first we need to locate an edge of the current cell that is intersected
by this midline. Such an edge does not necessarily exist; in this case
the given neighbour is not an actual neighbour of this cell
- Once we have an intersected edge, we create a new edge starting at the
intersection point. We follow the edges connected to the intersected
edge until we find another intersected edge, and use its intersection
point as end point of the new edge. */
/* First, we set up some variables that are used to check if a vertex is above
or below the midplane. */
/* we need a vector with half the size of the vector joining generator and
neighbour, pointing to the neighbour */
half_dx[0] = -0.5f * dx[0];
half_dx[1] = -0.5f * dx[1];
/* we need the squared length of this vector */
r2 = half_dx[0] * half_dx[0] + half_dx[1] * half_dx[1];
/* a vertex v = (vx, vy) is above the midline if
vx*half_dx[0] + vy*half_dx[1] > r2
i.e., if the length of the projected vertex position is longer than the
length of the vector pointing to the closest point on the midline (both
vectors originate at the position of the generator)
the vertex is below the midline if the projected position vector is shorter
if the projected position vector has the same length, the vertex is on the
midline */
/* start testing a random vertex: the first one */
test = cell->vertices[0][0] * half_dx[0] + cell->vertices[0][1] * half_dx[1] -
r2;
if (test < -VORONOI2D_TOLERANCE) {
/* vertex is below midline */
#ifdef VORONOI_VERBOSE
message("First vertex is below midline (%g %g --> %g)!",
cell->vertices[0][0] + cell->x[0],
cell->vertices[0][1] + cell->x[1], test);
#endif
/* store the test result; we might need it to compute the intersection
coordinates */
b1 = test;
/* move on until we find a vertex that is above or on the midline */
i = 1;
test = cell->vertices[i][0] * half_dx[0] +
cell->vertices[i][1] * half_dx[1] - r2;
while (i < cell->nvert && test < 0.) {
/* make sure we always store the latest test result */
b1 = test;
++i;
test = cell->vertices[i][0] * half_dx[0] +
cell->vertices[i][1] * half_dx[1] - r2;
}
/* loop finished, there are two possibilities:
- i == cell->nvert, all vertices lie below the midline and the given
neighbour is not an actual neighbour of this cell
- test >= 0., we found a vertex above (or on) the midline */
if (i == cell->nvert) {
/* the given neighbour is not an actual neighbour: exit the routine */
#ifdef VORONOI_VERBOSE
message("Not a neighbour!");
#endif
return;
}
/* we have found an intersected edge: i-1 -> i
we store the index of the vertices above and below the midline, make sure
we store the test result for later intersection computation, and set the
increment to positive, so that we look for the other intersected edge in
clockwise direction */
index_below1 = i - 1;
index_above1 = i;
a1 = test;
increment = 1;
} else {
/* vertex is above or on midline
in the case where it is on the midline, we count that as above as well:
the vertex will be removed, and a new vertex will be created at the same
position */
#ifdef VORONOI_VERBOSE
message("First vertex is above midline (%g %g --> %g)!",
cell->vertices[0][0] + cell->x[0],
cell->vertices[0][1] + cell->x[1], test);
#endif
/* store the test result */
a1 = test;
/* move on until we find a vertex that is below the midline */
i = 1;
test = cell->vertices[i][0] * half_dx[0] +
cell->vertices[i][1] * half_dx[1] - r2;
while (i < cell->nvert && test > -VORONOI2D_TOLERANCE) {
/* make sure we always store the most recent test result */
a1 = test;
++i;
test = cell->vertices[i][0] * half_dx[0] +
cell->vertices[i][1] * half_dx[1] - r2;
}
/* loop finished, there are two possibilities:
- i == cell->nvert, all vertices lie above the midline. This should
never happen.
- test <= 0., we found a vertex below (or on) the midline */
if (i == cell->nvert) {
/* fatal error! */
error("Could not find a vertex below the midline!");
}
/* we have found an intersected edge: i-1 -> i
we store the index of the vertices above and below the midline, make sure
we store the test result for later intersection computation, and set the
increment to negative, so that we look for the other intersected edge in
counterclockwise direction */
index_below1 = i;
index_above1 = i - 1;
increment = -1;
b1 = test;
}
#ifdef VORONOI_VERBOSE
message("First intersected edge: %g %g --> %g %g (%i --> %i)",
cell->vertices[index_below1][0] + cell->x[0],
cell->vertices[index_below1][1] + cell->x[1],
cell->vertices[index_above1][0] + cell->x[0],
cell->vertices[index_above1][1] + cell->x[1], index_below1,
index_above1);
#endif
/* now we need to find the second intersected edge
we start from the vertex above (or on) the midline and search in the
direction opposite to the intersected edge direction until we find a vertex
below the midline */
/* we make sure we store the test result for the second vertex above the
midline as well, since we need this for intersection point computations
the second vertex can be equal to the first */
a2 = a1;
i = index_above1 + increment;
if (i < 0) {
i = cell->nvert - 1;
}
if (i == cell->nvert) {
i = 0;
}
test = cell->vertices[i][0] * half_dx[0] + cell->vertices[i][1] * half_dx[1] -
r2;
/* this loop can never deadlock, as we know there is at least 1 vertex below
the midline */
while (test > -VORONOI2D_TOLERANCE) {
/* make sure we always store the most recent test result */
a2 = test;
i += increment;
if (i < 0) {
i = cell->nvert - 1;
}
if (i == cell->nvert) {
i = 0;
}
test = cell->vertices[i][0] * half_dx[0] +
cell->vertices[i][1] * half_dx[1] - r2;
}
index_below2 = i;
index_above2 = i - increment;
if (index_above2 < 0) {
index_above2 = cell->nvert - 1;
}
if (index_above2 == cell->nvert) {
index_above2 = 0;
}
/* we also store the test result for the second vertex below the midline */
b2 = test;
if (index_above1 == index_above2 && index_below1 == index_below2) {
/* There can be only 1 vertex above or below the midline, but we need 2
intersected edges, so if the vertices above the midline are the same, the
ones below need to be different and vice versa */
error("Only 1 intersected edge found!");
}
/* there is exactly one degenerate case we have not addressed yet: the case
where index_above1 and index_above2 are the same and are on the midline.
In this case we don't want to create 2 new vertices. Instead, we just keep
index_above1, which basically means nothing happens at all and we can just
return */
if (index_above1 == index_above2 && a1 == 0.) {
return;
}
/* to make the code below more clear, we make sure index_above1 always holds
the first vertex to remove, and index_above2 the last one, in clockwise
order
This means we need to interchange 1 and 2 if we were searching in counter-
clockwise direction above */
if (increment < 0) {
i = index_below1;
index_below1 = index_below2;
index_below2 = i;
i = index_above1;
index_above1 = index_above2;
index_above2 = i;
test = b1;
b1 = b2;
b2 = test;
test = a1;
a1 = a2;
a2 = test;
}
#ifdef VORONOI_VERBOSE
message("First vertex below: %g %g (%i, %g)",
cell->vertices[index_below1][0] + cell->x[0],
cell->vertices[index_below1][1] + cell->x[1], index_below1, b1);
message("First vertex above: %g %g (%i, %g)",
cell->vertices[index_above1][0] + cell->x[0],
cell->vertices[index_above1][1] + cell->x[1], index_above1, a1);
message("Second vertex below: %g %g (%i, %g)",
cell->vertices[index_below2][0] + cell->x[0],
cell->vertices[index_below2][1] + cell->x[1], index_below2, b2);
message("Second vertex above: %g %g (%i, %g)",
cell->vertices[index_above2][0] + cell->x[0],
cell->vertices[index_above2][1] + cell->x[1], index_above2, a2);
#endif
if (b1 == 0. || b2 == 0.) {
error("Vertex below midline is on midline!");
}
/* convert the test results (which correspond to the projected distance
between the vertex and the midline) to the fractions of the intersected
edges above and below the midline */
test = a1 / (a1 - b1);
a1 = test;
b1 = 1.0f - test;
test = a2 / (a2 - b2);
a2 = test;
b2 = 1.0f - test;
/* remove the vertices above the midline, and insert two new vertices,
corresponding to the intersection points of the intersected edges and the
midline
In practice, we just copy all remaining vertices, starting from the first
vertex below the midline (in clockwise order) */
nvert = 0;
i = index_below2;
while (i != index_above1) {
vertices[nvert][0] = cell->vertices[i][0];
vertices[nvert][1] = cell->vertices[i][1];
ngbs[nvert] = cell->ngbs[i];
++nvert;
VORONOI_CHECK_SIZE();
++i;
if (i == cell->nvert) {
i = 0;
}
}
/* now add the new vertices, they are always last */
vertices[nvert][0] = a1 * cell->vertices[index_below1][0] +
b1 * cell->vertices[index_above1][0];
vertices[nvert][1] = a1 * cell->vertices[index_below1][1] +
b1 * cell->vertices[index_above1][1];
ngbs[nvert] = id;
++nvert;
VORONOI_CHECK_SIZE();
vertices[nvert][0] = a2 * cell->vertices[index_below2][0] +
b2 * cell->vertices[index_above2][0];
vertices[nvert][1] = a2 * cell->vertices[index_below2][1] +
b2 * cell->vertices[index_above2][1];
ngbs[nvert] = cell->ngbs[index_above2];
++nvert;
VORONOI_CHECK_SIZE();
/* overwrite the original vertices */
cell->nvert = nvert;
for (i = 0; i < cell->nvert; ++i) {
cell->vertices[i][0] = vertices[i][0];
cell->vertices[i][1] = vertices[i][1];
cell->ngbs[i] = ngbs[i];
}
}
/**
* @brief Finalize a 2D Voronoi cell.
*
* @param cell 2D Voronoi cell.
* @return Maximal radius that could still change the structure of the cell.
*/
__attribute__((always_inline)) INLINE float voronoi_cell_finalize(
struct voronoi_cell *cell) {
int i;
float vertices[VORONOI2D_MAXNUMVERT][2];
float A, x[2], y[2], r2, r2max;
/* make a copy of the vertices (they are overwritten when the face midpoints
are computed */
for (i = 0; i < cell->nvert; ++i) {
vertices[i][0] = cell->vertices[i][0];
vertices[i][1] = cell->vertices[i][1];
}
r2max = 0.0f;
for (i = 0; i < cell->nvert; ++i) {
if (i < cell->nvert - 1) {
x[0] = vertices[i][0];
y[0] = vertices[i][1];
x[1] = vertices[i + 1][0];
y[1] = vertices[i + 1][1];
} else {
x[0] = vertices[i][0];
y[0] = vertices[i][1];
x[1] = vertices[0][0];
y[1] = vertices[0][1];
}
A = x[1] * y[0] - x[0] * y[1];
cell->volume += A;
cell->centroid[0] += (x[0] + x[1]) * A;
cell->centroid[1] += (y[0] + y[1]) * A;
/* Note that we only need the RELATIVE positions of the midpoints */
cell->face_midpoints[i][0] = 0.5f * (x[0] + x[1]);
cell->face_midpoints[i][1] = 0.5f * (y[0] + y[1]);
r2 = x[0] * x[0] + y[0] * y[0];
r2max = max(r2max, r2);
x[0] -= x[1];
y[0] -= y[1];
cell->face_lengths[i] = sqrtf(x[0] * x[0] + y[0] * y[0]);
}
cell->volume *= 0.5f;
A = 6 * cell->volume;
cell->centroid[0] /= A;
cell->centroid[1] /= A;
cell->centroid[0] += cell->x[0];
cell->centroid[1] += cell->x[1];
return 2.0f * sqrtf(r2max);
}
/**
* @brief Get the oriented surface area and midpoint of the face between a
* 2D Voronoi cell and the given neighbour.
*
* @param cell 2D Voronoi cell.
* @param ngb ID of a particle that is possibly a neighbour of this cell.
* @param midpoint Array to store the relative position of the face in.
* @return 0 if the given neighbour is not a neighbour, surface area otherwise.
*/
__attribute__((always_inline)) INLINE float voronoi_get_face(
const struct voronoi_cell *cell, unsigned long long ngb, float *midpoint) {
/* look up the neighbour */
int i = 0;
while (i < cell->nvert && cell->ngbs[i] != ngb) {
++i;
}
if (i == cell->nvert) {
/* The given cell is not a neighbour. */
return 0.0f;
}
midpoint[0] = cell->face_midpoints[i][0];
midpoint[1] = cell->face_midpoints[i][1];
midpoint[2] = 0.0f;
return cell->face_lengths[i];
}
/**
* @brief Get the centroid of a 2D Voronoi cell.
*
* @param cell 2D Voronoi cell.
* @param centroid Array to store the centroid in.
*/
__attribute__((always_inline)) INLINE void voronoi_get_centroid(
const struct voronoi_cell *cell, float *centroid) {
centroid[0] = cell->centroid[0];
centroid[1] = cell->centroid[1];
centroid[2] = 0.0f;
}
/*******************************************************************************
** EXTRA FUNCTIONS USED FOR DEBUGGING *****************************************
******************************************************************************/
/**
* @brief Print the given cell to the stdout in a format that can be plotted
* using gnuplot.
*
* @param cell voronoi_cell to print.
*/
__attribute__((always_inline)) INLINE void voronoi_print_cell(
const struct voronoi_cell *cell) {
int i, ip1;
/* print cell generator */
printf("%g %g\n\n", cell->x[0], cell->x[1]);
/* print cell vertices */
for (i = 0; i < cell->nvert; ++i) {
ip1 = i + 1;
if (ip1 == cell->nvert) {
ip1 = 0;
}
printf("%g %g\n%g %g\n\n", cell->vertices[i][0] + cell->x[0],
cell->vertices[i][1] + cell->x[1],
cell->vertices[ip1][0] + cell->x[0],
cell->vertices[ip1][1] + cell->x[1]);
}
}
#endif // SWIFT_VORONOIXD_ALGORITHM_H