Raytraced Sphere #3

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