const {cos, sin, sqrt, acos, atan, atan2, abs, PI} = Math const clamp = (a, b, x) => x < a ? a : x > b ? b : x const cvs = document.getElementById("cvs") const ctx = cvs.getContext('2d') const NB_SECTIONS = 6 const SCALE = devicePixelRatio let RADIUS, width, height, LINE_WIDTH const SPHERE_SIZE = 0.15 // % of viewport width function resize() { const vw = window.innerWidth * SPHERE_SIZE RADIUS = Math.round((vw - 20) / 2) LINE_WIDTH = vw / 50 // adjust the divisor to taste width = RADIUS * 2 + 20 height = RADIUS * 2 + 20 cvs.width = width * SCALE cvs.height = height * SCALE cvs.style.width = `${SPHERE_SIZE * 100}vw` cvs.style.height = `${SPHERE_SIZE * 100}vw` ctx.setTransform(SCALE, 0, 0, SCALE, 0, 0) ctx.fillStyle = '#071c2dff' ctx.lineCap = 'round' } resize() window.addEventListener('resize', resize) // orientation of camera let theta, phi const vec = (x = 0, y = 0, z = 0) => ({x, y, z}) vec.set = (o, x = 0, y = 0, z = 0) => { o.x = x; o.y = y; o.z = z; return o } const Z = vec(0, 0, 1) function project(o, {x, y, z}) { let ct = cos(theta), st = sin(theta) let cp = cos(phi), sp = sin(phi) let a = x * ct + y * st let px = y * ct - x * st let py = cp * z - sp * a let pz = cp * a + sp * z let tilt = -0.2 let cr = cos(tilt), sr = sin(tilt) let tx = cr * px - sr * pz let tz = sr * px + cr * pz return vec.set(o, tx, py, tz) } const _p = vec() function draw_section(n, o = 0) { let {x, y, z} = project(_p, n) let a = atan2(y, x) let ry = sqrt(1 - o * o) let rx = ry * abs(z) let W = sqrt(x * x + y * y) let sa = acos(clamp(-1, 1, o * (1 / W - W) / rx)) let sb = z > 0 ? 2 * PI - sa : -sa ctx.beginPath() ctx.ellipse(x * o * RADIUS, y * o * RADIUS, rx * RADIUS, ry * RADIUS, a, sa, sb, z <= 0) ctx.stroke() } const _n = vec() function draw_arcs() { for (let i = NB_SECTIONS; i--;) { let a = i / NB_SECTIONS * PI draw_section(vec.set(_n, cos(a), sin(a))) } for (let i = NB_SECTIONS - 1; i--;) { let a = (i + 1) / NB_SECTIONS * PI draw_section(Z, cos(a)) } } ctx.fillStyle = '#071c2dff' ctx.lineCap = 'round' function render() { requestAnimationFrame(render) theta = performance.now() / 6000 * PI phi = 1 ctx.save() ctx.clearRect(0, 0, width, height) ctx.translate(width >> 1, height >> 1) ctx.scale(1, -1) ctx.lineWidth = LINE_WIDTH / 2 ctx.strokeStyle = '#8bc8feff' ctx.scale(-1, -1) draw_arcs() ctx.scale(-1, -1) ctx.strokeStyle = '#8bc8feff' ctx.lineWidth = LINE_WIDTH + 2 ctx.beginPath() ctx.arc(0, 0, RADIUS, 0, 2 * PI) ctx.stroke() ctx.lineWidth = LINE_WIDTH ctx.strokeStyle = '#8bc8feff' draw_arcs() ctx.restore() } requestAnimationFrame(render)