ZB 2023-06-26 Inspiration was the image on this page: https://pl.pinterest.com/pin/461759768057576269/
Jeff Smith 2023-07-16 : Below is an online demo using CloudTk. This demo runs "Fun with canvas 2" in an Alpine Linux Docker Container. It is a 27.4MB image which is made up of Alpine Linux + tclkit + Fun-with-canvas-2.kit + libx11 + libxft + fontconfig + ttf-linux-libertine. It is run under a user account in the Container. The Container is restrictive with permissions for "Other" removed for "execute" and "read" for certain directories.
ZB 2023-07-16 It works very good indeed. I hope soon TCL/Tk replaces Java(script) on WWW pages. ;)
#!/usr/bin/env tclsh package require Tk # We're going to use "expr" a lot, so let's suppress that verbosity a little # ("m" stands for "math") -- on a second thought: also "lindex --> l" rename expr m rename lindex l proc in { li me } { return [m {([lsearch $li $me] < 0) ? 0 : 1}] } proc ni { li me } { return [m {([lsearch $li $me] < 0) ? 1 : 0}] } proc firstFont {fontlist default} { set avail {} foreach f [font families] { lappend avail [string tolower $f] } foreach try $fontlist { if { [in $avail [string tolower $try]] } { return $try } } return $default } # Having x1, y1, x2, y2 calculate circle's center coordinates and its radius proc box2cir { x1 y1 x2 y2 } { set radius [m {($x2 - $x1)/2}] set xc [m {($x1+$x2)/2}] set yc [m {($y1+$y2)/2}] return [list $xc $yc $radius] } # Having center's x, y and radius calculate x1, y1, x2, y2 of the circle proc circ2box { xc yc radius } { set x1 [m {$xc-$radius}] set x2 [m {$xc+$radius}] set y1 [m {$yc+$radius}] set y2 [m {$yc-$radius}] return [list $x1 $y1 $x2 $y2] } # Cartesian to polar proc c2p { x y } { set r [m {sqrt($x * $x + $y * $y)}] set a [m {atan($y / $x)}] if { $x < 0 || $y < 0 } { set a [m {$a + 3.14159}] } if { $x < 0 && $y < 0 } { set a [m {$a + 3.14159}] } return [list $a $r] } # Polar to Cartesian proc p2c { a r } { return [list [m {$r * cos($a)}] [m {$r * sin($a)}]] } # deg to rad proc d2r { d } { return [m {$d * 3.14159 / 180}] } # rad to deg proc r2d { r } { return [m {$r * 180 / 3.14159 }] } proc prepareEnvironment { path orgX orgY {colour white} {direction ccw} } { global circlesTable axisTable set fontFamily [firstFont {Verdana Arial} Helvetica] set width [m {[$path cget -width] - $orgX*2}] set height [m {[$path cget -height] - $orgY*2}] set oneSeventh [m {$width/7}] set halfHeight [m {$height/2}] for { set i 0 } { $i < 8 } { incr i } { $path create line [m {$oneSeventh*$i+$orgX}] $orgY \ [m {$oneSeventh*$i+$orgX}] [m {$height+$orgY}] -fill $colour \ -tag "vbar_$i" } for { set i 0 } { $i < 3 } { incr i } { $path create line $orgX [m {$halfHeight*$i+$orgY}] \ [m {$orgX+$width}] [m {$halfHeight*$i+$orgY}] -fill $colour \ -tag "hbar_$i" } $path delete "vbar_6" # Axis of rotation ("Z" axis) -- its coords set zx [m {$orgX + $oneSeventh * 5 / 2 }] set zy [m {$orgY + $height / 2}] # Still outer circle set x1 [m {$orgX + $oneSeventh * 5 / 2 - $halfHeight}] set y1 $orgY set x2 [m {$orgX + $oneSeventh * 5 / 2 + $halfHeight}] set y2 [m {$orgY+$height}] $path create oval $x1 $y1 $x2 $y2 -outline $colour -tag "still" # Big rolling circle's coords (at 0 deg) set x1b [m {$x1 + ($x2-$x1) / 4}] set y1b [m {$y1 + ($y2-$y1) / 8}] set x2b $x2 set y2b [m {$y2 - ($y2-$y1) / 8}] $path create oval $x1b $y1b $x2b $y2b -outline $colour -tag "big" # Medium rolling circle's coords set x1m [m {$x1 + ($x2-$x1) / 2}] set y1m [m {$y1 + ($y2-$y1) / 4}] set x2m $x2 set y2m [m {$y2 - ($y2-$y1) / 4}] $path create oval $x1m $y1m $x2m $y2m -outline $colour -tag "medium" # Small rolling circle's coords set x1s [m {$x1 + 3*($x2-$x1) / 4}] set y1s [m {$y1 + 3*($y2-$y1) / 8}] set x2s $x2 set y2s [m {$y2 - 3*($y2-$y1) / 8}] $path create oval $x1s $y1s $x2s $y2s -outline $colour -tag "small" # Center of the small circle set x1c [m {($x1s+$x2s)/2}] set y1c [m {($y1s+$y2s)/2}] # "Satellite" set frac [m {($x2s-$x1s) / 8}] set x1sa [m {$x1c-$frac}] set y1sa [m {$y1c-$frac}] set x2sa [m {$x1c+$frac}] set y2sa [m {$y1c+$frac}] $path create oval $x1sa $y1sa $x2sa $y2sa -outline red -tag "satellite" # Moving bars $path create line $x1c $orgY $x1c [m {$height+$orgY}] -fill $colour \ -tag "vertical_bar" $path create line $orgX $y1c [m {$width+$orgX-$oneSeventh-$oneSeventh}] $y1c \ -fill $colour -tag "horizontal_bar" # The counters set xtpos [m {int(6.33 * $oneSeventh)}] set ytpos [m {int(0.4 * $halfHeight)}] set smallFontSize [m {int(0.1 * $halfHeight)}] set bigFontSize [m {int(0.3 * $halfHeight)}] $path create text $xtpos $ytpos -text "input" -fill $colour -tag "input" \ -font "$fontFamily $smallFontSize" $path create text $xtpos [m {$ytpos + 0.4 * $halfHeight}] -text "000" \ -fill $colour -tag "upper" -font "$fontFamily $bigFontSize" set ytpos [m {int($ytpos + $halfHeight)}] $path create text $xtpos $ytpos -text "output" -fill $colour -tag "output" \ -font "$fontFamily $smallFontSize" $path create text $xtpos [m {$ytpos + 0.4 * $halfHeight}] -text "000" \ -fill $colour -tag "lower" -font "$fontFamily $bigFontSize" # The first entry of the final circlesTable (for "0 deg") and axisTable lappend circlesTable [list $x1b $y1b $x2b $y2b $x1m $y1m $x2m $y2m \ $x1s $y1s $x2s $y2s $x1sa $y1sa $x2sa $y2sa] lappend axisTable [list $x1c $y1c] # Translation of coordinates set tmp1 [list] ; set tmp2 [list] for { set i 0 } { $i < 16 } { incr i 2 } { set j [m {$i + 1}] ;# Index for corresponding y lappend tmp1 [m {[l [l $circlesTable 0] $i] - $zx}] lappend tmp1 [m {$zy - [l [l $circlesTable 0] $j]}] } lappend tmp2 [m {[l [l $axisTable 0] 0] - $zx}] lappend tmp2 [m {$zy - [l [l $axisTable 0] 1]}] # "Box coordinates" to "center + radius" set tmp3 [list] set howMany [llength $tmp1] for { set i 0 } { $i < $howMany } { incr i 4 } { set j [m {$i + 1}] ; set k [m {$i + 2}] ; set l [m {$i + 3}] lappend tmp3 [box2cir [l $tmp1 $i] [l $tmp1 $j] [l $tmp1 $k] [l $tmp1 $l]] } while { [set zz [join $tmp3]] ne $tmp3 } { set tmp3 $zz } ;# Flatten the list # Cartesian to polar set tmp4 [list] set howMany [llength $tmp3] for { set i 0 } { $i < $howMany } { incr i 3 } { set j [m {$i + 1}] ; set k [m {$i + 2}] lappend tmp4 [list [c2p [l $tmp3 $i] [l $tmp3 $j]] [l $tmp3 $k]] } while { [set zz [join $tmp4]] ne $tmp4 } { set tmp4 $zz } ;# Flatten the list set tmp5 [c2p [l $tmp2 0] [l $tmp2 1]] # Now let's calculate all possible coordinates (step 1 deg) set howMany [llength $tmp4] for { set d 1 } { $d < 360 } { incr d } { if { $direction eq "ccw" } { set a [d2r $d] } else { set a [d2r [m {360 - $d}]] } set v [list] for { set i 0 } { $i < $howMany } { incr i 3 } { set j [m {$i + 1}] ; set k [m {$i + 2}] set xy [p2c [m {$a + [l $tmp4 $i]}] [l $tmp4 $j]] ;# New Cart. coords set xyxy [circ2box [l $xy 0] [l $xy 1] [l $tmp4 $k]] ;# x,y,r --> x1,y1,x2.y2 # Now back-translation lappend v [m {int([l $xyxy 0] + $zx)}] lappend v [m {int($zy - [l $xyxy 1])}] lappend v [m {int([l $xyxy 2] + $zx)}] lappend v [m {int($zy - [l $xyxy 3])}] } # Row completion lappend circlesTable $v # Same calculation for "axis" table set xya [p2c [m {$a + [l $tmp5 0]}] [l $tmp5 1]] ;# New axis coords lappend axisTable [list [m {int([l $xya 0] + $zx)}] [m {int($zy - [l $xya 1])}]] } } proc rollIt { path twinkle interval } { global circlesTable axisTable set satc [$path itemcget satellite -outline] set fgc [$path itemcget horizontal_bar -fill] set bgc [$path cget -background] set orgX [l [$path coords horizontal_bar] 0] set orgY [l [$path coords vertical_bar ] 1] set x2 [l [$path coords horizontal_bar] 2] set y2 [l [$path coords vertical_bar ] 3] set width [m {[$path cget -width] - $orgX*2}] set height [m {[$path cget -height] - $orgY*2}] set i 0 while { 1 } { $path coords big [lrange [l $circlesTable $i] 0 3] $path coords medium [lrange [l $circlesTable $i] 4 7] $path coords small [lrange [l $circlesTable $i] 8 11] $path coords satellite [lrange [l $circlesTable $i] 12 15] set x1 [l [l $axisTable $i] 0] set y1 [l [l $axisTable $i] 1] $path coords horizontal_bar $orgX $y1 $x2 $y1 $path coords vertical_bar $x1 $orgY $x1 $y2 if { ! [m {$i % 5}] } { $path itemconfigure upper -text $i $path itemconfigure lower -text [m {int(sin([l [l $circlesTable $i] 0])*180)}] } if { $twinkle } { if { ! [m {$i % 2}] } { $path itemconfigure horizontal_bar -fill $bgc $path itemconfigure vertical_bar -fill $bgc } else { $path itemconfigure horizontal_bar -fill $fgc $path itemconfigure vertical_bar -fill $fgc } if { ! [m {$i % 30}] } { $path itemconfigure satellite -outline $bgc } elseif { ! [m {$i % 35}] } { $path itemconfigure satellite -outline $satc } } update after $interval incr i set i [m {$i % 360}] } } # A table that'll hold the data of all points set circlesTable [list] # A list that'll will hold the data of rotation axis set axisTable [list] set canvasPath .c pack [canvas $canvasPath -height 600 -width 1150 -background black] bind . <Escape> { exit 0 } wm protocol . WM_DELETE_WINDOW { exit 0 } set Xmargin 50 ; set Ymargin 50 prepareEnvironment $canvasPath $Xmargin $Ymargin white cw ;# „cw” or „ccw” rollIt $canvasPath yes 20 ;# „yes”=twinkle, frames each 20 ms