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