I have combined the raytraced scene of Raytraced sphere #1 with the circle-packing algorithm of Circle packing #1 to get some sort of dithering.
#raytracer #pixels #rays #dithering
Log in to post a comment.
// Raytraced Sphere #3. Created by Reinder Nijhoff 2018
// @reindernijhoff
//
// https://turtletoy.net/turtle/7367ba3a0c
//
Canvas.setpenopacity(1);
const canvas_size = 95;
const light_position = [-2,3,-4];
const ro = [0,0,-3.5];
const sphere_pos = [-.2,0,0];
const max_radius = 2;
const min_radius = .55;
const radius_decr = .9;
const max_tries = 400;
const circle_radius = .5;
const circle_buckets = [];
const circle_num_buckets = canvas_size/max_radius;
let radius = max_radius;
const circles = [];
const turtle = new Turtle();
for (let i=0; i<circle_num_buckets+2; i++) {
circle_buckets[i]=[];
for (let j=0; j<circle_num_buckets+2; j++) {
circle_buckets[i][j] = [];
}
}
function add_circle(t, r) {
let coord_found = false;
let tries = 0;
const drdr = r*r*2;
while (!coord_found && tries < max_tries) {
tries ++;
const x = Math.random() * (canvas_size-r)*2 -canvas_size + r;
const y = Math.random() * (canvas_size-r)*2 -canvas_size + r;
let possible = true;
const xb = Math.max(0,((.5*x/canvas_size+.5)*circle_num_buckets)|0);
const yb = Math.max(0,((.5*y/canvas_size+.5)*circle_num_buckets)|0);
for (let xbi = Math.max(0,xb-1); xbi<xb+2 && possible; xbi++) {
for (let ybi = Math.max(0,yb-1); ybi<yb+2 && possible; ybi++) {
const circles = circle_buckets[xbi][ybi];
for (let i=0; i<circles.length && possible; i++) {
const dx = circles[i][0] - x;
const dy = circles[i][1] - y;
if ( dx*dx + dy*dy < drdr) {
possible = false;
break;
}
}
}
}
if (possible) {
coord_found = true;
draw_circle(x,y,t, r);
circle_buckets[xb][yb].push([x,y]);
return true;
}
}
return false;
}
function draw_circle(x,y,t,r) {
const intensity = get_image_intensity(x/canvas_size, y/canvas_size);
// use intensity squared because it looks better
if ((r-min_radius)/max_radius > .65 * intensity * intensity) {
turtle.penup();
turtle.goto(x,y-circle_radius);
turtle.pendown();
turtle.circle(circle_radius);
}
}
function walk(i) {
if (!add_circle(turtle, radius)) {
radius *= radius_decr;
}
return radius >= min_radius;
}
function get_image_intensity(x,y) {
const rd = normalize3([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) {
hit = add3(ro, scale3(rd, dist));
normal = normalize3(hit);
} else {
dist = 10000;
}
if (rd[1] < 0) {
const plane_dist = -1/rd[1];
if (plane_dist < dist) {
dist = plane_dist;
plane_hit = true;
hit = add3(ro, scale3(rd, dist));
normal = [0,1,0];
}
}
if (dist > 0 && dist < 100) {
let vec_to_light = sub3(hit, light_position);
const light_dist_sqr = dot3(vec_to_light, vec_to_light);
vec_to_light = scale3(vec_to_light, -1/Math.sqrt(light_dist_sqr));
let light = dot3(normal, vec_to_light);
light *= 30 / light_dist_sqr;
// shadow ?
if (plane_hit && intersect_sphere(hit, vec_to_light, sphere_pos, 1) > 0) {
light = 0;
}
return Math.sqrt(Math.min(1, Math.max(0,light)));
} else {
return 0;
}
}
const scale3=(a,b)=>[a[0]*b,a[1]*b,a[2]*b];
const len3=(a)=>Math.sqrt(dot3(a,a));
const normalize3=(a)=>scale3(a,1/len3(a));
const add3=(a,b)=>[a[0]+b[0],a[1]+b[1],a[2]+b[2]];
const sub3=(a,b)=>[a[0]-b[0],a[1]-b[1],a[2]-b[2]];
const dot3=(a,b)=>a[0]*b[0]+a[1]*b[1]+a[2]*b[2];
function intersect_sphere(ro, rd, center, radius) {
const oc = sub3(ro, center);
const b = dot3( oc, rd );
const c = dot3( oc, oc ) - radius * radius;
const h = b*b - c;
if( h<0 ) return -1;
return -b - Math.sqrt( h );
}