| 
									
										
										
										
											2020-06-25 10:18:48 +00:00
										 |  |  | /** | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Run `yarn global add canvas` first | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const { createCanvas, loadImage } = require("canvas"); | 
					
						
							|  |  |  | const fs = require("fs"); | 
					
						
							|  |  |  | const path = require("path"); | 
					
						
							|  |  |  | const { fileURLToPath } = require("url"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | async function run() { | 
					
						
							|  |  |  |     console.log("Running"); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-14 10:14:15 +00:00
										 |  |  |     const fps = 14; | 
					
						
							|  |  |  |     const dimensions = 192; | 
					
						
							|  |  |  |     const beltBorder = 23.5; | 
					
						
							|  |  |  |     const lineSize = 5; | 
					
						
							| 
									
										
										
										
											2020-06-25 10:18:48 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     const borderColor = "#91949e"; | 
					
						
							|  |  |  |     const fillColor = "#d2d4d9"; | 
					
						
							|  |  |  |     const arrowColor = "#c0c2c7"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Generate arrow sprite
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-14 10:14:15 +00:00
										 |  |  |     const arrowW = 60; | 
					
						
							|  |  |  |     const arrowH = arrowW / 2; | 
					
						
							| 
									
										
										
										
											2020-06-25 10:18:48 +00:00
										 |  |  |     /** @type {HTMLCanvasElement} */ | 
					
						
							|  |  |  |     const arrowSprite = createCanvas(arrowW, arrowH); | 
					
						
							|  |  |  |     const arrowContext = arrowSprite.getContext("2d"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     arrowContext.quality = "best"; | 
					
						
							|  |  |  |     arrowContext.fillStyle = arrowColor; | 
					
						
							|  |  |  |     arrowContext.clearRect(0, 0, arrowW, arrowH); | 
					
						
							|  |  |  |     arrowContext.beginPath(); | 
					
						
							|  |  |  |     arrowContext.moveTo(0, arrowH); | 
					
						
							|  |  |  |     arrowContext.lineTo(arrowW / 2, 0); | 
					
						
							|  |  |  |     arrowContext.lineTo(arrowW, arrowH); | 
					
						
							|  |  |  |     arrowContext.closePath(); | 
					
						
							|  |  |  |     arrowContext.fill(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-14 10:14:15 +00:00
										 |  |  |     const promises = []; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-25 10:18:48 +00:00
										 |  |  |     // First, generate the forward belt
 | 
					
						
							|  |  |  |     for (let i = 0; i < fps; ++i) { | 
					
						
							|  |  |  |         /** @type {HTMLCanvasElement} */ | 
					
						
							|  |  |  |         const canvas = createCanvas(dimensions, dimensions); | 
					
						
							|  |  |  |         const context = canvas.getContext("2d"); | 
					
						
							|  |  |  |         context.quality = "best"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const procentual = i / fps; | 
					
						
							|  |  |  |         context.clearRect(0, 0, dimensions, dimensions); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         context.fillStyle = fillColor; | 
					
						
							|  |  |  |         context.strokeStyle = borderColor; | 
					
						
							| 
									
										
										
										
											2020-08-14 10:14:15 +00:00
										 |  |  |         context.lineWidth = lineSize; | 
					
						
							| 
									
										
										
										
											2020-06-25 10:18:48 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         context.beginPath(); | 
					
						
							|  |  |  |         context.rect(beltBorder, -10, dimensions - 2 * beltBorder, dimensions + 20); | 
					
						
							|  |  |  |         context.fill(); | 
					
						
							|  |  |  |         context.stroke(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const spacingBetweenArrows = (dimensions - 3 * arrowH) / 3; | 
					
						
							|  |  |  |         const spacingTotal = spacingBetweenArrows + arrowH; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (let k = 0; k < 5; ++k) { | 
					
						
							|  |  |  |             let y = dimensions - arrowH - (k - 1) * spacingTotal - procentual * spacingTotal; | 
					
						
							|  |  |  |             context.drawImage(arrowSprite, dimensions / 2 - arrowW / 2, y); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-14 10:14:15 +00:00
										 |  |  |         const out = fs.createWriteStream(path.join(__dirname, "built", "forward_" + i + ".png")); | 
					
						
							| 
									
										
										
										
											2020-06-25 10:18:48 +00:00
										 |  |  |         const stream = canvas.createPNGStream(); | 
					
						
							|  |  |  |         stream.pipe(out); | 
					
						
							| 
									
										
										
										
											2020-08-14 10:14:15 +00:00
										 |  |  |         promises.push(new Promise(resolve => stream.on("end", resolve))); | 
					
						
							| 
									
										
										
										
											2020-06-25 10:18:48 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Generate left and right side belt
 | 
					
						
							|  |  |  |     for (let i = 0; i < fps; ++i) { | 
					
						
							|  |  |  |         /** @type {HTMLCanvasElement} */ | 
					
						
							|  |  |  |         const canvas = createCanvas(dimensions, dimensions); | 
					
						
							|  |  |  |         const context = canvas.getContext("2d"); | 
					
						
							|  |  |  |         context.quality = "best"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const procentual = i / fps; | 
					
						
							|  |  |  |         const innerRadius = beltBorder; | 
					
						
							|  |  |  |         context.clearRect(0, 0, dimensions, dimensions); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         context.fillStyle = fillColor; | 
					
						
							|  |  |  |         context.strokeStyle = borderColor; | 
					
						
							| 
									
										
										
										
											2020-08-14 10:14:15 +00:00
										 |  |  |         context.lineWidth = lineSize; | 
					
						
							| 
									
										
										
										
											2020-06-25 10:18:48 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         context.beginPath(); | 
					
						
							|  |  |  |         context.moveTo(beltBorder, dimensions + 10); | 
					
						
							|  |  |  |         context.lineTo(beltBorder, dimensions - innerRadius); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const steps = 256; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const outerRadius = dimensions - 2 * beltBorder; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const originX = dimensions - innerRadius; | 
					
						
							|  |  |  |         const originY = dimensions - innerRadius; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const sqrt = x => Math.pow(Math.abs(x), 0.975) * Math.sign(x); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (let k = 0; k <= steps; ++k) { | 
					
						
							|  |  |  |             const pct = k / steps; | 
					
						
							|  |  |  |             const angleRad = Math.PI + pct * Math.PI * 0.5; | 
					
						
							|  |  |  |             const offX = originX + sqrt(Math.cos(angleRad)) * outerRadius; | 
					
						
							|  |  |  |             const offY = originY + sqrt(Math.sin(angleRad)) * outerRadius; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             context.lineTo(offX, offY); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         context.lineTo(dimensions + 10, beltBorder); | 
					
						
							|  |  |  |         context.lineTo(dimensions + 10, dimensions - beltBorder); | 
					
						
							|  |  |  |         context.lineTo(dimensions, dimensions - beltBorder); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (let k = 0; k <= steps; ++k) { | 
					
						
							|  |  |  |             const pct = 1 - k / steps; | 
					
						
							|  |  |  |             const angleRad = Math.PI + pct * Math.PI * 0.5; | 
					
						
							|  |  |  |             const offX = dimensions + Math.cos(angleRad) * innerRadius; | 
					
						
							|  |  |  |             const offY = dimensions + Math.sin(angleRad) * innerRadius; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             context.lineTo(offX, offY); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         context.lineTo(dimensions - beltBorder, dimensions + 10); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         context.closePath(); | 
					
						
							|  |  |  |         context.fill(); | 
					
						
							|  |  |  |         context.stroke(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Arrows
 | 
					
						
							|  |  |  |         const rotationalRadius = dimensions / 2 - arrowH / 2 + 0.5; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const circumfence = (rotationalRadius * Math.PI * 2) / 4; | 
					
						
							|  |  |  |         console.log("Circumfence:", circumfence, "px"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const remainingSpace = circumfence - 3 * arrowH + arrowH; | 
					
						
							|  |  |  |         console.log("Remainig:", remainingSpace); | 
					
						
							|  |  |  |         const spacing = remainingSpace / 3 + arrowH; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         console.log("Spacing: ", spacing); | 
					
						
							|  |  |  |         const angleSpacing = ((spacing / circumfence) * Math.PI) / 2; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (let i = 0; i < 5; ++i) { | 
					
						
							|  |  |  |             let angleRad = Math.PI + procentual * angleSpacing + (i - 1) * angleSpacing; | 
					
						
							|  |  |  |             const offX = dimensions - arrowH / 2 + Math.cos(angleRad * 0.995) * rotationalRadius; | 
					
						
							|  |  |  |             const offY = dimensions - arrowH / 2 + Math.sin(angleRad * 0.995) * rotationalRadius; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             angleRad = Math.max(Math.PI, Math.min(1.5 * Math.PI, angleRad)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             context.save(); | 
					
						
							|  |  |  |             context.translate(offX, offY); | 
					
						
							|  |  |  |             context.rotate(angleRad + Math.PI); | 
					
						
							|  |  |  |             context.drawImage(arrowSprite, -arrowW / 2, -arrowH / 2); | 
					
						
							|  |  |  |             context.restore(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         /** @type {HTMLCanvasElement} */ | 
					
						
							|  |  |  |         const flippedCanvas = createCanvas(dimensions, dimensions); | 
					
						
							|  |  |  |         const flippedContext = flippedCanvas.getContext("2d"); | 
					
						
							|  |  |  |         flippedContext.quality = "best"; | 
					
						
							|  |  |  |         flippedContext.clearRect(0, 0, dimensions, dimensions); | 
					
						
							|  |  |  |         flippedContext.scale(-1, 1); | 
					
						
							|  |  |  |         flippedContext.drawImage(canvas, -dimensions, 0, dimensions, dimensions); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-14 10:14:15 +00:00
										 |  |  |         const outRight = fs.createWriteStream(path.join(__dirname, "built", "right_" + i + ".png")); | 
					
						
							|  |  |  |         const streamRight = canvas.createPNGStream(); | 
					
						
							|  |  |  |         streamRight.pipe(outRight); | 
					
						
							| 
									
										
										
										
											2020-06-25 10:18:48 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-14 10:14:15 +00:00
										 |  |  |         const outLeft = fs.createWriteStream(path.join(__dirname, "built", "left_" + i + ".png")); | 
					
						
							| 
									
										
										
										
											2020-06-25 10:18:48 +00:00
										 |  |  |         const streamLeft = flippedCanvas.createPNGStream(); | 
					
						
							|  |  |  |         streamLeft.pipe(outLeft); | 
					
						
							| 
									
										
										
										
											2020-08-14 10:14:15 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         promises.push(new Promise(resolve => streamRight.on("end", resolve))); | 
					
						
							|  |  |  |         promises.push(new Promise(resolve => streamLeft.on("end", resolve))); | 
					
						
							| 
									
										
										
										
											2020-06-25 10:18:48 +00:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-08-14 10:14:15 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     console.log("Waiting for completion"); | 
					
						
							|  |  |  |     await Promise.all(promises); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Also wait a bit more
 | 
					
						
							|  |  |  |     await new Promise(resolve => setTimeout(resolve, 1000)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     console.log("Copying files to all locations"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Copy other files
 | 
					
						
							|  |  |  |     fs.copyFileSync( | 
					
						
							|  |  |  |         path.join(__dirname, "built", "forward_0.png"), | 
					
						
							|  |  |  |         path.join(__dirname, "..", "buildings", "belt_top.png") | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     fs.copyFileSync( | 
					
						
							|  |  |  |         path.join(__dirname, "built", "right_0.png"), | 
					
						
							|  |  |  |         path.join(__dirname, "..", "buildings", "belt_right.png") | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     fs.copyFileSync( | 
					
						
							|  |  |  |         path.join(__dirname, "built", "left_0.png"), | 
					
						
							|  |  |  |         path.join(__dirname, "..", "buildings", "belt_left.png") | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     console.log("Done!"); | 
					
						
							| 
									
										
										
										
											2020-06-25 10:18:48 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | run(); |