diff options
Diffstat (limited to 'main.js')
| -rw-r--r-- | main.js | 265 |
1 files changed, 69 insertions, 196 deletions
@@ -1,182 +1,62 @@ -const svg = document.getElementById('diagram'); -const arrows = Array.from(svg.querySelectorAll('.arrow')); -const preset = document.getElementById('layout-preset'); -const controls = Array.from(document.querySelectorAll('.axis-control')); -const steps = Array.from(document.querySelectorAll('.axis-control > input')); -const dirs = Array.from(document.querySelectorAll('.axis-control .dir input')); - -let state = [ 7, 2, null ]; -let last = 0; -let selected = null; - -const completeState = ([a, b, c]) => { - if (a == null) { - a = b - c; - } else if (b == null) { - b = c + a; - } else { - c = b - a; - } - return [a, b, c, -a, -b, -c]; -}; - -const updateValues = () => { - const full = completeState(state); - - full.forEach((val, i) => { - arrows[i].querySelector('text').textContent = val; - - if (i < steps.length) steps[i].value = Math.abs(val); - if (i < dirs.length) { - dirs[i].checked = val < 0; - dirs[i].disabled = val == 0; - } - }); - updateFocus(); -}; - -const updateFocus = () => { - const full = completeState(state); - - controls.forEach((control, i) => { - let className = 'axis-control'; - if (state[i] == null) className += ' driven'; - else if (i == selected) className += ' selected'; - if (state[i] == 0) className += ' dir-disabled'; - control.className = className; - }); - - full.forEach((val, i) => { - const positive = val > 0 || (val == 0 && i < 3); - - let className = 'arrow'; - if (state[i%3] == null) className += ' driven'; - if (!positive) className += ' negative'; - if (i%3 == selected) className += ' selected'; - arrows[i].className.baseVal = className; - }); -}; - -const select = (i) => { - if (state[i] != null) return false; - - const newState = [null, null, null]; - newState[i] = completeState(state)[i]; - newState[last] = state[last]; - state = newState; - last = i; - - return true; -}; - -arrows.forEach((arrow, i) => { - arrow.onclick = () => steps[i%3].focus(); -}); - -controls.forEach((control, i) => { - control.addEventListener('focusin', () => { selected = i; select(i); updateFocus(); }); - control.addEventListener('focusout', () => { selected = null; updateFocus(); }); -}); - -steps.forEach((input, i) => { - input.onchange = () => { - select(i); - state[i] = +input.value; - if (dirs[i].checked) state[i] = -state[i]; - preset.value = 'custom'; - updateValues(); - }; -}); - -dirs.forEach((input, i) => { - input.onchange = () => { - select(i); - state[i] = state[i] * -1; - preset.value = 'custom'; - updateValues(); - }; -}); - -let rot = 0; -let targetRot = 0; - -document.getElementById('turn-ccw').onclick = () => { - targetRot -= 30; -}; -document.getElementById('turn-cw').onclick = () => { - targetRot += 30; -}; - -preset.onchange = () => { - switch (preset.value) { - case 'custom': return; - case 'wicki-hayden': state = [ 7, 2, null ]; break; - case 'janko': state = [ 1, 2, null ]; break; - case 'harmonic': state = [ 7, 4, null ]; break; - case 'gerhard': state = [ 4, 3, null ]; break; - } - - last = state.findIndex(s => s != null); - updateValues(); -}; -preset.value = 'wicki-hayden'; -preset.onchange(); - -const bg = document.getElementById('canvas-bg').getContext('2d'); -const fg = document.getElementById('canvas-fg').getContext('2d'); +import * as layout from './layout.js'; +import * as pattern from './pattern.js'; const sqrt3 = Math.sqrt(3); const sqrt32 = sqrt3 / 2; -const size = 80; +const size = 50; const hs = size / 2; const hh = sqrt32 * size; -const hex2px = (q, r) => { +const hex2px = ([q, r]) => { const x = size * (3/2 * r); const y = -size * (sqrt32 * r + sqrt3 * q); return [x, y]; }; +const px2hex = ([x, y]) => { + const t1 = x / size; + const t2 = -y / hh / 2; -const mul = (x, y, mat) => { + const q = Math.floor( (Math.floor(-y / hh) + Math.floor(t2 - t1) + 2 ) / 3); + const r = Math.floor( (Math.floor(t1 - t2) + Math.floor(t1 + t2) + 2 ) / 3); + + return [q, r]; +} +const rotate = ([x, y], a) => { return [ - x * mat[0] + y * mat[1] + mat[2], - y * mat[3] + y * mat[4] + mat[5], + Math.cos(a) * x + Math.sin(a) * y, + -Math.sin(a) * x + Math.cos(a) * y, ]; }; +const hexagon = (ctx) => { + ctx.beginPath(); + ctx.moveTo(-hs, hh); + ctx.lineTo(hs, hh); + ctx.lineTo(size, 0); + ctx.lineTo(hs, -hh); + ctx.lineTo(-hs, -hh); + ctx.lineTo(-size, 0); + ctx.closePath(); +}; -const updateRotation = () => { - const delta = targetRot - rot; - if (Math.abs(delta) < 1) rot = targetRot; - else rot += delta * 0.1; - - if (rot < 60 && rot > -60) return; - - const full = completeState(state); - let driven = state.indexOf(null); +const main = document.querySelector('main'); +const bg = document.getElementById('canvas-bg').getContext('2d'); +const fg = document.getElementById('canvas-fg').getContext('2d'); - // cw rotation - while (rot >= 60) { - rot -= 60; - targetRot -= 60; - full.unshift(full.pop()); - driven++; - } +let mousePos = [0, 0]; +main.onmousemove = (e) => { + mousePos = [e.clientX - main.clientWidth/2, e.clientY - main.clientHeight/2]; +}; - // cw rotation - while (rot <= -60) { - rot += 60; - targetRot += 60; - full.push(full.shift()); - driven = (driven-1+3) % 3; +document.body.onkeydown = (e) => { + switch (e.key) { + case 'q': layout.turn(-1); break; + case 'e': layout.turn(1); break; } - - state = full.slice(0, 3); - state[driven%3] = null; - - updateValues(); }; +let lastCanvasSize = 0; + const updateBackground = (canvasSize) => { bg.strokeStyle = '#b9bdc1'; bg.strokeWidth = 1.5; @@ -190,16 +70,9 @@ const updateBackground = (canvasSize) => { const rMax = Math.min(rad, rad-q); for (let r = rMin; r <= rMax; r++) { bg.save(); - bg.translate(...hex2px(q, r)); + bg.translate(...hex2px([q, r])); - bg.beginPath(); - bg.moveTo(-hs, hh); - bg.lineTo(hs, hh); - bg.lineTo(size, 0); - bg.lineTo(hs, -hh); - bg.lineTo(-hs, -hh); - bg.lineTo(-size, 0); - bg.closePath(); + hexagon(bg); bg.stroke(); bg.restore(); @@ -207,17 +80,6 @@ const updateBackground = (canvasSize) => { } }; -const main = document.querySelector('main'); - -let mousePos = [0, 0]; -main.onmousemove = (e) => { - mousePos = [ - e.clientX - main.clientWidth/2, - e.clientY - main.clientHeight/2, - ]; -}; - -let lastCanvasSize = 0; const draw = () => { const width = window.innerWidth; const height = window.innerHeight; @@ -234,42 +96,53 @@ const draw = () => { fg.canvas.width = fg.canvas.width; - updateRotation(); + layout.update(); fg.translate(canvasSize/2, canvasSize/2); - fg.fillStyle = '#303336'; - fg.font = `${size*0.8}px sans-serif`; + fg.font = `${size*0.5}px sans-serif`; fg.textAlign = 'center'; + + fg.strokeStyle = '#ff0000'; + fg.strokeWidth = 5; + + + const rot = layout.getRot(); + const [qq, rr] = layout.getSteps(); - const rotRad = rot / 180 * Math.PI; + const steps = pattern.getSteps(); + const length = pattern.getLength(); - const [qq, rr] = completeState(state); const rad = Math.ceil(canvasSize / size / 3); for (let q = -rad; q <= rad; q++) { const rMin = Math.max(-rad, -q-rad); const rMax = Math.min(rad, rad-q); for (let r = rMin; r <= rMax; r++) { fg.save(); - fg.translate(...hex2px(q, r)); - - const val = q*qq + r*rr; - fg.rotate(-rotRad); - fg.fillText(val, 0, size/3); + fg.translate(...hex2px([q, r])); + + const note = q*qq + r*rr; + let step = note % length; + step = (step + length) % length; + + if (steps.includes(note)) { + fg.fillStyle = '#eeeeee'; + hexagon(fg); + fg.fill(); + } + + fg.rotate(-rot); + fg.fillStyle = '#303336'; + fg.fillText(step, 0, size/3); fg.restore(); } } - const mouse = [ - Math.cos(rotRad) * mousePos[0] + Math.sin(rotRad) * mousePos[1], - -Math.sin(rotRad) * mousePos[0] + Math.cos(rotRad) * mousePos[1], - ]; - - fg.arc(mouse[0], mouse[1], 5, 0, 2*Math.PI); - fg.fill() + // const mouse = px2hex(rotate(mousePos, rot)); + // fg.arc(...hex2px(mouse), 10, 0, 2*Math.PI); - document.body.style.setProperty('--global-rot', `${rot}deg`); + document.body.style.setProperty('--global-rot', `${rot}rad`); requestAnimationFrame(draw); }; |
