Death Star

From Rosetta Code
Revision as of 14:50, 5 September 2019 by Trizen (talk | contribs) (→‎{{header|Sidef}}: simplified the code to use the Vector class and added link to the output image)
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 task



AutoHotkey

Library: GDIP

<lang ahk>#NoEnv SetBatchLines, -1

  1. SingleInstance, Force
Uncomment if Gdip.ahk is not in your standard library
  1. 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</lang>

Brlcad

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

  1. We will be measuring in kilometers

units km

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

in sph1.s sph 0 0 0 60

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

in sph2.s sph 0 90 0 40

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

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

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

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

  1. Clear the wireframe display and draw the deathstar

B deathstar.r

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

rt</lang>

C

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

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

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; }</lang>

D

Translation of: C

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

}</lang>

The output is the same of the C version.

DWScript

Translation of: C

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

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

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

procedure Normalize(var v : TVector); begin

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

end;

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

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

end;

type

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

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

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

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

end;

procedure DrawSphere(k, ambient : Float); var

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

begin

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

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

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

end;

Normalize(light);

DrawSphere(2, 0.3);</lang>

Go

Output png
Translation of: C

<lang go>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)
   }

}</lang>

J

Translation of: Python

<lang J> 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</lang>


Java

<lang Java>

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

} </lang>

JavaScript

Layer circles and gradients to achieve result similar to that of the Wikipedia page for the Death Star. <lang JavaScript> <!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>

</lang>

Julia

<lang julia># run in REPL using Makie

function deathstarjpg()

   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)

end

deathstarjpg() </lang>

LSL

Rez a box on the ground, raise it up a few meters, add the following as a New Script. <lang LSL>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]);
   }

}</lang> Output: Death Star


Maple

<lang 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)</lang>

Mathematica / Wolfram Language

<lang Mathematica>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]</lang>

Openscad

<lang openscad>// We are performing geometric subtraction

difference() {

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

}</lang>

Perl

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

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

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

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

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

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

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

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

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

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

draw(2, 0.2);</lang>

Perl 6

Translation of: C

Reimplemented to output a .pgm image.

Works with: Rakudo version 2018.10

<lang perl6>class sphere {

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

}

my $depth = 255; # image color depth

my $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 ]);

  1. positive sphere at origin

my $pos = sphere.new(

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

);

  1. negative sphere offset to upper left

my $neg = sphere.new(

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

);

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

   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;

}

  1. normalize a vector

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

  1. dot product of two vectors

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

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

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

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

}</lang>

Phix

Translation of: Go
Library: pGUI

<lang Phix>-- -- demo\rosetta\DeathStar.exw -- include pGUI.e

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, atom 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) integer lum sequence vec integer r = floor((min(width,height)-40)/2) integer cx = floor(width/2) integer cy = floor(height/2) sequence pos = {0,0,0},

        neg = {r*-3/4,r*-3/4,r*-1/4}
   for y = pos[Y]-r to pos[Y]+r do
       for x = pos[X]-r to pos[X]+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
                   if hit2 and zs1<=zb1 then
                       vec = {neg[X] - x, neg[Y] - y, neg[Z] - zs2}
                   else
                       vec = {x - pos[X], y - pos[Y], zb1 - pos[Z]}

-- vec = {x, y, zb1}

                   end if
                   atom s = dot(direction, normalize(vec))
                   lum = and_bits(#FF,255*(iff(s<0?0:power(s,k))+amb)/(1+amb))
                   lum += lum*#100+lum*#10000
                   cdCanvasPixel(cddbuffer, cx+x, cy-y, lum)
               end if
           end if
       end for
   end for

end procedure

function redraw_cb(Ihandle /*ih*/, integer /*posx*/, integer /*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

function esc_close(Ihandle /*ih*/, atom c)

   if c=K_ESC then return IUP_CLOSE end if
   return IUP_CONTINUE

end function

procedure main()

   IupOpen()
   canvas = IupCanvas(NULL)
   IupSetAttribute(canvas, "RASTERSIZE", "340x340") -- initial size
   IupSetCallback(canvas, "MAP_CB", Icallback("map_cb"))
   dlg = IupDialog(canvas)
   IupSetAttribute(dlg, "TITLE", "Draw a sphere")
   IupSetCallback(dlg, "K_ANY",     Icallback("esc_close"))
   IupSetCallback(canvas, "ACTION", Icallback("redraw_cb"))
   IupMap(dlg)
   IupSetAttribute(canvas, "RASTERSIZE", NULL) -- release the minimum limitation
   IupShowXY(dlg,IUP_CENTER,IUP_CENTER)
   IupMainLoop()
   IupClose()

end procedure

main()</lang>

POV-Ray

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

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

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

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

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

difference {

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

} </lang>

Python

Translation of: C

<lang python>import sys, math, collections

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

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

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

def dot(v1, v2):

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

def hit_sphere(sph, x0, y0):

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

def draw_sphere(k, ambient, light):

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

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


Q

write an image in BMP format: <lang Q> / 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]; </lang>

Create the death star image:

<lang Q> 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];

</lang>(converted to JPG ...)

Racket

<lang racket>

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

</lang>

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.) <lang rexx>/*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(); m.=9; numeric digits

      numeric form;  parse value format(x,2,1,,0) 'E0' with g 'E' _ .;   g=g * .5'e'_ % 2
      h=d+6;           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*/
      numeric digits d;                       return  g/1

/*──────────────────────────────────────────────────────────────────────────────────────*/ 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 6=="f6"x then shading= '.:!*oe&#%@' /*shading characters for EBCDIC machine*/

            else shading= '·:!ºoe@░▒▓'          /*   "        "       "  ASCII     "   */

shadingL=length(shading) 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.*/
   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 a ···*/
                                                              else $pos=1 /*·························  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</lang> 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

Set lang

<lang 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</lang> Outputs:

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

(it's the best I could do!)

Sidef

Translation of: Perl

Writes a PGM to stdout. <lang ruby>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)</lang> 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. <lang tcl>package require Tcl 8.5

proc normalize vec {

   upvar 1 $vec v
   lassign $v x y z
   set len [expr {sqrt($x**2 + $y**2 + $z**2)}]
   set v [list [expr {$x/$len}] [expr {$y/$len}] [expr {$z/$len}]]
   return

}

proc dot {a b} {

   lassign $a ax ay az
   lassign $b bx by bz
   return [expr {-($ax*$bx + $ay*$by + $az*$bz)}]

}

  1. Intersection code; assumes that the vector is parallel to the Z-axis

proc hitSphere {sphere x y z1 z2} {

   dict with sphere {

set x [expr {$x - $cx}] set y [expr {$y - $cy}] set zsq [expr {$r**2 - $x**2 - $y**2}] if {$zsq < 0} {return 0} upvar 1 $z1 _1 $z2 _2 set zsq [expr {sqrt($zsq)}] set _1 [expr {$cz - $zsq}] set _2 [expr {$cz + $zsq}] return 1

   }

}

  1. How to do the intersection with our scene

proc intersectDeathStar {x y vecName} {

   global big small
   if {![hitSphere $big $x $y zb1 zb2]} {

# ray lands in blank space return 0

   }
   upvar 1 $vecName vec
   # ray hits big sphere; check if it hit the small one first
   set vec [if {

![hitSphere $small $x $y zs1 zs2] || $zs1 > $zb1 || $zs2 <= $zb1

   } then {

dict with big { list [expr {$x - $cx}] [expr {$y - $cy}] [expr {$zb1 - $cz}] }

   } else {

dict with small { list [expr {$cx - $x}] [expr {$cy - $y}] [expr {$cz - $zs2}] }

   }]
   normalize vec
   return 1

}

  1. Intensity calculators for different lighting components

proc diffuse {k intensity L N} {

   expr {[dot $L $N] ** $k * $intensity}

} proc specular {k intensity L N S} {

   # Calculate reflection vector
   set r [expr {2 * [dot $L $N]}]
   foreach l $L n $N {lappend R [expr {$l-$r*$n}]}
   normalize R
   # Calculate the specular reflection term
   return [expr {[dot $R $S] ** $k * $intensity}]

}

  1. Simple raytracing engine that uses parallel rays

proc raytraceEngine {diffparms specparms ambient intersector shades renderer fx tx sx fy ty sy} {

   global light
   for {set y $fy} {$y <= $ty} {set y [expr {$y + $sy}]} {

set line {} for {set x $fx} {$x <= $tx} {set x [expr {$x + $sx}]} { if {![$intersector $x $y vec]} { # ray lands in blank space set intensity end } else { # ray hits something; we've got the normalized vector set b [expr { [diffuse {*}$diffparms $light $vec] + [specular {*}$specparms $light $vec {0 0 -1}] + $ambient }] set intensity [expr {int((1-$b) * ([llength $shades]-1))}] if {$intensity < 0} { set intensity 0 } elseif {$intensity >= [llength $shades]-1} { set intensity end-1 } } lappend line [lindex $shades $intensity] } {*}$renderer $line

   }

}

  1. The general scene settings

set light {-50 30 50} set big {cx 20 cy 20 cz 0 r 20} set small {cx 7 cy 7 cz -10 r 15} normalize light

  1. Render as text

proc textDeathStar {diff spec lightBrightness ambient} {

   global big
   dict with big {

raytraceEngine [list $diff $lightBrightness] \ [list $spec $lightBrightness] $ambient intersectDeathStar \ [split ".:!*oe&#%@ " {}] {apply {l {puts [join $l ""]}}} \ [expr {$cx+floor(-$r)}] [expr {$cx+ceil($r)+0.5}] 0.5 \ [expr {$cy+floor(-$r)+0.5}] [expr {$cy+ceil($r)+0.5}] 1

   }

} textDeathStar 3 10 0.7 0.3</lang> Output:

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

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

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

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

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

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

   }
   raytraceEngine [list $diff $lightBrightness] \

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

Yabasic

<lang 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 </lang>