Version 0 of Drawing geometrical objects

Updated 2005-01-12 08:55:50 by AM

Arjen Markus (12 january 2005) From time to time I need to draw a diagram, preferably using some electronic medium, because then it can be incorporated in a document. I thoroughly dislike most drawing programs, as I am distracted by them: I want to make a horizontal line exactly horizontal, or the box should be shifted half a centimeter. In short: I spent too much time worrying about the layout, rather than the contents.

So, drawing via plot commands is much more to my liking: I can sketch the diagram on paper, using a pencil, figure out the coordinates and type the commands into a small file. Then inspect the result visually and fiddle a bit. Done.

Except that determining the coordinates can be nasty.

Here is a simple script that illustrates a way out:

  • define points and lines and circles as individual objects
  • allow their manipulation via simple computations and operations

Okay, nothing grant (yet). But it was a one-hour job to get it up and running from scratch :)


 # draw_geometry.tcl --
 #    Draw and manipulate (plane) geometrical objects. Well, it is
 #    merely an illustration of what I mean by "geometrical drawing app"
 #

 # PixelPoint --
 #    Compute pixel coordinates
 # Arguments:
 #    x         X-coordinate/point
 #    y         Y-coordinate/point
 #    type      Type of object
 # Result:
 #    List of pixel coordinates
 #
 proc PixelPoint {x {y {}} {type {}}} {
     if { $y == {} } {
         set px [lindex $x 1]
         set py [lindex $x 2]
         return [list [expr {$px*100+200}] [expr {-$py*100+200}]]
     }
     if { $type == "oval" } {
         return [list [expr {$x*100+200-2}] [expr {-$y*100+200-2}] \
                      [expr {$x*100+200+2}] [expr {-$y*100+200+2}]]
     }
 }

 # point --
 #    Create (and draw) a point at given coordinates
 # Arguments:
 #    x         X-coordinate
 #    y         Y-coordinate
 # Result:
 #    A point at the given coordinates
 #
 proc point {x y} {
     .c create oval [PixelPoint $x $y oval] -fill black
     return [list POINT $x $y]
 }

 # line --
 #    Create (and draw) a line through two points
 # Arguments:
 #    point1    First point
 #    point2    Second point
 # Result:
 #    A line through the two points
 #
 proc line {point1 point2} {
     .c create line [concat [PixelPoint $point1] [PixelPoint $point2]] -fill black
     return [list LINE $point1 $point2]
 }

 # circle --
 #    Create (and draw) a circle at given coordinates
 # Arguments:
 #    point     Centre of the circle
 #    rad       Radius
 # Result:
 #    A circle at the given centre and given radius
 #
 proc circle {point rad} {
     set x  [lindex $point 1]
     set y  [lindex $point 2]
     set p1 [list POINT [expr {$x+$rad}] [expr {$y+$rad}]]
     set p2 [list POINT [expr {$x-$rad}] [expr {$y-$rad}]]
     .c create oval [concat [PixelPoint $p1] [PixelPoint $p2]] -outline black
     return [list CIRCLE $point $rad]
 }

 # distance --
 #    Compute the distance between two objects
 # Arguments:
 #    obj1      Point, line, ...
 #    obj2      Point, line, ...
 # Result:
 #    Distance between the given objects (now: only points)
 #
 proc distance {obj1 obj2} {
     if { [lindex $obj1 0] == "POINT" } {
         set px1 [lindex $obj1 1]
         set py1 [lindex $obj1 2]
         if { [lindex $obj2 0] == "POINT" } {
             set px2 [lindex $obj2 1]
             set py2 [lindex $obj2 2]
             return [expr {hypot($px2-$px1,$py2-$py1)}]
         } else {
             error "Types unsupported"
         }
     } else {
         error "Types unsupported"
     }
 }

 # inprod --
 #    Compute the inproduct of two vectors
 # Arguments:
 #    vect1     First vector
 #    vect2     Second vector
 # Result:
 #    Inproduct
 #
 proc inprod {vect1 vect2} {
     set vx1 [lindex $vect1 1]
     set vy1 [lindex $vect1 2]
     set vx2 [lindex $vect2 1]
     set vy2 [lindex $vect2 2]

     return [expr {$vx1*$vx2+$vy1*$vy2}]
 }

 # pointonline --
 #    Compute the coordinates of a point on a line
 # Arguments:
 #    line      Line in question
 #    lambda    Parameter value
 # Result:
 #    Point on the line
 #
 proc pointonline {line lambda} {
     set v   [vectorfromline $line]

     set vx  [lindex $v 1]
     set vy  [lindex $v 2]
     set px  [lindex $line 1 1]
     set py  [lindex $line 1 2]
     set x   [expr {$px+$lambda*$vx}]
     set y   [expr {$py+$lambda*$vy}]

     return [point $x $y] ;# Make it visible
 }

 # vectorfromline --
 #    Compute the directional vector of a line
 # Arguments:
 #    line      Line in question
 # Result:
 #    Vector in the direction of the line
 #
 proc vectorfromline {line} {
     set px1 [lindex $line 1 1]
     set py1 [lindex $line 1 2]
     set px2 [lindex $line 2 1]
     set py2 [lindex $line 2 2]
     set vx  [expr {$px2-$px1}]
     set vy  [expr {$py2-$py1}]

     return [list VECTOR $vx $vy]
 }

 # diffvector --
 #    Compute the vector from one point to the next
 # Arguments:
 #    point1    First point
 #    point2    Second point
 # Result:
 #    Vector
 #
 proc diffvector {point1 point2} {
     set px1 [lindex $point1 1]
     set py1 [lindex $point1 2]
     set px2 [lindex $point2 1]
     set py2 [lindex $point2 2]
     set vx  [expr {$px2-$px1}]
     set vy  [expr {$py2-$py1}]

     return [list VECTOR $vx $vy]
 }

 # normal --
 #    Compute the normal vector to another vector or a line
 # Arguments:
 #    obj       Directed object
 # Result:
 #    Vector normal to the direction of the object
 #
 proc normal {obj} {
     if { [lindex $obj 0] == "LINE" } {
         set obj [vectorfromline $obj]
     }

     set vy  [expr {-[lindex $obj 1]}]
     set vx  [lindex $obj 2]
     set len [expr {hypot($vx,$vy)}]

     return [list VECTOR [expr {$vx/$len}] [expr {$vy/$len}]]
 }

 # intersect --
 #    Compute the intersection between two objects
 # Arguments:
 #    obj1      line, circle, ...
 #    obj2      line, circle, ...
 # Result:
 #    One point or a collection of points (now: only lines)
 #
 proc intersect {obj1 obj2} {
     if { [lindex $obj1 0] == "LINE" } {
         #
         # Construct the equation for the line obj1
         #
         set n1 [normal $obj1]
         set p1 [lindex $obj1 1]

         if { [lindex $obj2 0] == "LINE" } {
             #
             # Get the parametrisation of the line obj2
             #
             set v2     [vectorfromline $obj2]
             set p2     [lindex $obj2 1]
             set lambda [expr {[inprod [diffvector $p2 $p1] $n1]/ \
                               [inprod $v2 $n1]}]
             return [pointonline $obj2 $lambda]
         } else {
             error "Types unsupported"
         }
     } else {
         error "Types unsupported"
     }
 }

 #
 # Create the standard canvas
 #
 pack [canvas .c -width 400 -height 400 -bg white]

 #
 # Simple illustration:
 # Define two lines, get their intersection and draw a circle with that
 # point as the centre.
 #

 set P1 [point 0 0]
 set P2 [point 1 1]
 set P3 [point 1 0.5]
 set P4 [point 0.1 0.5]
 set L1 [line $P1 $P2]
 set L2 [line $P3 $P4]
 set P5 [intersect $L1 $L2]
 puts $P5
 set r  [distance $P2 $P1]
 set C  [circle $P5 $r]

[ Category Graphics

category Mathematics

]