extends Reference

class_name Delaunator

const EPSILON = pow(2, -52)
const EDGE_STACK = []

var coords = [] # PoolRealArray.
var halfedges = [] # PoolIntArray.
var hull = [] # PoolIntArray.
var triangles = [] # PoolIntArray.
var triangles_len = 0
var _cx
var _cy
var _dists = [] # PoolRealArray.
var _halfedges = [] # This array should be a PoolIntArray but we need to use the .slice() function on it.
var _hash_size
var _hull_hash = [] # PoolIntArray.
var _hull_next = [] # PoolIntArray.
var _hull_prev = [] # PoolIntArray.
var _hull_start
var _hull_tri = [] # PoolIntArray.
var _ids = [] # PoolIntArray.
var _triangles = []  # This array should be a PoolIntArray but we need to use the .slice() function on it.

func _init(points):
	if points.size() < 3:
		push_error(ProjectSettings.get_setting("application/config/name") + " needs at least 3 points.")
		return

	EDGE_STACK.resize(512)

	var n = points.size()

	coords.resize(n * 2)

	for i in n:
		var point = points[i]
		coords[2 * i] = point.x
		coords[2 * i + 1] = point.z

	_constructor()
	
func _constructor():
	var n = coords.size() >> 1

	# Arrays that will store the triangulation graph.
	var max_triangles = max(2 * n - 5, 0)
	_triangles.resize(max_triangles * 3)
	_halfedges.resize(max_triangles * 3)

	# Temporary arrays for tracking the edges of the advancing convex hull.
	_hash_size = ceil(sqrt(n))
	_hull_prev.resize(n) # Edge to prev edge.
	_hull_next.resize(n) # Edge to next edge.
	_hull_tri.resize(n) # Edge to adjacent triangle.

	_hull_hash.resize(_hash_size)
	for i in _hash_size:
		_hull_hash[i] = -1 # angular edge hash

	# Temporary arrays for sorting points.
	_ids.resize(n)
	_dists.resize(n)

	update()


func update():
	var n = coords.size() >> 1
	Global.loadings["world_creation"].new_phase("Triangulation des points...", _ids.size())

	# Populate an array of point indices; calculate input data bbox.
	var min_x = INF
	var min_y = INF
	var max_x = -INF
	var max_y = -INF

	for i in n:
		var x = coords[2 * i]
		var y = coords[2 * i + 1]
		if x < min_x: min_x = x
		if y < min_y: min_y = y
		if x > max_x: max_x = x
		if y > max_y: max_y = y
		_ids[i] = i

	var cx = (min_x + max_x) / 2
	var cy = (min_y + max_y) / 2

	var min_dist = INF
	var i0 = 0
	var i1 = 0
	var i2 = 0

	# Pick a seed point close to the center.
	for i in n:
		var d = dist(cx, cy, coords[2 * i], coords[2 * i + 1])
		if (d < min_dist):
			i0 = i
			min_dist = d
	var i0x = coords[2 * i0]
	var i0y = coords[2 * i0 + 1]

	min_dist = INF

	# Find the point closest to the seed.
	for i in n:
		if i == i0: continue
		var d = dist(i0x, i0y, coords[2 * i], coords[2 * i + 1])
		if (d < min_dist and d > 0):
			i1 = i
			min_dist = d
	var i1x = coords[2 * i1]
	var i1y = coords[2 * i1 + 1]

	var min_radius = INF

	# Find the third point which forms the smallest circumcircle with the first two.
	for i in n:
		if i == i0 or i == i1: continue
		var r = circumradius(i0x, i0y, i1x, i1y, coords[2 * i], coords[2 * i + 1])
		if r < min_radius:
			i2 = i
			min_radius = r
	var i2x = coords[2 * i2]
	var i2y = coords[2 * i2 + 1]

	if min_radius == INF:
		# Order collinear points by dx (or dy if all x are identical)
		# and return the list as a hull.
		for i in n:
			var _dist_temp

			if coords[2 * i] - coords[0]:
				_dist_temp = coords[2 * i] - coords[0]
			elif coords[2 * i + 1] - coords[1]:
				_dist_temp = coords[2 * i + 1] - coords[1]
			else:
				_dist_temp = 0

			_dists[i] = _dist_temp

		quicksort(_ids, _dists, 0, n - 1)
		var hull = []
		hull.resize(n)
		var j = 0
		var d0 = -INF

		for i in n:
			var id = _ids[i]
			if _dists[id] > d0:
				hull[j] = id
				j += 1
				d0 = _dists[id]
		hull = hull.slice(0, j - 1)
		triangles = []
		halfedges = []

		return

	# Swap the order of the seed points for counter-clockwise orientation.
	if orient(i0x, i0y, i1x, i1y, i2x, i2y):
		var i = i1
		var x = i1x
		var y = i1y
		i1 = i2
		i1x = i2x
		i1y = i2y
		i2 = i
		i2x = x
		i2y = y

	var center = circumcenter(i0x, i0y, i1x, i1y, i2x, i2y)
	_cx = center[0]
	_cy = center[1]

	for i in n:
		_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, n - 1)

	# Set up the seed triangle as the starting hull.
	_hull_start = i0
	var hull_size = 3

	_hull_next[i0] = i1
	_hull_prev[i2] = i1
	_hull_next[i1] = i2
	_hull_prev[i0] = i2
	_hull_next[i2] = i0
	_hull_prev[i1] = i0

	_hull_tri[i0] = 0
	_hull_tri[i1] = 1
	_hull_tri[i2] = 2

	for i in _hull_hash.size():
		_hull_hash[i] = -1
	_hull_hash[_hash_key(i0x, i0y)] = i0
	_hull_hash[_hash_key(i1x, i1y)] = i1
	_hull_hash[_hash_key(i2x, i2y)] = i2

#	triangles_len = 0
	_add_triangle(i0, i1, i2, -1, -1, -1)

	var xp = 0
	var yp = 0

	for k in _ids.size():
		var i = _ids[k]
		var x = coords[2 * i]
		var y = coords[2 * i + 1]

		# Skip near-duplicate points.
		if k > 0 and abs(x - xp) <= EPSILON and abs(y - yp) <= EPSILON: continue

		xp = x
		yp = y

		# Skip seed triangle points.
		if i == i0 or i == i1 or i == i2: continue

		# Find a visible edge on the convex hull using edge hash.
		var start = 0
		var key = _hash_key(x, y)

		for j in _hash_size:
			start = _hull_hash[fmod((key + j), _hash_size)]
			if (start != -1 and start != _hull_next[start]): break

		start = _hull_prev[start]
		var e = start

		while true:
			var q = _hull_next[e]
			if orient(x, y, coords[2 * e], coords[2 * e + 1], coords[2 * q], coords[2 * q + 1]): break
			e = q
			
			if (e == start):
				e = -1
				break

		if (e == -1): continue # Likely a near-duplicate point; Skip it.

		# Add the first triangle from the point.
		var t = _add_triangle(e, i, _hull_next[e], -1, -1, _hull_tri[e])
		# Recursively flip triangles from the point until they satisfy the Delaunay condition.
		_hull_tri[i] = _legalize(t + 2)
		_hull_tri[e] = t # Keep track of boundary triangles on the hull.
		hull_size += 1

		# Walk forward through the hull, adding more triangles and flipping recursively.
		n = _hull_next[e]

		while true:
			var q = _hull_next[n]
			if not orient(x, y, coords[2 * n], coords[2 * n + 1], coords[2 * q], coords[2 * q + 1]): break
			t = _add_triangle(n, i, q, _hull_tri[i], -1, _hull_tri[n])
			_hull_tri[i] = _legalize(t + 2)
			_hull_next[n] = n # Mark as removed.
			hull_size -= 1
			n = q

		# Walk backward from the other side, adding more triangles and flipping.
		if (e == start):
			while true:
				var q = _hull_prev[e]
				if not orient(x, y, coords[2 * q], coords[2 * q + 1], coords[2 * e], coords[2 * e + 1]): break
				t = _add_triangle(q, i, e, -1, _hull_tri[e], _hull_tri[q])
				_legalize(t + 2)
				_hull_tri[q] = t
				_hull_next[e] = e # Mark as removed.
				hull_size -= 1
				e = q

		# Update the hull indices.
		_hull_start = e
		_hull_prev[i] = e
		_hull_next[e] = i
		_hull_prev[n] = i
		_hull_next[i] = n

		# Save the two new edges in the hash table.
		_hull_hash[_hash_key(x, y)] = i
		_hull_hash[_hash_key(coords[2 * e], coords[2 * e + 1])] = e
		Global.loadings["world_creation"].increment_step()

	hull.resize(hull_size)
	var e = _hull_start
	for i in hull_size:
		hull[i] = e
		e = _hull_next[e]

	# Trim typed triangle mesh arrays.
	triangles = _triangles.slice(0, triangles_len - 1)
	halfedges = _halfedges.slice(0, triangles_len - 1)


func _hash_key(x, y):
	return fmod(floor(pseudo_angle(x - _cx, y - _cy) * _hash_size), _hash_size)


func _legalize(a):
	var i = 0
	var ar = 0

	# Recursion eliminated with a fixed-size stack.
	while true:
		var b = _halfedges[a]

#		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

		var a0 = a - a % 3
		ar = a0 + (a + 2) % 3

		if b == -1: # Convex hull edge.
			if i == 0: break
			i -= 1
			a = EDGE_STACK[i]
			continue

		var b0 = b - b % 3
		var al = a0 + (a + 1) % 3
		var bl = b0 + (b + 2) % 3

		var p0 = _triangles[ar]
		var pr = _triangles[a]
		var pl = _triangles[al]
		var p1 = _triangles[bl]

		var illegal = in_circle(
			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[a] = p1
			_triangles[b] = p0

			var hbl = _halfedges[bl]

			# Edge swapped on the other side of the hull (rare); Fix the halfedge reference.
			if (hbl == -1):
				var e = _hull_start
				while true:
					if _hull_tri[e] == bl:
						_hull_tri[e] = a
						break

					e = _hull_prev[e]
					if e == _hull_start: break

			_link(a, hbl)
			_link(b, _halfedges[ar])
			_link(ar, bl)

			var 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
				i += 1
		else:
			if i == 0: break
			i -= 1
			a = EDGE_STACK[i]

	return ar


func _link(a, b):
	_halfedges[a] = b
	if (b != -1):
		_halfedges[b] = a


# Add a new triangle given vertex indices and adjacent half-edge ids.
func _add_triangle(i0, i1, i2, a, b, c):
	var t = triangles_len

	_triangles[t] = i0
	_triangles[t + 1] = i1
	_triangles[t + 2] = i2

	_link(t, a)
	_link(t + 1, b)
	_link(t + 2, c)

	triangles_len += 3

	return t


# Monotonically increases with real angle, but doesn't need expensive trigonometry.
func pseudo_angle(dx, dy):
	var p = dx / (abs(dx) + abs(dy))

	if (dy > 0):
		return (3 - p) / 4 # [0..1]
	else:
		return (1 + p) / 4 # [0..1]


func dist(ax, ay, bx, by):
	var dx = ax - bx
	var dy = ay - by
	return dx * dx + dy * dy


# Return 2d orientation sign if we're confident in it through J. Shewchuk's error bound check.
func orient_if_sure(px, py, rx, ry, qx, qy):
	var l = (ry - py) * (qx - px)
	var r = (rx - px) * (qy - py)

	if (abs(l - r) >= 0.00000000000000033306690738754716 * abs(l + r)):
		return l - r
	else:
		return 0


# A more robust orientation test that's stable in a given triangle (to fix robustness issues).
func orient(rx, ry, qx, qy, px, py):
	var _sign

	if orient_if_sure(px, py, rx, ry, qx, qy):
		_sign = orient_if_sure(px, py, rx, ry, qx, qy)
	elif orient_if_sure(rx, ry, qx, qy, px, py):
		_sign = orient_if_sure(rx, ry, qx, qy, px, py)
	elif orient_if_sure(qx, qy, px, py, rx, ry):
		_sign = orient_if_sure(qx, qy, px, py, rx, ry)

	return false if _sign == null else _sign < 0


func in_circle(ax, ay, bx, by, cx, cy, px, py):
	var dx = ax - px
	var dy = ay - py
	var ex = bx - px
	var ey = by - py
	var fx = cx - px
	var fy = cy - py

	var ap = dx * dx + dy * dy
	var bp = ex * ex + ey * ey
	var cp = fx * fx + fy * fy

	return dx * (ey * cp - bp * fy) -\
		dy * (ex * cp - bp * fx) +\
		ap * (ex * fy - ey * fx) < 0


func circumradius(ax, ay, bx, by, cx, cy):
	var dx = bx - ax
	var dy = by - ay
	var ex = cx - ax
	var ey = cy - ay

	var bl = dx * dx + dy * dy
	var cl = ex * ex + ey * ey

	# When you divide by 0 in Godot you get an error.
	# It should return INF (positive or negative).
	var d
	if (dx * ey - dy * ex) == 0:
		d = INF
	elif (dx * ey - dy * ex) == -0:
		d = -INF
	else:
		d = 0.5 / (dx * ey - dy * ex)

	var x = (ey * bl - dy * cl) * d
	var y = (dx * cl - ex * bl) * d

	return x * x + y * y


func circumcenter(ax, ay, bx, by, cx, cy):
	var dx = bx - ax
	var dy = by - ay
	var ex = cx - ax
	var ey = cy - ay

	var bl = dx * dx + dy * dy
	var cl = ex * ex + ey * ey

	# When you divide by 0 in Godot you get an error.
	# It should return INF (positive or negative).
	var d
	if (dx * ey - dy * ex) == 0:
		d = INF
	elif (dx * ey - dy * ex) == -0:
		d = -INF
	else:
		d = 0.5 / (dx * ey - dy * ex)

	var x = ax + (ey * bl - dy * cl) * d
	var y = ay + (dx * cl - ex * bl) * d

	return [x, y]


func quicksort(ids, dists, left, right):
	if right - left <= 20:
		for i in range(left + 1, right + 1):
			var temp = ids[i]
			var temp_dist = dists[temp]
			var j = i - 1
			while j >= left and dists[ids[j]] > temp_dist:
				ids[j + 1] = ids[j]
				j -= 1
			ids[j + 1] = temp
	else:
		var median = (left + right) >> 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)

		var temp = ids[i]
		var temp_dist = dists[temp]

		while true:
			while true:
				i += 1
				if dists[ids[i]] >= temp_dist: break

			while true:
				j -= 1
				if dists[ids[j]] <= temp_dist: break

			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)


func swap(arr, i, j):
	var tmp = arr[i]
	arr[i] = arr[j]
	arr[j] = tmp
