/* IDEAS - Press keys to summon different line formations - e.g. F to fill the screen with lines */ const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); const posFriction = .01; const rotFriction = .0001; const lengthFriction = 0; const minVelMagnMod = 4; const maxVelMagnMod = 30; const velMagnStepMod = [.1, .3, 1]; const velThrottleTreshold = .6; const minRotVels = [.02, .2]; const maxRotVels = [.1, .7]; const rotVelStep = .005; let minRotVel = minRotVels[0]; let maxRotVel = maxRotVels[0]; let rotTreshold = 0; for (let i = 0; i < (maxRotVel - minRotVel) / rotVelStep; i++) { rotTreshold += minRotVel + i * rotVelStep; } const minTrailLengthVel = -1.2; const maxTrailLengthVel = 1.3; const maxTrailAVel = .015; const trailAVelChangeSpeed = 40; const minMouseLineLength = 5; const mouseLineLengthMod = 2; const mouseLineLengthSpeed = [3, 20]; const rotateInterval = 60; const rightClickInterval = 5; const trailInterval = 2; const mouseTrailInterval = 1; const colorChange = .01; const colorSpectrum = 240; const mouseHueAmpl = 80; const mouseHueSpeed = 30; let minVelMagn; let maxVelMagn; const velMagnStep = [0, 0, 0]; let scaleMod; function updateCanvas() { scaleMod = Math.min(window.innerWidth, window.innerHeight) / 830; canvas.width = window.innerWidth; canvas.height = window.innerHeight; minVelMagn = scaleMod * minVelMagnMod; maxVelMagn = scaleMod * maxVelMagnMod; for (let i = 0; i < velMagnStep.length; i++) { velMagnStep[i] = scaleMod * velMagnStepMod[i]; } } updateCanvas(); window.addEventListener("resize", updateCanvas); let mouseX = 0; let mouseY = 0; let mouseUpdate = false; function mouseMove(e) { if (e instanceof MouseEvent) { mouseX = e.clientX; mouseY = e.clientY; } else if (e instanceof TouchEvent) { mouseX = e.touches[0].clientX; mouseY = e.touches[0].clientY; } mouseUpdate = true; } document.addEventListener("mousemove", mouseMove); document.addEventListener("touchmove", mouseMove); class Line { constructor(x, y, rot, velX, velY, aVel, width, length, lVel, col, ls) { this.x = x; this.y = y; this.rot = rot; this.velX = velX; this.velY = velY; this.aVel = aVel; this.width = width; this.length = length; this.lVel = lVel; this.color = col; this.ls = ls; this.originalLS = ls; } updateState() { this.x += this.velX; this.y += this.velY; this.rot += this.aVel; this.length += this.lVel; } updateVels() { if (this.velX > 0) { this.velX -= Math.min(this.velX, posFriction); } else if (this.velX < 0) { this.velX -= Math.max(this.velX, -posFriction); } if (this.velY > 0) { this.velY -= Math.min(this.velY, posFriction); } else if (this.velY < 0) { this.velY -= Math.max(this.velY, -posFriction); } if (this.aVel > 0) { this.aVel -= Math.min(this.aVel, rotFriction); } else if (this.aVel < 0) { this.aVel -= Math.max(this.aVel, -rotFriction); } if (this.lVel > 0) { this.lVel -= Math.min(this.lVel, lengthFriction); } else if (this.wVel < 0) { this.lVel -= Math.max(this.lVel, -lengthFriction); } } draw() { const lineDir = this.rot + Math.PI / 2; ctx.shadowColor = this.color; ctx.shadowBlur = 30; ctx.beginPath(); ctx.moveTo(this.x - Math.cos(lineDir) * this.length / 2, this.y - Math.sin(lineDir) * this.length / 2); ctx.lineTo(this.x + Math.cos(lineDir) * this.length / 2, this.y + Math.sin(lineDir) * this.length / 2); ctx.strokeStyle = this.color; ctx.lineWidth = this.width; ctx.globalAlpha = this.originalLS <= 0 ? 1 : Math.max(this.ls / this.originalLS, 0); ctx.stroke(); } isTooOld() { return (this.ls <= 0); } } function realAtan(x, y) { if (x == 0) { if (y > 0) return Math.PI / 2; else return -Math.PI / 2; } if (x < 0) return Math.atan(y / x) + Math.PI; else return Math.atan(y / x); } const mainLine = new Line(canvas.width/2, canvas.height/2, 0, -1, -1, .01, 5, 200, 0, "#cf0", 0); let time = 0; let lastRotate = 0; let lastRotateRC = 0; let lastTrail = 0; let lastMouseTrail = 0; let lastTrailAVelChange = 0; let lastTrailAVel = 0; let targetTrailAVel = 0; let targetPosX = canvas.width / 2; let targetPosY = canvas.height / 2; let originalDistHalf = 0; let rotVel = minRotVel; let velMagnitude = minVelMagn; const mouseLine = new Line(mouseX, mouseY, 0, 0, 0, 0, 5, minMouseLineLength, 0, "#cdf", 0); const mainColor = [72, 100, 50]; const lines = [[],[]]; let leftMouseDown = false; let rightMouseDown = false; let lastMouseX = 0; let lastMouseY = 0; function mouseDown(e) { if (e instanceof MouseEvent) { mouseX = e.clientX; mouseY = e.clientY; } else if (e instanceof TouchEvent) { mouseX = e.touches[0].clientX; mouseY = e.touches[0].clientY; } lastMouseX = mouseX; lastMouseY = mouseY; mouseLine.length = minMouseLineLength; mouseUpdate = true; if (e.button == 0 || e instanceof TouchEvent) { leftMouseDown = true; } else if (e.button == 2) { rightMouseDown = true; minRotVel = minRotVels[1]; maxRotVel = maxRotVels[1]; } } function mouseUp(e) { if (e instanceof MouseEvent) { if (e.button == 0) { leftMouseDown = false; } else if (e.button == 2) { rightMouseDown = false; minRotVel = minRotVels[0]; maxRotVel = maxRotVels[0]; } } else if (e instanceof TouchEvent && e.touches.length == 0) { leftMouseDown = false; } } document.addEventListener("mousedown", mouseDown); document.addEventListener("touchstart", mouseDown); document.addEventListener("mouseup", mouseUp); document.addEventListener("touchend", mouseUp); function main() { ctx.clearRect(0, 0, canvas.width, canvas.height); lines.forEach(lGroup => { for (let i = 0; i < lGroup.length; i++) { if (lGroup[i].isTooOld()) { lGroup.splice(i, 1); --i; } else { lGroup[i].draw(); lGroup[i].updateState(); lGroup[i].updateVels(); lGroup[i].ls -= 1; } } }); mainLine.color = `hsl(${mainColor[0]}, ${mainColor[1]}%, ${mainColor[2] + 50}%)`; mainLine.length = 10 * velMagnitude; mainLine.draw(); mainLine.updateState(); mainLine.rot = realAtan(mainLine.velX, mainLine.velY); if (leftMouseDown) { mouseLine.x = mouseX; mouseLine.y = mouseY; mouseLine.color = "#fff"; const mouseLengthDiff = minMouseLineLength + mouseLineLengthMod * Math.sqrt((mouseX - lastMouseX)**2 + (mouseY - lastMouseY)**2) - mouseLine.length; if (mouseLengthDiff >= 0) { mouseLine.length += Math.min(mouseLineLengthSpeed[1], mouseLengthDiff); } else { mouseLine.length -= Math.min(mouseLineLengthSpeed[0], -mouseLengthDiff); } if (mouseX != lastMouseX && mouseY != lastMouseY) { mouseLine.rot = realAtan(mouseX - lastMouseX, mouseY - lastMouseY); } mouseLine.draw(); if (time - lastMouseTrail >= mouseTrailInterval) { const aVel = (-.5 + Math.random()) * maxTrailAVel; const lVel = minTrailLengthVel + Math.random() * (maxTrailLengthVel - minTrailLengthVel); const velMod = -.05 + Math.random() * .05; const velX = velMod * (mouseX - lastMouseX); const velY = velMod * (mouseY - lastMouseY); const color = `hsl(${mainColor[0] + mouseHueAmpl * Math.sin(2 * Math.PI * time / mouseHueSpeed)}, 100%, 85%)`; lines[1].push(new Line(mouseLine.x, mouseLine.y, mouseLine.rot, velX, velY, aVel, mouseLine.width, mouseLine.length, lVel, color, 40)); } } if (rightMouseDown && mouseUpdate && time - lastRotateRC >= rightClickInterval) { targetPosX = mouseX; targetPosY = mouseY; originalDistHalf = Math.sqrt((targetPosX - mainLine.x)**2 + (targetPosY - mainLine.y)**2) / 2; lastRotateRC = time; mouseUpdate = false; } else if (!rightMouseDown && time - lastRotate >= rotateInterval) { targetPosX = canvas.width * Math.random(); targetPosY = canvas.height * Math.random(); originalDistHalf = Math.sqrt((targetPosX - mainLine.x)**2 + (targetPosY - mainLine.y)**2) / 2; lastRotate = time; } if (time - lastTrail >= trailInterval) { if (time - lastTrailAVelChange >= trailAVelChangeSpeed) { lastTrailAVel = targetTrailAVel; targetTrailAVel = 2 * (-.5 + Math.random()) * maxTrailAVel; lastTrailAVelChange = time; } const aVel = lastTrailAVel + (targetTrailAVel - lastTrailAVel) * (time - lastTrailAVelChange) / trailAVelChangeSpeed; const lVel = minTrailLengthVel + Math.random() * (maxTrailLengthVel - minTrailLengthVel); const velMod = -.15 + Math.random() * .15; const velX = velMod * mainLine.velX; const velY = velMod * mainLine.velY; const color = `hsl(${mainColor[0] + colorSpectrum * (velMagnitude - minVelMagn) / (maxVelMagn - minVelMagn)}, ${mainColor[1]}%, ${mainColor[2]}%)`; lines[0].push(new Line(mainLine.x, mainLine.y, mainLine.rot, velX, velY, aVel, mainLine.width, mainLine.length, lVel, color, 100)); lastTrail = time; } if (Math.sqrt((targetPosX - mainLine.x)**2 + (targetPosY - mainLine.y)**2) / 2 < 5 * scaleMod) { originalDistHalf = 90 * scaleMod; } const targetRot = (realAtan(targetPosX - mainLine.x, targetPosY - mainLine.y) + 2 * Math.PI) % (2 * Math.PI); const currentRot = (mainLine.rot + 2 * Math.PI) % (2 * Math.PI); let deltaRot = (targetRot - currentRot + 2 * Math.PI) % (2 * Math.PI); if (deltaRot > Math.PI) { deltaRot -= 2 * Math.PI; } if (Math.abs(deltaRot) > rotTreshold && rotVel < maxRotVel) { rotVel += rotVelStep; } else if (Math.abs(deltaRot) < rotTreshold) { rotVel = Math.min(Math.max(rotVel - rotVelStep, minRotVel), Math.abs(deltaRot)); } if (deltaRot < 0) { mainLine.velX = velMagnitude * Math.cos(currentRot - Math.min(rotVel, -deltaRot)); mainLine.velY = velMagnitude * Math.sin(currentRot - Math.min(rotVel, -deltaRot)); } else { mainLine.velX = velMagnitude * Math.cos(currentRot + Math.min(rotVel, deltaRot)); mainLine.velY = velMagnitude * Math.sin(currentRot + Math.min(rotVel, deltaRot)); } const targetDist = Math.sqrt((targetPosX - mainLine.x)**2 + (targetPosY - mainLine.y)**2); if (targetDist > originalDistHalf && velMagnitude < maxVelMagn) { velMagnitude += velMagnStep[0]; } else if (targetDist < originalDistHalf) { velMagnitude = Math.max(velMagnitude - (((velMagnitude - minVelMagn) / (maxVelMagn - minVelMagn) > velThrottleTreshold) ? velMagnStep[2] : velMagnStep[1]), minVelMagn); } mainColor[0] += colorChange; lastMouseX = mouseX; lastMouseY = mouseY; ++time; requestAnimationFrame(main); } main();