Triangles, Triangles and Some Triangles

Delaunay Triangulation With the Bowyer-Watson Algorithm

Link to

presentation

Johan Karlsson

  • Coding for 30 years
  • Lots of Microsoft and Azure
  • Also dabbled with AWS
  • Loves using JavaScript in the browser
  • Active Solution since 2024
PROGRAM TABELL;
USES CRT;
VAR I, VAL:INTEGER;
  STOPP:CHAR; {CHAR BETYDER CHARACTER OCH KAN VARA VILKET TECKEN SOM HELST}
BEGIN
  CLRSCR;
  TEXTCOLOR (LIGHTBLUE);
  WRITELN('MED DETTA PROGRAM KAN DU FÅ EN GÅNGERTABELL UTSKRIVEN');
  WRITELN('VILKEN TABELL ÖNSKAS?');
  READLN(VAL);
  FOR I:=1 TO 12 DO
    BEGIN
      WRITELN(I:2,' * ',VAL,' = ',I*VAL);
    END;
  WRITELN('TRYCK TANGENT!');
  STOPP:=READKEY;
END.

Turbo Pascal, 1994

What and Why?

Creative Coding

IKEA September 2019

Virginia Kraljevic

virginiakraljevic.com

Inspiration

  • Two values: x and y
  • Angle and length
  • Math operations:
    • Addition
    • Subtraction
    • Multiplication
    • Division
    • ...

Vectors

  • Imagine a car
  • With a position
  • and a velocity
  • and an acceleration
  • They all are vectors!

Vectors cont.

  • Addition
  • Subtraction
  • Multiplication
  • Division
  • Get angle/length

Vectors cont.

add(v) {
  return new Vector(
    this.x + v.x,
    this.y + v.y);
}

addTo(v) {
  this.x += v.x;
  this.y += v.y;
}
sub(v) {
  return new Vector(
    this.x - v.x,
    this.y - v.y);
}
  
subFrom(v) {
  this.x -= v.x;
  this.y -= v.y;
}
mult(n) {
  return new Vector(
    this.x * n, 
    this.y * n);
}
  
div(n) {
  return new Vector(
    this.x / n, 
    this.y / n);
}
getAngle() {
  return Math.atan2(
    this.y, 
    this.x);
}
  
getLength() {
  return Math.hypot(
    this.x, 
    this.y);
}
class Vector {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  ...
}
  • Trigonometry

    • Math.atan2(0, -1); // 3.14
      
  • Pythagorean theorem

    • Math.hypot(3, 4); // 5

Vectors cont.

Triangle

  • Tree vertexes: a, b, c
  • Circumcircle
    • circumcenter
    • circumradius
  • Three edges:
    • ab
    • bc
    • ca

HTML <canvas> basics

Delaunay Triangulation

  • Triangulation - subdivision of a planar object into triangles
  • Delaunay Triangulation - given a set of points, gives the "best" triangulation where each point is a triangle vertex. 
  • Triangles because GPU's are good at drawing them
  • Used in GIS, maps, graphics, games

Delaunay.js

import { bowyerWatson, Triangle } from 'delaunay.js';
import Vector from 'vectory-lib';

function getRandomPoints() {
  let points = [];
  let nrOfPoints = 10;
  for(let i = 0; i < nrOfPoints; i++) {
    points.push(new Vector(
      Math.random() * 100,
      Math.random() * 100
    ));
  }
  return points;
}

let points = getRandomPoints();

let superTriangle = new Triangle(
    new Vector(-1000, 1000),
    new Vector(1000, 1000),
    new Vector(0, -1000)
);

let triangles = bowyerWatson(superTriangle, points);

// Do something fun with the triangles!

From a consumer (caller) perspective

  • Super triangle must be big enough to cover all points
  • From Russia
  • Mountain climber
  • Mathematician
  • 1890-1980
  • Delaunay triangulation 1934

 

Boris Delaunay

Adrian Bowyer and David Watson

Each published a paper of their own in the same number of The Computer Journal, Volume 24, Number 2, May 1981

  • Adrian Bowyer: Computing Dirichlet Tessellations. page 162-166
  • D. F. Watson: Computing the n-Dimensional Delaunay Tesselation with Application to Voronoi Polytopes. page 167-172

 

function BowyerWatson (pointList)
    // pointList is a set of coordinates defining the points to be triangulated
    triangulation := empty triangle mesh data structure
    // must be large enough to completely contain all the points in pointList
    add super-triangle to triangulation 
    // add all the points one at a time to the triangulation
    for each point in pointList do
        badTriangles := empty set
        // first find all the triangles that are no longer valid due to the insertion
        for each triangle in triangulation do 
            if point is inside circumcircle of triangle
                add triangle to badTriangles
        polygon := empty set
        for each triangle in badTriangles do // find the boundary of the polygonal hole
            for each edge in triangle do
                if edge is not shared by any other triangles in badTriangles
                    add edge to polygon
        for each triangle in badTriangles do // remove them from the data structure
            remove triangle from triangulation
        for each edge in polygon do // re-triangulate the polygonal hole
            newTri := form a triangle from edge to point
            add newTri to triangulation
    for each triangle in triangulation // done inserting points, now clean up
        if triangle contains a vertex from original super-triangle
            remove triangle from triangulation
    return triangulation

Bowyer-Watson (Wikipedia)

import Vector from 'vectory-lib';

export default class Triangle {
  constructor(a, b, c) {
    this.a = a;
    this.b = b;
    this.c = c;
  }

  vertexes() {
    return [this.a, this.b, this.c];
  }

  edges() {
    return [
      [this.a, this.b],
      [this.b, this.c],
      [this.c, this.a]
    ];
  }

  sharesAVertexWith(triangle) {
    for(const v of this.vertexes()) {
      for(const vv of triangle.vertexes()) {
        if(v.equals(vv)) {
          return true;
        }
      }
    }
    return false;
  }

  hasEdge(edge) {
    for(const e of this.edges()) {
      if(e[0].equals(edge[0]) && e[1].equals(edge[1]) ||
         e[1].equals(edge[0]) && e[0].equals(edge[1])) {
        return true;
      }
    }
    return false;
  }

  sharesAnEdgeWith(triangle) {
    for(const edge of triangle.edges()) {
      if(this.hasEdge(edge)) {
        return true;
      }
    } 
    return false;
  }

  get circumcenter() {
    if(!this._circumcenter) {
      let d = 2 * (this.a.x * (this.b.y - this.c.y) +
                   this.b.x * (this.c.y - this.a.y) +
                   this.c.x * (this.a.y - this.b.y));

      let x = 1 / d * ((this.a.x * this.a.x + this.a.y * this.a.y) * (this.b.y - this.c.y) +
                       (this.b.x * this.b.x + this.b.y * this.b.y) * (this.c.y - this.a.y) +
                       (this.c.x * this.c.x + this.c.y * this.c.y) * (this.a.y - this.b.y));

      let y = 1 / d * ((this.a.x * this.a.x + this.a.y * this.a.y) * (this.c.x - this.b.x) +
                       (this.b.x * this.b.x + this.b.y * this.b.y) * (this.a.x - this.c.x) +
                       (this.c.x * this.c.x + this.c.y * this.c.y) * (this.b.x - this.a.x));
      this._circumcenter = new Vector(x, y);
    }

    return this._circumcenter;

  }

  get centroid() {
    if(!this._centroid) {
      this._centroid = this.a.add(this.b).add(this.c).div(3);
    }
    return this._centroid;
  }

  get circumradiusSq() {
    if(!this._circumradiusSq) {
      this._circumradiusSq = this.circumcenter.sub(this.a).getLengthSq();
    }
    return this._circumradiusSq;
  }

  pointIsInsideCircumcircle(point) {
    let dist = point.sub(this.circumcenter).getLengthSq();

    return dist < this.circumradiusSq;
  }

  // Methods below are not needed for Delaunay triangulation, but useful for drawing.
  vertexesAsString() {
    return this.vertexes().map(vertex => `${vertex.x}, ${vertex.y}`).join(", ");
  }

  edgeLengths() {
    return this.edges().map(v => v[0].sub(v[1]).getLength());
  }
  
  heights() {
    let [a, b, c] = this.edgeLengths();
    function height(a, b, c) {
      const s = (a + b + c) / 2;
      const h = 2 * Math.sqrt(s * (s - a) * (s - b) * (s - c)) / b;
      return h;
    }
    return [height(a, b, c), height(b, c, a), height(c, a, b)];
  }
  
  shortestHeight() {
    return Math.min(...this.heights());
  }
}
import Triangle from './triangle.js';

export default function bowyerWatson (superTriangle, pointList) {
  // pointList is a set of coordinates defining the
  // points to be triangulated
  let triangulation = [];

  // add super-triangle to triangulation
  // must be large enough to completely contain all
  // the points in pointList
  triangulation.push(superTriangle);

  // add all the points one at a time to the triangulation
  pointList.forEach(point => {
    let badTriangles = [];

    // first find all the triangles that are no
    // longer valid due to the insertion
    triangulation.forEach(triangle => {
      if(triangle.pointIsInsideCircumcircle(point)) {
        badTriangles.push(triangle);
      }
    });
    let polygon = [];

    // find the boundary of the polygonal hole
    badTriangles.forEach(triangle => {
      triangle.edges().forEach(edge => {
        let edgeIsShared = badTriangles.some(otherTriangle => {
          return triangle !== otherTriangle && otherTriangle.hasEdge(edge);
        });
        if(!edgeIsShared) {
          // edge is not shared by any other
          // triangles in badTriangles
          polygon.push(edge);
        }
      });
    });

    // remove them from the data structure
    badTriangles.forEach(triangle => {
      let index = triangulation.indexOf(triangle);
      if (index > -1) {
        triangulation.splice(index, 1);
      }
    });

    // re-triangulate the polygonal hole
    polygon.forEach(edge => {
      // form a triangle from edge to point
      let newTri = new Triangle(edge[0], edge[1], point);
      triangulation.push(newTri);
    });
  });

  // done inserting points, now clean up
  let i = triangulation.length;
  while(i--) {
    let triangle = triangulation[i];
    if(triangle.sharesAVertexWith(superTriangle)) {
      // remove triangle from triangulation
      let index = triangulation.indexOf(triangle);
      if (index > -1) {
        triangulation.splice(index, 1);
      }
    }
  }

  return triangulation;
}

Vector math

moveTowards(v, length) {
  let delta = v.sub(this).setLength(length);
  return this.add(delta);
}

SVG stroke-dasharray

<svg viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg">
  <style>
    line {
      stroke: black;
    }
  </style>
  <line x1="0" y1="3" x2="30" y2="3" stroke-dasharray="4 3 2 1" />
</svg>

A list of comma and/or white space separated <length>s and <percentage>s that specify the lengths of alternating dashes and gaps.

Color Systems, RGB

Color Systems, HSL

Demo using delaunay.js

Voronoi

Georgy Voronoy

  • Russian mathematician
  • 1868 - 1908
  • Advisor of Boris Delaunay

 

Triangle Math, part of Triangle class

edges() {
  return [
    [this.a, this.b],
    [this.b, this.c],
    [this.c, this.a]
  ];
}

hasEdge(edge) {
  for(const e of this.edges()) {
    if(e[0].equals(edge[0]) && e[1].equals(edge[1]) ||
        e[1].equals(edge[0]) && e[0].equals(edge[1])) {
      return true;
    }
  }
  return false;
}

sharesAnEdgeWith(triangle) {
  for(const edge of triangle.edges()) {
    if(this.hasEdge(edge)) {
      return true;
    }
  } 
  return false;
}
equals(v) {
  return this.x == v.x && this.y == v.y;
}

Part of Vector class

Questions?

More

 

Triangles, Triangles and some Triangles

By Johan Karlsson

Triangles, Triangles and some Triangles

  • 11