RosettaCodeData/Task/Death-Star/Tcl/death-star-1.tcl

116 lines
3.1 KiB
Tcl

package require Tcl 8.5
proc normalize vec {
upvar 1 $vec v
lassign $v x y z
set len [expr {sqrt($x**2 + $y**2 + $z**2)}]
set v [list [expr {$x/$len}] [expr {$y/$len}] [expr {$z/$len}]]
return
}
proc dot {a b} {
lassign $a ax ay az
lassign $b bx by bz
return [expr {-($ax*$bx + $ay*$by + $az*$bz)}]
}
# Intersection code; assumes that the vector is parallel to the Z-axis
proc hitSphere {sphere x y z1 z2} {
dict with sphere {
set x [expr {$x - $cx}]
set y [expr {$y - $cy}]
set zsq [expr {$r**2 - $x**2 - $y**2}]
if {$zsq < 0} {return 0}
upvar 1 $z1 _1 $z2 _2
set zsq [expr {sqrt($zsq)}]
set _1 [expr {$cz - $zsq}]
set _2 [expr {$cz + $zsq}]
return 1
}
}
# How to do the intersection with our scene
proc intersectDeathStar {x y vecName} {
global big small
if {![hitSphere $big $x $y zb1 zb2]} {
# ray lands in blank space
return 0
}
upvar 1 $vecName vec
# ray hits big sphere; check if it hit the small one first
set vec [if {
![hitSphere $small $x $y zs1 zs2] || $zs1 > $zb1 || $zs2 <= $zb1
} then {
dict with big {
list [expr {$x - $cx}] [expr {$y - $cy}] [expr {$zb1 - $cz}]
}
} else {
dict with small {
list [expr {$cx - $x}] [expr {$cy - $y}] [expr {$cz - $zs2}]
}
}]
normalize vec
return 1
}
# Intensity calculators for different lighting components
proc diffuse {k intensity L N} {
expr {[dot $L $N] ** $k * $intensity}
}
proc specular {k intensity L N S} {
# Calculate reflection vector
set r [expr {2 * [dot $L $N]}]
foreach l $L n $N {lappend R [expr {$l-$r*$n}]}
normalize R
# Calculate the specular reflection term
return [expr {[dot $R $S] ** $k * $intensity}]
}
# Simple raytracing engine that uses parallel rays
proc raytraceEngine {diffparms specparms ambient intersector shades renderer fx tx sx fy ty sy} {
global light
for {set y $fy} {$y <= $ty} {set y [expr {$y + $sy}]} {
set line {}
for {set x $fx} {$x <= $tx} {set x [expr {$x + $sx}]} {
if {![$intersector $x $y vec]} {
# ray lands in blank space
set intensity end
} else {
# ray hits something; we've got the normalized vector
set b [expr {
[diffuse {*}$diffparms $light $vec]
+ [specular {*}$specparms $light $vec {0 0 -1}]
+ $ambient
}]
set intensity [expr {int((1-$b) * ([llength $shades]-1))}]
if {$intensity < 0} {
set intensity 0
} elseif {$intensity >= [llength $shades]-1} {
set intensity end-1
}
}
lappend line [lindex $shades $intensity]
}
{*}$renderer $line
}
}
# The general scene settings
set light {-50 30 50}
set big {cx 20 cy 20 cz 0 r 20}
set small {cx 7 cy 7 cz -10 r 15}
normalize light
# Render as text
proc textDeathStar {diff spec lightBrightness ambient} {
global big
dict with big {
raytraceEngine [list $diff $lightBrightness] \
[list $spec $lightBrightness] $ambient intersectDeathStar \
[split ".:!*oe&#%@ " {}] {apply {l {puts [join $l ""]}}} \
[expr {$cx+floor(-$r)}] [expr {$cx+ceil($r)+0.5}] 0.5 \
[expr {$cy+floor(-$r)+0.5}] [expr {$cy+ceil($r)+0.5}] 1
}
}
textDeathStar 3 10 0.7 0.3