The answer is rather simple actually; when I was checking for intersections in triangle_triangle_partition, I was not filtering out duplicates. Now that I am, everything works as expected. The corrected function:
--- returns the partitio of the first triangle into three subtriangles,
--- if it intersects the second, otherwise produces nil
--- @param T1 table<table<number>> the first triangle
--- @param T2 table<table<number>> the second triangle
--- @return table<table<table<number>>,table<table<number>>,table<table<number>>> table of sub triangles
local function triangle_triangle_partition(T1, T2)
local I = triangle_triangle_intersections(T1, T2)
if I == nil then return nil end
if #I == 0 then return nil end
if #I == 1 then return nil end
-- if #I ~= 2 then assert(false, ("I is not 2, it is instead: %f"):format(#I)) end
local IO = I[1]
local IU = vector_subtraction(I[2], IO)
local I_basis = {IO[1], IU[1]}
local T1A = {T1[1], T1[2]}
local T1AU = vector_subtraction({T1A[2]}, {T1A[1]})
local T1A_basis = {T1A[1], T1AU[1]}
local T1B = {T1[2], T1[3]}
local T1BU = vector_subtraction({T1B[2]}, {T1B[1]})
local T1B_basis = {T1B[1], T1BU[1]}
local T1C = {T1[3], T1[1]}
local T1CU = vector_subtraction({T1C[2]}, {T1C[1]})
local T1C_basis = {T1C[1], T1CU[1]}
local T2A = {T2[1], T2[2]}
local T2AU = vector_subtraction({T2A[2]}, {T2A[1]})
local T2A_basis = {T2A[1], T2AU[1]}
local T2B = {T2[2], T2[3]}
local T2BU = vector_subtraction({T2B[2]}, {T2B[1]})
local T2B_basis = {T2B[1], T2BU[1]}
local T2C = {T2[3], T2[1]}
local T2CU = vector_subtraction({T2C[2]}, {T2C[1]})
local T2C_basis = {T2C[1], T2CU[1]}
local points = {}
local non_intersecting = nil
local function add_unique(points, pt, eps)
for _, p in ipairs(points) do
if distance(p, pt) < eps then
return false -- Not unique
end
end
table.insert(points, pt)
return true -- Unique
end
-- T1A
local int1 = line_line_intersection(I_basis, T1A_basis)
if int1 == nil then
int1 = {solution = {}}
end
if #int1.solution ~= 0 then
local t = int1.solution[1]
local intersect = vector_addition(IO, scalar_multiplication(t, IU))
if point_line_segment_intersecting(intersect, T1A) then
if not add_unique(points, intersect, eps) then
non_intersecting = "T1A"
end
else
non_intersecting = "T1A"
end
else
non_intersecting = "T1A"
end
-- T1B
local int2 = line_line_intersection(I_basis, T1B_basis)
if int2 == nil then
int2 = {solution = {}}
end
if #int2.solution ~= 0 then
local t = int2.solution[1]
local intersect = vector_addition(IO, scalar_multiplication(t, IU))
if point_line_segment_intersecting(intersect, T1B) then
if not add_unique(points, intersect, eps) then
non_intersecting = "T1B"
end
else
non_intersecting = "T1B"
end
else
non_intersecting = "T1B"
end
-- T1C
local int3 = line_line_intersection(I_basis, T1C_basis)
if int3 == nil then
int3 = {solution = {}}
end
if #int3.solution ~= 0 then
local t = int3.solution[1]
local intersect = vector_addition(IO, scalar_multiplication(t, IU))
if point_line_segment_intersecting(intersect, T1C) then
if not add_unique(points, intersect, eps) then
non_intersecting = "T1C"
end
else
non_intersecting = "T1C"
end
else
non_intersecting = "T1C"
end
if #points ~= 2 then
-- print("Partition failure: got", #points, "points")
-- print("Triangle 1:", T1)
-- print("Triangle 2:", T2)
-- return nil
end
local quad = {}
local tri1
local A, B = points[1], points[2]
table.insert(quad, A[1])
table.insert(quad, B[1])
if non_intersecting == "T1A" then
table.insert(quad, T1A[1])
table.insert(quad, T1A[2])
tri1 = {A[1], B[1], T1B[2]}
elseif non_intersecting == "T1B" then
table.insert(quad, T1B[1])
table.insert(quad, T1B[2])
tri1 = {A[1], B[1], T1C[2]}
elseif non_intersecting == "T1C" then
table.insert(quad, T1C[1])
table.insert(quad, T1C[2])
tri1 = {A[1], B[1], T1A[2]}
end
quad = centroid_sort(quad)
if distance({quad[1]},{quad[3]}) > distance({quad[2]},{quad[4]}) then
return {
tri1 = tri1,
tri2 = {quad[2], quad[1], quad[4]},
tri3 = {quad[2], quad[3], quad[4]}
}
else
return {
tri1 = tri1,
tri2 = {quad[1], quad[2], quad[3]},
tri3 = {quad[3], quad[4], quad[1]}
}
end
end