/************************ Ordinal Three body Orbits ************************/ function calculateDistance(x1, y1, x2, y2) { return Math.sqrt(Math.pow((x2 - x1), 2) + Math.pow((y2 - y1), 2)); } class CanvasBasic { constructor(canvasId, dpr) { this.canvas = document.getElementById(canvasId); this.ctx = this.canvas.getContext("2d"); this.dpr = dpr } clear() { this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); } fade(coverColor) { this.ctx.fillStyle = coverColor this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); } setCanvas(size, zRatio) { let z = zRatio * size; this.canvas.width = size * this.dpr; this.canvas.height = size * this.dpr; this.canvas.style.width = size + 'px' this.canvas.style.height = size + 'px' this.canvas.style.position = "absolute"; this.canvas.style.left = "50%"; this.canvas.style.top = "50%"; this.canvas.style.position = "absolute"; this.canvas.style.transform = `translate3d(-50%,-50%,${z}px)` } resetCanvas(size, zRatio) { this.setCanvas(size, zRatio); this.clear(); } setContextStyle(color, shadowColor, shadowBlur, lineWidth) { this.ctx.strokeStyle = color; this.ctx.fillStyle = color; this.ctx.shadowColor = shadowColor; this.ctx.shadowBlur = shadowBlur; this.ctx.lineWidth = lineWidth * this.dpr; } } class Canvas3DDashboard extends CanvasBasic { constructor(canvasId, dpr) { super(canvasId, dpr); } drawRect(gridLen, numOfGrids, corner) { gridLen = gridLen * this.dpr; const size = gridLen * numOfGrids; if (typeof corner === 'undefined') { this.ctx.setLineDash([]) } else { let patten = Array(4).fill([(numOfGrids - corner * 2), corner * 2]).flat() patten.unshift(corner) this.ctx.setLineDash(patten.map(num => num * gridLen)) } this.ctx.strokeRect(this.canvas.width / 2 - size / 2, this.canvas.height / 2 - size / 2, size, size); } drawLine(gridLen, numOfGrids, corner) { gridLen = gridLen * this.dpr; const size = gridLen * numOfGrids this.ctx.setLineDash([gridLen * corner, gridLen * (1 - corner * 2), gridLen * corner, 0]) for (let i = 1; i < numOfGrids; i++) { this.ctx.moveTo(this.canvas.width / 2 - size / 2, this.canvas.height / 2 - size / 2 + gridLen * i); this.ctx.lineTo(this.canvas.width / 2 - size / 2 + size, this.canvas.height / 2 - size / 2 + gridLen * i); this.ctx.moveTo(this.canvas.width / 2 - size / 2 + gridLen * i, this.canvas.height / 2 - size / 2); this.ctx.lineTo(this.canvas.width / 2 - size / 2 + gridLen * i, this.canvas.height / 2 - size / 2 + size); } this.ctx.stroke(); } drawDecoration(gridLen, numOfGrids, deltaR) { gridLen = gridLen * this.dpr; const size = gridLen * numOfGrids const delta = gridLen * deltaR this.ctx.setLineDash([]); this.ctx.beginPath(); const drawSide = (sign) => { this.ctx.moveTo(this.canvas.width / 2 + sign * (size / 2 + delta), this.canvas.height / 2 - (size * 0.2 + delta)); this.ctx.lineTo(this.canvas.width / 2 + sign * (size / 2), this.canvas.height / 2 - (size * 0.2)); this.ctx.lineTo(this.canvas.width / 2 + sign * (size / 2), this.canvas.height / 2 + (size * 0.2)); this.ctx.lineTo(this.canvas.width / 2 + sign * (size / 2 + delta), this.canvas.height / 2 + (size * 0.2 + delta)); } drawSide(-1); // Left side drawSide(1); // Right side this.ctx.stroke(); } } class EffectBasic { constructor(effectsSettings, seed) { this.canvasHeight = window.innerHeight; this.dpr = window.devicePixelRatio || 1; this.seed = seed === undefined ? this.fetchBlockHeight() : seed; this.settings = effectsSettings; this.checkEra(this.seed) this.createDiv() this.UIBack = new Canvas3DDashboard('canvasUIBack', this.dpr); this.UIMid = new Canvas3DDashboard('canvasUIMid', this.dpr); this.UIFront = new Canvas3DDashboard('canvasUIFront', this.dpr); this.normalXFor3D = 0; this.normalYFor3D = 0; this.degFactorFor3D = 0; this.aimDegFactorFor3D = 0; this.mouseHasLeft = false; this.mouseInputPanel = document.getElementById('mouseInputPanel'); this.card3D = document.getElementById('card3D'); this.maxDis4card3DEffects = Math.min(window.innerHeight / 2, window.innerWidth / 2) this.width = window.innerWidth this.height = window.innerWidth } init() { this.fitCanvas() this.apply3DcardEffects() } checkEra(seed) { const difficultyAdjustmentPeriod = 2016; const halvingPeriod = 210000; const era = Math.floor((seed - 1) / difficultyAdjustmentPeriod); const epoch = Math.floor((seed - 1) / halvingPeriod); this.firstBlockAfterDiffAdjust = ((seed - 1) % difficultyAdjustmentPeriod === 0) this.firstBlockAfterHalving = ((seed - 1) % halvingPeriod === 0) this.shouldResetEachPeriod = !(this.firstBlockAfterDiffAdjust || this.firstBlockAfterHalving) console.log("era: ", era, 'havling epoch: ', epoch, 'firstBlockAfterDiffAdjust: ', this.firstBlockAfterDiffAdjust, "firstBlockAfterHalving: ", this.firstBlockAfterHalving, "shouldRestEachPeriod: ", this.shouldResetEachPeriod) this.color = this.settings.colorSchemes[era % this.settings.colorSchemes.length]; this.solidColor = `rgb(${this.color[0]},${this.color[1]},${this.color[2]})`; this.shallowColor = `rgba(${this.color[0]},${this.color[1]},${this.color[2]},0.7)`; this.dimColor = `rgba(${this.color[0]},${this.color[1]},${this.color[2]},0.5)`; this.loomColor = `rgba(${this.color[0]},${this.color[1]},${this.color[2]},0.2)`; this.energetic = this.firstBlockAfterHalving this.era = era this.epoch = epoch } fitCanvas(canvasHeight) { this.width = canvasHeight this.height = canvasHeight this.canvasHeight = canvasHeight this.maxDis4card3DEffects = Math.min(window.innerHeight / 2, window.innerWidth / 2) if (canvasHeight <= this.settings.canvasSizeThreshold) { this.useCard3DEffects = false this.closeDashboard() } else { this.useCard3DEffects = this.settings.sholdUseCard3DEffects this.draw3DDashboardDecoration(canvasHeight) } } onEachFrame() { this.handleMouseLeaveFor3Dcard() } apply3DcardEffects() { const updateEffect = (clientX, clientY) => { if (this.useCard3DEffects) { const dx = clientX - (this.card3D.getBoundingClientRect().left + this.card3D.clientWidth / 2); const dy = clientY - (this.card3D.getBoundingClientRect().top + this.card3D.clientHeight / 2); const angle = Math.atan2(dy, dx); const distance = Math.sqrt(dx * dx + dy * dy); this.normalXFor3D = Math.cos(angle); this.normalYFor3D = Math.sin(angle); this.aimDegFactorFor3D = Math.min(distance / this.maxDis4card3DEffects, 1); } } const projectionEnd = (e) => { this.energetic = this.firstBlockAfterHalving; } const projectionBegin = (e) => { if (e.touches.length == 3) { e.preventDefault(); this.energetic = true; this.aimDegFactorFor3D = 0; } else { this.energetic = this.firstBlockAfterHalving; } } const handleTouchMove = (e) => { if (e.touches.length <= 1) { e.preventDefault(); const firstTouch = e.touches[0]; updateEffect(firstTouch.clientX, firstTouch.clientY); } } this.mouseInputPanel.addEventListener('mousemove', (e) => { updateEffect(e.clientX, e.clientY); }); this.mouseInputPanel.addEventListener('touchmove', handleTouchMove, { passive: false }); this.mouseInputPanel.addEventListener('touchstart', projectionBegin, { passive: false }); this.mouseInputPanel.addEventListener('touchend', projectionEnd, false); this.mouseInputPanel.addEventListener('touchcancel', projectionEnd, false); this.mouseInputPanel.onmouseenter = this.mouseInputPanel.ontouchstart = () => { this.mouseHasLeft = false; }; this.mouseInputPanel.onmouseleave = this.mouseInputPanel.ontouchend = () => { this.mouseHasLeft = true; }; } handleMouseLeaveFor3Dcard() { if (this.useCard3DEffects) { if (this.mouseHasLeft) { this.degFactorFor3D = Math.max(this.degFactorFor3D - this.settings.anglePerFrame4card3DEffects, 0); } else { this.degFactorFor3D = this.degFactorFor3D + this.settings.anglePerFrame4card3DEffects < this.aimDegFactorFor3D ? this.degFactorFor3D + this.settings.anglePerFrame4card3DEffects : this.aimDegFactorFor3D; } this.card3D.style.transform = `rotate3d(${-this.normalYFor3D},${this.normalXFor3D},0,${this.degFactorFor3D * this.settings.max3DDegree}deg)`; } else { this.card3D.style.transform = `rotate3d(0,0,0,0deg)`; } } createDiv() { var divAll = document.createElement('div'); divAll.className = "ThreeBodyProblem-container isFullScreenWide isUnselectable"; divAll.style.cssText = `height: 100vh;background-color: ${this.settings.backgroundColor};`; var divPanel = document.createElement('div'); divPanel.id = "panel"; divPanel.style.cssText = "perspective:1500px;position:absolute;left:50%;top: 50%;transform: translate(-50%,-50%);"; var divCard3D = document.createElement('div'); divCard3D.id = "card3D"; divCard3D.style.cssText = "position: relative;width:0px;height:0px; transform-style: preserve-3d;"; var divMouseInputPanel = document.createElement('div'); divMouseInputPanel.id = "mouseInputPanel"; divMouseInputPanel.style.cssText = "position: absolute; width: 100%; height: 100vh;"; var createCanvas = function (id) { var canvas = document.createElement('canvas'); canvas.id = id; return canvas; }; var canvasIds = ['canvasUIBack', 'canvasUIMid', 'canvasUIFront', 'canvasTrace', 'canvasTail', 'canvasTop']; var canvases = canvasIds.map(createCanvas); for (var i = 0; i < canvasIds.length; i++) { divCard3D.appendChild(canvases[i]); } divPanel.appendChild(divCard3D); divAll.appendChild(divPanel); divAll.appendChild(divMouseInputPanel); document.body.appendChild(divAll); document.body.style.margin = "0"; document.body.style.padding = "0"; document.body.style.overflow = "hidden"; } closeDashboard() { this.UIBack.clear(); this.UIMid.clear(); this.UIFront.clear(); } draw3DDashboardDecoration(size) { var gridLen = size / 18 this.UIBack.resetCanvas(size, 0); this.UIBack.setContextStyle(this.loomColor, this.shallowColor, gridLen, gridLen * 0.1); this.UIBack.drawRect(gridLen, 14, 9); // this.UIBack.setContextStyle(this.loomColor, this.shallowColor, gridLen, gridLen * 0.1); // this.UIBack.drawRect(gridLen, 13); // this.UIBack.drawDecoration(gridLen, 14, 0.7); this.UIMid.resetCanvas(size, 0.03); this.UIMid.setContextStyle(this.loomColor, this.shallowColor, gridLen, gridLen * 0.03); // this.UIMid.drawRect(gridLen, 12); this.UIMid.setContextStyle(this.loomColor, this.shallowColor, gridLen, gridLen * 0.03); this.UIMid.drawLine(gridLen, 12, 0.15); this.UIFront.resetCanvas(size, 0.1); this.UIFront.setContextStyle(this.solidColor, this.shallowColor, gridLen, gridLen * 0.1); this.UIFront.drawRect(gridLen, 14, 9); } canvasNotSupported() { if (!(window.requestAnimationFrame && this.canvasTop.canvas && this.canvasTop.canvas?.getContext)) { console.log('Canvas not supported.') return true } if (!this.ctxTop) { console.log('Canvas not supported.') return true } return false } showMessage(title, content) { var canvasSize = this.canvasHeight var mainContainer = document.querySelector(".ThreeBodyProblem-container"); var container = document.createElement('div'); container.id = 'container'; container.style.cssText = `position:absolute;top:55%;left:50%;width:${canvasSize}px; height:${canvasSize}px;transform:translate3d(-50%,-50%,0px);` container.innerHTML = `

${title}

${content}

`; mainContainer.appendChild(container); var style = document.createElement('style'); style.innerHTML = ` body { width: 100%; height: 100vh; overflow: hidden; } .star-wars { display: flex; justify-content: center; position: relative; height: ${canvasSize}px; color: #ffffff; font-family: 'Star Jedi'; font-size: ${canvasSize * 0.6}%; perspective: 400px; text-align: center; } .crawl { position: relative; top: 0px; transform-origin: 50% 100%; animation: crawl ${55 / (canvasSize / 600)}s linear infinite; } .crawl > .title { font-size: 90%; text-align: center; } .crawl > .title h1 { margin: 0 0 100px; text-transform: uppercase; } @keyframes crawl { 0% { top: 0; transform: rotateX(60deg) translateY(0); } 100% { top: 0; transform: rotateX(60deg) translateY(-5500px); } } `; document.head.appendChild(style); } } class Effect extends EffectBasic { constructor(effectsSettings, seed) { super(effectsSettings, seed); this.canvasTail = new CanvasBasic("canvasTail", this.dpr) this.ctxTail = this.canvasTail.ctx this.canvasTrace = new CanvasBasic("canvasTrace", this.dpr) this.ctxTrace = this.canvasTrace.ctx this.canvasTop = new CanvasBasic("canvasTop", this.dpr) this.ctxTop = this.canvasTop.ctx this.drawPeriodCount = 0; this.shouldDrawTail = !this.shouldResetEachPeriod this.shouldBodyBlur = false this.pause = 0 this.init(); } setEra(seed) { this.checkEra(seed) this.shouldDrawTail = !this.shouldResetEachPeriod this.setOrbitStyle(this.canvasHeight) this.clearCanvas() this.draw3DDashboardDecoration(this.canvasHeight) } pose(nobody = false) { this.pause = 1000 this.closeDashboard() if (nobody) { this.canvasTop.clear() } } clearCanvas() { this.closeDashboard() this.canvasTail.clear() this.canvasTrace.clear() this.canvasTop.clear() } setOrbitStyle(canvasHeight) { this.canvasTail.setContextStyle(this.solidColor, null, 0, canvasHeight / 100) this.canvasTrace.setContextStyle(this.dimColor, null, 0, 0.5) this.canvasTop.setContextStyle(this.solidColor, this.shallowColor, 20, null) this.bodySize = Math.max(2, canvasHeight * 0.005) * this.dpr } fitCanvas(canvasHeight) { super.fitCanvas(canvasHeight); this.canvasTail.resetCanvas(canvasHeight, 0.075) this.canvasTrace.resetCanvas(canvasHeight, 0.075) this.canvasTop.resetCanvas(canvasHeight, 0.075) this.setOrbitStyle(canvasHeight) this.ctxTail.globalCompositeOperation = 'xor'; this.drawPeriodCount = 0 this.minimalDrawDist = canvasHeight / 6000 if (canvasHeight <= this.settings.canvasSizeThreshold) { this.shouldBodyBlur = false this.currentFPS = this.settings.minFPS this.zoomOut = this.settings.iframeZoomOut } else { this.shouldBodyBlur = true this.currentFPS = this.settings.maxFPS this.zoomOut = this.settings.fullScreenZoomOut } } onEachPeriod() { this.drawPeriodCount++; } onEachFrame() { if (this.shouldDrawTail) { this.canvasTail.fade(this.settings.tailCover); } super.onEachFrame() } draw(previous, positions, ibody) { if (positions.length == 0) return if (this.pause > 0) { this.pause--; return } this.drawBody(positions[positions.length - 1].x, positions[positions.length - 1].y, ibody) this.drawShadowLine(previous, positions, ibody) if (this.shouldDrawTail) { this.drawTail(previous, positions, ibody) } } drawTail(previous, positions, ibody) { this.ctxTail.beginPath() this.ctxTail.moveTo(previous.x, previous.y) for (let i = 0; i < positions.length; i++) { this.ctxTail.lineTo(positions[i].x, positions[i].y) } this.ctxTail.stroke() } drawShadowLine(previous, positions, ibody) { let px = previous.x let py = previous.y this.ctxTrace.beginPath() this.ctxTrace.moveTo(previous.x, previous.y) for (let i = 0; i < positions.length; i++) { if (calculateDistance(px, py, positions[i].x, positions[i].y) > this.minimalDrawDist || i == positions.length - 1) { this.ctxTrace.lineTo(positions[i].x, positions[i].y) px = positions[i].x py = positions[i].y } } this.ctxTrace.stroke() } drawBody(x, y, ibody) { if (ibody == 0) { this.canvasTop.clear() } this.ctxTop.beginPath() if (this.energetic) { let color = this.settings.colorSchemes[(this.era + 1 + ibody) % this.settings.colorSchemes.length]; this.ctxTop.fillStyle = `rgb(${color[0]},${color[1]},${color[2]})`; } else { this.ctxTop.fillStyle = this.solidColor } this.ctxTop.arc(x, y, this.bodySize, 0, 2 * Math.PI) this.ctxTop.fill() this.ctxTop.beginPath() this.ctxTop.fillStyle = "white" if (this.energetic) { this.ctxTop.arc(x, y, this.bodySize * 0.5, 0, 2 * Math.PI) } else { this.ctxTop.arc(x, y, this.bodySize * 0.7, 0, 2 * Math.PI) } this.ctxTop.fill() } } class EffectsSettings { constructor(maxFPS, max3DDegree) { this.backgroundColor = "rgb(22,22,22)" this.tailCover = "rgba(32,32,32,0.05)" this.max3DDegree = max3DDegree ?? 30 this.anglePerFrame4card3DEffects = 0.1 this.maxFPS = maxFPS ?? 60 this.minFPS = 30 this.fullScreenZoomOut = 1.4 this.iframeZoomOut = 1.0 this.frameLostTolerance = 10 this.sholdUseCard3DEffects = true this.canvasSizeThreshold = 200 this.colorSchemes = [ [170, 255, 0], [255, 219, 76], [65, 255, 181], [55, 214, 255], [255, 75, 100], [255, 128, 0], ] } } let effectsSettings = new EffectsSettings();