Death Star

From Rosetta Code
Revision as of 23:26, 27 September 2011 by rosettacode>Ledrug (→‎{{header|C}}: animate)
Task
Death Star
You are encouraged to solve this task according to the task description, using any language you may know.

Death Star is a task to display a region that consists of a large sphere with part of a smaller sphere removed from it as a result of geometric subtraction. (This will basically produce a shape like a "death star".)

See also: Draw a sphere.

Brlcad

<lang brlcad># We need a database to hold the objects opendb deathstar.g y

  1. We will be measuring in kilometers

units km

  1. Create a sphere of radius 60km centred at the origin

in sph1.s sph 0 0 0 60

  1. We will be subtracting an overlapping sphere with a radius of 40km
  2. The resultant hole will be smaller than this, because we only
  3. only catch the edge

in sph2.s sph 0 90 0 40

  1. Create a region named deathstar.r which consists of big minus small sphere

r deathstar.r u sph1.s - sph2.s

  1. We will use a plastic material texture with rgb colour 224,224,224
  2. with specular lighting value of 0.1 and no inheritance

mater deathstar.r "plastic sp=0.1" 224 224 224 0

  1. Clear the wireframe display and draw the deathstar

B deathstar.r

  1. We now trigger the raytracer to see our finished product

rt</lang>

C

Primitive ray tracing. <lang c>#include <stdio.h>

  1. include <math.h>
  2. include <unistd.h>

char shades[] = ".:!*oe&#%@";

double light[3] = { -50, 0, 50 }; void normalize(double * v) { double len = sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]); v[0] /= len; v[1] /= len; v[2] /= len; }

double dot(double *x, double *y) { double d = x[0]*y[0] + x[1]*y[1] + x[2]*y[2]; return d < 0 ? -d : 0; }

typedef struct { double cx, cy, cz, r; } sphere_t;

/* positive shpere and negative sphere */ sphere_t pos = { 20, 20, 0, 20 }, neg = { 1, 1, -6, 20 };

/* check if a ray (x,y, -inf)->(x, y, inf) hits a sphere; if so, return

  the intersecting z values.  z1 is closer to the eye */

int hit_sphere(sphere_t *sph, double x, double y, double *z1, double *z2) { double zsq; x -= sph->cx; y -= sph->cy; zsq = sph->r * sph->r - (x * x + y * y); if (zsq < 0) return 0; zsq = sqrt(zsq); *z1 = sph->cz - zsq; *z2 = sph->cz + zsq; return 1; }

void draw_sphere(double k, double ambient) { int i, j, intensity, hit_result; double b; double vec[3], x, y, zb1, zb2, zs1, zs2; for (i = floor(pos.cy - pos.r); i <= ceil(pos.cy + pos.r); i++) { y = i + .5; for (j = floor(pos.cx - 2 * pos.r); j <= ceil(pos.cx + 2 * pos.r); j++) { x = (j - pos.cx) / 2. + .5 + pos.cx;

/* ray lands in blank space, draw bg */ if (!hit_sphere(&pos, x, y, &zb1, &zb2)) hit_result = 0;

/* ray hits pos sphere but not neg, draw pos sphere surface */ else if (!hit_sphere(&neg, x, y, &zs1, &zs2)) hit_result = 1;

/* ray hits both, but pos front surface is closer */ else if (zs1 > zb1) hit_result = 1;

/* pos sphere surface is inside neg sphere, show bg */ else if (zs2 > zb2) hit_result = 0;

/* back surface on neg sphere is inside pos sphere, the only place where neg sphere surface will be shown */ else if (zs2 > zb1) hit_result = 2; else hit_result = 1;

switch(hit_result) { case 0: putchar('+'); continue; case 1: vec[0] = x - pos.cx; vec[1] = y - pos.cy; vec[2] = zb1 - pos.cz; break; default: vec[0] = neg.cx - x; vec[1] = neg.cy - y; vec[2] = neg.cz - zs2; }

normalize(vec); b = pow(dot(light, vec), k) + ambient; intensity = (1 - b) * (sizeof(shades) - 1); if (intensity < 0) intensity = 0; if (intensity >= sizeof(shades) - 1) intensity = sizeof(shades) - 2; putchar(shades[intensity]); } putchar('\n'); } }

int main() { double ang = 0;

while (1) { printf("\033[H"); light[1] = cos(ang * 2); light[2] = cos(ang); light[0] = sin(ang); normalize(light); ang += .05;

draw_sphere(2, .3); usleep(100000); } return 0; }</lang>

D

Translation of: C

<lang d>import std.stdio, std.math, std.numeric, std.algorithm;

/*const*/ struct V3 {

   double[3] v;
   @property V3 normalize() pure nothrow const {
       //immutable double len = sqrt(dotProduct(v, v));
       immutable double len= sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);
       // return V3(v[] / len);
       return V3([v[0] / len, v[1] / len, v[2] / len]);
   }
   double dot(const ref V3 y) pure nothrow const {
       immutable double d = dotProduct(v, y.v);
       return d < 0 ? -d : 0;
   }

}


const struct Sphere { double cx, cy, cz, r; }

void drawSphere(in double k, in double ambient, in V3 light)nothrow{

   /** Check if a ray (x,y, -inf).(x, y, inf) hits a sphere; if so,
   return the intersecting z values.  z1 is closer to the eye */
   static bool hitSphere(const ref Sphere sph,
                         in double x0, in double y0,
                         out double z1,
                         out double z2) pure nothrow {
       immutable double x = x0 - sph.cx;
       immutable double y = y0 - sph.cy;
       immutable double zsq = sph.r ^^ 2 - (x ^^ 2 + y ^^ 2);
       if (zsq < 0)
           return false;
       immutable double szsq = sqrt(zsq);
       z1 = sph.cz - szsq;
       z2 = sph.cz + szsq;
       return true;
   }
   enum string shades = ".:!*oe&#%@";
   // positive and negative spheres
   enum pos = Sphere(20, 20, 0, 20);
   enum neg = Sphere(1, 1, -6, 20);
   foreach (int i; cast(int)floor(pos.cy - pos.r) ..
                   cast(int)ceil(pos.cy + pos.r) + 1) {
       immutable double y = i + 0.5;
       jloop:
       foreach (int j; cast(int)floor(pos.cx - 2 * pos.r) ..
                       cast(int)ceil(pos.cx + 2 * pos.r) + 1) {
           immutable double x = (j - pos.cx) / 2.0 + 0.5 + pos.cx;
           enum Hit { background, posSphere, negSphere }
           Hit hitResult;
           double zb1, zs2;
           {
               double zb2, zs1;
               if (!hitSphere(pos, x, y, zb1, zb2)) {
                   // Ray lands in blank space, draw bg
                   hitResult = Hit.background;
               } else if (!hitSphere(neg, x, y, zs1, zs2)) {
                   // Ray hits pos sphere but not neg one,
                   // draw pos sphere surface
                   hitResult = Hit.posSphere;
               } else if (zs1 > zb1) {
                   // ray hits both, but pos front surface is closer
                   hitResult = Hit.posSphere;
               } else if (zs2 > zb2) {
                   // pos sphere surface is inside neg sphere,
                   // show bg
                   hitResult = Hit.background;
               } else if (zs2 > zb1) {
                   // Back surface on neg sphere is inside pos
                   // sphere, the only place where neg sphere
                   // surface will be shown
                   hitResult = Hit.negSphere;
               } else {
                   hitResult = Hit.posSphere;
               }
           }
           V3 vec_;
           final switch (hitResult) {
               case Hit.background:
                   putchar(' ');
                   continue jloop;
               case Hit.posSphere:
                   vec_ = V3([x - pos.cx, y - pos.cy, zb1 - pos.cz]);
                   break;
               case Hit.negSphere:
                   vec_ = V3([neg.cx-x, neg.cy-y, neg.cz-zs2]);
                   break;
           }
           immutable V3 nvec = vec_.normalize;
           immutable double b = light.dot(nvec) ^^ k + ambient;
           int intensity = cast(int)((1 - b) * shades.length);
           intensity = min(shades.length, max(0, intensity));
           putchar(shades[intensity]);
       }
       putchar('\n');
   }

}


void main() {

   enum light = V3([-50, 30, 50]).normalize;
   drawSphere(2, 0.5, light);

}</lang> The output is the same of the C version.

DWScript

Translation of: C

<lang delphi>const cShades = '.:!*oe&#%@';

type TVector = array [0..2] of Float;

var light : TVector = [-50.0, 30, 50];

procedure Normalize(var v : TVector); begin

  var len := Sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
  v[0] /= len; v[1] /= len; v[2] /= len;

end;

function Dot(x, y : TVector) : Float; begin

  var d :=x[0]*y[0] + x[1]*y[1] + x[2]*y[2];
  if d<0 then 
     Result:=-d 
  else Result:=0;

end;

type

  TSphere = record
     cx, cy, cz, r : Float;
  end;
  

const big : TSphere = (cx: 20; cy: 20; cz: 0; r: 20); const small : TSphere = (cx: 7; cy: 7; cz: -10; r: 15);

function HitSphere(sph : TSphere; x, y : Float; var z1, z2 : Float) : Boolean; begin

  x -= sph.cx;
  y -= sph.cy;
  var zsq = sph.r * sph.r - (x * x + y * y);
  if (zsq < 0) then Exit False;
  zsq := Sqrt(zsq);
  z1 := sph.cz - zsq;
  z2 := sph.cz + zsq;
  Result:=True;

end;

procedure DrawSphere(k, ambient : Float); var

  i, j, intensity : Integer;
  b : Float;
  x, y, zb1, zb2, zs1, zs2 : Float;
  vec : TVector;

begin

  for i:=Trunc(big.cy-big.r) to Trunc(big.cy+big.r)+1 do begin
     y := i + 0.5;
     for j := Trunc(big.cx-2*big.r) to Trunc(big.cx+2*big.r) do begin
        x := (j-big.cx)/2 + 0.5 + big.cx;

        if not HitSphere(big, x, y, zb1, zb2) then begin
           Print(' ');
           continue;
        end;
        if not HitSphere(small, x, y, zs1, zs2) then begin
           vec[0] := x - big.cx;
           vec[1] := y - big.cy;
           vec[2] := zb1 - big.cz;
        end else begin
           if zs1 < zb1 then begin
              if zs2 > zb2 then begin
                 Print(' ');
                 continue;
              end;
              if zs2 > zb1 then begin
                 vec[0] := small.cx - x;
                 vec[1] := small.cy - y;
                 vec[2] := small.cz - zs2;
              end else begin
                 vec[0] := x - big.cx;
                 vec[1] := y - big.cy;
                 vec[2] := zb1 - big.cz;
              end;
           end else begin
              vec[0] := x - big.cx;
              vec[1] := y - big.cy;
              vec[2] := zb1 - big.cz;
           end;
        end;

        Normalize(vec);
        b := Power(Dot(light, vec), k) + ambient;
        intensity := Round((1 - b) * Length(cShades));
        Print(cShades[ClampInt(intensity+1, 1, Length(cShades))]);
     end;
     PrintLn();
  end;

end;

Normalize(light);

DrawSphere(2, 0.3);</lang>

J

Translation of: Python

<lang J>mag =: +/&.:*:"1 norm=: %"1 0 mag dot =: +/@:*"1

NB. (pos;posr;neg;negr) getvec (x,y) getvec =: 4 :0 "1

 pt =. y
 'pos posr neg negr' =. x
 if. (dot~ pt-}:pos) > *:posr do.
   0 0 0
 else.
   zb =. ({:pos) (-,+)  posr -&.:*: pt mag@:- }:pos
   if. (dot~ pt-}:neg) > *:negr do.
     (pt,{:zb) - pos
   else.
     zs =. ({:neg) (-,+) negr -&.:*: pt mag@:- }:neg
     if. zs >&{. zb do. (pt,{:zb) - pos
     elseif. zs >&{: zb do. 0 0 0
     elseif. ({.zs) < ({:zb) do. neg - (pt,{.zs)
     elseif. do. (pt,{.zb) - pos end.
   end.
 end.

)


NB. (k;ambient;light) draw_sphere (pos;posr;neg;negr) draw_sphere =: 4 :0

 'pos posr neg negr' =. y
 'k ambient light' =. x
 vec=. norm y getvec ,"0// (2{.pos) +/ i: 200 j.~ 0.5+posr
 b=. (mag vec) * ambient + k * 0>. light dot vec

)

togray =: 256#. 255 255 255 <.@*"1 0 (%>./@,)

env=.(2; 0.5; (norm _50 30 50)) sph=. 20 20 0; 20; 1 1 _6; 20 'rgb' viewmat togray env draw_sphere sph</lang>

Alternative Solution:

This version classifies surface normals by comparison to reference surfaces. Multiple spheres may be removed. Exercises for the intrepid student: enhance color resolution with more reference surfaces or combinations of all surfaces matching better than some criterion, incorporate ThinFaces for anti-aliasing, map the colors for realism.

<lang J> load'graphics/viewmat'

resolution=: 8 spheres=: 3 1 #"1 ] 0 1,_1 1,:0.3 0.6 NB. spheres x y z r


coordinates=: (% <:)~ (,"1 0~"0 3 ,"0"1 0~)@:i: length=: +/ &.: *: centers=: _ 3&{. radii=: _ _1&{.

NB. resolution SlicePittedSphere spheres generates a binary array, 1 in the geometric object SlicePittedSphere=: (0 { {. > [: +./ }.)@:(radii@[ >:"0 3 ((length@:-"1"_ 1 centers)~ coordinates))~

spanTo=: conjunction def '(m<:y)*.y<:n' NB. algebraic similarity, m <= y <= n

tessellate=: ] ];._3"3~ 3 # [ NB. All cubical edge length x subarrays of array y

NB. Define "faces" as those points with 9 to 18 inclusive "solid" neighbors. detectFace=: (9 spanTo 18) @: (+/@:,"3) @: (3&tessellate)

NB. arrange faces in ANSYS brick face order ThickFaces=: ((|:"3 , (, |:"2))(,: |."1)3 3 3$2j1#1) /: 'SENWDU' i. 'DUEWSN' ThinFaces=: ((|:"3 , (, |:"2))(,: |."1)3 3 3$1j2#1) /: 'SENWDU' i. 'DUEWSN'

FACES=:ThickFaces NB. 6 below comes from #Faces

NORMALS=: 2 tessellate FACES

matchNormals=: [: +/@,"6 NORMALS ="6"6 _ (2 tessellate 3 tessellate ])

bestFit=: (i.>./)"1&.|:

topFace=: detectFace i:"1 1:

choose=: 4 : 'x}y'

viewmat resolution (topFace choose (,&(#FACES))@:bestFit@matchNormals)@SlicePittedSphere spheres


  <"_1 ThickFaces         NB. display the 6 cubes with reference faces

┌─────┬─────┬─────┬─────┬─────┬─────┐ │1 1 1│1 1 0│0 0 0│0 1 1│1 1 1│0 0 0│ │1 1 1│1 1 0│1 1 1│0 1 1│1 1 1│0 0 0│ │0 0 0│1 1 0│1 1 1│0 1 1│1 1 1│0 0 0│ │ │ │ │ │ │ │ │1 1 1│1 1 0│0 0 0│0 1 1│1 1 1│1 1 1│ │1 1 1│1 1 0│1 1 1│0 1 1│1 1 1│1 1 1│ │0 0 0│1 1 0│1 1 1│0 1 1│1 1 1│1 1 1│ │ │ │ │ │ │ │ │1 1 1│1 1 0│0 0 0│0 1 1│0 0 0│1 1 1│ │1 1 1│1 1 0│1 1 1│0 1 1│0 0 0│1 1 1│ │0 0 0│1 1 0│1 1 1│0 1 1│0 0 0│1 1 1│ └─────┴─────┴─────┴─────┴─────┴─────┘ </lang>

Perl

Writes a PGM to stdout. <lang perl>use strict;

sub sq { my $s = 0; $s += $_ ** 2 for @_; $s; }

sub hit { my ($sph, $x, $y) = @_; $x -= $sph->[0]; $y -= $sph->[1];

my $z = sq($sph->[3]) - sq($x, $y); return if $z < 0;

$z = sqrt $z; return $sph->[2] - $z, $sph->[2] + $z; }

sub normalize { my $v = shift; my $n = sqrt sq(@$v); $_ /= $n for @$v; $v; }

sub dot { my ($x, $y) = @_; my $s = $x->[0] * $y->[0] + $x->[1] * $y->[1] + $x->[2] * $y->[2]; $s > 0 ? $s : 0; }

my $pos = [ 120, 120, 0, 120 ]; my $neg = [ -77, -33, -100, 190 ]; my $light = normalize([ -12, 13, -10 ]); sub draw { my ($k, $amb) = @_; binmode STDOUT, ":raw"; print "P5\n", $pos->[0] * 2 + 3, " ", $pos->[1] * 2 + 3, "\n255\n"; for my $y (($pos->[1] - $pos->[3] - 1) .. ($pos->[1] + $pos->[3] + 1)) { my @row = (); for my $x (($pos->[0] - $pos->[3] - 1) .. ($pos->[0] + $pos->[3] + 1)) { my ($hit, @hs) = 0; my @h = hit($pos, $x, $y);

if (!@h) { $hit = 0 } elsif (!(@hs = hit($neg, $x, $y))) { $hit = 1 } elsif ($hs[0] > $h[0]) { $hit = 1 } elsif ($hs[1] > $h[0]) { $hit = $hs[1] > $h[1] ? 0 : 2 } else { $hit = 1 }

my ($val, $v); if ($hit == 0) { $val = 0 } elsif ($hit == 1) { $v = [ $x - $pos->[0], $y - $pos->[1], $h[0] - $pos->[2] ]; } else { $v = [ $neg->[0] - $x, $neg->[1] - $y, $neg->[2] - $hs[1] ]; } if ($v) { normalize($v); $val = int((dot($v, $light) ** $k + $amb) * 255); $val = ($val > 255) ? 255 : ($val < 0) ? 0 : $val; } push @row, $val; } print pack("C*", @row); } }

draw(2, 0.2);</lang>

Perl 6

Translation of: C

Reimplemented to output a .pgm image.

<lang perl6>class sphere {

  has $.cx; # center x coordinate
  has $.cy; # center y coordinate
  has $.cz; # center z coordinate
  has $.r;  # radius

}

my $depth = 255; # image color depth

my $x = my $y = 255; # dimensions of generated .pgm; must be odd

my $s = ($x - 1)/2; # scaled dimension to build geometry

my @light = normalize([ 4, -1, -3 ]);

  1. positive sphere at origin

my $pos = sphere.new(

   cx => 0,
   cy => 0,
   cz => 0,
   r  => $s.Int

);

  1. negative sphere offset to upper left

my $neg = sphere.new(

   cx => (-$s*.90).Int,
   cy => (-$s*.90).Int,
   cz => (-$s*.3).Int,
   r  => ($s*.7).Int

);

sub MAIN ($outfile = 'deathstar-perl6.pgm') {

   my $out = open( $outfile, :w, :bin ) or die "$!\n";
   $out.say("P5\n$x $y\n$depth"); # .pgm header
   say 'Calculating row:';
   $out.print( draw_ds(3, .15)».chrs );
   $out.close;

}

sub draw_ds ( $k, $ambient ) {

   my @pixels;
   my $bs = "\b" x 8;
   for ($pos.cy - $pos.r) .. ($pos.cy + $pos.r) -> $y {
       note $bs, $y, ' '; # monitor progress
       for ($pos.cx - $pos.r) .. ($pos.cx + $pos.r) -> $x {
           # black if we don't hit positive sphere, ignore negative sphere
           if not hit($pos, $x, $y, my $posz) {
               @pixels.push(0);
               next;
           }
           my @vec;
           # is front of positive sphere inside negative sphere?
           if hit($neg, $x, $y, my $negz) and $negz.min < $posz.min < $negz.max {
               # make black if whole positive sphere eaten here
               if $negz.min < $posz.max < $negz.max { @pixels.push(0); next; }
               # render inside of negative sphere
               @vec = normalize([$neg.cx - $x, $neg.cy - $y, -$negz.max - $neg.cz]);
           }
           else {
               # render outside of positive sphere
               @vec = normalize([$x - $pos.cx, $y - $pos.cy,  $posz.max - $pos.cz]);
           }
           my $intensity = dot(@light, @vec) ** $k + $ambient;
           @pixels.push( ($intensity * $depth).Int min $depth );
       }
   }
   say $bs, 'Writing file.';
   return @pixels;

}

  1. normalize a vector

sub normalize (@vec) { return @vec »/» ([+] @vec Z* @vec).sqrt }

  1. dot product of two vectors

sub dot (@x, @y) { return -([+] @x Z* @y) max 0 }

  1. are the coordinates within the radius of the sphere?

sub hit ($sphere, $x is copy, $y is copy, $z is rw) {

   $x -= $sphere.cx;
   $y -= $sphere.cy;
   my $z2 = $sphere.r * $sphere.r - $x * $x - $y * $y;
   return 0 if $z2 < 0;
   $z2 = $z2.sqrt;
   $z = $sphere.cz - $z2 .. $sphere.cz + $z2;
   return 1;

}</lang>

Python

Translation of: C

<lang python>import sys, math, collections

Sphere = collections.namedtuple("Sphere", "cx cy cz r") V3 = collections.namedtuple("V3", "x y z")

def normalize((x, y, z)):

   len = math.sqrt(x**2 + y**2 + z**2)
   return V3(x / len, y / len, z / len)

def dot(v1, v2):

   d = v1.x*v2.x + v1.y*v2.y + v1.z*v2.z
   return -d if d < 0 else 0.0

def hit_sphere(sph, x0, y0):

   x = x0 - sph.cx
   y = y0 - sph.cy
   zsq = sph.r ** 2 - (x ** 2 + y ** 2)
   if zsq < 0:
       return (False, 0, 0)
   szsq = math.sqrt(zsq)
   return (True, sph.cz - szsq, sph.cz + szsq)

def draw_sphere(k, ambient, light):

   shades = ".:!*oe&#%@"
   pos = Sphere(20.0, 20.0, 0.0, 20.0)
   neg = Sphere(1.0, 1.0, -6.0, 20.0)
   for i in xrange(int(math.floor(pos.cy - pos.r)),
                   int(math.ceil(pos.cy + pos.r) + 1)):
       y = i + 0.5
       for j in xrange(int(math.floor(pos.cx - 2 * pos.r)),
                       int(math.ceil(pos.cx + 2 * pos.r) + 1)):
           x = (j - pos.cx) / 2.0 + 0.5 + pos.cx
           (h, zb1, zb2) = hit_sphere(pos, x, y)
           if not h:
               hit_result = 0
           else:
               (h, zs1, zs2) = hit_sphere(neg, x, y)
               if not h:
                   hit_result = 1
               elif zs1 > zb1:
                   hit_result = 1
               elif zs2 > zb2:
                   hit_result = 0
               elif zs2 > zb1:
                   hit_result = 2
               else:
                   hit_result = 1
           if hit_result == 0:
               sys.stdout.write(' ')
               continue
           elif hit_result == 1:
               vec = V3(x - pos.cx, y - pos.cy, zb1 - pos.cz)
           elif hit_result == 2:
               vec = V3(neg.cx-x, neg.cy-y, neg.cz-zs2)
           vec = normalize(vec)
           b = dot(light, vec) ** k + ambient
           intensity = int((1 - b) * len(shades))
           intensity = min(len(shades), max(0, intensity))
           sys.stdout.write(shades[intensity])
       print

light = normalize(V3(-50, 30, 50)) draw_sphere(2, 0.5, light)</lang>

Openscad

<lang openscad>// We are performing geometric subtraction

difference() {

 // Create the primary sphere of radius 60 centred at the origin
 translate(v = [0,0,0]) {
   sphere(60);
 }
 /*Subtract an overlapping sphere with a radius of 40
    The resultant hole will be smaller than this, because we only
    only catch the edge
 */
 translate(v = [0,90,0]) {
   sphere(40);
 }

}</lang>

POV-Ray

<lang POV-Ray>camera { perspective location <0.0 , .8 ,-3.0> look_at 0

        aperture .1 blur_samples 20 variance 1/100000 focal_point 0}
                           

light_source{< 3,3,-3> color rgb 1}

sky_sphere { pigment{ color rgb <0,.2,.5>}}

plane {y,-5 pigment {color rgb .54} normal {hexagon} }

difference {

sphere { 0,1 }
sphere { <-1,1,-1>,1 }
 texture { 
   pigment{ granite } 
   finish { phong 1 reflection {0.10 metallic 0.5} }
 } 

} </lang>

Tcl

Translation of: C

Note that this code has a significant amount of refactoring relative to the C version, including the addition of specular reflections and the separation of the scene code from the raytracing from the rendering. <lang 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)}]

}

  1. 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

   }

}

  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

}

  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}]

}

  1. 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

   }

}

  1. 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

  1. 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</lang> Output:

                                #######&eeeeeeeee                                 
                         ee&&&&&&########%eeoooooooooooe                          
                     **oooee&&&&&&########%ooooo**********oo                      
                  !!!***oooee&&&&&&########%********!!!!!!!!***                   
               !!!!!!!****ooee&&&&&&#######%*****!!!!!!!!!!!!!!!**                
             ::::!!!!!!***oooee&&&&&&######***!!!!!!!::::::::::::!!*              
           :::::::!!!!!!***ooeee&&&&&&#####**!!!!!!:::::::::::::::::!*            
         ::::::::::!!!!!***oooee&&&&&&####*!!!!!!::::::::.........::::!*          
        ::::::::::!!!!!!***oooeee&&&&&&###!!!!!!:::::::..............:::!         
      ..:::::::::!!!!!!****oooeee&&&&&&##!!!!!!::::::..................::!*       
     ...::::::::!!!!!!****ooooeee&&&&&&!!!!!!:::::::....................::!*      
    ....::::::!!!!!!*****ooooeeee&&&&&!!!!!!:::::::......................::!*     
   ....::::::!!!!!*****oooooeeeee&&&&!!!!!!::::::::.......................::!*    
   ...::::::!!!!!*****oooooeeeee&&&!!!!!!:::::::::.........................::!    
  ...:::::!!!!!*****oooooeeeeee&&!!!!!!!:::::::::..........................::!*   
  ..:::::!!!!!****oooooeeeeee&&&!!!!!!!::::::::::..........................::!!   
 .::::::!!!!*****ooooeeeeee&&*!!!!!!!::::::::::::.........................:::!!*  
 :::::!!!!!****oooooeeeee&&**!!!!!!!::::::::::::::.......................::::!!*  
 !!!!!!!!****oooooeeeee&****!!!!!!!::::::::::::::::::..................::::::!!*  
 #!!!******oooooeeeeeoo*****!!!!!!!:::::::::::::::::::::::::::::::::::::::::!!!*  
 ##oooooooooooeeeeeeoooo****!!!!!!!:::::::::::::::::::::::::::::::::::::::!!!!**  
 %#####eeee&&&&&&&eeeooo****!!!!!!!!:::::::::::::::::::::::::::::::::::!!!!!!**o  
 %#########&&&&&&&&eeeooo****!!!!!!!!!::::::::::::::::::!!!!!!!!!!!!!!!!!!!****o  
 %##########&&&&&&&&eeeooo****!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!****ooe  
  %##########&&&&&&&&eeeooo*****!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!**********ooo   
  %%##########&&&&&&&&eeeoooo*****!!!!!!!!!!!!!!!!!!!*********************ooooe   
   %%##########&&&&&&&&eeeoooo***************************************oooooooee    
   @%###########&&&&&&&&&eeeooooo*************************ooooooooooooooooeee&    
    @%###########&&&&&&&&&eeeeoooooo*************ooooooooooooooooooooooeeeee&     
     @%%##########&&&&&&&&&&eeeeoooooooooooooooooooooooooooooooeeeeeeeeeee&&      
      @%%###########&&&&&&&&&&eeeeeoooooooooooooooooooeeeeeeeeeeeeeeeeee&&&       
        %%############&&&&&&&&&&eeeeeeeeeeooeeeeeeeeeeeeeeeeeeeeeeee&&&&&         
         @%%###########&&&&&&&&&&&&eeeeeeeeeeeeeeeeeeeeeeeeee&&&&&&&&&&&          
           %%############&&&&&&&&&&&&&&eeeeeeeeeeeeeee&&&&&&&&&&&&&&&&            
             %%############&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&              
               %%#############&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&                
                  %%#############&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&                   
                     %##############&&&&&&&&&&&&&&&&&&&&&&&&                      
                         %##############&&&&&&&&&&&&&&&&                          
                                #################                                 

To render it as an image, we just supply different code to map the intensities to displayable values:

Library: Tk
Rendering of the Death Star by the Tcl solution.

<lang tcl># Render as a picture (with many hard-coded settings) package require Tk proc guiDeathStar {photo diff spec lightBrightness ambient} {

   set row 0
   for {set i 255} {$i>=0} {incr i -1} {

lappend shades [format "#%02x%02x%02x" $i $i $i]

   }
   raytraceEngine [list $diff $lightBrightness] \

[list $spec $lightBrightness] $ambient intersectDeathStar \ $shades {apply {l { upvar 2 photo photo row row $photo put [list $l] -to 0 $row incr row update }}} 0 40 0.0625 0 40 0.0625 } pack [label .l -image [image create photo ds]] guiDeathStar ds 3 10 0.7 0.3</lang>