Raymarching fed into Contour Lines
Log in to post a comment.
// LL 2021 const turtle = new Turtle(); //Canvas.setpenopacity(-1); const detail_z = 51; // min=1, max=301, step=2 const detail_xy = 1.; // min=0.1, max=3, step=0.1 const passes = 2; // min=1, max=3, step=1 (X, XY, XYZ) const height = 1.5; // min=0, max=10, step=0.1 const terraforming1 = 5; // min=0, max=10, step=0.1 const terraforming2 = 5; // min=0, max=10, step=0.1 const terraforming3 = 0; // min=-5, max=5, step=0.1 const terraforming4 = 0; // min=-5, max=5, step=0.1 const terraforming5 = 0.1; // min=-0.5, max=0.5, step=0.01 const cam_dist = 2; // min=0.1, max=10.5, step=0.01 const cam_angle = -2; // min=-3.14159, max=3.15159, step=0.01 const MAX_STEPS = 256; const MAX_DIST = 100; const SURF_DIST = .001; const range = [ -1, 1 ]; function length2(v2) { return Math.sqrt(v2[0]*v2[0] + v2[1]*v2[1]); } function length3(v3) { return Math.sqrt(v3[0]*v3[0] + v3[1]*v3[1] + v3[2]*v3[2]); } function normalize3(v3) { var l = length3(v3); if (l<0.00001) l=1; return [v3[0]/l, v3[1]/l, v3[2]/l]; } function mul2(a2, f) { return [a2[0]*f, a2[1]*f]; } function mul3(a3, f) { return [a3[0]*f, a3[1]*f, a3[2]*f]; } function add3(a3, b3) { return [a3[0]+b3[0], a3[1]+b3[1], a3[2]+b3[2]]; } function sub3(a3, b3) { return [a3[0]-b3[0], a3[1]-b3[1], a3[2]-b3[2]]; } function cross3(a3, b3) { return [ a3[1] * b3[2] - a3[2] * b3[1], a3[2] * b3[0] - a3[0] * b3[2], a3[0] * b3[1] - a3[1] * b3[0] ]; } function fract3(v3) { return [ v3[0]-Math.floor(v3[0]), v3[1]-Math.floor(v3[1]), v3[2]-Math.floor(v3[2]) ]; } function floor3(v3) { return [ Math.floor(v3[0]), Math.floor(v3[1]), Math.floor(v3[2]) ]; } function abs3(v3) { return [ Math.abs(v3[0]), Math.abs(v3[1]), Math.abs(v3[2]) ]; } function dot3(a3, b3) { return a3[0] * b3[0] + a3[1] * b3[1] + a3[2] * b3[2]; } function mul_mat2(v2, m22) { return [ v2[0] * m22[0] + v2[1] * m22[1], v2[0] * m22[2] + v2[1] * m22[3] ]; } function clamp(x, min, max) { return Math.min(max, Math.max(min, x)); } function smoothstep(edge0, edge1, x) { x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); return x * x * (3 - 2 * x); } function tri3(x3) { return abs3(sub3(fract3(x3), .5)); } function mix(x, y, a) { return x * (1-a) + y * a; } function mix3(a3, b3, f) { return [ mix(a3[0], b3[0], f), mix(a3[1], b3[1], f), mix(a3[2], b3[2], f) ]; } function smin(a, b , s) { var h = clamp( 0.5 + 0.5*(b-a)/s, 0. , 1.); return mix(b, a, h) - h*(1.0-h)*s; } function rotation_mat22(a) { var s = Math.sin(a); var c = Math.cos(a); return [c, -s, s, c]; } function GetRayDir(uv2, p3, l3, z) { var f3 = normalize3(sub3(l3, p3)); var r3 = normalize3(cross3([0,1,0], f3)); var u3 = cross3(f3, r3); var c3 = mul3(f3, z); var i3 = add3(add3(c3, mul3(r3, uv2[0])), mul3(u3, uv2[1])); var d3 = normalize3(i3); return d3; } // Box function sdBox(p3, s3) { p3 = sub3(abs3(p3), s3); var r = Math.min(Math.max(p3[0], Math.max(p3[1], p3[2])), 0); var q3 = [Math.max(p3[0], 0), Math.max(p3[1], 0), Math.max(p3[2], 0)]; q3 = add3(q3, [r,r,r]); return length3(q3); } // Torus function sdTorus(p3, r1, r2) { var cp2 = [ length2([p3[0], p3[2]]) - r1, p3[1] ]; var d = length2(cp2) - r2; return d; } // Knot function sdKnot(p3, r1, r2, twist, split) { var cp2 = [ length2([p3[0], p3[2]]) - r1, p3[1] ]; var a = Math.atan2(p3[2], p3[0]); // polar angle between -pi and pi cp2 = mul_mat2(cp2, rotation_mat22(a * twist)); cp2[1] = Math.abs(cp2[1]) - split; var d = length2(cp2) - r2; return d * .8; } // Sphere function sdSphere(p3, r) { var d = length3(p3) - r; return d; } function warp3(p3) { const f = 0.1; const t = 100.0 * p3[1] + 2.0 * 1; return [p3[0] + Math.sin(t) * f, p3[1], p3[2] + Math.cos(t) * f]; } function displacement(p3) { return Math.sin(21*p3[0])*Math.sin(21*p3[1])*Math.sin(21*p3[2]); } function hash(n) { var f = Math.sin(n) * 753.5453123; return f - Math.trunc(f); } function noise3(v3) { var p3 = floor3(v3); var f3 = fract3(v3); f3[0] = f3[0] * f3[0] * (3.0 - 2.0 * f3[0]); f3[1] = f3[1] * f3[1] * (3.0 - 2.0 * f3[1]); f3[2] = f3[2] * f3[2] * (3.0 - 2.0 * f3[2]); var n = p3[0] + p3[1] * 157.0 + 113.0 * p3[2]; return mix(mix(mix( hash(n + 0.0), hash(n + 1.0),f3[0]), mix( hash(n + 157.0), hash(n + 158.0),f3[0]),f3[1]), mix(mix( hash(n + 113.0), hash(n + 114.0),f3[0]), mix( hash(n + 270.0), hash(n + 271.0),f3[0]),f3[1]),f3[2]); } function fbm_terr(p3, lacunarity, init_gain, gain) { const _octaves = 3; var q3 = [...p3]; var H = init_gain; var t = 0.; for (var i = 0; i < _octaves; i++) { t += noise3(q3) * H; q3 = mul3(q3, lacunarity); H *= gain; } return t; } function fbm_terr_r(p3, lacunarity, init_gain, gain) { const _octaves = 3; var q3 = [...p3]; var H = init_gain; var t = 0.; for (var i = 0; i < _octaves; i++) { t += (1. - Math.abs(noise3(q3) * 2. - 1.)) * H; q3 = mul3(q3, lacunarity); H *= gain; } return t; } // Adapted from https://www.shadertoy.com/view/ldyXRw function sdPlanet(p3) { var h0 = fbm_terr(mul3(p3, 2.0987 + terraforming1), 2.0244 + terraforming3, .454 + terraforming5, .454 + terraforming5); var n0 = smoothstep(.35, 1., h0); var h1 = fbm_terr_r(add3(mul3(p3, 1.50987 + terraforming2), [ 1.9489, 2.435, .5483 ]), 2.0244 + terraforming4, .454 + terraforming5, .454 + terraforming5); var n1 = smoothstep(.5, 1., h1); var n = n0 - n1; const radius = 1; var d = length3(p3) - radius - n * height; d -= displacement(p3) * 0.03; return d; } function map(p3) { var d = MAX_DIST; d = Math.min(d, sdPlanet(p3)); return d; } function RayMarch(ro3, rd3) { var dO = 0; for (var i = 0; i < MAX_STEPS; i++) { var p3 = add3(ro3, mul3(rd3, dO)); var dS = map(p3); dO += dS; if (dO > MAX_DIST) return MAX_DIST; if (Math.abs(dS)<SURF_DIST) break; } return dO; } var pass = -1; function key(p2) { return p2[0] * 10000 + p2[1] * 10 + pass; } var cache = {}; // p: 2D point in -100 to 100 range function zFunc(p2) { var key_p2 = key(p2); if (key_p2 in cache) { var dist = cache[key_p2]; return dist; } // Convert to -1 to 1 var uv2 = [ p2[0] / 100, p2[1] / 100 ]; // Ray origin var ro3 = [ cam_dist * Math.cos(cam_angle), .1, cam_dist * Math.sin(cam_angle) ]; var l3 = [ 0, 0, 0 ]; // Ray direction var rd3 = GetRayDir(uv2, ro3, l3, 1.); // Get distance to intersection var dist = RayMarch(ro3, rd3); if (dist < MAX_DIST) { // Get intersection point var p3 = add3(ro3, mul3(rd3, dist)); dist = (p3[pass]-range[0]) / (range[1]-range[0]) * detail_z; } cache[key_p2] = dist; return dist; } function floor(x) { return Math.round(x*10); } function line_key(line) { return floor(line[0][0]) * 1000000000 + floor(line[0][1]) * 1000000 + floor(line[1][0]) * 1000 + floor(line[1][1]); } var line_cache = {}; function is_good(line) { var key = line_key(line); if (key in line_cache) return false; line_cache[key]=1; return true; } function walk(i) { pass = Math.floor(i / (detail_z+1)); if (pass >= passes) return false; const lines = ContourLines(i%detail_z, 1/detail_xy, zFunc); lines.forEach(line => { if (is_good(line)) { turtle.jump(line[0]); turtle.goto(line[1]); } }); return true; } // Metaball Contour Lines. Created by Reinder Nijhoff 2020 - @reindernijhoff // The MIT License // https://turtletoy.net/turtle/104c4775c5 function ContourLines(z, step, zFunc) { const intersectSegmentZ = (z, v1, v2) => { if (v1[2] === v2[2]) return false; const t = (z - v1[2]) / (v2[2] - v1[2]); if (t <= 0 || t > 1) return false; return [v1[0]+(v2[0]-v1[0])*t, v1[1]+(v2[1]-v1[1])*t]; } const intersectTriangleZ = (z, p1, p2, p3) => { const p = []; const v1 = intersectSegmentZ(z, p1, p2); const v2 = intersectSegmentZ(z, p2, p3); const v3 = intersectSegmentZ(z, p3, p1); if (v1 && v2) p.push([v1, v2]); if (v1 && v3) p.push([v1, v3]); if (v2 && v3) p.push([v2, v3]); return p; } const result = []; for (let x = -100; x <= 100; x += step) { for (let y = -100; y <= 100; y += step) { const corners = [[x, y], [x+step, y], [x+step, y+step], [x, y+step]]; corners.forEach( c => c[2] = zFunc(c) ); const c3 = [x+step/2, y+step/2, zFunc([x+step/2, y+step/2])]; for (let i=0; i<4; i++) { result.push(...intersectTriangleZ(z, corners[i], corners[(i+1) & 3], c3)); } } } return result; }