-- lua-tikz3dtools-parametric-objects.lua
local mm = require "lua-tikz3dtools-matrix-math"
local rtc = require "lua-tikz3dtools-register-tex-cmd"
local ENV_ = {}
for k, v in pairs(_G) do ENV_[k] = v end
for k, v in pairs(mm) do ENV_[k] = v end
for k, v in pairs(math) do ENV_[k] = v end
local segments = {}
local observer_dir = { { 0, 0, -1, 1} }
local observer_pos = { { 0, 0, 0, 1} }
local function single_string_expression(str)
return load(("return %s"):format(str), "expression", "t", ENV_)()
end
local function single_string_function(str)
return load(("return function(u) return %s end"):format(str), "expression", "t", ENV_)()
end
local function double_string_function(str)
return load(("return function(u,v) return %s end"):format(str), "expression", "t", ENV_)()
end
local function append_label(hash)
local x = hash.x
local y = hash.y
local z = hash.z
local text = hash.text
local name = hash.name
local transformation = hash.transformation
x = single_string_expression(x)
y = single_string_expression(y)
z = single_string_expression(z)
transformation = single_string_expression(transformation)
local function parametric_point()
return { { x, y, z, 1 } }
end
local A = parametric_point()
local the_segment = mm.matrix_multiply(A, transformation)
table.insert(
segments,
{
segment = the_segment,
text = text,
name = name
}
)
end
local function append_point(hash)
local x = hash.x
local y = hash.y
local z = hash.z
local draw_options = hash.draw_options
local fill_options = hash.fill_options
local name = hash.name
local transformation = hash.transformation
x = single_string_expression(x)
y = single_string_expression(y)
z = single_string_expression(z)
transformation = single_string_expression(transformation)
local function parametric_point()
return { { x, y, z, 1 } }
end
local A = parametric_point()
local the_segment = mm.matrix_multiply(A, transformation)
table.insert(
segments,
{
segment = the_segment,
draw_options = draw_options,
fill_options = fill_options,
name = name
}
)
end
local function append_curve(hash)
local u_start = hash.u_start
local u_stop = hash.u_stop
local u_samples = hash.u_samples
local x = hash.x
local y = hash.y
local z = hash.z
local draw_options = hash.draw_options
local name = hash.name
local transformation = hash.transformation
x = single_string_function(x)
y = single_string_function(y)
z = single_string_function(z)
local u_step = (u_stop - u_start) / (u_samples - 1)
local function parametric_curve(u)
return { { x(u), y(u), z(u), 1 } }
end
for i = 0, u_samples - 2 do
local u = u_start + i * u_step
local A = parametric_curve(u)
local B = parametric_curve(u+u_step)
local the_segment = mm.matrix_multiply({ A[1], B[1] }, transformation)
table.insert(
segments,
{
segment = the_segment,
draw_options = draw_options,
fill_options = fill_options,
name = name
}
)
end
end
local function append_surface(hash)
local u_start = hash.u_start
local u_stop = hash.u_stop
local u_samples = hash.u_samples
local v_start = hash.v_start
local v_stop = hash.v_stop
local v_samples = hash.v_samples
local x = hash.x
local y = hash.y
local z = hash.z
local draw_options = hash.draw_options
local fill_options = hash.fill_options
local name = hash.name
local transformation = hash.transformation
x = double_string_function(x)
y = double_string_function(y)
z = double_string_function(z)
local u_step = (u_stop - u_start) / (u_samples - 1)
local v_step = (v_stop - v_start) / (v_samples - 1)
local function parametric_surface(u, v)
return { x(u,v), y(u,v), z(u,v), 1 }
end
for i = 0, u_samples - 2 do
local u = u_start + i * u_step
for j = 0, v_samples - 2 do
local v = v_start + j * v_step
local A = parametric_surface(u, v)
local B = parametric_surface(u + u_step, v)
local C = parametric_surface(u, v + v_step)
local D = parametric_surface(u + u_step, v + v_step)
local the_segment1 = mm.matrix_multiply({ A, B, D },transformation)
local the_segment2 = mm.matrix_multiply({ A, C, D },transformation)
table.insert(
segments,
{
segment = the_segment1,
draw_options = draw_options,
fill_options = fill_options,
name = name
}
)
table.insert(
segments,
{
segment = the_segment2,
draw_options = draw_options,
fill_options = fill_options,
name = name
}
)
end
end
end
local function is_point_in_triangle(point,triangle)
local P,Q,R = table.unpack(triangle)
P,Q,R = {P},{Q},{R}
local cross_PQ = mm.cross_product(
mm.matrix_subtract(Q,P)
,point
)
local cross_QR = mm.cross_product(
mm.matrix_subtract(R,Q)
,point
)
local cross_RP = mm.cross_product(
mm.matrix_subtract(P,R)
,point
)
local sign_1 = mm.sign(cross_PQ[1][1])
local sign_2 = mm.sign(cross_QR[1][1])
local sign_3 = mm.sign(cross_RP[1][1])
local sign_4 = mm.sign(cross_PQ[1][2])
local sign_5 = mm.sign(cross_QR[1][2])
local sign_6 = mm.sign(cross_RP[1][2])
local sign_7 = mm.sign(cross_PQ[1][3])
local sign_8 = mm.sign(cross_QR[1][3])
local sign_9 = mm.sign(cross_RP[1][3])
if (
(sign_1 == sign_2 and sign_2 == sign_3) and
(sign_4 == sign_5 and sign_5 == sign_6) and
(sign_7 == sign_8 and sign_8 == sign_9)
) then
return true
else
return false
end
end
local function compare_triangles(triangle_1,triangle_2)
if #triangle_1.segment == 1 or #triangle_2.segment == 1 then
local function depth_mid(seg)
if #seg == 1 then
-- line/curve segment: midpoint of S, E
local S = seg[1]
local mid = {{
S[1],S[2],S[3],1
}}
return mm.dot_product(mid, observer_dir)
else
-- triangle: centroid of P, Q, R
local P, Q = seg[1], seg[2]
local cent = {{
(P[1] + Q[1]) / 2,
(P[2] + Q[2]) / 2,
(P[3] + Q[3]) / 2,
1
}}
return mm.dot_product(cent, observer_dir)
end
end
local a = depth_mid(triangle_1.segment)
local b = depth_mid(triangle_2.segment)
return a > b
end
-- 1) if *either* is a 2‐point line/curve (#==3), depth‐sort by midpoint along observer
if #triangle_1.segment == 2 or #triangle_2.segment == 2 then
local function depth_mid(seg)
if #seg == 2 then
-- line/curve segment: midpoint of S, E
local S, E = seg[1], seg[2]
local mid = {{
(S[1] + E[1]) / 2,
(S[2] + E[2]) / 2,
(S[3] + E[3]) / 2,
1
}}
return mm.dot_product(mid, observer_dir)
else
-- triangle: centroid of P, Q, R
local P, Q, R = seg[1], seg[2], seg[3]
local cent = {{
(P[1] + Q[1] + R[1]) / 3,
(P[2] + Q[2] + R[2]) / 3,
(P[3] + Q[3] + R[3]) / 3,
1
}}
return mm.dot_product(cent, observer_dir)
end
end
local a = depth_mid(triangle_1.segment)
local b = depth_mid(triangle_2.segment)
return a > b
end
if #triangle_1.segment > 3 or #triangle_2.segment > 3 then
local function depth_mid(segment)
local x, y, z = 0, 0, 0
local n = #segment
for i = 1, n do
local pt = segment[i]
x = x + pt[1]
y = y + pt[2]
z = z + pt[3]
end
local centroid = {{x / n, y / n, z / n, 1}}
return mm.dot_product(centroid, observer_dir)
end
return depth_mid(triangle_1.segment) > depth_mid(triangle_2.segment)
end
---
local P_1, Q_1, R_1 = table.unpack(triangle_1.segment)
local P_2, Q_2, R_2 = table.unpack(triangle_2.segment)
P_1, Q_1, R_1 = {P_1}, {Q_1}, {R_1}
P_2, Q_2, R_2 = {P_2}, {Q_2}, {R_2}
local observer_basis = mm.get_observer_plane_basis(observer_dir)
local P_1_projection = mm.project_point_onto_basis(P_1,observer_basis)
local Q_1_projection = mm.project_point_onto_basis(Q_1,observer_basis)
local R_1_projection = mm.project_point_onto_basis(R_1,observer_basis)
local P_2_projection = mm.project_point_onto_basis(P_2,observer_basis)
local Q_2_projection = mm.project_point_onto_basis(Q_2,observer_basis)
local R_2_projection = mm.project_point_onto_basis(R_2,observer_basis)
-- compare to triangle 1
local P_test = is_point_in_triangle(P_2_projection,{P_1_projection[1],Q_1_projection[1],R_1_projection[1]})
local Q_test = is_point_in_triangle(Q_2_projection,{P_1_projection[1],Q_1_projection[1],R_1_projection[1]})
local R_test = is_point_in_triangle(R_2_projection,{P_1_projection[1],Q_1_projection[1],R_1_projection[1]})
if (P_test or Q_test or R_test) then
local test
if P_test then
test = "P"
else
if Q_test then
test = "Q"
else
test = "R"
end
end
local normal_1 = mm.cross_product(
mm.matrix_subtract(Q_1,P_1)
,mm.matrix_subtract(R_1,P_1)
)
if mm.dot_product(normal_1,observer_dir) < 1 then
normal_1 = mm.matrix_scale(-1,normal_1)
end
local signed_distance_to_plane
if test == "P" then
signed_distance_to_plane = mm.norm(
mm.matrix_add(
P_2
,mm.matrix_scale(-1,P_2_projection)
)
)
if (
mm.dot_product(
mm.matrix_subtract(P_2,P_2_projection)
,normal_1
) < 1
) then
signed_distance_to_plane = -signed_distance_to_plane
end
if mm.sign(signed_distance_to_plane) == "positive" then
return true
else
return false
end
end
if test == "Q" then
signed_distance_to_plane = mm.norm(
mm.matrix_subtract(Q_2,Q_2_projection)
)
if (
mm.dot_product(
mm.matrix_subtract(Q_2,Q_2_projection)
,normal_1
) < 0
) then
signed_distance_to_plane = -signed_distance_to_plane
end
if mm.sign(signed_distance_to_plane) == "positive" then
return true
else
return false
end
end
if test == "R" then
signed_distance_to_plane = mm.norm(
mm.matrix_subtract(R_2,R_2_projection)
)
if (
mm.dot_product(
mm.matrix_subtract(R_2,R_2_projection)
,normal_1
) < 0
) then
signed_distance_to_plane = -signed_distance_to_plane
end
if mm.sign(signed_distance_to_plane) == "positive" then
return true
else
return false
end
end
else
local midpoint_1 = mm.midpoint({P_1[1],Q_1[1],R_1[1]})
local midpoint_2 = mm.midpoint({P_2[1],Q_2[1],R_2[1]})
local dot_product_1 = mm.dot_product(midpoint_1,observer_dir)
local dot_product_2 = mm.dot_product(midpoint_2,observer_dir)
return dot_product_1 > dot_product_2
end
return false -- test
end
local function get_line(hash)
local a1 = hash.a1
local b1 = hash.b1
local c1 = hash.c1
local d1 = hash.d1
local a2 = hash.a2
local b2 = hash.b2
local c2 = hash.c2
local d2 = hash.d2
local xmin = hash.xmin
local xmax = hash.xmax
local ymin = hash.ymin
local ymax = hash.ymax
local zmin = hash.zmin
local zmax = hash.zmax
local function Lyofx(x)
return (
(
(
d2 -
c2 * d1 / c1
) - (
a2 -
c2 * a1 / c1
) * x
) / (
b2 -
c2 * b1 / c1
)
)
end
local function Lzofx(x)
return (
(
(
d2 -
b2 * d1 / b1
) - (
a2 -
b2 * a1 / b1
) * x
) / (
c2 -
b2 * c1 / b1
)
)
end
local function Lzofy(y)
return (
(
(
d2 -
a2 * d1 / a1
) - (
b2 -
a2 * b1 / a1
) * y
) / (
c2 -
a2 * c1 / a1
)
)
end
local function Lxofy(y)
return (
(
(
d2 -
c2 * d1 / c1
) - (
b2 -
c2 * b1 / c1
) * y
) / (
a2 -
c2 * a1 / c1
)
)
end
local function Lyofz(z)
return (
(
(
d2 -
a2 * d1 / a1
) - (
c2 -
a2 * c1 / a1
) * z
) / (
b2 -
a2 * b1 / a1
)
)
end
local function Lxofz(z)
return (
(
(
d2 -
b2 * d1 / b1
) - (
c2 -
b2 * c1 / b1
) * z
) / (
a2 -
b2 * a1 / b1
)
)
end
local startx, starty, startz
local endx, endy, endz
if not (
math.abs(b2-c2*b1/c1)<0.0001 or
math.abs(c2-b2*c1/b1)<0.0001
) then
startx = xmin
starty = Lyofx(xmin)
startz = Lzofx(xmin)
endx = xmax
endy = Lyofx(xmax)
endz = Lzofx(xmax)
-- y coord
if endy>ymax then
endx = Lxofy(ymax)
endy = Lyofx(Lxofy(ymax))
endz = Lzofx(Lxofy(ymax))
end
if endy<ymin then
endx = Lxofy(ymin)
endy = Lyofx(Lxofy(ymin))
endz = Lzofx(Lxofy(ymin))
end
if starty>ymax then
startx = Lxofy(ymax)
starty = Lyofx(Lxofy(ymax))
startz = Lzofx(Lxofy(ymax))
end
if starty<ymin then
startx = Lxofy(ymin)
starty = Lyofx(Lxofy(ymin))
startz = Lzofx(Lxofy(ymin))
end
-- z coord
if endz>zmax then
endx = Lxofz(zmax)
endy = Lyofx(Lxofz(zmax))
endz = Lzofx(Lxofz(zmax))
end
if endz<zmin then
endx = Lxofz(zmin)
endy = Lyofx(Lxofz(zmin))
endz = Lzofx(Lxofz(zmin))
end
if startz>zmax then
startx = Lxofz(zmax)
starty = Lyofx(Lxofz(zmax))
startz = Lzofx(Lxofz(zmax))
end
if startz<zmin then
startx = Lxofz(zmin)
starty = Lyofx(Lxofz(zmin))
startz = Lzofx(Lxofz(zmin))
end
else
if not (
math.abs(c2-a2*c1/a1)<0.001 or
math.abs(a2-c2*a1/c1)<0.001
) then
startx = Lxofy(ymin)
starty = ymin
startz = Lzofy(ymin)
endx = Lxofy(ymax)
endy = ymax
endz = Lzofy(ymax)
if endx>xmax then
endx = Lxofy(Lyofx(xmax))
endy = Lyofx(xmax)
endz = Lzofy(Lyofx(xmax))
end
if endx<xmin then
endx = Lxofy(Lyofx(xmin))
endy = Lyofx(xmin)
endz = Lzofy(Lyofx(xmin))
end
if startx>xmax then
startx = Lxofy(Lyofx(xmax))
starty = Lyofx(xmax)
startz = Lzofy(Lyofx(xmax))
end
if startx<xmin then
startx = Lxofy(Lyofx(xmin))
starty = Lyofx(xmin)
startz = Lzofy(Lyofx(xmin))
end
if endz>zmax then
endx = Lxofy(Lyofz(zmax))
endy = Lyofz(zmax)
endz = Lzofy(Lyofz(zmax))
end
if endz<zmin then
endx = Lxofy(Lyofz(zmin))
endy = Lyofz(zmin)
endz = Lzofy(Lyofz(zmin))
end
if startz>zmax then
startx = Lxofy(Lyofz(zmax))
starty = Lyofz(zmax)
startz = Lzofy(Lyofz(zmax))
end
if startz<zmin then
startx = Lxofy(Lyofz(zmin))
starty = Lyofz(zmin)
startz = Lzofy(Lyofz(zmin))
end
else
if not (
math.abs(b2-a2*b1/a1)<0.001 or
math.abs(a2-b2*a1/b1)<0.001
) then
startx = Lxofz(zmin)
starty = Lyofz(zmin)
startz = zmin
endx = Lxofz(zmax)
endy = Lyofz(zmax)
endz = zmax
if endx>xmax then
endx = Lxofz(Lzofx(xmax))
endy = Lyofz(Lzofx(xmax))
endz = Lzofx(xmax)
end
if endx<xmin then
endx = Lxofz(Lzofx(xmin))
endy = Lyofz(Lzofx(xmin))
endz = Lzofx(xmin)
end
if endy>ymax then
endx = Lxofz(Lzofy(ymax))
endy = Lyofz(Lzofy(ymax))
endz = Lzofy(ymax)
end
if endy<ymin then
endx = Lxofz(Lzofy(ymin))
endy = Lyofz(Lzofy(ymin))
endz = Lzofy(ymin)
end
if startx>xmax then
startx = Lxofz(Lzofx(xmax))
starty = Lyofz(Lzofx(xmax))
startz = Lzofx(xmax)
end
if startx<xmin then
startx = Lxofz(Lzofx(xmin))
starty = Lyofz(Lzofx(xmin))
startz = Lzofx(xmin)
end
if starty>ymax then
startx = Lxofz(Lzofy(ymax))
starty = Lyofz(Lzofy(ymax))
startz = Lzofy(ymax)
end
if starty<ymin then
startx = Lxofz(Lzofy(ymin))
starty = Lyofz(Lzofy(ymin))
startz = Lzofy(ymin)
end
end
end
end
return {
{startx,starty,startz}
,{endx,endy,endz}
}
end
local function get_bounding_box(T)
local tri = T.segment
local P = tri[1]
local Q = tri[2]
local R = tri[3]
local xmin = math.min(P[1], Q[1], R[1])
local xmax = math.max(P[1], Q[1], R[1])
local ymin = math.min(P[2], Q[2], R[2])
local ymax = math.max(P[2], Q[2], R[2])
local zmin = math.min(P[3], Q[3], R[3])
local zmax = math.max(P[3], Q[3], R[3])
local function combine_bounding_boxes(bb1, bb1)
local xmin1 = bb1.xmin
local xmax1 = bb1.xmax
local ymin1 = bb1.ymin
local ymax1 = bb1.ymax
local zmin1 = bb1.zmin
local zmax1 = bb1.zmax
local xmin2 = bb2.xmin
local xmax2 = bb2.xmax
local ymin2 = bb2.ymin
local ymax2 = bb2.ymax
local zmin2 = bb2.zmin
local zmax2 = bb2.zmax
local xmin = math.min(xmin1, xmin2)
local xmax = math.max(xmax1, xmax2)
local ymin = math.min(ymin1, ymin2)
local ymax = math.max(ymax1, ymax2)
local zmin = math.min(zmin1, zmin2)
local zmax = math.max(zmax1, zmax2)
local function is_overlapping(bb1, bb2)
local xmin1 = bb1.xmin
local xmax1 = bb1.xmax
local ymin1 = bb1.ymin
local ymax1 = bb1.ymax
local zmin1 = bb1.zmin
local zmax1 = bb1.zmax
local xmin2 = bb2.xmin
local xmax2 = bb2.xmax
local ymin2 = bb2.ymin
local ymax2 = bb2.ymax
local zmin2 = bb2.zmin
local zmax2 = bb2.zmax
return (
xmin1 < xmax2 and xmax1 > xmin2 and
ymin1 < ymax2 and ymax1 > ymin2
)
end
local function point_in_basis(point, basis)
local origin = basis.origin[1] -- {x, y, z, 1}
local u = basis.basis[1] -- {x, y, z, 1}
local v = basis.basis[2] -- {x, y, z, 1}
-- Extract direction vectors u and v (subtract origin)
local u_vec = {{
u[1] - origin[1],
u[2] - origin[2],
u[3] - origin[3]
}}
local v_vec = {{
v[1] - origin[1],
v[2] - origin[2],
v[3] - origin[3]
}}
-- Compute w = point - origin
local w = {{
point[1][1] - origin[1],
point[1][2] - origin[2],
point[1][3] - origin[3]
}}
-- Matrix A = [u_vec; v_vec] as 3×2
local A = {
{u_vec[1][1], v_vec[1][1]},
{u_vec[1][2], v_vec[1][2]},
{u_vec[1][3], v_vec[1][3]}
}
-- Aᵀ A
local AT = mm.transpose(A)
local ATA = mm.matrix_multiply(AT, A)
-- Inverse of Aᵀ A
local ATA_inv = mm.inverse(ATA)
-- Aᵀ w
local w_matrix = {
{w[1][1]},
{w[1][2]},
{w[1][3]}
}
local ATw = mm.matrix_multiply(AT, w_matrix)
-- Solve [α; β] = (Aᵀ A)^−1 · Aᵀ w
local result = mm.matrix_multiply(ATA_inv, ATw)
local alpha = result[1][1]
local beta = result[2][1]
return {{alpha, beta, 0, 1}}
end
local function is_intersecting(triangle, line)
local T = triangle
local P = {T[1]}
local Q = {T[2]}
local R = {T[3]}
local PQ = matrix_subtract(Q,P)
local PR = matrix_subtract(R,P)
local O = P
local basis = {
origin = O,
u = PQ,
v = PR
}
local L = line
local S = {L[1]}
local E = {L[2]}
local d = matrix_subtract(E,S)
end
local function clip_triangles()
local broken_segs = {}
local used_i = {}
for i, seg in ipairs(segments) do
table.insert(used_i, i)
for j, seg2 in ipairs(segments) do
if not used_i[j] then
local bb1 = get_bounding_box(seg)
local bb2 = get_bounding_box(seg2)
local T1 = seg.segment
local T2 = seg2.segment
if is_overlapping(bb1, bb2) then
local bb = combine_bounding_boxes(bb1, bb2)
local u1 = mm.matrix_subtract(
{T1[2]}
,{T1[1]}
)
local v1 = mm.matrix_subtract(
{T1[3]}
,{T1[1]}
)
local n1 = mm.cross_product(u1, v1)
local d1 = mm.dot_product(n1, {T1[1]})
local u2 = mm.matrix_subtract(
{T2[2]}
,{T2[1]}
)
local v2 = mm.matrix_subtract(
{T2[3]}
,{T2[1]}
)
local n2 = mm.cross_product(u2, v2)
local d2 = mm.dot_product(n2, {T2[1]})
local clipping_line = get_line{
xmin = bb.xmin
,xmax = bb.xmax
,ymin = bb.ymin
,ymax = bb.ymax
,zmin = bb.zmin
,zmax = bb.zmax
,a1 = n1[1][1]
,b1 = n1[1][2]
,c1 = n1[1][3]
,d1 = d1
,a2 = n2[1][1]
,b2 = n2[1][2]
,c2 = n2[1][3]
,d2 = d2
}
end
end
end
end
end
local function render_segments()
table.sort(segments, compare_triangles)
for _, segment in ipairs(segments) do
local pts = segment.segment
local skip = false
-- check bounding box for every point
for _, P in ipairs(pts) do
if math.abs(P[1]) > 100 or math.abs(P[2]) > 100 then
skip = true
break
end
end
if not skip then
if #pts == 2 then
-- draw a line segment
local S, E = pts[1], pts[2]
tex.sprint(string.format(
"\\path[%s] (%f,%f) -- (%f,%f);",
segment.draw_options, S[1], S[2], E[1], E[2]
))
elseif #pts == 3 then
-- draw a filled triangle
local P, Q, R = pts[1], pts[2], pts[3]
tex.sprint(string.format(
"\\path[preaction={%s},postaction={%s}] (%f,%f) -- (%f,%f) -- (%f,%f) -- cycle;",
segment.fill_options, segment.draw_options,
P[1], P[2], Q[1], Q[2], R[1], R[2]
))
elseif #pts > 3 then
-- draw a general polygon
local path = {}
for _, P in ipairs(pts) do
table.insert(path, string.format("(%f,%f)", P[1], P[2]))
end
tex.sprint(string.format(
"\\path[preaction={%s},postaction={%s}] %s -- cycle;",
segment.fill_options or "", segment.draw_options or "",
table.concat(path, " -- ")
))
elseif #pts == 1 and segment.text then
local P = pts[1]
tex.sprint(string.format(
"\\node at (%f,%f) {%s};",
P[1], P[2],
segment.text or ""
))
elseif #pts == 1 and not segment.text then
local P = pts[1]
tex.sprint(string.format(
"\\path[preaction={%s},postaction={%s}] (%f,%f) circle[radius = 0.06];",
segment.fill_options or "", segment.draw_options or "",
P[1], P[2]
))
end
end
end
-- clear for next frame
segments = {}
end
rtc.register_tex_cmd(
"appendlabel",
function()
append_label{
x = token.get_macro("tikz@td@p@l@x"),
y = token.get_macro("tikz@td@p@l@y"),
z = token.get_macro("tikz@td@p@l@z"),
text = token.get_macro("tikz@td@p@l@text"),
name = token.get_macro("tikz@td@p@l@name"),
transformation = token.get_macro("tikz@td@p@l@transformation")
}
end,
{ }
)
rtc.register_tex_cmd(
"appendpoint",
function()
append_point{
x = token.get_macro("tikz@td@p@p@x"),
y = token.get_macro("tikz@td@p@p@y"),
z = token.get_macro("tikz@td@p@p@z"),
draw_options = token.get_macro("tikz@td@p@p@drawoptions"),
fill_options = token.get_macro("tikz@td@p@p@filloptions"),
name = token.get_macro("tikz@td@p@p@name"),
transformation = token.get_macro("tikz@td@p@p@transformation")
}
end,
{ }
)
rtc.register_tex_cmd(
"appendcurve",
function()
append_curve{
u_start = token.get_macro("tikz@td@p@c@umin"),
u_stop = token.get_macro("tikz@td@p@c@umax"),
u_samples = token.get_macro("tikz@td@p@c@usamples"),
x = token.get_macro("tikz@td@p@c@x"),
y = token.get_macro("tikz@td@p@c@y"),
z = token.get_macro("tikz@td@p@c@z"),
draw_options = token.get_macro("tikz@td@p@c@drawoptions"),
name = token.get_macro("tikz@td@p@c@name"),
transformation = token.get_macro("tikz@td@p@c@transformation")
}
end,
{ }
)
local function append_plane(hash)
local a = single_string_expression(hash.a)
local b = single_string_expression(hash.b)
local c = single_string_expression(hash.c)
local d = single_string_expression(hash.d)
local xmin = single_string_expression(hash.xmin)
local xmax = single_string_expression(hash.xmax)
local ymin = single_string_expression(hash.ymin)
local ymax = single_string_expression(hash.ymax)
local zmin = single_string_expression(hash.zmin)
local zmax = single_string_expression(hash.zmax)
local fill_options = hash.fill_options
local draw_options = hash.draw_options
local transform = single_string_expression(hash.transformation)
local normal = {{a,b,c,1}}
local intersections = {}
local sorted_intersections = {}
local function plane(u,v)
local x = (d-normal[1][2]*u-normal[1][3]*v)/normal[1][1]
local y = (d-normal[1][1]*u-normal[1][3]*v)/normal[1][2]
local z = (d-normal[1][1]*u-normal[1][2]*v)/normal[1][3]
local result = {{x,y,z,1}}
return result
end
local function add_intersection(i)
if (
i[1][1]>=xmin and i[1][1]<=xmax and
i[1][2]>=ymin and i[1][2]<=ymax and
i[1][3]>=zmin and i[1][3]<=zmax
) then
table.insert(intersections,{{i[1][1],i[1][2],i[1][3],1}})
end
end
if normal[1][1]~=0 then
for y = ymin, ymax, ymax-ymin do
for z = zmin, zmax, zmax-zmin do
local x = plane(y,z)[1][1]
add_intersection({{x,y,z,1}})
end
end
end
if normal[1][2]~=0 then
for x = xmin, xmax, xmax-xmin do
for z = zmin, zmax, zmax-zmin do
local y = plane(x,z)[1][2]
add_intersection({{x,y,z,1}})
end
end
end
if normal[1][3]~=0 then
for x = xmin, xmax, xmax-xmin do
for y = ymin, ymax, ymax-ymin do
local z = plane(x,y)[1][3]
add_intersection({{x,y,z,1}})
end
end
end
local n = mm.normalize(normal)
local u = mm.orthogonal_vector(n)
local u = mm.normalize(u)
local v = mm.cross_product(n,u)
local centroid = {{0,0,0,1}}
local number_of_points = 0
for index, value in ipairs(intersections) do
centroid[1][1] = centroid[1][1]+value[1][1]
centroid[1][2] = centroid[1][2]+value[1][2]
centroid[1][3] = centroid[1][3]+value[1][3]
number_of_points = number_of_points + 1
end
local centroid = {{
centroid[1][1]/number_of_points
,centroid[1][2]/number_of_points
,centroid[1][3]/number_of_points
,1
}}
for index, value in ipairs(intersections) do
local ax = mm.dot_product(
{{
value[1][1]-centroid[1][1]
,value[1][2]-centroid[1][2]
,value[1][3]-centroid[1][3]
,1
}},{{u[1][1],u[1][2],u[1][3],1}}
)
local ay = mm.dot_product(
{{
value[1][1]-centroid[1][1]
,value[1][2]-centroid[1][2]
,value[1][3]-centroid[1][3]
,1
}},{{v[1][1],v[1][2],v[1][3],1}}
)
local anglea = math.atan2(ay,ax)
table.insert(
sorted_intersections
,{angle = anglea, point = {{value[1][1],value[1][2],value[1][3],1}}, draw_options = draw_options, fill_options = fill_options}
)
end
table.sort(
sorted_intersections
,function(a, b)
return a.angle < b.angle
end
)
local result = { segment = {}, draw_options = draw_options, fill_options = fill_options }
for i, plane in ipairs(sorted_intersections) do
table.insert(result.segment,plane.point)
end
for i, point in ipairs(result.segment) do
result.segment[i] = mm.matrix_multiply(result.segment[i],transform)[1]
end
table.insert(segments,result)
end