C++程序  |  634行  |  14.31 KB

/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Delaunay.cpp
// $Id: Delaunay.cpp,v 1.10 2011/06/17 13:35:48 mbansal Exp $

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include "Delaunay.h"

#define QQ 9   // Optimal value as determined by testing
#define DM 38  // 2^(1+DM/2) element sort capability. DM=38 for >10^6 elements
#define NYL (-1)
#define valid(l) ccw(orig(basel), dest(l), dest(basel))


CDelaunay::CDelaunay()
{
}

CDelaunay::~CDelaunay()
{
}

// Allocate storage, construct triangulation, compute voronoi corners
int CDelaunay::triangulate(SEdgeVector **edges, int n_sites, int width, int height)
{
  EdgePointer cep;

  deleteAllEdges();
  buildTriangulation(n_sites);
  cep = consolidateEdges();
  *edges = ev;

  // Note: construction_list will change ev
  return constructList(cep, width, height);
}

// builds delaunay triangulation
void CDelaunay::buildTriangulation(int size)
{
  int i, rows;
  EdgePointer lefte, righte;

  rows = (int)( 0.5 + sqrt( (double) size / log( (double) size )));

  // Sort the pointers by  x-coordinate of site
  for ( i=0 ; i < size ; i++ ) {
    sp[i] = (SitePointer) i;
  }

  spsortx( sp, 0, size-1 );
  build( 0, size-1, &lefte, &righte, rows );
  oneBndryEdge = lefte;
}

// Recursive Delaunay Triangulation Procedure
// Contains modifications for axis-switching division.
void CDelaunay::build(int lo, int hi, EdgePointer *le, EdgePointer *re, int rows)
{
  EdgePointer a, b, c, ldo, rdi, ldi, rdo, maxx, minx;
  int split, lowrows;
  int low, high;
  SitePointer s1, s2, s3;
  low = lo;
  high = hi;

  if ( low < (high-2) ) {
    // more than three elements; do recursion
    minx = sp[low];
    maxx = sp[high];
    if (rows == 1) {    // time to switch axis of division
      spsorty( sp, low, high);
      rows = 65536;
    }
    lowrows = rows/2;
    split = low - 1 + (int)
      (0.5 + ((double)(high-low+1) * ((double)lowrows / (double)rows)));
    build( low, split, &ldo, &ldi, lowrows );
    build( split+1, high, &rdi, &rdo, (rows-lowrows) );
    doMerge(&ldo, ldi, rdi, &rdo);
    while (orig(ldo) != minx) {
      ldo = rprev(ldo);
    }
    while (orig(rdo) != maxx) {
      rdo = (SitePointer) lprev(rdo);
    }
    *le = ldo;
    *re = rdo;
  }
  else if (low >= (high - 1)) { // two or one points
    a = makeEdge(sp[low], sp[high]);
    *le = a;
    *re = (EdgePointer) sym(a);
  } else { // three points
    // 3 cases: triangles of 2 orientations, and 3 points on a line
    a = makeEdge((s1 = sp[low]), (s2 = sp[low+1]));
    b = makeEdge(s2, (s3 = sp[high]));
    splice((EdgePointer) sym(a), b);
    if (ccw(s1, s3, s2)) {
      c = connectLeft(b, a);
      *le = (EdgePointer) sym(c);
      *re = c;
    } else {
      *le = a;
      *re = (EdgePointer) sym(b);
      if (ccw(s1, s2, s3)) {
        // not colinear
        c = connectLeft(b, a);
      }
    }
  }
}

// Quad-edge manipulation primitives
EdgePointer CDelaunay::makeEdge(SitePointer origin, SitePointer destination)
{
  EdgePointer temp, ans;
  temp = allocEdge();
  ans = temp;

  onext(temp) = ans;
  orig(temp) = origin;
  onext(++temp) = (EdgePointer) (ans + 3);
  onext(++temp) = (EdgePointer) (ans + 2);
  orig(temp) = destination;
  onext(++temp) = (EdgePointer) (ans + 1);

  return(ans);
}

void CDelaunay::splice(EdgePointer a, EdgePointer b)
{
  EdgePointer alpha, beta, temp;
  alpha = (EdgePointer) rot(onext(a));
  beta = (EdgePointer) rot(onext(b));
  temp = onext(alpha);
  onext(alpha) = onext(beta);
  onext(beta) = temp;
  temp = onext(a);
  onext(a) = onext(b);
  onext(b) = temp;
}

EdgePointer CDelaunay::connectLeft(EdgePointer a, EdgePointer b)
{
  EdgePointer ans;
  ans = makeEdge(dest(a), orig(b));
  splice(ans, (EdgePointer) lnext(a));
  splice((EdgePointer) sym(ans), b);
  return(ans);
}

EdgePointer CDelaunay::connectRight(EdgePointer a, EdgePointer b)
{
  EdgePointer ans;
  ans = makeEdge(dest(a), orig(b));
  splice(ans, (EdgePointer) sym(a));
  splice((EdgePointer) sym(ans), (EdgePointer) oprev(b));
  return(ans);
}

// disconnects e from the rest of the structure and destroys it
void CDelaunay::deleteEdge(EdgePointer e)
{
  splice(e, (EdgePointer) oprev(e));
  splice((EdgePointer) sym(e), (EdgePointer) oprev(sym(e)));
  freeEdge(e);
}

//
// Overall storage allocation
//

// Quad-edge storage allocation
CSite *CDelaunay::allocMemory(int n)
{
  unsigned int size;

  size = ((sizeof(CSite) + sizeof(SitePointer)) * n +
          (sizeof(SitePointer) + sizeof(EdgePointer)) * 12
          ) * n;
  if (!(sa = (CSite*) malloc(size))) {
    return NULL;
  }
  sp = (SitePointer *) (sa + n);
  ev = (SEdgeVector *) (org = sp + n);
  next = (EdgePointer *) (org + 12 * n);
  ei = (struct EDGE_INFO *) (next + 12 * n);
  return sa;
}

void CDelaunay::freeMemory()
{
  if (sa) {
    free(sa);
    sa = (CSite*)NULL;
  }
}

//
// Edge storage management
//

void CDelaunay::deleteAllEdges()
{
    nextEdge = 0;
    availEdge = NYL;
}

EdgePointer CDelaunay::allocEdge()
{
  EdgePointer ans;

  if (availEdge == NYL) {
    ans = nextEdge, nextEdge += 4;
  } else {
    ans = availEdge, availEdge = onext(availEdge);
  }
  return(ans);
}

void CDelaunay::freeEdge(EdgePointer e)
{
  e ^= e & 3;
  onext(e) = availEdge;
  availEdge = e;
}

EdgePointer CDelaunay::consolidateEdges()
{
  EdgePointer e;
  int i,j;

  while (availEdge != NYL) {
    nextEdge -= 4; e = availEdge; availEdge = onext(availEdge);

    if (e==nextEdge) {
      continue; // the one deleted was the last one anyway
    }
    if ((oneBndryEdge&~3) == nextEdge) {
      oneBndryEdge = (EdgePointer) (e | (oneBndryEdge&3));
    }
    for (i=0,j=3; i<4; i++,j=rot(j)) {
      onext(e+i) = onext(nextEdge+i);
      onext(rot(onext(e+i))) = (EdgePointer) (e+j);
    }
  }
  return nextEdge;
}

//
// Sorting Routines
//

int CDelaunay::xcmpsp(int i, int j)
{
  double d = sa[(i>=0)?sp[i]:sp1].X() - sa[(j>=0)?sp[j]:sp1].X();
  if ( d > 0. ) {
    return 1;
  }
  if ( d < 0. ) {
    return -1;
  }
  d = sa[(i>=0)?sp[i]:sp1].Y() - sa[(j>=0)?sp[j]:sp1].Y();
  if ( d > 0. ) {
    return 1;
  }
  if ( d < 0. ) {
    return -1;
  }
  return 0;
}

int CDelaunay::ycmpsp(int i, int j)
{
  double d = sa[(i>=0)?sp[i]:sp1].Y() - sa[(j>=0)?sp[j]:sp1].Y();
  if ( d > 0. ) {
    return 1;
  }
  if ( d < 0. ) {
    return -1;
  }
  d = sa[(i>=0)?sp[i]:sp1].X() - sa[(j>=0)?sp[j]:sp1].X();
  if ( d > 0. ) {
    return 1;
  }
  if ( d < 0. ) {
    return -1;
  }
  return 0;
}

int CDelaunay::cmpev(int i, int j)
{
  return (ev[i].first - ev[j].first);
}

void CDelaunay::swapsp(int i, int j)
{
  int t;
  t = (i>=0) ? sp[i] : sp1;

  if (i>=0) {
    sp[i] = (j>=0)?sp[j]:sp1;
  } else {
    sp1 = (j>=0)?sp[j]:sp1;
  }

  if (j>=0) {
    sp[j] = (SitePointer) t;
  } else {
    sp1 = (SitePointer) t;
  }
}

void CDelaunay::swapev(int i, int j)
{
  SEdgeVector temp;

  temp = ev[i];
  ev[i] = ev[j];
  ev[j] = temp;
}

void CDelaunay::copysp(int i, int j)
{
  if (j>=0) {
    sp[j] = (i>=0)?sp[i]:sp1;
  } else {
    sp1 = (i>=0)?sp[i]:sp1;
  }
}

void CDelaunay::copyev(int i, int j)
{
  ev[j] = ev[i];
}

void CDelaunay::spsortx(SitePointer *sp_in, int low, int high)
{
  sp = sp_in;
  rcssort(low,high,-1,&CDelaunay::xcmpsp,&CDelaunay::swapsp,&CDelaunay::copysp);
}

void CDelaunay::spsorty(SitePointer *sp_in, int low, int high )
{
  sp = sp_in;
  rcssort(low,high,-1,&CDelaunay::ycmpsp,&CDelaunay::swapsp,&CDelaunay::copysp);
}

void CDelaunay::rcssort(int lowelt, int highelt, int temp,
                    int (CDelaunay::*comparison)(int,int),
                    void (CDelaunay::*swap)(int,int),
                    void (CDelaunay::*copy)(int,int))
{
  int m,sij,si,sj,sL,sk;
  int stack[DM];

  if (highelt-lowelt<=1) {
    return;
  }
  if (highelt-lowelt>QQ) {
    m = 0;
    si = lowelt; sj = highelt;
    for (;;) { // partition [si,sj] about median-of-3.
      sij = (sj+si) >> 1;

      // Now to sort elements si,sij,sj into order & set temp=their median
      if ( (this->*comparison)( si,sij ) > 0 ) {
        (this->*swap)( si,sij );
      }
      if ( (this->*comparison)( sij,sj ) > 0 ) {
        (this->*swap)( sj,sij );
        if ( (this->*comparison)( si,sij ) > 0 ) {
          (this->*swap)( si,sij );
        }
      }
      (this->*copy)( sij,temp );

      // Now to partition into elements <=temp, >=temp, and ==temp.
      sk = si; sL = sj;
      do {
        do {
          sL--;
        } while( (this->*comparison)( sL,temp ) > 0 );
        do {
          sk++;
        } while( (this->*comparison)( temp,sk ) > 0 );
        if ( sk < sL ) {
          (this->*swap)( sL,sk );
        }
      } while(sk <= sL);

      // Now to recurse on shorter partition, store longer partition on stack
      if ( sL-si > sj-sk ) {
        if ( sL-si < QQ ) {
          if( m==0 ) {
            break;  // empty stack && both partitions < QQ so break
          } else {
            sj = stack[--m];
            si = stack[--m];
          }
        }
        else {
          if ( sj-sk < QQ ) {
            sj = sL;
          } else {
            stack[m++] = si;
            stack[m++] = sL;
            si = sk;
          }
        }
      }
      else {
        if ( sj-sk < QQ ) {
          if ( m==0 ) {
            break; // empty stack && both partitions < QQ so break
          } else {
            sj = stack[--m];
            si = stack[--m];
          }
        }
        else {
          if ( sL-si < QQ ) {
            si = sk;
          } else {
            stack[m++] = sk;
            stack[m++] = sj;
            sj = sL;
          }
        }
      }
    }
  }

  // Now for 0 or Data bounded  "straight insertion" sort of [0,nels-1]; if it is
  // known that el[-1] = -INF, then can omit the "sk>=0" test and save time.
  for (si=lowelt; si<highelt; si++) {
    if ( (this->*comparison)( si,si+1 ) > 0 ) {
      (this->*copy)( si+1,temp );
      sj = sk = si;
      sj++;
      do {
        (this->*copy)( sk,sj );
        sj = sk;
        sk--;
      } while ( (this->*comparison)( sk,temp ) > 0 && sk>=lowelt );
      (this->*copy)( temp,sj );
    }
  }
}

//
// Geometric primitives
//

// incircle, as in the Guibas-Stolfi paper.
int CDelaunay::incircle(SitePointer a, SitePointer b, SitePointer c, SitePointer d)
{
  double adx, ady, bdx, bdy, cdx, cdy, dx, dy, nad, nbd, ncd;
  dx = sa[d].X();
  dy = sa[d].Y();
  adx = sa[a].X() - dx;
  ady = sa[a].Y() - dy;
  bdx = sa[b].X() - dx;
  bdy = sa[b].Y() - dy;
  cdx = sa[c].X() - dx;
  cdy = sa[c].Y() - dy;
  nad = adx*adx+ady*ady;
  nbd = bdx*bdx+bdy*bdy;
  ncd = cdx*cdx+cdy*cdy;
  return( (0.0 < (nad * (bdx * cdy - bdy * cdx)
                  + nbd * (cdx * ady - cdy * adx)
                  + ncd * (adx * bdy - ady * bdx))) ? TRUE : FALSE );
}

// TRUE iff A, B, C form a counterclockwise oriented triangle
int CDelaunay::ccw(SitePointer a, SitePointer b, SitePointer c)
{
  int result;

  double ax = sa[a].X();
  double bx = sa[b].X();
  double cx = sa[c].X();
  double ay = sa[a].Y();
  double by = sa[b].Y();
  double cy = sa[c].Y();

  double val = (ax - cx)*(by - cy) - (bx - cx)*(ay - cy);
  if ( val > 0.0) {
    return true;
  }

  return false;
}

//
// The Merge Procedure.
//

void CDelaunay::doMerge(EdgePointer *ldo, EdgePointer ldi, EdgePointer rdi, EdgePointer *rdo)
{
  int rvalid, lvalid;
  EdgePointer basel,lcand,rcand,t;

  for (;;) {
    while (ccw(orig(ldi), dest(ldi), orig(rdi))) {
        ldi = (EdgePointer) lnext(ldi);
    }
    if (ccw(dest(rdi), orig(rdi), orig(ldi))) {
        rdi = (EdgePointer)rprev(rdi);
    } else {
      break;
    }
  }

  basel = connectLeft((EdgePointer) sym(rdi), ldi);
  lcand = rprev(basel);
  rcand = (EdgePointer) oprev(basel);
  if (orig(basel) == orig(*rdo)) {
    *rdo = basel;
  }
  if (dest(basel) == orig(*ldo)) {
    *ldo = (EdgePointer) sym(basel);
  }

  for (;;) {
#if 1
    if (valid(t=onext(lcand))) {
#else
    t = (EdgePointer)onext(lcand);
    if (valid(basel, t)) {
#endif
      while (incircle(dest(lcand), dest(t), orig(lcand), orig(basel))) {
        deleteEdge(lcand);
        lcand = t;
        t = onext(lcand);
      }
    }
#if 1
    if (valid(t=(EdgePointer)oprev(rcand))) {
#else
    t = (EdgePointer)oprev(rcand);
    if (valid(basel, t)) {
#endif
      while (incircle(dest(t), dest(rcand), orig(rcand), dest(basel))) {
        deleteEdge(rcand);
        rcand = t;
        t = (EdgePointer)oprev(rcand);
      }
    }

#if 1
    lvalid = valid(lcand);
    rvalid = valid(rcand);
#else
    lvalid = valid(basel, lcand);
    rvalid = valid(basel, rcand);
#endif
    if ((! lvalid) && (! rvalid)) {
      return;
    }

    if (!lvalid ||
        (rvalid && incircle(dest(lcand), orig(lcand), orig(rcand), dest(rcand)))) {
      basel = connectLeft(rcand, (EdgePointer) sym(basel));
      rcand = (EdgePointer) lnext(sym(basel));
    } else {
      basel = (EdgePointer) sym(connectRight(lcand, basel));
      lcand = rprev(basel);
    }
  }
}

int CDelaunay::constructList(EdgePointer last, int width, int height)
{
  int c, i;
  EdgePointer curr, src, nex;
  SEdgeVector *currv, *prevv;

  c = (int) ((curr = (EdgePointer) ((last & ~3))) >> 1);

  for (last -= 4; last >= 0; last -= 4) {
    src = orig(last);
    nex = dest(last);
    orig(--curr) = src;
    orig(--curr) = nex;
    orig(--curr) = nex;
    orig(--curr) = src;
  }
  rcssort(0, c - 1, -1, &CDelaunay::cmpev, &CDelaunay::swapev, &CDelaunay::copyev);

  // Throw out any edges that are too far apart
  currv = prevv = ev;
  for (i = c; i--; currv++) {
      if ((int) fabs(sa[currv->first].getVCenter().x - sa[currv->second].getVCenter().x) <= width &&
          (int) fabs(sa[currv->first].getVCenter().y - sa[currv->second].getVCenter().y) <= height) {
          *(prevv++) = *currv;
      } else {
        c--;
      }
  }
  return c;
}

// Fill in site neighbor information
void CDelaunay::linkNeighbors(SEdgeVector *edge, int nedge, int nsite)
{
  int i;

  for (i = 0; i < nsite; i++) {
    sa[i].setNeighbor(edge);
    sa[i].setNumNeighbors(0);
    for (; edge->first == i && nedge; edge++, nedge--) {
      sa[i].incrNumNeighbors();
    }
  }
}