Origin: http://wiki.tcl.tk/3494 Author: Keith Vetter
This is a fun little graphical animation which morphs an octahedron into an open cuboctahedron and back. Hidden within this code is actually a simple 3-d to 2-d transformation package.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 | #!/bin/sh
# The next line is executed by /bin/sh, but not Tcl \
exec wish $0 ${1+"$@"}
##+##########################################################################
#
# Octabug
#
# Animates the morphing of a octahedron into an open cuboctahedron.
# by Keith Vetter
#
# Revisions:
# KPV Mar 07, 1995 - initial revision
# KPV Jun 07, 2002 - some minor code clean up
#
##+##########################################################################
#
# do_display
#
# Sets up the display
#
proc do_display {} {
wm title . "Octabug"
canvas .c -relief raised -borderwidth 4
pack .c -side top
.c config -height 600 -width 600
xyz .eye "Eye Position" eye_ {5 4 3}
frame .buttons
button .anim -text Animate -command { set go [expr 1 - $go]; animate}
button .qbtn -text Quit -command exit
pack .buttons -side left -expand yes -fill both
pack .anim .qbtn -side top -expand yes -in .buttons
}
##+##########################################################################
#
# animate
#
# Sets things in motion
#
proc animate {} {
global go param
if $go {
set param [expr ($param + 1) % 100]
triag
after 1 animate
}
}
##+##########################################################################
#
# Triag
#
# Draws all 8 triangles of the octabug.
#
proc triag {} {
global mem param
set t $param
set t [expr $t*2.0/100] ;# Change to 0-2 range
set t1 $t ;# Remember
if {$t > 1} { set t [expr 2.0 - $t] } ;# Exploit symmetry
set t [expr $t + 1.0] ;# 1.0-2.0 range
.c delete poly
if [info exists mem($t1,a)] { ;# Did we memoize entry already?
set a $mem($t1,a)
set b $mem($t1,b)
} else { ;# Nope, recompute
set d [expr sqrt(12 - 3 * $t * $t)]
set a [expr (3*$t + $d) / 6]
set b [expr $t - $a]
if {$t1 > 1} { ;# In or out?
set d $a ; set a $b ; set b $d
}
set mem($t1,a) $a ;# Memoize--faster on next loop
set mem($t1,b) $b
}
triag2 $a $b 7 -1 -1 -1 ;# Draw all the triangles...
triag2 $a $b 6 -1 1 -1 ;# ...back to front if we can
triag2 $a $b 5 1 -1 -1
triag2 $a $b 4 1 1 -1
triag2 $a $b 3 -1 -1 1
triag2 $a $b 2 -1 1 1
triag2 $a $b 1 1 -1 1
triag2 $a $b 0 1 1 1
update
}
##+##########################################################################
#
# Triag2
#
# Draws an individual triangle
#
proc triag2 {a b color x y z} {
global colors
set color [lindex $colors $color]
set p1 [3d_obj2screen 0 [expr $y*$a] [expr $z*$b]]
set p2 [3d_obj2screen [expr $x*$b] 0 [expr $z*$a]]
set p3 [3d_obj2screen [expr $x*$a] [expr $y*$b] 0 ]
eval .c create polygon $p1 $p2 $p3 -fill $color -tags poly
}
##+##########################################################################
#
# 3d Canvas
#
# Simple 3d canvas package. After specifying the eye, the page size and a
# few other variables, this package will draw points and lines in 3d space.
#
# This is very simple. No clipping, z-buffering, or rotation is provided.
#
# Procedures:
# 3d_init
# Generates the transformation matrix needed to map from world to screen.
# Must be called after setting or changing the eye, etc.
# 3d_obj2screen
# Converts x,y,z of world coordinates into x,y of screen coordinates
#
# Variables:
# 3d(ex) 3d(ey) 3d(ez) == eye position
# 3d(rx) 3d(ry) 3d(rz) == reference point
# 3d(x) 3d(y) == canvas size
# 3d(cx) 3d(cy) == viewport center (reference point goes here)
# 3d(sx) 3d(sy) == size of viewport
#
set 3d(ex) 5 ;# Eye position
set 3d(ey) 4
set 3d(ez) 3
set 3d(rx) 0 ;# Reference point
set 3d(ry) 0
set 3d(rz) 0
set 3d(x) 600 ;# Page size
set 3d(y) 600
set 3d(cx) [expr $3d(x) / 2.0] ;# Mid-point
set 3d(cy) [expr $3d(y) / 2.0]
set 3d(sx) [expr $3d(cx) - 5.0] ;# Viewport size
set 3d(sy) [expr $3d(cy) - 6.0]
##+##########################################################################
#
# 3d_init
#
# Computes the transformation matrix for the current eye and center.
# Note, calling this resets all scaling, translations, etc.
#
proc 3d_init {} {
global 3d_mat 3d
if {$3d(ex) == 0 && $3d(ey) == 0} { set 3d(ey) .01 }
set xy [expr sqrt($3d(ex)*$3d(ex) + $3d(ey)*$3d(ey))]
set xyz [expr sqrt($xy*$xy + $3d(ez)*$3d(ez))]
3d_ident 3d_mat
3d_ident t ;# T0 - center to origin
set t(3,0) [expr -$3d(rx)]
set t(3,1) [expr -$3d(ry)]
set t(3,2) [expr -$3d(rz)]
3d_m44 3d_mat t 3d_mat
3d_ident t ;# T1 -- Origin To Eye
set t(3,0) [expr -$3d(ex)]
set t(3,1) [expr -$3d(ey)]
set t(3,2) [expr -$3d(ez)]
3d_m44 3d_mat t 3d_mat
3d_ident t ;# T2 -- Rotate 90 Around X
set t(1,1) 0 ; set t(2,2) 0
set t(1,2) -1 ; set t(2,1) 1
3d_m44 3d_mat t 3d_mat
3d_ident t ;# T3 -- rotate to eye line
set t(0,0) [set t(2,2) [expr -$3d(ey) / $xy]]
set t(0,2) [expr $3d(ex) / $xy]
set t(2,0) [expr -$t(0,2)]
3d_m44 3d_mat t 3d_mat
3d_ident t ;# T4 -- Rotate To Eye Line
set t(1,1) [set t(2,2) [expr $xy / $xyz]]
set t(1,2) [expr $3d(ez) / $xyz]
set t(2,1) [expr -$t(1,2)]
3d_m44 3d_mat t 3d_mat
3d_ident t ;# T5 -- Left-Handed Coords
set t(2,2) -1
3d_m44 3d_mat t 3d_mat
3d_ident t ;# N - Scale By D/S
set t(0,0) [set t(1,1) 4]
3d_m44 3d_mat t 3d_mat
}
##+##########################################################################
#
# 3d_ident matrix
#
# Returns $mm as the identity matrix of size 4
#
proc 3d_ident mm {
upvar 1 $mm m
catch "uplevel [list unset $mm]" ;# Erase all entries
foreach a {0,1 0,2 0,3 1,0 1,2 1,3 2,0 2,1 2,3 3,0 3,1 3,2} {
set m($a) 0
}
set m(0,0) [set m(1,1) [set m(2,2) [set m(3,3) 1.0]]]
}
##+##########################################################################
#
# 3d_m44 ma mb mc
#
# Matrix multiply ma x mb => mc of size 4. mc can be either ma or mb.
#
proc 3d_m44 {ma mb mc} {
upvar 1 $ma aa
upvar 1 $mb bb
upvar 1 $mc cc
for {set r 0} {$r < 4} {incr r} {
set result($r,0) [expr .0 + $aa($r,0)*$bb(0,0) + $aa($r,1)*$bb(1,0) \
+ $aa($r,2)*$bb(2,0) + $aa($r,3)*$bb(3,0)]
set result($r,1) [expr .0 + $aa($r,0)*$bb(0,1) + $aa($r,1)*$bb(1,1) \
+ $aa($r,2)*$bb(2,1) + $aa($r,3)*$bb(3,1)]
set result($r,2) [expr .0 + $aa($r,0)*$bb(0,2) + $aa($r,1)*$bb(1,2) \
+ $aa($r,2)*$bb(2,2) + $aa($r,3)*$bb(3,2)]
set result($r,3) [expr .0 + $aa($r,0)*$bb(0,3) + $aa($r,1)*$bb(1,3) \
+ $aa($r,2)*$bb(2,3) + $aa($r,3)*$bb(3,3)]
}
catch "uplevel [list unset $mc]"
foreach arr [array names result] {
set cc($arr) $result($arr)
}
}
##+##########################################################################
#
# 3d_obj2screen
#
# Converts a 3d position into 2d screen coordinates based on the current
# transformation matrix 3d_mat set up by 3d_init.
#
proc 3d_obj2screen {x y z} {
global 3d_mat 3d
set xe [expr $x*$3d_mat(0,0)+$y*$3d_mat(1,0)+$z*$3d_mat(2,0)+$3d_mat(3,0)]
set ye [expr $x*$3d_mat(0,1)+$y*$3d_mat(1,1)+$z*$3d_mat(2,1)+$3d_mat(3,1)]
set ze [expr $x*$3d_mat(0,1)+$y*$3d_mat(1,2)+$z*$3d_mat(2,2)+$3d_mat(3,2)]
set sx [expr $3d(cx) + ($xe / $ze) * $3d(sx)]
set sy [expr $3d(cx) - ($ye / $ze) * $3d(sy)]
return [list $sx $sy]
}
##+##########################################################################
#
# 3d_axis
#
# Draws x,y,z axes
#
proc 3d_axis {c} {
$c delete axis
set o [3d_obj2screen 0 0 0]
$c create line $o [3d_obj2screen 1.2 0 0] -fill black -arrow last -tag axis
$c create line $o [3d_obj2screen 0 1.2 0] -fill black -arrow last -tag axis
$c create line $o [3d_obj2screen 0 0 1.2] -fill black -arrow last -tag axis
}
##+##########################################################################
#
# Xyz
#
# Creates the subwindow with XYZ scales.
#
proc xyz {w title tag values} {
global eyex eyey eyez centerx centery centerz num_steps
catch {set x [expr round([lindex $values 0])]}
catch {set y [expr round([lindex $values 1])]}
catch {set z [expr round([lindex $values 2])]}
set values [list $x $y $z]
frame $w
pack $w -side left -expand y;# -pady .1i
label $w.ltitle -text $title -relief raised -bd 3
bind $w.ltitle <Double-Button-1> reeye
pack $w.ltitle -side top -fill x
foreach l {x y z} { ;# Create 3 scales for x,y,z
frame $w.f$l -bd 2 -relief raised ;# Holds scale & label
scale $w.f$l.$l -from 10 -to 0 -relief ridge -length 75
$w.f$l.$l config -var 3d(e$l) ;# -comm "redraw"
bind $w.f$l.$l <ButtonRelease-1> "after 1 redraw"
label $w.f$l.l$l -text [string toupper $l]
$w.f$l.l$l config -bg [lindex [$w.f$l.$l config -bg] 4]
pack $w.f$l -side left -expand yes
pack $w.f$l.l$l $w.f$l.$l -side top -fill x
$w.f$l.$l set [lindex $values 0] ;# Set the scale value
set values [lrange $values 1 end]
}
}
##+##########################################################################
#
# redraw
#
# Updates 3d stuff when eye position changes
#
proc redraw {} {
global param
3d_init
triag
}
##+##########################################################################
#
# reeye
#
# Repositions the eye to the default location
#
proc reeye {} {
global 3d
set 3d(ex) 5 ; set 3d(ey) 4 ; set 3d(ez) 3
redraw
}
##+##########################################################################
#############################################################################
#############################################################################
set go 0 ;# Animation off
set param 0 ;# Time parameter
set colors {red green blue cyan slateblue magenta chocolate yellow}
3d_init ;# Initialize the 3d world
do_display ;# Draw the display
triag ;# Draw initial shape
|
GPS - This is very impressive! Thanks for sharing it. I've been playing with perspective based projection for a while, but stereographic has stumped me (mostly I can't find simple examples). What kind of projection does this use?
DKF - This appears to be using a simple perspective projection i.e. take a view plane and an eye point, and map from points in your 3D space to points on your view plane by drawing a line passing through the target point and the eye point, and plotting a point on the view plane where the line intersects it.
Stereographic projection is something else - it is used to map points on a sphere to points on a plane, and it tends to map distances to their inverses (the closer two things are together in the real world, the further apart they are in the projection.) Stereographic projections (or at least things that are conceptually very similar) are used when studying atomic structures with X-Ray crystallography.
KPV - Yes, as DKF says, it's a simple perspective projection.