### FSD Raytraced Sphere #1

A combination of Floyd-Steinberg Dithering and raytracing.

Special thanks to @reinder for the raytracing algorithm.
This demonstrates the process could be repeated for any algorithm that samples intensity at a point.

```// Forked from "Floyd–Steinberg dithering" by imakerobots

// raytraced sphere part from https://turtletoy.net/turtle/11075dfee0
const canvas_size = 95;
const brown_rot = 360;
const brown_for_min = 1;
const brown_for_max = 10;

const light_position = [-2,3,-4];
const ro = [0,0,-3.5];
const sphere_pos = [-.2,0,0];

function get_image_intensity(x,y) {
x /= canvas_size;
y /= canvas_size;

const rd = vec_normalize([x,-y,2]);
let normal;
let light = 0;
let hit;
let plane_hit = false;

let dist = intersect_sphere(ro, rd, sphere_pos, 1);
if (dist > 0) {
normal = vec_normalize(hit);
} else {
dist = 10000;
}
if (rd[1] < 0) {
const plane_dist = -1/rd[1];
if (plane_dist < dist) {
dist = plane_dist;
plane_hit = true;
normal = [0,1,0];
}
}

if (dist > 0 && dist < 100) {
let vec_to_light = vec_sub(hit, light_position);
const light_dist_sqr = vec_dot(vec_to_light, vec_to_light);

vec_to_light = vec_mul(vec_to_light, -1/Math.sqrt(light_dist_sqr));

let light = vec_dot(normal, vec_to_light);
light *= 20 / light_dist_sqr;  // changed the intensity a bit

if (plane_hit && intersect_sphere(hit, vec_to_light, sphere_pos, 1) > 0) {
light = 0.01;  // added some ambient light.  make 0 for none.
}

return Math.sqrt(Math.min(1, Math.max(0,light)));
} else {
return 0;
}
}

// math functions
function vec_normalize(a) {
const l = Math.sqrt(vec_dot(a,a));
return [a[0]/l,a[1]/l,a[2]/l];
}

return [a[0]+b[0], a[1]+b[1], a[2]+b[2]]
}

function vec_mul(a, b) {
return [a[0]*b, a[1]*b, a[2]*b]
}

function vec_sub(a, b) {
return [a[0]-b[0], a[1]-b[1], a[2]-b[2]]
}

function vec_dot(a, b) {
return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];
}

function intersect_sphere(ro, rd, center, radius) {
const oc = vec_sub(ro, center);
const b = vec_dot( oc, rd );
const h = b*b - c;
if( h<0 ) return -1;
return -b - Math.sqrt( h );
}

Canvas.setpenopacity(1);

// Global code will be evaluated once.
const turtle = new Turtle();
const level_of_detail=2;  // min=1.5, max=20, step=0.5

turtle.penup();
turtle.goto(-100,-100);
const stepSize = level_of_detail/10.0;
const stepsPerLine = Math.floor(200.0/stepSize);
stepsRemaining=stepsPerLine*stepsPerLine;
dx=1;

error1 = new Array(stepsPerLine);
error2 = new Array(stepsPerLine);
for(i=0;i<error1.length;++i) {
error1[i] = error2[i] = 0;
}

function newLine() {
for(var i=0;i<stepsPerLine;++i) {
error1[i] = error2[i];
error2[i] = 0;
}
}

function get_pixel(x,y) {
return get_image_intensity( x,y );
}

function find_closest_palette_color(oldpixel) {
return oldpixel>0.5?1.0:0.0;
}

function walk(i) {
var x=turtle.x()/stepSize+stepsPerLine/2;
var y=turtle.y()/stepSize+stepsPerLine/2;
if(x<0) x=0;
if(x>=stepsPerLine) x=stepsPerLine-1;
x=Math.floor(x);

var oldpixel = error1[x] + get_pixel(turtle.x(),turtle.y());
var newpixel = find_closest_palette_color(oldpixel);
var quant_error = oldpixel - newpixel;
var xP1 = x+dx;
var xM1 = x-dx;
if(xP1>=0 && xP1<stepsPerLine) error1[xP1] += quant_error * 7.0/16.0;
if(xM1>=0 && xM1<stepsPerLine) error2[xM1] += quant_error * 3.0/16.0;
error2[x  ] += quant_error * 5.0/16.0;
if(xP1>=0 && xP1<stepsPerLine) error2[xP1] += quant_error * 1.0/16.0;

if(newpixel>0.5) {
turtle.penup();
} else {
turtle.pendown();
}

turtle.forward(stepSize);
if(turtle.x()>=100) {
turtle.right(90);
turtle.forward(stepSize);
turtle.right(90);
dx=-dx;
newLine();
} else if(turtle.x()<=-100) {
turtle.left(90);
turtle.forward(stepSize);
turtle.left(90);
dx=-dx;
newLine();
}

return turtle.y()<100 && turtle.y()>=-100;//stepsRemaining-- > 0;
}```