Death Star

From Rosetta Code
Revision as of 15:41, 15 July 2023 by PSNOW123 (talk | contribs) (New post in addition to an existing post which was retained. The previous post used a library "JavaFX" which has been removed from the Java JDK since version 11.)
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;

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

}

Alternatively, without using JavaFX which ws removed from the JavaJDK from version 11.

Java

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;
            var zsq = self.r * self.r - x * x - y * y;
            if (zsq >= 0) {
                var 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;

            var w: usize = @intFromFloat(pos.r * 4);
            var 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 });
            }
        }
    };
}