Ray marching with both spherical and cartesian repetition
Log in to post a comment.
// Forked from "Deep ocean floor" by PavlikEnemy // https://turtletoy.net/turtle/4bbcc9c6b4 // Forked from "Making waves" by PavlikEnemy // https://turtletoy.net/turtle/13016b43d5 Canvas.setpenopacity(0.3); const t = new Turtle(); const scrZ = 2.0; const scrRx = 1.0; const scrRy = 1.0; const eyeZ = 4.0; const camRotX = 0; const camRotY = 0; const step = 0.25; const Dmax = scrZ + 10; const INF = Dmax * 2; const EPS = 0.0001; const MAX_N_STEPS = 64; function plus3(v1, v2) { return [v1[0] + v2[0], v1[1] + v2[1], v1[2] + v2[2]]; } function minus3(v1, v2) { return [v1[0] - v2[0], v1[1] - v2[1], v1[2] - v2[2]]; } function mod3(v1, v2) { return [v1[0] % v2[0], v1[1] % v2[1], v1[2] % v2[2]]; } function multiply3(a, v) { return [a * v[0], a * v[1], a * v[2]]; } function abs3(v) { return [Math.abs(v[0]), Math.abs(v[1]), Math.abs(v[2])]; } function length3(v) { return Math.sqrt(v[0]**2 + v[1]**2 + v[2]**2); } function normalize3(v) { return multiply3(1 / length3(v), v); } function rotate2(v, phi) { return [v[0] * Math.cos(phi) - v[1] * Math.sin(phi), v[0] * Math.sin(phi) + v[1] * Math.cos(phi)]; } function cast_ray_from_screen(x, y) { let px = -scrRx + 2 * scrRx * (x / 200); let py = -scrRy + 2 * scrRy * (y / 200); let pz = scrZ; [px, py] = rotate2([px, py], camRotY); [py, pz] = rotate2([py, pz], camRotX); let ex = 0, ey = 0, ez = eyeZ; //[ex, ey] = rotate2([ex, ey], camRotY) [ey, ez] = rotate2([ey, ez], camRotX) let p = [px, py, pz]; let eye = [ex, ey, ez]; let dir = normalize3(minus3(p, eye)); return [p, dir]; } function intersect_df(p, dir, df) { let D = 0; var n_steps; for (n_steps = 0; n_steps < MAX_N_STEPS; n_steps++) { let d = scene_df(p); if ((d < EPS) || (D > Dmax)) { break; } else { p = plus3(p, multiply3(d, dir)); D += d; } } return [p, D, n_steps]; } function cylinder_z_df(p, c, h, r) { // axis z const [px, py, pz] = minus3(p, c); 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); } } } function cylinder_x_df(p, c, h, r) { const [px, py, pz] = minus3(p, c); let dyz = Math.sqrt(py**2 + pz**2) - r; let dx = Math.abs(px) - h/2; if (dyz > 0) { if (dx > 0) { return Math.sqrt(dyz**2 + dx**2); } else { return dyz; } } else { if (dx > 0) { return dx; } else { return Math.max(dyz, dx); } } } function plane_df(p) { // z = 0 return p[2]; } function sphere_df(p, c, r) { return length3(minus3(c, p)) - r; } function smooth_min(a, b, k) { // see https://iquilezles.org/articles/smin/ let h = Math.max(k - Math.abs(a - b), 0) / k; return Math.min(a, b) - h*h*h*k*(1/6); } function smooth_max(a, b, k=1) { return -smooth_min(-a, -b, k); } function cartesian2spherical(p) { let rho = length3(p); let theta = Math.asin(p[1] / rho); let phi = Math.atan2(p[2], p[0]); return [rho, theta, phi]; } function spherical2cartesian(rho, theta, phi) { return [rho * Math.cos(theta) * Math.cos(phi), rho * Math.sin(theta), rho * Math.cos(theta) * Math.sin(phi)]; } function scene_df(p) { let p0 = p; // infinite repetition const periods = [4.0, 4.0, 4.0]; const half_periods = multiply3(0.5, periods); if (length3(p) > 1.5) { p = abs3(p); p = minus3(mod3(plus3(p, half_periods), periods), half_periods); } // spherical repetition with extra shift let [rho, theta, phi] = cartesian2spherical(p); phi += 20 * Math.PI/180; let per_theta = Math.PI / 13; let per_phi = Math.PI / 12; let t_th = Math.floor((Math.abs(theta) + per_theta/2) / per_theta); let t_ph = Math.floor((Math.abs(phi) + per_phi/2) / per_phi); if (t_th <= 6) { if (t_th > 0) { theta = (Math.abs(theta) + per_theta/2) % per_theta - per_theta/2; } if (t_ph > 0) { phi = (Math.abs(phi) + t_th * per_phi/2 + per_phi/2) % per_phi - per_phi/2; } p = spherical2cartesian(rho, theta, phi); } const R = 1.0; const r = 0.03; const h = 12 * r; let df = cylinder_x_df(p, [R, 0, 0], h, r); for (let i = 0; i < 3; i++) { let psi = (t_th * 19 + t_ph * 17) * Math.PI / 6 + 2 * Math.PI / 3 * i; let c = [R + h/2 + r/2, r * Math.cos(psi), r * Math.sin(psi)]; let s_df = sphere_df(p, c, r) df = smooth_min(df, s_df, 0.05); } let sph_df = sphere_df(p, [0, 0, 0], R); let noise = 0.02 * Math.sin(30 * p0[0]) * Math.sin(30 * p0[1]) * Math.sin(30 * p0[2]); sph_df += noise; df += 0.5 * noise; df = smooth_min(sph_df, df, 0.2); return df; } let x = 0; let y = 0; function walk(frame) { const [p, dir] = cast_ray_from_screen(x, y); const [p_int, dist_to_int, n_steps] = intersect_df(p, dir, scene_df); if (dist_to_int < Dmax) { //let s = step * Math.max(0.1, (1 - n_steps / 50)); let s = step * Math.max(0.1, (1 - n_steps / 50))**2.5; 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); }