initial commit

This commit is contained in:
2025-11-26 18:58:15 +08:00
commit 3ec494ca69
168 changed files with 16142 additions and 0 deletions

1
icegps-triangulation/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,16 @@
plugins {
id("java-library")
alias(libs.plugins.kotlin.jvm)
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlin {
compilerOptions {
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
}
}
dependencies {
implementation(project(":math"))
}

View File

@@ -0,0 +1,596 @@
package com.icegps.triangulation
import kotlin.math.*
val EPSILON: Double = 2.0.pow(-52)
/**
* A Kotlin port of Mapbox's Delaunator incredibly fast JavaScript library for Delaunay triangulation of 2D points.
*
* @description Port of Mapbox's Delaunator (JavaScript) library - https://github.com/mapbox/delaunator
* @property coords flat positions' array - [x0, y0, x1, y1..]
*
* @since f0ed80d - commit
* @author Ricardo Matias
*/
@Suppress("unused")
class Delaunator(val coords: DoubleArray) {
val EDGE_STACK = IntArray(512)
private var count = coords.size shr 1
// arrays that will store the triangulation graph
val maxTriangles = (2 * count - 5).coerceAtLeast(0)
private val _triangles = IntArray(maxTriangles * 3)
private val _halfedges = IntArray(maxTriangles * 3)
lateinit var triangles: IntArray
lateinit var halfedges: IntArray
// temporary arrays for tracking the edges of the advancing convex hull
private var hashSize = ceil(sqrt(count * 1.0)).toInt()
private var hullPrev = IntArray(count) // edge to prev edge
private var hullNext = IntArray(count) // edge to next edge
private var hullTri = IntArray(count) // edge to adjacent triangle
private var hullHash = IntArray(hashSize) // angular edge hash
private var hullStart: Int = -1
// temporary arrays for sorting points
private var ids = IntArray(count)
private var dists = DoubleArray(count)
private var cx: Double = Double.NaN
private var cy: Double = Double.NaN
private var trianglesLen: Int = -1
lateinit var hull: IntArray
init {
update()
}
fun update() {
if (coords.size <= 2) {
halfedges = IntArray(0)
triangles = IntArray(0)
hull = IntArray(0)
return
}
// populate an array of point indices calculate input data bbox
var minX = Double.POSITIVE_INFINITY
var minY = Double.POSITIVE_INFINITY
var maxX = Double.NEGATIVE_INFINITY
var maxY = Double.NEGATIVE_INFINITY
// points -> points
// minX, minY, maxX, maxY
for (i in 0 until count) {
val x = coords[2 * i]
val y = coords[2 * i + 1]
if (x < minX) minX = x
if (y < minY) minY = y
if (x > maxX) maxX = x
if (y > maxY) maxY = y
ids[i] = i
}
val cx = (minX + maxX) / 2
val cy = (minY + maxY) / 2
var minDist = Double.POSITIVE_INFINITY
var i0: Int = -1
var i1: Int = -1
var i2: Int = -1
// pick a seed point close to the center
for (i in 0 until count) {
val d = dist(cx, cy, coords[2 * i], coords[2 * i + 1])
if (d < minDist) {
i0 = i
minDist = d
}
}
val i0x = coords[2 * i0]
val i0y = coords[2 * i0 + 1]
minDist = Double.POSITIVE_INFINITY
// Find the point closest to the seed
for(i in 0 until count) {
if (i == i0) continue
val d = dist(i0x, i0y, coords[2 * i], coords[2 * i + 1])
if (d < minDist && d > 0) {
i1 = i
minDist = d
}
}
var i1x = coords[2 * i1]
var i1y = coords[2 * i1 + 1]
var minRadius = Double.POSITIVE_INFINITY
// Find the third point which forms the smallest circumcircle with the first two
for (i in 0 until count) {
if(i == i0 || i == i1) continue
val r = circumradius(i0x, i0y, i1x, i1y, coords[2 * i], coords[2 * i + 1])
if(r < minRadius) {
i2 = i
minRadius = r
}
}
if (minRadius == Double.POSITIVE_INFINITY) {
// order collinear points by dx (or dy if all x are identical)
// and return the list as a hull
for (i in 0 until count) {
val a = (coords[2 * i] - coords[0])
val b = (coords[2 * i + 1] - coords[1])
dists[i] = if (a == 0.0) b else a
}
quicksort(ids, dists, 0, count - 1)
val nhull = IntArray(count)
var j = 0
var d0 = Double.NEGATIVE_INFINITY
for (i in 0 until count) {
val id = ids[i]
if (dists[id] > d0) {
nhull[j++] = id
d0 = dists[id]
}
}
hull = nhull.copyOf(j)
triangles = IntArray(0)
halfedges = IntArray(0)
return
}
var i2x = coords[2 * i2]
var i2y = coords[2 * i2 + 1]
// swap the order of the seed points for counter-clockwise orientation
if (orient2d(i0x, i0y, i1x, i1y, i2x, i2y) < 0.0) {
val i = i1
val x = i1x
val y = i1y
i1 = i2
i1x = i2x
i1y = i2y
i2 = i
i2x = x
i2y = y
}
val center = circumcenter(i0x, i0y, i1x, i1y, i2x, i2y)
this.cx = center[0]
this.cy = center[1]
for (i in 0 until count) {
dists[i] = dist(coords[2 * i], coords[2 * i + 1], center[0], center[1])
}
// sort the points by distance from the seed triangle circumcenter
quicksort(ids, dists, 0, count - 1)
// set up the seed triangle as the starting hull
hullStart = i0
var hullSize = 3
hullNext[i0] = i1
hullNext[i1] = i2
hullNext[i2] = i0
hullPrev[i2] = i1
hullPrev[i0] = i2
hullPrev[i1] = i0
hullTri[i0] = 0
hullTri[i1] = 1
hullTri[i2] = 2
hullHash.fill(-1)
hullHash[hashKey(i0x, i0y)] = i0
hullHash[hashKey(i1x, i1y)] = i1
hullHash[hashKey(i2x, i2y)] = i2
trianglesLen = 0
addTriangle(i0, i1, i2, -1, -1, -1)
var xp = 0.0
var yp = 0.0
for (k in ids.indices) {
val i = ids[k]
val x = coords[2 * i]
val y = coords[2 * i + 1]
// skip near-duplicate points
if (k > 0 && abs(x - xp) <= EPSILON && abs(y - yp) <= EPSILON) continue
xp = x
yp = y
// skip seed triangle points
if (i == i0 || i == i1 || i == i2) continue
// find a visible edge on the convex hull using edge hash
var start = 0
val key = hashKey(x, y)
for (j in 0 until hashSize) {
start = hullHash[(key + j) % hashSize]
if (start != -1 && start != hullNext[start]) break
}
start = hullPrev[start]
var e = start
var q = hullNext[e]
while (orient2d(x, y, coords[2 * e], coords[2 * e + 1], coords[2 * q], coords[2 * q + 1]) >= 0) {
e = q
if (e == start) {
e = -1
break
}
q = hullNext[e]
}
if (e == -1) continue // likely a near-duplicate point skip it
// add the first triangle from the point
var t = addTriangle(e, i, hullNext[e], -1, -1, hullTri[e])
// recursively flip triangles from the point until they satisfy the Delaunay condition
hullTri[i] = legalize(t + 2)
hullTri[e] = t // keep track of boundary triangles on the hull
hullSize++
// walk forward through the hull, adding more triangles and flipping recursively
var next = hullNext[e]
q = hullNext[next]
while (orient2d(x, y, coords[2 * next], coords[2 * next + 1], coords[2 * q], coords[2 * q + 1]) < 0) {
t = addTriangle(next, i, q, hullTri[i], -1, hullTri[next])
hullTri[i] = legalize(t + 2)
hullNext[next] = next // mark as removed
hullSize--
next = q
q = hullNext[next]
}
// walk backward from the other side, adding more triangles and flipping
if (e == start) {
q = hullPrev[e]
while (orient2d(x, y, coords[2 * q], coords[2 * q + 1], coords[2 * e], coords[2 * e + 1]) < 0) {
t = addTriangle(q, i, e, -1, hullTri[e], hullTri[q])
legalize(t + 2)
hullTri[q] = t
hullNext[e] = e // mark as removed
hullSize--
e = q
q = hullPrev[e]
}
}
// update the hull indices
hullStart = e
hullPrev[i] = e
hullNext[e] = i
hullPrev[next] = i
hullNext[i] = next
// save the two new edges in the hash table
hullHash[hashKey(x, y)] = i
hullHash[hashKey(coords[2 * e], coords[2 * e + 1])] = e
}
hull = IntArray(hullSize)
var e = hullStart
for (i in 0 until hullSize) {
hull[i] = e
e = hullNext[e]
}
// trim typed triangle mesh arrays
triangles = _triangles.copyOf(trianglesLen)
halfedges = _halfedges.copyOf(trianglesLen)
}
private fun legalize(a: Int): Int {
var i = 0
var na = a
var ar: Int
// recursion eliminated with a fixed-size stack
while (true) {
val b = _halfedges[na]
/* if the pair of triangles doesn't satisfy the Delaunay condition
* (p1 is inside the circumcircle of [p0, pl, pr]), flip them,
* then do the same check/flip recursively for the new pair of triangles
*
* pl pl
* /||\ / \
* al/ || \bl al/ \a
* / || \ / \
* / a||b \ flip /___ar___\
* p0\ || /p1 => p0\---bl---/p1
* \ || / \ /
* ar\ || /br b\ /br
* \||/ \ /
* pr pr
*/
val a0 = na - na % 3
ar = a0 + (na + 2) % 3
if (b == -1) { // convex hull edge
if (i == 0) break
na = EDGE_STACK[--i]
continue
}
val b0 = b - b % 3
val al = a0 + (na + 1) % 3
val bl = b0 + (b + 2) % 3
val p0 = _triangles[ar]
val pr = _triangles[na]
val pl = _triangles[al]
val p1 = _triangles[bl]
val illegal = inCircleRobust(
coords[2 * p0], coords[2 * p0 + 1],
coords[2 * pr], coords[2 * pr + 1],
coords[2 * pl], coords[2 * pl + 1],
coords[2 * p1], coords[2 * p1 + 1])
if (illegal) {
_triangles[na] = p1
_triangles[b] = p0
val hbl = _halfedges[bl]
// edge swapped on the other side of the hull (rare) fix the halfedge reference
if (hbl == -1) {
var e = hullStart
do {
if (hullTri[e] == bl) {
hullTri[e] = na
break
}
e = hullPrev[e]
} while (e != hullStart)
}
link(na, hbl)
link(b, _halfedges[ar])
link(ar, bl)
val br = b0 + (b + 1) % 3
// don't worry about hitting the cap: it can only happen on extremely degenerate input
if (i < EDGE_STACK.size) {
EDGE_STACK[i++] = br
}
} else {
if (i == 0) break
na = EDGE_STACK[--i]
}
}
return ar
}
private fun link(a:Int, b:Int) {
_halfedges[a] = b
if (b != -1) _halfedges[b] = a
}
// add a new triangle given vertex indices and adjacent half-edge ids
private fun addTriangle(i0: Int, i1: Int, i2: Int, a: Int, b: Int, c: Int): Int {
val t = trianglesLen
_triangles[t] = i0
_triangles[t + 1] = i1
_triangles[t + 2] = i2
link(t, a)
link(t + 1, b)
link(t + 2, c)
trianglesLen += 3
return t
}
private fun hashKey(x: Double, y: Double): Int {
return (floor(pseudoAngle(x - cx, y - cy) * hashSize) % hashSize).toInt()
}
}
fun circumradius(ax: Double, ay: Double,
bx: Double, by: Double,
cx: Double, cy: Double): Double {
val dx = bx - ax
val dy = by - ay
val ex = cx - ax
val ey = cy - ay
val bl = dx * dx + dy * dy
val cl = ex * ex + ey * ey
val d = 0.5 / (dx * ey - dy * ex)
val x = (ey * bl - dy * cl) * d
val y = (dx * cl - ex * bl) * d
return x * x + y * y
}
fun circumcenter(ax: Double, ay: Double,
bx: Double, by: Double,
cx: Double, cy: Double): DoubleArray {
val dx = bx - ax
val dy = by - ay
val ex = cx - ax
val ey = cy - ay
val bl = dx * dx + dy * dy
val cl = ex * ex + ey * ey
val d = 0.5 / (dx * ey - dy * ex)
val x = ax + (ey * bl - dy * cl) * d
val y = ay + (dx * cl - ex * bl) * d
return doubleArrayOf(x, y)
}
fun quicksort(ids: IntArray, dists: DoubleArray, left: Int, right: Int) {
if (right - left <= 20) {
for (i in (left + 1)..right) {
val temp = ids[i]
val tempDist = dists[temp]
var j = i - 1
while (j >= left && dists[ids[j]] > tempDist) ids[j + 1] = ids[j--]
ids[j + 1] = temp
}
} else {
val median = (left + right) shr 1
var i = left + 1
var j = right
swap(ids, median, i)
if (dists[ids[left]] > dists[ids[right]]) swap(ids, left, right)
if (dists[ids[i]] > dists[ids[right]]) swap(ids, i, right)
if (dists[ids[left]] > dists[ids[i]]) swap(ids, left, i)
val temp = ids[i]
val tempDist = dists[temp]
while (true) {
do i++ while (dists[ids[i]] < tempDist)
do j-- while (dists[ids[j]] > tempDist)
if (j < i) break
swap(ids, i, j)
}
ids[left + 1] = ids[j]
ids[j] = temp
if (right - i + 1 >= j - left) {
quicksort(ids, dists, i, right)
quicksort(ids, dists, left, j - 1)
} else {
quicksort(ids, dists, left, j - 1)
quicksort(ids, dists, i, right)
}
}
}
private fun swap(arr: IntArray, i: Int, j: Int) {
val tmp = arr[i]
arr[i] = arr[j]
arr[j] = tmp
}
// monotonically increases with real angle, but doesn't need expensive trigonometry
private fun pseudoAngle(dx: Double, dy: Double): Double {
val p = dx / (abs(dx) + abs(dy))
val a = if (dy > 0.0) 3.0 - p else 1.0 + p
return a / 4.0 // [0..1]
}
private fun inCircle(ax: Double, ay: Double,
bx: Double, by: Double,
cx: Double, cy: Double,
px: Double, py: Double): Boolean {
val dx = ax - px
val dy = ay - py
val ex = bx - px
val ey = by - py
val fx = cx - px
val fy = cy - py
val ap = dx * dx + dy * dy
val bp = ex * ex + ey * ey
val cp = fx * fx + fy * fy
return dx * (ey * cp - bp * fy) -
dy * (ex * cp - bp * fx) +
ap * (ex * fy - ey * fx) < 0
}
private fun inCircleRobust(
ax: Double, ay: Double,
bx: Double, by: Double,
cx: Double, cy: Double,
px: Double, py: Double
): Boolean {
val dx = twoDiff(ax, px)
val dy = twoDiff(ay, py)
val ex = twoDiff(bx, px)
val ey = twoDiff(by, py)
val fx = twoDiff(cx, px)
val fy = twoDiff(cy, py)
val ap = ddAddDd(ddMultDd(dx, dx), ddMultDd(dy, dy))
val bp = ddAddDd(ddMultDd(ex, ex), ddMultDd(ey, ey))
val cp = ddAddDd(ddMultDd(fx, fx), ddMultDd(fy, fy))
val dd = ddAddDd(
ddDiffDd(
ddMultDd(dx, ddDiffDd(ddMultDd(ey, cp), ddMultDd(bp, fy))),
ddMultDd(dy, ddDiffDd(ddMultDd(ex, cp), ddMultDd(bp, fx)))
),
ddMultDd(ap, ddDiffDd(ddMultDd(ex, fy), ddMultDd(ey, fx)))
)
return (dd[1]) <= 0
}
private fun dist(ax: Double, ay: Double, bx: Double, by: Double): Double {
//val dx = ax - bx
//val dy = ay - by
//return dx * dx + dy * dy
// double-double implementation but I think it is overkill.
val dx = twoDiff(ax, bx)
val dy = twoDiff(ay, by)
val dx2 = ddMultDd(dx, dx)
val dy2 = ddMultDd(dy, dy)
val d2 = ddAddDd(dx2, dy2)
return d2[0] + d2[1]
}

View File

@@ -0,0 +1,225 @@
package com.icegps.triangulation
import com.icegps.math.geometry.Vector2D
import com.icegps.triangulation.Delaunay.Companion.from
import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.sin
/*
ISC License
Copyright 2021 Ricardo Matias.
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
*/
/**
* Use [from] static method to use the delaunay triangulation
*
* @description Port of d3-delaunay (JavaScript) library - https://github.com/d3/d3-delaunay
* @property points flat positions' array - [x0, y0, x1, y1..]
*
* @since 9258fa3 - commit
* @author Ricardo Matias
*/
@Suppress("unused")
class Delaunay(val points: DoubleArray) {
companion object {
/**
* Entry point for the delaunay triangulation
*
* @property points a list of 2D points
*/
fun from(points: List<Vector2D>): Delaunay {
val n = points.size
val coords = DoubleArray(n * 2)
for (i in points.indices) {
val p = points[i]
coords[2 * i] = p.x
coords[2 * i + 1] = p.y
}
return Delaunay(coords)
}
}
private var delaunator: Delaunator = Delaunator(points)
val inedges = IntArray(points.size / 2)
private val hullIndex = IntArray(points.size / 2)
var halfedges: IntArray = delaunator.halfedges
var hull: IntArray = delaunator.hull
var triangles: IntArray = delaunator.triangles
init {
init()
}
fun update() {
delaunator.update()
init()
}
fun neighbors(i: Int) = sequence<Int> {
val e0 = inedges[i]
if (e0 != -1) {
var e = e0
var p0 = -1
loop@ do {
p0 = triangles[e]
yield(p0)
e = if (e % 3 == 2) e - 2 else e + 1
if (e == -1) {
break@loop
}
if (triangles[e] != i) {
break@loop
//error("bad triangulation")
}
e = halfedges[e]
if (e == -1) {
val p = hull[(hullIndex[i] + 1) % hull.size]
if (p != p0) {
yield(p)
}
break@loop
}
} while (e != e0)
}
}
fun collinear(): Boolean {
for (i in 0 until triangles.size step 3) {
val a = 2 * triangles[i]
val b = 2 * triangles[i + 1]
val c = 2 * triangles[i + 2]
val coords = points
val cross = (coords[c] - coords[a]) * (coords[b + 1] - coords[a + 1])
-(coords[b] - coords[a]) * (coords[c + 1] - coords[a + 1])
if (cross > 1e-10) return false;
}
return true
}
private fun jitter(x: Double, y: Double, r: Double): DoubleArray {
return doubleArrayOf(x + sin(x + y) * r, y + cos(x - y) * r)
}
fun init() {
if (hull.size > 2 && collinear()) {
println("warning: triangulation is collinear")
val r = 1E-8
for (i in 0 until points.size step 2) {
val p = jitter(points[i], points[i + 1], r)
points[i] = p[0]
points[i + 1] = p[1]
}
delaunator = Delaunator(points)
halfedges = delaunator.halfedges
hull = delaunator.hull
triangles = delaunator.triangles
}
inedges.fill(-1)
hullIndex.fill(-1)
// Compute an index from each point to an (arbitrary) incoming halfedge
// Used to give the first neighbor of each point for this reason,
// on the hull we give priority to exterior halfedges
for (e in halfedges.indices) {
val p = triangles[nextHalfedge(e)]
if (halfedges[e] == -1 || inedges[p] == -1) inedges[p] = e
}
for (i in hull.indices) {
hullIndex[hull[i]] = i
}
// degenerate case: 1 or 2 (distinct) points
if (hull.size in 1..2) {
triangles = IntArray(3) { -1 }
halfedges = IntArray(3) { -1 }
triangles[0] = hull[0]
inedges[hull[0]] = 1
if (hull.size == 2) {
inedges[hull[1]] = 0
triangles[1] = hull[1]
triangles[2] = hull[1]
}
}
}
fun find(x: Double, y: Double, i: Int = 0): Int {
var i1 = i
var c = step(i, x, y)
while (c >= 0 && c != i && c != i1) {
i1 = c
c = step(i1, x, y)
}
return c
}
fun nextHalfedge(e: Int) = if (e % 3 == 2) e - 2 else e + 1
fun prevHalfedge(e: Int) = if (e % 3 == 0) e + 2 else e - 1
fun step(i: Int, x: Double, y: Double): Int {
if (inedges[i] == -1 || points.isEmpty()) return (i + 1) % (points.size shr 1)
var c = i
var dc = (x - points[i * 2]).pow(2) + (y - points[i * 2 + 1]).pow(2)
val e0 = inedges[i]
var e = e0
do {
val t = triangles[e]
val dt = (x - points[t * 2]).pow(2) + (y - points[t * 2 + 1]).pow(2)
if (dt < dc) {
dc = dt
c = t
}
e = if (e % 3 == 2) e - 2 else e + 1
if (triangles[e] != i) {
//error("bad triangulation")
break
} // bad triangulation
e = halfedges[e]
if (e == -1) {
e = hull[(hullIndex[i] + 1) % hull.size]
if (e != t) {
if ((x - points[e * 2]).pow(2) + (y - points[e * 2 + 1]).pow(2) < dc) return e
}
break
}
} while (e != e0)
return c
}
}

View File

@@ -0,0 +1,69 @@
package com.icegps.triangulation
import com.icegps.math.geometry.Vector3D
import com.icegps.math.geometry.toVector2D
/**
* Kotlin/OPENRNDR idiomatic interface to `Delaunay`
*/
class DelaunayTriangulation(val points: List<Vector3D>) {
val delaunay: Delaunay = Delaunay.from(points.map { it.toVector2D() })
fun neighbors(pointIndex: Int): Sequence<Int> {
return delaunay.neighbors(pointIndex)
}
fun neighborPoints(pointIndex: Int): List<Vector3D> {
return neighbors(pointIndex).map { points[it] }.toList()
}
fun triangleIndices(): List<IntArray> {
val list = mutableListOf<IntArray>()
for (i in delaunay.triangles.indices step 3) {
list.add(
intArrayOf(
delaunay.triangles[i],
delaunay.triangles[i + 1],
delaunay.triangles[i + 2]
)
)
}
return list
}
fun triangles(filterPredicate: (Int, Int, Int) -> Boolean = { _, _, _ -> true }): List<Triangle> {
val list = mutableListOf<Triangle>()
for (i in delaunay.triangles.indices step 3) {
val t0 = delaunay.triangles[i]
val t1 = delaunay.triangles[i + 1]
val t2 = delaunay.triangles[i + 2]
// originally they are defined *counterclockwise*
if (filterPredicate(t2, t1, t0)) {
val p1 = points[t0]
val p2 = points[t1]
val p3 = points[t2]
list.add(Triangle(p3, p2, p1))
}
}
return list
}
fun nearest(query: Vector3D): Int = delaunay.find(query.x, query.y)
fun nearestPoint(query: Vector3D): Vector3D = points[nearest(query)]
}
/**
* Computes the Delaunay triangulation for the list of 2D points.
*
* The Delaunay triangulation is a triangulation of a set of points such that
* no point is inside the circumcircle of any triangle. It maximizes the minimum
* angle of all the angles in the triangles, avoiding skinny triangles.
*
* @return A DelaunayTriangulation object representing the triangulation of the given points.
*/
fun List<Vector3D>.delaunayTriangulation(): DelaunayTriangulation {
return DelaunayTriangulation(this)
}

View File

@@ -0,0 +1,340 @@
package com.icegps.triangulation
import kotlin.math.pow
// original code: https://github.com/FlorisSteenkamp/double-double/
/**
* Returns the difference and exact error of subtracting two floating point
* numbers.
* Uses an EFT (error-free transformation), i.e. `a-b === x+y` exactly.
* The returned result is a non-overlapping expansion (smallest value first!).
*
* * **precondition:** `abs(a) >= abs(b)` - A fast test that can be used is
* `(a > b) === (a > -b)`
*
* See https://people.eecs.berkeley.edu/~jrs/papers/robustr.pdf
*/
fun fastTwoDiff(a: Double, b: Double): DoubleArray {
val x = a - b;
val y = (a - x) - b;
return doubleArrayOf(y, x)
}
/**
* Returns the sum and exact error of adding two floating point numbers.
* Uses an EFT (error-free transformation), i.e. a+b === x+y exactly.
* The returned sum is a non-overlapping expansion (smallest value first!).
*
* Precondition: abs(a) >= abs(b) - A fast test that can be used is
* (a > b) === (a > -b)
*
* See https://people.eecs.berkeley.edu/~jrs/papers/robustr.pdf
*/
fun fastTwoSum(a: Double, b: Double): DoubleArray {
val x = a + b;
return doubleArrayOf(b - (x - a), x)
}
/**
* Truncates a floating point value's significand and returns the result.
* Similar to split, but with the ability to specify the number of bits to keep.
*
* **Theorem 17 (Veltkamp-Dekker)**: Let a be a p-bit floating-point number, where
* p >= 3. Choose a splitting point s such that p/2 <= s <= p-1. Then the
* following algorithm will produce a (p-s)-bit value a_hi and a
* nonoverlapping (s-1)-bit value a_lo such that abs(a_hi) >= abs(a_lo) and
* a = a_hi + a_lo.
*
* * see [Shewchuk](https://people.eecs.berkeley.edu/~jrs/papers/robustr.pdf)
*
* @param a a double
* @param bits the number of significand bits to leave intact
*/
fun reduceSignificand(
a: Double,
bits: Int
): Double {
val s = 53 - bits;
val f = 2.0.pow(s) + 1;
val c = f * a;
val r = c - (c - a);
return r;
}
/**
* === 2^Math.ceil(p/2) + 1 where p is the # of significand bits in a double === 53.
* @internal
*/
const val f = 134217729; // 2**27 + 1;
/**
* Returns the result of splitting a double into 2 26-bit doubles.
*
* Theorem 17 (Veltkamp-Dekker): Let a be a p-bit floating-point number, where
* p >= 3. Choose a splitting point s such that p/2 <= s <= p-1. Then the
* following algorithm will produce a (p-s)-bit value a_hi and a
* nonoverlapping (s-1)-bit value a_lo such that abs(a_hi) >= abs(a_lo) and
* a = a_hi + a_lo.
*
* see e.g. [Shewchuk](https://people.eecs.berkeley.edu/~jrs/papers/robustr.pdf)
* @param a A double floating point number
*/
fun split(a: Double): DoubleArray {
val c = f * a;
val a_h = c - (c - a);
val a_l = a - a_h;
return doubleArrayOf(a_h, a_l)
}
/**
* Returns the exact result of subtracting b from a.
*
* @param a minuend - a double-double precision floating point number
* @param b subtrahend - a double-double precision floating point number
*/
fun twoDiff(a: Double, b: Double): DoubleArray {
val x = a - b;
val bvirt = a - x;
val y = (a - (x + bvirt)) + (bvirt - b);
return doubleArrayOf(y, x)
}
/**
* Returns the exact result of multiplying two doubles.
*
* * the resulting array is the reverse of the standard twoSum in the literature.
*
* Theorem 18 (Shewchuk): Let a and b be p-bit floating-point numbers, where
* p >= 6. Then the following algorithm will produce a nonoverlapping expansion
* x + y such that ab = x + y, where x is an approximation to ab and y
* represents the roundoff error in the calculation of x. Furthermore, if
* round-to-even tiebreaking is used, x and y are non-adjacent.
*
* See https://people.eecs.berkeley.edu/~jrs/papers/robustr.pdf
* @param a A double
* @param b Another double
*/
fun twoProduct(a: Double, b: Double): DoubleArray {
val x = a * b;
//const [ah, al] = split(a);
val c = f * a;
val ah = c - (c - a);
val al = a - ah;
//const [bh, bl] = split(b);
val d = f * b;
val bh = d - (d - b);
val bl = b - bh;
val y = (al * bl) - ((x - (ah * bh)) - (al * bh) - (ah * bl));
//const err1 = x - (ah * bh);
//const err2 = err1 - (al * bh);
//const err3 = err2 - (ah * bl);
//const y = (al * bl) - err3;
return doubleArrayOf(y, x)
}
fun twoSquare(a: Double): DoubleArray {
val x = a * a;
//const [ah, al] = split(a);
val c = f * a;
val ah = c - (c - a);
val al = a - ah;
val y = (al * al) - ((x - (ah * ah)) - 2 * (ah * al));
return doubleArrayOf(y, x)
}
/**
* Returns the exact result of adding two doubles.
*
* * the resulting array is the reverse of the standard twoSum in the literature.
*
* Theorem 7 (Knuth): Let a and b be p-bit floating-point numbers. Then the
* following algorithm will produce a nonoverlapping expansion x + y such that
* a + b = x + y, where x is an approximation to a + b and y is the roundoff
* error in the calculation of x.
*
* See https://people.eecs.berkeley.edu/~jrs/papers/robustr.pdf
*/
fun twoSum(a: Double, b: Double): DoubleArray {
val x = a + b;
val bv = x - a;
return doubleArrayOf((a - (x - bv)) + (b - bv), x)
}
/**
* Returns the result of subtracting the second given double-double-precision
* floating point number from the first.
*
* * relative error bound: 3u^2 + 13u^3, i.e. fl(a-b) = (a-b)(1+ϵ),
* where ϵ <= 3u^2 + 13u^3, u = 0.5 * Number.EPSILON
* * the error bound is not sharp - the worst case that could be found by the
* authors were 2.25u^2
*
* ALGORITHM 6 of https://hal.archives-ouvertes.fr/hal-01351529v3/document
* @param x a double-double precision floating point number
* @param y another double-double precision floating point number
*/
fun ddDiffDd(x: DoubleArray, y: DoubleArray): DoubleArray {
val xl = x[0];
val xh = x[1];
val yl = y[0];
val yh = y[1];
//const [sl,sh] = twoSum(xh,yh);
val sh = xh - yh;
val _1 = sh - xh;
val sl = (xh - (sh - _1)) + (-yh - _1);
//const [tl,th] = twoSum(xl,yl);
val th = xl - yl;
val _2 = th - xl;
val tl = (xl - (th - _2)) + (-yl - _2);
val c = sl + th;
//const [vl,vh] = fastTwoSum(sh,c)
val vh = sh + c;
val vl = c - (vh - sh);
val w = tl + vl
//const [zl,zh] = fastTwoSum(vh,w)
val zh = vh + w;
val zl = w - (zh - vh);
return doubleArrayOf(zl, zh)
}
/**
* Returns the product of two double-double-precision floating point numbers.
*
* * relative error bound: 7u^2, i.e. fl(a+b) = (a+b)(1+ϵ),
* where ϵ <= 7u^2, u = 0.5 * Number.EPSILON
* the error bound is not sharp - the worst case that could be found by the
* authors were 5u^2
*
* * ALGORITHM 10 of https://hal.archives-ouvertes.fr/hal-01351529v3/document
* @param x a double-double precision floating point number
* @param y another double-double precision floating point number
*/
fun ddMultDd(x: DoubleArray, y: DoubleArray): DoubleArray {
//const xl = x[0];
val xh = x[1];
//const yl = y[0];
val yh = y[1];
//const [cl1,ch] = twoProduct(xh,yh);
val ch = xh * yh;
val c = f * xh;
val ah = c - (c - xh);
val al = xh - ah;
val d = f * yh;
val bh = d - (d - yh);
val bl = yh - bh;
val cl1 = (al * bl) - ((ch - (ah * bh)) - (al * bh) - (ah * bl));
//return fastTwoSum(ch,cl1 + (xh*yl + xl*yh));
val b = cl1 + (xh * y[0] + x[0] * yh);
val xx = ch + b;
return doubleArrayOf(b - (xx - ch), xx)
}
/**
* Returns the result of adding two double-double-precision floating point
* numbers.
*
* * relative error bound: 3u^2 + 13u^3, i.e. fl(a+b) = (a+b)(1+ϵ),
* where ϵ <= 3u^2 + 13u^3, u = 0.5 * Number.EPSILON
* * the error bound is not sharp - the worst case that could be found by the
* authors were 2.25u^2
*
* ALGORITHM 6 of https://hal.archives-ouvertes.fr/hal-01351529v3/document
* @param x a double-double precision floating point number
* @param y another double-double precision floating point number
*/
fun ddAddDd(x: DoubleArray, y: DoubleArray): DoubleArray {
val xl = x[0];
val xh = x[1];
val yl = y[0];
val yh = y[1];
//const [sl,sh] = twoSum(xh,yh);
val sh = xh + yh;
val _1 = sh - xh;
val sl = (xh - (sh - _1)) + (yh - _1);
//val [tl,th] = twoSum(xl,yl);
val th = xl + yl;
val _2 = th - xl;
val tl = (xl - (th - _2)) + (yl - _2);
val c = sl + th;
//val [vl,vh] = fastTwoSum(sh,c)
val vh = sh + c;
val vl = c - (vh - sh);
val w = tl + vl
//val [zl,zh] = fastTwoSum(vh,w)
val zh = vh + w;
val zl = w - (zh - vh);
return doubleArrayOf(zl, zh)
}
/**
* Returns the product of a double-double-precision floating point number and a
* double.
*
* * slower than ALGORITHM 8 (one call to fastTwoSum more) but about 2x more
* accurate
* * relative error bound: 1.5u^2 + 4u^3, i.e. fl(a+b) = (a+b)(1+ϵ),
* where ϵ <= 1.5u^2 + 4u^3, u = 0.5 * Number.EPSILON
* * the bound is very sharp
* * probably prefer `ddMultDouble2` due to extra speed
*
* * ALGORITHM 7 of https://hal.archives-ouvertes.fr/hal-01351529v3/document
* @param y a double
* @param x a double-double precision floating point number
*/
fun ddMultDouble1(y: Double, x: DoubleArray): DoubleArray {
val xl = x[0];
val xh = x[1];
//val [cl1,ch] = twoProduct(xh,y);
val ch = xh * y;
val c = f * xh;
val ah = c - (c - xh);
val al = xh - ah;
val d = f * y;
val bh = d - (d - y);
val bl = y - bh;
val cl1 = (al * bl) - ((ch - (ah * bh)) - (al * bh) - (ah * bl));
val cl2 = xl * y;
//val [tl1,th] = fastTwoSum(ch,cl2);
val th = ch + cl2;
val tl1 = cl2 - (th - ch);
val tl2 = tl1 + cl1;
//val [zl,zh] = fastTwoSum(th,tl2);
val zh = th + tl2;
val zl = tl2 - (zh - th);
return doubleArrayOf(zl, zh);
}

View File

@@ -0,0 +1,19 @@
package com.icegps.triangulation
fun orient2d(bx: Double, by: Double, ax: Double, ay: Double, cx: Double, cy: Double): Double {
// (ax,ay) (bx,by) are swapped such that the sign of the determinant is flipped. which is what Delaunator.kt expects.
/*
| a b | = | ax - cx ay - cy |
| c d | | bx - cx by - cy |
*/
val a = twoDiff(ax, cx)
val b = twoDiff(ay, cy)
val c = twoDiff(bx, cx)
val d = twoDiff(by, cy)
val determinant = ddDiffDd(ddMultDd(a, d), ddMultDd(b, c))
return determinant[1]
}

View File

@@ -0,0 +1,13 @@
package com.icegps.triangulation
import com.icegps.math.geometry.Vector3D
/**
* @author tabidachinokaze
* @date 2025/11/26
*/
data class Triangle(
val x1: Vector3D,
val x2: Vector3D,
val x3: Vector3D,
)