Death Star

From Rosetta Code
Task
Death Star
You are encouraged to solve this task according to the task description, using any language you may know.
Task

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".)


Related tasks



11l

Translation of: Python
T Sphere
   Float cx, cy, cz, r
   F (cx, cy, cz, r)
      .cx = cx
      .cy = cy
      .cz = cz
      .r = r

F dotp(v1, v2)
   V d = dot(v1, v2)
   R I d < 0 {-d} E 0.0

F hit_sphere(sph, x0, y0)
   V x = x0 - sph.cx
   V y = y0 - sph.cy
   V zsq = sph.r ^ 2 - (x ^ 2 + y ^ 2)
   I zsq < 0
      R (0B, 0.0, 0.0)
   V szsq = sqrt(zsq)
   R (1B, sph.cz - szsq, sph.cz + szsq)

F draw_sphere(k, ambient, light)
   V shades = ‘.:!*oe&#%@’
   V pos = Sphere(20.0, 20.0, 0.0, 20.0)
   V neg = Sphere(1.0, 1.0, -6.0, 20.0)

   L(i) Int(floor(pos.cy - pos.r)) .< Int(ceil(pos.cy + pos.r) + 1)
      V y = i + 0.5
      L(j) Int(floor(pos.cx - 2 * pos.r)) .< Int(ceil(pos.cx + 2 * pos.r) + 1)
         V x = (j - pos.cx) / 2.0 + 0.5 + pos.cx

         V (h, zb1, zb2) = hit_sphere(pos, x, y)
         Int hit_result
         Float zs2
         I !h
            hit_result = 0
         E
            (h, V zs1, zs2) = hit_sphere(neg, x, y)
            I !h
               hit_result = 1
            E I zs1 > zb1
               hit_result = 1
            E I zs2 > zb2
               hit_result = 0
            E I zs2 > zb1
               hit_result = 2
            E
               hit_result = 1

         V vec = (0.0, 0.0, 0.0)
         I hit_result == 0
            print(‘ ’, end' ‘’)
            L.continue
         E I hit_result == 1
            vec = (x - pos.cx, y - pos.cy, zb1 - pos.cz)
         E I hit_result == 2
            vec = (neg.cx - x, neg.cy - y, neg.cz - zs2)
         vec = normalize(vec)

         V b = dotp(light, vec) ^ k + ambient
         V intensity = Int((1 - b) * shades.len)
         intensity = min(shades.len, max(0, intensity))
         print(shades[intensity], end' ‘’)
      print()

V light = normalize((-50.0, 30.0, 50.0))
draw_sphere(2, 0.5, light)
Output:
                                    eeeee:::::::                                 
                                eeeeeeeee..............                          
                             ooeeeeeeeeee..................                      
                           ooooeeeeeeeee......................                   
                        oooooooeeeeeeee..........................                
                      ooooooooooeeeee..............................              
                    **ooooooooooeeee.................................            
                  ****ooooooooooee.....................................          
                !*****ooooooooooe.......................................         
              !!!*****ooooooooo:..........................................       
            :!!!!*****ooooooo:::...........................................      
          :::!!!!*****ooooo!:::::...........................................     
        ::::!!!!!*****ooo!!!!::::............................................    
       .::::!!!!*****oo*!!!!!::::............................................    
     ...::::!!!!*********!!!!:::::............................................   
    ...::::!!!!****o*****!!!!!::::............................................   
  ....::::!!!!***ooo******!!!!!::::............................................  
 ....::::!!!!*ooooooo*****!!!!!:::::...........................................  
...::::!!!!!oooooooooo*****!!!!!:::::..........................................  
:::::!!!!eeooooooooooo******!!!!!:::::.........................................  
!!!!!eeeeeeeooooooooooo******!!!!!:::::........................................  
eeeeeeeeeeeeoooooooooooo******!!!!!:::::.......................................  
eeeeeeeeeeeeeoooooooooooo******!!!!!!:::::.....................................  
eeeeeeeeeeeeeeoooooooooooo******!!!!!!:::::....................................  
 eeeeeeeeeeeeeeoooooooooooo*******!!!!!!:::::.................................   
 eeeeeeeeeeeeeeeooooooooooooo******!!!!!!::::::..............................:   
  eeeeeeeeeeeeeeeooooooooooooo*******!!!!!!:::::::..........................:    
  eeeeeeeeeeeeeeeeoooooooooooooo*******!!!!!!!:::::::.....................::!    
   eeeeeeeeeeeeeeeeeooooooooooooo********!!!!!!!:::::::::..............::::!     
    eeeeeeeeeeeeeeeeeoooooooooooooo********!!!!!!!!::::::::::::::::::::::!*      
     eeeeeeeeeeeeeeeeeeooooooooooooooo********!!!!!!!!!!:::::::::::::!!!!*       
       eeeeeeeeeeeeeeeeeoooooooooooooooo**********!!!!!!!!!!!!!!!!!!!!!*         
        eeeeeeeeeeeeeeeeeeooooooooooooooooo************!!!!!!!!!!!!****          
          eeeeeeeeeeeeeeeeeeoooooooooooooooooo**********************o            
            eeeeeeeeeeeeeeeeeeeooooooooooooooooooooo************ooo              
              eeeeeeeeeeeeeeeeeeeeooooooooooooooooooooooooooooooo                
                 eeeeeeeeeeeeeeeeeeeeooooooooooooooooooooooooo                   
                    eeeeeeeeeeeeeeeeeeeeeoooooooooooooooooo                      
                        eeeeeeeeeeeeeeeeeeeeeeeeeeeeeee                          
                               eeeeeeeeeeeeeeeee                                 

Ada

Library: SDLAda
Translation of: Go
with Ada.Numerics.Elementary_Functions;
with Ada.Numerics.Generic_Real_Arrays;

with SDL.Video.Windows.Makers;
with SDL.Video.Renderers.Makers;
with SDL.Video.Palettes;
with SDL.Events.Events;

procedure Death_Star is

   Width   : constant := 400;
   Height  : constant := 400;

   package Float_Arrays is
      new Ada.Numerics.Generic_Real_Arrays (Float);
   use Ada.Numerics.Elementary_Functions;
   use Float_Arrays;

   Window   : SDL.Video.Windows.Window;
   Renderer : SDL.Video.Renderers.Renderer;

   subtype Vector_3 is Real_Vector (1 .. 3);

   type Sphere_Type is record
      Cx, Cy, Cz : Integer;
      R          : Integer;
   end record;

   function Normalize (V : Vector_3) return Vector_3 is
      (V / Sqrt (V * V));

   procedure Hit (S      :     Sphere_Type;
                  X, Y   :     Integer;
                  Z1, Z2 : out Float;
                  Is_Hit : out Boolean)
   is
      NX    : constant Integer := X - S.Cx;
      NY    : constant Integer := Y - S.Cy;
      Zsq   : constant Integer := S.R * S.R - (NX * NX + NY * NY);
      Zsqrt : Float;
   begin
      if Zsq >= 0 then
         Zsqrt  := Sqrt (Float (Zsq));
         Z1     := Float (S.Cz) - Zsqrt;
         Z2     := Float (S.Cz) + Zsqrt;
         Is_Hit := True;
         return;
      end if;
      Z1     := 0.0;
      Z2     := 0.0;
      Is_Hit := False;
   end Hit;

   procedure Draw_Death_Star (Pos, Neg : Sphere_Type;
                              K, Amb   : Float;
                              Dir      : Vector_3)
   is
      Vec      : Vector_3;
      ZB1, ZB2 : Float;
      ZS1, ZS2 : Float;
      Is_Hit   : Boolean;
      S        : Float;
      Lum      : Integer;
   begin
      for Y in Pos.Cy - Pos.R .. Pos.Cy + Pos.R loop
         for X in Pos.Cx - Pos.R .. Pos.Cx + Pos.R loop
            Hit (Pos, X, Y, ZB1, ZB2, Is_Hit);
            if not Is_Hit then
               goto Continue;
            end if;
            Hit (Neg, X, Y, ZS1, ZS2, Is_Hit);
            if Is_Hit then
               if ZS1 > ZB1 then
                  Is_Hit := False;
               elsif ZS2 > ZB2 then
                  goto Continue;
               end if;
            end if;

            if Is_Hit then
               Vec := (Float (Neg.Cx - X),
                       Float (Neg.Cy - Y),
                       Float (Neg.Cz) - ZS2);
            else
               Vec := (Float (X - Pos.Cx),
                       Float (Y - Pos.Cy),
                       ZB1 - Float (Pos.Cz));
            end if;
            S := Float'Max (0.0, Dir * Normalize (Vec));

            Lum := Integer (255.0 * (S ** K + Amb) / (1.0 + Amb));
            Lum := Integer'Max (0, Lum);
            Lum := Integer'Min (Lum, 255);

            Renderer.Set_Draw_Colour ((SDL.Video.Palettes.Colour_Component (Lum),
                                       SDL.Video.Palettes.Colour_Component (Lum),
                                       SDL.Video.Palettes.Colour_Component (Lum),
                                       255));
            Renderer.Draw (Point => (SDL.C.int (X + Width  / 2),
                                     SDL.C.int (Y + Height / 2)));
            <<Continue>>
         end loop;
      end loop;
   end Draw_Death_Star;

   procedure Wait is
      use type SDL.Events.Event_Types;
      Event : SDL.Events.Events.Events;
   begin
      loop
         while SDL.Events.Events.Poll (Event) loop
            if Event.Common.Event_Type = SDL.Events.Quit then
               return;
            end if;
         end loop;
         delay 0.100;
      end loop;
   end Wait;

   Direction : constant Vector_3    := Normalize ((20.0, -40.0, -10.0));
   Positive  : constant Sphere_Type := (0, 0, 0, 120);
   Negative  : constant Sphere_Type := (-90, -90, -30, 100);
begin
   if not SDL.Initialise (Flags => SDL.Enable_Screen) then
      return;
   end if;

   SDL.Video.Windows.Makers.Create (Win      => Window,
                                    Title    => "Death star",
                                    Position => SDL.Natural_Coordinates'(X => 10, Y => 10),
                                    Size     => SDL.Positive_Sizes'(Width, Height),
                                    Flags    => 0);
   SDL.Video.Renderers.Makers.Create (Renderer, Window.Get_Surface);
   Renderer.Set_Draw_Colour ((0, 0, 0, 255));
   Renderer.Fill (Rectangle => (0, 0, Width, Height));

   Draw_Death_Star (Positive, Negative, 1.5, 0.2, Direction);
   Window.Update_Surface;

   Wait;
   Window.Finalize;
   SDL.Finalise;
end Death_Star;

ALGOL 68

Translation of: C
Translation of: 11l
BEGIN # draw a "Death Star" - translated from the C and 11l samples #
    STRING shades = ".:!*oe&#%@";

    PROC normalize = ( []REAL v )[]REAL:
         BEGIN
            REAL len = sqrt( v[ 1 ] * v[ 1 ] + v[ 2 ] * v[ 2 ] + v[ 3 ] * v[ 3 ] );
            ( v[ 1 ] / len, v[ 2 ] / len, v[ 3 ] / len )
         END # normalize # ;

    PROC dot = ( []REAL x, y )REAL:
         BEGIN
            REAL d = x[ 1 ] * y[ 1 ] + x[ 2 ] * y[ 2 ] + x[ 3 ] * y[ 3 ];
            IF d < 0 THEN - d ELSE 0 FI
         END # dot # ;

    MODE SPHERE = STRUCT( REAL cx, cy, cz, r );

    # positive shpere and negative sphere #
    SPHERE 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                  #
    PROC hit_sphere = ( SPHERE sph, REAL x in, y in, REF REAL z1, z2 )BOOL:
         IF   REAL x    = x in - cx OF sph;
              REAL y    = y in - cy OF sph;
              REAL zsq := r OF sph * r OF sph - ( x * x + y * y );
              zsq < 0
         THEN FALSE
         ELSE zsq := sqrt( zsq );
              z1  := cz OF sph - zsq;
              z2  := cz OF sph + zsq;
              TRUE
         FI # hit_sphere # ;

    PROC draw_sphere = ( REAL k, ambient, []REAL light )VOID:
         FOR i FROM ENTIER ( cy OF pos - r OF pos ) TO ENTIER ( cy OF pos + r OF pos ) + 1 DO
             REAL y := i + 0.5;
             FOR j FROM ENTIER ( cx OF pos - 2 * r OF pos ) TO ENTIER (cx OF pos + 2 * r OF pos ) + 1 DO
                 REAL x := ( j - cx OF pos ) / 2.0 + 0.5 + cx OF pos;
                 REAL zb1 := 0, zb2 := 0, zs1 := 0, zs2 := 0;
                 INT hit_result
                         = IF   NOT hit_sphere( pos, x, y, zb1, zb2 ) THEN
                               0   # ray lands in blank space, draw bg #
                           ELIF NOT hit_sphere( neg, x, y,  zs1,  zs2 ) THEN
                               1   # ray hits pos sphere but not neg, draw pos sphere surface #
                           ELIF zs1 > zb1 THEN
                               1   # ray hits both, but pos front surface is closer #
                           ELIF zs2 > zb2 THEN
                               0   # pos sphere surface is inside neg sphere, show bg #
                           ELIF zs2 > zb1 THEN
                               2   # back surface on neg sphere is inside pos sphere,      #
                                   # the only place where neg sphere surface will be shown #
                           ELSE
                               1   # show the pos sphere #
                           FI;
                 IF hit_result = 0 THEN
                     print( ( " " ) )
                 ELSE
                     []REAL vec =
                         normalize( IF hit_result = 1
                                    THEN []REAL( x   - cx OF pos
                                               , y   - cy OF pos
                                               , zb1 - cz OF pos
                                               )
                                    ELSE []REAL( cx OF neg - x
                                               , cy OF neg - y
                                               , cz OF neg - zs2
                                               )
                                    FI
                                  );
                     REAL b          = ( dot( light, vec ) ^ k ) + ambient;
                     INT  intensity := ENTIER ( ( 1 - b ) * ( ( UPB shades - LWB shades ) + 1 ) ) + 1;
                     IF   intensity < LWB shades THEN intensity := LWB shades
                     ELIF intensity > UPB shades THEN intensity := UPB shades
                     FI;
                     print( ( shades[ intensity ] ) )
                 FI
             OD;
             print( ( newline ) )
         OD # draw_sphere # ;

    BEGIN
        []REAL light = ( -50, 30, 50 );
        draw_sphere( 2, 0.5, normalize( light ) )
    END
END
Output:

Same as 11l

AutoHotkey

Library: GDIP
#NoEnv
SetBatchLines, -1
#SingleInstance, Force

; Uncomment if Gdip.ahk is not in your standard library
#Include, Gdip.ahk

; Settings
X := 200, Y := 200, Width := 200, Height := 200 ; Location and size of sphere
rotation := 60 ; degrees
ARGB := 0xFFFF0000 ; Color=Solid Red

If !pToken := Gdip_Startup() ; Start gdi+
{
	MsgBox, 48, gdiplus error!, Gdiplus failed to start. Please ensure you have gdiplus on your system
	ExitApp
}
OnExit, Exit

Gui, -Caption +E0x80000 +LastFound +AlwaysOnTop +ToolWindow +OwnDialogs ; Create GUI
Gui, Show, NA ; Show GUI
hwnd1 := WinExist() ; Get a handle to this window we have created in order to update it later
hbm := CreateDIBSection(A_ScreenWidth, A_ScreenHeight) ; Create a gdi bitmap drawing area
hdc := CreateCompatibleDC() ; Get a device context compatible with the screen
obm := SelectObject(hdc, hbm) ; Select the bitmap into the device context
pGraphics := Gdip_GraphicsFromHDC(hdc) ; Get a pointer to the graphics of the bitmap, for use with drawing functions
Gdip_SetSmoothingMode(pGraphics, 4) ; Set the smoothing mode to antialias = 4 to make shapes appear smother

Gdip_TranslateWorldTransform(pGraphics, X, Y)
Gdip_RotateWorldTransform(pGraphics, rotation)

; Base ellipse
pBrush := Gdip_CreateLineBrushFromRect(0, 0, Width, Height, ARGB, 0xFF000000)
Gdip_FillEllipse(pGraphics, pBrush, 0, 0, Width, Height)

; First highlight ellipse
pBrush := Gdip_CreateLineBrushFromRect(Width*0.1, Height*0.01, Width*0.8, Height*0.6, 0x33FFFFFF, 0x00FFFFFF)
Gdip_FillEllipse(pGraphics, pBrush, Width*0.1, Height*0.01, Width*0.8, Height*0.6)

; Second highlight ellipse
pBrush := Gdip_CreateLineBrushFromRect(Width*0.3, Height*0.02, Width*0.3, Height*0.2, 0xBBFFFFFF, 0x00FFFFFF)
Gdip_FillEllipse(pGraphics, pBrush, Width*0.3, Height*0.02, Width*0.3, Height*0.2)


; Reset variables for smaller subtracted sphere
X-=150
Y-=10
Width*=0.5
Height*=0.4
rotation-=180

Gdip_TranslateWorldTransform(pGraphics, X, Y)
Gdip_RotateWorldTransform(pGraphics, rotation)

; Base ellipse
pBrush := Gdip_CreateLineBrushFromRect(0, 0, Width, Height, ARGB, 0xFF000000)
Gdip_FillEllipse(pGraphics, pBrush, 0, 0, Width, Height)

; First highlight ellipse
pBrush := Gdip_CreateLineBrushFromRect(Width*0.1, Height*0.01, Width*0.8, Height*0.6, 0x33FFFFFF, 0x00FFFFFF)
Gdip_FillEllipse(pGraphics, pBrush, Width*0.1, Height*0.01, Width*0.8, Height*0.6)

; Second highlight ellipse
pBrush := Gdip_CreateLineBrushFromRect(Width*0.3, Height*0.02, Width*0.3, Height*0.2, 0xBBFFFFFF, 0x00FFFFFF)
Gdip_FillEllipse(pGraphics, pBrush, Width*0.3, Height*0.02, Width*0.3, Height*0.2)


UpdateLayeredWindow(hwnd1, hdc, 0, 0, A_ScreenWidth, A_ScreenHeight)
SelectObject(hdc, obm) ; Select the object back into the hdc
Gdip_DeletePath(Path)
Gdip_DeleteBrush(pBrush)
DeleteObject(hbm) ; Now the bitmap may be deleted
DeleteDC(hdc) ; Also the device context related to the bitmap may be deleted
Gdip_DeleteGraphics(G) ; The graphics may now be deleted
Return

Exit:
; gdi+ may now be shutdown on exiting the program
Gdip_Shutdown(pToken)
ExitApp

Brlcad

# We need a database to hold the objects
opendb deathstar.g y

# We will be measuring in kilometers
units km

# Create a sphere of radius 60km centred at the origin
in sph1.s sph 0 0 0 60

# We will be subtracting an overlapping sphere with a radius of 40km
# The resultant hole will be smaller than this, because we only
# only catch the edge
in sph2.s sph 0 90 0 40

# Create a region named deathstar.r which consists of big minus small sphere
r deathstar.r u sph1.s - sph2.s

# We will use a plastic material texture with rgb colour 224,224,224
# with specular lighting value of 0.1 and no inheritance
mater deathstar.r "plastic sp=0.1" 224 224 224 0

# Clear the wireframe display and draw the deathstar
B deathstar.r

# We now trigger the raytracer to see our finished product
rt

C

Primitive ray tracing.

#include <stdio.h>
#include <math.h>
#include <unistd.h>

const 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;
}

D

Translation of: C
import std.stdio, std.math, std.numeric, std.algorithm;

struct V3 {
    double[3] v;

    @property V3 normalize() pure nothrow const @nogc {
        immutable double len = dotProduct(v, v).sqrt;
        return [v[0] / len, v[1] / len, v[2] / len].V3;
    }

    double dot(in ref V3 y) pure nothrow const @nogc {
        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(in ref Sphere sph,
                          in double x0, in double y0,
                          out double z1,
                          out double z2) pure nothrow @nogc {
        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 = zsq.sqrt;
        z1 = sph.cz - szsq;
        z2 = sph.cz + szsq;
        return true;
    }

    immutable shades = ".:!*oe&#%@";
    // Positive and negative spheres.
    immutable pos = Sphere(20, 20, 0, 20);
    immutable neg = Sphere(1, 1, -6, 20);

    foreach (immutable 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 }

            double zb1, zs2;
            immutable Hit hitResult = {
                double zb2, zs1;

                if (!hitSphere(pos, x, y, zb1, zb2)) {
                    // Ray lands in blank space, draw bg.
                    return Hit.background;
                } else if (!hitSphere(neg, x, y, zs1, zs2)) {
                    // Ray hits pos sphere but not neg one,
                    // draw pos sphere surface.
                    return Hit.posSphere;
                } else if (zs1 > zb1) {
                    // ray hits both, but pos front surface is closer.
                    return Hit.posSphere;
                } else if (zs2 > zb2) {
                    // pos sphere surface is inside neg sphere,
                    // show bg.
                    return 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.
                    return Hit.negSphere;
                } else {
                    return Hit.posSphere;
                }
            }();

            V3 vec_;
            final switch (hitResult) {
                case Hit.background:
                    ' '.putchar;
                    continue JLOOP;
                case Hit.posSphere:
                    vec_ = [x - pos.cx, y - pos.cy, zb1 - pos.cz].V3;
                    break;
                case Hit.negSphere:
                    vec_ = [neg.cx - x, neg.cy - y, neg.cz - zs2].V3;
                    break;
            }
            immutable nvec = vec_.normalize;

            immutable double b = light.dot(nvec) ^^ k + ambient;
            immutable intensity = cast(int)((1 - b) * shades.length);
            immutable normInt = min(shades.length, max(0, intensity));
            shades[normInt].putchar;
        }

        '\n'.putchar;
    }
}


void main() {
    immutable light = [-50, 30, 50].V3.normalize;
    drawSphere(2, 0.5, light);
}

The output is the same of the C version.

Delphi

Library: system.Math
Translation of: C

Translate of #C and #Go, with copy of some parts of #DWScript.

program Death_Star;

{$APPTYPE CONSOLE}

uses
  Winapi.Windows,
  System.SysUtils,
  system.Math,
  Vcl.Graphics,
  Vcl.Imaging.pngimage;

type
  TVector = array of double;

var
  light: TVector = [20, -40, -10];

function ClampInt(value, amin, amax: Integer): Integer;
begin
  Result := Max(amin, Min(amax, value))
end;

procedure Normalize(var v: TVector);
begin
  var len := Sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
  v[0] := v[0] / len;
  v[1] := v[1] / len;
  v[2] := v[2] / len;
end;

function Dot(x, y: TVector): Double;
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: Double;
  end;

const
  pos: TSphere = (
    cx: 0;
    cy: 0;
    cz: 0;
    r: 120
  );

const
  neg: TSphere = (
    cx: -90;
    cy: -90;
    cz: -30;
    r: 80
  );

function HitSphere(sph: TSphere; x, y: double; var z1, z2: Double): Boolean;
begin
  x := x - sph.cx;
  y := 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;

function DeathStar(pos, neg: TSphere; k, amb: Double; light: TVector): TBitmap;
var
  w, h, yMax, xMax, s: double;
  zp1, zp2, zn1, zn2, b: Double;
  x, y: Integer;
  hit: Boolean;
  vec: TVector;
  intensity: Byte;
  ox, oy: Integer;
begin
  w := pos.r * 4;
  h := pos.r * 3;
  ox := -trunc(pos.cx - w / 2);
  oy := -trunc(pos.cy - h / 2);

  vec := [0, 0, 0];
  Result := TBitmap.Create;
  Result.SetSize(trunc(w), trunc(h));

  yMax := pos.cy + pos.r;
  for y := Trunc(pos.cy - pos.r) to Trunc(yMax) do
  begin
    xMax := pos.cx + pos.r;
    for x := trunc(pos.cy - pos.r) to trunc(xMax) do
    begin
      hit := HitSphere(pos, x, y, zp1, zp2);
      if not hit then
        continue;

      hit := HitSphere(neg, x, y, zn1, zn2);

      if hit then
      begin
        if zn1 > zp1 then
          hit := false
        else if zn2 > zp2 then
          continue;
      end;

      if hit then
      begin
        vec[0] := neg.cx - x;
        vec[1] := neg.cy - y;
        vec[2] := neg.cz - zn2;
      end
      else
      begin
        vec[0] := x - pos.cx;
        vec[1] := y - pos.cy;
        vec[2] := zp1 - pos.cz;
      end;

      Normalize(vec);

      s := max(0, dot(light, vec));

      b := Power(s, k) + amb;

      intensity := ClampInt(round(255 * b / (1 + amb)), 0, 254);

      Result.Canvas.Pixels[x + ox, y + oy] := rgb(intensity, intensity, intensity);
    end;
  end;
end;

var
  bmp: TBitmap;

begin
  Normalize(light);
  bmp := DeathStar(pos, neg, 1.2, 0.3, light);

  with TPngImage.Create do
  begin
    Assign(bmp);
    TransparentColor := clwhite;
    SaveToFile('out.png');
    bmp.Free;
    Free;
  end;
end.

DWScript

Translation of: C
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);

Frink

This program not only draws a Death Star and renders it onscreen projected on the x,y, and z axes but also outputs a .stl file for 3-D printing. Frink has built-in routines for 3-D modeling.

res = 254 / in
v = callJava["frink.graphics.VoxelArray", "makeSphere", [1/2 inch res]]

dish = callJava["frink.graphics.VoxelArray", "makeSphere", [1/2 inch res]]
dish.translate[round[.45 inch res], round[.45 inch res], round[.45 inch res]]
v.remove[dish]

v.projectX[undef].show["X"]
v.projectY[undef].show["Y"]
v.projectZ[undef].show["Z"]   

filename = "DeathStar.stl"
print["Writing $filename..."]
w = new Writer[filename]
w.println[v.toSTLFormat["DeathStar", 1/(res mm)]]
w.close[]
println["done."]

Go

Output png
Translation of: C
package main

import (
    "fmt"
    "image"
    "image/color"
    "image/png"
    "math"
    "os"
)

type vector [3]float64

func (v *vector) normalize() {
    invLen := 1 / math.Sqrt(dot(v, v))
    v[0] *= invLen
    v[1] *= invLen
    v[2] *= invLen
}

func dot(x, y *vector) float64 {
    return x[0]*y[0] + x[1]*y[1] + x[2]*y[2]
}

type sphere struct {
    cx, cy, cz int
    r          int
}

func (s *sphere) hit(x, y int) (z1, z2 float64, hit bool) {
    x -= s.cx
    y -= s.cy
    if zsq := s.r*s.r - (x*x + y*y); zsq >= 0 {
        zsqrt := math.Sqrt(float64(zsq))
        return float64(s.cz) - zsqrt, float64(s.cz) + zsqrt, true
    }
    return 0, 0, false
}

func deathStar(pos, neg *sphere, k, amb float64, dir *vector) *image.Gray {
    w, h := pos.r*4, pos.r*3
    bounds := image.Rect(pos.cx-w/2, pos.cy-h/2, pos.cx+w/2, pos.cy+h/2)
    img := image.NewGray(bounds)
    vec := new(vector)
    for y, yMax := pos.cy-pos.r, pos.cy+pos.r; y <= yMax; y++ {
        for x, xMax := pos.cx-pos.r, pos.cx+pos.r; x <= xMax; x++ {
            zb1, zb2, hit := pos.hit(x, y)
            if !hit {
                continue
            }
            zs1, zs2, hit := neg.hit(x, y)
            if hit {
                if zs1 > zb1 {
                    hit = false
                } else if zs2 > zb2 {
                    continue
                }
            }
            if hit {
                vec[0] = float64(neg.cx - x)
                vec[1] = float64(neg.cy - y)
                vec[2] = float64(neg.cz) - zs2
            } else {
                vec[0] = float64(x - pos.cx)
                vec[1] = float64(y - pos.cy)
                vec[2] = zb1 - float64(pos.cz)
            }
            vec.normalize()
            s := dot(dir, vec)
            if s < 0 {
                s = 0
            }
            lum := 255 * (math.Pow(s, k) + amb) / (1 + amb)
            if lum < 0 {
                lum = 0
            } else if lum > 255 {
                lum = 255
            }
            img.SetGray(x, y, color.Gray{uint8(lum)})
        }
    }
    return img
}

func main() {
    dir := &vector{20, -40, -10}
    dir.normalize()
    pos := &sphere{0, 0, 0, 120}
    neg := &sphere{-90, -90, -30, 100}

    img := deathStar(pos, neg, 1.5, .2, dir)
    f, err := os.Create("dstar.png")
    if err != nil {
        fmt.Println(err)
        return
    }
    if err = png.Encode(f, img); err != nil {
        fmt.Println(err)
    }
    if err = f.Close(); err != nil {
        fmt.Println(err)
    }
}

Haskell

ASCII art

import Data.List (genericLength)

shades = ".:!*oe%#&@"
n = genericLength shades
dot a b = sum $ zipWith (*) a b
normalize x = (/ sqrt (x `dot` x)) <$> x

deathStar r k amb = unlines $
  [ [ if x*x + y*y <= r*r
      then let vec = normalize $ normal x y
               b = (light `dot` vec) ** k + amb
               intensity = (1 - b)*(n - 1)
           in shades !! round ((0 `max` intensity) `min` n)
      else ' '
    | y <- map (/2.12) [- 2*r - 0.5 .. 2*r + 0.5]  ]
  | x <- [ - r - 0.5 .. r + 0.5] ]
  where
    light = normalize [-30,-30,-50]
    normal x y
      | (x+r)**2 + (y+r)**2 <= r**2 = [x+r, y+r, sph2 x y]
      | otherwise = [x, y, sph1 x y]
    sph1 x y = sqrt (r*r - x*x - y*y)
    sph2 x y = r - sqrt (r*r - (x+r)**2 - (y+r)**2)
λ> putStrLn $ deathStar 10 4 0.1
                                            
                                            
               eeeeoo*&&&&&&&               
          eeeeeoooo**!&&&&&&&&&&&&          
       eeooooooo***!!&&&&&&&&&&&&&&&&       
     eooooo*****!!!::&&&&&&&&&&&&&&&&&&     
    eooo****!!!!:::.&&&&&&&&&&&&&&&&&&&&    
  eeeoo***!!!::::.&&&&&&##############&&&&  
 eeeoo***!!:::...&&&########%%%%%%%%#####&& 
 eeoo**!!!::...&&######%%%%%%%eeeeee%%%%### 
eooo**!!::..&&&#####%%%%%eeeeeooooooeeee%%#&
oo**!!:&&&&&&&####%%%%eeeoooo********oooee%#
&&&&&&&&&&&&#####%%%eeeoo****!!!!!!!!***oe%#
&&&&&&&&&&&####%%%eeeoo***!!!::::::::!!**oe#
 &&&&&&&&&&###%%%eeooo**!!:::.......::!!*oe 
 &&&&&&&&&####%%eeeoo**!!::..........::!*o% 
  &&&&&&&&####%%eeoo**!!::...........:!*o%  
    &&&&&&####%%eeoo**!!::..........:!*o    
     &&&&&&###%%%eeoo**!!::......::!*oe     
       &&&&&###%%%eeoo**!!!!:::!!!*o%       
          &&&&###%%%eeoooo****ooe%          
               &&####%%%%%%%#               
                                            
                                            

J

Translation of: Python
load'graphics/viewmat'
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

Java

Library: JavaFX
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Point3D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.TriangleMesh;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
public class DeathStar extends Application {

	private static final int DIVISION = 200;// the bigger the higher resolution
	float radius = 300;// radius of the sphere

	@Override
	public void start(Stage primaryStage) throws Exception {
		Point3D otherSphere = new Point3D(-radius, 0, -radius * 1.5);
		final TriangleMesh triangleMesh = createMesh(DIVISION, radius, otherSphere);
		MeshView a = new MeshView(triangleMesh);

		a.setTranslateY(radius);
		a.setTranslateX(radius);
		a.setRotationAxis(Rotate.Y_AXIS);
		Scene scene = new Scene(new Group(a));
//		uncomment if you want to move the other sphere
		
//		scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
//			Point3D sphere = otherSphere;
//
//			@Override
//			public void handle(KeyEvent e) {
//				KeyCode code = e.getCode();
//				switch (code) {
//				case UP:
//					sphere = sphere.add(0, -10, 0);
//					break;
//				case DOWN:
//					sphere = sphere.add(0, 10, 0);
//					break;
//				case LEFT:
//					sphere = sphere.add(-10, 0, 0);
//					break;
//				case RIGHT:
//					sphere = sphere.add(10, 0, 0);
//					break;
//				case W:
//					sphere = sphere.add(0, 0, 10);
//					break;
//				case S:
//					sphere = sphere.add(0, 0, -10);
//					break;
//				default:
//					return;
//				}
//				a.setMesh(createMesh(DIVISION, radius, sphere));
//
//			}
//		});

		primaryStage.setScene(scene);
		primaryStage.show();
	}

	static TriangleMesh createMesh(final int division, final float radius, final Point3D centerOtherSphere) {
		Rotate rotate = new Rotate(180, centerOtherSphere);
		final int div2 = division / 2;

		final int nPoints = division * (div2 - 1) + 2;
		final int nTPoints = (division + 1) * (div2 - 1) + division * 2;
		final int nFaces = division * (div2 - 2) * 2 + division * 2;

		final float rDiv = 1.f / division;

		float points[] = new float[nPoints * 3];
		float tPoints[] = new float[nTPoints * 2];
		int faces[] = new int[nFaces * 6];

		int pPos = 0, tPos = 0;

		for (int y = 0; y < div2 - 1; ++y) {
			float va = rDiv * (y + 1 - div2 / 2) * 2 * (float) Math.PI;
			float sin_va = (float) Math.sin(va);
			float cos_va = (float) Math.cos(va);

			float ty = 0.5f + sin_va * 0.5f;
			for (int i = 0; i < division; ++i) {
				double a = rDiv * i * 2 * (float) Math.PI;
				float hSin = (float) Math.sin(a);
				float hCos = (float) Math.cos(a);
				points[pPos + 0] = hSin * cos_va * radius;
				points[pPos + 2] = hCos * cos_va * radius;
				points[pPos + 1] = sin_va * radius;

				final Point3D point3D = new Point3D(points[pPos + 0], points[pPos + 1], points[pPos + 2]);
				double distance = centerOtherSphere.distance(point3D);
				if (distance <= radius) {
					Point3D subtract = centerOtherSphere.subtract(point3D);
					Point3D transform = rotate.transform(subtract);
					points[pPos + 0] = (float) transform.getX();
					points[pPos + 1] = (float) transform.getY();
					points[pPos + 2] = (float) transform.getZ();
					
				}
				tPoints[tPos + 0] = 1 - rDiv * i;
				tPoints[tPos + 1] = ty;
				pPos += 3;
				tPos += 2;
			}
			tPoints[tPos + 0] = 0;
			tPoints[tPos + 1] = ty;
			tPos += 2;
		}

		points[pPos + 0] = 0;
		points[pPos + 1] = -radius;
		points[pPos + 2] = 0;
		points[pPos + 3] = 0;
		points[pPos + 4] = radius;
		points[pPos + 5] = 0;
		pPos += 6;

		int pS = (div2 - 1) * division;

		float textureDelta = 1.f / 256;
		for (int i = 0; i < division; ++i) {
			tPoints[tPos + 0] = rDiv * (0.5f + i);
			tPoints[tPos + 1] = textureDelta;
			tPos += 2;
		}

		for (int i = 0; i < division; ++i) {
			tPoints[tPos + 0] = rDiv * (0.5f + i);
			tPoints[tPos + 1] = 1 - textureDelta;
			tPos += 2;
		}

		int fIndex = 0;
		for (int y = 0; y < div2 - 2; ++y) {
			for (int x = 0; x < division; ++x) {
				int p0 = y * division + x;
				int p1 = p0 + 1;
				int p2 = p0 + division;
				int p3 = p1 + division;

				int t0 = p0 + y;
				int t1 = t0 + 1;
				int t2 = t0 + division + 1;
				int t3 = t1 + division + 1;

				// add p0, p1, p2
				faces[fIndex + 0] = p0;
				faces[fIndex + 1] = t0;
				faces[fIndex + 2] = p1 % division == 0 ? p1 - division : p1;
				faces[fIndex + 3] = t1;
				faces[fIndex + 4] = p2;
				faces[fIndex + 5] = t2;
				fIndex += 6;

				// add p3, p2, p1
				faces[fIndex + 0] = p3 % division == 0 ? p3 - division : p3;
				faces[fIndex + 1] = t3;
				faces[fIndex + 2] = p2;
				faces[fIndex + 3] = t2;
				faces[fIndex + 4] = p1 % division == 0 ? p1 - division : p1;
				faces[fIndex + 5] = t1;
				fIndex += 6;
			}
		}

		int p0 = pS;
		int tB = (div2 - 1) * (division + 1);
		for (int x = 0; x < division; ++x) {
			int p2 = x, p1 = x + 1, t0 = tB + x;
			faces[fIndex + 0] = p0;
			faces[fIndex + 1] = t0;
			faces[fIndex + 2] = p1 == division ? 0 : p1;
			faces[fIndex + 3] = p1;
			faces[fIndex + 4] = p2;
			faces[fIndex + 5] = p2;
			fIndex += 6;
		}

		p0 = p0 + 1;
		tB = tB + division;
		int pB = (div2 - 2) * division;

		for (int x = 0; x < division; ++x) {
			int p1 = pB + x, p2 = pB + x + 1, t0 = tB + x;
			int t1 = (div2 - 2) * (division + 1) + x, t2 = t1 + 1;
			faces[fIndex + 0] = p0;
			faces[fIndex + 1] = t0;
			faces[fIndex + 2] = p1;
			faces[fIndex + 3] = t1;
			faces[fIndex + 4] = p2 % division == 0 ? p2 - division : p2;
			faces[fIndex + 5] = t2;
			fIndex += 6;
		}

		TriangleMesh m = new TriangleMesh();
		m.getPoints().setAll(points);
		m.getTexCoords().setAll(tPoints);
		m.getFaces().setAll(faces);

		return m;
	}

	public static void main(String[] args) {

		launch(args);
	}

}

Using Java 11

Alternatively, without using JavaFX, which has been removed from the JavaJDK since version 11.

import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.List;

import javax.imageio.ImageIO;

public final class DeathStar {

	public static void main(String[] aArgs) throws IOException {
		Vector direction = new Vector(20.0, -40.0, -10.0);
		direction.normalise();
		Sphere positive = new Sphere(0, 0, 0, 120);
		Sphere negative = new Sphere(-90, -90, -30, 100);

		BufferedImage image = deathStar(positive, negative, direction, 1.5, 0.5);
		
		ImageIO.write(image, "png", new File("DeathStarJava.png"));
	}
	
	private static BufferedImage deathStar(
			Sphere aPositive, Sphere aNegative, Vector aDirection, double aShadow, double aBrightness) {
		final int width = aPositive.radius * 4;
		final int height = aPositive.radius * 3;
		BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		Graphics graphics = result.getGraphics();
        graphics.setColor(Color.CYAN);
        graphics.fillRect(0, 0, width, height);
        
		Vector ray = new Vector(0.0, 0.0, 0.0);
		final int deltaX = aPositive.x - width / 2;
		final int deltaY = aPositive.y - height / 2;

		double xMax = aPositive.x + aPositive.radius;
		double yMax = aPositive.y + aPositive.radius;
		for ( int y = aPositive.y - aPositive.radius; y < yMax; y++ ) {
		    for ( int x = aPositive.x - aPositive.radius; x < xMax; x++ ) {
		    	List<Object> contacts = aPositive.contact(x, y);
		    	final double zb1 = (double) contacts.get(0);
		    	final int zb2 = (int) contacts.get(1);
		    	final boolean positiveHit = (boolean) contacts.get(2);
		    	if ( ! positiveHit ) {
		    		continue;
		    	}
	       		contacts = aNegative.contact(x, y);
	       		final double zs1 = (double) contacts.get(0);
	       		final int zs2 = (int) contacts.get(1);
	       		boolean negativeHit = (boolean) contacts.get(2);
	       		if ( negativeHit ) {
	       			if ( zs1 > zb1 ) {
	       				negativeHit = false;
	       			} else if ( zs2 > zb2 ) {
	       				continue;
	       			}
	       		}
	       		
		       	if ( negativeHit ) {
		       		ray.x = aNegative.x - x;
		       		ray.y = aNegative.y - y;
		       		ray.z = aNegative.z - zs2;
		       	} else {
		       		ray.x = x - aPositive.x;
		       		ray.y = y - aPositive.y;
		       		ray.z = zb1 - aPositive.z;
		       	}
		       	ray.normalise();
		       	double rayComponent = ray.scalarProduct(aDirection);
		       	if ( rayComponent < 0 ) { 
		       		rayComponent = 0;
		       	}
		       	int color = (int) ( 255 * ( Math.pow(rayComponent, aShadow) + aBrightness) / ( 1 + aBrightness ) );
		       	if ( color < 0 ) {
		       		color = 0;
		       	} else if ( color > 255 ) {
		       		color = 255;
		       	}
		       	result.setRGB(x - deltaX, y - deltaY, color);
		    }
		}
		return result;
	}	
	
	private static class Vector {
		
		public Vector(double aX, double aY, double aZ) {
			x = aX; y = aY; z = aZ;
		}
		
		public double scalarProduct(Vector aOther) {
			return x * aOther.x + y * aOther.y + z * aOther.z;
		}
		
		public Vector normalise() {
			final double magnitude = Math.sqrt(this.scalarProduct(this));
			return new Vector(x /= magnitude, y /= magnitude, z /= magnitude);
		}
		
		private double x, y, z;
		
	}
	
	private static class Sphere {
		
		public Sphere(int aX, int aY, int aZ, int aRadius) {
			x = aX; y = aY; z = aZ; radius = aRadius;
		}
		
		public List<Object> contact(int aX, int aY) {
			final int xx = aX - x;
			final int yy = aY - y;
			final int zSquared = radius * radius - ( xx * xx + yy * yy );
			if ( zSquared >= 0 ) {
				final double zz = Math.sqrt(zSquared);
				return List.of(z - zz, z, true);
			}
			return List.of( 0.0, 0, false );
		}	
		
		private int x, y, z, radius;
		
	}

}
Output:

Media:DeathStarJava.png

JavaScript

Layer circles and gradients to achieve result similar to that of the Wikipedia page for the Death Star.

<!DOCTYPE html>
<html>
<body style="margin:0">
  <canvas id="myCanvas" width="250" height="250" style="border:1px solid #d3d3d3;">
    Your browser does not support the HTML5 canvas tag.
  </canvas>
  <script>
    var c = document.getElementById("myCanvas");
    var ctx = c.getContext("2d");
    //Fill the canvas with a dark gray background
    ctx.fillStyle = "#222222";
    ctx.fillRect(0,0,250,250);

    // Create radial gradient for large base circle
    var grd = ctx.createRadialGradient(225,175,190,225,150,130);
    grd.addColorStop(0,"#EEEEEE");
    grd.addColorStop(1,"black");
    //Apply gradient and fill circle
    ctx.fillStyle = grd;
    ctx.beginPath();
    ctx.arc(125,125,105,0,2*Math.PI);
    ctx.fill();
    
    // Create linear gradient for small inner circle
    var grd = ctx.createLinearGradient(75,90,102,90);
    grd.addColorStop(0,"black");
    grd.addColorStop(1,"gray");
    //Apply gradient and fill circle
    ctx.fillStyle = grd;
    ctx.beginPath();
    ctx.arc(90,90,30,0,2*Math.PI);
    ctx.fill();
    
    //Add another small circle on top of the previous one to enhance the "shadow"
    ctx.fillStyle = "black";
    ctx.beginPath();
    ctx.arc(80,90,17,0,2*Math.PI);
    ctx.fill();
  </script> 
</body>
</html>

Julia

# run in REPL
using GLMakie

function deathstar()
    n = 60
    θ = [0; (0.5: n - 0.5) / n; 1]
    φ = [(0: 2n - 2) * 2 / (2n - 1); 2]
    # if x is +0.9 radius units, replace it with the coordinates of sphere surface
    # at (1.2,0,0) center, radius 0.5 units
    x = [(x1 = cospi(φ)*sinpi(θ)) > 0.9 ? 1.2 - x1 * 0.5 : x1 for θ in θ, φ in φ]
    y = [sinpi(φ)*sinpi(θ) for θ in θ, φ in φ]
    z = [cospi(θ) for θ in θ, φ in φ]
    scene = Scene(backgroundcolor=:black)
    surface!(scene, x, y, z, color = rand(RGBAf0, 124, 124), show_axis=false)
    return scene
end

scene = deathstar()

LSL

Rez a box on the ground, raise it up a few meters, add the following as a New Script.

default {
    state_entry() {
        llSetPrimitiveParams([PRIM_NAME, "RosettaCode DeathStar"]);
        llSetPrimitiveParams([PRIM_DESC, llGetObjectName()]);
        llSetPrimitiveParams([PRIM_TYPE, PRIM_TYPE_SPHERE, PRIM_HOLE_CIRCLE, <0.0, 1.0, 0.0>, 0.0, <0.0, 0.0, 0.0>, <0.12, 1.0, 0.0>]);
        llSetPrimitiveParams([PRIM_ROTATION, <-0.586217, 0.395411, -0.586217, 0.395411>]);
        llSetPrimitiveParams([PRIM_TEXTURE, ALL_SIDES, TEXTURE_BLANK, ZERO_VECTOR, ZERO_VECTOR, 0.0]);
        llSetPrimitiveParams([PRIM_TEXT, llGetObjectName(), <1.0, 1.0, 1.0>, 1.0]);
        llSetPrimitiveParams([PRIM_COLOR, ALL_SIDES, <0.5, 0.5, 0.5>, 1.0]);
        llSetPrimitiveParams([PRIM_BUMP_SHINY, ALL_SIDES, PRIM_SHINY_HIGH, PRIM_BUMP_NONE]);
        llSetPrimitiveParams([PRIM_SIZE, <10.0, 10.0, 10.0>]);
        llSetPrimitiveParams([PRIM_OMEGA, <0.0, 0.0, 1.0>, 1.0, 1.0]);
    }
}

Output: Death Star

Lua

Translation of: C
function V3(x,y,z) return {x=x,y=y,z=z} end
function dot(v,w) return v.x*w.x + v.y*w.y + v.z*w.z end
function norm(v) local m=math.sqrt(dot(v,v)) return V3(v.x/m, v.y/m, v.z/m) end
function clamp(n,lo,hi) return math.floor(math.min(math.max(lo,n),hi)) end
function hittest(s, x, y)
  local z = s.r^2 - (x-s.x)^2 - (y-s.y)^2
  if z >= 0 then
    z = math.sqrt(z)
    return true, s.z-z, s.z+z
  end
  return false, 0, 0
end

function deathstar(pos, neg, sun, k, amb)
  shades = {[0]=" ",".",":","!","*","o","e","&","#","%","@"}
  for y = pos.x-pos.r-0.5, pos.x+pos.r+0.5 do
    for x = pos.x-pos.r-0.5, pos.x+pos.r+0.5, 0.5 do
      local hitpos, pz1, pz2 = hittest(pos, x, y)
      local result, hitneg, nz1, nz2 = 0
      if hitpos then
        hitneg, nz1, nz2 = hittest(neg, x, y)
        if not hitneg or nz1 > pz1 then result = 1
        elseif nz2 > pz2 then result = 0
        elseif nz2 > pz1 then result = 2
        else result = 1
        end
      end
      local shade = 0
      if result > 0 then
        if result == 1 then
          shade = clamp((1-dot(sun, norm(V3(x-pos.x, y-pos.y, pz1-pos.z)))^k+amb) * #shades, 1, #shades)
        else
          shade = clamp((1-dot(sun, norm(V3(neg.x-x, neg.y-y, neg.z-nz2)))^k+amb) * #shades, 1, #shades)
        end
      end
      io.write(shades[shade])
    end
    io.write("\n")
  end
end

deathstar({x=20, y=20, z=0, r=20}, {x=10, y=10, z=-15, r=10}, norm(V3(-2,1,3)), 2, 0.1)
Output:
                                         @@@%%%%%%%%%#########%
                                 @@@@@%%%%%%#######&&&&&&&&&&&&&&&&&&##
                            @@@@@@%%%%%%######&&&&&&&eeeeeeeeeeeeeeeeeeeee&&
                       @@@@@@@@@@@@@@@@@@@&&&&&&eeeeeeeoooooooooooooooooooooeee&
                    @@@@@&####%%%%@@@@@@@@@@@@%eeeeoooooooo*******************oooee
                 @@@@eeee&&&&####%%%@@@@@@@@@@@@%oooooo********!!!!!!!!!!!!!!*****oooe
               @@@**ooooeeee&&&####%%%@@@@@@@@@@@%oo*******!!!!!!!!!!!!!!!!!!!!!!!****oo&
             @@@!!!****ooooeee&&&###%%%%@@@@@@@@@@%*****!!!!!!!!:::::::::::::::::!!!!!**ooe
           @@@:::!!!!!****oooeee&&###%%%%@@@@@@@@@%***!!!!!!!::::::::::::::::::::::::!!!***oe
         @@@@::::::::!!!***oooeee&&&##%%%%@@@@@@@@@**!!!!!!!:::::::::............::::::!!!**oo
        @@@.......::::!!!!***ooeee&&###%%%@@@@@@@@@*!!!!!!::::::::..................:::::!!!**oe
      %@@@@.........::::!!!**oooee&&&##%%%@@@@@@@@*!!!!!!::::::::......................::::!!!*oe
     %@@@@...........:::!!!***ooeee&&###%%%@@@@@@**!!!!!!:::::::........................::::!!!*oo
    %@@@@@...........:::!!!***ooeee&&###%%@@@@@@***!!!!!!:::::::.........................::::!!!*oe
    @@@@@@..........::::!!!**oooee&&&##%%%@@@@@****!!!!!!:::::::..........................::::!!**oe
   %@@@@@@::::...:::::!!!!***ooeee&&###%%@@@@o*****!!!!!!:::::::..........................::::!!!**o
   @@@@@@@@!!!:::::!!!!!***oooee&&&##%%%@@oooo******!!!!!!:::::::.........................:::::!!**oe
  %@@@@@@@@@o****!******ooooeee&&###%%%eeeooooo*****!!!!!!!:::::::........................::::!!!**oe
  %@@@@@@@@@@@eeoooooooeeeee&&&##%%%&eeeeeeooooo*****!!!!!!!::::::::.....................:::::!!!**oo&
  %@@@@@@@@@@@@@@@@##&&&#####%###&&&&&eeeeeoooooo******!!!!!!:::::::::..................:::::!!!!**oe&
  %@@@@@@@@@@@@@@@@@@@%%%%%%######&&&&&eeeeeoooooo******!!!!!!!:::::::::::...........:::::::!!!!***oe&
  %%@@@@@@@@@@@@@@@@@@@%%%%%%######&&&&&eeeeeeooooo*******!!!!!!!::::::::::::::::::::::::::!!!!***ooe&
  #%@@@@@@@@@@@@@@@@@@@%%%%%%%######&&&&&eeeeeeoooooo*******!!!!!!!!::::::::::::::::::::!!!!!!***ooee
   %%@@@@@@@@@@@@@@@@@@@%%%%%%%######&&&&&&eeeeeeoooooo*******!!!!!!!!!!!!::::::::::!!!!!!!!****ooee&
   #%@@@@@@@@@@@@@@@@@@@@%%%%%%%######&&&&&&eeeeeeooooooo*********!!!!!!!!!!!!!!!!!!!!!!!!****oooee&
    %%@@@@@@@@@@@@@@@@@@@@@%%%%%%%######&&&&&&eeeeeeeooooooo**********!!!!!!!!!!!!!!!*******ooooee&#
    &%%@@@@@@@@@@@@@@@@@@@@@%%%%%%%#######&&&&&&eeeeeeeooooooooo*************************oooooeee&#
     #%%@@@@@@@@@@@@@@@@@@@@@@%%%%%%%#######&&&&&&&eeeeeeeooooooooooo****************ooooooeeee&&#
      &%%@@@@@@@@@@@@@@@@@@@@@@%%%%%%%%#######&&&&&&&&eeeeeeeeeoooooooooooooooooooooooooeeeee&&##
        #%%@@@@@@@@@@@@@@@@@@@@@@%%%%%%%%########&&&&&&&&eeeeeeeeeeeeoooooooooooooeeeeeeee&&&##%
         #%%@@@@@@@@@@@@@@@@@@@@@@@%%%%%%%%%########&&&&&&&&&&eeeeeeeeeeeeeeeeeeeeeeee&&&&&##%
           #%%@@@@@@@@@@@@@@@@@@@@@@@@%%%%%%%%%#########&&&&&&&&&&&&&&&&eeeee&&&&&&&&&&####%%
             #%%@@@@@@@@@@@@@@@@@@@@@@@@@%%%%%%%%%%###########&&&&&&&&&&&&&&&&&&&&######%%%
               #%%@@@@@@@@@@@@@@@@@@@@@@@@@@%%%%%%%%%%%#############################%%%%@
                 #%%@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%%%%%%%%%%%%%###############%%%%%%%@
                    #%%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%%%%%%%%%%%%%%%%%%%%%%%%%@@@
                       #%%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%%%%%%@@@@@@@@@
                            %%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
                                 %@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
                                         @@@@@@@@@@@@@@@@@@@@@@

Maple

with(plots):
with(plottools):
plots:-display(
   implicitplot3d(x^2 + y^2 + z^2 = 1, x = -1..0.85, y = -1..1, z = -1..1, style = surface, grid = [50,50,50]),
   translate(rotate(implicitplot3d(x^2 + y^2 + z^2 = 1, x = 0.85..1, y = -1..1, z = -1..1, style = surface, grid = [50,50,50]), 0, Pi, 0), 1.70, 0, 0),
axes = none, scaling = constrained, color = gray)

Mathematica / Wolfram Language

RegionPlot3D[x^2 + y^2 + z^2 < 1 && (x + 1.7)^2 + y^2 + z^2 > 1, 
{x, -1, 1}, {y, -1, 1}, {z, -1, 1}, 
Boxed -> False, Mesh -> False, Axes -> False, Background -> Black, PlotPoints -> 100]

Nim

Translation of: Go
Library: nimPNG

The result is written in a PNG file. For this purpose, we used the modules “bitmap” and “grayscale_image” created for the tasks “Bitmap” and “Grayscale image”. To write the PNG file, we use the third party library “nimPNG”.

import math

import bitmap, grayscale_image, nimPNG

type

  Vector = array[3, float]

  Sphere = object
    cx, cy, cz: int
    r: int

#---------------------------------------------------------------------------------------------------

func dot(x, y: Vector): float {.inline.} =
  x[0] * y[0] + x[1] * y[1] + x[2] * y[2]

#---------------------------------------------------------------------------------------------------

func normalize(v: var Vector) =
  let invLen = 1 / sqrt(dot(v, v))
  v[0] *= invLen
  v[1] *= invLen
  v[2] *= invLen

#---------------------------------------------------------------------------------------------------

func hit(s: Sphere; x, y: int): tuple[z1, z2: float; hit: bool] =
  let x = x - s.cx
  let y = y - s.cy
  let zsq = s.r * s.r - (x * x + y * y)
  if zsq >= 0:
    let zsqrt = sqrt(zsq.toFloat)
    result = (s.cz.toFloat - zsqrt, s.cz.toFloat, true)
  else:
    result = (0.0, 0.0, false)

#---------------------------------------------------------------------------------------------------

func deathStar(pos, neg: Sphere; k, amb: float; dir: Vector): GrayImage =

  let w = pos.r * 4
  let h = pos.r * 3
  result = newGrayImage(w, h)
  var vect: Vector
  let deltaX = pos.cx - w div 2
  let deltaY = pos.cy - h div 2

  let xMax = pos.cx + pos.r
  let yMax = pos.cy + pos.r
  for y in (pos.cy - pos.r)..yMax:
    for x in (pos.cx - pos.r)..xMax:
      let (zb1, zb2, posHit) = pos.hit(x, y)
      if not posHit: continue
      var (zs1, zs2, negHit) = neg.hit(x, y)
      if negHit:
        if zs1 > zb1: negHit = false
        elif zs2 > zb2: continue
      if negHit:
        vect[0] = (neg.cx - x).toFloat
        vect[1] = (neg.cy - y).toFloat
        vect[2] = neg.cz.toFloat - zs2
      else:
        vect[0] = (x - pos.cx).toFloat
        vect[1] = (y - pos.cy).toFloat
        vect[2] = zb1 - pos.cz.toFloat
      vect.normalize()
      var s = dot(dir, vect)
      if s < 0: s = 0
      var lum = (255 * (s.pow(k) + amb) / (1 + amb)).toInt
      if lum < 0: lum = 0
      elif lum > 255: lum = 255
      result[x - deltaX, y - deltaY] = Luminance(lum)

#———————————————————————————————————————————————————————————————————————————————————————————————————

var dir: Vector = [float 20, -40, -10]
dir.normalize()
let pos = Sphere(cx: 0, cy: 0, cz: 0, r: 120)
let neg = Sphere(cx: -90, cy: -90, cz: -30, r: 100)

let grayImage = deathStar(pos, neg, 1.5, 0.2, dir)

# Save to PNG. We convert to an RGB image then transform the pixels
# in a sequence of bytes (actually a copy) in order to call "savePNG24".
let rgbImage = grayImage.toImage
var data = newSeqOfCap[byte](rgbImage.pixels.len * 3)
for color in rgbImage.pixels:
  data.add([color.r, color.g, color.b])
echo savePNG24("death_star.png", data, rgbImage.w, rgbImage.h)

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);
  }
}

Perl

Writes a PGM to stdout.

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);

Phix

Translation of: Go
Library: Phix/pGUI
Library: Phix/online

You can run this online here. Note it is rather slow to redraw fullscreen.

--
-- demo\rosetta\DeathStar.exw
-- ==========================
--
--  Translated from Go.
--
with javascript_semantics
include pGUI.e

constant title = "Death Star"
Ihandle dlg, canvas
cdCanvas cddbuffer, cdcanvas

function dot(sequence x, sequence y)
    return sum(sq_mul(x,y))
end function

function normalize(sequence v)
    atom len = sqrt(dot(v, v))
    if len=0 then return {0,0,0} end if
    return sq_mul(v,1/len)
end function

enum X,Y,Z

function hit(sequence s, atom x, y, r)
    x -= s[X]
    y -= s[Y]
    atom zsq := r*r - (x*x + y*y)
    if zsq >= 0 then
        atom zsqrt := sqrt(zsq)
        return {s[Z] - zsqrt, s[Z] + zsqrt, true}
    end if
    return {0, 0, false}
end function
 
procedure deathStar(integer width, height, atom k, atom amb, sequence direction)

    atom t0 = time()+1, t1 = t0,
         lmul = 255/(1+amb)
    integer r = floor((min(width,height)-40)/2),
           cx = floor(width/2),
           cy = floor(height/2)
    sequence pos = {0,0,0},
             neg = {r*-3/4,r*-3/4,r*-1/4}

    for y = -r to +r do
        if time()>t1 then
            -- Let the user know we aren't completely dead just yet
            IupSetStrAttribute(dlg,"TITLE","%s - drawing (%d%%)",{title,100*(y+r)/(2*r)})
            t1 = time()+1
            --
            -- Hmm, not entirely sure why this is needed, but without it 
            --  after ~7 seconds the window gets a "(Not Responding)" and
            --  then something decides to force a full repaint, which at 
            --  fullscreen will never finish in < 7s on this ancient box.
            -- I suppose this is the corrollary to the above, this time
            --  letting Windows 10 know the process is not quite dead...
            --  Currently and possibly forever neither of these routines
            --  exist in pGUI.js, the browser is more forgiving anyway.
            --
            if platform()!=JS then
                if IupLoopStep()=IUP_CLOSE then
                    IupExitLoop()
                    exit
                end if
            end if
        end if
        for x = -r to +r do
            atom {zb1, zb2, hit1} := hit(pos, x, y, r)
            if hit1 then
                atom {zs1, zs2, hit2} := hit(neg, x, y, r/2)
                if not hit2 or zs2<=zb2 then
                    bool dish = hit2 and zs1<=zb1
                    sequence vec = iff(dish?sq_sub(neg,{x,y,zs2}):{x,y,zb1})
                    atom s = dot(direction, normalize(vec)),
                         l = iff(s<=0?0:power(s,k))
                    integer lum = and_bits(#FF,lmul*(l+amb))
                    cdCanvasPixel(cddbuffer, cx+x, cy-y, lum*#10101)
                end if
            end if
        end for
    end for
    if t1!=t0 then
        IupSetStrAttribute(dlg,"TITLE",title)
    end if
end procedure

function redraw_cb(Ihandle /*ih*/, integer /*posx*/, /*posy*/)
    integer {width, height} = IupGetIntInt(canvas, "DRAWSIZE")
    cdCanvasActivate(cddbuffer)
    cdCanvasClear(cddbuffer) 
    deathStar(width, height, 1.5, 0.2, normalize({20, -40, -10}))
    cdCanvasFlush(cddbuffer)
    return IUP_DEFAULT
end function

function map_cb(Ihandle ih)
    cdcanvas = cdCreateCanvas(CD_IUP, ih)
    cddbuffer = cdCreateCanvas(CD_DBUFFER, cdcanvas)
    cdCanvasSetBackground(cddbuffer, CD_BLACK)
    return IUP_DEFAULT
end function

procedure main()
    IupOpen()
    canvas = IupCanvas("RASTERSIZE=340x340")
    IupSetCallbacks(canvas, {"MAP_CB", Icallback("map_cb"),
                             "ACTION", Icallback("redraw_cb")})
    dlg = IupDialog(canvas,`TITLE="%s"`,{title})

    IupMap(dlg)
    IupSetAttribute(canvas, "RASTERSIZE", NULL) -- release the minimum limitation
    IupShow(dlg)
    if platform()!=JS then
        IupMainLoop()
        IupClose()
    end if
end procedure

main()

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

Python

Translation of: C
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)

Q

write an image in BMP format:

/ https://en.wikipedia.org/wiki/BMP_file_format
/ BITMAPINFOHEADER / RGB24

/ generate a header

genheader:{[w;h]
   0x424d, "x"$(f2i4[54+4*h*w],0,0,0,0,54,0,0,0,40,0,0,0,
                f2i4[h],f2i4[w],1,0,24,0,0,0,0,0,
                f2i4[h*((w*3)+((w*3)mod 4))],
                19,11,0,0,19,11,0,0,0,0,0,0,0,0,0,0)};

/ generate a raster line at a vertical position

genrow:{[w;y;fcn]
    row:enlist 0i;xx:0i;do[w;row,:fcn[xx;y];xx+:1i];row,:((w mod 4)#0i);1_row};

/ generate a bitmap

genbitmap:{[w;h;fcn]
    ary:enlist 0i;yy:0i;do[h;ary,:genrow[w;yy;fcn];yy+:1i];"x"$1_ary};

/ deal with endianness
/ might need to reverse last line if host computer is not a PC

f2i4:{[x] r:x;
  s0:r mod 256;r-:s0; r%:256;
  s1:r mod 256;r-:s1; r%:256;
  s2:r mod 256;r-:s2; r%:256;
  s3:r mod 256;
  "h"$(s0,s1,s2,s3)}

/ compose and write a file

writebmp:{[w;h;fcn;fn] 
    fn 1: (genheader[h;w],genbitmap[w;h;fcn])};

/ / usage example:
/ w:400;
/ h:300;
/ fcn:{x0:x-w%2;y0:y-h%2;r:175;$[(r*r)>((x0*x0)+(y0*y0));(0;0;255);(0;255;0)]};
/ fn:`:demo.bmp;
/ writebmp[w;h;fcn;fn];

Create the death star image:

w:400; h:300; r:150; l:-0.5 0.7 0.5
sqrt0:{$[x>0;sqrt x;0]};

/ get x,y,z position of point on sphere given x,y,r

z:{[x;y;r]sqrt0((r*r)-((x*x)+(y*y)))};

/ get diffused light at point on sphere

is:{[x;y;r]
   z0:z[x;y;r];
   s:(x;y;z0)%r;
   $[z0>0;i:0.5*1+(+/)(s*l);i:0];
   i};

/ get pixel value at given image position

fcn:{[xpx;ypx]
   x:xpx-w%2;
   y:ypx-h%2;
   z1:z[x;y;r];
   x2:x+190;
   z2:170-z[x2;y;r];
   $[(r*r)<((x*x)+(y*y));
      $[y>-50;
          i:3#0;
          i:200 100 50];
      $[z2>z1;
         i:3#is[x;y;r]*140;
         i:3#is[(-1*x2);(-1*y);r]*120]
   ];
   "i"$i};

/ do it ...

\l bmp.q
fn:`:demo.bmp;
writebmp[w;h;fcn;fn];
(converted to JPG ...)

Racket

#lang racket
(require plot)
(plot3d (polar3d (λ (φ θ) (real-part (- (sin θ) (sqrt (- (sqr 1/3) (sqr (cos θ)))))))
                 #:samples 100 #:line-style 'transparent #:color 9)
        #:altitude 60 #:angle 80
        #:height  500 #:width 400
        #:x-min  -1/2 #:x-max 1/2
        #:y-min  -1/2 #:y-max 1/2
        #:z-min     0 #:z-max 1)

Raku

(formerly Perl 6)

Translation of: C
Reimplemented to output a .pgm image.
Works with: Rakudo version 2018.10
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 $width = my $height = 255; # dimensions of generated .pgm; must be odd

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

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

# positive sphere at origin
my $pos = sphere.new(
    cx => 0,
    cy => 0,
    cz => 0,
    r  => $s.Int
);

# 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') {
    spurt $outfile, ("P5\n$width $height\n$depth\n"); # .pgm header
    my $out = open( $outfile, :a, :bin ) orelse .die;
    say 'Working...';
    $out.write( Blob.new( |draw_ds(3, .15) ) );
    say 'File written.';
    $out.close;
}

sub draw_ds ( $k, $ambient ) {
    my @pixels[$height];

    (($pos.cy - $pos.r) .. ($pos.cy + $pos.r)).race.map: -> $y {
        my @row[$width];
        (($pos.cx - $pos.r) .. ($pos.cx + $pos.r)).map: -> $x {
            # black if we don't hit positive sphere, ignore negative sphere
            if not hit($pos, $x, $y, my $posz) {
                @row[$x + $s] = 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 { @row[$x + $s] = 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;
            @row[$x + $s] = ($intensity * $depth).Int min $depth;
        }
         @pixels[$y + $s] = @row;
    }
    flat |@pixels.map: *.list;
}

# normalize a vector
sub normalize (@vec) { @vec »/» ([+] @vec »*« @vec).sqrt }

# dot product of two vectors
sub dot (@x, @y) { -([+] @x »*« @y) max 0 }

# 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 False if $z2 < 0;
    $z2 = $z2.sqrt;
    $z = $sphere.cz - $z2 .. $sphere.cz + $z2;
    True;
}

REXX

Translation of: D

(Apologies for the comments making the lines so wide, but it was easier to read and compare to the original   D   source.)

/*REXX program displays a sphere with another sphere subtracted where it's superimposed.*/
call deathStar   2,   .5,   v3('-50  30  50')
exit                                             /*stick a fork in it,  we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
dot:   #=0;  do j=1  for words(x);  #=# + word(x,j)*word(y,j);  end; return #
dot.:  procedure; parse arg x,y; d=dot(x,y); if d<0  then return -d; return 0
ceil:  procedure; parse arg x;   _=trunc(x);                         return _+(x>0)*(x\=_)
floor: procedure; parse arg x;   _=trunc(x);                         return _-(x<0)*(x\=_)
v3:    procedure; parse arg a b c;      #=sqrt(a**2 + b**2 + c**2);  return a/#  b/#  c/#
/*──────────────────────────────────────────────────────────────────────────────────────*/
sqrt:  procedure; parse arg x; if x=0  then return 0;  d=digits();  h= d+6; numeric digits
       m.=9; numeric form; parse value format(x,2,1,,0) 'E0' with g 'E' _ .; g=g*.5'e'_%2
         do j=0  while h>9;      m.j= h;              h= h % 2 + 1;    end /*j*/
         do k=j+5  to 0  by -1;  numeric digits m.k;  g= (g +x/g)* .5; end /*k*/; return g
/*──────────────────────────────────────────────────────────────────────────────────────*/
hitSphere: procedure expose !.; parse arg xx yy zz r,x0,y0;  z= r*r -(x0-xx)**2-(y0-yy)**2
           if z<0  then return 0;  _= sqrt(z);  !.z1= zz - _;    !.z2= zz + _;    return 1
/*──────────────────────────────────────────────────────────────────────────────────────*/
deathStar: procedure; parse arg k,ambient,sun    /* [↓]  display the deathstar to screen*/
parse var  sun   s1 s2 s3                        /*identify the light source coördinates*/
if 5=="f5"x  then shading= '.:!*oe&#%@'          /*dithering chars for an EBCDIC machine*/
             else shading= '·:!ºoe@░▒▓'          /*    "       "    "   "  ASCII    "   */
shadingL= length(shading)                        /*the number of dithering characters.  */
shades.= ' ';            do i=1  for shadingL;    shades.i= substr(shading, i, 1)
                         end   /*i*/
ship=  20   20  0 20  ;           parse var  ship    shipX  shipY  shipZ  shipR
hole= ' 1    1 -6 20' ;           parse var  hole    holeX  holeY  holeZ  .

  do   i=floor(shipY-shipR  )  to ceil(shipY+shipR  )+1;    y= i +.5;   @= /*@   is a single line of the deathstar to be displayed.*/
    do j=floor(shipX-shipR*2)  to ceil(shipX+shipR*2)+1;    !.= 0
    x=.5 * (j-shipX+1) + shipX;       $bg= 0;    $pos= 0;    $neg= 0       /*$BG,  $POS,  and  $NEG  are boolean values.           */
    ?= hitSphere(ship, x, y);                    b1= !.z1;   b2= !.z2      /*?  is boolean,  "true"  indicates ray hits the sphere.*/
                                                                           /*$BG:  if 1, its background;  if zero, it's foreground.*/
    if \? then $bg= 1                                                      /*ray lands in blank space, so draw the background.     */
          else do; ?= hitSphere(hole, x, y);     s1= !.z1;   s2= !.z2
               if \? then $pos= 1                                          /*ray hits ship but not the hole, so draw ship surface. */
                     else if s1>b1 then $pos=1                             /*ray hits both, but ship front surface is closer.      */
                                   else if s2>b2 then $bg= 1               /*ship surface is inside hole,  so show the background. */
                                                 else if s2>b1 then $neg=1 /*hole back surface is inside ship;  the only place ··· */
                                                               else $pos=1 /*························ a hole surface will be shown.*/
               end
        select
        when $bg   then do;   @= @' ';    iterate j;     end               /*append a blank character to the line to be displayed. */
        when $pos  then vec_= v3(x-shipX  y-shipY  b1-shipZ)
        when $neg  then vec_= v3(holeX-x  holeY-y  holeZ-s2)
        end    /*select*/

    b=1 +min(shadingL, max(0, trunc((1 - (dot.(sun, v3(vec_))**k + ambient)) * shadingL)))
    @=@ || shades.b                                 /*B:  the ray's intensity│brightness*/
    end      /*j*/                                  /* [↑]  build a line for the sphere.*/

  if @\=''  then say strip(@, 'T')                  /*strip trailing blanks from line.  */
  end        /*i*/                                  /* [↑]  show all lines for sphere.  */
return
output   when using the internal default input:

(Shown at   1/2   size.)

                                    eeeee:::::::
                                eeeeeeeee··············
                             ooeeeeeeeeee··················
                           ooooeeeeeeeee······················
                        oooooooeeeeeeee··························
                      ooooooooooeeeee······························
                    ººooooooooooeeee·································
                  ººººooooooooooee·····································
                !ºººººooooooooooe·······································
              !!!ºººººooooooooo:··········································
            :!!!!ºººººooooooo:::···········································
          :::!!!!ºººººooooo!:::::···········································
        ::::!!!!!ºººººooo!!!!::::············································
       ·::::!!!!ºººººooº!!!!!::::············································
     ···::::!!!!ººººººººº!!!!:::::············································
    ···::::!!!!ººººoººººº!!!!!::::············································
  ····::::!!!!ºººoooºººººº!!!!!::::············································
 ····::::!!!!ºoooooooººººº!!!!!:::::···········································
···::::!!!!!ooooooooooººººº!!!!!:::::··········································
:::::!!!!eeoooooooooooºººººº!!!!!:::::·········································
!!!!!eeeeeeeoooooooooooºººººº!!!!!:::::········································
eeeeeeeeeeeeooooooooooooºººººº!!!!!:::::·······································
eeeeeeeeeeeeeooooooooooooºººººº!!!!!!:::::·····································
eeeeeeeeeeeeeeooooooooooooºººººº!!!!!!:::::····································
 eeeeeeeeeeeeeeooooooooooooººººººº!!!!!!:::::·································
 eeeeeeeeeeeeeeeoooooooooooooºººººº!!!!!!::::::······························:
  eeeeeeeeeeeeeeeoooooooooooooººººººº!!!!!!:::::::··························:
  eeeeeeeeeeeeeeeeooooooooooooooººººººº!!!!!!!:::::::·····················::!
   eeeeeeeeeeeeeeeeeoooooooooooooºººººººº!!!!!!!:::::::::··············::::!
    eeeeeeeeeeeeeeeeeooooooooooooooºººººººº!!!!!!!!::::::::::::::::::::::!º
     eeeeeeeeeeeeeeeeeeoooooooooooooooºººººººº!!!!!!!!!!:::::::::::::!!!!º
       eeeeeeeeeeeeeeeeeooooooooooooooooºººººººººº!!!!!!!!!!!!!!!!!!!!!º
        eeeeeeeeeeeeeeeeeeoooooooooooooooooºººººººººººº!!!!!!!!!!!!ºººº
          eeeeeeeeeeeeeeeeeeooooooooooooooooooººººººººººººººººººººººo
            eeeeeeeeeeeeeeeeeeeoooooooooooooooooooooººººººººººººooo
              eeeeeeeeeeeeeeeeeeeeooooooooooooooooooooooooooooooo
                 eeeeeeeeeeeeeeeeeeeeooooooooooooooooooooooooo
                    eeeeeeeeeeeeeeeeeeeeeoooooooooooooooooo
                        eeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
                               eeeeeeeeeeeeeeeee

Set lang

set ! 32
set ! 32
set ! 46
set ! 45
set ! 126
set ! 34
set ! 34
set ! 126
set ! 45
set ! 46
set ! 10
set ! 46
set ! 39
set ! 40
set ! 95
set ! 41
set ! 32
set ! 32
set ! 32
set ! 32
set ! 32
set ! 39
set ! 46
set ! 10
set ! 124
set ! 61
set ! 61
set ! 61
set ! 61
set ! 61
set ! 61
set ! 61
set ! 61
set ! 61
set ! 61
set ! 124
set ! 10
set ! 39
set ! 46
set ! 32
set ! 32
set ! 32
set ! 32
set ! 32
set ! 32
set ! 32
set ! 32
set ! 46
set ! 39
set ! 10
set ! 32
set ! 32
set ! 126
set ! 45
set ! 46
set ! 95
set ! 95
set ! 46
set ! 45
set ! 126

Outputs:

  .-~""~-.
.'(_)     '.
|==========|
'.        .'
  ~-.__.-~

(it's the best I could do!)

Sidef

Translation of: Perl

Writes a PGM to stdout.

func hitf(sph, x, y) {
    x -= sph[0]
    y -= sph[1]

    var z = (sph[3]**2 - (x**2 + y**2))

    z < 0 && return nil

    z.sqrt!
    [sph[2] - z, sph[2] + z]
}

func normalize(v) {
    v / v.abs
}

func dot(x, y) {
    max(0, x*y)
}

var pos = [120, 120, 0, 120]
var neg = [-77, -33, -100, 190]
var light = normalize(Vector(-12, 13, -10))

func draw(k, amb) {
    STDOUT.binmode(':raw')
    print ("P5\n", pos[0]*2 + 3, " ", pos[1]*2 + 3, "\n255\n")

    for y in ((pos[1] - pos[3] - 1) .. (pos[1] + pos[3] + 1)) {
        var row = []
        for x in ((pos[0] - pos[3] - 1) .. (pos[0] + pos[3] + 1)) {

            var hit = 0
            var hs = []
            var h = hitf(pos, x, y)

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

            var (val, v)

            given(hit) {
                when (0) { val = 0}
                when (1) { v = Vector(x-pos[0], y-pos[1], h[0]-pos[2]) }
                default  { v = Vector(neg[0]-x, neg[1]-y, neg[2]-hs[1]) }
            }

            if (defined(v)) {
                v = normalize(v)
                val = int((dot(v, light)**k + amb) * 255)
                val = (val > 255 ? 255 : (val < 0 ? 0 : val))
            }
            row.append(val)
        }
        print 'C*'.pack(row...)
    }
}

draw(2, 0.2)

Output image: here.

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.

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

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

VBScript

ASCII graphics. Should be invoked with cscript. Modified from LUA

'deathstar ascii graphics

option explicit               

const x_=0
const y_=1
const z_=2
const r_=3

function clamp(x,b,t) 
  if x<b then 
     clamp=b 
  elseif x>t then
    clamp =t 
  else 
    clamp=x 
  end if 
end function

function dot(v,w) dot=v(x_)*w(x_)+v(y_)*w(y_)+v(z_)*w(z_): end function

function normal (byval v) 
    dim ilen:ilen=1/sqr(dot(v,v)): 
    v(x_)=v(x_)*ilen: v(y_)=v(y_)*ilen: v(z_)=v(z_)*ilen:
    normal=v:
end function

function hittest(s,x,y)
   dim z
   z = s(r_)^2 - (x-s(x_))^2 - (y-s(y_))^2
   if z>=0  then
     z=sqr(z)
     hittest=array(s(z_)-z,s(z_)+z)
   else
     hittest=0
  end if
end function
            
sub deathstar(pos, neg, sun, k, amb)
  dim x,y,shades,result,shade,hp,hn,xx,b 
  shades=array(" ",".",":","!","*","o","e","&","#","%","@")
  for y = pos(y_)-pos(r_)-0.5 to pos(y_)+pos(r_)+0.5 
    for x = pos(x_)-pos(r_)-0.5 to pos(x_)+pos(r_)+.5
      hp=hittest (pos, x, y)
      hn=hittest(neg,x,y)
      if not  isarray(hp) then
         result=0
      elseif not isarray(hn) then
        result=1
      elseif hn(0)>hp(0)  then
        result=1        
      elseif  hn(1)>hp(1) then
        result=0
      elseif hn(1)>hp(0) then
        result=2
      else
        result=1
      end if

      shade=-1
      select case result
      case 0
        shade=0        
      case 1
        xx=normal(array(x-pos(x_),y-pos(y_),hp(0)-pos(z_)))
        'shade=clamp(1-dot(sun,xx)^k+amb,1,ubound(shades))
      case 2
        xx=normal(array(neg(x_)-x,neg(y_)-y,neg(z_)-hn(1)))
        'shade=clamp(1-dot(sun,xx)^k+amb,1,ubound(shades))
      end select
      if shade <>0 then
        b=dot(sun,xx)^k+amb
        shade=clamp((1-b) *ubound(shades),1,ubound(shades))        
      end if       
      wscript.stdout.write string(2,shades(shade))
    next
    wscript.stdout.write vbcrlf
  next
end sub

deathstar array(20, 20, 0, 20),array(10,10,-15,10), normal(array(-2,1,3)), 2, 0.1
Output:
                                 ####&&&&&&&&&&&&
                         %%######&&&&eeeeeeeeooooooooee&&
                     %%%%####&&&&eeeeeeoooooo************ooee
                   %%%%####%%%%%%eeoooooo******!!!!!!!!!!!!**oo
               %%%%ee&&&&####%%%%%%##oo****!!!!!!!!::::::::!!!!**oo
             %%%%ooooeeee&&####%%%%%%##**!!!!!!::::::::::::::::!!!!oo
           %%%%!!****ooee&&&&##%%%%%%##!!!!::::::::............::::!!**
         %%%%::::!!****ooee&&####%%%%%%&&!!::::....................::!!oo
         %%....::::!!**ooooee&&##%%%%%%##::::........................::!!
       %%........::::!!**ooee&&####%%%%##::::..........................::!!
     ##%%..........::!!**ooee&&&&##%%%%##::..............................::oo
     %%%%..........::!!!!**ooee&&##%%%%::::..............................::!!
   ##%%..............::!!**ooee&&##%%%%::::................................::oo
   ##%%..............::!!**ooee&&##%%%%::::................................::**
   %%%%............::::!!**oo&&&&##%%!!::::................................::!!
   %%%%............::!!**ooee&&##%%!!!!::::..................................!!
 ##%%%%%%........::::!!**ooee&&##**!!!!::::..................................::**
 ##%%%%%%!!::::::!!!!**ooee&&##****!!!!::::..................................::**
 ##%%%%%%%%**********ooee&&##oo****!!!!!!::::................................::**
 ##%%%%%%%%%%##eeeeee&&eeeeeeoooo****!!!!::::..............................::!!**
 ##%%%%%%%%%%######&&&&&&eeeeoooo****!!!!::::::............................::!!**
 ##%%%%%%%%%%%%######&&&&eeeeoooo******!!!!::::::..........................::!!oo
 &&##%%%%%%%%%%######&&&&eeeeeeoooo****!!!!!!::::::......................::::!!oo
 &&##%%%%%%%%%%######&&&&&&eeeeoooooo****!!!!!!::::::..................::::!!**ee
   ##%%%%%%%%%%%%######&&&&eeeeeeoooo******!!!!!!::::::::..........::::::!!!!**
   ##%%%%%%%%%%%%######&&&&&&eeeeeeoooo******!!!!!!::::::::::::::::::::!!!!**oo
   &&##%%%%%%%%%%%%######&&&&&&eeeeoooooo******!!!!!!!!::::::::::::!!!!!!**ooee
   ee##%%%%%%%%%%%%########&&&&eeeeeeoooooo********!!!!!!!!!!!!!!!!!!!!****oo&&
     &&##%%%%%%%%%%%%######&&&&&&eeeeeeoooooo**********!!!!!!!!!!!!******ooee
     ee##%%%%%%%%%%%%%%######&&&&&&eeeeeeoooooooo********************ooooee&&
       &&##%%%%%%%%%%%%########&&&&&&&&eeeeeeoooooooooo********ooooooooee&&
         ####%%%%%%%%%%%%########&&&&&&&&eeeeeeeeooooooooooooooooooeeee&&
         ee##%%%%%%%%%%%%%%##########&&&&&&&&eeeeeeeeeeeeeeeeeeeeeeee&&##
           &&##%%%%%%%%%%%%%%%%########&&&&&&&&&&eeeeeeeeeeeeee&&&&&&##
             &&##%%%%%%%%%%%%%%%%##########&&&&&&&&&&&&&&&&&&&&&&####
               &&##%%%%%%%%%%%%%%%%%%##############&&&&&&&&######%%
                   ####%%%%%%%%%%%%%%%%%%######################
                     &&##%%%%%%%%%%%%%%%%%%%%%%%%######%%%%%%
                         &&##%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                                 ##%%%%%%%%%%%%%%

Wren

Translation of: Go
Library: DOME
import "dome" for Window
import "graphics" for Canvas, Color, ImageData
import "math" for Vector

var Normalize = Fn.new{ |vec|
    var invLen = 1 / vec.dot(vec).sqrt
    vec.x = vec.x * invLen
    vec.y = vec.y * invLen
    vec.z = vec.z * invLen
}

class Sphere {
    construct new(cx, cy, cz, r) {
        _cx = cx
        _cy = cy
        _cz = cz
        _r  = r
    }

    cx { _cx }
    cy { _cy }
    cz { _cz }
    r  { _r  }

    hit(x, y) {
        x = x - _cx
        y = y - _cy
        var zsq = _r*_r - x*x - y*y
        if (zsq >= 0) {
            var zsqrt = zsq.sqrt
            return [_cz - zsqrt, _cz + zsqrt, true]
        }
        return [0, 0, false]
    }
}

class DeathStar {
    construct new(width, height) {
        Window.title = "Death star"
        Window.resize(width, height)
        Canvas.resize(width, height)
    }

    init() {
        Canvas.cls(Color.white)
        var dir = Vector.new(20, -40, 10)
        Normalize.call(dir)
        var pos = Sphere.new(220, 190, 220, 120)
        var neg = Sphere.new(130, 100, 190, 100)
        deathStar(pos, neg, 1.5, 0.2, dir)
    }

    deathStar(pos, neg, k, amb, dir) {
        var w = pos.r * 4
        var h = pos.r * 3
        var img = ImageData.create("deathStar", w, h)
        var vec = Vector.new(0, 0, 0)
        for (y in pos.cy - pos.r..pos.cy + pos.r) {
            for (x in pos.cx - pos.r..pos.cx + pos.r) {
                var res = pos.hit(x, y)
                var zb1 = res[0]
                var zb2 = res[1]
                var hit = res[2]
                if (!hit) continue
                res = neg.hit(x, y)
                var zs1 = res[0]
                var zs2 = res[1]
                hit = res[2]
                if (hit) {
                    if (zs1 > zb1) {
                        hit = false
                    } else if (zs2 > zb2) {
                        continue
                    }
                }
                if (hit) {
                    vec.x = neg.cx - x
                    vec.y = neg.cy - y
                    vec.z = neg.cz - zs2
                } else {
                    vec.x = x - pos.cx
                    vec.y = y - pos.cy
                    vec.z = zb1 - pos.cz
                }
                Normalize.call(vec)
                var s = dir.dot(vec)
                if (s < 0) s = 0
                var lum = 255 * (s.pow(k) + amb) / (1 + amb)
                lum = lum.clamp(0, 255)
                img.pset(x, y, Color.rgb(lum, lum, lum))
            }
        }
        img.draw(pos.cx - w/2, pos.cy - h/2)
        img.saveToFile("deathStar.png")
    }

    update() {
    }

    draw(alpha) {
    }
}

var Game = DeathStar.new(400, 400)

Yabasic

open window 100,100
window origin "cc"
backcolor 0,0,0
clear window

tonos = 100
interv = int(255 / tonos)
dim shades(tonos)

shades(1) = 255
for i = 2 to tonos
	shades(i) = shades(i-1) - interv
next i

dim light(3)

light(0) = 30
light(1) = 30
light(2) = -50


sub normalize(v())
    local long
	
    long = sqrt(v(0)*v(0) + v(1)*v(1) + v(2)*v(2))
    v(0) = v(0) / long
    v(1) = v(1) / long
    v(2) = v(2) / long
end sub

 
sub punto(x(), y())
    local d
        
    d = x(0)*y(0) + x(1)*y(1) + x(2)*y(2)
    if d < 0 then
    	return -d
    else
    	return 0
    end if
end sub


//* positive shpere and negative sphere */
dim pos(3)
dim neg(3)

// x, y, z, r

pos(0) = 10
pos(1) = 10
pos(2) = 0
pos(3) = 20

neg(0) = 0
neg(1) = 0
neg(2) = -5
neg(3) = 15


sub hit_sphere(sph(), x, y)
	local zsq
	
	x = x - sph(0)
	y = y - sph(1)
	zsq = sph(3) * sph(3) - (x * x + y * y)
	if (zsq < 0) then
		return 0
	else
		return sqrt(zsq)
	end if
end sub

 
sub draw_sphere(k, ambient)
    local i, j, intensity, hit_result, result, b, vec(3), x, y, zb1, zb2, zs1, zs2, ini1, fin1, ini2, fin2
	
    ini1 = int(pos(1) - pos(3))
    fin1 = int(pos(1) + pos(3) + .5)
    for i = ini1 to fin1
        y = i + .5
        ini2 = int(pos(0) - 2 * pos(3))
        fin2 = int(pos(0) + 2 * pos(3) + .5)
        for j = ini2 to fin2
            x = (j - pos(0)) / 2 + .5 + pos(0)
            
            // ray lands in blank space, draw bg
            result = hit_sphere(pos(), x, y)
            
            if not result then
		hit_result = 0

		//* ray hits pos sphere but not neg, draw pos sphere surface */
	    else
		zb1 = pos(2) - result
		zb2 = pos(2) + result
		result = hit_sphere(neg(), x, y)
		if not result then
		    hit_result = 1
		else
		    zs1 = neg(2) - result
		    zs2 = neg(2) + result
		    if (zs1 > zb1) then
			hit_result = 1
		    elseif (zs2 > zb2) then
			hit_result = 0
		    elseif (zs2 > zb1) then
			hit_result = 2
		    else
			hit_result = 1
		    end if
		end if
	    end if
	  
  	    if not hit_result then
  	        color 0,0,0
  	        dot x, y
  	    else
	        switch(hit_result)
	        case 1:
		    vec(0) = x - pos(0)
		    vec(1) = y - pos(1)
		    vec(2) = zb1 - pos(2)
		    break
	        default:
		    vec(0) = neg(0) - x
		    vec(1) = neg(1) - y
		    vec(2) = neg(2) - zs2
	        end switch
				
                normalize(vec())
                b = (punto(light(), vec())^k) + ambient
                intensity = (1 - b) * tonos
                if (intensity < 1) intensity = 1
                if (intensity > tonos) intensity = tonos
                color shades(intensity),shades(intensity),shades(intensity)
                dot x,y
            end if
        next j
    next i
end sub
 
 
ang = 0
 
while(true)
	//clear window
	light(1) = cos(ang * 2)
	light(2) = cos(ang)
	light(0) = sin(ang)
	normalize(light())
	ang = ang + .05

	draw_sphere(2, .3)
wend

Zig

Translation of: C

Primitive ray tracing. Writes a PGM to stdout.

const std = @import("std");
const Allocator = std.mem.Allocator;
pub fn main() !void {
    // buffer stdout --------------------------------------
    const stdout_file = std.io.getStdOut().writer();
    var bw = std.io.bufferedWriter(stdout_file);
    const stdout = bw.writer();

    // allocator ------------------------------------------
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer {
        const ok = gpa.deinit();
        std.debug.assert(ok == .ok);
    }
    const allocator = gpa.allocator();

    // deathstar ------------------------------------------
    var dstar = try DeathStar(f32).init(allocator);
    defer dstar.deinit();

    // print deathstar PGM to stdout ----------------------
    const comments = [_][]const u8{
        "Rosetta Code",
        "DeathStar",
        "https://rosettacode.org/wiki/Death_Star",
    };
    try dstar.print(stdout, comments[0..]);

    // ----------------------------------------------------
    try bw.flush();
}
fn Vector(comptime T: type) type {
    return struct {
        const Self = @This();
        x: T,
        y: T,
        z: T,

        pub fn init(x: T, y: T, z: T) Self {
            return Self{ .x = x, .y = y, .z = z };
        }
        pub fn zero() Self {
            return Self{ .x = 0.0, .y = 0.0, .z = 0.0 };
        }
        fn dot(a: *const Self, b: *const Self) T {
            return a.x * b.x + a.y * b.y + a.z * b.z;
        }
        fn length(self: *const Self) T {
            return std.math.sqrt(self.dot(self));
        }
        pub fn normalize(self: *Self) void {
            const inv_length = 1 / self.length();
            self.*.x *= inv_length;
            self.*.y *= inv_length;
            self.*.z *= inv_length;
        }
    };
}
fn SphereHit(comptime T: type) type {
    return struct { z1: T, z2: T };
}
fn Sphere(comptime T: type) type {
    return struct {
        const Self = @This();
        cx: T,
        cy: T,
        cz: T,
        r: T,

        pub fn init(cx: T, cy: T, cz: T, r: T) Self {
            return Self{ .cx = cx, .cy = cy, .cz = cz, .r = r };
        }
        /// 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.
        pub fn hit(self: *const Self, xx: T, yy: T) ?SphereHit(T) {
            const x = xx - self.cx;
            const y = yy - self.cy;
            const zsq = self.r * self.r - x * x - y * y;
            if (zsq >= 0) {
                const zsqrt = std.math.sqrt(zsq);
                return .{ .z1 = self.cz - zsqrt, .z2 = self.cz + zsqrt };
            }
            return null;
        }
    };
}
fn DeathStar(comptime T: type) type {
    return struct {
        const Self = @This();
        allocator: Allocator,
        w: usize,
        h: usize,
        img: ImageData(),

        const Hit = enum { background, neg, pos };

        pub fn init(allocator: Allocator) !Self {
            var dir = Vector(T).init(20, -40, 10);
            dir.normalize();
            // positive sphere and negative sphere
            const pos = Sphere(T).init(180, 240, 220, 120);
            const neg = Sphere(T).init(60, 150, 100, 100);

            const k: T = 1.5;
            const amb: T = 0.2;

            const w: usize = @intFromFloat(pos.r * 4);
            const h: usize = @intFromFloat(pos.r * 3);
            var img = try ImageData().init(allocator, "deathStar", w, h);

            var vec = Vector(T).zero();

            const start_y: usize = @intFromFloat(pos.cy - pos.r);
            const end_y: usize = @intFromFloat(pos.cy + pos.r);
            const start_x: usize = @intFromFloat(pos.cx - pos.r);
            const end_x: usize = @intFromFloat(pos.cx + pos.r);

            for (start_y..end_y + 1) |j| {
                for (start_x..end_x + 1) |i| {
                    const x: T = @floatFromInt(i);
                    const y: T = @floatFromInt(j);

                    const result_pos = pos.hit(x, y);
                    // ray lands in blank space, show bg
                    if (result_pos == null)
                        continue;

                    const zb1 = result_pos.?.z1;
                    const zb2 = result_pos.?.z2;

                    const result_neg = neg.hit(x, y);

                    switch (calcHit(result_neg, zb1, zb2)) {
                        .background => continue,
                        .neg => {
                            vec.x = neg.cx - x;
                            vec.y = neg.cy - y;
                            vec.z = neg.cz - result_neg.?.z2; // zs2
                        },
                        .pos => {
                            vec.x = x - pos.cx;
                            vec.y = y - pos.cy;
                            vec.z = zb1 - pos.cz;
                        },
                    }
                    vec.normalize();
                    var s = dir.dot(&vec);
                    if (s < 0) s = 0;
                    const lum = 255 * (std.math.pow(T, s, k) + amb) / (1 + amb);
                    const lumi: u8 = @intFromFloat(std.math.clamp(lum, 0, 255));
                    img.pset(i, j, Gray{ .w = lumi });
                }
            }
            return Self{ .allocator = allocator, .w = w, .h = h, .img = img };
        }
        pub fn deinit(self: *Self) void {
            self.img.deinit();
        }
        pub fn print(self: *Self, writer: anytype, optional_comments: ?[]const []const u8) !void {
            try self.img.print(writer, optional_comments);
        }
        /// Ray has hit the positive sphere.
        /// How does it intersect the negative sphere ?
        fn calcHit(neg_hit: ?SphereHit(T), zb1: T, zb2: T) Hit {
            if (neg_hit) |result| {
                const zs1 = result.z1;
                const zs2 = result.z2;
                if (zs1 > zb1) {
                    // ray hits both, but pos front surface is closer
                    return Hit.pos;
                } else if (zs2 > zb2) {
                    // pos sphere surface is inside neg sphere, show bg
                    return 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
                    return Hit.neg;
                } else {
                    return Hit.pos;
                }
            } else {
                // ray hits pos sphere but not neg, draw pos sphere surface
                return Hit.pos;
            }
        }
    };
}
const Gray = struct {
    w: u8,
    const black = Gray{ .w = 0 };
};
fn ImageData() type {
    return struct {
        const Self = @This();
        allocator: Allocator,
        name: []const u8,
        w: usize,
        h: usize,
        image: []Gray,

        pub fn init(allocator: Allocator, name: []const u8, w: usize, h: usize) !Self {
            const image = try allocator.alloc(Gray, h * w);
            // black background fill
            for (image) |*pixel| pixel.* = Gray.black;
            return Self{ .allocator = allocator, .image = image, .name = name, .w = w, .h = h };
        }
        pub fn deinit(self: *Self) void {
            self.allocator.free(self.image);
        }
        pub fn pset(self: *Self, x: usize, y: usize, gray: Gray) void {
            self.image[x * self.w + y] = gray;
        }
        /// Write PGM P2 ASCII to 'writer'
        pub fn print(self: *const Self, writer: anytype, optional_comments: ?[]const []const u8) !void {
            try writer.print("P2\n", .{});

            if (optional_comments) |lines| {
                for (lines) |line|
                    try writer.print("# {s}\n", .{line});
            }

            try writer.print("{d} {d}\n{d}\n", .{ self.w, self.h, 255 });

            for (self.image, 0..) |pixel, i| {
                const sep = if (i % self.w == self.w - 1) "\n" else " ";
                try writer.print("{d}{s}", .{ pixel.w, sep });
            }
        }
    };
}