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