833 lines
20 KiB
C
833 lines
20 KiB
C
/*
|
|
* ***** BEGIN GPL LICENSE BLOCK *****
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* 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 General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* The Original Code is Copyright (C) 2012 Blender Foundation.
|
|
* All rights reserved.
|
|
*
|
|
* Contributor(s): Sergey Sharybin
|
|
*
|
|
* ***** END GPL LICENSE BLOCK *****
|
|
*/
|
|
|
|
/*
|
|
* Fortune's algorithm implemented using explanation and some code snippets from
|
|
* http://blog.ivank.net/fortunes-algorithm-and-implementation.html
|
|
*/
|
|
|
|
/** \file blender/blenlib/intern/voronoi.c
|
|
* \ingroup bli
|
|
*/
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_listbase.h"
|
|
#include "BLI_math.h"
|
|
#include "BLI_voronoi.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#define VORONOI_EPS 1e-2f
|
|
|
|
enum {
|
|
voronoiEventType_Site = 0,
|
|
voronoiEventType_Circle = 1
|
|
};
|
|
|
|
typedef struct VoronoiEvent {
|
|
struct VoronoiEvent *next, *prev;
|
|
|
|
int type; /* type of event (site or circle) */
|
|
float site[2]; /* site for which event was generated */
|
|
|
|
struct VoronoiParabola *parabola; /* parabola for which event was generated */
|
|
} VoronoiEvent;
|
|
|
|
typedef struct VoronoiParabola {
|
|
struct VoronoiParabola *left, *right, *parent;
|
|
VoronoiEvent *event;
|
|
int is_leaf;
|
|
float site[2];
|
|
VoronoiEdge *edge;
|
|
} VoronoiParabola;
|
|
|
|
typedef struct VoronoiProcess {
|
|
ListBase queue, edges;
|
|
VoronoiParabola *root;
|
|
int width, height;
|
|
float current_y;
|
|
} VoronoiProcess;
|
|
|
|
/* event */
|
|
|
|
static void voronoi_insertEvent(VoronoiProcess *process, VoronoiEvent *event)
|
|
{
|
|
VoronoiEvent *current_event = process->queue.first;
|
|
|
|
while (current_event) {
|
|
if (current_event->site[1] < event->site[1]) {
|
|
break;
|
|
}
|
|
if (current_event->site[1] == event->site[1]) {
|
|
event->site[1] -= VORONOI_EPS;
|
|
}
|
|
|
|
current_event = current_event->next;
|
|
}
|
|
|
|
BLI_insertlinkbefore(&process->queue, current_event, event);
|
|
}
|
|
|
|
/* edge */
|
|
static VoronoiEdge *voronoiEdge_new(float start[2], float left[2], float right[2])
|
|
{
|
|
VoronoiEdge *edge = MEM_callocN(sizeof(VoronoiEdge), "voronoi edge");
|
|
|
|
copy_v2_v2(edge->start, start);
|
|
copy_v2_v2(edge->left, left);
|
|
copy_v2_v2(edge->right, right);
|
|
|
|
edge->neighbour = NULL;
|
|
edge->end[0] = 0;
|
|
edge->end[1] = 0;
|
|
|
|
edge->f = (right[0] - left[0]) / (left[1] - right[1]);
|
|
edge->g = start[1] - edge->f * start[0];
|
|
|
|
edge->direction[0] = right[1] - left[1];
|
|
edge->direction[1] = -(right[0] - left[0]);
|
|
|
|
return edge;
|
|
}
|
|
|
|
/* parabola */
|
|
|
|
static VoronoiParabola *voronoiParabola_new(void)
|
|
{
|
|
VoronoiParabola *parabola = MEM_callocN(sizeof(VoronoiParabola), "voronoi parabola");
|
|
|
|
parabola->is_leaf = FALSE;
|
|
parabola->event = NULL;
|
|
parabola->edge = NULL;
|
|
parabola->parent = NULL;
|
|
|
|
return parabola;
|
|
}
|
|
|
|
static VoronoiParabola *voronoiParabola_newSite(float site[2])
|
|
{
|
|
VoronoiParabola *parabola = MEM_callocN(sizeof(VoronoiParabola), "voronoi parabola site");
|
|
|
|
copy_v2_v2(parabola->site, site);
|
|
parabola->is_leaf = TRUE;
|
|
parabola->event = NULL;
|
|
parabola->edge = NULL;
|
|
parabola->parent = NULL;
|
|
|
|
return parabola;
|
|
}
|
|
|
|
/* returns the closest leave which is on the left of current node */
|
|
static VoronoiParabola *voronoiParabola_getLeftChild(VoronoiParabola *parabola)
|
|
{
|
|
VoronoiParabola *current_parabola;
|
|
|
|
if (!parabola)
|
|
return NULL;
|
|
|
|
current_parabola = parabola->left;
|
|
while (!current_parabola->is_leaf) {
|
|
current_parabola = current_parabola->right;
|
|
}
|
|
|
|
return current_parabola;
|
|
}
|
|
|
|
/* returns the closest leave which is on the right of current node */
|
|
static VoronoiParabola *voronoiParabola_getRightChild(VoronoiParabola *parabola)
|
|
{
|
|
VoronoiParabola *current_parabola;
|
|
|
|
if (!parabola)
|
|
return NULL;
|
|
|
|
current_parabola = parabola->right;
|
|
while (!current_parabola->is_leaf) {
|
|
current_parabola = current_parabola->left;
|
|
}
|
|
|
|
return current_parabola;
|
|
}
|
|
|
|
/* returns the closest parent which is on the left */
|
|
static VoronoiParabola *voronoiParabola_getLeftParent(VoronoiParabola *parabola)
|
|
{
|
|
VoronoiParabola *current_par = parabola->parent;
|
|
VoronoiParabola *last_parabola = parabola;
|
|
|
|
while (current_par->left == last_parabola) {
|
|
if (!current_par->parent)
|
|
return NULL;
|
|
|
|
last_parabola = current_par;
|
|
current_par = current_par->parent;
|
|
}
|
|
|
|
return current_par;
|
|
}
|
|
|
|
/* returns the closest parent which is on the right */
|
|
static VoronoiParabola *voronoiParabola_getRightParent(VoronoiParabola *parabola)
|
|
{
|
|
VoronoiParabola *current_parabola = parabola->parent;
|
|
VoronoiParabola *last_parabola = parabola;
|
|
|
|
while (current_parabola->right == last_parabola) {
|
|
if (!current_parabola->parent)
|
|
return NULL;
|
|
|
|
last_parabola = current_parabola;
|
|
current_parabola = current_parabola->parent;
|
|
}
|
|
|
|
return current_parabola;
|
|
}
|
|
|
|
static void voronoiParabola_setLeft(VoronoiParabola *parabola, VoronoiParabola *left)
|
|
{
|
|
parabola->left = left;
|
|
left->parent = parabola;
|
|
}
|
|
|
|
static void voronoiParabola_setRight(VoronoiParabola *parabola, VoronoiParabola *right)
|
|
{
|
|
parabola->right = right;
|
|
right->parent = parabola;
|
|
}
|
|
|
|
static float voronoi_getY(VoronoiProcess *process, float p[2], float x)
|
|
{
|
|
float ly = process->current_y;
|
|
|
|
float dp = 2 * (p[1] - ly);
|
|
float a1 = 1 / dp;
|
|
float b1 = -2 * p[0] / dp;
|
|
float c1 = ly + dp / 4 + p[0] * p[0] / dp;
|
|
|
|
return a1 * x * x + b1 * x + c1;
|
|
}
|
|
|
|
static float voronoi_getXOfEdge(VoronoiProcess *process, VoronoiParabola *par, float y)
|
|
{
|
|
VoronoiParabola *left = voronoiParabola_getLeftChild(par);
|
|
VoronoiParabola *right = voronoiParabola_getRightChild(par);
|
|
float p[2], r[2];
|
|
float dp, a1, b1, c1, a2, b2, c2, a, b, c, disc, ry, x1, x2;
|
|
float ly = process->current_y;
|
|
|
|
copy_v2_v2(p, left->site);
|
|
copy_v2_v2(r, right->site);
|
|
|
|
dp = 2.0f * (p[1] - y);
|
|
a1 = 1.0f / dp;
|
|
b1 = -2.0f * p[0] / dp;
|
|
c1 = y + dp / 4 + p[0] * p[0] / dp;
|
|
|
|
dp = 2.0f * (r[1] - y);
|
|
a2 = 1.0f / dp;
|
|
b2 = -2.0f * r[0] / dp;
|
|
c2 = ly + dp / 4 + r[0] * r[0] / dp;
|
|
|
|
a = a1 - a2;
|
|
b = b1 - b2;
|
|
c = c1 - c2;
|
|
|
|
disc = b * b - 4 * a * c;
|
|
x1 = (-b + sqrtf(disc)) / (2 * a);
|
|
x2 = (-b - sqrtf(disc)) / (2 * a);
|
|
|
|
if (p[1] < r[1])
|
|
ry = max_ff(x1, x2);
|
|
else
|
|
ry = min_ff(x1, x2);
|
|
|
|
return ry;
|
|
}
|
|
|
|
static VoronoiParabola *voronoi_getParabolaByX(VoronoiProcess *process, float xx)
|
|
{
|
|
VoronoiParabola *par = process->root;
|
|
float x = 0.0f;
|
|
float ly = process->current_y;
|
|
|
|
while (!par->is_leaf) {
|
|
x = voronoi_getXOfEdge(process, par, ly);
|
|
|
|
if (x > xx)
|
|
par = par->left;
|
|
else
|
|
par = par->right;
|
|
}
|
|
|
|
return par;
|
|
}
|
|
|
|
static int voronoi_getEdgeIntersection(VoronoiEdge *a, VoronoiEdge *b, float p[2])
|
|
{
|
|
float x = (b->g - a->g) / (a->f - b->f);
|
|
float y = a->f * x + a->g;
|
|
|
|
if ((x - a->start[0]) / a->direction[0] < 0)
|
|
return 0;
|
|
|
|
if ((y - a->start[1]) / a->direction[1] < 0)
|
|
return 0;
|
|
|
|
if ((x - b->start[0]) / b->direction[0] < 0)
|
|
return 0;
|
|
|
|
if ((y - b->start[1]) / b->direction[1] < 0)
|
|
return 0;
|
|
|
|
p[0] = x;
|
|
p[1] = y;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void voronoi_checkCircle(VoronoiProcess *process, VoronoiParabola *b)
|
|
{
|
|
VoronoiParabola *lp = voronoiParabola_getLeftParent(b);
|
|
VoronoiParabola *rp = voronoiParabola_getRightParent(b);
|
|
|
|
VoronoiParabola *a = voronoiParabola_getLeftChild(lp);
|
|
VoronoiParabola *c = voronoiParabola_getRightChild(rp);
|
|
|
|
VoronoiEvent *event;
|
|
|
|
float ly = process->current_y;
|
|
float s[2], dx, dy, d;
|
|
|
|
if (!a || !c || len_squared_v2v2(a->site, c->site) < VORONOI_EPS)
|
|
return;
|
|
|
|
if (!voronoi_getEdgeIntersection(lp->edge, rp->edge, s))
|
|
return;
|
|
|
|
dx = a->site[0] - s[0];
|
|
dy = a->site[1] - s[1];
|
|
|
|
d = sqrtf((dx * dx) + (dy * dy));
|
|
|
|
if (s[1] - d >= ly)
|
|
return;
|
|
|
|
event = MEM_callocN(sizeof(VoronoiEvent), "voronoi circle event");
|
|
|
|
event->type = voronoiEventType_Circle;
|
|
|
|
event->site[0] = s[0];
|
|
event->site[1] = s[1] - d;
|
|
|
|
b->event = event;
|
|
event->parabola = b;
|
|
|
|
voronoi_insertEvent(process, event);
|
|
}
|
|
|
|
static void voronoi_addParabola(VoronoiProcess *process, float site[2])
|
|
{
|
|
VoronoiParabola *root = process->root;
|
|
VoronoiParabola *par, *p0, *p1, *p2;
|
|
VoronoiEdge *el, *er;
|
|
float start[2];
|
|
|
|
if (!process->root) {
|
|
process->root = voronoiParabola_newSite(site);
|
|
|
|
return;
|
|
}
|
|
|
|
if (root->is_leaf && root->site[1] - site[1] < 0) {
|
|
float *fp = root->site;
|
|
float s[2];
|
|
|
|
root->is_leaf = FALSE;
|
|
voronoiParabola_setLeft(root, voronoiParabola_newSite(fp));
|
|
voronoiParabola_setRight(root, voronoiParabola_newSite(site));
|
|
|
|
s[0] = (site[0] + fp[0]) / 2.0f;
|
|
s[1] = process->height;
|
|
|
|
if (site[0] > fp[0])
|
|
root->edge = voronoiEdge_new(s, fp, site);
|
|
else
|
|
root->edge = voronoiEdge_new(s, site, fp);
|
|
|
|
BLI_addtail(&process->edges, root->edge);
|
|
|
|
return;
|
|
}
|
|
|
|
par = voronoi_getParabolaByX(process, site[0]);
|
|
|
|
if (par->event) {
|
|
BLI_freelinkN(&process->queue, par->event);
|
|
|
|
par->event = NULL;
|
|
}
|
|
|
|
start[0] = site[0];
|
|
start[1] = voronoi_getY(process, par->site, site[0]);
|
|
|
|
el = voronoiEdge_new(start, par->site, site);
|
|
er = voronoiEdge_new(start, site, par->site);
|
|
|
|
el->neighbour = er;
|
|
BLI_addtail(&process->edges, el);
|
|
|
|
par->edge = er;
|
|
par->is_leaf = FALSE;
|
|
|
|
p0 = voronoiParabola_newSite(par->site);
|
|
p1 = voronoiParabola_newSite(site);
|
|
p2 = voronoiParabola_newSite(par->site);
|
|
|
|
voronoiParabola_setRight(par, p2);
|
|
voronoiParabola_setLeft(par, voronoiParabola_new());
|
|
par->left->edge = el;
|
|
|
|
voronoiParabola_setLeft(par->left, p0);
|
|
voronoiParabola_setRight(par->left, p1);
|
|
|
|
voronoi_checkCircle(process, p0);
|
|
voronoi_checkCircle(process, p2);
|
|
}
|
|
|
|
static void voronoi_removeParabola(VoronoiProcess *process, VoronoiEvent *event)
|
|
{
|
|
VoronoiParabola *p1 = event->parabola;
|
|
|
|
VoronoiParabola *xl = voronoiParabola_getLeftParent(p1);
|
|
VoronoiParabola *xr = voronoiParabola_getRightParent(p1);
|
|
|
|
VoronoiParabola *p0 = voronoiParabola_getLeftChild(xl);
|
|
VoronoiParabola *p2 = voronoiParabola_getRightChild(xr);
|
|
|
|
VoronoiParabola *higher = NULL, *par, *gparent;
|
|
|
|
float p[2];
|
|
|
|
if (p0->event) {
|
|
BLI_freelinkN(&process->queue, p0->event);
|
|
p0->event = NULL;
|
|
}
|
|
|
|
if (p2->event) {
|
|
BLI_freelinkN(&process->queue, p2->event);
|
|
p2->event = NULL;
|
|
}
|
|
|
|
p[0] = event->site[0];
|
|
p[1] = voronoi_getY(process, p1->site, event->site[0]);
|
|
|
|
copy_v2_v2(xl->edge->end, p);
|
|
copy_v2_v2(xr->edge->end, p);
|
|
|
|
par = p1;
|
|
while (par != process->root) {
|
|
par = par->parent;
|
|
|
|
if (par == xl)
|
|
higher = xl;
|
|
if (par == xr)
|
|
higher = xr;
|
|
}
|
|
|
|
higher->edge = voronoiEdge_new(p, p0->site, p2->site);
|
|
BLI_addtail(&process->edges, higher->edge);
|
|
|
|
gparent = p1->parent->parent;
|
|
if (p1->parent->left == p1) {
|
|
if (gparent->left == p1->parent)
|
|
voronoiParabola_setLeft(gparent, p1->parent->right);
|
|
if (gparent->right == p1->parent)
|
|
voronoiParabola_setRight(gparent, p1->parent->right);
|
|
}
|
|
else {
|
|
if (gparent->left == p1->parent)
|
|
voronoiParabola_setLeft(gparent, p1->parent->left);
|
|
if (gparent->right == p1->parent)
|
|
voronoiParabola_setRight(gparent, p1->parent->left);
|
|
}
|
|
|
|
MEM_freeN(p1->parent);
|
|
MEM_freeN(p1);
|
|
|
|
voronoi_checkCircle(process, p0);
|
|
voronoi_checkCircle(process, p2);
|
|
}
|
|
|
|
static void voronoi_finishEdge(VoronoiProcess *process, VoronoiParabola *parabola)
|
|
{
|
|
float mx;
|
|
|
|
if (parabola->is_leaf) {
|
|
MEM_freeN(parabola);
|
|
return;
|
|
}
|
|
|
|
if (parabola->edge->direction[0] > 0.0f)
|
|
mx = max_ff(process->width, parabola->edge->start[0] + 10);
|
|
else
|
|
mx = min_ff(0.0f, parabola->edge->start[0] - 10.0f);
|
|
|
|
parabola->edge->end[0] = mx;
|
|
parabola->edge->end[1] = mx * parabola->edge->f + parabola->edge->g;
|
|
|
|
voronoi_finishEdge(process, parabola->left);
|
|
voronoi_finishEdge(process, parabola->right);
|
|
|
|
MEM_freeN(parabola);
|
|
}
|
|
|
|
static void voronoi_clampEdgeVertex(int width, int height, float *coord, float *other_coord)
|
|
{
|
|
const float corners[4][2] = {{0.0f, 0.0f},
|
|
{width - 1, 0.0f},
|
|
{width - 1, height - 1},
|
|
{0.0f, height - 1}};
|
|
int i;
|
|
|
|
if (IN_RANGE_INCL(coord[0], 0, width - 1) && IN_RANGE_INCL(coord[1], 0, height - 1)) {
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
float v1[2], v2[2];
|
|
float p[2];
|
|
|
|
copy_v2_v2(v1, corners[i]);
|
|
|
|
if (i == 3)
|
|
copy_v2_v2(v2, corners[0]);
|
|
else
|
|
copy_v2_v2(v2, corners[i + 1]);
|
|
|
|
if (isect_seg_seg_v2_point(v1, v2, coord, other_coord, p) == 1) {
|
|
if (i == 0 && coord[1] > p[1])
|
|
continue;
|
|
if (i == 1 && coord[0] < p[0])
|
|
continue;
|
|
if (i == 2 && coord[1] < p[1])
|
|
continue;
|
|
if (i == 3 && coord[0] > p[0])
|
|
continue;
|
|
|
|
copy_v2_v2(coord, p);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void voronoi_clampEdges(ListBase *edges, int width, int height, ListBase *clamped_edges)
|
|
{
|
|
VoronoiEdge *edge;
|
|
|
|
edge = edges->first;
|
|
while (edge) {
|
|
VoronoiEdge *new_edge = MEM_callocN(sizeof(VoronoiEdge), "clamped edge");
|
|
|
|
*new_edge = *edge;
|
|
BLI_addtail(clamped_edges, new_edge);
|
|
|
|
voronoi_clampEdgeVertex(width, height, new_edge->start, new_edge->end);
|
|
voronoi_clampEdgeVertex(width, height, new_edge->end, new_edge->start);
|
|
|
|
edge = edge->next;
|
|
}
|
|
}
|
|
|
|
static int voronoi_getNextSideCoord(ListBase *edges, float coord[2], int dim, int dir, float next_coord[2])
|
|
{
|
|
VoronoiEdge *edge = edges->first;
|
|
float distance = FLT_MAX;
|
|
int other_dim = dim ? 0 : 1;
|
|
|
|
while (edge) {
|
|
int ok = FALSE;
|
|
float co[2], cur_distance;
|
|
|
|
if (fabsf(edge->start[other_dim] - coord[other_dim]) < VORONOI_EPS &&
|
|
len_squared_v2v2(coord, edge->start) > VORONOI_EPS)
|
|
{
|
|
copy_v2_v2(co, edge->start);
|
|
ok = TRUE;
|
|
}
|
|
|
|
if (fabsf(edge->end[other_dim] - coord[other_dim]) < VORONOI_EPS &&
|
|
len_squared_v2v2(coord, edge->end) > VORONOI_EPS)
|
|
{
|
|
copy_v2_v2(co, edge->end);
|
|
ok = TRUE;
|
|
}
|
|
|
|
if (ok) {
|
|
if (dir > 0 && coord[dim] > co[dim]) {
|
|
ok = FALSE;
|
|
}
|
|
else if (dir < 0 && coord[dim] < co[dim]) {
|
|
ok = FALSE;
|
|
}
|
|
}
|
|
|
|
if (ok) {
|
|
cur_distance = len_squared_v2v2(coord, co);
|
|
if (cur_distance < distance) {
|
|
copy_v2_v2(next_coord, co);
|
|
distance = cur_distance;
|
|
}
|
|
}
|
|
|
|
edge = edge->next;
|
|
}
|
|
|
|
return distance < FLT_MAX;
|
|
}
|
|
|
|
static void voronoi_createBoundaryEdges(ListBase *edges, int width, int height)
|
|
{
|
|
const float corners[4][2] = {{width - 1, 0.0f},
|
|
{width - 1, height - 1},
|
|
{0.0f, height - 1},
|
|
{0.0f, 0.0f}};
|
|
int i, dim = 0, dir = 1;
|
|
|
|
float coord[2] = {0.0f, 0.0f};
|
|
float next_coord[2] = {0.0f, 0.0f};
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
while (voronoi_getNextSideCoord(edges, coord, dim, dir, next_coord)) {
|
|
VoronoiEdge *edge = MEM_callocN(sizeof(VoronoiEdge), "boundary edge");
|
|
|
|
copy_v2_v2(edge->start, coord);
|
|
copy_v2_v2(edge->end, next_coord);
|
|
BLI_addtail(edges, edge);
|
|
|
|
copy_v2_v2(coord, next_coord);
|
|
}
|
|
|
|
if (len_squared_v2v2(coord, corners[i]) > VORONOI_EPS) {
|
|
VoronoiEdge *edge = MEM_callocN(sizeof(VoronoiEdge), "boundary edge");
|
|
|
|
copy_v2_v2(edge->start, coord);
|
|
copy_v2_v2(edge->end, corners[i]);
|
|
BLI_addtail(edges, edge);
|
|
copy_v2_v2(coord, corners[i]);
|
|
}
|
|
|
|
dim = dim ? 0 : 1;
|
|
if (i == 1)
|
|
dir = -1;
|
|
}
|
|
}
|
|
|
|
void BLI_voronoi_compute(const VoronoiSite *sites, int sites_total, int width, int height, ListBase *edges)
|
|
{
|
|
VoronoiProcess process;
|
|
VoronoiEdge *edge;
|
|
int i;
|
|
|
|
memset(&process, 0, sizeof(VoronoiProcess));
|
|
|
|
process.width = width;
|
|
process.height = height;
|
|
|
|
for (i = 0; i < sites_total; i++) {
|
|
VoronoiEvent *event = MEM_callocN(sizeof(VoronoiEvent), "voronoi site event");
|
|
|
|
event->type = voronoiEventType_Site;
|
|
copy_v2_v2(event->site, sites[i].co);
|
|
|
|
voronoi_insertEvent(&process, event);
|
|
}
|
|
|
|
while (process.queue.first) {
|
|
VoronoiEvent *event = process.queue.first;
|
|
|
|
process.current_y = event->site[1];
|
|
|
|
if (event->type == voronoiEventType_Site) {
|
|
voronoi_addParabola(&process, event->site);
|
|
}
|
|
else {
|
|
voronoi_removeParabola(&process, event);
|
|
}
|
|
|
|
BLI_freelinkN(&process.queue, event);
|
|
}
|
|
|
|
voronoi_finishEdge(&process, process.root);
|
|
|
|
edge = process.edges.first;
|
|
while (edge) {
|
|
if (edge->neighbour) {
|
|
copy_v2_v2(edge->start, edge->neighbour->end);
|
|
MEM_freeN(edge->neighbour);
|
|
}
|
|
|
|
edge = edge->next;
|
|
}
|
|
|
|
BLI_movelisttolist(edges, &process.edges);
|
|
}
|
|
|
|
static int testVoronoiEdge(const float site[2], const float point[2], const VoronoiEdge *edge)
|
|
{
|
|
float p[2];
|
|
|
|
if (isect_seg_seg_v2_point(site, point, edge->start, edge->end, p) == 1) {
|
|
if (len_squared_v2v2(p, edge->start) > VORONOI_EPS &&
|
|
len_squared_v2v2(p, edge->end) > VORONOI_EPS)
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static int voronoi_addTriangulationPoint(const float coord[2], const float color[3],
|
|
VoronoiTriangulationPoint **triangulated_points,
|
|
int *triangulated_points_total)
|
|
{
|
|
VoronoiTriangulationPoint *triangulation_point;
|
|
int i;
|
|
|
|
for (i = 0; i < *triangulated_points_total; i++) {
|
|
if (equals_v2v2(coord, (*triangulated_points)[i].co)) {
|
|
triangulation_point = &(*triangulated_points)[i];
|
|
|
|
add_v3_v3(triangulation_point->color, color);
|
|
triangulation_point->power++;
|
|
|
|
return i;
|
|
}
|
|
}
|
|
|
|
if (*triangulated_points) {
|
|
*triangulated_points = MEM_reallocN(*triangulated_points,
|
|
sizeof(VoronoiTriangulationPoint) * (*triangulated_points_total + 1));
|
|
}
|
|
else {
|
|
*triangulated_points = MEM_callocN(sizeof(VoronoiTriangulationPoint), "triangulation points");
|
|
}
|
|
|
|
triangulation_point = &(*triangulated_points)[(*triangulated_points_total)];
|
|
copy_v2_v2(triangulation_point->co, coord);
|
|
copy_v3_v3(triangulation_point->color, color);
|
|
|
|
triangulation_point->power = 1;
|
|
|
|
(*triangulated_points_total)++;
|
|
|
|
return (*triangulated_points_total) - 1;
|
|
}
|
|
|
|
static void voronoi_addTriangle(int v1, int v2, int v3, int (**triangles)[3], int *triangles_total)
|
|
{
|
|
int *triangle;
|
|
|
|
if (*triangles) {
|
|
*triangles = MEM_reallocN(*triangles, sizeof(int[3]) * (*triangles_total + 1));
|
|
}
|
|
else {
|
|
*triangles = MEM_callocN(sizeof(int[3]), "trianglulation triangles");
|
|
}
|
|
|
|
triangle = (int *)&(*triangles)[(*triangles_total)];
|
|
|
|
triangle[0] = v1;
|
|
triangle[1] = v2;
|
|
triangle[2] = v3;
|
|
|
|
(*triangles_total)++;
|
|
}
|
|
|
|
void BLI_voronoi_triangulate(const VoronoiSite *sites, int sites_total, ListBase *edges, int width, int height,
|
|
VoronoiTriangulationPoint **triangulated_points_r, int *triangulated_points_total_r,
|
|
int (**triangles_r)[3], int *triangles_total_r)
|
|
{
|
|
VoronoiTriangulationPoint *triangulated_points = NULL;
|
|
int (*triangles)[3] = NULL;
|
|
int triangulated_points_total = 0, triangles_total = 0;
|
|
int i;
|
|
ListBase boundary_edges = {NULL, NULL};
|
|
|
|
voronoi_clampEdges(edges, width, height, &boundary_edges);
|
|
voronoi_createBoundaryEdges(&boundary_edges, width, height);
|
|
|
|
for (i = 0; i < sites_total; i++) {
|
|
VoronoiEdge *edge;
|
|
int v1;
|
|
|
|
v1 = voronoi_addTriangulationPoint(sites[i].co, sites[i].color, &triangulated_points, &triangulated_points_total);
|
|
|
|
edge = boundary_edges.first;
|
|
while (edge) {
|
|
VoronoiEdge *test_edge = boundary_edges.first;
|
|
int ok_start = TRUE, ok_end = TRUE;
|
|
|
|
while (test_edge) {
|
|
if (ok_start && !testVoronoiEdge(sites[i].co, edge->start, test_edge)) {
|
|
ok_start = FALSE;
|
|
break;
|
|
}
|
|
|
|
if (ok_end && !testVoronoiEdge(sites[i].co, edge->end, test_edge)) {
|
|
ok_end = FALSE;
|
|
break;
|
|
}
|
|
|
|
test_edge = test_edge->next;
|
|
}
|
|
|
|
if (ok_start && ok_end) {
|
|
int v2, v3;
|
|
|
|
v2 = voronoi_addTriangulationPoint(edge->start, sites[i].color, &triangulated_points, &triangulated_points_total);
|
|
v3 = voronoi_addTriangulationPoint(edge->end, sites[i].color, &triangulated_points, &triangulated_points_total);
|
|
|
|
voronoi_addTriangle(v1, v2, v3, &triangles, &triangles_total);
|
|
}
|
|
|
|
edge = edge->next;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < triangulated_points_total; i++) {
|
|
VoronoiTriangulationPoint *triangulation_point = &triangulated_points[i];
|
|
|
|
mul_v3_fl(triangulation_point->color, 1.0f / triangulation_point->power);
|
|
}
|
|
|
|
*triangulated_points_r = triangulated_points;
|
|
*triangulated_points_total_r = triangulated_points_total;
|
|
|
|
*triangles_r = triangles;
|
|
*triangles_total_r = triangles_total;
|
|
|
|
BLI_freelistN(&boundary_edges);
|
|
}
|