## 3D Hilbert Curve

Keith Vetter 2003-04-07: this whizzlet draws both the standard Hilbert Curve and a analogous one in 3-dimensions.

One clever aspect of this program is how it does hidden surface removal. The simple painters algorithm--drawing distant objects first so nearer objects paint over obscured surfaces (as used in Fractal Mountains)--doesn't work easily because the components of the curve can't be generated in a back-to-front ordering.

Instead, it uses tk's canvas stacking order. As each object gets generated it is tagged with its depth; then when everything is drawn, then canvas raise is used to place nearer objects before distant ones. This method won't work for complex objects but for the simple ones here it makes an effective hidden surface algorithm.

LV is there a relationship between this code and the sdarchive starkit that talks about the same thing?

In cases where wiki code is turned into starkits, perhaps the starkit's url could be added to the appropriate page, and, vice versa, the wikit page could be placed in the starkit's help information, so that someone with problems knows where to submit questions and comments?

```##+##########################################################################
#
# hilbert3d.tcl -- draws the Hilbert Curve in both 2 and 3 dimensions
# by Keith Vetter
#
package require Tk

namespace eval 3D {
variable matrix
variable 3d

set 3d(ex)  3                               ;# Eye position
set 3d(ey)  4
set 3d(ez)  3
set 3d(rx)  0                               ;# Reference point
set 3d(ry)  0
set 3d(rz)  0

}

array set S {lvl 0 color cyan connect 1 3d 3 eye 3 3Dtype 0}
array set DIRS {E {S E E N} N {W N N E} S {E S S W} W {N W W S}}
array set QTRS {E {1 2 3 4} N {3 2 1 4} S {1 4 3 2} W {3 4 1 2}}
array set XY   {E {l t r t r b l b} N {r b r t l t l b}
W {r b l b l t r t} S {l t l b r b r t}}
array set hpath3d {
0,start 1    0,path {X z x Y X Z x}   0,sub { 1  2  2 20 20  4  4  5}
1,start 1    1,path {z Y Z X z y Z}   1,sub { 6  7  7 19 19  9  9 10}
2,start 1    2,path {X Y x z X y x}   2,sub {11  0  0 23 23 14 14 15}
3,start 4    3,path {x Z X Y x z X}   3,sub {16 17 17  7  7 13 13  8}
4,start 2    4,path {X y x Z X Y x}   4,sub {19 14 14 10 10  0  0 22}
5,start 7    5,path {z y Z x z Y Z}   5,sub {23  9  9 15 15  7  7 21}
6,start 1    6,path {Y X y z Y x y}   6,sub { 0 11 11 13 13 15 15 14}
7,start 1    7,path {z X Z Y z x Z}   7,sub { 2  1  1  3  3  5  5  4}
8,start 2    8,path {Z y z X Z Y z}   8,sub {21 18 18 11 11 20 20 23}
9,start 7    9,path {z x Z y z X Z}   9,sub {13  5  5 14 14  1  1 17}
10,start 4   10,path {Y x y Z Y X y}  10,sub { 3 15 15  4  4 11 11 12}
11,start 1   11,path {Y z y X Y Z y}  11,sub { 7  6  6  8  8 10 10  9}
12,start 7   12,path {x z X y x Z X}  12,sub { 5 13 13 18 18 17 17  1}
13,start 7   13,path {x y X z x Y X}  13,sub {22 12 12  6  6  3  3 19}
14,start 2   14,path {X Z x y X z x}  14,sub { 8  4  4  9  9  2  2 16}
15,start 4   15,path {Y Z y x Y z y}  15,sub {20 10 10  5  5  6  6 18}
16,start 4   16,path {Z Y z x Z y z}  16,sub {10 20 20 22 22 18 18  6}
17,start 4   17,path {x Y X Z x y X}  17,sub {15  3  3 21 21 12 12 11}
18,start 2   18,path {Z X z y Z x z}  18,sub { 4  8  8 12 12 16 16  2}
19,start 2   19,path {y Z Y X y z Y}  19,sub {18 21 21  1  1 23 23 20}
20,start 4   20,path {Z x z Y Z X z}  20,sub {17 16 16  0  0  8  8 13}
21,start 2   21,path {y X Y Z y x Y}  21,sub {14 19 19 17 17 22 22  0}
22,start 7   22,path {y z Y x y Z Y}  22,sub { 9 23 23 16 16 21 21  7}
23,start 7   23,path {y x Y z y X Y}  23,sub {12 22 22  2  2 19 19  3}
}

proc DoDisplay {} {
global S

wm title . TkHilbert
pack [frame .ctrl -relief ridge -bd 2 -padx 5 -pady 5] \
-side right -fill both -ipady 5
pack [frame .top -relief raised -bd 2] -side top -fill x
pack [frame .screen -bd 2 -relief raised] -side top -fill both -expand 1
canvas .c -relief raised -borderwidth 0 -height 500 -width 500
label .msg -textvariable S(msg) -bd 2 -bg white -relief ridge
pack .msg -side bottom -fill both -in .screen
pack .c -side top -expand 1 -fill both -in .screen

set colors {red orange yellow green blue cyan purple violet white}
lappend colors [lindex [.c config -bg] 3] black
foreach color \$colors {
-variable S(color) -value \$color -command ReColor
bind .top.b\$color <3> [list .c config -bg \$color]
}
eval pack [winfo children .top] -side left -fill y

DoCtrlFrame
ReColor
trace add variable S(draw) write Tracer
trace add variable S(3d) write Tracer
bind .sLevel <ButtonRelease-1> {if {! \$S(draw)} DrawHilbertA}
}
proc DoCtrlFrame {} {

frame .ctrl.top
scale .sLevel -from 0 -to 7 -label Level -variable S(lvl) -relief ridge
.sLevel configure -font "[font actual [.sLevel cget -font]] -weight bold"

button .draw -text "Draw Curve" -command DrawHilbertA -bd 4
button .clear -text "Clear Curve" -command {.c delete all} -bd 4
button .stop -text "Stop Drawing" -command {set S(draw) 0} -bd 4
.draw configure -font "[font actual [.draw cget -font]] -weight bold"
.clear configure -font [.draw cget -font]
.stop configure -font [.draw cget -font]

image create bitmap ::img::up -data {
#define up_width 11
#define up_height 9
static char up_bits = {
0x00, 0x00, 0x20, 0x00, 0x70, 0x00, 0xf8, 0x00, 0xfc, 0x01, 0xfe,
0x03, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00
}}
image create bitmap ::img::down -data {
#define down_width 11
#define down_height 9
static char down_bits = {
0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0xfe, 0x03, 0xfc, 0x01, 0xf8,
0x00,0x70, 0x00, 0x20, 0x00, 0x00, 0x00
}}

button .up -image ::img::up -command {UpDown 1}
button .down -image ::img::down -command {UpDown -1}
checkbutton .connect -text "Show Connections" -variable S(connect) \
-relief raised -command ShowConnectors

frame .3df -bd 2 -relief ridge
radiobutton .2d -text "2 Dimensions" -variable S(3d) -value 2 \
-command DrawHilbertA -relief ridge
radiobutton .3d -text "3 Dimensions" -variable S(3d) -value 3 \
-command DrawHilbertA
label .leye -text "Eye Position" -bd 2 -relief raised
foreach q {x y z} {
frame .feye\$q -bd 2 -relief raised
label .leye\$q -text [string toupper \$q]
scale .eye\$q -from -0 -to 10 -relief ridge -length 75 -orient v \
-variable ::3D::3d(e\$q)
bind .eye\$q <ButtonRelease-1> {if {\$S(3d)==3 && !\$S(draw)} DrawHilbertA}
pack .leye\$q .eye\$q -in .feye\$q -side top -fill x
}

grid .ctrl.top     -in .ctrl -row 0 -sticky news
grid .sLevel .up   -in .ctrl.top -row 0 -sticky news
grid ^       .down -in .ctrl.top -row 1 -sticky news
grid .draw    -in .ctrl -row 21 -sticky ew
grid .clear   -in .ctrl -row 22 -sticky ew
grid .stop    -in .ctrl -row 23 -sticky ew -pady 10
grid .2d      -in .ctrl -row 31 -sticky ew
grid .3df     -in .ctrl -row 32 -sticky ew
grid rowconfigure .3df 1 -minsize 10
grid .3d   - - -in .3df -row 1 -sticky ew
grid .leye - - -in .3df -row 2 -sticky ew
grid .feyex .feyey .feyez -in .3df -row 3

grid .connect -in .ctrl -row 101 -sticky ew
grid .about   -in .ctrl -row 102 -sticky ew

grid rowconfigure .ctrl 10 -minsize 10
grid rowconfigure .ctrl 20 -minsize 10
grid rowconfigure .ctrl 30 -minsize 50
grid rowconfigure .ctrl 50 -weight 1

}
##+##########################################################################
#
# Tracer -- traces the S(draw) variable and activates widgets accordingly
#
proc Tracer {var1 var2 op} {
global S
set eyes {.leye .eyex .eyey .eyez .leyex .leyey .leyez}
set ww {.up .down .connect .draw .clear .3d}

if {\$var1 == "S" && \$var2 == "3d"} {
if {\$S(3d) == 3} {set state1 normal} else { set state1 disabled }
foreach w \$eyes {\$w config -state \$state1}
return
}

if {\$S(draw) == 0} {                        ;# Turning off drawing
.stop config -state disabled
.sLevel config -state normal -fg [lindex [.sLevel config -fg] 3]
foreach w \$ww { \$w config -state normal}
if {\$S(3d) == 3} {foreach w \$eyes {\$w config -state normal}}
} else {
.stop config -state normal
.sLevel config -state disabled -fg [.up cget -disabledforeground]
foreach w \$ww { \$w config -state disabled}
if {\$S(3d) == 3} {foreach w \$eyes {\$w config -state disabled}}
}
}

##+##########################################################################
#
# DrawHilbert -- sets up the state and draws the Hilbert curve
#
proc DrawHilbertA {} {after 1 DrawHilbert}
proc DrawHilbert {{lvl {}}} {
global S 3D depths

if {\$lvl == {}} { set lvl \$S(lvl) } else { set S(lvl) \$lvl }
.c delete all
set S(draw) 1
set S(first) {}
set S(ccolor) [expr {\$S(connect) ? \$S(color) : [.c cget -bg]}]

if {\$S(3d) == 3} {
set S(width) [expr {\$lvl < 3 ? (20 - 5*\$lvl) : 5}]
set n [expr {int(pow(2,3*(\$lvl+1)) - 1)}]
set S(msg) "Hilbert 3D Curve Level \$lvl (\$n edges)"
catch {unset depths}
Hilbert3d \$S(3Dtype) \$lvl [GetStartCube]
ZBuffer
} else {
set S(width) [expr {\$lvl <= 4 ? (25 - 5*\$lvl) : 8 - \$lvl}]

set n [expr {int(pow(4,\$lvl+1) - 1)}]
set S(msg) "Hilbert Curve Level \$lvl (\$n edges)"
Hilbert [GetStartBox] E \$lvl
}
set S(draw) 0
set S(first) {}
if {! \$::S(connect)} {.c lower connect}
}
##+##########################################################################
#
# UpDown -- draws the curve one level up or down from current
#
proc UpDown {dlvl} {
global S

if {\$dlvl < 0 && \$S(lvl) == 0} return
if {\$dlvl > 0 && \$S(lvl) >= [.sLevel cget -to]} return

incr S(lvl) \$dlvl
DrawHilbert
}
##+##########################################################################
#
# Hilbert -- draws a specified level Hilbert curve
#
proc Hilbert {box dir lvl} {
global S DIRS QTRS

if {! \$S(draw)} return

if {\$lvl == 0} {
Hilbert0 \$box \$dir
return
}

set lvl2 [expr {\$lvl - 1}]
foreach quarter \$QTRS(\$dir) newDir \$DIRS(\$dir) {
set b2 [QuarterBox \$box \$quarter]
Hilbert \$b2 \$newDir \$lvl2
}
}
##+##########################################################################
#
# Hilbert0 -- draws the most basic hilbert curve inside \$box facing \$dir
#
proc Hilbert0 {box dir} {
global S XY

set xy \$S(first)                            ;# Possibly connect to last
set xy {}
foreach {l t r b} [ShrinkBox \$box] break
foreach i \$XY(\$dir) {                       ;# Walk coord list for this dir
lappend xy [set \$i]
}
if {\$S(first) != ""} {
.c create line [concat \$S(first) [lrange \$xy 0 1]] -width \$S(width) \
-tag {hilbert connect} -fill \$S(ccolor)
}

.c create line \$xy -tag hilbert -width \$S(width) -fill \$S(color) \
-capstyle round
set S(first) [lrange \$xy end-1 end]         ;# So next connects w/ this one
}
##+##########################################################################
#
# GetStartBox -- returns coordinates of the area to draw our shape in
#
proc GetStartBox {} {
return [list 9 9 [expr {[winfo width .c]-9}] [expr {[winfo height .c]-9}]]
}
##+##########################################################################
#
# ShrinkBox -- shrinks a box to 1/4 of its size
#
proc ShrinkBox {box} {
foreach {l t r b} \$box break

set dx [expr {(\$r - \$l) / 4.0}]
set dy [expr {(\$b - \$t) / 4.0}]
set l [expr {\$l + \$dx}]     ; set r [expr {\$r - \$dx}]
set t [expr {\$t + \$dy}]     ; set b [expr {\$b - \$dy}]
return [list \$l \$t \$r \$b]
}
##+##########################################################################
#
# QuarterBox -- Returns coordinates of 1 of the 4 quadrants of BOX.
# 1 = up/left, 2 = up/right, 3 = lower/right, 4 = lower/left
#
proc QuarterBox {box corner} {
foreach {l t r b} \$box break
set hx [expr {(\$r - \$l) / 2.0}]
set hy [expr {(\$b - \$t) / 2.0}]

if {\$corner == 1} {                         ;# Upper left
set r [expr {\$r - \$hx}]
set b [expr {\$b - \$hy}]
} elseif {\$corner == 2} {                   ;# Upper right
set l [expr {\$l + \$hx}]
set b [expr {\$b - \$hy}]
} elseif {\$corner == 3} {                   ;# Lower right
set l [expr {\$l + \$hx}]
set t [expr {\$t + \$hy}]
} elseif {\$corner == 4} {                   ;# Lower left
set r [expr {\$r - \$hx}]
set t [expr {\$t + \$hy}]
}
return [list \$l \$t \$r \$b]
}
proc ShowConnectors {} {
if {\$::S(connect)} {
.c itemconfig connect -fill \$::S(color)
ZBuffer
} else {
.c itemconfig connect -fill [.c cget -bg]
.c lower connect
}
}
proc ReColor {} {
global S
foreach {S(red) S(green) S(blue)} [winfo rgb . \$S(color)] break
if {\$S(color) == "black"} {
foreach {S(red) S(green) S(blue)} [winfo rgb . white] break
}
if {\$S(3d) == 3} {
if {[.c find withtag line] != {}} {
after 1 DrawHilbert
}
return
}
.c itemconfig hilbert -fill \$::S(color)
if {! \$::S(connect)} {.c itemconfig connect -fill [.c cget -bg]}
}
set msg "TkHilbert\nby Keith Vetter, Feb 2003\n\n"
append msg "Draws the Hilbert Curve and a 3-Dimensional analog.\n"
append msg "This curve was discovered by David Hilbert in 1891 and\n"
append msg "was one of the first plane-filling curves ever found."
tk_messageBox -title "About TkHilbert" -message \$msg
}
##+##########################################################################
#
# 3D Hilbert curver routines
#
proc GetStartCube {} {
global 3D

set 3D(X) [expr {[winfo width  .c] - 20}]
set 3D(Y) [expr {[winfo height .c] - 20}]
set 3D(X2) [expr {\$3D(X) / 2.0}]            ;# Mid-point
set 3D(Y2) [expr {\$3D(Y) / 2.0}]
set 3D(X3) [expr {\$3D(X2) - 100.0}]         ;# Scaling factor
set 3D(Y3) [expr {\$3D(Y2) - 120.0}]

3D::Init
return [list 1 1 1 -1 -1 -1]
}
##+##########################################################################
#
# Hilbert3d
#
# Main routine to draw a 3-D Hilbert curve. If level is 0 then we just
# draw the base figure (with TYPE orientation). Otherwise we divide up
# the volume (CUBE) into 8 quadrants in the order of the path for TYPE.
# We then draw Hilbert curves in each of the quadrants of LEVEL - 1.
#
proc Hilbert3d {type lvl c} {
global S hpath3d

if {! \$S(draw)} return

if {\$lvl == 0} {
Hilbert3d0 \$type \$c
return
}

# Draw the 8 sub Hilbert shapes for this type. We start in the starting
# quadrant for this type, draw the correctly oriented sub-unit based on
# the info in hpath3d. We continue along the path for this type and draw
# the remaining sub-units.
set lvl2 [expr {\$lvl - 1}]                  ;# Down a level
set oct \$hpath3d(\$type,start)               ;# Octant to start in

foreach path \$hpath3d(\$type,path) sub \$hpath3d(\$type,sub) {
set c2 [Octant \$c \$oct]                 ;# Divide up the cube world

Hilbert3d \$sub \$lvl2 \$c2                ;# Draw next level Hilbert curve
set oct [NewOctant \$oct \$path]          ;# Move to next octant
}
}
##+##########################################################################
#
# Hilbert3d0 -- draws the lowest level of the 3-d Hilbert curve
#
# hpath3d specifies start corner of the cube and also the path of the curve.
# Each letter in the path specifies an axis to follow with capital meaning
# to go in.
#
proc Hilbert3d0 {type c} {
global hpath3d

foreach {x1 y1 z1 x2 y2 z2} [ShrinkCube \$c] break

# Get starting location
set x [expr {\$hpath3d(\$type,start) & 4 ? \$x1 : \$x2}]
set y [expr {\$hpath3d(\$type,start) & 2 ? \$y1 : \$y2}]
set z [expr {\$hpath3d(\$type,start) & 1 ? \$z1 : \$z2}]

x_line \$x \$y \$z connect                     ;# Start the line
foreach p \$hpath3d(\$type,path) {
switch \$p {
X { set x \$x1 }
x { set x \$x2 }
Y { set y \$y1 }
y { set y \$y2 }
Z { set z \$z1 }
z { set z \$z2 }
}
x_line \$x \$y \$z
}
}
proc ShrinkCube {cube} {
foreach {x1 y1 z1 x2 y2 z2} \$cube break

set dx [expr {(\$x1 - \$x2) / 4.0}]
set dy [expr {(\$y1 - \$y2) / 4.0}]
set dz [expr {(\$z1 - \$z2) / 4.0}]

set x1 [expr {\$x1 - \$dx}]
set x2 [expr {\$x2 + \$dx}]
set y1 [expr {\$y1 - \$dy}]
set y2 [expr {\$y2 + \$dy}]
set z1 [expr {\$z1 - \$dz}]
set z2 [expr {\$z2 + \$dz}]

return [list \$x1 \$y1 \$z1 \$x2 \$y2 \$z2]
}
proc x_line {x y z {connect ""}} {
global S
foreach {sx sy} [3D::Obj2screen \$x \$y \$z] break

lappend S(xyz) \$x \$y \$z
if {\$S(first) != {}} {
foreach {color tag} [DepthColor \$S(lastZ) \$z] break
if {! \$S(connect) && \$connect != ""} {set color \$S(ccolor)}
.c create line [concat \$S(first) \$sx \$sy] -fill \$color \
-width \$S(width) -capstyle round -tag "line \$tag \$connect"
}
set S(first) [list \$sx \$sy]
set S(lastZ) \$z
}
##+##########################################################################
#
# Obj2screen - converts 3-d coordinates (x,y,z) into screen coordinates.
# The eye is fixed on the Z axis and we project onto a x-y plane
# through the origin. It boils down to essentially dividing by Z.
#
proc obj2screen {x y z} {
global S 3D

set eye \$S(eye)
set x [expr {double(\$x) * \$eye / (\$eye - \$z)}] ;# Depth adjustment
set y [expr {double(\$y) * \$eye / (\$eye - \$z)}]

set sx [expr {\$3D(X2) + round(\$x * \$3D(X3))}] ;# To world coord
set sy [expr {\$3D(Y2) + round(\$y * \$3D(Y3))}]
return [list \$sx \$sy]
}
##+##########################################################################
#
# DepthColor -- provides depth cueing by shading per depth
#
proc DepthColor {z1 z2} {
global S depths

set z [expr {(\$z1 + \$z2) / 2.0}]            ;# Mid point depth
set z [expr {1 - ((\$z + 1) / 2)}]           ;# Convert to 1-0 scale
set zz [expr {1 - \$z/2}]                    ;# .5 - 1 scale

set red   [expr {int(\$zz * \$S(red))}]
set green [expr {int(\$zz * \$S(green))}]
set blue  [expr {int(\$zz * \$S(blue))}]
set color [format "\#%04X%04X%04X" \$red \$green \$blue]

set tag [format "Z%03d" [expr {int(999*\$z)}]]
set depths(\$tag) 1                          ;# Used for Z buffering
return [list \$color \$tag]
}
##+##########################################################################
#
# Octant -- returns a CUBE which is one of the eight sub-quadrants of C
#
proc Octant {cube which} {
foreach {x1 y1 z1 x2 y2 z2} \$cube break
set hx [expr {(\$x1 + \$x2) / 2.0}]
set hy [expr {(\$y1 + \$y2) / 2.0}]
set hz [expr {(\$z1 + \$z2) / 2.0}]

if {\$which & 1} {set z2 \$hz} else {set z1 \$hz}
if {\$which & 2} {set y2 \$hy} else {set y1 \$hy}
if {\$which & 4} {set x2 \$hx} else {set x1 \$hx}
return [list \$x1 \$y1 \$z1 \$x2 \$y2 \$z2]
}
proc NewOctant {which dir} {
switch \$dir {
X { incr which  4}
x { incr which -4}
Y { incr which  2}
y { incr which -2}
Z { incr which  1}
z { incr which -1}
}
return \$which
}
##+##########################################################################
#
# ZBuffer -- do a Z buffer hidden line algorithm by ordering every line
# in the canvas stacking list based on the lines "Z###" tag that we
# added to every line, the number in which is the depth of the line.
# The hash depths keeps a list of all "Z###" tags used.
#
proc ZBuffer {} {
foreach d [lsort [array names ::depths]] {
.c lower \$d
}
}

################################################################
################################################################
################################################################
################################################################

##+##########################################################################
#
# 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.
#
# A simple example is given at the bottom.
#
# 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::Line canvas x1 y1 z1 x2 y2 z3 ?x3 y3 z3 ...? ?options?
#      Draws a line between all the 3d points specified.
#
# 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
#
# Revision history
#   Keith Vetter 12/17/95  Initial revision from a C based version into tcl.
#   Keith Vetter  1/10/95  Finished the revision.
#

##+##########################################################################
#
# 3D::Init
#
# Computes the transformation matrix for the current eye and center.
# Note, calling this resets all scaling, translations, etc.
#
proc 3D::Init {} {
variable matrix
variable 3d

set xy [expr {sqrt(\$3d(ex)*\$3d(ex) + \$3d(ey)*\$3d(ey))}]
set xyz [expr {sqrt(\$xy*\$xy + \$3d(ez)*\$3d(ez))}]

3D::Ident matrix
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 matrix t matrix
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 matrix t matrix
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 matrix t matrix
if {\$xy != 0} {
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 matrix t matrix
}
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 matrix t matrix
3D::Ident t                                 ;# T5 -- Left-Handed coords
set t(2,2) -1
3D::M44 matrix t matrix
3D::Ident t                                 ;# N - Scale By D/S
set t(0,0) [set t(1,1) 4]
3D::M44 matrix t matrix

set 3d(x)   [winfo width .c]                ;# Page size
set 3d(y)   [winfo height .c]
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::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::PrintMatrix matrix
#
# Prints out the 4x4 matrix \$mm
#
proc m {} {::3D::PrintMatrix ::matrix}
proc 3D::PrintMatrix {mm} {
upvar 1 \$mm m

for {set r 0} {\$r < 4} {incr r} {
for {set c 0} {\$c < 4} {incr c} {
puts -nonewline [format "%.3f\t" \$m(\$r,\$c)]
}
puts ""
}
puts ""
}
##+##########################################################################
#
# 3D::Obj2screen
#
# Converts a 3d position into 2d screen coordinates based on the current
# transformation matrix matrix set up by 3D::Init.
#
proc 3D::Obj2screen {x y z} {
variable matrix
variable 3d

set xe [expr {\$x*\$matrix(0,0)+\$y*\$matrix(1,0)+\$z*\$matrix(2,0)+\$matrix(3,0)}]
set ye [expr {\$x*\$matrix(0,1)+\$y*\$matrix(1,1)+\$z*\$matrix(2,1)+\$matrix(3,1)}]
set ze [expr {\$x*\$matrix(0,1)+\$y*\$matrix(1,2)+\$z*\$matrix(2,2)+\$matrix(3,2)}]

set sx [expr \$3d(cx) + (\$xe / \$ze) * \$3d(sx)]
set sy [expr \$3d(cx) - (\$ye / \$ze) * \$3d(sy)]

return [list \$sx \$sy]
}
################################################################
################################################################
DoDisplay
bind . <Map> DrawHilbert```

uniquename 2013jul29

This code could use an image to show what it produces:

(Thanks to 'gnome-screenshot', 'mtpaint', and ImageMagick 'convert' on Linux for, respectively, capturing the image to a PNG file, cropping the image, and converting the resulting PNG file to a JPEG file about 3 times smaller. Thank you FOSS developers everywhere, from someone who is also contributing FOSS code to the world --- in the form of Tk scripts.)

 Category 3D Graphics Category Application Category Fractal Category Graphics Category Mathematics Category Whizzlet