Mitch Richling: Torus Knots
Author: | Mitch Richling |
Updated: | 2021-09-22 |
Table of Contents
1. What is this page all about?
Torus knots have many applications including some deep applications in physics. Unfortunately these knots are difficult to visualize for students, and difficult for educators to draw. The whole point of this page is to provide self contained, relatively simple, tools for physicists and educators who wish to render torus knots.
2. Usage Rights
The images of torus knots linked from this page may be freely used for educational and academic purposes. The code may be freely used to generate your own images. If you publish my images, then please provide a proper photo attribution or byline. If you use the code here to create images you publish, then please include this page in your bibliography.
3. Creating Torus Knot Images
3.1. Create the Geometry
The first task is to generate the geometric description of the objects our 3D rendering engine will draw. Our knots are curves, but curves are 1D objects – 3D rendering tools don't render 1D objects, they render 3D objects. So instead of generating a geometric description of a curve, we create a "tube" – a fat curve with some thickness. Many ways exist to do that, but the one I selected for this application is what I call the "ball and cylinder" technique. The idea is to sample the curve along a regularly spaced grid, draw a sphere at each sample point, and connect the spheres together with cylinders. I used this same technique is used to render the Lorenz Strange Attractor. As a side note, this is the kind of geometric structure my LISP calculator program produces as well.
While two scripts may be found in this section, only the first one was used for the images above. The scripts can do a bit more than is demonstrated here. They can dump CSV-style data that may be imported into graphing packages, draw sphere sweep-like geometries instead of ball-n-cylinder, and can add arrows to the curves.
mkTorusKnot.rb
- Draw torus knots.
mkTorusHoops.rb
- Draw hoops around a torus.
The mkTorusKnot.rb
is used to generate a file, tordat.txt
, that is used by the rendering tool in the next step. Here is a code snip
showing how we do it:
./mkTorusKnot.rb 5 2 1 3 1000 pcyl > tordat.txt
For additional details regarding the script arguments and usage, consult the comments at the top of the scripts and the examples in the section Automating the whole thing
3.2. Render the 3D scene
The geometric object is rendered using POV-Ray. All of the above images are generated with a single
POV-Ray source file that "includes" the output of the mkTorusKnot.rb
script placed into a file named tordat.txt
. The POV-Ray input
file is torus_knot.pov
and here is how we use it:
povray -D -P -W800 -H600 -Q11 +A0.4 -AM2 -R5 +Itorus_knot.pov +Otorus_knot.png rm tordat.txt
After the above commands run, the image will be in a file called torus_knot.png
3.3. Annotate and create thumbnail
The tool here is ImageMagick. We use a larger font that one might normally expect because we immediately shrink the image down by 50%.
s0="gravity northwest fill black text 1,11 'Torus Knot ($p,$q)'" convert torus_knot.png -pointsize 40 -draw "$s0" -resize 50% torus_knot_small.gif
3.4. Automating the whole thing
It would be a real pain to do the above steps for each image manually, so we automate the process for a selection of low order knots with a little bash script.
#!/bin/bash # -*- Mode:Shell-script; Coding:utf-8; fill-column:132 -*- #################################################################################################################################### # @file go.sh # @author Mitch Richling http://www.mitchr.me # @Copyright Copyright 2013 by Mitch Richling. All rights reserved. # @brief Automate the generation of images for my web page of all prime indexed torus knots from 2 up to 11. @EOL # @Std bash CONVERT='TRUE' DISPLAY='TRUE' ##---------------------------------------------------------------------------------------------------------------------------------- for p in 2 3 5 7 11; do for q in 2 3 5 7 11; do # for p in 5; do # for q in 2; do if [ $p != $q ]; then echo Working on knot $p $q POVOPT=' -Q11 +A0.4 -AM2 -R5 ' # Uncomment the next line for faster renders #POVOPT=' ' SIZE='-W1920 -H1440' # Generate the knot geometry ruby ./mkTorusKnot.rb $p $q 1 3 1000 pcyl > tordat.txt # Render the image into a PNG file if [ -z "MSYSTEM" ]; then povray -D -P -W1920 -H1440 $POVOPT +Itorus_knot.pov +Otorus_knot_${p}_${q}.png else /c/Program\ Files/POV-Ray/v3.7/bin/pvengine.exe -D -P $SIZE $POVOPT +Itorus_knot.pov +Otorus_knot_${p}_${q}.png //EXIT fi rm tordat.txt if [ "$CONVERT" = 'TRUE' ]; then # Create the thumbnail by annotating the image and reducing the size s0="gravity northwest fill black text 1,11 'Torus Knot ($p,$q)'" convert torus_knot_${p}_${q}.png -pointsize 130 -draw "$s0" -resize 20% torus_knot_${p}_${q}_small.gif # Create the big image by annotating the image s1="gravity northwest fill black text 1,11 'Torus Knot ($p,$q)'" s2="gravity southeast fill black text 1,11 'By Mitch Richling'" convert torus_knot_${p}_${q}.png -pointsize 130 -draw "$s2" -draw "$s1" torus_knot_${p}_${q}_big.gif fi fi done done # Display the images if [ "$DISPLAY" = 'TRUE' ]; then if [ -z "MSYSTEM" ]; then pqiv -f `ls torus_knot_*.png torus_knot_*_big.gif | sort` torus_knot_*_small.gif & else explorer . fi fi
4. Standard Torus Knot Images
Here are the images generated by the go.sh
script. Note the thumbnails link to larger images.
5. Images with "hoops"
Here are some examples illustrating "hoops" that one can make with mkTorusHoops.rb
curves offset from the torus, and arrows. The
thumbnails below don't link to larger images.
6. Code
6.1. mkTorusKnot.rb
#!/home/richmit/bin/ruby # -*- Mode:Ruby; Coding:utf-8; fill-column:132 -*- #################################################################################################################################### # @file mkTorusKnot.rb # @author Mitch Richling http://www.mitchr.me # @Copyright Copyright 2013 by Mitch Richling. All rights reserved. # @brief Dump out data that can be used to draw a torus knot.@EOL # @Std Ruby 1.9 # # Dump out data that can be used to draw a torus knot. Focus is on PovRay and scientific visualization tools like # GNUplot. # # USE: p q minorRadius MajorRadius numberOfObjectsInCurve what # what is one of: # * data => csv file # * pcyl => povray cylinder data # * psph => povray sphere data # * pcyla => povray cylinder data with arrows! # ##---------------------------------------------------------------------------------------------------------------------------------- require "matrix" ##---------------------------------------------------------------------------------------------------------------------------------- $p = ARGV[0].to_f $q = ARGV[1].to_f $sRad = ARGV[2].to_f $bRad = ARGV[3].to_f $numObj = ARGV[4].to_f $doWhat = ARGV[5].to_sym ##---------------------------------------------------------------------------------------------------------------------------------- # Compute points on the torus knot. The parametrization used is: # $$\begin{array}{rcl} # x(t) & = & \cos(qt) [R_M+R_m\cos(pt)] \\ # y(t) & = & \sin(qt) [R_M+R_m\cos(pt)] \\ # z(t) & = & R_m\sin(pt) \\ # \end{array}$$ # Here $t\in[0,2\pi)$, $p,q\in\mathbb{Z}$ are fixed, prime numbers such that $p\neq q$, $R_m\in\mathbb{R}$ is the # minor radius, and $R_M\in\mathbb{R}$ is the major radius def functionname(ti) t = ti*(2*Math::PI)/$numObj Vector[Math::cos($q*t)*($bRad+$sRad*Math::cos($p*t)), Math::sin($q*t)*($bRad+$sRad*Math::cos($p*t)), $sRad*Math::sin($p*t)] end ##---------------------------------------------------------------------------------------------------------------------------------- case $doWhat when :data # Dump out data for a plotting program. # GNUplot example: set datafile separator ","; splot "< ./mkTorusKnot.rb 5 2 1 3 1000 data" using 1:2:3 with lines for ti in 0..($numObj-1) puts(functionname(ti).to_a.join(',')) end puts(functionname(0).to_a.join(',')) when :psph # Dump out data for a sphere sweep povray object sphRad = '0.1' for ti in 0..($numObj-1) puts(" sphere { <#{functionname(ti).to_a.join(',')}> , #{sphRad} }") end when :pcyla, :pcyl # Dump out data for connected cylinder with arrows povray data sphRad = 0.051 cylRad = 0.051 conRad = 3*sphRad conTal = 2*conRad arrFrq = ($numObj/15).to_i zeroPoint = lastPoint = functionname(0) for ti in 1..($numObj-1) currentPoint = functionname(ti) puts(" cylinder { <#{lastPoint.to_a.join(',')}>, <#{currentPoint.to_a.join(',')}> , #{cylRad} }") puts(" sphere { <#{currentPoint.to_a.join(',')}> , #{sphRad} }") if (($doWhat == :pcyla) && ((ti % arrFrq) == 0)) then # Compute location of cone "tail" tmp = (lastPoint - currentPoint) tmp = conTal * tmp / tmp.magnitude + currentPoint puts(" cone { <#{lastPoint.to_a.join(',')}>, #{conRad} <#{tmp.to_a.join(',')}>, 0 }") end lastPoint = currentPoint end puts(" cylinder { <#{lastPoint.to_a.join(',')}>, <#{zeroPoint.to_a.join(',')}> , #{cylRad} }") puts(" sphere { <#{zeroPoint.to_a.join(',')}> , #{sphRad} }") else puts "ERROR: Unknown 6th argument!" end
6.2. mkTorusHoops.rb
#!/usr/bin/ruby1.9.1 # -*- Mode:Ruby; Coding:utf-8; fill-column:132 -*- #################################################################################################################################### # @file mkTorusHoops.rb # @author Mitch Richling http://www.mitchr.me # @Copyright Copyright 2013 by Mitch Richling. All rights reserved. # @brief Generate files describing hoops around a torus.@EOL # @Std Ruby 1.9 # # Dump out data that can be used to draw hoops around a torus. Focus is on PovRay and scientific visualization tools # like GNUplot. # # USE: p q minorRadius MajorRadius numberOfObjectsInCurve what # what is one of: # * data => CSV data # * pcyl => povray cylinder data # * pcyla => povray cylinder data with arrows! # ##---------------------------------------------------------------------------------------------------------------------------------- require "matrix" ##---------------------------------------------------------------------------------------------------------------------------------- $p = ARGV[0].to_f $q = ARGV[1].to_f $sRad = ARGV[2].to_f $bRad = ARGV[3].to_f $numObj = ARGV[4].to_f $doWhat = ARGV[5].to_sym ##---------------------------------------------------------------------------------------------------------------------------------- # Compute points on the torus. The parametrization used is: # $$\begin{array}{rcl} # x(t) & = & \cos(u) [R_M+R_m\cos(v)] \\ # y(t) & = & \sin(u) [R_M+R_m\cos(v)] \\ # z(t) & = & R_m\sin(v) \\ # \end{array}$$ # Here $t\in[0,2\pi)$, $p,q\in\mathbb{Z}$ are fixed, prime numbers such that $p\neq q$, $R_m\in\mathbb{R}$ is the # minor radius, and $R_M\in\mathbb{R}$ is the major radius def functionname(u,v) Vector[Math::cos(u)*($bRad+$sRad*Math::cos(v)), Math::sin(u)*($bRad+$sRad*Math::cos(v)), $sRad*Math::sin(v)] end ##---------------------------------------------------------------------------------------------------------------------------------- case $doWhat when :data # Dump out data for a plotting program. # GNUplot example: set datafile separator ","; splot "< ./mkTorusHoops.rb 5 2 1 3 1000 data" using 1:2:3 with points 0.upto($p) do |pi| u = pi*(2*Math::PI)/$p for vi in 0..$numObj v = vi*(2*Math::PI)/($numObj-1) puts(functionname(u,v).to_a.join(',')) end puts(functionname(u,0).to_a.join(',')) end 0.upto($q) do |qi| v = qi*(2*Math::PI)/$q for ui in 0..$numObj u = ui*(2*Math::PI)/($numObj-1) puts(functionname(u,v).to_a.join(',')) end puts(functionname(0,v).to_a.join(',')) end when :pcyla, :pcyl # Dump out data for connected cylinder with arrows povray data sphRad = 0.051 cylRad = 0.051 conRad = 3*sphRad conTal = 2*conRad arrFrq = ($numObj/10).to_i 0.upto($p-1) do |pi| u = pi*(2*Math::PI)/$p zeroPoint = lastPoint = functionname(u,0) for vi in 1..($numObj-1) v = vi*(2*Math::PI)/$numObj currentPoint = functionname(u,v) puts(" cylinder { <#{lastPoint.to_a.join(',')}>, <#{currentPoint.to_a.join(',')}> , #{cylRad} }") puts(" sphere { <#{currentPoint.to_a.join(',')}> , #{sphRad} }") if (($doWhat == :pcyla) && ((vi % arrFrq) == 0)) then # Compute location of cone "tail" tmp = (lastPoint - currentPoint) tmp = conTal * tmp / tmp.magnitude + currentPoint puts(" cone { <#{lastPoint.to_a.join(',')}>, #{conRad} <#{tmp.to_a.join(',')}>, 0 }") end lastPoint = currentPoint end puts(" cylinder { <#{lastPoint.to_a.join(',')}>, <#{zeroPoint.to_a.join(',')}> , #{cylRad} }") puts(" sphere { <#{zeroPoint.to_a.join(',')}> , #{sphRad} }") end 0.upto($q-1) do |qi| v = qi*(2*Math::PI)/$q zeroPoint = lastPoint = functionname(0,v) for ui in 1..($numObj-1) u = ui*(2*Math::PI)/$numObj currentPoint = functionname(u,v) puts(" cylinder { <#{lastPoint.to_a.join(',')}>, <#{currentPoint.to_a.join(',')}> , #{cylRad} }") puts(" sphere { <#{currentPoint.to_a.join(',')}> , #{sphRad} }") if (($doWhat == :pcyla) && ((ui % arrFrq) == 0)) then # Compute location of cone "tail" tmp = (lastPoint - currentPoint) tmp = conTal * tmp / tmp.magnitude + currentPoint puts(" cone { <#{lastPoint.to_a.join(',')}>, #{conRad} <#{tmp.to_a.join(',')}>, 0 }") end lastPoint = currentPoint end puts(" cylinder { <#{lastPoint.to_a.join(',')}>, <#{zeroPoint.to_a.join(',')}> , #{cylRad} }") puts(" sphere { <#{zeroPoint.to_a.join(',')}> , #{sphRad} }") end else puts "ERROR: Unknown 6th argument!" end
6.3. torus_knot.pov
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // @file torus_knot.pov // @author Mitch Richling http://www.mitchr.me // @Copyright Copyright 2013 by Mitch Richling. All rights reserved. // @brief Torus knot template.@EOL // @Std Povray_3.6 // // Draw a shiny plastic tube (from the include file tordat.txt) and a transparent torus. Primary application is // torus knots, but anything with a curve and torus may be drawn -- OK, anything with a torus. :) // //---------------------------------------------------------------------------------------------------------------------------------- #include "colors.inc" #include "textures.inc" union { union { #include "tordat.txt" texture { pigment { color Blue } finish { ambient .50 diffuse 0.05 reflection 0.07 specular 0.9 roughness 0.03 phong 1 phong_size 600 } } } torus { 3, 1 texture { pigment { rgbt <1,0,0,0.7> } finish { ambient 0.9 diffuse 0.05 specular 0.11 roughness 0.1 reflection 0.0 phong 0.3 phong_size 760 } } interior { ior 1.0 refraction 0.0 fade_distance 15 fade_power 1.5 caustics 1 } rotate <90,0,0> } rotate <55,14,25> } camera { location <0,0,-10> look_at <0,0,0> } light_source { < 0, 0,-30> color White } light_source { <30, 30,-30> color White } light_source { <30,-30,-30> color White } background { color White }
6.4. go.sh
Some effort has been made to make sure this script works on UNIX'ish systems (OS X & Linux) as well as on Windows under MSYS2. You may have to make adjustments for system differences – for example if you installed POV-Ray in a non-default different location.
#!/bin/bash # -*- Mode:Shell-script; Coding:utf-8; fill-column:132 -*- #################################################################################################################################### # @file go.sh # @author Mitch Richling http://www.mitchr.me # @Copyright Copyright 2013 by Mitch Richling. All rights reserved. # @brief Automate the generation of images for my web page of all prime indexed torus knots from 2 up to 11. @EOL # @Std bash CONVERT='TRUE' DISPLAY='TRUE' ##---------------------------------------------------------------------------------------------------------------------------------- for p in 2 3 5 7 11; do for q in 2 3 5 7 11; do # for p in 5; do # for q in 2; do if [ $p != $q ]; then echo Working on knot $p $q POVOPT=' -Q11 +A0.4 -AM2 -R5 ' # Uncomment the next line for faster renders #POVOPT=' ' SIZE='-W1920 -H1440' # Generate the knot geometry ruby ./mkTorusKnot.rb $p $q 1 3 1000 pcyl > tordat.txt # Render the image into a PNG file if [ -z "MSYSTEM" ]; then povray -D -P -W1920 -H1440 $POVOPT +Itorus_knot.pov +Otorus_knot_${p}_${q}.png else /c/Program\ Files/POV-Ray/v3.7/bin/pvengine.exe -D -P $SIZE $POVOPT +Itorus_knot.pov +Otorus_knot_${p}_${q}.png //EXIT fi rm tordat.txt if [ "$CONVERT" = 'TRUE' ]; then # Create the thumbnail by annotating the image and reducing the size s0="gravity northwest fill black text 1,11 'Torus Knot ($p,$q)'" convert torus_knot_${p}_${q}.png -pointsize 130 -draw "$s0" -resize 20% torus_knot_${p}_${q}_small.gif # Create the big image by annotating the image s1="gravity northwest fill black text 1,11 'Torus Knot ($p,$q)'" s2="gravity southeast fill black text 1,11 'By Mitch Richling'" convert torus_knot_${p}_${q}.png -pointsize 130 -draw "$s2" -draw "$s1" torus_knot_${p}_${q}_big.gif fi fi done done # Display the images if [ "$DISPLAY" = 'TRUE' ]; then if [ -z "MSYSTEM" ]; then pqiv -f `ls torus_knot_*.png torus_knot_*_big.gif | sort` torus_knot_*_small.gif & else explorer . fi fi