:)
Log in to post a comment.
// TurtleToy — EDM Cone Wireframe (radial 3D -> 2D projection) // Docs: global code runs once; walk(i) is called until it returns false. // Adjustable Variables syntax is supported by TurtleToy UI sliders. https://turtletoy.net/syntax Canvas.setpenopacity(1); const turtle = new Turtle(); // ---------- Adjustable controls ---------- const elev = 55; // min=0 max=85 step=1 Camera tilt (deg) const azim = 110; // min=0 max=360 step=1 Camera yaw (deg) const persp = 0.00; // min=0 max=1 step=0.01 Perspective amount (0=ortho) const rMin = 0.6; // min=0.1 max=2 step=0.05 Inner radius (bass center) const rMax = 5.0; // min=2 max=8 step=0.05 Outer radius (highs) const Nr = 80; // min=30 max=200 step=1 Radial samples const Nt = 240; // min=120 max=720 step=1 Angular samples (0..6π) const cone = 0.35; // min=0 max=1 step=0.01 Outward lift like a cone const spokeStep = 12; // min=1 max=60 step=1 Draw a spoke every N angles const drawRings = 1; // min=0 max=1 step=1 (Off,On) const drawSpokes = 1; // min=0 max=1 step=1 (Off,On) // ---------- EDM-like amplitude model (synthetic) ---------- const TWO_PI = Math.PI * 2; const thetaStart = 0, thetaEnd = 6*Math.PI; // "song length" in radians function gauss(x, mu, sigma) { const d = (x - mu) / sigma; return Math.exp(-d * d); } const theta0 = 3*Math.PI, theta1 = 3.8*Math.PI; // "drop" window function amp(r, theta) { const bass = (0.9 + 0.4 * Math.sin(4*theta)) * gauss(r, 1.0, 0.35); const mids = (0.6*Math.sin(0.6*theta) + 0.3*Math.cos(1.2*theta)) * gauss(r, 2.6, 0.6); const highs = (0.25*Math.sin(3.5*theta) + 0.15*Math.sin(8*theta)) * gauss(r, 4.2, 0.4); // triangular-ish gate for the drop, normalized to ~[0,1] let gate = 0; if (theta >= theta0 && theta <= theta1) { gate = (theta - theta0) * (theta1 - theta); const gmax = Math.pow((theta1 - theta0)/2, 2); gate = gate / (gmax + 1e-9); } const drop = 0.8 * gauss(r, 2.2, 1.2) * gate; return bass + mids + highs + drop; } // ---------- 3D -> 2D projection helpers ---------- function deg2rad(d){ return d * Math.PI / 180; } function rotateX(p, a){ const ca = Math.cos(a), sa = Math.sin(a); return { x: p.x, y: p.y*ca - p.z*sa, z: p.y*sa + p.z*ca }; } function rotateZ(p, a){ const ca = Math.cos(a), sa = Math.sin(a); return { x: p.x*ca - p.y*sa, y: p.x*sa + p.y*ca, z: p.z }; } function project(p){ if (persp > 0) { const f = 1.0; // focal-ish const s = f / (f + p.z * persp); return { x: p.x * s, y: p.y * s }; } return { x: p.x, y: p.y }; } function point3D(r, theta) { const z = amp(r, theta) + cone * (r - rMin); const x = r * Math.cos(theta); const y = r * Math.sin(theta); let p = {x, y, z}; p = rotateX(p, deg2rad(elev)); p = rotateZ(p, deg2rad(azim)); return project(p); } // ---------- Build grid of points (rings x angles) ---------- const rings = []; for (let i = 0; i < Nr; i++) { const r = rMin + (rMax - rMin) * (i/(Nr - 1)); const ring = []; for (let j = 0; j < Nt; j++) { const theta = thetaStart + (thetaEnd - thetaStart) * (j/(Nt - 1)); ring.push(point3D(r, theta)); } rings.push(ring); } // ---------- Convert to segments (line list) ---------- const segments = []; // ring segments (wrap around) if (drawRings) { for (let i = 0; i < Nr; i++) { const ring = rings[i]; for (let j = 0; j < Nt; j++) { const a = ring[j]; const b = ring[(j+1) % Nt]; segments.push([a.x, a.y, b.x, b.y]); } } } // spoke segments (radial) if (drawSpokes) { for (let j = 0; j < Nt; j += spokeStep) { for (let i = 0; i < Nr - 1; i++) { const a = rings[i][j]; const b = rings[i+1][j]; segments.push([a.x, a.y, b.x, b.y]); } } } // ---------- Fit to canvas (center & scale to ~[-95,95]) ---------- let minx=1e9, miny=1e9, maxx=-1e9, maxy=-1e9; for (const s of segments) { if (s[0] < minx) minx = s[0]; if (s[2] < minx) minx = s[2]; if (s[1] < miny) miny = s[1]; if (s[3] < miny) miny = s[3]; if (s[0] > maxx) maxx = s[0]; if (s[2] > maxx) maxx = s[2]; if (s[1] > maxy) maxy = s[1]; if (s[3] > maxy) maxy = s[3]; } const cx = (minx + maxx)/2, cy = (miny + maxy)/2; const scale = 190 / Math.max(maxx - minx, maxy - miny + 1e-9); // keep some margin for (let k=0; k<segments.length; k++) { const s = segments[k]; segments[k] = [ (s[0] - cx) * scale, (s[1] - cy) * scale, (s[2] - cx) * scale, (s[3] - cy) * scale ]; } // ---------- Draw incrementally ---------- function walk(i) { if (i >= segments.length) return false; const s = segments[i]; turtle.jump(s[0], s[1]); turtle.goto(s[2], s[3]); return true; }