diff --git a/sphere.js b/sphere.js
index 3b95e36..dc58d4b 100644
--- a/sphere.js
+++ b/sphere.js
@@ -1,69 +1,63 @@
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.createElement('canvas')
+const cvs = document.getElementById("cvs")
const ctx = cvs.getContext('2d')
-const RADIUS = 150
-const NB_SECTIONS = 6
-const LINE_WIDTH = 3
+const NB_SECTIONS = 6
+const SCALE = devicePixelRatio
-const SCALE = devicePixelRatio
-const width = RADIUS * 2 + 20
-const height = RADIUS * 2 + 20
-cvs.width = width * SCALE
-cvs.height = height * SCALE
-cvs.style.width = `${width }px`
-cvs.style.height = `${height}px`
+let RADIUS, width, height, LINE_WIDTH
-document.body.appendChild(cvs)
+const SPHERE_SIZE = 0.15 // % of viewport width
-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
+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'
}
-const X = vec(1, 0, 0)
-const Y = vec(0, 1, 0)
-const Z = vec(0, 0, 1)
+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)
-
- // original projection
- let a = x * ct + y * st
+ let a = x * ct + y * st
let px = y * ct - x * st
let py = cp * z - sp * a
let pz = cp * a + sp * z
-
- // --- add subtle right tilt (KEY PART) ---
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)
}
-// draw camera-facing section of sphere with normal v and offset o (-1 < o < 1)
const _p = vec()
function draw_section(n, o = 0) {
- let {x, y, z} = project(_p, n) // project normal on camera
- let a = atan2(y, x) // angle of projected normal -> angle of ellipse
- let ry = sqrt(1 - o * o) // radius of section -> y-radius of ellipse
- let rx = ry * abs(z) // x-radius of ellipse
+ 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)) // ellipse start angle
- let sb = z > 0 ? 2 * PI - sa : - sa // ellipse end angle
-
+ 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()
@@ -71,67 +65,40 @@ function draw_section(n, o = 0) {
const _n = vec()
function draw_arcs() {
- if (with_great_circles.checked)
- for (let i = NB_SECTIONS; i--;) {
- let a = i / NB_SECTIONS * Math.PI
- draw_section(vec.set(_n, cos(a), sin(a)))
- }
-
+ 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 * Math.PI
+ let a = (i + 1) / NB_SECTIONS * PI
draw_section(Z, cos(a))
- if (with_sections.checked) {
- draw_section(X, cos(a))
- draw_section(Y, cos(a))
- }
}
}
-const front_grad = ctx.createRadialGradient(0, 0, RADIUS * 2 / 3, 0, 0, RADIUS)
-const back_grad = ctx.createRadialGradient(0, 0, RADIUS * 2 / 3, 0, 0, RADIUS)
-
-front_grad.addColorStop(0, '#8bc8feff')
-front_grad.addColorStop(1, '#8bc8feff')
-back_grad.addColorStop(1, '#8bc8feff')
-back_grad.addColorStop(0, '#8bc8feff')
-
ctx.fillStyle = '#071c2dff'
ctx.lineCap = 'round'
-ctx.scale(SCALE, SCALE)
function render() {
requestAnimationFrame(render)
-
theta = performance.now() / 6000 * PI
phi = 1
-
- // 1. change the basis of the canvas
ctx.save()
- ctx.fillRect(0, 0, width, height)
+ ctx.clearRect(0, 0, width, height)
ctx.translate(width >> 1, height >> 1)
ctx.scale(1, -1)
-
- // 2. draw back arcs
- if (with_back.checked) {
- ctx.lineWidth = LINE_WIDTH / 2
- ctx.strokeStyle = with_gradient.checked ? back_grad : '#8bc8feff'
- ctx.scale(-1, -1) // the trick is to flip the canvas
- draw_arcs()
- ctx.scale(-1, -1)
- }
-
- // 3. draw sphere border
- ctx.strokeStyle = with_gradient.checked ? '#8bc8feff' : '#8bc8feff'
+ 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 * Math.PI)
+ ctx.arc(0, 0, RADIUS, 0, 2 * PI)
ctx.stroke()
-
- // 4. draw front arcs
ctx.lineWidth = LINE_WIDTH
- ctx.strokeStyle = with_gradient.checked ? front_grad : '#8bc8feff'
+ ctx.strokeStyle = '#8bc8feff'
draw_arcs()
-
ctx.restore()
}