memoscan
← all memos

Memo 0xaee40627…b9a7e2 on Ethereum

Firmament```javascript "it rests among the stars / gathered up in the furthest firmament"; let canvas, // p5js canvas. state; // Keep all shared state in one place. //-[ setup functions ]--------------------------------------------------------// // Creates the p5js canvas and disables looping. // The rest of the setup is done when the Immutables properties are received. p.setup = _ => { canvas = p.createCanvas(1, 1); canvas.style("display", "block"); p.frameRate(10); p.noLoop(); }; // Responds to the properties provided by Immutables. p.myCustomRedrawAccordingToNewPropsHandler = properties => { if (!properties.transactionHash) return; // Check if existing state already matches the new properties. if ( state && state.transactionHash == properties.transactionHash && state.editionId == properties.editionId ) { state.square = properties.square; p.windowResized(); return; } // Pull bits from the hash to seed the RNG. p.randomSeed("0x" + properties.transactionHash.substr(2 + 8 * (properties.editionId % 8), 8)); // Intialize the state. state = { ...properties, animate: properties.square == 1, progress: 0, }; setStyling(); placeForegroundStars(); drawConstellation(); placeBackgroundStars(); addDust(); describe(); // Resize the window and turn looping on or off as needed. p.windowResized(); state.animate ? p.loop() : (p.noLoop(), p.redraw()); }; p.windowResized = _ => { if (!state) return; const minDim = p.min(p.windowWidth, p.windowHeight) * .8 / state.square | 0; if (minDim === p.width) return; p.resizeCanvas(minDim, minDim); state.resize = true; }; p.keyTyped = _ => { if (!state || state.square != 1) return; if (p.key == "A") { if (state.animate) { state.animate = false; p.noLoop(); p.redraw(); } else { state.animate = true; state.progress = 0; p.loop(); } } if (p.key == "B") { state.simple = !state.simple; } if (p.key == "C") { state.clear = !state.clear; } }; const setStyling = _ => { // Determine star size. state.chibi = p.random() < .06; state.dashed = p.random() < .06; state.sparse = p.random() < .18; const gap = state.chibi ? .7 : .5; state.drawLine = !state.dashed ? line => p.line(...line.slice(0, 4)) : line => { const dashes = p.max(1, line[6] / 3 | 0), scale = dashes - gap; for (let i = 0; i < dashes; i++) { p.line( p.lerp(line[0], line[2], i / scale), p.lerp(line[1], line[3], i / scale), p.lerp(line[0], line[2], (i + 1 - gap) / scale), p.lerp(line[1], line[3], (i + 1 - gap) / scale) ); } }; let color = p.random(); state.color = color < .12 ? [224, 42, 32, "Fire"] : color < .24 ? [96, 128, 247, "Water"] : color < .36 ? [148, 164, 196, "Air"] : [239, 239, 153, "Earth"]; }, placeForegroundStars = _ => { state.stars = []; const mainSizer = _ => state.chibi ? p.random(1.3, 1.8) : 1; // Place 2 to 4 stars somewhat uniformly. const preset = p.random(2, 5) | 0, angleOffset = p.random(p.TWO_PI / preset); for (let i = 0; i < preset; i++) { const dist = p.random(14, 20), angle = angleOffset + (i * p.TWO_PI + p.random(p.PI)) / preset, position = [dist * p.cos(angle), dist * p.sin(angle)]; let star = makeStar(position, .75, mainSizer()); state.stars.push(star); } // Place the remaining stars randomly. const starCount = 4 + p.abs(p.randomGaussian(1, 3)) | 0; for (let i = preset; i < starCount; i++) { const position = getOpening(state.stars); if (position) { let star = makeStar(position, .75, mainSizer()); state.stars.push(star); } } }, drawConstellation = _ => { state.lines = []; const connectableStars = [...state.stars], lineCount = state.stars.length * 1.3 + p.randomGaussian(.8, 2.3) | 0; while (state.lines.length < lineCount && connectableStars.length > 1) { const index1 = p.random(connectableStars.length) | 0, index2 = p.random(connectableStars.length) | 0; // Make sure different stars were selected. if (index1 === index2) { if (p.random() < .25) { // Random chance to remove this star from consideration. connectableStars.splice(index1, 1); } continue; } const star1 = connectableStars[index1], star2 = connectableStars[index2], xdiff = star2[0] - star1[0], ydiff = star2[1] - star1[1]; // Check if the stars are too close. if (xdiff * xdiff + ydiff * ydiff < 25) { if (p.random() < .33) { // Random chance to remove a star from consideration. const dist1 = star1[0] * star1[0] + star1[1] * star1[1], dist2 = star2[0] * star2[0] + star2[1] * star2[1], removeIndex = p.random(dist1 + dist2) < dist1 ? index1 : index2; connectableStars.splice(removeIndex, 1); } continue; } // Check if this line would cover a star. let covering = false; for (let star of state.stars) { if (star !== star1 && star !== star2 && isBetween(star1, star2, star, xdiff, ydiff)) { covering = star; break; } } if (covering) { let i = connectableStars.indexOf(covering); if (i >= 0 && p.random() < .2) { // Random chance to remove a star from consideration. const dist1 = star1[0] * star1[0] + star1[1] * star1[1], dist2 = star2[0] * star2[0] + star2[1] * star2[1], removeIndex = p.random(dist1 + dist2) < dist1 ? index1 : index2; connectableStars.splice(removeIndex, 1); } continue; } // Check if this line conflicts with another line. let intersections = 0, duplicate = false; for (let line of state.lines) { // Check if these lines have the same endpoints. if (line[4] === star1 && line[5] === star2 || line[4] === star2 && line[5] === star1) { duplicate = true; break; } // Check if these lines intersect. if (intersects(star1, star2, line)) { intersections++; } } if (duplicate) { if (p.random() < .4) { // Random chance to remove a star from consideration. const dist1 = star1[0] * star1[0] + star1[1] * star1[1], dist2 = star2[0] * star2[0] + star2[1] * star2[1], removeIndex = p.random(dist1 + dist2) < dist1 ? index1 : index2; connectableStars.splice(removeIndex, 1); } continue; } // The more intersections, the more likely this line will not be used. if (p.random() >= (1 / (intersections * lineCount / 2 + 1))) continue; if (intersections) state.intersections = true; state.lines.push(lineBetween(star1, star2, xdiff, ydiff)); } }, placeBackgroundStars = _ => { const totalStarCount = (state.sparse ? 20 : 35) + p.abs(p.randomGaussian(0, 15)) | 0; for (let i = state.stars.length; i < totalStarCount; i++) { const position = getOpening(state.stars, 22); if (position) { // Check that this new star isn't on a constellation line. let covered = false; for (let line of state.lines) { if (isOn(line, position, 2.5 + 1.5 * state.chibi)) { covered = true; break; } } if (!covered) { state.stars.push(makeStar(position, .15, p.random(.4, 1))); } } } }, addDust = _ => { state.dust = []; const dustCount = (state.sparse ? 80 : 350) + p.abs(p.randomGaussian(0, 100)) | 0; for (let i = 0; i < dustCount; i++) { const mote = makeStar( getOpening([], p.random(18, 24)), .05, p.random(.2, .6) * p.random(.25, 1) ); // Dust motes are dimmer than foreground and background stars. mote[3] *= p.random(.4, .7); state.dust.push(mote); } }, describe = _ => { let cycle = false; const groups = new Map(), getId = group => group.id || getId(group.parent), getRoot = group => group.parent && getRoot(group.parent) || group, lockId = group => group.id || (group.id = lockId(group.parent)); // Figure out how many groups of stars/lines there are. // In graph theory I guess these are "components"? // IDK it's been at least 15 years since I took a data structure class. for (let i = 0; i < state.lines.length; i++) { const line = state.lines[i], group1 = groups.get(line[4]), group2 = groups.get(line[5]); if (!group1) { if (!group2) { // Don't laugh at my disjoint-set data structure please. const newGroup = {id: i + 1}; groups.set(line[4], newGroup); groups.set(line[5], newGroup); } else { groups.set(line[4], group2); } } else { // group1 exists. if (!group2) { groups.set(line[5], group1); } else { if (getId(group1) !== getId(group2)) { // The worst possible way to union sets, probably. // There's definite O(n^2) energy, but n is small, so... const root = getRoot(group2); delete root.id; root.parent = group1; } else { // The group has run back into itself, so we have a cycle. cycle = true; } } } } // Collect the group IDs from all lines. const groupIds = new Map(); for (let group of groups.values()) { groupIds.set(lockId(group), true); } const lineTraits = []; if (state.intersections) lineTraits.push("Star-crossed"); if (groupIds.size > 1) lineTraits.push("Fragmented"); if (!cycle && state.lines.length > 1) lineTraits.push("Simple"); if (state.lines.length == 1) lineTraits.push("Minimalist"); if (!state.lines.length) lineTraits.push("Missing"); // Put an "and" between the last two traits. if (lineTraits.length > 1) { const last = lineTraits.pop() lineTraits.push(lineTraits.pop() + " and " + last); } const figureDescription = lineTraits.join(", ") + (lineTraits.length ? " " : "") + (state.dashed ? "Asterism" : "Constellation"), starDescription = (state.sparse ? "Sparse" : "Crowded") + " and " + (state.chibi ? "Luminous" : "Twinkling"), metadata = { "Figure": figureDescription, "Stars": starDescription, "Element": state.color[3], "transactionHash": state.transactionHash, }; console.log("metadata:", metadata); }; //-[ draw functions ]---------------------------------------------------------// p.draw = _ => { if (!state) return; // When not animating, fast forward to a frame where everything is drawn. const frame = state.animate ? state.progress : 1000; // Advance a frame unless this is a resize. if (!state.resize) { state.progress++; } else { state.resize = false; } // The canvas is 25 by 25, origin in the middle, rotated by time of day-ish. p.resetMatrix(); p.scale(p.width / 50); p.translate(25, 25); if (state.animate) { p.rotate(((new Date()).getTime() % 86164100) / 86164100 * p.TWO_PI); } p.noStroke(); p.blendMode(p.BLEND); p.background(16); p.blendMode(p.SCREEN); // Draw elements from back to front. Stardust first. if (!state.simple) for (let i = 0; i < state.dust.length; i++) { const dust = state.dust[i]; p.fill( p.constrain((dust[3] + (state.animate ? p.random(-10, 10) : 0)) * p.constrain((frame - i / 40) / p.random(10, 14), 0, 1), 0, 255) ); dust[dust.length - 1](); } // Then stars. for (let i = 0; i < state.stars.length; i++) { const star = state.stars[i]; p.fill( p.constrain((star[3] + (state.animate ? p.random(-20, 10) : 0)) * p.constrain((frame - i / 2) / 8, 0, 1), 0, 255) ); star[star.length - 1](); } // Then the asterism. p.strokeCap(p.ROUND); p.strokeWeight(state.chibi ? .66 : .4); if (!state.clear) for (let i = 0; i < state.lines.length; i++) { const line = state.lines[i]; let color = state.color.slice(0, 3); let flicker = !state.animate ? 1 : p.random(.93, 1) * p.random(.93, 1) * p.constrain( (frame - 6 - .6 * state.lines.length - i / 5 + p.random(2)) / 5, 0, 1 ); for (let i = 0; i < color.length; i++) { color[i] *= flicker; } p.stroke(...color); state.drawLine(line); } }; //-[ helper functions ]-------------------------------------------------------// const getOpening = (stars, distance = 18) => { let collision, tries = 0; do { collision = false; tries++; let point; // Random point in a circle with radius 20, using rejection sampling. do { point = [p.random(-distance, distance), p.random(-distance, distance)]; } while (point[0] * point[0] + point[1] * point[1] > distance * distance); for (let star of stars) { let xdiff = star[0] - point[0], ydiff = star[1] - point[1]; if (xdiff * xdiff + ydiff * ydiff < 2 * star[2] * star[2]) { collision = true; break; } } if (!collision) return point; } while (tries < 100); }, makeStar = (position, burstPercent, scale) => { const size = (scale || 1) * p.random(.6, .9), margin = state.chibi ? 1 : .6, brightness = p.random(240, 255); if (p.random() < burstPercent) { const blunt = p.random(.01, .07), fatness = p.random(.3, .6), sides = p.max(3, p.randomGaussian(5.5, .25) | 0), rotation = p.random(p.PI / sides), angle = p.TWO_PI / sides; // IDK. const maxInner = p.sin(p.HALF_PI - angle / 2), halfCutSquared = (1 - p.cos(p.PI * (1 - 1 / sides))) / 2, minInner = p.sqrt(1 - halfCutSquared), inner = minInner + (maxInner - minInner) * fatness; return [...position, size * 1.5 + margin, brightness, _ => { p.push(); p.translate(...position); p.rotate(rotation); p.scale(size); p.beginShape(); for (let s = 0; s < sides; s++) { p.vertex(p.cos(angle * (s - blunt)), p.sin(angle * (s - blunt))); p.vertex(p.cos(angle * (s + blunt)), p.sin(angle * (s + blunt))); p.vertex( inner * p.cos(angle * (s + .5)), inner * p.sin(angle * (s + .5)) ); } p.endShape(p.CLOSE); p.pop(); }]; } else { return [...position, size + margin, brightness, _ => { p.ellipse(...position, size, size); }]; } }, lineBetween = (star1, star2, xdiff, ydiff) => { const distance = p.sqrt(xdiff * xdiff + ydiff * ydiff), xOff = xdiff / distance, yOff = ydiff / distance, s1Dist = star1[2], s2Dist = star2[2]; return [ star1[0] + s1Dist * xOff, star1[1] + s1Dist * yOff, star2[0] - s2Dist * xOff, star2[1] - s2Dist * yOff, star1, star2, distance, ]; }, distanceSquared = (a, b) => { let x = a[0] - b[0], y = a[1] - b[1]; return x * x + y * y; }, isBetween = (star1, star2, covered, xdiff, ydiff, threshold = 2.5) => { let lengthSquared = xdiff * xdiff + ydiff * ydiff; let perpDistance = p.abs(ydiff * (covered[0] - star1[0]) - xdiff * (covered[1] - star1[1])) / p.sqrt(lengthSquared); return ( perpDistance < threshold && distanceSquared(star1, covered) < lengthSquared && distanceSquared(star2, covered) < lengthSquared ) }, // Same as `isBetween` except that it takes a line instead of endpoints. isOn = (line, point, threshold = 2.5) => isBetween( [line[0], line[1]], [line[2], line[3]], point, line[2] - line[0], line[3] - line[1], threshold ), clockwise = (star1, star2, star3) => (star3[1] - star1[1]) * (star2[0] - star1[0]) < (star2[1] - star1[1]) * (star3[0] - star1[0]), intersects = (star1, star2, line) => clockwise(star1, line, line.slice(2, 4)) != clockwise(star2, line, line.slice(2, 4)) && clockwise(star1, star2, line) != clockwise(star1, star2, line.slice(2, 4)); ```
unknown sendersent to0x499f4943…3001·#15,306,064·view on Etherscan
Az```javascript "it rests among the stars / gathered up in the furthest firmament"; let canvas, // p5js canvas. state; // Keep all shared state in one place. //-[ setup functions ]--------------------------------------------------------// // Creates the p5js canvas and disables looping. // The rest of the setup is done when the Immutables properties are received. p.setup = _ => { canvas = p.createCanvas(1, 1); canvas.style("display", "block"); p.frameRate(10); p.noLoop(); }; // Responds to the properties provided by Immutables. p.myCustomRedrawAccordingToNewPropsHandler = properties => { if (!properties.transactionHash) return; // Check if existing state already matches the new properties. if ( state && state.transactionHash == properties.transactionHash && state.editionId == properties.editionId ) { state.square = properties.square; p.windowResized(); return; } // Pull bits from the hash to seed the RNG. p.randomSeed("0x" + properties.transactionHash.substr(2 + 8 * (properties.editionId % 8), 8)); // Intialize the state. state = { ...properties, animate: properties.square == 1, progress: 0, }; setStyling(); placeForegroundStars(); drawConstellation(); placeBackgroundStars(); addDust(); describe(); // Resize the window and turn looping on or off as needed. p.windowResized(); state.animate ? p.loop() : (p.noLoop(), p.redraw()); }; p.windowResized = _ => { if (!state) return; const minDim = p.min(p.windowWidth, p.windowHeight) * .8 / state.square | 0; if (minDim === p.width) return; p.resizeCanvas(minDim, minDim); state.resize = true; }; p.keyTyped = _ => { if (!state || state.square != 1) return; if (p.key == "A") { if (state.animate) { state.animate = false; p.noLoop(); p.redraw(); } else { state.animate = true; state.progress = 0; p.loop(); } } if (p.key == "B") { state.simple = !state.simple; } if (p.key == "C") { state.clear = !state.clear; } }; const setStyling = _ => { // Determine star size. state.chibi = p.random() < .06; state.dashed = p.random() < .06; state.sparse = p.random() < .18; const gap = state.chibi ? .7 : .5; state.drawLine = !state.dashed ? line => p.line(...line.slice(0, 4)) : line => { const dashes = p.max(1, line[6] / 3 | 0), scale = dashes - gap; for (let i = 0; i < dashes; i++) { p.line( p.lerp(line[0], line[2], i / scale), p.lerp(line[1], line[3], i / scale), p.lerp(line[0], line[2], (i + 1 - gap) / scale), p.lerp(line[1], line[3], (i + 1 - gap) / scale) ); } }; let color = p.random(); state.color = color < .12 ? [224, 42, 32, "Fire"] : color < .24 ? [96, 128, 247, "Water"] : color < .36 ? [148, 164, 196, "Air"] : [239, 239, 153, "Earth"]; }, placeForegroundStars = _ => { state.stars = []; const mainSizer = _ => state.chibi ? p.random(1.3, 1.8) : 1; // Place 2 to 4 stars somewhat uniformly. const preset = p.random(2, 5) | 0, angleOffset = p.random(p.TWO_PI / preset); for (let i = 0; i < preset; i++) { const dist = p.random(14, 20), angle = angleOffset + (i * p.TWO_PI + p.random(p.PI)) / preset, position = [dist * p.cos(angle), dist * p.sin(angle)]; let star = makeStar(position, .75, mainSizer()); state.stars.push(star); } // Place the remaining stars randomly. const starCount = 4 + p.abs(p.randomGaussian(1, 3)) | 0; for (let i = preset; i < starCount; i++) { const position = getOpening(state.stars); if (position) { let star = makeStar(position, .75, mainSizer()); state.stars.push(star); } } }, drawConstellation = _ => { state.lines = []; const connectableStars = [...state.stars], lineCount = state.stars.length * 1.3 + p.randomGaussian(.8, 2.3) | 0; while (state.lines.length < lineCount && connectableStars.length > 1) { const index1 = p.random(connectableStars.length) | 0, index2 = p.random(connectableStars.length) | 0; // Make sure different stars were selected. if (index1 === index2) { if (p.random() < .25) { // Random chance to remove this star from consideration. connectableStars.splice(index1, 1); } continue; } const star1 = connectableStars[index1], star2 = connectableStars[index2], xdiff = star2[0] - star1[0], ydiff = star2[1] - star1[1]; // Check if the stars are too close. if (xdiff * xdiff + ydiff * ydiff < 25) { if (p.random() < .33) { // Random chance to remove a star from consideration. const dist1 = star1[0] * star1[0] + star1[1] * star1[1], dist2 = star2[0] * star2[0] + star2[1] * star2[1], removeIndex = p.random(dist1 + dist2) < dist1 ? index1 : index2; connectableStars.splice(removeIndex, 1); } continue; } // Check if this line would cover a star. let covering = false; for (let star of state.stars) { if (star !== star1 && star !== star2 && isBetween(star1, star2, star, xdiff, ydiff)) { covering = star; break; } } if (covering) { let i = connectableStars.indexOf(covering); if (i >= 0 && p.random() < .2) { // Random chance to remove a star from consideration. const dist1 = star1[0] * star1[0] + star1[1] * star1[1], dist2 = star2[0] * star2[0] + star2[1] * star2[1], removeIndex = p.random(dist1 + dist2) < dist1 ? index1 : index2; connectableStars.splice(removeIndex, 1); } continue; } // Check if this line conflicts with another line. let intersections = 0, duplicate = false; for (let line of state.lines) { // Check if these lines have the same endpoints. if (line[4] === star1 && line[5] === star2 || line[4] === star2 && line[5] === star1) { duplicate = true; break; } // Check if these lines intersect. if (intersects(star1, star2, line)) { intersections++; } } if (duplicate) { if (p.random() < .4) { // Random chance to remove a star from consideration. const dist1 = star1[0] * star1[0] + star1[1] * star1[1], dist2 = star2[0] * star2[0] + star2[1] * star2[1], removeIndex = p.random(dist1 + dist2) < dist1 ? index1 : index2; connectableStars.splice(removeIndex, 1); } continue; } // The more intersections, the more likely this line will not be used. if (p.random() >= (1 / (intersections * lineCount / 2 + 1))) continue; if (intersections) state.intersections = true; state.lines.push(lineBetween(star1, star2, xdiff, ydiff)); } }, placeBackgroundStars = _ => { const totalStarCount = (state.sparse ? 20 : 35) + p.abs(p.randomGaussian(0, 15)) | 0; for (let i = state.stars.length; i < totalStarCount; i++) { const position = getOpening(state.stars, 22); if (position) { // Check that this new star isn't on a constellation line. let covered = false; for (let line of state.lines) { if (isOn(line, position, 2.5 + 1.5 * state.chibi)) { covered = true; break; } } if (!covered) { state.stars.push(makeStar(position, .15, p.random(.4, 1))); } } } }, addDust = _ => { state.dust = []; const dustCount = (state.sparse ? 80 : 350) + p.abs(p.randomGaussian(0, 100)) | 0; for (let i = 0; i < dustCount; i++) { const mote = makeStar( getOpening([], p.random(18, 24)), .05, p.random(.2, .6) * p.random(.25, 1) ); // Dust motes are dimmer than foreground and background stars. mote[3] *= p.random(.4, .7); state.dust.push(mote); } }, describe = _ => { let cycle = false; const groups = new Map(), getId = group => group.id || getId(group.parent), getRoot = group => group.parent && getRoot(group.parent) || group, lockId = group => group.id || (group.id = lockId(group.parent)); // Figure out how many groups of stars/lines there are. // In graph theory I guess these are "components"? // IDK it's been at least 15 years since I took a data structure class. for (let i = 0; i < state.lines.length; i++) { const line = state.lines[i], group1 = groups.get(line[4]), group2 = groups.get(line[5]); if (!group1) { if (!group2) { // Don't laugh at my disjoint-set data structure please. const newGroup = {id: i + 1}; groups.set(line[4], newGroup); groups.set(line[5], newGroup); } else { groups.set(line[4], group2); } } else { // group1 exists. if (!group2) { groups.set(line[5], group1); } else { if (getId(group1) !== getId(group2)) { // The worst possible way to union sets, probably. // There's definite O(n^2) energy, but n is small, so... const root = getRoot(group2); delete root.id; root.parent = group1; } else { // The group has run back into itself, so we have a cycle. cycle = true; } } } } // Collect the group IDs from all lines. const groupIds = new Map(); for (let group of groups.values()) { groupIds.set(lockId(group), true); } const lineTraits = []; if (state.intersections) lineTraits.push("Star-crossed"); if (groupIds.size > 1) lineTraits.push("Fragmented"); if (!cycle && state.lines.length > 1) lineTraits.push("Simple"); if (state.lines.length == 1) lineTraits.push("Minimalist"); if (!state.lines.length) lineTraits.push("Missing"); // Put an "and" between the last two traits. if (lineTraits.length > 1) { const last = lineTraits.pop() lineTraits.push(lineTraits.pop() + " and " + last); } const figureDescription = lineTraits.join(", ") + (lineTraits.length ? " " : "") + (state.dashed ? "Asterism" : "Constellation"), starDescription = (state.sparse ? "Sparse" : "Crowded") + " and " + (state.chibi ? "Luminous" : "Twinkling"), metadata = { "Figure": figureDescription, "Stars": starDescription, "Element": state.color[3], "transactionHash": state.transactionHash, }; console.log("metadata:", metadata); }; //-[ draw functions ]---------------------------------------------------------// p.draw = _ => { if (!state) return; // When not animating, fast forward to a frame where everything is drawn. const frame = state.animate ? state.progress : 1000; // Advance a frame unless this is a resize. if (!state.resize) { state.progress++; } else { state.resize = false; } // The canvas is 25 by 25, origin in the middle, rotated by time of day-ish. p.resetMatrix(); p.scale(p.width / 50); p.translate(25, 25); if (state.animate) { p.rotate(((new Date()).getTime() % 86164100) / 86164100 * p.TWO_PI); } p.noStroke(); p.blendMode(p.BLEND); p.background(16); p.blendMode(p.SCREEN); // Draw elements from back to front. Stardust first. if (!state.simple) for (let i = 0; i < state.dust.length; i++) { const dust = state.dust[i]; p.fill( p.constrain((dust[3] + (state.animate ? p.random(-10, 10) : 0)) * p.constrain((frame - i / 40) / p.random(10, 14), 0, 1), 0, 255) ); dust[dust.length - 1](); } // Then stars. for (let i = 0; i < state.stars.length; i++) { const star = state.stars[i]; p.fill( p.constrain((star[3] + (state.animate ? p.random(-20, 10) : 0)) * p.constrain((frame - i / 2) / 8, 0, 1), 0, 255) ); star[star.length - 1](); } // Then the asterism. p.strokeCap(p.ROUND); p.strokeWeight(state.chibi ? .66 : .4); if (!state.clear) for (let i = 0; i < state.lines.length; i++) { const line = state.lines[i]; let color = state.color.slice(0, 3); let flicker = !state.animate ? 1 : p.random(.93, 1) * p.random(.93, 1) * p.constrain( (frame - 6 - .6 * state.lines.length - i / 5 + p.random(2)) / 5, 0, 1 ); for (let i = 0; i < color.length; i++) { color[i] *= flicker; } p.stroke(...color); state.drawLine(line); } }; //-[ helper functions ]-------------------------------------------------------// const getOpening = (stars, distance = 18) => { let collision, tries = 0; do { collision = false; tries++; let point; // Random point in a circle with radius 20, using rejection sampling. do { point = [p.random(-distance, distance), p.random(-distance, distance)]; } while (point[0] * point[0] + point[1] * point[1] > distance * distance); for (let star of stars) { let xdiff = star[0] - point[0], ydiff = star[1] - point[1]; if (xdiff * xdiff + ydiff * ydiff < 2 * star[2] * star[2]) { collision = true; break; } } if (!collision) return point; } while (tries < 100); }, makeStar = (position, burstPercent, scale) => { const size = (scale || 1) * p.random(.6, .9), margin = state.chibi ? 1 : .6, brightness = p.random(240, 255); if (p.random() < burstPercent) { const blunt = p.random(.01, .07), fatness = p.random(.3, .6), sides = p.max(3, p.randomGaussian(5.5, .25) | 0), rotation = p.random(p.PI / sides), angle = p.TWO_PI / sides; // IDK. const maxInner = p.sin(p.HALF_PI - angle / 2), halfCutSquared = (1 - p.cos(p.PI * (1 - 1 / sides))) / 2, minInner = p.sqrt(1 - halfCutSquared), inner = minInner + (maxInner - minInner) * fatness; return [...position, size * 1.5 + margin, brightness, _ => { p.push(); p.translate(...position); p.rotate(rotation); p.scale(size); p.beginShape(); for (let s = 0; s < sides; s++) { p.vertex(p.cos(angle * (s - blunt)), p.sin(angle * (s - blunt))); p.vertex(p.cos(angle * (s + blunt)), p.sin(angle * (s + blunt))); p.vertex( inner * p.cos(angle * (s + .5)), inner * p.sin(angle * (s + .5)) ); } p.endShape(p.CLOSE); p.pop(); }]; } else { return [...position, size + margin, brightness, _ => { p.ellipse(...position, size, size); }]; } }, lineBetween = (star1, star2, xdiff, ydiff) => { const distance = p.sqrt(xdiff * xdiff + ydiff * ydiff), xOff = xdiff / distance, yOff = ydiff / distance, s1Dist = star1[2], s2Dist = star2[2]; return [ star1[0] + s1Dist * xOff, star1[1] + s1Dist * yOff, star2[0] - s2Dist * xOff, star2[1] - s2Dist * yOff, star1, star2, distance, ]; }, distanceSquared = (a, b) => { let x = a[0] - b[0], y = a[1] - b[1]; return x * x + y * y; }, isBetween = (star1, star2, covered, xdiff, ydiff, threshold = 2.5) => { let lengthSquared = xdiff * xdiff + ydiff * ydiff; let perpDistance = p.abs(ydiff * (covered[0] - star1[0]) - xdiff * (covered[1] - star1[1])) / p.sqrt(lengthSquared); return ( perpDistance < threshold && distanceSquared(star1, covered) < lengthSquared && distanceSquared(star2, covered) < lengthSquared ) }, // Same as `isBetween` except that it takes a line instead of endpoints. isOn = (line, point, threshold = 2.5) => isBetween( [line[0], line[1]], [line[2], line[3]], point, line[2] - line[0], line[3] - line[1], threshold ), clockwise = (star1, star2, star3) => (star3[1] - star1[1]) * (star2[0] - star1[0]) < (star2[1] - star1[1]) * (star3[0] - star1[0]), intersects = (star1, star2, line) => clockwise(star1, line, line.slice(2, 4)) != clockwise(star2, line, line.slice(2, 4)) && clockwise(star1, star2, line) != clockwise(star1, star2, line.slice(2, 4)); ```