Sine wave cube in less than 1K
Remake of a cube rendered with sine waves
I came across this wonderful codepen by Jon Kantner, which in turn is a remake of this dribble by Mark Edwards/Bjango, so the most logical idea was to remake it again in less than 1024 bytes.
Roll in Signed Distance Functions and Ray Marching. A cube is probably the second most trivial 3d primitive after a sphere, so the SDF is relatively easy. Then rotate every frame, ray march to work out colours from a fixed light source, and set an array of brightness. Then sample that array to work out the intensity for drawing sine waves, where the intensity affects the amplitude of the wave.
The renderer on this page has 2x canvases, the pixel shader-esque version, and the sine wave version which is the final product. Press the ‘Toggle renderer’ button to… wait for it… toggle them. In the sine wave version, samples are taken every n lines (20px?), so it’s a known issue that 90%+ of the calculations are pointless in the y axis.
I used terser from the get go to minify code, but after getting everything rendering, just a 3d cube without any sine waves, terser could only terse it down to ~3.5K. Then began the hand minification, and after lots of passes I reached the 1024 byte goal. No real hacks were needed this time; often whilst code golfing we abuse undeclared variables and many, many other NSFW tricks.
This was definitely AI enhanced, using Cursor to get some of the 3d functions up and running since I’m lazy.
Compressed code (1011 bytes):
let e=.01,A=Math.abs,C=Math.cos,M=Math.max,m=Math.min,S=Math.sin,_=Math.sqrt,c=(e,t,l)=>{let r=A(e)-1,n=A(t)-1,a=A(l)-1,o=M(r,0),c=M(n,0),f=M(a,0);return _(o*o+c*c+f*f)+m(M(r,M(n,a)),0)},r=(e,t,l,r,M)=>{let n=C(M),a=S(M),o=e*n+l*a,c=-e*a+l*n,f=C(r),i=S(r);return[o,t*f-c*i,t*i+c*f]},n=(t,l,r)=>{let M=c(t+e,l,r)-c(t-e,l,r),n=c(t,l+e,r)-c(t,l-e,r),a=c(t,l,r+e)-c(t,l,r-e),o=_(M*M+n*n+a*a);return[M/o,n/o,a/o]},z=(e,t,l,M,a)=>{let o=0;for(;o<99;){let f=e*o,i=t*o,h=l*o-5,[m,s,S]=r(f,i,h,M,a),X=c(m,s,S);if(X<.002)return n(m,s,S);o+=X}},d=e=>{let t=5e-4*e,l=89e-5*e,r=400,n=[];for(let e=0;e<16e4;e++){let a=.5*(e%r/r*2-1),o=.5*(1-~~(e/r)/r*2),c=1,f=_(a*a+o*o+c*c);a/=f,o/=f,c/=f;let i=z(a,o,c,t,l);if(i){let[r,a,o]=i,c=C(-t),f=a*S(-t)+o*c,h=C(-l),m=S(-l),s=M(0,-(r*h+f*m));n[e]=.2+.8*s}}X.fillRect(0,0,r,r),X.strokeStyle="#fff";for(let t=0;t<20;t++){let l=20*(t+.5);X.beginPath();for(let t=0;t<r;t++){let M=(n[t+l*r]||0)*r*.03,a=l+S(.6*t+.004*e)*M;t?X.lineTo(t,a):X.moveTo(t,a)}X.stroke()}requestAnimationFrame(d)}
Expanded code:
let e = 0.01, // epsilon
A = Math.abs,
C = Math.cos,
M = Math.max,
m = Math.min,
S = Math.sin,
_ = Math.sqrt,
// cube:
c = (x, y, z) => {
let px = A(x) - 1; // last number is cube size
let py = A(y) - 1;
let pz = A(z) - 1;
let qx = M(px, 0);
let qy = M(py, 0);
let qz = M(pz, 0);
return _(qx * qx + qy * qy + qz * qz) + m(M(px, M(py, pz)), 0);
},
// rotate:
r = (x, y, z, rotX, rotY) => {
let cosY = C(rotY),
sinY = S(rotY),
x1 = x * cosY + z * sinY,
z1 = -x * sinY + z * cosY,
cosX = C(rotX),
sinX = S(rotX);
return [x1, y * cosX - z1 * sinX, y * sinX + z1 * cosX];
},
// Calculate normal using finite differences:
n = (x, y, z) => {
let dx = c(x + e, y, z) - c(x - e, y, z);
let dy = c(x, y + e, z) - c(x, y - e, z);
let dz = c(x, y, z + e) - c(x, y, z - e);
let len = _(dx * dx + dy * dy + dz * dz);
return [dx / len, dy / len, dz / len];
},
// Ray marching function:
z = (rx, ry, rz, rotationX, rotationY) => {
let t = 0;
while (t < 99) {
let x = rx * t,
y = ry * t,
z =
-5 + // camera position z
rz * t;
// Rotate the point to match camera rotation
let [px, py, pz] = r(x, y, z, rotationX, rotationY);
// Calculate distance to cube
let dist = c(px, py, pz);
if (dist < 0.002) {
// Hit! Calculate normal for shading
return n(px, py, pz);
}
t += dist;
}
// return nothing if no hit
},
// Render function:
d = (time) => {
let rotationX = time * 0.0005,
rotationY = time * 0.00089;
let size = 400;
let tanFovAspect = 0.5; // tan(field of vision / 2);
let intensities = [];
for (let i = 0; i < size * size; i++) {
let x = i % size;
let y = ~~(i / size);
// Convert pixel coordinates to normalized device coordinates
let ndcX = (x / size) * 2 - 1;
let ndcY = 1 - (y / size) * 2;
// Calculate ray direction
let rx =
tanFovAspect *
// aspect * // assuming an aspect of 1, ie square canvas, no need for this
ndcX,
ry = tanFovAspect * ndcY,
rz = 1;
// Normalize ray direction
let len = _(rx * rx + ry * ry + rz * rz);
rx /= len;
ry /= len;
rz /= len;
// Ray march
let result = z(rx, ry, rz, rotationX, rotationY);
// pixel esque shader pixel index:
// let idx = i * 4;
if (result) {
// Rotate normal back to world space (inverse: -X then -Y)
let [nx, ny, nz] = result;
let cosX = C(-rotationX),
sinX = S(-rotationX);
let z1 = ny * sinX + nz * cosX;
let cosY = C(-rotationY),
sinY = S(-rotationY);
let worldNormal = nx * cosY + z1 * sinY;
let dot = M(0, -worldNormal);
// lighting for pixel esque shader:
/*
let ambient = 0.2;
let intensity = ambient + (1 - ambient) * dot;
data[idx] = 100 * intensity; // R
data[idx + 1] = 200 * intensity; // G
data[idx + 2] = 255 * intensity; // B
data[idx + 3] = 255; // A
//*/
intensities[i] = 0.2 + 0.8 * dot;
} else {
// Background for pixel esque shader:
/*
let bgIntensity = 0.1;
data[idx] = bgIntensity * 50;
data[idx + 1] = bgIntensity * 50;
data[idx + 2] = bgIntensity * 60;
data[idx + 3] = 255;
//*/
// intensities[i] = 0; // no need, leave it empty
}
}
// pixel esque shader:
// ctx.putImageData(imageData, 0, 0);
// Draw horizontal sine waves
X.fillRect(0, 0, size, size);
X.strokeStyle = "#fff";
let numWaves = 20;
let waveSpacing = size / numWaves;
for (let w = 0; w < numWaves; w++) {
let y = (w + 0.5) * waveSpacing;
// Draw sine wave with amplitude
X.beginPath();
for (let x = 0; x < size; x++) {
const intense = intensities[x + y * size] || 0;
let amplitude = intense * size * 0.03;
let waveY = y + S(x * 0.6 + time * 0.004) * amplitude;
if (x) {
X.lineTo(x, waveY);
} else {
X.moveTo(x, waveY);
}
}
X.stroke();
}
requestAnimationFrame(d);
};