Glowing spinner

My first ever attempt at ray marching (from scratch)

Log in to post a comment.

Canvas.setpenopacity(-0.3);

const t = new Turtle();


const scrZ = 4.0;
const scrR = 1.0;

const eyeZ = 8.0;

const camRotX = -60 * Math.PI/180;

const step = 0.25;

const R = 1.0;
const R1 = 0.5;
const R2 = 0.15;

const Dmax = scrZ + 2 * scrR;
const INF = 2 * Dmax;

const EPS = 0.0001;

const MAX_N_STEPS = 256;

const glowR = 0.2 * R;


function plus(v1, v2) {
    return [v1[0] + v2[0], v1[1] + v2[1], v1[2] + v2[2]];
}

function minus(v1, v2) {
    return [v1[0] - v2[0], v1[1] - v2[1], v1[2] - v2[2]];
}

function multiply(a, v) {
    return [a * v[0], a * v[1], a * v[2]];
}

function length(v) {
    return Math.sqrt(v[0]**2 + v[1]**2 + v[2]**2);
}

function normalize(v) {
    let L = length(v);
    return [v[0] / L, v[1] / L, v[2] / L];
}


function torus_df(p, c, R, r) { // axis z
    let pc = minus(p, c);
    let px = pc[0], py = pc[1], pz = pc[2];
    
    let dxy = Math.sqrt(px**2 + py**2);
    
    let cx = px / dxy * R;
    let cy = py / dxy * R;
    
    let d = [px - cx, py - cy, pz];
    
    return length(d) - r;
}

function cylinder_df(p, h, r) { // center (0, 0, 0), axis z
    let px = p[0], py = p[1], pz = p[2];
    
    let dxy = Math.sqrt(px**2 + py**2) - r;
    let dz = Math.abs(pz) - h/2;

    if (dxy > 0) {
        if (dz > 0) {
            return Math.sqrt(dxy**2 + dz**2);
        }
        else {
            return dxy;
        }
    }
    else {
        if (dz > 0) {
            return dz;
        }
        else {
            return Math.max(dxy, dz);
        }
    }
}

// see https://iquilezles.org/articles/smin/
function smooth_min(a, b, k) {
    let h = Math.max(k - Math.abs(a - b), 0) / k;
    return Math.min(a, b) - h*h*h*k*(1/6);
}


function scene_df(p) {
    let phi = -Math.PI / 12;
    let phi1 = phi - Math.PI / 6, phi2 = phi - 5 * Math.PI / 6, phi3 = phi - 3 * Math.PI / 2;
    let df1 = torus_df(p, [R * Math.cos(phi1), R * Math.sin(phi1), 0], R1, R2);
    let df2 = torus_df(p, [R * Math.cos(phi2), R * Math.sin(phi2), 0], R1, R2);
    let df3 = torus_df(p, [R * Math.cos(phi3), R * Math.sin(phi3), 0], R1, R2);
    let df4 = cylinder_df(p, 4 * R2, 0.7 * (R - R1 - R2));
    let df12 = smooth_min(df1, df2, 0.35);
    let df123 = smooth_min(df12, df3, 0.35);
    let df1234 = smooth_min(df123, df4, 0.75);
    return df1234;
}


let x = 0;
let y = 0;

function walk(frame) {
    let px = -scrR + 2 * scrR * (x / 200);
    let py = -scrR + 2 * scrR * (y / 200);
    let pz = scrZ;
    
    [py, pz] = [py * Math.cos(camRotX) - pz * Math.sin(camRotX), py * Math.sin(camRotX) + pz * Math.cos(camRotX)];

    let ex = 0, ey = 0, ez = eyeZ;
    
    [ey, ez] = [ey * Math.cos(camRotX) - ez * Math.sin(camRotX), ey * Math.sin(camRotX) + ez * Math.cos(camRotX)];

    let p = [px, py, pz];
    let dir = normalize([px - ex, py - ey, pz - ez]);
    
    
    let D = 0;
    let d_min = INF;
    var n_steps;
    
    for (n_steps = 0; n_steps < MAX_N_STEPS; n_steps++) {
        let d = scene_df(p);
        
        d_min = Math.min(d_min, d);
        
        if ((d < EPS) || (D > Dmax)) {
            break;
        }
        else {
            p = plus(p, multiply(d, dir));
            
            D += d;
        }
    }
    
    
    if (D < Dmax) {
        let s = step * Math.max(0.1, (1 - n_steps / 50));
        
        t.seth(360 * Math.random());

        for (let i = 0; i < 4; i++) {
            t.jump(x - 100, y - 100);
            t.right(90);
            t.forward(s);
        }
    }

    if ((d_min > EPS) && (d_min < glowR)) {
        let s = step * (1 - d_min / glowR)**2;
        
        t.seth(360 * Math.random());
        
        for (let i = 0; i < 4; i++) {
            t.jump(x - 100, y - 100);
            t.right(90);
            t.forward(s);
        }
    }
    
    x += step;
    if (x > 200) {
        y += step;
        x = 0;
    }
    
    return (y < 200);
}