Last active
November 21, 2022 15:21
-
-
Save Sorebit/8c7c7a5191c1d8c4963f0adf9906db9d to your computer and use it in GitHub Desktop.
π§ This is a small water pond filled with digital deterministic fish.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"/> | |
<title>π§</title> | |
<style type="text/css"> | |
* {margin: 0; padding: 0;} | |
canvas {position: absolute; left: 0; top: 0;} | |
body {overflow: hidden;} | |
#main {background: #235d80; z-index: 1;} | |
</style> | |
<canvas id="main"></canvas> | |
<script src="main.js" defer></script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is a small water pond filled with digital deterministic fish. | |
// Their movement and colors are based on start parameters. | |
class Game { | |
constructor(particleCount, fullyDeveloped) { | |
this.tickNum = 0; | |
this.can = document.getElementById('main'); | |
this.ctx = this.can.getContext('2d'); | |
// Whether the fish should have their tails grown or grow them publicly | |
fullyDeveloped = fullyDeveloped || false; | |
this.particles = this.createParticles(particleCount, fullyDeveloped); | |
} | |
createParticles(N, fullyDeveloped) { | |
// Returns N new particles | |
let pts = []; | |
for (let i = 0; i < N; i++) { | |
pts.push( | |
new Particle( | |
Math.random() * Math.PI, // random spin | |
120 + (Math.random() - 0.5) * 30, // [90, 150] frames | |
fullyDeveloped, | |
) | |
); | |
} | |
return pts; | |
} | |
updateParticles() { | |
for (let i in this.particles) { | |
this.particles[i].update(this.tickNum) | |
} | |
} | |
drawParticle(pt) { | |
this.ctx.lineWidth = 7; | |
this.ctx.lineCap = 'round'; | |
for (let i = 0; i < pt.frames.length(); i++) { | |
const frame = pt.frames.at(i); | |
// Position on canvas | |
const cx = this.can.width * (frame.x); | |
const cy = this.can.height * (frame.y); | |
// | |
const l = (pt.frames.length() - i) / (pt.frames.length() + 1); | |
// Position of the segment | |
const spin = frame.spin; | |
const x = l * (pt.length / 2) * Math.cos(spin); | |
const y = l * (pt.length / 2) * Math.sin(spin); | |
const c = frame.color; | |
this.ctx.strokeStyle = `rgba(${c.r},${c.g},${c.b},0.2)`; | |
this.ctx.beginPath(); | |
// A line with it's middle on the particle spine | |
this.ctx.moveTo(cx + x, cy - y); | |
this.ctx.lineTo(cx - x, cy + y); | |
this.ctx.stroke(); | |
this.ctx.closePath(); | |
} | |
} | |
draw() { | |
// Clear screen | |
this.ctx.clearRect(0, 0, this.can.width, this.can.height); | |
// Draw every particle | |
for (let i in this.particles) { | |
this.drawParticle(this.particles[i]); | |
} | |
} | |
tick() { | |
this.updateParticles(); | |
this.draw(); | |
this.tickNum += 1; | |
requestAnimationFrame(this.tick.bind(this)); | |
} | |
}; | |
class Particle { | |
// So the particle is made up of segments like so | |
// 0: S | |
// 1: -S- | |
// 2: --S-- | |
// The S represents the spine, the hyphens represent the sides of the segment | |
constructor(spin, maxFrames, startFullyDeveloped) { | |
// maxFrames - how many segments the tail has | |
// START PARAMS | |
this.startSpin = spin; | |
const spinDir = Math.random() >= 0.5 ? 1 : -1; | |
this.spinConst = 0.005 + Math.random() * 0.05 * spinDir; | |
this.spinMod = 0.5 + Math.random() * 0.5; | |
this.waveOff = new Vec2( | |
Math.random() * Math.PI * 2, | |
Math.random() * Math.PI * 2 | |
) | |
this.baseColor = { | |
r: Math.min(240, 190 + Math.floor(Math.random() * 30 - 10)), | |
g: Math.min(240, 200 + Math.floor(Math.random() * 30 - 10)), | |
b: Math.min(240, 220 + Math.floor(Math.random() * 30 - 10)), | |
} | |
// The tail is remembered within frames. | |
this.length = 20 + Math.random() * 40; // How many pixels long is the tail | |
this.frames = new CappedList(maxFrames); | |
startFullyDeveloped = startFullyDeveloped || false; | |
if (startFullyDeveloped) { | |
// Start up by inserting frames | |
for (let i = 0; i < maxFrames; i++) { | |
this.update(maxFrames - i); | |
} | |
} | |
} | |
posAt(tickNum) { | |
const center = new Vec2(0.5, 0.5); | |
// Move in what direction exactly??? | |
const x = center.x + Math.sin(this.waveOff.x + tickNum * 0.002) * (0.25) * | |
(1 + (Math.sin(tickNum * 0.02) * 0.1)); | |
const y = center.y + Math.sin(this.waveOff.y + tickNum * 0.002) * 0.35; | |
return new Vec2(x, y); | |
} | |
spinAt(tickNum) { | |
return (this.startSpin + this.spinConst * tickNum) * this.spinMod; | |
} | |
colorAt(tickNum) { | |
const colorOff = Math.sin(this.spinAt(tickNum)) * 20; | |
// console.log(colorOff); | |
const r = Math.min(255, this.baseColor.r + colorOff); | |
const g = Math.min(255, this.baseColor.g + colorOff); | |
const b = Math.min(255, this.baseColor.b + colorOff); | |
return {r: r, g: g, b: b}; | |
} | |
frameAt(tickNum) { | |
// Update spin direction? | |
const spin = this.startSpin + this.spinConst * tickNum; | |
const pos = this.posAt(tickNum); | |
const color = this.colorAt(tickNum); | |
return { | |
spin: spin, | |
x: pos.x, y: pos.y, | |
color: color | |
// TODO sin length | |
}; | |
} | |
update(tickNum) { | |
const f = this.frameAt(tickNum) | |
// Remember frames | |
this.frames.add(f); | |
} | |
}; | |
// --- Utilities --------------------------------------------------------------- | |
class Vec2 { | |
constructor(x, y) { this.x = x; this.y = y; } | |
} | |
class CappedList { | |
constructor(cap) { | |
this.items = []; | |
this._cap = cap; | |
} | |
at(index) { | |
return this.items[index]; | |
} | |
add(item) { | |
this.items.push(item); | |
// Keep at most N latest frames | |
if (this.items.length > this._cap) { | |
this.items.shift(); | |
} | |
} | |
length() { | |
return this.items.length; | |
} | |
} | |
// --- Main -------------------------------------------------------------------- | |
function main() { | |
const game = new Game(8, false); | |
function handleWindowResize() { | |
game.can.width = window.innerWidth; | |
game.can.height = window.innerHeight; | |
} | |
window.onresize = handleWindowResize; | |
handleWindowResize(); | |
game.tick(); | |
} | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment