Keith Vetter 2016-11-22 : Here's a fun extension of
symdoodle that lets you vary the number of axes that the line will get reflected around. It creates a kind of kaleidoscope affect.
One interesting technical note: I originally thought I'd need to find the nearest axis for each point and take the offsets from that and apply to all the other axes. But I realized I could just compute the offsets from any of the axes, e.g. the x-axis, and apply that offset all around.
package require Tk
set S(size) 800
set S(axis,color) grey
set S(bg,color) white
set S(colors) {purple magenta red orange yellow green blue cyan white black}
set g(color) magenta
set g(pen,size) 4
set g(axes) 21
set g(show,axis) 1
proc main {} {
global S g
wm title . "Super SymDoodle"
frame .f -relief sunken -borderwidth 2
foreach color $S(colors) {
checkbutton .f.b$color -width 3 -text "" -variable g(color,$color) -bg $color \
-command [list NewColor $color]
bind .f.b$color <3> [list .c config -bg $color]
}
::ttk::button .f.c -text C -width 0 -command {.c delete line} -takefocus 0
::ttk::button .f.h -text X -width 0 -command ToggleAxis -takefocus 0
scale .f.pen -from 1 -to 20 -variable g(pen,size) -orient h -bd 2 -relief ridge \
-showvalue 0 -command [list NewScaleValue .f.pen "Pen size: "]
scale .f.axis -from 2 -to 50 -variable g(axes) -orient h -bd 2 -relief ridge \
-showvalue 0 -command [list NewScaleValue .f.axis "Axis: "]
bind .f.axis <ButtonRelease-1> DrawAxis
pack {*}[winfo children .f] -side left -fill y
foreach child [winfo children .f] {
if {$child ni [info commands .f.b*]} {
pack config $child -padx {2mm 0}
}
}
canvas .c -height $S(size) -width $S(size) -bg $S(bg,color) -bd 0 -highlightthickness 0
bind .c <1> {penDown %W %x %y}
bind .c <B1-Motion> {penMove %W %x %y}
bind .c <Configure> {Recenter %W %h %w}
DrawAxis
NewColor $g(color)
pack .f -side top -fill x
pack .c -side top -fill both -expand 1
}
proc NewColor {color} {
global g
foreach arr [array names g color,*] { set g($arr) 0 }
set g(color,$color) 1
set g(color) $color
}
proc NewScaleValue {w text value} {
$w config -label "$text$value"
}
proc ToggleAxis {} {
set ::g(show,axis) [expr {! $::g(show,axis)}]
DrawAxis
}
proc DrawAxis {} {
# Draw the g(axes) lines of symmetry and store in AXIS(...) the unit vector and its normal
global g AXIS S
.c delete axis
if { ! $g(show,axis)} return
for {set axis 0} {$axis < $g(axes)} {incr axis} {
set angle [expr {acos(-1) * $axis / $g(axes)}]
set AXIS(axis,$axis) [list [expr {cos($angle)}] [expr {sin($angle)}] ]
set AXIS(normal,$axis) [VNormal $AXIS(axis,$axis)]
set xy0 [VScale $AXIS(axis,$axis) 4000]
set xy1 [VScale $AXIS(axis,$axis) -4000]
.c create line [concat $xy0 $xy1] -tag axis -fill $S(axis,color)
}
}
proc penDown {w x y} {
global g
set x [$w canvasx $x]
set y [$w canvasy $y]
set xys [ReflectPoint $x $y]
set g(currentline,ids) {}
foreach xy $xys {
lassign $xy x y
set id [$w create line $x $y $x $y -fill $g(color) -tag line -width $g(pen,size)]
lappend g(currentline,ids) $id
}
}
proc penMove {w x y} {
global g
set x [$w canvasx $x]
set y [$w canvasy $y]
set xys [ReflectPoint $x $y]
foreach xy $xys id $g(currentline,ids) {
lassign $xy x y
eval $w coords $id [concat [$w coords $id] $x $y]
}
}
proc ReflectPoint {x y} {
# Return a list of points where x,y is reflected 4 ways around each axis
global AXIS g
set xys {}
for {set axis 0} {$axis < $g(axes)} {incr axis} {
foreach {dx dy} {1 1 1 -1 -1 1 -1 -1} {
set xx [expr {$x * $dx}]
set yy [expr {$y * $dy}]
set xy [VAdd [VScale $AXIS(axis,$axis) $xx] [VScale $AXIS(normal,$axis) $yy]]
lappend xys $xy
}
}
return $xys
}
proc Recenter {W h w} {
# Update the canvas's scrollregion to put point 0,0 into the middle
set h [expr {$h / 2.0}]
set w [expr {$w / 2.0}]
$W config -scrollregion [list -$w -$h $w $h]
}
proc VAdd {v1 v2 {scaling 1}} {
foreach {x1 y1} $v1 {x2 y2} $v2 break
return [list [expr {$x1 + $scaling*$x2}] [expr {$y1 + $scaling*$y2}]]
}
proc VScale {v scaling} {return [VAdd {0 0} $v $scaling]}
proc VNormal {v} { foreach {x y} $v break; return [list $y [expr {-1 * $x}]]}
main
return