escargo 15 Apr 2003 - Are we supposed to be able to prune the shrubs? (Clicking on some of the greenery makes it go away? Also, there are sometimes some z-order problems. I had some of the shrubs get drawn on the wrong side of one of the chairs. Also, sometimes the table is visible through the walls of the house. (I really like the light switch!)
I Updated Merry to Happy in the window title. I've heard of Merry Christmas, but never Merry Easter. I guess Merry and Happy have similar meanings though.
dbohdan 2018-08-17: Fixed Tcl 8.5-8.6 compatibility. Eliminated update and replaced eval with {*}.
package require Tcl 8.5
set ::tcl_precision 17
proc deg2rad {deg} {expr {$deg * atan(1)/45.}}
trace var 3d(angle) w "set 3d(th) \[deg2rad \$3d(angle)];#"
array set 3d {angle 30 scale 100 bright 1 lastDim .25 flat 0}
proc 3d {type w points args} {
variable 3d
set cmd [list $w create $type]
foreach point $points {lappend cmd {*}[3d'project $point]}
if {$type == "poly" && [lsearch $args -outline] < 0} {
lappend cmd -outline black ;# looks better...
}
set cmd [concat $cmd $args]
if {$3d(bright) != 1} {
foreach att {-outline -fill} {
if {[set pos [lsearch $cmd $att]] > 0} {
set f [lindex $cmd [incr pos]]
set cmd [lreplace $cmd $pos $pos [dimColor $f $3d(bright)]]
}
}
}
set 3d([{*}$cmd]) [list $type $points $args] ;# backing store
}
proc 3d'axes w {
foreach {name from to color} {
Xaxis {-30 0 0} {30 0 0} red
X1 {1 0 0} {1 .05 0} red
Yaxis {0 -30 0} {0 30 0} green
Y1 {0 1 0} {0 1 .05} green
Zaxis {0 0 -30} {0 0 30} blue
Z1 {0 0 1} {.05 0 1} blue
} {3d line $w [list $from $to] -fill $color -tag axes}
}
proc 3d'project point {
variable 3d
foreach {x y z} $point break
if {$z==""} {set z 0}
set factor $3d(scale)
switch -- $3d(flat) {
x {list [expr {$y*$factor}] [expr {-$z*$factor}] ;# side view}
y {list [expr {$x*$factor}] [expr {-$z*$factor}] ;# front view}
z {list [expr {$x*$factor}] [expr {-$y*$factor}] ;# top view}
default {
set rad [expr {$y * abs(1-($3d(angle)/90.))}]
if {abs($y)<6} {set factor [expr {$factor*(1-$y/6.)}]};#perspective
set 2dx [expr {($x + $rad*cos($3d(th))) * $factor}]
set 2dy [expr {($z + $rad*sin($3d(th))) * -$factor}];#+y goes down
list $2dx $2dy
}
}
}
proc 3d'redraw {w {tag all} {flat ""}} {
variable 3d
if {$flat != ""} {set 3d(flat) $flat}
set 3d(angle) [expr {$3d(angle)>180? 180: $3d(angle)<0? 0: $3d(angle)}]
foreach item [$w find withtag $tag] {
foreach {type points args} $::3d($item) break
unset 3d($item)
$w delete $item
3d $type $w $points {*}$args
}
}
proc 3d'move {w tag vector} {
variable 3d
foreach item [$w find withtag $tag] {
set newpoints {}
foreach point [lindex $3d($item) 1] {
lappend newpoints [vector'add $point $vector]
}
set 3d($item) [lreplace $3d($item) 1 1 $newpoints]
}
3d'redraw $w $tag
}
proc 3d'scale {w tag factors {rpoint {}}} {
variable 3d
if {$rpoint==""} {set rpoint [3d'center $w $tag]}
foreach {x0 y0 z0} $rpoint break
foreach {xf yf zf} $factors break
if {$yf == ""} {set yf $xf}
if {$zf == ""} {set zf $yf}
foreach item [$w find withtag $tag] {
set newpoints {}
foreach point [lindex $3d($item) 1] {
foreach {x y z} $point break
if {$z == ""} {set z 0}
set x1 [expr {($x - $x0) * $xf + $x0}]
set y1 [expr {($y - $y0) * $yf + $y0}]
set z1 [expr {($z - $z0) * $zf + $z0}]
lappend newpoints [list $x1 $y1 $z1]
}
set 3d($item) [lreplace $3d($item) 1 1 $newpoints]
}
3d'redraw $w $tag
}
proc 3d'rotate {w tag rvector {rpoint {}}} {
variable 3d
foreach {rx ry rz} $rvector break ;# rotation angles in degrees
foreach i {x y z} {set rd$i [deg2rad [set r$i]]}
if {$rpoint == ""} {set rpoint [3d'center $w $tag]}
foreach {xc yc zc} $rpoint break
foreach item [$w find withtag $tag] {
set newpoints {}
foreach point [lindex $3d($item) 1] {
foreach {x y z} $point break
if {$z == ""} {set z 0}
set x1 [expr {$x-$xc}]
set y1 [expr {$y-$yc}]
set z1 [expr {$z-$zc}]
if {$rx != 0} {
if {[set rad [expr {hypot($y1,$z1)}]]} {
set th [expr {atan2($z1,$y1) - $rdx}]
set y [expr {$yc + $rad * cos($th)}]
set z [expr {$zc + $rad * sin($th)}]
} ;# tests for nonzero rad necessary on Unix
}
if {$ry != 0} {
if {[set rad [expr {hypot($x1,$z1)}]]} {
set th [expr {atan2($z1,$x1) - $rdy}]
set x [expr {$xc + $rad * cos($th)}]
set z [expr {$zc + $rad * sin($th)}]
}
}
if {$rz != 0} {
if {[set rad [expr {hypot($x1,$y1)}]]} {
set th [expr {atan2($y1,$x1) - $rdz}]
set x [expr {$xc + $rad * cos($th)}]
set y [expr {$yc + $rad * sin($th)}]
}
}
lappend newpoints [list $x $y $z]
}
set 3d($item) [lreplace $3d($item) 1 1 $newpoints]
}
3d'redraw $w $tag
}
proc 3d'bcube {w tag} {
#-- compute "bounding cube" (minx maxx miny maxy minz maxz)
variable 3d
set xs {}; set ys {}; set zs {}
foreach item [$w find withtag $tag] {
foreach point [lindex $3d($item) 1] {
foreach {x y z} $point break
lappend xs $x
lappend ys $y
lappend zs $z
}
}
concat [minmax $xs] [minmax $ys] [minmax $zs]
}
proc 3d'center {w tag} {
foreach {x x1 y y1 z z1} [3d'bcube $w $tag] break
list [expr {($x+$x1)/2.}] [expr {($y+$y1)/2.}] [expr {($z+$z1)/2.}]
}
proc 3d'addtag {w item tag} {
variable 3d
set args [lindex $3d($item) 2]
set found 0; set newargs {}
foreach {att val} $args {
if {$att == "-tag"} {lappend val $tag; incr found}
lappend newargs $att $val
}
if {!$found} {lappend newargs -tag $tag}
set 3d($item) [lreplace $3d($item) 2 2 $newargs]
}
proc dim {w factor {tag all}} {
variable 3d
if {$factor == 0} {
set factor [expr {1./$3d(lastDim)}]
set 3d(lastDim) $factor ;# allow toggle for light switch
} else {set 3d(bright) [expr {$3d(bright)*$factor}]}
if {$tag == "all"} {
$w config -bg [dimColor [$w cget -bg] $factor]
}
foreach item [$w find withtag $tag] {
foreach att {-fill -outline} {
if {![catch {$w itemcget $item $att} f]} {
$w itemconf $item $att [dimColor $f $factor]
}
}
}
}
proc dimColor {color factor} {
if {$color == ""} {return ""}
foreach {r g b} [winfo rgb . $color] break
set res "#"
foreach i {r g b} {
set col [expr {round([set $i]*$factor)}]
if {$col > 0xFFFF} {set col 0xFFFF}
append res [format %4.4x $col]
}
set res
}
proc minmax L {
set sorted [lsort -real $L]
list [lindex $sorted 0] [lindex $sorted end]
}
proc vector'add {v1 v2} {
set res {}
foreach i $v1 j $v2 {
if {$i == ""} {set i 0}
if {$j == ""} {set j 0}
lappend res [expr {$i + $j}]
}
set res
}
#-------------------------------- A mighty elaborate and playful demo:
if {[file tail [info script]] == [file tail $argv0]} {
proc plant {c x y {diameter 0.6} {branches 8}} {
set root [list $x $y 0]
for {set i 0} {$i<$branches} {incr i} {
set x1 [expr {$x + rand()*$diameter - $diameter/2}]
set y1 [expr {$y + rand()*$diameter - $diameter/2}]
set z [expr {rand()*0.25 + $diameter}]
set width [expr {round($diameter*6)}]
3d line $c [list $root [list $x1 $y1 $z]] -width $width\
-fill [lpick {DarkGreen green4 ForestGreen SeaGreen YellowGreen}]\
-tag plant
}
}
proc chair {c x y {colors {white blue}}} {
set h1 0.12
set h2 0.2
set h3 0.3
set y1 0.25; set y2 0.26
set tag chair[incr ::chairID]
set tag2 [list $tag mv]
foreach {c1 c2} $colors break
3d line $c "{0 $y2} {.05 $y2 $h2} {.25 $y2 $h2} {.3 $y2}" -fill $c1\
-width 2 -tag $tag2
3d poly $c "{.05 0 $h1} {.05 $y1 $h1} {.3 $y1 $h1} {.3 0 $h1}" \
-fill $c2 -tag $tag2 -width 2
3d poly $c "{.05 0 $h1} {0 0 $h3} {0 $y1 $h3} {.05 $y1 $h1}" \
-fill $c2 -tag $tag2 -width 2
3d line $c "{0 0} {.05 0 $h2} {.25 0 $h2} {.3 0}" -fill $c1 \
-width 2 -tag $tag2
3d'move $c $tag [list $x $y 0]
set tag
}
set chairID 0
proc every {ms body} {eval $body; after $ms [info level 0]}
proc lpick L {lindex $L [expr {int(rand() * [llength $L])}]}
proc moveFlag {w} {
variable 3d
foreach i [$w find withtag =flag] {
set points [lindex $3d($i) 1]
if {[lindex [lindex $points 0] 2] > 1.5} {
set randv {}
foreach _ {x y z} {
lappend randv [expr {rand()*0.05-0.025}]
}
set p1 [vector'add [lindex $points 1] $randv]
set p2 [vector'add [lindex $points 2] $randv]
set points [lreplace $points 1 2 $p1 $p2]
set 3d($i) [lreplace $3d($i) 1 1 $points]
}
}
3d'redraw $w =flag
$w lower =flag backWall
}
proc placeEggs w {
foreach color {
red green blue cyan magenta yellow orange pink purple brown
} {
set x [expr {rand() * 5.4 - 1.9}]
set y [expr {rand() * 4 - 2}]
3d oval $w "{$x $y .04} {[expr $x+.1] [expr $y+.04] -.04}"\
-fill $color -tag egg
}
$w lower egg frontWall
wm title . "Happy 3D Easter!"
$w bind egg <1> {
%W delete current
wm title . "[wm title .] 0" ;# append found eggs to title
if {[%W find withtag egg] == ""} {
tk_messageBox -message Super!
placeEggs %W
}
}
}
proc swings {w x0 y0} {
set x1 [expr {$x0 + 0.8}]
set xm [expr {($x0 + $x1)/2}]
set x2 [expr {$xm - 0.05}]
set x3 [expr {$xm + 0.05}]
set y1 [expr {$y0 + 0.7}]
set y2 [expr {$y0 + 0.3}] ;# rope 1
set y3 [expr {$y0 + 0.5}] ;# rope 2
set h 0.8 ;# top crossbar
set s 0.14 ;# height of swing seat
set col turquoise4
3d line $w "{$x0 $y1} {$xm $y1 $h} {$x1 $y1}" -width 2 -fill $col
3d line $w "{$xm $y0 $h} {$xm $y1 $h}" -width 2 -fill $col
3d line $w "{$xm $y3 $h} {$xm $y3 $s}" -tag swingm
3d poly $w "{$x2 $y2 $s} {$x3 $y2 $s} {$x3 $y3 $s} {$x2 $y3 $s}"\
-fill orange -tag swingm
3d line $w "{$xm $y2 $h} {$xm $y2 $s}" -tag swingm
3d line $w "{$x0 $y0} {$xm $y0 $h} {$x1 $y0}" -width 2 -fill $col\
-tag swingfg
set swingpoint [list $xm $y2 $h]
$w bind swingm <1> [list swing'move %W swingm $swingpoint 20]
}
proc swing'move {w tag rpoint angle} {
$w raise swingfg
if {$angle<=0} return
3d'rotate $w $tag [list 0 $angle 0] $rpoint
set angle2 [expr {$angle*-2}]
after 250 [list 3d'rotate $w $tag [list 0 $angle2 0] $rpoint]
after 500 [list 3d'rotate $w $tag [list 0 $angle 0] $rpoint]
after 500 [list swing'move $w $tag $rpoint [incr angle -1]]
}
proc toycart {w x y {color red}} {
3d oval $w {{.01 .18 .1} {.09 .2 0}} -fill black -tags {cart mv}
3d oval $w {{.19 .18 .1} {.27 .2 0}} -fill black -tags {cart mv}
3d poly $w {{.01 .01 .1} {.01 .19 .1} {.29 .19 .1} {.29 .01 .1}}\
-fill $color -tags {cart mv}
3d poly $w {{.01 .19 .1} {0 .2 .15} {.3 .2 .15} {.29 .19 .1}}\
-fill $color -tags {cart mv}
3d poly $w {{.01 .01 .1} {0 0 .15} {0 .2 .15} {.01 .19 .1}}\
-fill $color -tags {cart mv}
3d poly $w {{.29 .01 .1} {.3 0 .15} {.3 .2 .15} {.29 .19 .1}}\
-fill $color -tags {cart mv}
3d poly $w {{.01 .01 .1} {0 0 .15} {.3 0 .15} {.29 .01 .1}}\
-fill $color -tags {cart mv front}
3d line $w {{.3 .1 .1} {.55 .1 0}} -width 2 \
-fill $color -tags {cart mv}
3d line $w {{.55 .07 0} {.55 .13 0}} -width 2 \
-fill $color -tags {cart mv}
3d oval $w {{.01 .02 .1} {.09 0 0}} -fill black -tags {cart mv}
3d oval $w {{.19 .02 .1} {.27 0 0}} -fill black -tags {cart mv}
3d'move $w cart [list $x $y] ;# bring to target position
$w bind egg <3> {
set item [%W find withtag current]
3d'addtag %W $item cart ;# let it move with the cart...
3d'move %W $item {0 0 .11} ;# ...and raise it on board
%W raise front egg
}
return cart
}
#---------------------------------- let's build up the scene...
set c [canvas .c -width 600 -height 400 \
-scrollregion {-250 -300 350 100} -bg steelblue1]
pack $c -fill both -expand 1
3d'axes $c
3d poly $c {{-4 -3} {6 -3} {6 -3 -2} {-4 -3 -2}} -fill brown ;# earth
3d poly $c {{-4 -3} {6 -3} {6 2} {-4 2}} -fill green3 ;# lawn
3d poly $c {{-4 2} {.3 2} {.3 2 .4} {-4 2 .4}} -fill DarkOrange2;# fence
3d poly $c {{.7 2} {6 2} {6 2 .4} {.7 2 .4}} -fill DarkOrange2 ;# fence
3d poly $c {{.3 .1} {1.7 .1} {1.7 -.7} {.3 -.7}} -fill gray ;#terrace
plant $c 1 1.9
3d line $c {{.5 1.8} {.5 1.8 2.85}} -fill white -width 3 ;# flagpole
set flagCoords {{.5 1.8 2.5} {.62 2 2.5} {.62 2 2.8} {.5 1.8 2.8}}
3d poly $c $flagCoords -fill blue -tags =flag ;# flag
$c bind =flag <1> {
$c delete =flag; 3d poly $c $flagCoords -fill blue -tags =flag
}
3d poly $c {{0 .1} {0 1} {2 1} {2 .1}} -fill orange -tag in ;#floor
3d oval $c {{.3 .3} {1.8 .8}} -fill purple -tag in ;# carpet
plant $c -1.3 1.8 0.5
plant $c 3 1.8 0.6
swings $c -1.6 -0.3
3d oval $c {{-3.2 -2.7} {-1.5 -1}} -fill beige ;# pool
3d oval $c {{-3.1 -2.6} {-1.6 -1.1}} -fill DeepSkyBlue3 ;# water in pool
3d poly $c {{.2 1} {.36 1.3} {.36 1.3 .8} {.2 1 .8}} \
-fill brown -tag {=door in} ;# door
3d oval $c {{.34 1.25 .29} {.37 1.29 .32}} -fill yellow \
-outline orange -tag {=door in} ;#knob
$c bind =door <1> {
3d'rotate %W =door {0 0 -15} {.2 1 .4}; %W lower =door backWall}
$c bind =door <3> {
3d'rotate %W =door {0 0 15} {.2 1 .4}; %W lower =door backWall}
3d poly $c {{0 1} {.2 1} {.2 1 .7} {.54 1 .7} {.54 1}
{1.3 1} {1.3 1 .3} {.8 1 .3} {.8 1 .7} {1.3 1 .7}
{1.3 1} {2 1} {2 1 1} {0 1 1}} -fill bisque -outline bisque \
-tag {backWall in} ;# back wall
3d poly $c {{.57 1 .4} {.65 1 .4} {.65 1 .48} {.57 1 .48}} \
-fill white -tag {=lightSwitch in} ;# light switch
$c bind =lightSwitch <1> {dim %W 0 in}
3d line $c {{1 1 .3} {1 1 .7}} -fill white -width 2 -tag in;# window bar
3d poly $c {{-.05 1.05 1} {-.05 .5 1.5} {2.05 .5 1.5} {2.05 1.05 1}}\
-fill red ;# (back) roof
3d poly $c {{0 .1} {0 1} {0 1 1} {0 .5 1.5} {0 .1 1}} \
-fill beige ;# left side wall
foreach {x y} {.51 .31 .51 .49 .79 .49 .79 .31} {
3d line $c [list [list $x $y 0] [list $x $y .3]] \
-fill black -width 3 -tag {=table mv}} ;# table legs
3d poly $c {{.5 .3 .3} {.5 .5 .3} {.8 .5 .3} {.8 .3 .3}} \
-fill lightblue -tag {=table mv in} ;# table plate
3d poly $c {{2 .1} {2 1} {2 1 1} {2 .5 1.5} {2 .1 1}} -fill pink ;#wall
3d poly $c {{0 .1} {.3 .1} {.3 .1 .8} {1.7 .1 .8} {1.7 .1 .3}
{1 .1 .3} {1 .1 .8} {.9 .1 .8} {.9 .1} {2 .1} {2 .1 1} {0 .1 1}} \
-fill LightYellow -outline LightYellow -tag frontWall ;# front wall
placeEggs $c
3d poly $c {{.99 .1 .29} {1.7 .1 .29} {1.7 .1 .81} {.99 .1 .81}}\
-fill {} -width 2 -outline NavyBlue ;#window frame
3d poly $c {{-.05 .05 1} {-.05 .5 1.5} {2.05 .5 1.5} {2.05 .05 1}}\
-fill red ;# (front) roof
chair $c -0.5 -1.8
toycart $c 2 -2
3d'rotate $c [chair $c 0 -2.5] {0 0 -60}
for {set i 0} {$i<10} {incr i} {
plant $c [expr {5-rand()*6}] [expr {-3+rand()*2.3}] 0.2 5
}
plant $c -2.5 -.8 .7
plant $c 2.8 -.8 .5
#--------------------------------------------------------- Bindings
bind . <Left> {incr 3d(angle) 5; 3d'redraw .c all 3d}
bind . <Right> {incr 3d(angle) -5; 3d'redraw .c all 3d}
bind . <Up> {set 3d(scale) [expr {$3d(scale)*1.25}]; 3d'redraw .c}
bind . <Down> {set 3d(scale) [expr {$3d(scale)/1.25}]; 3d'redraw .c}
#-- test transformations with current "mv" (movable) object
set mv =table ;# initially: table (best move it out of house first)
bind . <Shift-Left> {3d'move $c $mv {-.1 0 0}}
bind . <Shift-Right> {3d'move $c $mv {.1 0 0}}
bind . <Shift-Up> {3d'move $c $mv {0 .1 0}}
bind . <Shift-Down> {3d'move $c $mv {0 -.1 0}}
bind . <Alt-Left> {3d'rotate $c $mv {0 0 5}}
bind . <Alt-Right> {3d'rotate $c $mv {0 0 -5}}
bind . <Alt-Up> {3d'rotate $c $mv {0 5 0}}
bind . <Alt-Down> {3d'rotate $c $mv {0 -5 0}}
bind . + {3d'scale $c $mv 1.25} ;# grow
bind . - {3d'scale $c $mv 0.8} ;# shrink
$c bind mv <1> {
set mv [lindex [%W gettags current] 0]
3d'move %W $mv {-.01 -.01 -.01} ;# visual feedback in 3D
after 100 [list 3d'move %W $mv {.01 .01 .01}]
}
$c bind plant <1> {%W delete current} ;# for "gardening"
bind . x {3d'redraw $c all x} ;# side view, along x axis
bind . y {3d'redraw $c all y} ;# front view, along y axis
bind . z {3d'redraw $c all z} ;# top view, along z axis
bind . 3 {3d'redraw $c all 3d} ;# perspectivic view
bind . F [list 3d'move $c =flag {0 0 .1}] ;# hoist flag
bind . f [list 3d'move $c =flag {0 0 -.1}] ;# lower flag
bind . d {dim .c .8} ;# decrease brightness
bind . D {dim .c 1.25} ;# increase brightness
bind . <Escape> {exec wish $argv0 &; exit} ;# restart
bind . ? {console show} ;# for debugging
#-------------------------------------------- Initial animation...
set 3d(scale) 0.2 ;# start with a view from far away
3d'redraw .c
raise . ;# necessary on Windows
proc zoomIn {} {
if {$::3d(scale) < 80} {
event generate . <Up>
after idle zoomIn
}
}
zoomIn
every 250 {moveFlag .c} ;# so there's always something moving
}
