2D Integer-based Rotations

George Peter Staplin April 1, 2006 - The code that follows performs 2D rotations by using scaling and a precomputed table. The table is generated using sin() and cos(), but it could easily be generated once, and the need for floating point completely eliminated.

Miguel Sofer responded to my question about how to do this, and quite graciously volunteered to write the majority of this code. I wrote the graphical part.


2D_integer-based_rotations_ScrShot


 #
 # This is a joint project --
 # by Miguel Sofer (primarily) and George Peter Staplin
 #

 package require Tk

 #
 # This is used by the test procedure to compare the irot result with drot.
 #
 proc drot {x y a} {
     set a [expr {($a*acos(0))/90}];# convert to radians
     set c [expr {cos($a)}]
     set s [expr {sin($a)}]
     list [expr {int(round($x*$c-$y*$s))}] [expr {int(round($x*$s+$y*$c))}]
 }


 # create the table
 set Cos {}; set Sin {}
 set M [expr {pow(2,30)}]
 set Coeff [expr {acos(0)/90}]
 for {set a 0} {$a <= 45} {incr a} {
     set A [expr {$Coeff*$a}]
     lappend Cos [expr {int($M*cos($A))}]
     lappend Sin [expr {int($M*sin($A))}]
 }
 set M [expr {int($M)}]

 proc ifun a {
     global Cos Sin

     set a [expr {$a%360}]
     # sign of sin and sign of cos
     set ss 1; set sc 1

     # Insure -180<$a<=180
     if {$a > 180} {
         set a [expr {$a-360}]
     }

     # Consider negative angles; after this 0<=$a<=180
     if {$a < 0} {
         set ss [expr {-$ss}]
         set a [expr {-$a}]
     }

     # Convert to first quadrant
     if {$a > 90} {
         set sc [expr {-$sc}]
        set a [expr {180-$a}]
     }

     # Lookup only the first 45 degrees
     if {$a <= 45} {
        set cos [expr {$sc*[lindex $Cos $a]}]
        set sin [expr {$ss*[lindex $Sin $a]}]
     } else {
        set a [expr {90-$a}]
        set cos [expr {$sc*[lindex $Sin $a]}]
        set sin [expr {$ss*[lindex $Cos $a]}]
     }

    list $cos $sin
 }

 proc irot {x y a} {
     global M
     foreach {c s} [ifun $a] break

     set c [expr {wide($c)}]
     set s [expr {wide($s)}]

     set xx [expr {int(($x*$c-$y*$s)/$M)}]
     set yy [expr {int(($x*$s+$y*$c)/$M)}]

     list $xx $yy
 }

 proc test {x y a} {
     list [irot $x $y $a] [drot $x $y $a]
 }

 proc point {win x y} {
     $win create rectangle $x $y [expr {$x + 4}] [expr {$y + 4}] -fill white
     $win create text [expr {$x + 5}] [expr {$y - 5}] -text "$x,$y" -fill white 
 }

 proc draw.circle {win radius centerx centery} {
     set lastx [expr {$centerx + $radius}]
     set lasty [expr {$centery + $radius}]

     for {set d 1} {$d < 360} {incr d} {
        set rot [irot $radius $radius $d]
        set x [lindex $rot 0] ; set y [lindex $rot 1]

        set x [expr {$x + $centerx}] ; set y [expr {$y + $centery}]

        $win create line $lastx $lasty $x $y -fill white
        set lastx $x ; set lasty $y
     }

     point $win $centerx $centery
 }

 proc main {} {
     wm title . "Miguel and George's Excellent Adventure!"
     pack [canvas .c -bg black] -fill both -expand 1

     draw.circle .c 50 100 100
     draw.circle .c 25 200 200
     draw.circle .c 50 300 100
 }
 main