Created
July 3, 2023 07:21
-
-
Save prayerslayer/89be3dcf0b7083f6414f7175bc6aba37 to your computer and use it in GitHub Desktop.
P5js Solitaire
This file contains 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
const w = 400, | |
h = 400; | |
const cardWidth = 40, | |
cardAspectRatio = 64 / 89, | |
cardHeight = cardWidth / cardAspectRatio; | |
let trajectories = []; | |
function sleep(ms) { | |
return new Promise((resolve) => setTimeout(resolve, ms)); | |
} | |
function setup() { | |
createCanvas(w, h); | |
for (let i = 0; i < 100; i++) { | |
trajectories.push(getBouncePath()); | |
} | |
} | |
let last_ms = 0; | |
let trajIdx = 0, | |
pointIdx = 0; | |
function draw() { | |
background("white"); | |
let r = 0; | |
angleMode(RADIANS); | |
for (const [i, t] of trajectories.entries()) { | |
for (const [j, p] of t[1].entries()) { | |
if (i <= trajIdx) { | |
if (i < trajIdx || j <= pointIdx) { | |
if (i == trajIdx) { | |
fill(d3.interpolateCool(j / pointIdx)); | |
} else { | |
fill("white"); | |
} | |
const [x, y] = p; | |
push(); | |
translate(x, y); | |
rotate(r); | |
rect(-cardWidth / 2, -cardHeight / 2, cardWidth, cardHeight); | |
pop(); | |
r += PI / 2 ** 5; | |
//rotate(0) | |
} | |
} | |
} | |
} | |
const ms = millis(); | |
if (ms - last_ms > 50 && trajIdx < trajectories.length) { | |
pointIdx += 1; | |
if (pointIdx >= trajectories[trajIdx][1].length) { | |
pointIdx = 0; | |
trajIdx += 1; | |
} | |
//print(trajIdx, pointIdx) | |
} | |
} | |
function getPointOnEllipse(cx, cy, w, h, angle) { | |
const r = | |
(w * h) / | |
Math.sqrt((w * Math.sin(angle)) ** 2 + (h * Math.cos(angle)) ** 2); | |
const x = r * Math.cos(angle) + cx; | |
const y = r * Math.sin(angle) + cy; | |
return [x, y]; | |
} | |
/** | |
* Tries to get equidistant points on the defined ellipse arc. | |
* Since apparently we can't have analytical or exact solutions, | |
* and I don't want to get into numerical integration with JS, | |
* we do a simple, if ineffecient, scanning procedure: | |
* Move in very small increments along the arc, keep track of the | |
* cumulative distance and add the point to the result set where | |
* cumdist >= arcLength. | |
*/ | |
function getPointsOnEllipseSegment( | |
cx, | |
cy, | |
a, | |
b, | |
angle0, | |
angle1, | |
arcLength = 5 | |
) { | |
let thetaDelta = 0.0001; | |
if (angle1 < angle0) { | |
angle1 += 2 * PI; | |
} | |
let theta = angle0; | |
let cumDist = 0; | |
let arcPoints = [getPointOnEllipse(cx, cy, a, b, theta)]; | |
let ellPoints = [getPointOnEllipse(cx, cy, a, b, theta)]; | |
while (theta <= angle1) { | |
theta += thetaDelta; | |
const nextPoint = getPointOnEllipse(cx, cy, a, b, theta); | |
const d = calcDist(arcPoints[arcPoints.length - 1], nextPoint); | |
cumDist += d; | |
arcPoints.push(nextPoint); | |
if (cumDist >= arcLength) { | |
arcPoints = [nextPoint]; | |
ellPoints.push(nextPoint); | |
cumDist = 0; | |
} | |
} | |
return ellPoints; | |
} | |
function getBouncePath() { | |
const numBounces = random(4, 8); | |
const points = []; | |
const bounceTrajectory = []; | |
let dir = random() > 0.5; // right if true, left if false | |
const bounceWidth = Math.floor(random(cardWidth, w / 2)); | |
for (let i = 0; i < numBounces; i++) { | |
if (i === 0) { | |
let x = Math.floor(random(cardWidth, w - cardWidth)); | |
let y = Math.floor(random(cardHeight, h - cardHeight)); | |
bounceTrajectory.push([x, y]); | |
continue; | |
} | |
const lastPoint = bounceTrajectory[bounceTrajectory.length - 1]; | |
let x = dir ? lastPoint[0] + bounceWidth : lastPoint[0] - bounceWidth; | |
x = Math.floor(x); | |
let y = random(Math.min(h, lastPoint[1] + cardHeight), h); | |
y = Math.floor(y); | |
bounceTrajectory.push([x, y]); | |
if (y > h - cardHeight || i === numBounces - 1) { | |
break; | |
} | |
} | |
const keyframes = []; | |
for (const [i, p] of bounceTrajectory.entries()) { | |
const [x, y] = p; | |
const a = bounceWidth / 2; | |
const b = h - y; | |
const points = getPointsOnEllipseSegment(x, h, a, b, PI, 2 * PI); | |
if (!dir) { | |
points.reverse(); | |
} | |
keyframes.push(...points); | |
} | |
return [bounceTrajectory, keyframes]; | |
} | |
function calcDist(p0, p1) { | |
// Apparently this is faster than the built-in dist() function? | |
const [x0, y0] = p0; | |
const [x1, y1] = p1; | |
return Math.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment