Drawing a torus with Julia
Posted on December 15, 2022
by Stéphane Laurent
I already showed how to draw a torus whose equator passes through three given points with three.js, Asymptote, R, POV-Ray, and Haskell. Here I have a method for Python, using PyVista, but it is a bit useless because once you know how to draw a circular path passing through three points you can “tubify” it with PyVista, that is to say you can add a radius to the path. Similarly, you can do that with the R package rgl by using the cylinder3d
function.
Now I want to show how to do that with Julia. I’m using the Meshes package because I find it easy to use.
Here is the code:
using LinearAlgebra
using Meshes
# plane passing by points p1, p2, p3 #
function plane3pts(p1, p2, p3)
= LinearAlgebra.cross(p2 - p1, p3 - p1)
normal if LinearAlgebra.norm_sqr(normal) == 0
"The three points are colinear.")
error(end
= LinearAlgebra.dot(Meshes.coordinates(p1), normal)
offset return (normal = normal, offset = offset)
end
# center, radius and normal of the circle passing by three points #
function circleCenterAndRadius(p1, p2, p3)
= p2 - p1
v12 = p3 - p1
v13 = Meshes.coordinates(p1 + v12/2)
p12 = Meshes.coordinates(p1 + v13/2)
p13 , offset = plane3pts(p1, p2, p3)
normal= transpose(hcat(normal, v12, v13))
A = [
b ,
offset, v12),
LinearAlgebra.dot(p12, v13)
LinearAlgebra.dot(p13
]= Meshes.Point(inv(A) * b)
center = LinearAlgebra.norm2(p1 - center)
r return (center = center, radius = r, normal = normal)
end
# key transformation #
function keyTransform(p1, p2, p3)
, radius, normal = circleCenterAndRadius(p1, p2, p3)
center= normal / LinearAlgebra.norm2(normal)
normal = sqrt(normal[1]^2 + normal[2]^2)
s if s == 0
return (matrix = I, center = center, radius = radius)
end
= [normal[2] / s, -normal[1] / s, 0]
u = LinearAlgebra.cross(normal, u)
v = hcat(u, v, normal)
R return (rotMatrix = R, center = center, radius = radius)
end
"""
torusMesh(R, r; nu = 50, nv = 30)
Mesh of a torus with major radius `R` and minor radius `r`,
with the z-axis as its axis of rotation and the xy-plane as its
equatorial plane.
# Arguments
- `R`: major radius
- `r`: minor radius
- `nu`, `nv`: numbers of subdivisions
"""
function torusMesh(R, r; nu = 50, nv = 30)
= R^2 - r^2
kxy = sqrt(kxy) * r
kz = sqrt(kxy) / r
s = LinRange(-s*pi, s*pi, nu)
u_ = LinRange(-pi, pi, nv)
v_ = [Meshes.Point(
points * sin(u/s), kxy * cos(u/s), kz * sin(v)] / (R - r*cos(v))
[kxy for u in u_ for v in v_
)
]= Meshes.GridTopology((nv, nu), (true, true))
topo return Meshes.SimpleMesh(points, topo)
end
"""
torusMesh(r, p1, p2, p3; nu = 50, nv = 30)
Mesh of a torus whose equator passes through three given points.
# Arguments
- `r`: minor radius
- `p1`, `p2`, `p3`: the three points
- `nu`, `nv`: numbers of subdivisions
"""
function torusMesh(r, p1, p2, p3; nu = 50, nv = 30)
, center, R = keyTransform(p1, p2, p3)
M= torusMesh(R, r; nu = nu, nv = nv)
mesh = [
vertices + Meshes.Vec3(M * Meshes.coordinates(v))
center for v in Meshes.vertices(mesh)
]return Meshes.SimpleMesh(vertices, Meshes.topology(mesh))
end
And an example:
using MeshViz
= 1 / sqrt(3)
a = [ a, -a, -a]
p1 = [ a, a, a]
p2 = [-a, -a, a]
p3 = [-a, a, -a]
p4
= torusMesh(0.1, p1, p2, p3)
mesh1 = torusMesh(0.1, p1, p2, p4)
mesh2 = torusMesh(0.1, p1, p3, p4)
mesh3 = torusMesh(0.1, p2, p3, p4)
mesh4
function draw()
!(mesh2; color = :red)
MeshViz.viz!(mesh3; color = :blue)
MeshViz.viz!(mesh4; color = :green)
MeshViz.vizend
; color=:yellow)
MeshViz.viz(mesh1 draw()