Updated 2011-09-25 06:35:30 by RLE
 # hoco.tcl --
 # 
 # Part of: Useless Widgets Package
 # Contents: homogeneous coordinates procedures
 # Date: Wed Nov 24, 2004
 # 
 # Abstract
 # 
 # 
 # 
 # Copyright (c) 2004 Marco Maggi
 # 
 # The author  hereby grant permission to use,  copy, modify, distribute,
 # and  license this  software  and its  documentation  for any  purpose,
 # provided that  existing copyright notices  are retained in  all copies
 # and that  this notice  is included verbatim  in any  distributions. No
 # written agreement, license, or royalty  fee is required for any of the
 # authorized uses.  Modifications to this software may be copyrighted by
 # their authors and need not  follow the licensing terms described here,
 # provided that the new terms are clearly indicated on the first page of
 # each file where they apply.
 # 
 # IN NO  EVENT SHALL THE AUTHOR  OR DISTRIBUTORS BE LIABLE  TO ANY PARTY
 # FOR  DIRECT, INDIRECT, SPECIAL,  INCIDENTAL, OR  CONSEQUENTIAL DAMAGES
 # ARISING OUT  OF THE  USE OF THIS  SOFTWARE, ITS DOCUMENTATION,  OR ANY
 # DERIVATIVES  THEREOF, EVEN  IF THE  AUTHOR  HAVE BEEN  ADVISED OF  THE
 # POSSIBILITY OF SUCH DAMAGE.
 # 
 # THE  AUTHOR  AND DISTRIBUTORS  SPECIFICALLY  DISCLAIM ANY  WARRANTIES,
 # INCLUDING,   BUT   NOT  LIMITED   TO,   THE   IMPLIED  WARRANTIES   OF
 # MERCHANTABILITY,    FITNESS   FOR    A    PARTICULAR   PURPOSE,    AND
 # NON-INFRINGEMENT.  THIS  SOFTWARE IS PROVIDED  ON AN "AS  IS" BASIS,
 # AND  THE  AUTHOR  AND  DISTRIBUTORS  HAVE  NO  OBLIGATION  TO  PROVIDE
 # MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
 #
 
 #page
 ## ------------------------------------------------------------
 ## Math procedures.
 ## ------------------------------------------------------------
 
 proc uwp_math_deg2rad { angle } {expr {double($angle)*0.0174532925199}}
 proc uwp_math_rad2deg { angle } {expr {double($angle)*57.2957795131}}
 proc uwp_math_sin_cos { angle nickname } {
     uplevel [list set cos_$nickname [expr {cos(double($angle))}]]
     uplevel [list set sin_$nickname [expr {sin(double($angle))}]]
 }
 proc uwp_math_evallist { expr_list } {
     foreach expr $expr_list { lappend matrix [uplevel [list expr $expr]] }
     return $matrix
 }
 interp alias {} uwp_math_fundamental_rotation_around_z {} \
     uwp_math_fundamental_rotation t \
     {{$cos_t} {-$sin_t} 0 0  {$sin_t} {$cos_t} 0 0 0 0 1 0  0 0 0 1}
 interp alias {} uwp_math_fundamental_rotation_around_x {} \
     uwp_math_fundamental_rotation p \
     {1 0 0 0  0 {$cos_p} {-$sin_p} 0  0 {$sin_p} {$cos_p} 0 0 0 0 1}
 interp alias {} uwp_math_fundamental_rotation_around_y {} \
     uwp_math_fundamental_rotation f \
     {{$cos_f} 0 {$sin_f} 0  0 1 0 0  {-$sin_f} 0 {$cos_f} 0 0 0 0 1}
 proc uwp_math_fundamental_rotation { alias expression angle } {
     uwp_math_sin_cos $angle $alias
     uwp_math_evallist $expression
 }
 proc uwp_math_fundamental_translation {x y z} {list 1 0 0 $x 0 1 0 $y 0 0 1 $z 0 0 0 1}
 # The "transform_list" argument is a list of the form:
 #
 # {
 #   { premultiplication_boolean { a11 a12 a13 ... a44 }
 #   { premultiplication_boolean { b11 b12 b13 ... b44 }
 #   { premultiplication_boolean { c11 c12 c13 ... c44 }
 #   ...
 # }
 #
 # matrices are applied in the same order in which the appear in the
 # list.
 proc uwp_math_transformation { transform_list coords } {
     set matrix_elms {a11 a12 a13 a14  a21 a22 a23 a24  a31 a32 a33 a34  a41 a42 a43 a44}
     foreach transform $transform_list {
         foreach {premultiplication matrix} $transform {
             foreach $matrix_elms $matrix {}
             set result {}
             foreach {x y z other} $coords {
                 if { $premultiplication } {
                     lappend result \
                         [expr {double($x)*double($a11)+double($y)*double($a12)+
                                double($z)*double($a13)+double($other)*double($a14)}] \
                         [expr {double($x)*double($a21)+double($y)*double($a22)+
                                double($z)*double($a23)+double($other)*double($a24)}] \
                         [expr {double($x)*double($a31)+double($y)*double($a32)+
                                double($z)*double($a33)+double($other)*double($a34)}] \
                         [expr {double($x)*double($a41)+double($y)*double($a42)+
                                double($z)*double($a43)+double($other)*double($a44)}]
                 } else {
                     lappend result \
                         [expr {double($x)*double($a11)+double($y)*double($a21)+
                                double($z)*double($a31)+double($other)*double($a41)}] \
                         [expr {double($x)*double($a12)+double($y)*double($a22)+
                                double($z)*double($a32)+double($other)*double($a42)}] \
                         [expr {double($x)*double($a13)+double($y)*double($a23)+
                                double($z)*double($a33)+double($other)*double($a43)}] \
                         [expr {double($x)*double($a14)+double($y)*double($a24)+
                                double($z)*double($a34)+double($other)*double($a44)}]
                 }
             }
             set coords $result
         }
     }
     set result {}
     foreach { x y z o } $coords {
         if { $o != 1.0 } {
             set x [expr {$x/$o}]
             set y [expr {$y/$o}]
         }
         lappend result $x $y
     }
     return $result
 }
 #page
 ## ------------------------------------------------------------
 ## Token management.
 ## ------------------------------------------------------------
 
 set uwp_counter 0
 array set uwp_token_map {}
 proc uwp_token_get { ns key id } { uwp_token_access $ns $id; return $data($key) }
 proc uwp_token_declare { ns id } {
     upvar        \#0 uwp_token_map map uwp_counter counter
     set map($ns:$id) uwp__[incr counter]
 }
 proc uwp_token_forget { ns id } {
     upvar        \#0 uwp_token_map map
     unset -nocomplain $map($ns:$id)
     unset -nocomplain map($ns:$id)
 }
 proc uwp_token_access { ns id } {
     upvar        \#0 uwp_token_map map
     uplevel [list upvar \#0 $map($ns:$id) data]
 }
 proc uwp_token_aliases { prefix namespace } {
     foreach c {declare forget access get} {
         interp alias {} $prefix$c {} uwp_token_$c $namespace
     }
 }
 #page
 ## ------------------------------------------------------------
 ## Transform variables and declarations.
 ## ------------------------------------------------------------
 
 uwp_token_aliases uwp_hoco_transform_token_ hoco_tran
 
 # Transform type attributes:
 # -names        = the list of parameter names
 # -deg2rad        = a list of boolean declaring if the parameter requires
 #                 conversion from degrees to radians
 # -defaults        = the list of default values for the parameters
 # -transform        = the name of the procedure that converts the parameters
 #                  into the transformation
 proc uwp_hoco_transform_declare { id args } {
     uwp_hoco_transform_token_declare $id
     uwp_hoco_transform_token_access $id
     array set data $args
 }
 interp alias {} uwp_hoco_transform_forget {} uwp_hoco_transform_token_forget
 proc uwp_hoco_transform_compute { id parameters } {
     uwp_hoco_transform_token_access $id
     foreach name $data(-names) deg2rad $data(-deg2rad) value $parameters \
         { lappend values [expr {($deg2rad)? [uwp_math_deg2rad $value] : $value}] }
     $data(-transform) $data(-names) $values
 }
 proc uwp_hoco_transform_get_parameter_name_index { id parm_name } {
     uwp_hoco_transform_token_access $id
     lsearch $data(-names) $parm_name
 }
 interp alias {} uwp_hoco_transform_get_parameter_defaults \
     {} uwp_hoco_transform_token_get -defaults
 interp alias {} uwp_hoco_transform_get_parameter_names \
     {} uwp_hoco_transform_token_get -names
 #page
 ## ------------------------------------------------------------
 ## Default transformation matrix procedures.
 ## ------------------------------------------------------------
 
 proc uwp_hoco_canvas_parameters_to_transform { names values } {
     foreach $names $values {}
     uwp_math_sin_cos $theta t
     list [list yes [uwp_math_evallist \
         {{$cos_t} {-$sin_t} 0 {$x} {-$sin_t} {-$cos_t} 0 {$y} 0 0 1 0 0 0 0 1}]]
 }
 proc uwp_hoco_homogeneous_parameters_to_transform { names values } {
     foreach $names $values {}
     foreach { axis angleName mode } { z theta yes y phi yes x psi no } {
         lappend result [list $mode \
             [uwp_math_fundamental_rotation_around_$axis [set $angleName]]]
     }
     lappend result [list yes [uwp_math_fundamental_translation $x $y $z]]
 }
 proc uwp_hoco_workspace_parameters_to_transform { names values } {
     foreach $names $values {}
     foreach { axis angleName mode } { y phi yes x psi no } {
         lappend result [list $mode \
             [uwp_math_fundamental_rotation_around_$axis [set $angleName]]]
     }
     return $result
 }
 proc uwp_hoco_dh_parameters_to_transform { names values } {
     foreach $names $values {}
     uwp_math_sin_cos $theta t
     uwp_math_sin_cos $alpha a
     list [list yes [uwp_math_evallist {
         {$cos_t}  {-($cos_a*$sin_t)}  {$sin_a*$sin_t}     {double($a)*$cos_t}
         {$sin_t}  {$cos_a*$cos_t}     {-($sin_a*$cos_t)}  {double($a)*$sin_t}
         0         {$sin_a}            {$cos_a}            {double($d)}
         0 0 0 1}]]
 }
 proc uwp_hoco_perspective_parameters_to_transform { names values } {
     foreach $names $values {}
     list [list yes [uwp_math_evallist {
         1 0 0 0
         0 1 0 0
         0 0 1 0
         0 0 {-1.0/double($d)} 1
     }]]
 }
 #page
 ## ------------------------------------------------------------
 ## Default transform types.
 ## ------------------------------------------------------------
 
 uwp_hoco_transform_declare canvas \
     -names { theta x y } -deg2rad { yes no no } -defaults { 0.0 300.0 300.0 } \
     -transform uwp_hoco_canvas_parameters_to_transform
 
 uwp_hoco_transform_declare homogeneous \
     -names { theta psi phi x y z } -deg2rad { yes yes yes no no no } \
     -defaults { 0.0 0.0 0.0  0.0 0.0 0.0 } \
     -transform uwp_hoco_homogeneous_parameters_to_transform
 
 uwp_hoco_transform_declare workspace \
     -names { psi phi } -deg2rad { yes yes } -defaults { 0.0 0.0 } \
     -transform uwp_hoco_workspace_parameters_to_transform
 
 uwp_hoco_transform_declare dh \
     -names { d theta a alpha } -deg2rad { no yes no yes } \
     -defaults { 0.0 0.0 0.0 0.0 } \
     -transform uwp_hoco_dh_parameters_to_transform    
 
 uwp_hoco_transform_declare perspective \
     -names { d } -deg2rad { no } \
     -defaults { 1700 } \
     -transform uwp_hoco_perspective_parameters_to_transform    
 
 #page
 ## ------------------------------------------------------------
 ## Transform instances procedures.
 ## ------------------------------------------------------------
 
 uwp_token_aliases uwp_hoco_instance_token_ hoco_inst
 
 # Transform instance attributes:
 # -type                = the name of a transform type
 # -parameters        = a list holding the current values of the parameters
 # -transform        = the transformation
 # -dynamic        = the list of parameters that are modifiable in this
 #                  transform instance, it must be a sub-set of the
 #                  transform type parameters or the empty string
 proc uwp_hoco_instance_declare { id args } {
     uwp_hoco_instance_token_declare $id
     uwp_hoco_instance_token_access $id
     array set data $args
     if { ! [info exists data(-parameters)] } {
         set data(-parameters) \
             [uwp_hoco_transform_get_parameter_defaults $data(-type)]
     }
     uwp_hoco_instance_update_transform $id
 }
 interp alias {} uwp_hoco_instance_forget {} uwp_hoco_instance_token_forget
 proc uwp_hoco_instance_update_transform { id } {
     uwp_hoco_instance_token_access $id
     set data(transform) \
         [uwp_hoco_transform_compute $data(-type) $data(-parameters)]
 }
 proc uwp_hoco_instance_update_parameter { id parm_name parm_value } {
     uwp_hoco_instance_token_access $id
     set idx [uwp_hoco_transform_get_parameter_name_index $data(-type) $parm_name]
     lset data(-parameters) $idx $parm_value
     uwp_hoco_instance_update_transform $id
 }
 proc uwp_hoco_instance_get_transform { transform_names } {
     foreach id $transform_names \
         { uwp_hoco_instance_token_access $id; lappend result $data(transform) }
     return [join $result]
 }
 proc uwp_hoco_instance_get_all_parameter_names { id } {
     uwp_hoco_instance_token_access $id
     uwp_hoco_transform_get_parameter_names $data(-type)
 }
 proc uwp_hoco_instance_get_dynamic_parameter_names { id } {
     uwp_hoco_instance_token_access $id
     return $data(-dynamic)
 }
 proc uwp_hoco_instance_get_parameter_value { id parm_name } {
     uwp_hoco_instance_token_access $id
     set idx [uwp_hoco_transform_get_parameter_name_index $data(-type) $parm_name]
     lindex $data(-parameters) $idx
 }
 #page
 ## ------------------------------------------------------------
 ## Default transform instances.
 ## ------------------------------------------------------------
 
 uwp_hoco_instance_declare canvas -type canvas -dynamic {}
 uwp_hoco_instance_declare world  -type homogeneous -dynamic { theta phi psi }
 
 #page
 ## ------------------------------------------------------------
 ## Basic graphical elements.
 ## ------------------------------------------------------------
 
 # Prototype of drawing procedure:
 #
 #        proc widget_canvas_draw { command coords {main_tag {}} {tags {}} }
 #
 # command        = what to draw (line, polygon, ...)
 # coords        = the list of homogeneous coordinates of the points
 # main_tag        = the main tag of the object: the one used to delete it
 # tags                = optional list of tags
 
 proc uwp_wireframe_draw_reference_frame { frame_tag transforms element } {
     set axis_template {{-1 0 0 1  1 0 0 1} {0 -1 0 1  0 1 0 1} {0 0 -1 1  0 0 1 1}}
     foreach coords $axis_template axis_tag { xaxis yaxis zaxis } {
         widget_canvas_draw line \
             [uwp_math_transformation \
                  [concat [list $element] \
                       [uwp_hoco_instance_get_transform $transforms]] \
                  $coords] ${frame_tag}_$axis_tag \
             [list $frame_tag reference_frame_$axis_tag reference_frame]
     }
 }
 proc uwp_wireframe_draw_prism { prism_tag transforms element {tags {}} } {
     foreach {face coords} {
         A {-1 -1 -1 1        1 -1 -1 1        1 1 -1 1        -1 1 -1 1        -1 -1 -1 1}
          B {-1 -1 1 1        1 -1 1 1        1 1 1 1                -1 1 1 1        -1 -1 1 1}
         C {-1 -1 -1 1        -1 -1 1 1        -1 1 1 1        -1 1 -1 1        -1 -1 -1 1}
         D {1 -1 -1 1        1 -1 1 1        1 1 1 1                1 1 -1 1        1 -1 -1 1}
     } {
         widget_canvas_draw line [uwp_math_transformation \
              [concat [list $element] [uwp_hoco_instance_get_transform $transforms]] \
                 $coords] ${prism_tag}_$face $tags
     }
 }
 #page
 proc uwp_wireframe_draw_workspace { item_tag transforms } {
     uwp_wireframe_draw_plane ${item_tag}_XY $transforms { 0 4 5   1 5 4 } \
         [list workspace_XY_plane $item_tag]
     uwp_wireframe_draw_plane ${item_tag}_YZ $transforms { 1 5 6   2 6 5 } \
         [list workspace_YZ_plane $item_tag]
     uwp_wireframe_draw_plane ${item_tag}_ZX $transforms { 2 6 0   4 0 6 } \
         [list workspace_ZX_plane $item_tag]
 }
 proc uwp_wireframe_draw_plane { main_tag transforms indices {tags {}} } {
     set delta 30.0
     set num 11
     set max [expr {$delta*double($num-1)}]
     set coords { 0 0 0 1  0 0 0 1}
     set counter 0
     foreach { a b c } $indices {
         set vector $coords
         set plane_tag ${main_tag}_[incr counter]
         for {set x 0.0} {$x <= $max} {set x [expr {$x+$delta}]} {
             lset vector $a $x
             lset vector $b $x
             lset vector $c $max
             widget_canvas_draw line \
                 [uwp_math_transformation \
                      [uwp_hoco_instance_get_transform $transforms] $vector] \
                 ${plane_tag}_$x [concat [list $main_tag] $tags]
         }
     }
 }
 #page
 proc uwp_wireframe_regular_polygon { number_of_points } {
     set angle [expr {[uwp_math_deg2rad 360.0]/double($number_of_points)}]
     lappend coords 1 0 0 1
     for {set i 1} {$i < $number_of_points} {incr i} {
         lappend coords \
             [expr {cos(double($angle)*double($i))}] \
             [expr {sin(double($angle)*double($i))}] 0 1
     }
     lappend coords 1 0 0 1
 }
 proc uwp_wireframe_draw_path { coords transforms element tag {tags {}} } {
     widget_canvas_draw line \
         [uwp_math_transformation \
              [concat [list $element] \
                   [uwp_hoco_instance_get_transform $transforms]] \
              $coords] $tag $tags
 }
 
 ### end of file
 # Local Variables:
 # mode: tcl
 # End:

See also: Playing with planes in 3D which uses this code.