Updated 2013-08-19 10:34:19 by RLE

Keith Vetter 2006-09-08 : an optical illusion invented by Misha Pavel.

 ##+##########################################################################
 #
 # Bulging Squares.tcl -- Optical illusion by Misha Pavel
 # by Keith Vetter, September 2006
 # http://www.cut-the-knot.org/SimpleGames/CommonThing.shtml
 
 package require Tk
 catch {package require tile}                    ;# Brute force in tile
 catch {namespace import -force ::ttk::*}
 
 array set S {title "Bulging Squares" occlusion Squares object Square
    opacity 0 delay 20 step 2 r 40 r2 80 r3 74}
 set PI [expr {acos(-1)}]
 
 proc DoDisplay {} {
    wm title . $::S(title)
 
    frame .ctrl
    canvas .c -bd 2 -relief ridge
    bind .c <Configure> {ReCenter %W %h %w}
 
    labelframe .object -text "Object"
    foreach what {Square Triangle Pentagon Star "Hex Star"} {
        set w ".object.[string tolower $what]"
        radiobutton $w -text $what -variable S(object) -value $what \
            -command {Go object}
        pack $w -side top -anchor w
    }
    labelframe .occlusion -text "Occlusion"
    foreach what {Squares "5 Triangles" "6 Triangles"} {
        set w ".occlusion.[string tolower $what]"
        radiobutton $w -text $what -variable S(occlusion) -value $what \
            -command {Go occlusion}
        pack $w -side top -anchor w
    }
    checkbutton .opacity -text "Translucent" -variable S(opacity) -command DoOpacity
    button .about -text About -command About
 
    pack .ctrl -side right -fill y -padx 5 -pady 5
    pack .c -side top -fill both -expand 1
    pack .object .occlusion -in .ctrl -side top -fill x
    pack .opacity -side top -in .ctrl -fill x -pady 10
    pack .about -side bottom -in .ctrl -fill x -pady 10
 }
 proc ReCenter {W h w} {                         ;# Called by configure event
    set h2 [expr {$h / 2}] ; set w2 [expr {$w / 2}]
    $W config -scrollregion [list -$w2 -$h2 $w2 $h2]
    Resize
 }
 proc Go {who} {
    DrawObject
    DrawOcclusion
    .c raise occ
    Resize
    DoOpacity
 }
 proc DrawObject {} {
    global S
 
    .c delete obj
 
    set skip 1
    if {$S(object) eq "Square"} { set n 4}
    if {$S(object) eq "Triangle"} { set n 3}
    if {$S(object) eq "Pentagon"} { set n 5}
    if {$S(object) eq "Star"} { set n 5; set skip 2}
    if {$S(object) eq "Hex Star"} { set n 3}
    set xy {}
    set xy2 {}
    for {set i 0} {$i < $n} {incr i} {
        set x [expr {$S(r) * cos($i * $skip * 2 * $::PI / $n)}]
        set x2 [expr {-$S(r) * cos($i * $skip * 2 * $::PI / $n)}]
        set y [expr {$S(r) * sin($i * $skip * 2 * $::PI / $n)}]
        set y2 [expr {-$S(r) * sin($i * $skip * 2 * $::PI / $n)}]
        lappend xy $x $y
        lappend xy2 $x2 $y2
    }
    .c create poly $xy -fill \#04B204 -width 0 -tag obj
    if {$S(object) eq "Hex Star"} {
        .c create poly $xy2 -fill \#04B204 -width 0 -tag obj
    }
 }
 proc DrawOcclusion {} {
    global S
 
    .c delete occ
    if {[string match {[56] Triangles} $S(occlusion)]} {
        set n [lindex $S(occlusion) 0]
        DrawOccludintTriangles $n
        return
    }
    set a $S(r2)
    set b [expr {$S(r2) - $S(r3)}]
    set xy [list -$a -$a -$b -$b]
    .c create rect $xy -fill red -width 0 -tag occ
    set xy [list $a -$a $b -$b]
    .c create rect $xy -fill red -width 0 -tag occ
 
    set xy [list -$a $a -$b $b]
    .c create rect $xy -fill red -width 0 -tag occ
    set xy [list $a $a $b $b]
    .c create rect $xy -fill red -width 0 -tag occ
 }
 proc DrawOccludintTriangles {n} {
    global S
 
    for {set i 0} {$i < $n} {incr i} {
        set a0 [expr {$i * 2 * $::PI / $n}]
        set a1 [expr {($i+1) * 2 * $::PI / $n}]
        set a2 [expr {($i+.5) * 2 * $::PI / $n}]
 
        set x0 [expr {$S(r2) * cos($a0)}]
        set y0 [expr {$S(r2) * sin($a0)}]
        set x1 [expr {$S(r2) * cos($a1)}]
        set y1 [expr {$S(r2) * sin($a1)}]
        set xy [list 0 0 $x0 $y0 $x1 $y1]
        set id [.c create poly $xy -fill red -width 0 -tag occ ]
 
        set dx [expr {10 * cos($a2)}]
        set dy [expr {10 * sin($a2)}]
        .c move $id $dx $dy
    }
 
 }
 
 # From http://wiki.tcl.tk/CanvasRotation
 proc _RotateItem {w tagOrId Ox Oy angle} {
    set angle [expr {$angle * atan(1) * 4 / 180.0}] ;# Radians
    foreach id [$w find withtag $tagOrId] {     ;# Do each component separately
        set xy {}
        foreach {x y} [$w coords $id] {
            # rotates vector (Ox,Oy)->(x,y) by angle clockwise
 
            set x [expr {$x - $Ox}]             ;# Shift to origin
            set y [expr {$y - $Oy}]
 
            set xx [expr {$x * cos($angle) - $y * sin($angle)}] ;# Rotate
            set yy [expr {$x * sin($angle) + $y * cos($angle)}]
 
            set xx [expr {$xx + $Ox}]           ;# Shift back
            set yy [expr {$yy + $Oy}]
            lappend xy $xx $yy
        }
        $w coords $id $xy
    }
 }
 
 proc DoRotate {} {
    foreach aid [after info] { after cancel $aid }
    after $::S(delay) DoRotate
    _RotateItem .c obj 0 0 $::S(step)
 }
 proc DoOpacity {args} {
    .c itemconfig occ -stipple [expr {$::S(opacity) ? "gray50" : ""}]
 }
 proc Resize {} {
    set w [winfo width .c]
    set h [winfo height .c]
 
    foreach {x0 y0 x1 y1} [.c bbox all] break
    set sx [expr {($w-40)/2.0 / $x1}]
    set sy [expr {($h-40)/2.0 / $y1}]
    set sc [expr {$sx > $sy ? $sy : $sx}]
    .c scale all 0 0 $sc $sc
 }
 proc About {} {
    set msg "$::S(title)\nby Keith Vetter, September 2006\n\n"
    append msg "Optical illusion by Misha Pavel"
    tk_messageBox -message $msg -title "About $::S(title)"
 }
 
 ################################################################
 DoDisplay
 DoRotate
 Go all
 
 return

uniquename 2013aug18

There is one of aspect of this code that is not conveyed by the image above --- namely, the (green) polygon under the upper (red) polygons is animated --- the lower polygon is rotating. So people looking for code that performs a rotation of objects on the Tk canvas may find this code of interest.

Another feature of this code is shown in the following image --- this is an example of drawing 'translucent' versions of polygons. That technique of making objects on the canvas semi-transparent may be a nice trick to know about --- that is to say, it may be handy to know that this code provides an example of how to achieve that semi-transparent effect.

Another effect that is not clear from this static image is that the rotating 6-pointed star appears to be pulsating in-and-out (contracting and expanding) --- when the star is rotating under the SOLID triangles. But when you click on the 'translucent' checkbutton and the triangles become semi-transparent, it is seen the the 6-pointed star is simply rotating and not pulsating at all. So in this case, this is a 'Bulging Star Illusion', not a 'Bulging Square Illusion'. I think that the rotating star is more striking than the rotating square.