// svgs const fingerprintSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400"><title>fingerprint</title><g id="finger-print"><path d="M160.38,222.11s12,84.05-46.83,130.88" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M158,200.5s6-38.43,43.22-37.23,42,37.23,42,37.23,3.6,32.42,3.6,45.62" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M178.39,280.94s6.23-46.79,1.2-74.44c-4.8-26.42,37.22-31.22,40.82-6s20.41,90.05-42,175.3" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M200,205.3S225.21,293,155.57,372.2" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M204.8,378.2S242,321.77,245.63,266.54" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M131.56,365s33.62-30,40.82-62.43" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M94.34,342.18s30-25.22,39.62-54a140.18,140.18,0,0,0,6-62.44c-1.2-9.6-7.2-43.22,15.61-63.64s39.63-19.21,51.63-18" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M227.62,148.87s90.05,40.82,3.6,226.93" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M258.83,367.39s20.42-37.22,21.62-56.43" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M285.25,290.55s10.81-57.63-2.4-102.06C257.63,84,105.14,111.64,117.15,213.7c4.8,31.22,2.4,37.23,1.2,43.23" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M77.53,329s33.62-28.82,36-49.23" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M64.32,311s28.82-20.41,31.22-46.83S93,219,94.34,198.1c1.2-19.22,10.8-50.43,39.62-75.65" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M153.17,111.64s62.44-32.41,116.47,14.41" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M284.05,141.66s56.43,66,4.8,211.33" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M52.31,295.35s36-30,24-67.24c-4.81-10.8-3.6-19.21-3.6-19.21" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M77.53,188.49C107.55,36,310.46,46.81,327.27,199.3" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M329.68,222.11S336.88,287,318.87,329" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M242.76,65.48c71.31,21.73,130.11,96.81,107.33,222.67" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M42.71,276.14s16.81-12,12-44.43c-4.8-14.4-1.2-38.42-1.2-38.42,14.39-100.7,94.14-144,168-132.66" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M298.91,66.41A170.26,170.26,0,0,1,370.5,205.3c0,1.83,0,3.65-.09,5.47" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M61,106.55A170.64,170.64,0,0,1,276.52,52.89" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M36,252.13A171.12,171.12,0,0,1,49,126.05" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/></g></svg>`; const phoneSvg = `<svg id="phone" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 683 1333"><title>phone-screen</title><path id="outer-path" d="M111.31,1309.27c-50.43,0-91.31-42.08-91.31-94L19.69,114.5c0-51.91,40.89-94,91.32-94H565c50.43,0,91.31,42.09,91.31,94L656,1215.27c0,51.92-40.89,94-91.32,94Z" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M619,115.5c0-.17,0-.33,0-.5,0-31.2-26-56.5-58-56.5H508s-7,1-10,4-3,4-3,10-9,32-30,32H212c-21,0-30-26-30-32s0-7-3-10-10-4-10-4H119c-32,0-58,25.3-58,56.5,0,.17,0,.33,0,.5v1102c0,.17,0,.33,0,.5,0,31.2,26,56.5,58,56.5H561c32,0,58-25.3,58-56.5,0-.17,0-.33,0-.5Z" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><circle cx="408" cy="73.5" r="11" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><rect x="300" y="68.5" width="80" height="11" rx="5.5" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/></svg>`; const fingerASvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 418 865"><title>finger-a</title><g id="finger-a"><path d="M192.33,238.36s29.38,17.34,68.57,8.09" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M192.33,105.42s10.88,25.43,39.18,26.59S274,100.8,275,93.86s2.18-16.18,2.18-16.18S259.81,53.4,231.51,54.56s-37,20.8-37,20.8Z" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M158.58,475.34s55.51,1.15,71.84,13.87,35.92-13.87,35.92-13.87" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M188,423.32s23.95-1.16,31.57,15" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M255.45,461.47s-45.71-12.72-52.24-5.78" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M211.92,391s43.53,3.47,49,43.93" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M255,808.5l12.43-97.34s35.91-228.89,35.91-271.66S299,141.26,296.81,119.29,283.75,41.84,238,36.06s-55.51,34.68-57.69,55.49-17.41,209.23-18.5,230S140.08,433.72,139,460.31s-27.21,150.28-44.62,178" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/></g></svg>`; const fingerBSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 418 865"><title>finger-b</title><g id="finger-b"><path d="M228.5,851.5s29-194,42-236,48-210,51-258,22-227,7-262-42-49-59-46-33,18-33,18l-32,134s-4,29-6,50-40,141-40,141-20,35-27,67-48,173-70,219" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M197.5,251.5s26,6,35,0" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M146.5,485.5s47,32,66,13" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M164.5,455.5s25,27,51,14" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M172.5,422.5s-23,20-19,45" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M242.5,62.57s37,.93,52,26.93l-5,23s-11,24-40,24S221,132.37,221,132.37" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/></g></svg>`; const fingerCSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 418 865"><title>finger-c</title><g id="finger-c"><path d="M186.14,851.73S231.86,700,232.83,692.2,350.54,375.08,351.51,291.42c0,0,45.72-122.57,37-180.93s-34.05-60.31-34.05-60.31S321.36,40.45,312.6,55s-28.21,73-28.21,73-40.85,107-39.88,113.81c0,0-52.53,89.5-67.12,136.19,0,0-21.4,25.29-30.16,42.8s-13.62,39.88-13.62,43.77S45.09,666.91,28.55,682.47" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M138.48,469.44s20.42,19.45,54.47,8.75" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M160.85,446.09s10.7,11.67,23.35,11.67" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M172.52,404.26s-21.4,33.08-20.42,53.5" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M250.34,242.78s18.49-2.91,26.27,1.95" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M284.39,128s49.61,1,64.2-41.83c0,0-9.83-24.74-36-27.93" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/></g></svg>`; const lockSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400"><title>lock</title><g id="lock"><path d="M299,317c0,36-30,33-30,33H124c-26,0-25-33-25-33V217s-1-33,25-33H269s30-3,30,33"/><path d="M200,65a79,79,0,0,0-79,79v64h29V144a50,50,0,0,1,100,0V313h29V144A79,79,0,0,0,200,65Z"/></g></svg>`; // colors const lightColors = { bg: "white", outline: "black", fingerprint: "black" }; const darkColors = { bg: "black", outline: "white", fingerprint: "red" }; const canvas = document.querySelector(".main-canvas"); const canvas2 = document.createElement("canvas"); const ctx = canvas.getContext("2d"); const ctx2 = canvas2.getContext("2d"); function main() { let resp = computeRes(); const glitchManager = new GlitchManager(resp); let textManager = new TextManager(resp, ["Setup DNA Unlock"]); let stateIndex = 0; const createState = (id, time) => ({ id, time }); const states = [ createState("fadein", 2500), createState("1stinstruction", 500), createState("finger-hold", 2000), createState("finger-off", 500), createState("needles", 3600), createState("glitch-beginning", 250), createState("demonic-scrawl", 6000), createState("lock", 500), createState("unlock", 400), createState("again?", 4000) ]; function onScreenChange() { Object.assign(resp, computeRes()); canvas.style.width = resp.styleSize.w + "px"; canvas.style.height = resp.styleSize.h + "px"; canvas.width = resp.rasterSize.w; canvas.height = resp.rasterSize.h; canvas2.width = resp.rasterSize.w; canvas2.height = resp.rasterSize.h; } onScreenChange(); window.onresize = onScreenChange; const phonePaths = svgStringToPathList(phoneSvg); const lockPaths = svgStringToPathList(lockSvg).map((x) => getPathPoints(x).map(toLV3) ); const fingerprintPaths = svgStringToPathList(fingerprintSvg).map((x) => getPathPoints(x).map(toLV3) ); const phoneFrontPoints = getPathPoints(phonePaths[0]).map(toLV3); const phoneBackPoints = getPathPoints(phonePaths[0]).map( (x) => new LV3(x.x, x.y, -30) ); const phoneDisplayPoints = getPathPoints(phonePaths[1]).map(toLV3); const phoneCenter = getPhoneCenter(phoneFrontPoints); const fingerAnim = new FingerAnimator( fingerASvg, fingerBSvg, fingerCSvg, resp ); const phoneParts = { phoneCenter, phoneBackPoints, phoneDisplayPoints, phoneFrontPoints }; let lines = Line.createInitialList(); const needles = Needle.createInitialList(); // lock colors const lockBlackColor = new LV3(0, 0, 0); const greenHex = hex2RGB("#17AB69"); const lockGreenColor = new LV3(greenHex.r, greenHex.g, greenHex.b); let time = 0; let prevDelta = 0; let lastBG = "white"; const body = document.body; function renderFunction(delta) { const dt = delta - prevDelta; time += dt; const state = states[stateIndex]; let colors = state.id === "demonic-scrawl" ? darkColors : lightColors; if (state.id === "glitch-beginning") { colors = Math.random() < 0.5 ? darkColors : lightColors; } if (colors.bg !== lastBG) { lastBG = colors.bg; body.style.backgroundColor = colors.bg; } const T = Math.min(time / state.time, 1); let firstCtx = ctx; if (state.id === "glitch-beginning" || state.id === "demonic-scrawl") { firstCtx = ctx2; } firstCtx.fillStyle = colors.bg; firstCtx.fillRect(0, 0, resp.rasterSize.w, resp.rasterSize.h); firstCtx.fillStyle = colors.outline; // firstCtx.font = '50px serif' // firstCtx.fillText(`${Math.floor(time)}`, 30, 60); // firstCtx.fillText(`${state.id}`, 30, 120); // firstCtx.fillText(`${T}`, 30, 180); // each state switch (state.id) { case "fadein": const clrs = Object.assign({}, colors, { outline: `rgba(0, 0, 0, ${T})` }); drawPhone(resp, ctx, 0, clrs, phoneParts); textManager.draw(ctx, T, clrs); break; case "1stinstruction": drawPhone(resp, ctx, 0, colors, phoneParts); drawFinger(resp, ctx, T, colors, fingerAnim); textManager.draw(ctx, T, colors); break; case "lock": drawPhone(resp, ctx, 0, colors, phoneParts); drawLock(lockPaths, resp, ctx, 0, 0, "black"); break; case "unlock": { const t = Math.floor(T * 10) / 10; const lockColor = lockGreenColor.sub(lockBlackColor).scale(t); lockColor.x = Math.floor(lockColor.x); lockColor.y = Math.floor(lockColor.y); lockColor.z = Math.floor(lockColor.z); drawPhone(resp, ctx, 0, colors, phoneParts); drawLock( lockPaths, resp, ctx, t, 0, `rgb(${lockColor.x}, ${lockColor.y}, ${lockColor.z})` ); } break; case "again?": drawPhone(resp, ctx, 0, colors, phoneParts); drawLock( lockPaths, resp, ctx, 1, 0, `rgb(${lockGreenColor.x}, ${lockGreenColor.y}, ${lockGreenColor.z})` ); textManager.draw(ctx, T, colors); break; case "finger-hold": drawPhone(resp, ctx, 0, colors, phoneParts); drawFinger( resp, ctx, 1, Object.assign({}, colors, { outline: `rgba(0, 0, 0, ${0.3})` }), fingerAnim ); drawFingerprint(resp, ctx, colors, 0, fingerprintPaths); textManager.draw(ctx, T, colors); break; case "finger-off": drawPhone(resp, ctx, T * -60, colors, phoneParts); drawFinger( resp, ctx, 1 - T, Object.assign({}, colors, { outline: `rgba(0, 0, 0, ${0.3})` }), fingerAnim ); drawFingerprint(resp, ctx, colors, T * -60, fingerprintPaths); drawNeedles(needles, resp, ctx, T * -60, dt); break; case "needles": drawPhone(resp, ctx, -60, colors, phoneParts); drawFingerprint( resp, ctx, Object.assign({}, colors, { fingerprint: `rgba(50, 50, 50, 0.3)` }), -60, fingerprintPaths ); drawNeedles(needles, resp, ctx, -60, dt); drawFinger(resp, ctx, 0, colors, fingerAnim); textManager.draw(ctx, T, colors); break; case "glitch-beginning": glitchManager.N = 4; glitchManager.refreshRate = 200; glitchManager.tick(dt); drawPhone(resp, ctx2, -60, colors, phoneParts); drawFingerprint(resp, ctx2, colors, -60, fingerprintPaths); drawNeedles(needles, resp, ctx2, -60, dt); drawGlitchedScreen(glitchManager, resp, canvas2, ctx, colors); break; case "demonic-scrawl": glitchManager.N = 12; glitchManager.refreshRate = 100; glitchManager.tick(dt); const angle = -60 + T * 60; drawPhone(resp, ctx2, angle, colors, phoneParts); drawFingerprint(resp, ctx2, colors, angle, fingerprintPaths); drawNeedles(needles, resp, ctx2, angle, dt); drawLines(lines, resp, ctx2, angle, dt); textManager.draw(ctx2, T, colors); drawGlitchedScreen(glitchManager, resp, canvas2, ctx, colors); break; } prevDelta = delta; if (time > state.time) { stateIndex = (stateIndex + 1) % states.length; switch (states[stateIndex].id) { case "demonic-scrawl": lines = Line.createInitialList(); textManager.textList = [ "Your privacy is", "our priority" ]; break; case "needles": textManager.textList = [`Your DNA is then`, "extracted and analysed"]; break; case "1stinstruction": case "finger-hold": textManager.textList = [`Place your finger`, "on the sensor"]; break; case "fadein": textManager.textList = ["Setup DNA Unlock"]; break; case "again?": textManager.textList = ["easy."]; break; } time = 0; } requestAnimationFrame(renderFunction); } requestAnimationFrame(renderFunction); } function drawPhone( resp, ctx, angle, colors, { phoneCenter, phoneBackPoints, phoneDisplayPoints, phoneFrontPoints } ) { const mat = buildMatrix([ LMat4.trans(resp.rasterSize.w / 2, resp.rasterSize.h / 2, 0), getPhoneRotationMat(resp, angle), LMat4.scale(3 * resp.anchor), LMat4.scale(0.2), LMat4.trans(-phoneCenter.x, -phoneCenter.y, 0) ]); // back of phone const pp3a = phoneBackPoints.map((x) => mat.multLV3(x)); ctx.fillStyle = colors.bg; ctx.strokeStyle = colors.outline; drawPath(ctx, pp3a, true); // front of phone const pp1a = phoneFrontPoints.map((x) => mat.multLV3(x)); ctx.fillStyle = colors.bg; ctx.strokeStyle = colors.outline; fillPath(ctx, pp1a, true); drawPath(ctx, pp1a, true); // draw inner part of phone ctx.fillStyle = colors.bg; ctx.strokeStyle = colors.outline; const pp2a = phoneDisplayPoints.map((x) => mat.multLV3(x)); drawPath(ctx, pp2a, true); } function drawFinger(resp, ctx, time2, colors, fingerAnimator) { const time = Math.max(0, Math.min(time2, 1)); const toFinger = (t) => { let finger = "b"; if (t <= 0.45) finger = "c"; else if (t >= 0.95) finger = "a"; if (t <= 0.5) { const pa = fingerAnimator.getTranslations("c"); const pb = fingerAnimator.getTranslations("b"); const p = pb .sub(pa) .scale(t / 0.5) .add(pa); return [p, finger]; } else { const pa = fingerAnimator.getTranslations("b"); const pb = fingerAnimator.getTranslations("a"); const p = pb .sub(pa) .scale((t - 0.5) / 0.5) .add(pa); return [p, finger]; } }; const [p, fingerId] = toFinger(time); const fingerMat = buildMatrix([ LMat4.trans(p.x, p.y, 0), LMat4.trans(resp.rasterSize.w / 2, resp.rasterSize.h / 2, 0), LMat4.scale(0.6 * resp.anchor), LMat4.trans(-230, -135, 0) ]); ctx.strokeStyle = colors.outline; fingerAnimator.getFinger(fingerId).forEach((a) => { const b = a.map((x) => fingerMat.multLV3(x)); drawPath(ctx, b, false); }); } function drawFingerprint(resp, ctx, colors, angle = 0, fingerprintPaths) { const fingerprintMatrix = buildMatrix([ LMat4.trans(resp.rasterSize.w / 2, resp.rasterSize.h / 2, 0), getPhoneRotationMat(resp, angle), LMat4.scale(0.23 * resp.anchor), LMat4.trans(-200, -200, 0) ]); ctx.strokeStyle = colors.fingerprint; fingerprintPaths.forEach((a) => { const b = a.map((x) => fingerprintMatrix.multLV3(x)); drawPath(ctx, b, false); }); } function drawNeedles(needles, resp, ctx, angle, delta) { const needlesMat = buildMatrix([ LMat4.trans(resp.rasterSize.w / 2, resp.rasterSize.h / 2, 0), getPhoneRotationMat(resp, angle), LMat4.scale(resp.anchor * 0.6) ]); needles.forEach((needle) => { needle.draw(ctx, needlesMat); needle.tick(delta * 0.05); }); } function drawLines(lines, resp, ctx, angle, delta) { ctx.strokeStyle = "red"; const linesMat = buildMatrix([ LMat4.trans(resp.rasterSize.w / 2, resp.rasterSize.h / 2, 0), getPhoneRotationMat(resp, angle), LMat4.scale(resp.anchor * 0.65) ]); lines.forEach((l) => l.draw(ctx, linesMat)); const newLines = []; lines.forEach((l) => { if (l.isComplete() && !l.spawned) { newLines.push(...l.createChildren()); } else { l.tick(delta * 0.003); } }); lines.push(...newLines); } function drawLock(lockPaths, resp, ctx, time, angle = 0, color) { const xOffset = resp.anchor * 18; const scale = 0.14; const yox = 380; const lockMat = buildMatrix([ LMat4.trans(resp.rasterSize.w / 2 + xOffset, yox * resp.anchor, 0), getPhoneRotationMat(resp, angle), LMat4.scale(scale * resp.anchor), LMat4.trans(-200, -200, 0) ]); ctx.fillStyle = color; fillPath( ctx, lockPaths[0].map((x) => lockMat.multLV3(x)), true ); const lockMat2 = buildMatrix([ LMat4.trans(resp.rasterSize.w / 2 + xOffset, yox * resp.anchor, 0), getPhoneRotationMat(resp, angle), LMat4.scale(scale * resp.anchor), // rotate LMat4.trans(65, 0, 0), LMat4.rotateY(time * 180), LMat4.trans(-65, 0, 0), //end rotate LMat4.trans(-200, -200, 0) ]); fillPath( ctx, lockPaths[1].map((x) => lockMat2.multLV3(x)), true ); } function drawGlitchedScreen(glitchManager, resp, canvasFrom, ctxTo, colors) { ctxTo.fillStyle = colors.bg; ctxTo.fillRect(0, 0, resp.rasterSize.w, resp.rasterSize.h); ctxTo.drawImage(canvasFrom, 0, 0, resp.rasterSize.w, resp.rasterSize.h); const cvWidth = resp.rasterSize.w; function createGlitch(ypos, sz = 2, N = 5) { if (ypos + N * 2 >= resp.rasterSize.h) return; for (let i = 0; i < N; i++) { const xCopyAmount = cvWidth - i * sz; ctxTo.drawImage( canvasFrom, 0, // x from ypos + i, // y from xCopyAmount, // copy width sz, // copy height i * sz, // x from ypos + i, // y from xCopyAmount, // copy over width sz // copy over height ); } const yp2 = N + ypos; for (let j = 0; j < N; j++) { const i = N - j; const xTo = N * sz - i * sz; const xCopyAmount = cvWidth - xTo; ctxTo.drawImage( canvasFrom, 0, // x from yp2 + i, // y from xCopyAmount, // copy width sz, xTo, // x to yp2 + i, // y to xCopyAmount, sz ); } } glitchManager.glitches.forEach((g) => createGlitch(g.ypos, g.sz, g.N)); } function getPathPoints(pathString) { const cp = compilePath(pathString); return computePoints(cp); } function getPhoneCenter(phoneFrontPoints) { let phoneCenter = new LV2(0, 0); const toLV2 = (x) => new LV2(x.x, x.y); let min = toLV2(phoneFrontPoints[0]), max = toLV2(phoneFrontPoints[0]); phoneFrontPoints.forEach((pp) => { const p = toLV2(pp); min = minLV2(min, p); max = maxLV2(max, p); }); phoneCenter = max.sub(min).scale(0.5); phoneCenter.x = Math.floor(phoneCenter.x); phoneCenter.y = Math.floor(phoneCenter.y); return phoneCenter; } function circle(ctx, p, rad, color = "white") { ctx.strokeStyle = color; ctx.beginPath(); ctx.arc(p.x, p.y, rad, 0, 2 * Math.PI); ctx.closePath(); ctx.stroke(); } function getPhoneRotationMat(resp, angle) { return buildMatrix([ LMat4.trans(-angle * resp.anchor * 2, angle * resp.anchor * 2, 0), LMat4.rotateY(angle), LMat4.rotateZ(-angle / 8) ]); } function computeRes() { const width = window.innerWidth; const height = window.innerHeight; const currentResp = width / height; const res = 2 / 3; let styleSize = { w: 0, h: 0 }; if (currentResp >= res) { // landscape styleSize.h = Math.min(800, height - 2); styleSize.w = Math.floor(res * styleSize.h); } else { // portait styleSize.w = Math.min(450, width - 2); styleSize.h = Math.floor(styleSize.w / res); } const rasterSize = { w: styleSize.w * window.devicePixelRatio, h: styleSize.h * window.devicePixelRatio }; const anchor = rasterSize.w * 0.0012; return { styleSize, rasterSize, anchor }; } class Line { constructor(p, v, len, decendent = 0) { this.p = p; this.v = v; this.len = len; this.decendent = decendent; this.spawned = false; this.t = 0; } draw(ctx, t) { const end = this.p.add(this.v.scale(this.len * this.t)); const a = t.multLV3(this.p); const b = t.multLV3(end); ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.stroke(); } tick(am = 0.2) { this.t += am; this.t = Math.min(1, this.t); } isComplete() { return this.t >= 1; } static choice(N) { return Math.floor(Math.random() * N); } createChildren() { if (this.decendent > 80) return []; // possibilities 0deg, -30deg, 30deg const numChildren = this.decendent < 3 ? Line.choice(3) + 1 : Line.choice(3) + Line.choice(3) - 1; const childrenMade = new Set(); const children = []; const end = this.p.add(this.v.scale(this.len)); for (let i = 0; i < numChildren; i++) { const deg = Line.choice(3); if (!childrenMade.has(deg)) { const p = end.copy(); let v = this.v.copy(); const angle = deg === 0 ? 0 : deg === 1 ? -30 : 30; const mat = LMat4.rotateZ(angle); v = mat.multLV3(v).unit(); const newLen = Math.max( this.len * (1 + (Math.random() * 0.25 - 0.1)), 10 ); if (Line.inPhoneDisplay(p)) { children.push(new Line(p, v, newLen, this.decendent + 1)); childrenMade.add(deg); } } } this.spawned = true; return children; } static inPhoneDisplay(p) { const mn = new LV2(-202, -458); const mx = new LV2(245, 548); return p.x >= mn.x && p.x <= mx.x && p.y >= mn.y && p.y <= mx.y; } static createInitialList() { const lines = []; const lineCenters = new LV3(0, 0, 0); const N = 25; const radius = 60; for (let i = 0; i < N; i++) { const angle = (360 * i) / N + (Math.random() - 0.5) * 5; const vec = new LV3(Math.cos(angle / 57.3), Math.sin(angle / 57.3), 0); const p = lineCenters.add(vec.scale(radius)); const LL = new Line(p, vec.unit(), 20 + Math.random() * 7); lines.push(LL); } return lines; } } class FingerAnimator { constructor(fa, fb, fc, resp) { this.pa = svgStringToPathList(fa).map((x) => getPathPoints(x).map(toLV3)); this.pb = svgStringToPathList(fb).map((x) => getPathPoints(x).map(toLV3)); this.pc = svgStringToPathList(fc).map((x) => getPathPoints(x).map(toLV3)); this.resp = resp; } getFinger(type) { switch (type) { case "c": return this.pc; case "b": return this.pb; case "a": default: return this.pa; } } getTranslations(type) { const resp = this.resp; switch (type) { case "b": return this.getTranslations("a").sub( new LV3(130 * resp.anchor, 90 * resp.anchor, 0) ); case "c": return this.getTranslations("b").sub( new LV3(80 * resp.anchor, 80 * resp.anchor, 0) ); case "a": default: return new LV2(0, 0); } } } class Needle { constructor(p, offsetPerc) { this.p = p; this.baseLen = 6; this.len = 120; this.bloodPercent = Math.random() * 0.3 + 0.15; // compute angle this.radians = Math.atan((this.baseLen * 0.5) / this.len); this.percentOut = 1; this.spd = Math.floor(50 + Math.random() * 40); this.t = Math.floor((offsetPerc || 0) * this.spd); } tick(am = 1) { this.t += am; const spd = this.spd; const hlv = spd * 0.5; const qt = spd * 0.25; this.t %= spd; if (this.t <= qt) { this.percentOut = this.t / qt; } else if (this.t <= hlv) { this.percentOut = 1 - (this.t - qt) / qt; } else { this.percentOut = 0; } // this.percentOut = (Math.sin(this.t * 0.2)+1) / 2; } computePoints() { const length = this.len * this.percentOut; const tip = this.p.add(new LV3(0, 0, length)); const b1 = this.p.add(new LV3(0, -length * Math.tan(this.radians), 0)); const b2 = this.p.add(new LV3(0, length * Math.tan(this.radians), 0)); let blood1, blood2; const bloodLength = this.len * this.bloodPercent; if (length >= this.bloodPercent * this.len) { blood1 = tip.add( new LV3( 0, -Math.sin(this.radians) * bloodLength, -Math.cos(this.radians) * bloodLength ) ); blood2 = tip.add( new LV3( 0, Math.sin(this.radians) * bloodLength, -Math.cos(this.radians) * bloodLength ) ); } return [this.p.copy(), b1, b2, tip, blood1, blood2].map( (x) => x && x.add(new LV3(0, 0, 0)) ); } draw(ctx, mat) { if (this.percentOut <= 0) return; const ptz = this.computePoints().map((x) => x ? mat.multLV3(x) : undefined ); const v = ptz[3].sub(ptz[0]).unit(); if (v.dot(new LV3(0, 0, -1)) > 0.6) { return; } ctx.fillStyle = "#8C9995"; fillPath(ctx, ptz.slice(1, 4), true); if (ptz[4] && ptz[5]) { ctx.fillStyle = "red"; fillPath(ctx, ptz.slice(3, 6), "red"); } } static createInitialList() { const needles = []; const needlesGen = [ { rad: 20, N: 5 }, { rad: 30, N: 10 }, { rad: 40, N: 15 } ]; needlesGen.forEach(({ rad, N }) => { for (let i = 0; i < N; i++) { const angle = (i / N) * 360; const vec = new LV3( Math.cos(angle / 57.3), Math.sin(angle / 57.3), 0 ).scale(rad); const n = new Needle(new LV3(vec.x, vec.y, 0), Math.random()); needles.push(n); } }); return needles; } } class GlitchManager { constructor(resp) { this.resp = resp; this.N = 10; this.glitches = []; this.t = 0; this.refreshRate = 200; this.refresh(); } tick(dt) { this.t += dt; if (this.t >= this.refreshRate) { this.refresh(); this.t -= this.refreshRate; } } refresh() { const resp = this.resp; const N = Math.random() * this.N; const newG = []; for (let i = 0; i < N; i++) { newG.push({ ypos: Math.floor(Math.random() * (resp.rasterSize.h - 1)), N: Math.floor( Math.floor(Math.random() * 5 * resp.anchor) * 2 + 8 * resp.anchor ), sz: Math.max( Math.floor(Math.random() * 3 * resp.anchor + 2 * resp.anchor), 1 ) }); } this.glitches = newG; } } class TextManager { constructor(resp, textList, multiline = true) { this.resp = resp; this.textList = textList; this.textIndex = 0; this.multiline = multiline; } draw(ctx, T, colors) { if (this.textList.length === 0) return; const resp = this.resp; const text = this.getText(T); if (!text) return; this.setupDraw(ctx, colors); if (!this.multiline) { ctx.fillText( text, resp.rasterSize.w / 2 - resp.anchor * text.length * 7.8, resp.rasterSize.h - resp.anchor * 120 ); } else { for (let i = 0; i < text.length; i++) { const t = text[i]; ctx.fillText( t, resp.rasterSize.w / 2 - resp.anchor * t.length * 7.8, resp.rasterSize.h - resp.anchor * 120 + resp.anchor * i * 42 ); } } } getText(T) { if (this.multiline) { return this.textList; } return this.textList[Math.floor(T * this.textList.length)]; } setupDraw(ctx, colors) { const resp = this.resp; ctx.fillStyle = colors.outline; ctx.font = `${ 36 * resp.anchor }px SF Pro Display,SF Pro Icons,Helvetica Neue,Helvetica,Arial,sans-serif`; } } const BZC = { /** * @param {number} n * @param {number} i */ nChooseI(n, i) { return this.factorial(n) / (this.factorial(i) * this.factorial(n - i)); }, /** * @param {number} n */ factorial(n) { let out = 1; for (let i = 1; i <= n; i++) out *= i; return out; }, /** * @param {number} t * @param {Array<LV3>} points * @returns {LV3} */ B(t, points) { let n = points.length - 1; let out = points[0].copy().scale(0); for (let i = 0; i < points.length; i++) { const p1 = Math.pow(1 - t, n - i); const p2 = Math.pow(t, i); const scalar = this.nChooseI(n, i) * p1 * p2; const v = points[i].scale(scalar); out = out.add(v); } return out; }, /** * @param {number} t * @param {Array<number>} points * @returns {number} */ timingFunction(t, points) { let n = points.length - 1; let out = points[0] * 0; for (let i = 0; i < points.length; i++) { const p1 = Math.pow(1 - t, n - i); const p2 = Math.pow(t, i); const scalar = this.nChooseI(n, i) * p1 * p2; const v = points[i] * scalar; out += v; } return out; } }; const Arc = { /** * @param {LV2} u * @param {LV2} v * @returns {number} */ angularDiff(u, v) { const sign = u.x * v.y - u.y * v.x < 0 ? -1 : 1; const mags = u.mag() * v.mag(); return sign * Math.acos(u.dot(v) / mags); }, /** * @param {number} rotation - rotation in angles * @returns {LMat3} */ createRotationMat(rotation) { return new LMat3([ Math.cos(rotation / 57.3), -Math.sin(rotation / 57.3), 0, Math.sin(rotation / 57.3), Math.cos(rotation / 57.3), 0, 0, 0, 1 ]); }, /** * @param {number} x1 start x pos * @param {number} y1 start y pos * @param {number} x2 end x pos * @param {number} y2 end y pos * @param {number} fA - large arc flag, 1 if use large arc, else 0 * @param {number} fS - sweep flag, 1 if use one side, else 0 * @param {number} rx - x-radius of arc * @param {number} ry - y-radius of arc * @param {number} rotationIn - rotation or arc in angles * @returns {[LV2, number, number, number, number]} center, angle1, angleDist, rx, ry */ computeVariables(x1, y1, x2, y2, fA, fS, rx, ry, rotationIn) { const rotation = rotationIn / 57.3; // in radians const _m = new LMat3([ Math.cos(rotation), Math.sin(rotation), 0, -Math.sin(rotation), Math.cos(rotation), 0, 0, 0, 1 ]); const _pc = new LV2(x1, y1).sub(new LV2(x2, y2)).scale(0.5); const pi = _m.multLV2(_pc); // pi is x1', y1' const primer = pi.x ** 2 / rx ** 2 + pi.y ** 2 / ry ** 2; let primerOverride = false; let scaledUp = false; if (primer > 1) { const oldRx = rx; const oldRy = ry; rx = Math.sqrt(primer) * rx; ry = Math.sqrt(primer) * ry; primerOverride = true; scaledUp = oldRx < rx && oldRy < ry; // scaledUp = false; } const scalar_p1 = rx ** 2 * ry ** 2 - rx ** 2 * pi.y ** 2 - ry ** 2 * pi.x ** 2; const scalar_p2 = rx ** 2 * pi.y ** 2 + ry ** 2 * pi.x ** 2; const prefix = fA === fS ? -1 : 1; const scalar = prefix * Math.sqrt(scaledUp ? 0 : scalar_p1 / scalar_p2); const _center = new LV2((rx * pi.y) / ry, -(ry * pi.x) / rx).scale(scalar); const _m2 = new LMat3([ Math.cos(rotation), -Math.sin(rotation), 0, Math.sin(rotation), Math.cos(rotation), 0, 0, 0, 1 ]); const offset = new LV2(x1, y1).add(new LV2(x2, y2)).scale(0.5); const center = _m2.multLV2(_center).add(offset); let angle1 = undefined; let angleDist = undefined; { // compute angular variables const a1 = this.angularDiff( new LV2(1, 0), new LV2((pi.x - _center.x) / rx, (pi.y - _center.y) / ry) ); let ad1 = this.angularDiff( new LV2((pi.x - _center.x) / rx, (pi.y - _center.y) / ry), new LV2((-pi.x - _center.x) / rx, (-pi.y - _center.y) / ry) ); const sc = 180 / Math.PI; ad1 = isNaN(ad1) ? 180 / sc : ad1; angle1 = a1 * sc; angleDist = (ad1 * sc) % 360; if (fS === 0 && angleDist > 0) angleDist -= 360; else if (fS === 1 && angleDist < 0) angleDist += 360; } return [center, angle1, angleDist, rx, ry]; }, /** * @param {LMat3} m * @param {number} rx * @param {number} ry * @param {number} angle * @param {LV2} center * @returns {LV2} */ getPoint(m, rx, ry, angle, center) { const scalar = Math.PI / 180; const _p = new LV2( rx * Math.cos(angle * scalar), ry * Math.sin(angle * scalar) ); return m.multLV2(_p).add(center); }, makeGenerator(start, end, radi, rotation, fA, fS) { const [center, angleStart, anglularDistance, rx, ry] = Arc.computeVariables( start.x, start.y, end.x, end.y, fA, fS, radi.x, radi.y, rotation ); const m = Arc.createRotationMat(rotation); // const dist = anglularDistance - angleStart; // console.log(anglularDistance, angleStart, '::') return (t) => { const i = angleStart + t * anglularDistance; return this.getPoint(m, rx, ry, i, center); }; } }; function svgStringToPathList(svg) { let rest = svg; const list = []; while (true) { const pathR = /\sd="([A-Za-z0-9\.,\s-]+)"/gm; const x = pathR.exec(rest); if (x && x.length >= 1) { list.push(x[1]); rest = rest.replace(x[0], ""); } else { break; } } return list; } /** * @param {Array<object>} compiled path * @param {number} N */ function computePoints(cp, N = 10) { const points = []; let last = undefined; let secondLast = undefined; for (let i = 0; i < cp.length; i++) { const a = cp[i]; const iz = (chars) => chars.includes(a.type); if (iz("ML")) { points.push(a.pts[0]); last = a.pts[0].copy(); secondLast = last.copy(); } else if (iz("Cc")) { const pts = [ last.copy(), ...a.pts.map((x) => { return a.type === "c" ? x.add(last) : x; }) ]; for (let i = 0; i <= N; i++) { const p = BZC.B(i / N, pts); points.push(p); } last = pts[3].copy(); secondLast = pts[2].copy(); } else if (iz("Aa")) { const start = last.copy(); const options = a.options; const end = a.type === "a" ? options.end.add(last) : options.end.copy(); const gen = Arc.makeGenerator( start, end, options.rad, options.rotation, options.fA, options.fS ); for (let i = 0; i <= N; i++) { const p = gen(i / N); points.push(p); } secondLast = end.copy(); last = end.copy(); } else if (iz("Hh")) { const hx = a.pts[0]; const p = new LV2(a.type === "h" ? hx + last.x : hx, last.y); points.push(p); last = p.copy(); secondLast = p.copy(); } else if (iz("Vv")) { const vy = a.pts[0]; const p = new LV2(last.x, a.type === "v" ? vy + last.y : vy); points.push(p); last = p.copy(); secondLast = p.copy(); } else if (iz("Ss")) { const pts = [ last.copy(), last.sub(secondLast).add(last), ...a.pts.map((x) => { return a.type === "s" ? x.add(last) : x; }) ]; for (let i = 0; i <= N; i++) { const p = BZC.B(i / N, pts); points.push(p); } last = pts[3].copy(); secondLast = pts[2].copy(); } } return points; } /** * @param {string} path * @returns {Array<object>} */ function compilePath(path) { const items = []; let i = 0; let mode = "space"; let item = undefined; const types = "MmcCQqLlVvhHsS"; let _ = 0; while (i < path.length) { _++; if (_ > 5000) break; const ch = path.charAt(i); const rest = path.substring(i); if (rest === "Z" || rest === "z") { item = { type: "L", pts: [items[0].pts[0].copy()] }; items.push(item); i++; continue; } switch (mode) { case "num-1": { const numRegex = /(-?[\d]*\.?[\d]*)[\s,]*/g; const [full, g1] = numRegex.exec(rest); if (g1) { const n1 = Number(g1); i += full.length; item.pts.push(n1); mode = "space"; } } break; case "num": { if (rest === "Z") { mode = "space"; break; } const numRegex = /(-?[\d]*\.?[\d]*)([,\s]|-)+([\d]*\.?[\d]*)[,\s]*/g; const [full, g1, g2, g3] = numRegex.exec(rest); if (g1 && g3) { const n1 = Number(g1); const n2 = Number(g3) * (g2 === "-" ? -1 : 1); const p = new LV2(n1, n2); item.pts.push(p); i += full.length; if (full.length === 0) return; if ("Mm".includes(item.type)) { mode = "space"; } else if ("LltT".includes(item.type)) { if (item.pts.length === 1) mode = "space"; } else if ("cC".includes(item.type)) { if (item.pts.length === 3) mode = "space"; } else if ("qQsS".includes(item.type)) { if (item.pts.length === 2) mode = "space"; } } else { i++; } } break; case "a-mode": { const list = []; for (let j = 0; j < 7; j++) { const numRegex = /(-?[\d]+\.?[\d]*)[\s,]*/g; const rest = path.substring(i); const ls = numRegex.exec(rest); const p = Number(ls[1]); list.push(p); i += ls[0].length; } item.options = { rad: new LV2(list[0], list[1]), rotation: list[2], fA: list[3], fS: list[4], end: new LV2(list[5], list[6]) }; mode = "space"; } break; case "space": { const numRegex = /$(-?[\d]*\.?[\d]*)/g; if (" ,".includes(ch)) i++; else if (types.includes(ch)) { mode = "vVhH".includes(ch) ? "num-1" : "num"; item = { type: ch, pts: [] }; items.push(item); i++; } else if ("Aa".includes(ch)) { mode = "a-mode"; item = { type: ch, pts: [] }; items.push(item); i++; } else if (numRegex.test(rest)) { const lastType = items.slice(-1)[0].type; item = { type: lastType, pts: [] }; items.push(item); mode = "vVhH".includes(lastType) ? "num-1" : "num"; } else if (ch === "Z" || ch === "z") { item = { type: "L", pts: [items[0].pts[0].copy()] }; i++; } } break; default: i++; } } return items; } function drawBezier(points, N) { ctx.beginPath(); for (let i = 0; i <= N; i++) { const p = BZC.B(i / N, points); if (i === 0) ctx.moveTo(p.x, p.y); else ctx.lineTo(p.x, p.y); } ctx.stroke(); } function fillBezier(points, N, color = { r: 255, g: 0, b: 0 }) { ctx.beginPath(); ctx.fillStyle = rgbColorToString(color); for (let i = 0; i <= N; i++) { const p = BZC.B(i / N, points); if (i === 0) ctx.moveTo(p.x, p.y); else ctx.lineTo(p.x, p.y); } ctx.closePath(); ctx.fill(); } function drawPoint(p, color = { r: 255, g: 0, b: 0 }) { ctx.beginPath(); ctx.strokeStyle = rgbColorToString(color); ctx.arc(p.x, p.y, 4, 0, 360); ctx.stroke(); } function drawPath(ctx, points, closePath = true) { ctx.beginPath(); ctx.moveTo(points[0].x, points[0].y); for (let i = 1; i < points.length; i++) { const p = points[i]; ctx.lineTo(p.x, p.y); } if (closePath) ctx.closePath(); ctx.stroke(); } function fillPath(ctx, points, color) { ctx.beginPath(); ctx.fillStyle = rgbColorToString(color); ctx.moveTo(points[0].x, points[0].y); for (let i = 0; i < points.length; i++) { const p = points[i]; ctx.lineTo(p.x, p.y); } ctx.closePath(); ctx.fill(); } function angleDifference(v1, v2) { return Math.acos(v1.dot(v2)) * (180 / Math.PI); } function computeSurfaceVectorDiff(path, vector = new LV3(0, 0, -1)) { const v1 = path[1].sub(path[0]).unit(); const v2 = path[path.length - 1].sub(path[0]).unit(); const normal = v1.cross(v2); const d = angleDifference(normal, vector); return Math.abs(d); } /** * @typedef {Object<string, number>} RgbColor * @property {number} r * @property {number} g * @property {number} b * @property {number} [a] */ /** * * @param {string} hex * @returns {RgbColor} */ function hex2RGB(hex) { const r = parseInt(hex.substring(1, 3), 16); const g = parseInt(hex.substring(3, 5), 16); const b = parseInt(hex.substring(5, 8), 16); return { r, g, b, a: 1 }; } /** * * @param {RgbColor} rgb * @param {number} scalar * @returns {RgbColor} */ function scaleColor(rgb, scalar) { return { r: rgb.r * scalar, g: rgb.g * scalar, b: rgb.b * scalar, a: rgb.a }; } /** * * @param {RgbColor} rgb * @param {number} scalar * @returns {RgbColor} */ function scaleOpacity(rgb, scalar) { return { r: rgb.r, g: rgb.g, b: rgb.b, a: rgb.a === undefined ? scalar : rgb.a * scalar }; } /** * @param {RgbColor} color * @returns {string} */ function rgbColorToString(color) { return `rgba(${color.r}, ${color.g}, ${color.b}, ${ color.a === undefined ? 1 : color.a })`; } function combineTransforms(arr) { return arr.reduce((p, c) => p.mult(c), LMat4.identity()); } /** * * @param {LMat4} T * @param {Array<LV3>} path * @returns {Array<LV3>} */ function transformMapper(T, path) { return path.map((p) => T.multLV3(p.copy())); } function buildMatrix(arr = []) { if (arr.length > 0) { let m = arr[0]; for (let i = 1; i < arr.length; i++) { m = m.mult(arr[i]); } return m; } return LMat4.identity(); } function maxLV2(a, b) { return new LV2(Math.max(a.x, b.x), Math.max(a.y, b.y)); } function minLV2(a, b) { return new LV2(Math.min(a.x, b.x), Math.min(a.y, b.y)); } function mostExtremeXY(pts) { let max = pts[0].copy(); let min = pts[0].copy(); for (let p of pts) { max = maxLV2(max, p); min = minLV2(min, p); } return [min, max]; } function toLV3(x) { return new LV3(x.x, x.y, 0); } const CONSTANTS = { rad2deg: 57.29577951308232 }; class LV2 { // x: number // y: number constructor(x, y) { this.x = x; this.y = y; } // return: string // eg. [1,2] toString() { return "[" + this.x + "," + this.y + "]"; } // return: LV2 copy() { return new LV2(this.x, this.y); } // o: { x, y } setAs(o) { this.x = o.x; this.y = o.y; } // x: number // y: number setValues(x, y) { this.x = x; this.y = y; } // o: { x: number, y: number } // return: LV2 add(o) { return new LV2(this.x + o.x, this.y + o.y); } // o: { x: number, y: number } iadd(o) { this.x += o.x; this.y += o.y; } // o: { x: number, y: number } // return: LV2 sub(o) { return new LV2(this.x - o.x, this.y - o.y); } // o: { x: number, y: number } isub(o) { this.x -= o.x; this.y -= o.y; } // s: number // return: LV2 scale(s) { return new LV2(this.x * s, this.y * s); } // s: number iscale(s) { this.x *= s; this.y *= s; } // s: number // return: LV2 div(s) { return new LV2(this.x / s, this.y / s); } // s: number idiv(s) { this.x /= s; this.y /= s; } // o: { x: number, y: number } // return: number dot(o) { return this.x * o.x + this.y * o.y; } // o: { x: number, y: number } // return: number dist(o) { var dx = this.x - o.x; var dy = this.y - o.y; return Math.sqrt(dx * dx + dy * dy); } // return: number mag() { return Math.sqrt(this.x * this.x + this.y * this.y); } // return: LV2 round() { return new LV2(Math.round(this.x), Math.round(this.y)); } // return: LV2 floor() { return new LV2(Math.floor(this.x), Math.floor(this.y)); } iround() { this.x = Math.round(this.x); this.y = Math.round(this.y); } ifloor() { this.x = Math.floor(this.x); this.y = Math.floor(this.y); } // return: LV2 unit() { var m = Math.sqrt(this.x * this.x + this.y * this.y); return new LV2(this.x / m, this.y / m); } iunit() { var m = Math.sqrt(this.x * this.x + this.y * this.y); this.x /= m; this.y /= m; } // target: LV2 // time: number [0-1] // return: LV2 interpolateTo(target, time) { var to = target.copy(); to.isub(this); to.iscale(time); to.iadd(this); return to; } // return: number getAngle() { var angle = CONSTANTS.rad2deg * Math.atan(this.y / this.x); if (this.x < 0.0) angle += 180.0; else if (this.y < 0.0) angle += 360.0; return angle; } // angle: number // return: LV2 static fromAngle(angle) { var rv = new LV2(0, 0); angle /= CONSTANTS.rad2deg; rv.x = Math.cos(angle); rv.y = Math.sin(angle); return rv; } } class LV3 { // x: number // y: number // z: number constructor(x, y, z) { this.x = x; this.y = y; this.z = z; } // return: string toString() { return "[" + this.x + "," + this.y + "," + this.z + "]"; } // return: LV3 copy() { return new LV3(this.x, this.y, this.z); } // o: LV3 setAs(o) { this.x = o.x; this.y = o.y; this.z = o.z; } // x: number // y: number // z: number setValues(x, y, z) { this.x = x; this.y = y; this.z = z; } // o: LV3 // return: LV3 add(o) { return new LV3(this.x + o.x, this.y + o.y, this.z + o.z); } // o: LV3 iadd(o) { this.x += o.x; this.y += o.y; this.z += o.z; } // o: LV3 // return: LV3 sub(o) { return new LV3(this.x - o.x, this.y - o.y, this.z - o.z); } // o: LV3 isub(o) { this.x -= o.x; this.y -= o.y; this.z -= o.z; } // s: number // return: LV3 scale(s) { return new LV3(this.x * s, this.y * s, this.z * s); } // s: number // return: LV3 iscale(s) { this.x *= s; this.y *= s; this.z *= s; } // s: number // return: LV3 div(s) { return new LV3(this.x / s, this.y / s, this.z / s); } // s: number idiv(s) { this.x /= s; this.y /= s; this.z /= s; } // o: LV3 // return: number dot(o) { return this.x * o.x + this.y * o.y + this.z * o.z; } // o: LV3 // return: LV3 cross(o) { return new LV3( this.y * o.z - this.z * o.y, this.z * o.x - this.x * o.z, this.x * o.y - this.y * o.x ); } // o: LV3 icross(o) { var x = this.x; var y = this.y; var z = this.z; this.x = y * o.z - z * o.y; this.y = z * o.x - x * o.z; this.z = x * o.y - y * o.x; } // o: LV3 // return: number dist(o) { var dx = this.x - o.x; var dy = this.y - o.y; var dz = this.z - o.z; return Math.sqrt(dx * dx + dy * dy + dz * dz); } // return: number mag() { return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); } // return: LV3 round() { return new LV3(Math.round(this.x), Math.round(this.y), Math.round(this.z)); } // return: LV3 floor() { return new LV3(Math.floor(this.x), Math.floor(this.y), Math.floor(this.z)); } iround() { this.x = Math.round(this.x); this.y = Math.round(this.y); this.z = Math.round(this.z); } ifloor() { this.x = Math.floor(this.x); this.y = Math.floor(this.y); this.z = Math.floor(this.z); } // return: LV3 unit() { var m = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); return new LV3(this.x / m, this.y / m, this.z / m); } iunit() { var m = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); this.x /= m; this.y /= m; this.z /= m; } } class LMat3 { // inp?: number[9] constructor(inp) { if (!inp) this.arr = [0, 0, 0, 0, 0, 0, 0, 0, 0]; else this.arr = inp; } // return: string toString() { return ( "|" + this.arr[0] + "," + this.arr[1] + "," + this.arr[2] + "|

" + "|" + this.arr[3] + "," + this.arr[4] + "," + this.arr[5] + "|

" + "|" + this.arr[6] + "," + this.arr[7] + "," + this.arr[8] + "|

" ); } // return: LMat3 copy() { return new LMat3(this.arr.slice()); } itranspose() { this.arr = [ this.arr[0], this.arr[3], this.arr[6], this.arr[1], this.arr[4], this.arr[7], this.arr[2], this.arr[5], this.arr[8] ]; } // return: LMat3 transpose() { return new LMat3([ this.arr[0], this.arr[3], this.arr[6], this.arr[1], this.arr[4], this.arr[7], this.arr[2], this.arr[5], this.arr[8] ]); } // m: LMat3 imult(m) { this.arr = [ this.arr[0] * m.arr[0] + this.arr[1] * m.arr[3] + this.arr[2] * m.arr[6], this.arr[0] * m.arr[1] + this.arr[1] * m.arr[4] + this.arr[2] * m.arr[7], this.arr[0] * m.arr[2] + this.arr[1] * m.arr[5] + this.arr[2] * m.arr[8], this.arr[3] * m.arr[0] + this.arr[4] * m.arr[3] + this.arr[5] * m.arr[6], this.arr[3] * m.arr[1] + this.arr[4] * m.arr[4] + this.arr[5] * m.arr[7], this.arr[3] * m.arr[2] + this.arr[4] * m.arr[5] + this.arr[5] * m.arr[8], this.arr[6] * m.arr[0] + this.arr[7] * m.arr[3] + this.arr[8] * m.arr[6], this.arr[6] * m.arr[1] + this.arr[7] * m.arr[4] + this.arr[8] * m.arr[7], this.arr[6] * m.arr[2] + this.arr[7] * m.arr[5] + this.arr[8] * m.arr[8] ]; } // m: LMat3 // return: LMat3 mult(m) { return new LMat3([ this.arr[0] * m.arr[0] + this.arr[1] * m.arr[3] + this.arr[2] * m.arr[6], this.arr[0] * m.arr[1] + this.arr[1] * m.arr[4] + this.arr[2] * m.arr[7], this.arr[0] * m.arr[2] + this.arr[1] * m.arr[5] + this.arr[2] * m.arr[8], this.arr[3] * m.arr[0] + this.arr[4] * m.arr[3] + this.arr[5] * m.arr[6], this.arr[3] * m.arr[1] + this.arr[4] * m.arr[4] + this.arr[5] * m.arr[7], this.arr[3] * m.arr[2] + this.arr[4] * m.arr[5] + this.arr[5] * m.arr[8], this.arr[6] * m.arr[0] + this.arr[7] * m.arr[3] + this.arr[8] * m.arr[6], this.arr[6] * m.arr[1] + this.arr[7] * m.arr[4] + this.arr[8] * m.arr[7], this.arr[6] * m.arr[2] + this.arr[7] * m.arr[5] + this.arr[8] * m.arr[8] ]); } // p: LV2 // return: LV2 multLV2(p) { return new LV2( p.x * this.arr[0] + p.y * this.arr[1] + 0 * this.arr[2], p.x * this.arr[3] + p.y * this.arr[4] + 0 * this.arr[5] ); } // p: LV3 // return: LV3 multLV3(p) { return new LV3( p.x * this.arr[0] + p.y * this.arr[1] + p.z * this.arr[2], p.x * this.arr[3] + p.y * this.arr[4] + p.z * this.arr[5], p.x * this.arr[6] + p.y * this.arr[7] + p.z * this.arr[8] ); } // return: LMat3 static zero() { return new LMat3(); } // return: LMat3 static identity() { return new LMat3([1, 0, 0, 0, 1, 0, 0, 0, 1]); } // scalar: number // return: LMat3 static scale(scalar) { return new LMat3([scalar, 0, 0, 0, scalar, 0, 0, 0, 1]); } // x: number // y: number // return: LMat3 static trans(x, y) { return new LMat3([1, 0, x, 0, 1, y, 0, 0, 1]); } // angle: number // return: LMat3 static rotate(angle) { angle *= 0.0174533; var cosine = Math.cos(angle); var sinus = Math.sin(angle); return new LMat3([cosine, -sinus, 0, sinus, cosine, 0, 0, 0, 1]); } // angle: number // return: LMat3 static rotateX(angle) { angle *= 0.0174533; var cosine = Math.cos(angle); var sinus = Math.sin(angle); return new LMat3([1, 0, 0, 0, cosine, -sinus, 0, sinus, cosine]); } // angle: number // return: LMat3 static rotateY(angle) { angle *= 0.0174533; var cosine = Math.cos(angle); var sinus = Math.sin(angle); return new LMat3([cosine, 0, sinus, 0, 1, 0, -sinus, 0, cosine]); } // angle: number // return: LMat3 static rotateZ(angle) { angle *= 0.0174533; var cosine = Math.cos(angle); var sinus = Math.sin(angle); return new LMat3([cosine, -sinue, 0, sinus, cosine, 0, 0, 0, 1]); } } class LMat4 { // inp?: [number * 16] constructor(inp) { if (inp === undefined) { this.arr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; } else this.arr = inp; } // return: string toString() { return ( "|" + this.arr[0] + "," + this.arr[1] + "," + this.arr[2] + "," + this.arr[3] + "|

" + "|" + this.arr[4] + "," + this.arr[5] + "," + this.arr[6] + "," + this.arr[7] + "|

" + "|" + this.arr[8] + "," + this.arr[9] + "," + this.arr[10] + "," + this.arr[11] + "|

" + "|" + this.arr[12] + "," + this.arr[13] + "," + this.arr[14] + "," + this.arr[15] + "|

" ); } // return: LMat4 copy() { return new LMat4(this.arr.slice()); } itranspose() { this.arr = [ this.arr[0], this.arr[4], this.arr[8], this.arr[12], this.arr[1], this.arr[5], this.arr[9], this.arr[13], this.arr[2], this.arr[6], this.arr[10], this.arr[14], this.arr[3], this.arr[7], this.arr[11], this.arr[15] ]; } // return: LMat4 transpose() { return new LMat4([ this.arr[0], this.arr[4], this.arr[8], this.arr[12], this.arr[1], this.arr[5], this.arr[9], this.arr[13], this.arr[2], this.arr[6], this.arr[10], this.arr[14], this.arr[3], this.arr[7], this.arr[11], this.arr[15] ]); } // m: LMat4 imult(m) { this.arr = [ this.arr[0] * m.arr[0] + this.arr[1] * m.arr[4] + this.arr[2] * m.arr[8] + this.arr[3] * m.arr[12], this.arr[0] * m.arr[1] + this.arr[1] * m.arr[5] + this.arr[2] * m.arr[9] + this.arr[3] * m.arr[13], this.arr[0] * m.arr[2] + this.arr[1] * m.arr[6] + this.arr[2] * m.arr[10] + this.arr[3] * m.arr[14], this.arr[0] * m.arr[3] + this.arr[1] * m.arr[7] + this.arr[2] * m.arr[11] + this.arr[3] * m.arr[15], this.arr[4] * m.arr[0] + this.arr[5] * m.arr[4] + this.arr[6] * m.arr[8] + this.arr[7] * m.arr[12], this.arr[4] * m.arr[1] + this.arr[5] * m.arr[5] + this.arr[6] * m.arr[9] + this.arr[7] * m.arr[13], this.arr[4] * m.arr[2] + this.arr[5] * m.arr[6] + this.arr[6] * m.arr[10] + this.arr[7] * m.arr[14], this.arr[4] * m.arr[3] + this.arr[5] * m.arr[7] + this.arr[6] * m.arr[11] + this.arr[7] * m.arr[15], this.arr[8] * m.arr[0] + this.arr[9] * m.arr[4] + this.arr[10] * m.arr[8] + this.arr[11] * m.arr[12], this.arr[8] * m.arr[1] + this.arr[9] * m.arr[5] + this.arr[10] * m.arr[9] + this.arr[11] * m.arr[13], this.arr[8] * m.arr[2] + this.arr[9] * m.arr[6] + this.arr[10] * m.arr[10] + this.arr[11] * m.arr[14], this.arr[8] * m.arr[3] + this.arr[9] * m.arr[7] + this.arr[10] * m.arr[11] + this.arr[11] * m.arr[15], this.arr[12] * m.arr[0] + this.arr[13] * m.arr[4] + this.arr[14] * m.arr[8] + this.arr[15] * m.arr[12], this.arr[12] * m.arr[1] + this.arr[13] * m.arr[5] + this.arr[14] * m.arr[9] + this.arr[15] * m.arr[13], this.arr[12] * m.arr[2] + this.arr[13] * m.arr[6] + this.arr[14] * m.arr[10] + this.arr[15] * m.arr[14], this.arr[12] * m.arr[3] + this.arr[13] * m.arr[7] + this.arr[14] * m.arr[11] + this.arr[15] * m.arr[15] ]; } // m: LMat4 // return: LMat4 mult(m) { return new LMat4([ this.arr[0] * m.arr[0] + this.arr[1] * m.arr[4] + this.arr[2] * m.arr[8] + this.arr[3] * m.arr[12], this.arr[0] * m.arr[1] + this.arr[1] * m.arr[5] + this.arr[2] * m.arr[9] + this.arr[3] * m.arr[13], this.arr[0] * m.arr[2] + this.arr[1] * m.arr[6] + this.arr[2] * m.arr[10] + this.arr[3] * m.arr[14], this.arr[0] * m.arr[3] + this.arr[1] * m.arr[7] + this.arr[2] * m.arr[11] + this.arr[3] * m.arr[15], this.arr[4] * m.arr[0] + this.arr[5] * m.arr[4] + this.arr[6] * m.arr[8] + this.arr[7] * m.arr[12], this.arr[4] * m.arr[1] + this.arr[5] * m.arr[5] + this.arr[6] * m.arr[9] + this.arr[7] * m.arr[13], this.arr[4] * m.arr[2] + this.arr[5] * m.arr[6] + this.arr[6] * m.arr[10] + this.arr[7] * m.arr[14], this.arr[4] * m.arr[3] + this.arr[5] * m.arr[7] + this.arr[6] * m.arr[11] + this.arr[7] * m.arr[15], this.arr[8] * m.arr[0] + this.arr[9] * m.arr[4] + this.arr[10] * m.arr[8] + this.arr[11] * m.arr[12], this.arr[8] * m.arr[1] + this.arr[9] * m.arr[5] + this.arr[10] * m.arr[9] + this.arr[11] * m.arr[13], this.arr[8] * m.arr[2] + this.arr[9] * m.arr[6] + this.arr[10] * m.arr[10] + this.arr[11] * m.arr[14], this.arr[8] * m.arr[3] + this.arr[9] * m.arr[7] + this.arr[10] * m.arr[11] + this.arr[11] * m.arr[15], this.arr[12] * m.arr[0] + this.arr[13] * m.arr[4] + this.arr[14] * m.arr[8] + this.arr[15] * m.arr[12], this.arr[12] * m.arr[1] + this.arr[13] * m.arr[5] + this.arr[14] * m.arr[9] + this.arr[15] * m.arr[13], this.arr[12] * m.arr[2] + this.arr[13] * m.arr[6] + this.arr[14] * m.arr[10] + this.arr[15] * m.arr[14], this.arr[12] * m.arr[3] + this.arr[13] * m.arr[7] + this.arr[14] * m.arr[11] + this.arr[15] * m.arr[15] ]); } // p: LV3 // return: LV3 multLV3(p) { return new LV3( p.x * this.arr[0] + p.y * this.arr[1] + p.z * this.arr[2] + this.arr[3], p.x * this.arr[4] + p.y * this.arr[5] + p.z * this.arr[6] + this.arr[7], p.x * this.arr[8] + p.y * this.arr[9] + p.z * this.arr[10] + this.arr[11] ); } // scalar: number // return: LMat4 static scale(scalar) { return new LMat4([ scalar, 0, 0, 0, 0, scalar, 0, 0, 0, 0, scalar, 0, 0, 0, 0, 1 ]); } // scalar: x // scalar: y // scalar: z // return: LMat4 static scaleXYZ(x, y, z) { return new LMat4([x, 0, 0, 0, 0, y, 0, 0, 0, 0, z, 0, 0, 0, 0, 1]); } // x: number // y: number // z: number // return: LMat4 static trans(x, y, z) { return new LMat4([1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z, 0, 0, 0, 1]); } // angle: number // return: LMat4 static rotateX(angle) { angle *= 0.0174533; var cosine = Math.cos(angle); var sinus = Math.sin(angle); return new LMat4([ 1, 0, 0, 0, 0, cosine, -sinus, 0, 0, sinus, cosine, 0, 0, 0, 0, 1 ]); } // angle: number // return: LMat4 static rotateY(angle) { angle *= 0.0174533; var cosine = Math.cos(angle); var sinus = Math.sin(angle); return new LMat4([ cosine, 0, sinus, 0, 0, 1, 0, 0, -sinus, 0, cosine, 0, 0, 0, 0, 1 ]); } // angle: number // return: LMat4 static rotateZ(angle) { angle *= 0.0174533; var cosine = Math.cos(angle); var sinus = Math.sin(angle); return new LMat4([ cosine, -sinus, 0, 0, sinus, cosine, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]); } // return: LMat4 static zero() { return new LMat4(); } // return: LMat4 static identity() { return new LMat4([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); } } main();

!