Q Sphere
to generate a new and unique variation
Every variart piece is a small program. This is the one that drew what you're looking at.
// A ball tiled like a football: the sphere is split into cells around
// evenly spread points, each cell is striped by parallel planes through a
// blue or yellow ramp, and the front half is projected onto the canvas.
var facets = intVariable('facets', 12, 40);
var bands = intVariable('bands', 4, 12);
var ground = '#d8d6d1';
var ramps = [
['#f5f4ef', '#c4dcef', '#8dbbe0', '#4f8cc6', '#23589a', '#122d57'],
['#f5f4ef', '#fbefa6', '#f7df52', '#efc72c', '#d8a92c', '#a88636']
];
var cx = width / 2;
var cy = height / 2;
var radius = 175;
function dot(a, b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; }
function sub(a, b) { return [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; }
function scale(a, k) { return [a[0] * k, a[1] * k, a[2] * k]; }
function unit(a) { return scale(a, 1 / Math.sqrt(dot(a, a))); }
function cross(a, b) {
return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
}
function rotate(p, ax, ay) {
var y = p[1] * Math.cos(ax) - p[2] * Math.sin(ax);
var z = p[1] * Math.sin(ax) + p[2] * Math.cos(ax);
var x = p[0] * Math.cos(ay) + z * Math.sin(ay);
return [x, y, -p[0] * Math.sin(ay) + z * Math.cos(ay)];
}
// Keep the part of polygon pts where p . n >= d.
function clip(pts, n, d) {
var out = [];
for (var i = 0; i < pts.length; i++) {
var a = pts[i];
var b = pts[(i + 1) % pts.length];
var da = dot(a, n) - d;
var db = dot(b, n) - d;
if (da >= 0) out.push(a);
if ((da >= 0) != (db >= 0)) {
out.push(sub(a, scale(sub(a, b), da / (da - db))));
}
}
return out;
}
// Chords become arcs: fill long edges with points pushed back onto the sphere.
function draw(pts, fill) {
if (pts.length < 3) return;
var flat = [];
for (var i = 0; i < pts.length; i++) {
var a = pts[i];
var b = pts[(i + 1) % pts.length];
var steps = Math.ceil(Math.sqrt(dot(sub(a, b), sub(a, b))) / 0.03);
for (var j = 0; j < steps; j++) {
var p = unit(sub(a, scale(sub(a, b), j / steps)));
flat.push(cx + p[0] * radius, cy - p[1] * radius);
}
}
s.polygon(flat).attr({fill: fill, stroke: fill, strokeWidth: 0.6});
}
var tiltX = getRandomArbitary(0, Math.PI * 2);
var tiltY = getRandomArbitary(0, Math.PI * 2);
var centres = [];
for (var i = 0; i < facets; i++) {
var y = 1 - 2 * (i + 0.5) / facets;
var r = Math.sqrt(1 - y * y);
var a = i * Math.PI * (3 - Math.sqrt(5));
centres.push(rotate([Math.cos(a) * r, y, Math.sin(a) * r], tiltX, tiltY));
}
var reach = 2.2 / Math.sqrt(facets);
var toViewer = [0, 0, 1];
var first = getRandomInt(0, 1);
s.rect(0, 0, width, height).attr({fill: ground});
for (var f = 0; f < facets; f++) {
var c = centres[f];
if (c[2] < -reach) continue;
// A generous cap around the centre, cut down to this cell.
var u = unit(cross(c, Math.abs(c[0]) < 0.9 ? [1, 0, 0] : [0, 1, 0]));
var v = cross(c, u);
var cell = [];
for (var k = 0; k < 64; k++) {
var t = k / 64 * Math.PI * 2;
var edge = [Math.cos(t) * Math.sin(1.2), Math.sin(t) * Math.sin(1.2), Math.cos(1.2)];
cell.push([
c[0] * edge[2] + u[0] * edge[0] + v[0] * edge[1],
c[1] * edge[2] + u[1] * edge[0] + v[1] * edge[1],
c[2] * edge[2] + u[2] * edge[0] + v[2] * edge[1]
]);
}
for (var g = 0; g < facets; g++) {
if (g != f) cell = clip(cell, sub(c, centres[g]), 0);
}
cell = clip(cell, toViewer, 0);
if (cell.length < 3) continue;
var ramp = ramps[(f + first) % 2];
var spin = getRandomArbitary(0, Math.PI * 2);
var axis = sub(scale(u, Math.cos(spin)), scale(v, -Math.sin(spin)));
var lo = Infinity, hi = -Infinity;
for (var q = 0; q < cell.length; q++) {
lo = Math.min(lo, dot(cell[q], axis));
hi = Math.max(hi, dot(cell[q], axis));
}
var step = reach / bands;
var from = Math.floor(lo / step) * step;
for (var b = 0; from + b * step < hi; b++) {
var strip = clip(cell, axis, from + b * step);
strip = clip(strip, scale(axis, -1), -(from + (b + 1) * step));
var shade = Math.floor((from + b * step + reach / 2) / reach * ramp.length);
draw(strip, ramp[Math.max(0, Math.min(ramp.length - 1, shade))]);
}
}
// Darken towards the rim so the disc reads as a ball.
var shadow = s.gradient('r(0.4, 0.35, 0.75)rgba(0,0,0,0)-rgba(0,0,0,0)-rgba(0,0,0,0.25)');
s.circle(cx, cy, radius + 1).attr({fill: shadow});
Copyright © 2014–2026 Logic and Matter. All rights reserved. Questions? Comments? hello@variart.io