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



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 triangulationBowyer-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
- Colorful Triangle Pattern Generator
- npm install foo
- import foo from 'foo';
- Parcel
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
- My CodePen profile
- Coding Train Youtube with Dan Shiffman
- Voronoi pixel shader on Shadertoy
- Delaunay.js on GitHub

Triangles, Triangles and some Triangles
By Johan Karlsson
Triangles, Triangles and some Triangles
- 11