p5js with AI 100日チャレンジ
これは何?
こちらの書籍を知って読んでみて触発されたので,自分もプロンプトのみでコーディングをしてどこまでいけるのか?を試してみたくなった次第.
もちろんやりたいのは p5.js を用いた Generative Art.
ルール
- どの AI モデルを使っても良い
- 極力 AI が吐き出したコードに手を加えない(あくまでプロンプトのみ)
- うまくいかなかったとしても,時間が来たらその時点での作品とコードをここに晒す
- 少しでも変化をつけ,全く同じなものは作らない
Day1: Door like shapes
まずは格子状にシェイプ(△,□,◯)を並べ,時間経過とともに小さくなったり,本のサイズに戻るようなアニメーションをする幾何学アート.吐かれたコードを自分で手直しをすれば頭の中の完成品になるんだが,あえてプロンプトのみでコーディングをしてみたかったので,完成には至らず.だいぶ気持ちの悪い挙動になってしまった…
ちなみに今日は Cursor の Agent で Auto-select で書いてみた.
コード
// Grid parameters
const gridSize = 10; // Number of cells in each row/column
let cellSize;
let shapes = [];
let colors = [];
let currentPalette = 'Hokusai'; // You can change this to any palette name from colorPalette.js
let globalTime = 0;
function setup() {
createCanvas((W = min(windowWidth, windowHeight) - 50), W);
rectMode(CENTER);
cellSize = W / gridSize;
// Select 3 colors from the chosen palette
const palette = colorPalette.find((p) => p.name === currentPalette);
if (palette) {
// Take the first 3 colors from the palette
colors = palette.colors.slice(0, 3);
} else {
// Fallback colors if palette not found
colors = ['#074A59', '#F2C166', '#F28241'];
}
// Initialize shapes
for (let i = 0; i < gridSize; i++) {
for (let j = 0; j < gridSize; j++) {
// Randomly choose shape type (0: square, 1: triangle, 2: circle)
const shapeType = floor(random(3));
// Randomly choose color index
const colorIndex = floor(random(3));
// For squares and triangles, ensure one side is parallel to x-axis
// by setting rotation to multiples of 90 degrees
const baseRotation =
shapeType === 2 ? random(TWO_PI) : (floor(random(4)) * PI) / 2;
// Animation parameters
const animationDelay = random(0, 10); // Random delay before animation starts
const animationDuration = random(3, 8); // Random duration for each animation cycle
const isActive = random() < 0.3; // Only 30% of shapes are active at a time
const phase = random(TWO_PI); // Random phase for sine wave
// Door-like animation parameters
const doorDirection = floor(random(2)); // 0: horizontal, 1: vertical
const doorSide = floor(random(2)); // 0: left/top, 1: right/bottom
shapes.push({
x: i * cellSize + cellSize / 2,
y: j * cellSize + cellSize / 2,
type: shapeType,
color: colors[colorIndex],
rotation: baseRotation,
baseRotation: baseRotation,
animationDelay: animationDelay,
animationDuration: animationDuration,
isActive: isActive,
phase: phase,
lastAnimationTime: 0,
isAnimating: false,
doorDirection: doorDirection,
doorSide: doorSide,
isClosing: false,
isOpening: false,
});
}
}
}
function draw() {
background(255);
globalTime += 0.03;
// Draw each shape
for (let shape of shapes) {
push();
translate(shape.x, shape.y);
rotate(shape.baseRotation);
// Set fill color
fill(shape.color);
noStroke();
// Check if it's time to start a new animation cycle
if (
shape.isActive &&
globalTime - shape.lastAnimationTime > shape.animationDelay
) {
if (!shape.isAnimating && !shape.isClosing && !shape.isOpening) {
shape.isAnimating = true;
shape.lastAnimationTime = globalTime;
// Start with closing phase
shape.isClosing = true;
shape.isOpening = false;
}
// Handle closing phase
if (shape.isClosing) {
let closeProgress =
(globalTime - shape.lastAnimationTime) / shape.animationDuration;
if (closeProgress > 1) {
closeProgress = 1;
shape.isClosing = false;
shape.isOpening = true;
shape.lastAnimationTime = globalTime;
}
// Draw shape with door-like closing effect
if (shape.type === 0) {
// Square
if (shape.doorDirection === 0) {
// Horizontal door
const width = cellSize * 0.8 * (1 - closeProgress);
if (shape.doorSide === 0) {
// Left side
rect(-cellSize * 0.4 + width / 2, 0, width, cellSize * 0.8);
} else {
// Right side
rect(cellSize * 0.4 - width / 2, 0, width, cellSize * 0.8);
}
} else {
// Vertical door
const height = cellSize * 0.8 * (1 - closeProgress);
if (shape.doorSide === 0) {
// Top side
rect(0, -cellSize * 0.4 + height / 2, cellSize * 0.8, height);
} else {
// Bottom side
rect(0, cellSize * 0.4 - height / 2, cellSize * 0.8, height);
}
}
} else if (shape.type === 1) {
// Triangle
const size = cellSize * 0.8;
if (shape.doorDirection === 0) {
// Horizontal door
const width = size * (1 - closeProgress);
if (shape.doorSide === 0) {
// Left side
const x = -size / 2 + width / 2;
triangle(x, -size / 2, x + width, size / 2, x, size / 2);
} else {
// Right side
const x = size / 2 - width / 2;
triangle(x, -size / 2, x + width, size / 2, x, size / 2);
}
} else {
// Vertical door
const height = size * (1 - closeProgress);
if (shape.doorSide === 0) {
// Top side
const y = -size / 2 + height / 2;
triangle(0, y, size / 2, y + height, -size / 2, y + height);
} else {
// Bottom side
const y = size / 2 - height / 2;
triangle(0, y, size / 2, y + height, -size / 2, y + height);
}
}
} else {
// Circle - use a different approach for circles
const diameter = cellSize * 0.8 * (1 - closeProgress);
ellipse(0, 0, diameter, diameter);
}
}
// Handle opening phase
if (shape.isOpening) {
let openProgress =
(globalTime - shape.lastAnimationTime) / shape.animationDuration;
if (openProgress > 1) {
openProgress = 1;
shape.isOpening = false;
shape.isAnimating = false;
shape.lastAnimationTime = globalTime;
// shape.animationDelay = random(2, 8); // New random delay before next animation
}
// Draw shape with door-like opening effect
if (shape.type === 0) {
// Square
if (shape.doorDirection === 0) {
// Horizontal door
const width = cellSize * 0.8 * openProgress;
if (shape.doorSide === 0) {
// Left side
rect(-cellSize * 0.4 + width / 2, 0, width, cellSize * 0.8);
} else {
// Right side
rect(cellSize * 0.4 - width / 2, 0, width, cellSize * 0.8);
}
} else {
// Vertical door
const height = cellSize * 0.8 * openProgress;
if (shape.doorSide === 0) {
// Top side
rect(0, -cellSize * 0.4 + height / 2, cellSize * 0.8, height);
} else {
// Bottom side
rect(0, cellSize * 0.4 - height / 2, cellSize * 0.8, height);
}
}
} else if (shape.type === 1) {
// Triangle
const size = cellSize * 0.8;
if (shape.doorDirection === 0) {
// Horizontal door
const width = size * openProgress;
if (shape.doorSide === 0) {
// Left side
const x = -size / 2 + width / 2;
triangle(x, -size / 2, x + width, size / 2, x, size / 2);
} else {
// Right side
const x = size / 2 - width / 2;
triangle(x, -size / 2, x + width, size / 2, x, size / 2);
}
} else {
// Vertical door
const height = size * openProgress;
if (shape.doorSide === 0) {
// Top side
const y = -size / 2 + height / 2;
triangle(0, y, size / 2, y + height, -size / 2, y + height);
} else {
// Bottom side
const y = size / 2 - height / 2;
triangle(0, y, size / 2, y + height, -size / 2, y + height);
}
}
} else {
// Circle - use a different approach for circles
const diameter = cellSize * 0.8 * openProgress;
ellipse(0, 0, diameter, diameter);
}
}
} else {
// Draw shape normally when not animating
if (shape.type === 0) {
// Square
rectMode(CENTER);
rect(0, 0, cellSize * 0.8, cellSize * 0.8);
} else if (shape.type === 1) {
// Triangle
const size = cellSize * 0.8;
triangle(0, -size / 2, size / 2, size / 2, -size / 2, size / 2);
} else {
// Circle
ellipse(0, 0, cellSize * 0.8, cellSize * 0.8);
}
}
pop();
}
}
function keyPressed() {
if (key === 's') {
saveGif(`mySketch-${round(new Date().getTime() / 100000)}`, 5);
}
if (key === 'c') {
saveCanvas(`mySketch-${round(new Date().getTime() / 100000)}`, 'jpeg');
}
// Change palette with number keys 1-9
if (key >= '1' && key <= '9') {
const index = parseInt(key) - 1;
if (index < colorPalette.length) {
currentPalette = colorPalette[index].name;
// Reinitialize shapes with new colors
setup();
}
}
// Reset animation with 'r' key
if (key === 'r') {
setup();
}
}

Dat2: Geometric Art - 2
三角関数を多重にかけることで,変わった挙動をするように変更してみた.
本当は point() 関数を使って全然違う絵を描きたかったが,昨日の絵の文脈のまま書いてしまったのと,思ったより見たら面白かったので,今日はこれを作品とする.
ちなみに今日も Cursor に全振りしてる.
コード
// Grid parameters
const gridSize = 10; // Number of cells in each row/column
let cellSize;
let shapes = [];
let colors = [];
let currentPalette = 'Hokusai'; // You can change this to any palette name from colorPalette.js
let globalTime = 0;
function setup() {
createCanvas((W = min(windowWidth, windowHeight) - 50), W);
cellSize = W / gridSize;
// Select 3 colors from the chosen palette
const palette = colorPalette.find((p) => p.name === currentPalette);
if (palette) {
// Take the first 3 colors from the palette
colors = palette.colors.slice(0, 3);
} else {
// Fallback colors if palette not found
colors = ['#074A59', '#F2C166', '#F28241'];
}
// Initialize shapes
for (let i = 0; i < gridSize; i++) {
for (let j = 0; j < gridSize; j++) {
// Randomly choose shape type (0: square, 1: triangle, 2: circle)
const shapeType = floor(random(3));
// Randomly choose color index
const colorIndex = floor(random(3));
// For squares and triangles, ensure one side is parallel to x-axis
// by setting rotation to multiples of 90 degrees
const baseRotation =
shapeType === 2 ? random(TWO_PI) : (floor(random(4)) * PI) / 2;
// Animation parameters
const animationDelay = random(0, 10); // Random delay before animation starts
const animationDuration = random(3, 8); // Random duration for each animation cycle
const isActive = random() < 0.3; // Only 30% of shapes are active at a time
const phase = random(TWO_PI); // Random phase for sine wave
// Fish fin animation parameters
const finType = floor(random(3)); // 0: dorsal fin, 1: pectoral fin, 2: caudal fin
const finSize = random(0.5, 1.5); // Random size multiplier
const finSpeed = random(0.5, 2); // Random speed multiplier
const finAmplitude = random(0.2, 0.5); // Random amplitude for fin movement
const finFrequency = random(1, 3); // Random frequency for fin movement
// Create the shape object
const shape = {
x: i * cellSize + cellSize / 2,
y: j * cellSize + cellSize / 2,
type: shapeType,
color: colors[colorIndex],
rotation: baseRotation,
baseRotation: baseRotation,
animationDelay: animationDelay,
animationDuration: animationDuration,
isActive: isActive,
phase: phase,
lastAnimationTime: 0,
isAnimating: false,
finType: finType,
finSize: finSize,
finSpeed: finSpeed,
finAmplitude: finAmplitude,
finFrequency: finFrequency,
points: [],
};
// Generate points for the shape
if (shapeType === 0) {
// Square points
const size = cellSize * 0.8;
shape.points = [
{ x: -size / 2, y: -size / 2 },
{ x: size / 2, y: -size / 2 },
{ x: size / 2, y: size / 2 },
{ x: -size / 2, y: size / 2 },
];
} else if (shapeType === 1) {
// Triangle points
const size = cellSize * 0.8;
shape.points = [
{ x: 0, y: -size / 2 },
{ x: size / 2, y: size / 2 },
{ x: -size / 2, y: size / 2 },
];
} else {
// Circle points (approximated with many points)
const radius = cellSize * 0.4;
const numPoints = 20;
for (let p = 0; p < numPoints; p++) {
const angle = (p / numPoints) * TWO_PI;
shape.points.push({
x: radius * cos(angle),
y: radius * sin(angle),
});
}
}
// Add the shape to the shapes array
shapes.push(shape);
}
}
}
function draw() {
background(255);
globalTime += 0.03;
// Draw each shape
for (let shape of shapes) {
push();
translate(shape.x, shape.y);
rotate(shape.baseRotation);
// Set fill color
fill(shape.color);
noStroke();
// Check if it's time to start a new animation cycle
if (
shape.isActive &&
globalTime - shape.lastAnimationTime > shape.animationDelay
) {
if (!shape.isAnimating) {
shape.isAnimating = true;
shape.lastAnimationTime = globalTime;
}
// Draw shape with fish fin-like animation
if (shape.type === 0) {
// Square
drawFinShape(shape);
} else if (shape.type === 1) {
// Triangle
drawFinShape(shape);
} else {
// Circle
drawFinShape(shape);
}
} else {
// Draw shape normally when not animating
if (shape.type === 0) {
// Square
beginShape();
for (let point of shape.points) {
vertex(point.x, point.y);
}
endShape(CLOSE);
} else if (shape.type === 1) {
// Triangle
beginShape();
for (let point of shape.points) {
vertex(point.x, point.y);
}
endShape(CLOSE);
} else {
// Circle
beginShape();
for (let point of shape.points) {
vertex(point.x, point.y);
}
endShape(CLOSE);
}
}
pop();
}
}
function drawFinShape(shape) {
// Calculate time-based animation
const t = globalTime * shape.finSpeed;
// Draw the shape with fin-like movement
beginShape();
if (shape.type === 0) {
// Square with fin movement
for (let i = 0; i < shape.points.length; i++) {
const point = shape.points[i];
let dx = 0;
let dy = 0;
// Apply different fin movements based on fin type
if (shape.finType === 0) {
// Dorsal fin (top edge)
if (i === 0 || i === 1) {
const wave =
sin(t * shape.finFrequency + shape.phase) *
cos(t * shape.finFrequency * 0.5) *
shape.finAmplitude *
cellSize;
dy = wave;
}
} else if (shape.finType === 1) {
// Pectoral fin (side edges)
if (i === 0 || i === 3) {
const wave =
sin(t * shape.finFrequency + shape.phase) *
cos(t * shape.finFrequency * 0.7) *
shape.finAmplitude *
cellSize;
dx = wave;
}
} else {
// Caudal fin (all points with varying intensity)
const intensity = map(i, 0, shape.points.length - 1, 0.2, 1);
const wave =
sin(t * shape.finFrequency + shape.phase + i * 0.5) *
cos(t * shape.finFrequency * 0.3) *
shape.finAmplitude *
cellSize *
intensity;
dx = wave * cos((i * PI) / 2);
dy = wave * sin((i * PI) / 2);
}
vertex(point.x + dx, point.y + dy);
}
} else if (shape.type === 1) {
// Triangle with fin movement
for (let i = 0; i < shape.points.length; i++) {
const point = shape.points[i];
let dx = 0;
let dy = 0;
// Apply different fin movements based on fin type
if (shape.finType === 0) {
// Dorsal fin (top point)
if (i === 0) {
const wave =
sin(t * shape.finFrequency + shape.phase) *
cos(t * shape.finFrequency * 0.5) *
shape.finAmplitude *
cellSize;
dy = wave;
}
} else if (shape.finType === 1) {
// Pectoral fin (bottom points)
if (i === 1 || i === 2) {
const wave =
sin(t * shape.finFrequency + shape.phase) *
cos(t * shape.finFrequency * 0.7) *
shape.finAmplitude *
cellSize;
dx = wave * (i === 1 ? 1 : -1);
}
} else {
// Caudal fin (all points with varying intensity)
const intensity = map(i, 0, shape.points.length - 1, 0.2, 1);
const wave =
sin(t * shape.finFrequency + shape.phase + i * 0.5) *
cos(t * shape.finFrequency * 0.3) *
shape.finAmplitude *
cellSize *
intensity;
dx = wave * cos((i * PI) / 2);
dy = wave * sin((i * PI) / 2);
}
vertex(point.x + dx, point.y + dy);
}
} else {
// Circle with fin movement
for (let i = 0; i < shape.points.length; i++) {
const point = shape.points[i];
let dx = 0;
let dy = 0;
// Apply different fin movements based on fin type
if (shape.finType === 0) {
// Dorsal fin (top half)
if (point.y < 0) {
const wave =
sin(t * shape.finFrequency + shape.phase) *
cos(t * shape.finFrequency * 0.5) *
shape.finAmplitude *
cellSize;
dy = wave;
}
} else if (shape.finType === 1) {
// Pectoral fin (left/right sides)
if (abs(point.x) > abs(point.y)) {
const wave =
sin(t * shape.finFrequency + shape.phase) *
cos(t * shape.finFrequency * 0.7) *
shape.finAmplitude *
cellSize;
dx = wave * (point.x > 0 ? 1 : -1);
}
} else {
// Caudal fin (all points with varying intensity)
const angle = atan2(point.y, point.x);
const intensity = map(sin(angle), -1, 1, 0.2, 1);
const wave =
sin(t * shape.finFrequency + shape.phase + i * 0.5) *
cos(t * shape.finFrequency * 0.3) *
shape.finAmplitude *
cellSize *
intensity;
dx = wave * cos(angle);
dy = wave * sin(angle);
}
vertex(point.x + dx, point.y + dy);
}
}
endShape(CLOSE);
}
function keyPressed() {
if (key === 's') {
saveGif(`mySketch-${round(new Date().getTime() / 100000)}`, 5);
}
if (key === 'c') {
saveCanvas(`mySketch-${round(new Date().getTime() / 100000)}`, 'jpeg');
}
// Change palette with number keys 1-9
if (key >= '1' && key <= '9') {
const index = parseInt(key) - 1;
if (index < colorPalette.length) {
currentPalette = colorPalette[index].name;
// Reinitialize shapes with new colors
setup();
}
}
// Reset animation with 'r' key
if (key === 'r') {
setup();
}
}

Day3: Bubble
正直ちょっと不満というか,納得はいっていない.というのも,開始10minくらいでできたものをアップしているから.ただ,完全に不満ではなく,思ったよりは良いものが一瞬で出てきたなと.
これをブラッシュアップしてより良い作品に仕上げられそうに感じている.
コード
// Splatoon-inspired fluid paint simulation
let splatters = [];
let colors = [];
let maxSplatters = 50;
function setup() {
createCanvas(800, 600);
background(240);
// Splatoon-inspired color palette
colors = [
color(240, 28, 132), // Pink
color(0, 232, 254), // Cyan
color(255, 137, 1), // Orange
color(0, 249, 138), // Green
color(190, 92, 255), // Purple
color(255, 221, 0), // Yellow
];
textAlign(CENTER);
textSize(16);
}
function draw() {
// Update and display all splatters
for (let i = 0; i < splatters.length; i++) {
splatters[i].update();
splatters[i].display();
}
// Display instructions
fill(50);
noStroke();
text('Click or drag to create fluid paint splatters!', width / 2, 30);
}
function mouseDragged() {
createSplatter(mouseX, mouseY);
return false; // Prevent default behavior
}
function mousePressed() {
createSplatter(mouseX, mouseY);
}
function createSplatter(x, y) {
// Limit the number of splatters for performance
if (splatters.length >= maxSplatters) {
// Remove the oldest splatter
splatters.shift();
}
let selectedColor = random(colors);
splatters.push(new FluidSplatter(x, y, selectedColor));
}
class FluidSplatter {
constructor(x, y, color) {
this.x = x;
this.y = y;
this.color = color;
this.size = random(40, 120);
this.blobs = [];
this.blobCount = floor(random(5, 15));
// Create main blob
this.mainBlob = {
offsetX: 0,
offsetY: 0,
size: this.size,
noiseScale: random(0.3, 0.8),
noiseOffset: random(100),
};
// Create connected blobs
let mainAngle = random(TWO_PI);
for (let i = 0; i < this.blobCount; i++) {
// Create blobs in a more connected pattern
let angle = mainAngle + random(-PI / 2, PI / 2);
let distance = random(this.size * 0.3, this.size * 0.8);
this.blobs.push({
offsetX: cos(angle) * distance,
offsetY: sin(angle) * distance,
size: random(this.size * 0.3, this.size * 0.7),
noiseScale: random(0.3, 0.8),
noiseOffset: random(100),
connectionWidth: random(this.size * 0.1, this.size * 0.3),
});
// Slightly adjust the main angle for next blob to create a flow
mainAngle += random(-PI / 4, PI / 4);
}
this.splashProgress = 0;
this.splashSpeed = random(0.03, 0.08);
this.splashComplete = false;
// Add some flow properties
this.flowDirection = random(TWO_PI);
this.flowSpeed = random(0.2, 1);
this.flowAmount = 0;
this.maxFlow = random(10, 30);
}
update() {
if (!this.splashComplete) {
// Animate the splash effect
this.splashProgress += this.splashSpeed;
if (this.splashProgress >= 1) {
this.splashComplete = true;
}
} else {
// After splash is complete, add some subtle flow
this.flowAmount += this.flowSpeed;
if (this.flowAmount > this.maxFlow) {
this.flowSpeed = 0;
}
}
}
display() {
push();
// Apply the color with some transparency
let c = this.color;
fill(red(c), green(c), blue(c), 220);
noStroke();
// Draw connections first (underneath)
if (this.splashProgress > 0.3) {
let connectionProgress = map(this.splashProgress, 0.3, 1, 0, 1);
for (let blob of this.blobs) {
// Draw connection between main blob and this blob
this.drawConnection(
this.x,
this.y,
this.x + blob.offsetX * this.splashProgress,
this.y + blob.offsetY * this.splashProgress,
blob.connectionWidth * connectionProgress,
);
}
}
// Draw main blob
let flowOffsetX = cos(this.flowDirection) * this.flowAmount;
let flowOffsetY = sin(this.flowDirection) * this.flowAmount;
this.drawFluidBlob(
this.x + flowOffsetX,
this.y + flowOffsetY,
this.mainBlob.size * this.splashProgress,
this.mainBlob.noiseScale,
this.mainBlob.noiseOffset,
);
// Draw connected blobs
if (this.splashProgress > 0.2) {
let blobProgress = map(this.splashProgress, 0.2, 1, 0, 1);
for (let blob of this.blobs) {
this.drawFluidBlob(
this.x + blob.offsetX * this.splashProgress + flowOffsetX * 0.7,
this.y + blob.offsetY * this.splashProgress + flowOffsetY * 0.7,
blob.size * blobProgress,
blob.noiseScale,
blob.noiseOffset,
);
}
}
pop();
}
drawFluidBlob(x, y, size, noiseScale, noiseOffset) {
push();
translate(x, y);
beginShape();
for (let a = 0; a < TWO_PI; a += 0.1) {
// Use noise to create organic, fluid-like edges
let xoff = map(cos(a), -1, 1, 0, noiseScale);
let yoff = map(sin(a), -1, 1, 0, noiseScale);
let r = map(
noise(xoff + noiseOffset, yoff + noiseOffset, frameCount * 0.01),
0,
1,
size * 0.7,
size,
);
let px = cos(a) * r;
let py = sin(a) * r;
vertex(px, py);
}
endShape(CLOSE);
// Add some smaller circles inside for a more fluid look
for (let i = 0; i < 3; i++) {
let angle = random(TWO_PI);
let distance = random(size * 0.1, size * 0.4);
let smallSize = random(size * 0.1, size * 0.3);
ellipse(
cos(angle) * distance,
sin(angle) * distance,
smallSize,
smallSize,
);
}
pop();
}
drawConnection(x1, y1, x2, y2, width) {
// Draw a smooth connection between two points
push();
// Find the midpoint with some random offset for natural flow
let midX = (x1 + x2) / 2 + random(-width / 2, width / 2);
let midY = (y1 + y2) / 2 + random(-width / 2, width / 2);
// Draw a curved shape to connect the blobs
beginShape();
// Start at first point
vertex(x1, y1);
// Add control points for a natural curve
let controlX1 = x1 + (midX - x1) * 0.5;
let controlY1 = y1 + (midY - y1) * 0.5;
let controlX2 = midX + (x2 - midX) * 0.5;
let controlY2 = midY + (y2 - midY) * 0.5;
// Add points along the curve with some width
for (let t = 0; t <= 1; t += 0.1) {
// Bezier curve calculation
let bx = bezierPoint(x1, controlX1, controlX2, x2, t);
let by = bezierPoint(y1, controlY1, controlY2, y2, t);
// Add perpendicular width
let angle =
atan2(
bezierTangent(y1, controlY1, controlY2, y2, t),
bezierTangent(x1, controlX1, controlX2, x2, t),
) + HALF_PI;
// Width varies along the connection for a more natural look
let currentWidth = width * (1 - abs(t - 0.5) * 1.5);
if (currentWidth > 0) {
vertex(bx + cos(angle) * currentWidth, by + sin(angle) * currentWidth);
}
}
// Go back along the other side
for (let t = 1; t >= 0; t -= 0.1) {
let bx = bezierPoint(x1, controlX1, controlX2, x2, t);
let by = bezierPoint(y1, controlY1, controlY2, y2, t);
let angle =
atan2(
bezierTangent(y1, controlY1, controlY2, y2, t),
bezierTangent(x1, controlX1, controlX2, x2, t),
) + HALF_PI;
let currentWidth = width * (1 - abs(t - 0.5) * 1.5);
if (currentWidth > 0) {
vertex(bx - cos(angle) * currentWidth, by - sin(angle) * currentWidth);
}
}
endShape(CLOSE);
pop();
}
}
function keyPressed() {
if (key === 's') {
saveGif(`mySketch-${round(new Date().getTime() / 100000)}`, 5);
}
if (key === 'c') {
saveCanvas(`mySketch-${round(new Date().getTime() / 100000)}`, 'jpeg');
}
}

Day4: Circle Packing
今日からどんなプロンプトを投げたかも乗せておく
本当は,任意の絵文字を画面の中央に拡大して描画し,それを画像をして認識し,その画像内を円充填したかったが,何度やっても loadPixles() 後の pixels 配列を観ても,絵文字の部分がすべて 255 のままだったため,無理だった.途中からガチで自分自身でデバッグして無理だったので,自分の能力不足のため,どっかでリベンジする.
ということで,違う円充填を描いた.
キャンバス内に quad を用いてランダムな配置とサイズで図形を描き,その内部を4色の円で充填させたもの.

- 一回絵文字からやるのは忘れて,シンプルに円充填をしてみて欲しい
- 円充填がどういうものかそもそも知っていますか?
- デフォルメした牛を横から見た姿の外枠だけを線で描き,その内部を円充填できる?
- 流石に牛の外枠は難しかったと思うので,ランダムな四角形(rect ではなく,quadを使う)をいくつか画面上にランダム配置させ,その内部を円充填して
- isInsideQuad() メソッドの頂点判定処理を書いて
- 色はデザインや色彩のノウハウに基づいた3or4色にして
コード
let W; // キャンバスのサイズ
let circles = []; // 円の配列
let gridSize = 10; // グリッドのサイズ
let maxAttempts = 1000; // 円を配置する試行回数
let quads = [];
let maxQuads = 5; // 四角形の数
let colors = [];
function setup() {
W = min(windowWidth, windowHeight) - 100;
createCanvas(W, W);
background(255);
noStroke();
drawCowOutline();
noLoop();
colors = [
color(41, 128, 185), // ベースカラー(青)
color(39, 174, 96), // アナログスキーム(青緑)
color(142, 68, 173), // アナログスキーム(紫)
color(230, 126, 34), // アクセントカラー(オレンジ)
];
// ランダムな四角形を生成
for (let i = 0; i < maxQuads; i++) {
let x1 = random(width);
let y1 = random(height);
let x2 = random(width);
let y2 = random(height);
let x3 = random(width);
let y3 = random(height);
let x4 = random(width);
let y4 = random(height);
quads.push({ x1, y1, x2, y2, x3, y3, x4, y4 });
}
// 円を配置
for (let i = 0; i < maxAttempts; i++) {
let x = random(width);
let y = random(height);
let r = random(5, 20);
let valid = true;
// 既存の円と重ならないかチェック
for (let circle of circles) {
let d = dist(x, y, circle.x, circle.y);
if (d < r + circle.r) {
valid = false;
break;
}
}
// 四角形の内部にあるかチェック
if (valid && isInsideQuad(x, y)) {
circles.push({ x, y, r, color: colors[floor(random(colors.length))] });
}
}
}
function draw() {
background(255);
drawCowOutline();
// 四角形を描画
console.log(quads);
for (let item of quads) {
quad(
item.x1,
item.y1,
item.x2,
item.y2,
item.x3,
item.y3,
item.x4,
item.y4,
);
}
push();
for (let circle of circles) {
fill(circle.color);
ellipse(circle.x, circle.y, circle.r * 2);
}
pop();
}
// 円のサイズを決定する関数
function getCircleSize(x, y) {
let minSize = 2;
let maxSize = gridSize;
// ここで円の大きさを決定するロジックを追加
return random(minSize, maxSize);
}
// 点が四角形の内部にあるかチェックする関数
function isInsideQuad(x, y) {
let inside = false;
for (let quad of quads) {
// 四角形の頂点を配列に格納
let vertices = [
{ x: quad.x1, y: quad.y1 },
{ x: quad.x2, y: quad.y2 },
{ x: quad.x3, y: quad.y3 },
{ x: quad.x4, y: quad.y4 },
];
// Ray Casting Algorithm
let j = vertices.length - 1;
for (let i = 0; i < vertices.length; i++) {
let xi = vertices[i].x;
let yi = vertices[i].y;
let xj = vertices[j].x;
let yj = vertices[j].y;
// 点が辺の上にある場合は内部と判定
if (xi === xj && yi === yj) continue;
if (xi === x && yi === y) return true;
// 辺と水平線の交差判定
let intersect =
yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
if (intersect) inside = !inside;
j = i;
}
if (inside) return true;
}
return false;
}
function drawCowOutline() {
beginShape();
// 牛の頭部
vertex(100, 100);
vertex(150, 50);
vertex(200, 100);
// 牛の胴体
vertex(250, 150);
vertex(300, 200);
vertex(250, 250);
// 牛の後ろ足
vertex(200, 300);
vertex(150, 250);
// 牛の前足
vertex(100, 200);
vertex(50, 150);
endShape(CLOSE);
}
function keyPressed() {
if (key === 'c') {
saveCanvas('myCanvas', 'png');
}
}
Day5: simple animation with trigonometric functions
アイディアそのものが全く浮かばなかったので,ちょっと置きに行った作品.
困ったら誰かの作品を模倣するところから言っても良いかもなぁ.
- ドットを使ってアニメーションを描いてください.三角関数を複数組み合わせて,美しい動きをして欲しい
- あまりにも単調な動きのため,もっと三角関数を複数回組み合わせて動きも複雑化して
- もっと複雑な動きをして欲しい.ダイナミックさも付け加えて
- elilpseを絵文字にしたのですが,進行方向に向かって角度を自動で変更して欲しい

コード
let time = 0;
const numDots = 80;
let baseRadius;
// 動的なパラメータ
let pulse = 0;
let rotationSpeed = 0.3;
let expansionFactor = 1;
// 前フレームの位置を保存する配列
let prevPositions = [];
function setup() {
createCanvas((W = min(windowWidth, windowHeight) - 300), W);
baseRadius = W / 4.5;
textSize(W / 32);
textAlign(CENTER, CENTER);
}
function draw() {
background(255);
// 動的なパラメータの更新
pulse = sin(time * 0.5) * 0.2 + 1; // 0.8から1.2の間で脈動
rotationSpeed = 0.3 + sin(time * 0.2) * 0.1; // 回転速度の変化
expansionFactor = 1 + sin(time * 0.3) * 0.3; // 全体の拡大縮小
push();
translate(width / 2, height / 2);
// 現在の位置を保存する配列
let currentPositions = [];
for (let i = 0; i < numDots; i++) {
const angle = (i / numDots) * TWO_PI;
// より複雑な半径の変化
const radiusVariation =
sin(time + angle * 3) * 30 * pulse +
cos(time * 0.7 + angle * 2) * 20 +
sin(time * 1.3 + angle * 4) * 15 +
cos(time * 0.4 + angle * 5) * 10 +
sin(time * 2.1 + angle * 6) * 8;
const radius = (baseRadius + radiusVariation) * expansionFactor;
// より複雑な位置計算
const xOffset =
sin(time * 0.5 + angle * 2) * 40 * pulse +
cos(time * 0.3 + angle * 3) * 30 +
sin(time * 1.2 + angle * 4) * 20 +
cos(time * 0.8 + angle * 5) * 15;
const yOffset =
cos(time * 0.7 + angle * 2) * 40 * pulse +
sin(time * 0.4 + angle * 3) * 30 +
cos(time * 1.5 + angle * 4) * 20 +
sin(time * 0.9 + angle * 5) * 15;
const x = cos(angle + time * rotationSpeed) * radius + xOffset;
const y = sin(angle + time * rotationSpeed) * radius + yOffset;
// 現在の位置を保存
currentPositions[i] = { x, y };
// より複雑な色の変化
const hue =
(i * 2 +
time * 30 +
sin(time + i * 0.1) * 30 +
cos(time * 0.5 + i * 0.2) * 20) %
360;
const saturation =
70 + sin(time * 2 + i * 0.05) * 30 + cos(time * 1.2 + i * 0.1) * 15;
const brightness =
80 + cos(time * 1.5 + i * 0.03) * 20 + sin(time * 0.8 + i * 0.07) * 15;
fill(hue, saturation, brightness);
noStroke();
// 回転角度の計算
let rotation = 0;
if (prevPositions[i]) {
const dx = x - prevPositions[i].x;
const dy = y - prevPositions[i].y;
rotation = atan2(dy, dx);
}
// はさみの絵文字を回転させて描画
push();
translate(x, y);
rotate(rotation);
text('✄', 0, 0);
pop();
}
// 前フレームの位置を更新
prevPositions = currentPositions;
pop();
time += 0.03;
}
function keyPressed() {
if (key === 's') {
saveGif(`mySketch-${round(new Date().getTime() / 100000)}`, 4);
}
if (key === 'c') {
saveCanvas(`mySketch-${round(new Date().getTime() / 100000)}`, 'jpeg');
}
}
Day6: Dotts Image
ドットによる点描のアニメーションや画像は地味に好きなのでなにかないかなーということで書いてみた.
改良の余地はあるが,眠すぎるのでこれが今の実力ということで,明日以降の俺に託す.
- p5jsを用いて点描でなにか作品を作ってみたい.デフォルメした犬のシュナウザーを横から見た姿を描ける?
- 添付画像のように割といい感じのコードでした!これをもっとシンプルに,シルエットのようにして欲しい
- おしいが,手足が単なる長方形になってしまっているので,もう少し犬の足に近づけて.顔も,斜め左下方向への長方形で,角丸に
- コードが途中で途切れたので続けて
途中経過のものもすべて添付



コード
function setup() {
createCanvas(600, 400);
background(240);
noLoop();
}
function draw() {
let silhouetteColor = color(50); // シルエットの色 (濃いグレー)
// 点描の密度や点の大きさを調整するパラメータ
let densityFactor = 1.3; // 点の密度を少し上げる
let dotMinSize = 2;
let dotMaxSize = 5; // 少し点の最大サイズを上げてみる
// 元の各パーツの密度目安
let baseDensity = 1000;
// --- シュナウザーのシルエット描画 ---
// 体 (少し調整)
drawDottedEllipse(
width / 2,
height / 2 + 35,
200,
130,
silhouetteColor,
baseDensity * 1.5 * densityFactor,
dotMinSize,
dotMaxSize,
);
// 顔 (斜め左下方向への角丸長方形)
push();
translate(width / 2 - 105, height / 2 - 30); // 顔の中心位置を調整
rotate(radians(-25)); // 回転角度を調整
// drawDottedRoundedRect のx,yは回転後のローカル座標なので0,0でOK
drawDottedRoundedRect(
0,
0,
90,
60,
15,
silhouetteColor,
baseDensity * 0.9 * densityFactor,
dotMinSize,
dotMaxSize,
);
pop();
// マズル (顔と一体化するように位置調整)
// 顔の回転と位置に合わせて調整が必要
// push/popとtranslate/rotateで顔と一緒に描画する方が連動しやすいが、ここでは個別に配置
push();
translate(width / 2 - 130, height / 2 + 5); // マズルの基準点
rotate(radians(-10)); // マズルも少し傾ける
drawDottedRectangle(
0,
0,
70,
40,
silhouetteColor,
baseDensity * 0.7 * densityFactor,
dotMinSize,
dotMaxSize,
);
pop();
// 耳 (垂れ耳風シルエット)
push();
translate(width / 2 - 90, height / 2 - 65); // 位置調整
rotate(radians(-30)); // 角度調整
drawDottedEllipse(
0,
0,
45,
75,
silhouetteColor,
baseDensity * 0.5 * densityFactor,
dotMinSize,
dotMaxSize,
);
pop();
// 前足1
drawDottedLeg(
width / 2 - 55,
height / 2 + 70,
35,
55,
28,
48,
10,
silhouetteColor,
baseDensity * 0.45 * densityFactor,
dotMinSize,
dotMaxSize,
);
// 前足2
drawDottedLeg(
width / 2 - 20,
height / 2 + 70,
35,
55,
28,
48,
-5,
silhouetteColor,
baseDensity * 0.45 * densityFactor,
dotMinSize,
dotMaxSize,
);
// 後ろ足1 (太もも部分がより角度がつくように)
drawDottedLeg(
width / 2 + 55,
height / 2 + 60,
45,
65,
35,
55,
-35,
silhouetteColor,
baseDensity * 0.5 * densityFactor,
dotMinSize,
dotMaxSize,
);
// 後ろ足2 (少し奥にあるように見せるため、サイズや位置を微調整)
drawDottedLeg(
width / 2 + 85,
height / 2 + 65,
42,
62,
33,
53,
-45,
silhouetteColor,
baseDensity * 0.45 * densityFactor,
dotMinSize,
dotMaxSize,
);
// 尻尾
push();
translate(width / 2 + 105, height / 2 + 15); // 位置調整
rotate(radians(30)); // 角度調整
drawDottedEllipse(
0,
0,
25,
45,
silhouetteColor,
baseDensity * 0.3 * densityFactor,
dotMinSize,
dotMaxSize,
);
pop();
}
// 指定された範囲に点を描画するヘルパー関数 (楕円)
function drawDottedEllipse(x, y, w, h, col, numPoints, minSize, maxSize) {
// 楕円の中心をx,yとする
for (let i = 0; i < numPoints; i++) {
let angle = random(TWO_PI);
// 楕円内部に均等に分布させるための工夫
let rScale = sqrt(random()); // sqrt(random()) にすると中心付近の密度が下がり、より均一に近くなる
let rX = (w / 2) * rScale;
let rY = (h / 2) * rScale;
let dotX = x + cos(angle) * rX;
let dotY = y + sin(angle) * rY;
let dSize = random(minSize, maxSize);
fill(col);
noStroke();
ellipse(dotX, dotY, dSize, dSize);
}
}
// 指定された範囲に点を描画するヘルパー関数 (四角形)
function drawDottedRectangle(x, y, w, h, col, numPoints, minSize, maxSize) {
// 四角形の左上をx,yとする
for (let i = 0; i < numPoints; i++) {
let dotX = random(x, x + w);
let dotY = random(y, y + h);
let dSize = random(minSize, maxSize);
fill(col);
noStroke();
ellipse(dotX, dotY, dSize, dSize);
}
}
// 指定された範囲に点を描画するヘルパー関数 (角丸四角形)
// rectX, rectY は現在の描画コンテキストでの角丸四角形の左上の座標(通常は0,0 nếu translate後)
function drawDottedRoundedRect(
rectX,
rectY,
w,
h,
r,
col,
numPoints,
minSize,
maxSize,
) {
r = min(r, w / 2, h / 2); // 半径が幅や高さの半分を超えないように調整
let drawnPoints = 0;
let maxAttempts = numPoints * 10; // 無限ループを避けるための最大試行回数
for (let i = 0; i < maxAttempts && drawnPoints < numPoints; i++) {
// 角丸四角形のバウンディングボックス内でランダムな点を生成 (ローカル座標)
let px = random(w); // rectX から rectX + w ではなく、ローカルの 0 から w
let py = random(h); // rectY から rectY + h ではなく、ローカルの 0 から h
let inside = false;
// 点が角丸四角形の内部にあるか判定
if (px > r && px < w - r) {
// 中央の垂直帯内部(yは全長OK)
inside = true;
} else if (py > r && py < h - r) {
// 中央の水平帯内部(xは全長OK)
inside = true;
} else {
// 角の領域にある可能性
if (px <= r && py <= r) {
// 左上角
if (dist(px, py, r, r) <= r) inside = true;
} else if (px >= w - r && py <= r) {
// 右上角
if (dist(px, py, w - r, r) <= r) inside = true;
} else if (px <= r && py >= h - r) {
// 左下角
if (dist(px, py, r, h - r) <= r) inside = true;
} else if (px >= w - r && py >= h - r) {
// 右下角
if (dist(px, py, w - r, h - r) <= r) inside = true;
}
}
if (inside) {
let dSize = random(minSize, maxSize);
fill(col);
noStroke();
// 描画はローカル座標 (px, py) で行う。`translate()` で既に位置決めされている前提。
ellipse(rectX + px, rectY + py, dSize, dSize); // 描画は rectX, rectY を基準にオフセット
drawnPoints++;
}
}
}
// 改良された足の描画関数 (2つの楕円セグメントで構成)
// baseX, baseY: 足の付け根の回転軸となる位置
// upperW, upperH: 上部セグメントの幅と高さ
// lowerW, lowerH: 下部セグメントの幅と高さ
// legAngle: 足全体の基準線からの傾き (度数法)
function drawDottedLeg(
baseX,
baseY,
upperW,
upperH,
lowerW,
lowerH,
legAngle,
col,
numPoints,
minSize,
maxSize,
) {
push();
translate(baseX, baseY); // 足の付け根に原点を移動
rotate(radians(legAngle)); // 足全体を指定された角度だけ回転
// 上部セグメント (例: 太もも)
// 回転軸(0,0)から下に伸びるように、中心を(0, upperH/2)に配置
drawDottedEllipse(
0,
upperH / 2,
upperW,
upperH,
col,
floor(numPoints * 0.6),
minSize,
maxSize,
);
// 下部セグメント (例: すね + 足先)
// 上部セグメントの終端あたりから始まるように配置
// Y座標のオフセット: 上部セグメントの高さ + 下部セグメントの高さの半分
// 少し重なるように upperH * 0.85 などで調整可能
let lowerSegmentCenterY = upperH * 0.9 + (lowerH / 2) * 0.8; // Y位置を微調整して接続を自然に
let lowerSegmentOffsetX = upperW * 0.05; // X位置を微調整して「く」の字に(お好みで)
drawDottedEllipse(
lowerSegmentOffsetX,
lowerSegmentCenterY,
lowerW,
lowerH,
col,
floor(numPoints * 0.4),
minSize,
maxSize,
);
pop();
}
// キーボード操作
function keyPressed() {
const timestamp = round(new Date().getTime() / 100000);
saveCanvas(`screenshot-${timestamp}`, 'jpeg');
}
Day7: easing animation
プロンプトは以前のものがなくなってしまったので今回はなしで.
Okazz さんの様なアニメーションをしてみたかったので,ちょっとコードはパクった.
といってもまだ不完全さは残る.

コード
const ANIMATION_SPAN = 200; // アニメーションの長さ
const GRID_SIZE = 10; // グリッドのサイズ
const SCALE_FACTOR = 0; // 最小サイズを0に設定
const DELAY_FACTOR = 5; // 隣接する正方形間の遅延
// キャンバス設定
let canvasSize;
let cellSize;
const rectangles = [];
function setup() {
canvasSize = min(windowWidth, windowHeight) - 200;
createCanvas(canvasSize, canvasSize);
noStroke();
rectMode(CENTER);
// セルのサイズを計算
cellSize = canvasSize / GRID_SIZE;
// グリッド状に正方形を配置
for (let i = 0; i < GRID_SIZE; i++) {
for (let j = 0; j < GRID_SIZE; j++) {
// 格子状に配置
const x = i * cellSize + cellSize / 2;
const y = j * cellSize + cellSize / 2;
// 位置による遅延を設定 (左上から右下へ)
const delay = (i + j) * DELAY_FACTOR;
rectangles.push(new AnimatedRectangle(x, y, cellSize, cellSize, delay));
}
}
}
function draw() {
background(255);
// すべての長方形を更新して描画
for (const rect of rectangles) {
rect.update();
rect.display();
}
}
class AnimatedRectangle {
constructor(x, y, width, height, delay) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.delay = delay;
this.timer = 0;
this.direction = 1; // 1: 増加, -1: 減少
this.animationProgress = 0;
}
update() {
// 遅延後にアニメーションを開始
if (frameCount < this.delay) {
this.animationProgress = 1; // 最初は最大サイズ
return;
}
// タイマーを更新
this.timer += this.direction;
// アニメーション方向を切り替え
if (this.timer >= ANIMATION_SPAN) {
this.direction = -1;
} else if (this.timer <= 0) {
this.direction = 1;
}
// イージング関数を適用してアニメーション進捗を計算
const normalizedTime =
this.direction === -1
? norm(this.timer, 0, ANIMATION_SPAN)
: norm(this.timer, ANIMATION_SPAN, 0);
this.animationProgress =
this.direction === 1
? easeInOutQuint(normalizedTime)
: easeInOutQuint(1 - normalizedTime);
}
display() {
// サイズをアニメーション進捗に基づいて補間
const currentWidth =
lerp(SCALE_FACTOR, 1, this.animationProgress) * this.width;
const currentHeight =
lerp(SCALE_FACTOR, 1, this.animationProgress) * this.height;
fill(0);
rect(this.x, this.y, currentWidth, currentHeight);
}
}
// イージング関数(5次元の入出力補間)
function easeInOutQuint(x) {
return x < 0.5 ? 16 * x * x * x * x * x : 1 - pow(-2 * x + 2, 5) / 2;
}
// キーボード操作
function keyPressed() {
if (key === 's') {
saveGif(`animation-${round(new Date().getTime() / 100000)}`, 8);
} else if (key === 'c') {
saveCanvas(`screenshot-${round(new Date().getTime() / 100000)}`, 'jpeg');
}
}
Day8: Colorful interactive and animation
最近は Claude Code で書かせることが増えてきた.𝕏 からパクったプロンプトをそのまま Claude Code で実行した形.
Create an amazing animation multicolor and interactive using p5js

- マウス操作に応じて,背景のドットの動きが変化する(左右のクリックで若干違いもある)
- あとは,
s,c, r, pキーごとに違う挙動が設定されていたりと
結果としては,自分の普段書くクセなども盛り込んだコードになっており,なかなかやるなと感心している.
コードは長いのでこちらをご参照
Day9: Pop Image
まさに小さな子どもの服やおもちゃなどの柄に使われそうな作品が出来上がった.今回も Claude Code に書かせてみた.ちなみにプロンプトは2つで微調整をした形.
- Create an pop and multicolor image using p5js
- Textbooks are not required. Place the objects in a grid. The shape and size of the objects are random

コード
let W;
let popColors;
let grid;
let shapes = [];
function setup() {
W = min(windowWidth, windowHeight) - 50;
createCanvas(W, W);
// Pop art inspired color palette
popColors = [
'#FF006E', // Hot Pink
'#FFBE0B', // Electric Yellow
'#FB5607', // Bright Orange
'#8338EC', // Electric Purple
'#3A86FF', // Bright Blue
'#06FFA5', // Electric Green
'#FF4081', // Pink
'#00E676', // Green
'#FF5722', // Deep Orange
'#E91E63', // Pink
'#9C27B0', // Purple
'#2196F3', // Blue
'#4CAF50', // Green
'#FF9800', // Orange
'#F44336', // Red
'#FFEB3B', // Yellow
'#00BCD4', // Cyan
'#795548', // Brown
];
noLoop(); // Static image
createPopArtComposition();
}
function draw() {
// Create clean background
background(255);
// Draw geometric pop art elements in grid
drawPopArtShapes();
}
function createPopArtComposition() {
// Create grid of pop art elements with random shapes and sizes
let cols = 8;
let rows = 8;
let cellW = W / cols;
let cellH = W / rows;
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
let x = i * cellW;
let y = j * cellH;
// Random size variations
let sizeVariation = random(0.3, 0.9);
let w = cellW * sizeVariation;
let h = cellH * sizeVariation;
// Sometimes make shapes very different aspect ratios
if (random() < 0.3) {
w = cellW * random(0.2, 0.9);
h = cellH * random(0.2, 0.9);
}
shapes.push({
x: x + cellW / 2,
y: y + cellH / 2,
w: w,
h: h,
type: random([
'circle',
'rect',
'triangle',
'star',
'hexagon',
'diamond',
'pentagon',
]),
color: random(popColors),
rotation: random(TWO_PI),
});
}
}
}
function drawPopArtShapes() {
for (let shape of shapes) {
push();
translate(shape.x, shape.y);
rotate(shape.rotation);
// Drop shadow effect
fill(0, 100);
noStroke();
drawShape(shape.type, 5, 5, shape.w, shape.h);
// Main shape
fill(shape.color);
stroke(0);
strokeWeight(4);
drawShape(shape.type, 0, 0, shape.w, shape.h);
// Highlight effect
fill(255, 150);
noStroke();
drawShape(shape.type, -2, -2, shape.w * 0.3, shape.h * 0.3);
pop();
}
}
function drawShape(type, x, y, w, h) {
push();
translate(x, y);
switch (type) {
case 'circle':
ellipse(0, 0, w, h);
break;
case 'rect':
rectMode(CENTER);
rect(0, 0, w, h, 10);
break;
case 'triangle':
triangle(-w / 2, h / 2, w / 2, h / 2, 0, -h / 2);
break;
case 'star':
drawStar(0, 0, w / 4, w / 2, 5);
break;
case 'hexagon':
drawHexagon(0, 0, w / 2);
break;
case 'diamond':
drawDiamond(0, 0, w / 2, h / 2);
break;
case 'pentagon':
drawPentagon(0, 0, w / 2);
break;
}
pop();
}
function drawStar(x, y, radius1, radius2, npoints) {
let angle = TWO_PI / npoints;
let halfAngle = angle / 2.0;
beginShape();
for (let a = 0; a < TWO_PI; a += angle) {
let sx = x + cos(a) * radius2;
let sy = y + sin(a) * radius2;
vertex(sx, sy);
sx = x + cos(a + halfAngle) * radius1;
sy = y + sin(a + halfAngle) * radius1;
vertex(sx, sy);
}
endShape(CLOSE);
}
function drawHexagon(x, y, radius) {
beginShape();
for (let i = 0; i < 6; i++) {
let angle = (i * TWO_PI) / 6;
let sx = x + cos(angle) * radius;
let sy = y + sin(angle) * radius;
vertex(sx, sy);
}
endShape(CLOSE);
}
function drawDiamond(x, y, w, h) {
beginShape();
vertex(x, y - h);
vertex(x + w, y);
vertex(x, y + h);
vertex(x - w, y);
endShape(CLOSE);
}
function drawPentagon(x, y, radius) {
beginShape();
for (let i = 0; i < 5; i++) {
let angle = (i * TWO_PI) / 5 - PI / 2;
let sx = x + cos(angle) * radius;
let sy = y + sin(angle) * radius;
vertex(sx, sy);
}
endShape(CLOSE);
}
function mousePressed() {
// Regenerate the composition
shapes = [];
createPopArtComposition();
redraw();
}
function keyPressed() {
if (key === 'c') {
saveCanvas(`pop-art-${round(new Date().getTime() / 100000)}`, 'jpeg');
}
if (key === 'r') {
// Regenerate composition
shapes = [];
createPopArtComposition();
redraw();
}
}
Day10: Symbiosis of digital(images) and reality
タイトル通りだが,やってみたかったというか,個人的に追っているものの一つに.デジタルアートと現実を上手く同居したような作品を考えていて,その一環の一つ.
指定した画像をピクセルで管理し,デジタルでまばらなピクセルと1ピクセルできっちり指定した画像を表示することで現実っぽい描写.
今回の作品は OpenProcessing にも投稿済み

コード
let img;
let pixelData = [];
let canvasSize;
let isVertical = false;
function setup() {
canvasSize = min(windowWidth, windowHeight) - 200;
let canvas = createCanvas(canvasSize, canvasSize);
canvas.parent('canvas-container');
noLoop();
background(255);
textAlign(CENTER, CENTER);
textSize(16);
fill(100);
text('Upload an image to begin', width / 2, height / 2);
let imageInput = select('#imageInput');
imageInput.changed(handleImageUpload);
}
function draw() {
if (!img || !img.width) {
return;
}
background(255);
drawPixelatedImage();
}
function handleImageUpload() {
let input = select('#imageInput').elt;
if (input.files && input.files[0]) {
let file = input.files[0];
let reader = new FileReader();
reader.onload = function (e) {
img = loadImage(e.target.result, imageLoaded);
};
reader.readAsDataURL(file);
}
}
function imageLoaded() {
if (!img) return;
isVertical = img.height > img.width;
processImagePixels();
redraw();
}
function processImagePixels() {
pixelData = [];
img.loadPixels();
let scaleFactor = min(canvasSize / img.width, canvasSize / img.height);
let displayWidth = img.width * scaleFactor;
let displayHeight = img.height * scaleFactor;
let offsetX = (canvasSize - displayWidth) / 2;
let offsetY = (canvasSize - displayHeight) / 2;
let minRes = 1;
let maxRes = 30;
if (isVertical) {
for (let y = 0; y < displayHeight; ) {
let progress = 1 - y / displayHeight;
let pixelSize =
progress > 0.7 ? minRes : lerp(maxRes, minRes, progress / 0.7);
for (let x = 0; x < displayWidth; x += pixelSize) {
let sourceX = int(x / scaleFactor);
let sourceY = int(y / scaleFactor);
if (
sourceX >= 0 &&
sourceX < img.width &&
sourceY >= 0 &&
sourceY < img.height
) {
let index = (sourceY * img.width + sourceX) * 4;
let r = img.pixels[index];
let g = img.pixels[index + 1];
let b = img.pixels[index + 2];
pixelData.push({
x: x + offsetX,
y: y + offsetY,
color: color(r, g, b),
size: pixelSize,
progress: progress,
});
}
}
y += pixelSize;
}
} else {
for (let x = 0; x < displayWidth; ) {
let progress = 1 - x / displayWidth;
let pixelSize =
progress > 0.7 ? minRes : lerp(maxRes, minRes, progress / 0.7);
for (let y = 0; y < displayHeight; y += pixelSize) {
let sourceX = int(x / scaleFactor);
let sourceY = int(y / scaleFactor);
if (
sourceX >= 0 &&
sourceX < img.width &&
sourceY >= 0 &&
sourceY < img.height
) {
let index = (sourceY * img.width + sourceX) * 4;
let r = img.pixels[index];
let g = img.pixels[index + 1];
let b = img.pixels[index + 2];
pixelData.push({
x: x + offsetX,
y: y + offsetY,
color: color(r, g, b),
size: pixelSize,
progress: progress,
});
}
}
x += pixelSize;
}
}
}
function drawPixelatedImage() {
if (pixelData.length === 0) return;
for (let pixel of pixelData) {
let progress = pixel.progress;
let pixelColor = pixel.color;
if (
brightness(pixelColor) > 90 &&
red(pixelColor) > 240 &&
green(pixelColor) > 240 &&
blue(pixelColor) > 240
) {
pixelColor = color(255, 255, 255);
}
if (progress > 0.7) {
fill(pixelColor);
noStroke();
rect(pixel.x, pixel.y, pixel.size, pixel.size);
} else {
let normalizedProgress = progress / 0.7;
let fillAlpha = map(normalizedProgress, 0, 1, 0, 255);
fillAlpha = constrain(fillAlpha, 0, 255);
let strokeOpacity = map(normalizedProgress, 0, 1, 255, 100);
let strokeColor = lerpColor(color(0), pixelColor, normalizedProgress);
strokeColor = color(
red(strokeColor),
green(strokeColor),
blue(strokeColor),
strokeOpacity,
);
stroke(strokeColor);
strokeWeight(map(normalizedProgress, 0, 1, 1, 0.3));
fill(red(pixelColor), green(pixelColor), blue(pixelColor), fillAlpha);
drawGradualRect(
pixel.x,
pixel.y,
pixel.size,
pixel.size,
normalizedProgress,
);
}
}
}
function drawGradualRect(x, y, w, h, progress) {
let maxJitter = 3;
let jitterAmount = map(progress, 0, 1, maxJitter, 0);
if (progress < 0.9) {
let corners = [
{
x: x + random(-jitterAmount, jitterAmount),
y: y + random(-jitterAmount, jitterAmount),
},
{
x: x + w + random(-jitterAmount, jitterAmount),
y: y + random(-jitterAmount, jitterAmount),
},
{
x: x + w + random(-jitterAmount, jitterAmount),
y: y + h + random(-jitterAmount, jitterAmount),
},
{
x: x + random(-jitterAmount, jitterAmount),
y: y + h + random(-jitterAmount, jitterAmount),
},
];
beginShape();
for (let corner of corners) {
vertex(corner.x, corner.y);
}
endShape(CLOSE);
} else {
rect(x, y, w, h);
}
}
function keyPressed() {
if (key === 's') {
saveGif(`photo-pixel-gradient-${round(new Date().getTime() / 100000)}`, 1);
}
if (key === 'c') {
saveCanvas(
`photo-pixel-gradient-${round(new Date().getTime() / 100000)}`,
'jpeg',
);
}
}
Day11: BATTLE
本当はミルククラウンを描きたかったが,プロンプトが下手すぎてうまく描けず.引き続き言語化に挑戦する.

コード
let points = [];
let cp = [];
let seed;
const numPoints = 500;
const volumeSize = 500;
const noiseScale = 0.8;
const sphereRadius = 2.5;
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
cp = random(colorPalette).colors;
seed = random(1000);
let xOffset = random(1000);
let yOffset = random(1000);
let zOffset = random(1000);
for (let i = 0; i < numPoints; i++) {
let x = map(noise(xOffset), 0, 1, -volumeSize, volumeSize);
let y = map(noise(yOffset), 0, 1, -volumeSize, volumeSize);
let z = map(noise(zOffset), 0, 1, -volumeSize, volumeSize);
points.push(createVector(x, y, z));
xOffset += noiseScale;
yOffset += noiseScale;
zOffset += noiseScale;
}
const blendAmount = numPoints / 4;
for (let i = 0; i < blendAmount; i++) {
const ratio = i / blendAmount;
const index = numPoints - blendAmount + i;
points[index] = p5.Vector.lerp(points[index], points[0], ratio);
}
}
function draw() {
background(255);
randomSeed(seed);
orbitControl();
noFill();
beginShape();
for (const p of points) {
stroke(random(cp));
vertex(p.x, p.y, p.z);
}
vertex(points[0].x, points[0].y, points[0].z);
endShape();
noStroke();
fill(50);
for (const p of points) {
push();
translate(p.x, p.y, p.z);
stroke(cp[3]);
sphere(sphereRadius);
pop();
}
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}
function keyPressed() {
if (key === 'c') {
saveCanvas(
`dense-geometric-pattern-${round(new Date().getTime() / 100000)}`,
'jpeg',
);
}
}
Day12: with Torus Knot
タイトル通り,Torus knot をベースに,たくさんの点が流れるような動きでドーナツを魅せる感じにしたかったのだが,一本のつながった点描による線がドーナツを表示する形になってしまい,なんかうまいプロンプトが思いつかなかったので置きにいってしまった⋯
コード
// Torus Knot Configuration
class TorusKnotConfig {
constructor(R, r, p, q) {
this.R = R; // Major radius
this.r = r; // Minor radius
this.p = p; // Knot parameter p
this.q = q; // Knot parameter q
this.noiseScale = random(0.001, 0.005); // Noise scale for irregularity
this.noiseOffset = random(1000); // Unique noise offset for each knot
}
}
// Particle class for better organization
class Particle {
constructor(color, offset, knotIndex) {
this.color = color;
this.offset = offset;
this.knotIndex = knotIndex;
this.phase = random(TWO_PI);
this.rotationSpeedX = random(0.05, 0.15);
this.rotationSpeedY = random(0.05, 0.15);
this.rotationSpeedZ = random(0.05, 0.15);
this.rotationX = random(TWO_PI);
this.rotationY = random(TWO_PI);
this.rotationZ = random(TWO_PI);
this.size = random(12, 20);
this.pulseSpeed = random(0.02, 0.05);
this.pulseOffset = random(TWO_PI);
}
update(t, knot) {
this.rotationX += this.rotationSpeedX;
this.rotationY += this.rotationSpeedY;
this.rotationZ += this.rotationSpeedZ;
}
getPosition(t, knot) {
// Base torus knot position
let pos = torusKnot(t, knot);
// Add Perlin noise for irregularity
let noiseX = noise(t * knot.noiseScale + knot.noiseOffset, 0) * 50 - 25;
let noiseY = noise(t * knot.noiseScale + knot.noiseOffset, 100) * 50 - 25;
let noiseZ = noise(t * knot.noiseScale + knot.noiseOffset, 200) * 50 - 25;
pos.x += noiseX;
pos.y += noiseY;
pos.z += noiseZ;
return pos;
}
display(t, knot) {
let pos = this.getPosition(t, knot);
// Pulsing size effect
let pulseSize =
this.size + sin(frameCount * this.pulseSpeed + this.pulseOffset) * 3;
// Distance-based alpha (fade distant particles)
let distance = dist(0, 0, 0, pos.x, pos.y, pos.z);
let alpha = map(distance, 0, 500, 255, 100);
let displayColor = color(
red(this.color),
green(this.color),
blue(this.color),
// alpha,
);
stroke(displayColor);
strokeWeight(0.5);
// fill(displayColor);
push();
translate(pos.x, pos.y, pos.z);
rotateX(this.rotationX);
rotateY(this.rotationY);
rotateZ(this.rotationZ);
box(pulseSize);
pop();
}
}
// Global variables
let knotConfigs = [];
let particles = [];
let speed = 0.002;
let numParticlesPerKnot = 80;
let trails = [];
// Color palettes
let colorPalettes = [];
let currentPalette = 0;
// Camera control
let camX = 0;
let camY = 0;
function setup() {
createCanvas(
min(windowWidth, windowHeight) - 50,
min(windowWidth, windowHeight) - 50,
WEBGL,
);
colorPalettes = [
[color(255, 150, 0), color(0, 200, 255), color(255, 0, 150)],
[color(255, 100, 100), color(100, 150, 255), color(150, 255, 150)],
[color(255, 200, 100), color(200, 100, 255), color(100, 255, 200)],
[color(255, 255, 255), color(200, 200, 255), color(255, 200, 200)],
];
// Initialize multiple torus knot configurations
initializeKnots();
// Initialize particles for each knot
initializeParticles();
}
function initializeKnots() {
knotConfigs = [];
// Create 3-5 different torus knots with varying parameters
let numKnots = floor(random(3, 6));
for (let i = 0; i < numKnots; i++) {
let R = random(200, 350);
let r = random(50, 100);
let p = floor(random(2, 5));
let q = floor(random(5, 10));
knotConfigs.push(new TorusKnotConfig(R, r, p, q));
}
}
function initializeParticles() {
particles = [];
for (let k = 0; k < knotConfigs.length; k++) {
let palette = colorPalettes[currentPalette];
for (let i = 0; i < numParticlesPerKnot; i++) {
let particleColor = palette[floor(random(palette.length))];
let offset = (i / numParticlesPerKnot) * TWO_PI;
particles.push(new Particle(particleColor, offset, k));
}
}
}
function draw() {
// Semi-transparent background for trail effect
background(25);
rotateX(PI / 6);
// Lighting
ambientLight(80);
pointLight(255, 255, 255, 400, -400, 400);
pointLight(150, 150, 255, -400, 400, -400);
// Camera control
let camDist = 800 + sin(frameCount * 0.01) * 100; // Slight zoom animation
camera(
camX + sin(frameCount * 0.005) * 50,
camY + cos(frameCount * 0.003) * 50,
camDist,
0,
0,
0,
0,
1,
0,
);
// Draw all particles
for (let particle of particles) {
let knot = knotConfigs[particle.knotIndex];
let t = particle.offset + frameCount * speed;
particle.update(t, knot);
particle.display(t, knot);
}
// Draw subtle wireframe reference
if (frameCount % 60 < 30) {
drawKnotWireframes();
}
}
function drawKnotWireframes() {
noFill();
strokeWeight(0.3);
for (let k = 0; k < knotConfigs.length; k++) {
stroke(255, 255, 255, 20);
beginShape();
let steps = 200;
for (let i = 0; i <= steps; i++) {
let t = (i / steps) * TWO_PI;
let pos = torusKnot(t, knotConfigs[k]);
vertex(pos.x, pos.y, pos.z);
}
endShape(CLOSE);
}
}
// Torus Knot parametric equations
function torusKnot(t, knot) {
let x = (knot.R + knot.r * cos(knot.q * t)) * cos(knot.p * t);
let y = (knot.R + knot.r * cos(knot.q * t)) * sin(knot.p * t);
let z = knot.r * sin(knot.q * t);
return { x: x, y: y, z: z };
}
function keyPressed() {
// Save functions
if (key === 's') {
saveGif(`torusKnot-${round(new Date().getTime() / 100000)}`, 5);
}
if (key === 'c') {
saveCanvas(`torusKnot-${round(new Date().getTime() / 100000)}`, 'jpeg');
}
// Regenerate with new parameters
if (key === 'r') {
initializeKnots();
initializeParticles();
}
// Change color palette
if (key === 'p' || key === 'P') {
currentPalette = (currentPalette + 1) % colorPalettes.length;
initializeParticles();
}
// Toggle wireframe
if (key === 'w' || key === 'W') {
// Already implemented as animation
}
}
function windowResized() {
let size = min(windowWidth, windowHeight) - 50;
resizeCanvas(size, size);
}
Day13: Random MRT Network Map

code
// ===========================
// Config
// ===========================
let W,
routes = [],
gridSize,
hubPoints = [];
const GRID = { edgeMin: 2, edgeMax: 12, hubMin: 4, hubMax: 10, cells: 14 };
const ROUTE = {
min: 7,
max: 10,
waypoints: [6, 9],
stations: [8, 12],
hubs: [3, 6],
};
const THRESH = {
selfIntersect: 2.5,
minWaypoint: 1.5,
hubReach: 1.5,
cornerRadius: 0.8,
transfer: 0.6,
};
const PROB = { hubPass: 0.7, maxAttempts: 20 };
const STYLE = {
line: 5,
lineOutline: 8,
stationNormal: 9,
stationTransfer: 11,
strokeNormal: 3.5,
strokeTransfer: 4.5,
};
const colors = [
'#E63946',
'#06FFA5',
'#4361EE',
'#F77F00',
'#9B5DE5',
'#FFD60A',
'#FF006E',
'#00B4D8',
'#90E0EF',
];
const lineNames = [
'Red',
'Green',
'Blue',
'Orange',
'Purple',
'Yellow',
'Magenta',
'Cyan',
'Light Blue',
].map((n) => n + ' Line');
const stationNames = [
'Dawn Square',
'Memory Cross',
'Light Terminal',
'Time Gap',
'Silent Station',
'Hope Bridge',
'Shadow Town',
'Star Plaza',
'Wind Passage',
'Mirror Port',
'Forest Gate',
'Rainbow',
'Moon Stop',
'Cloud Nine',
'Sunrise Hill',
'Daybreak',
'Poet Corner',
'Music Hall',
'Gallery',
'Dance Plaza',
'Soul Junction',
'Heart Station',
'Future Gate',
'Past Window',
'Galaxy Central',
'Sky Port',
'Seaside',
'Summit',
'Dream Station',
'Echo Park',
'Crystal Bay',
'Zenith',
'Ember Square',
'Twilight Ave',
'Lotus Garden',
'Harmony',
'Cascade',
'Velvet Station',
'Iris Gate',
'Nova Plaza',
'Horizon',
'Serenity',
'Phoenix Park',
'Meteor Stop',
];
// ===========================
// Helpers
// ===========================
const dist2d = (p1, p2) => Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2);
const pathDist = (path) =>
path.reduce((sum, p, i) => (i > 0 ? sum + dist2d(path[i - 1], p) : sum), 0);
const getEdgePos = () => {
const side = floor(random(4));
return [
{ x: GRID.edgeMin, y: floor(random(3, 11)) },
{ x: GRID.edgeMax, y: floor(random(3, 11)) },
{ x: floor(random(3, 11)), y: GRID.edgeMin },
{ x: floor(random(3, 11)), y: GRID.edgeMax },
][side];
};
const isTooClose = (x, y, visited, thresh) =>
visited.slice(0, -1).some((v) => dist(x, y, v.x, v.y) < thresh);
// ===========================
// Setup
// ===========================
function setup() {
createCanvas((W = min(windowWidth, windowHeight) - 50), W);
gridSize = W / GRID.cells;
generate();
}
function generate() {
routes = [];
// Create hubs
hubPoints = Array.from({ length: floor(random(...ROUTE.hubs)) }, () => ({
x: floor(random(GRID.hubMin, GRID.hubMax)),
y: floor(random(GRID.hubMin, GRID.hubMax)),
}));
// Generate routes
const numRoutes = floor(random(ROUTE.min, ROUTE.max));
routes.push(createLoopLine());
for (let i = 0; i < numRoutes - 1; i++) {
routes.push(createRoute(colors[i], lineNames[i]));
}
}
// ===========================
// Route Creation
// ===========================
function createRoute(color, name) {
const waypoints = [];
const visited = [];
const start = getEdgePos();
waypoints.push(start);
visited.push({ ...start });
// Generate waypoints
let { x, y } = start;
let lastDir = null;
let dirCounter = 0;
const numWaypoints = floor(random(...ROUTE.waypoints));
const targetHub =
random() > 1 - PROB.hubPass && hubPoints.length > 0
? random(hubPoints)
: null;
let hubReached = false;
for (let i = 1; i < numWaypoints; i++) {
let valid = false;
let attempts = 0;
let newX, newY, dir;
while (!valid && attempts++ < PROB.maxAttempts) {
// Move towards hub or random direction
if (targetHub && !hubReached && i > 2 && random() > 0.5) {
const dx = targetHub.x - x;
const dy = targetHub.y - y;
if (abs(dx) > abs(dy)) {
newX = constrain(
x + (dx > 0 ? floor(random(1, 3)) : -floor(random(1, 3))),
GRID.edgeMin,
GRID.edgeMax,
);
newY = y;
} else {
newX = x;
newY = constrain(
y + (dy > 0 ? floor(random(1, 3)) : -floor(random(1, 3))),
GRID.edgeMin,
GRID.edgeMax,
);
}
if (dist(newX, newY, targetHub.x, targetHub.y) < THRESH.hubReach) {
[newX, newY] = [targetHub.x, targetHub.y];
hubReached = true;
}
} else {
dirCounter++;
const forceChange = dirCounter >= floor(random(2, 4));
// Select direction
let validDir = false;
while (!validDir && attempts < PROB.maxAttempts) {
const r = random();
dir = r < 0.35 ? 'h' : r < 0.7 ? 'v' : 'd';
if (dir !== lastDir || forceChange) validDir = true;
}
// Apply movement
const d = floor(random(2, dir === 'd' ? 4 : 5));
if (dir === 'h') {
newX = constrain(
x + (random() > 0.5 ? d : -d),
GRID.edgeMin,
GRID.edgeMax,
);
newY = y;
} else if (dir === 'v') {
newX = x;
newY = constrain(
y + (random() > 0.5 ? d : -d),
GRID.edgeMin,
GRID.edgeMax,
);
} else {
newX = constrain(
x + (random() > 0.5 ? d : -d),
GRID.edgeMin,
GRID.edgeMax,
);
newY = constrain(
y + (random() > 0.5 ? d : -d),
GRID.edgeMin,
GRID.edgeMax,
);
}
}
// Validate
const notClose = dist(newX, newY, x, y) > THRESH.minWaypoint;
const notVisited = !isTooClose(newX, newY, visited, THRESH.selfIntersect);
valid = notClose && notVisited;
}
if (valid) {
waypoints.push({ x: newX, y: newY });
visited.push({ x: newX, y: newY });
[x, y] = [newX, newY];
if (dir !== lastDir) {
lastDir = dir;
dirCounter = 0;
}
}
}
return createRouteFromWaypoints(color, waypoints, name);
}
function createLoopLine() {
const cx = random(5, 9),
cy = random(5, 9),
r = random(2.3, 3.5);
const numStations = floor(random(...ROUTE.stations));
const stations = [];
const path = [];
for (let i = 0; i < numStations; i++) {
const angle = (TWO_PI / numStations) * i - HALF_PI;
stations.push({
x: (cx + cos(angle) * r) * gridSize,
y: (cy + sin(angle) * r) * gridSize,
name: random(stationNames),
});
}
for (let i = 0; i <= 100; i++) {
const angle = (TWO_PI / 100) * i - HALF_PI;
path.push({
x: (cx + cos(angle) * r) * gridSize,
y: (cy + sin(angle) * r) * gridSize,
});
}
return { color: random(colors), name: 'Loop Line', stations, path };
}
function createRouteFromWaypoints(color, waypoints, name) {
const path = generatePath(waypoints);
const route = { color, name, stations: [], path };
if (path.length === 0) return route;
// Place stations
const totalDist = pathDist(path);
const numStations = floor(random(...ROUTE.stations));
const spacing = totalDist / (numStations - 1);
route.stations.push({
x: path[0].x,
y: path[0].y,
name: random(stationNames),
});
for (let s = 1; s < numStations - 1; s++) {
const pos = findPointAtDist(path, s * spacing);
if (pos) route.stations.push({ ...pos, name: random(stationNames) });
}
const last = path[path.length - 1];
route.stations.push({ x: last.x, y: last.y, name: random(stationNames) });
return route;
}
function findPointAtDist(path, targetDist) {
let acc = 0;
for (let i = 0; i < path.length - 1; i++) {
const segDist = dist2d(path[i], path[i + 1]);
if (acc + segDist >= targetDist) {
const t = (targetDist - acc) / segDist;
return {
x: lerp(path[i].x, path[i + 1].x, t),
y: lerp(path[i].y, path[i + 1].y, t),
};
}
acc += segDist;
}
return null;
}
function generatePath(waypoints) {
const path = [];
const cornerR = gridSize * THRESH.cornerRadius;
for (let i = 0; i < waypoints.length; i++) {
const cur = waypoints[i],
next = waypoints[i + 1],
prev = waypoints[i - 1];
if (i === 0) path.push({ x: cur.x * gridSize, y: cur.y * gridSize });
if (next) {
const isCorner =
prev &&
(cur.x - prev.x !== next.x - cur.x ||
cur.y - prev.y !== next.y - cur.y);
if (isCorner) {
// Rounded corner
let [pdx, pdy] = [cur.x - prev.x, cur.y - prev.y];
let [ndx, ndy] = [next.x - cur.x, next.y - cur.y];
const pLen = Math.sqrt(pdx ** 2 + pdy ** 2);
const nLen = Math.sqrt(ndx ** 2 + ndy ** 2);
[pdx, pdy] = [pdx / pLen, pdy / pLen];
[ndx, ndy] = [ndx / nLen, ndy / nLen];
const approach = Math.min(cornerR / gridSize, 0.8);
const [ax, ay] = [
(cur.x - pdx * approach) * gridSize,
(cur.y - pdy * approach) * gridSize,
];
const [ex, ey] = [
(cur.x + ndx * approach) * gridSize,
(cur.y + ndy * approach) * gridSize,
];
// Line to approach
const last = path[path.length - 1];
const steps = Math.floor(dist2d(last, { x: ax, y: ay }) / 10);
for (let t = 1; t <= steps; t++) {
path.push({
x: lerp(last.x, ax, t / steps),
y: lerp(last.y, ay, t / steps),
});
}
// Bezier curve
const [cx, cy] = [cur.x * gridSize, cur.y * gridSize];
for (let t = 0; t <= 15; t++) {
const p = t / 15;
path.push({
x: (1 - p) ** 2 * ax + 2 * (1 - p) * p * cx + p ** 2 * ex,
y: (1 - p) ** 2 * ay + 2 * (1 - p) * p * cy + p ** 2 * ey,
});
}
} else {
// Straight line
const [dx, dy] = [next.x - cur.x, next.y - cur.y];
const steps = Math.floor(Math.sqrt(dx ** 2 + dy ** 2) * 8);
for (let t = 1; t <= steps; t++) {
path.push({
x: cur.x * gridSize + dx * gridSize * (t / steps),
y: cur.y * gridSize + dy * gridSize * (t / steps),
});
}
}
}
}
return path;
}
// ===========================
// Drawing
// ===========================
function draw() {
background(245, 243, 238);
// Grid
push();
stroke(230, 230, 230);
strokeWeight(0.5);
for (let i = 0; i <= GRID.cells; i++) {
const p = i * gridSize;
line(p, 0, p, W);
line(0, p, W, p);
}
pop();
// Routes
for (const r of routes) {
push();
stroke(255);
strokeWeight(STYLE.lineOutline);
strokeCap(ROUND);
strokeJoin(ROUND);
noFill();
beginShape();
r.path.forEach((p) => vertex(p.x, p.y));
endShape();
stroke(r.color);
strokeWeight(STYLE.line);
beginShape();
r.path.forEach((p) => vertex(p.x, p.y));
endShape();
pop();
}
// Stations
for (const r of routes) {
push();
for (const s of r.stations) {
const isTransfer = routes.some(
(route) =>
route.stations.filter(
(st) => dist(s.x, s.y, st.x, st.y) < gridSize * THRESH.transfer,
).length > 1,
);
const size = isTransfer ? STYLE.stationTransfer : STYLE.stationNormal;
const sw = isTransfer ? STYLE.strokeTransfer : STYLE.strokeNormal;
fill(255);
stroke(r.color);
strokeWeight(sw);
ellipse(s.x, s.y, size * 2);
if (isTransfer) {
noStroke();
fill(255);
ellipse(s.x, s.y, size * 0.6);
}
}
pop();
}
// Station names
push();
textFont('sans-serif');
textSize(10);
for (const r of routes) {
for (let i = 0; i < r.stations.length; i++) {
const s = r.stations[i];
let angle;
if (r.name === 'Loop Line') {
angle = atan2(s.y - W / 2, s.x - W / 2);
} else if (i > 0 && i < r.stations.length - 1) {
angle =
atan2(
r.stations[i + 1].y - r.stations[i - 1].y,
r.stations[i + 1].x - r.stations[i - 1].x,
) + HALF_PI;
} else if (i === 0) {
angle = atan2(r.stations[1].y - s.y, r.stations[1].x - s.x) + HALF_PI;
} else {
angle =
atan2(s.y - r.stations[i - 1].y, s.x - r.stations[i - 1].x) + HALF_PI;
}
const [tx, ty] = [s.x + cos(angle) * 22, s.y + sin(angle) * 22];
const tw = textWidth(s.name);
fill(245, 243, 238, 240);
noStroke();
rect(tx - tw / 2 - 3, ty - 7, tw + 6, 14, 2);
fill(r.color);
textAlign(CENTER, CENTER);
text(s.name, tx, ty);
}
}
pop();
// Legend
push();
const [lx, ly] = [W * 0.03, W * 0.88];
textFont('sans-serif');
textSize(10);
routes.forEach((r, i) => {
const y = ly - (routes.length - 1 - i) * 16;
stroke(r.color);
strokeWeight(3.5);
line(lx, y, lx + 22, y);
noStroke();
fill(80);
textAlign(LEFT, CENTER);
text(r.name, lx + 28, y);
});
pop();
// Title
push();
fill(0, 0, 0, 100);
noStroke();
textAlign(LEFT, TOP);
textSize(W * 0.022);
textFont('sans-serif');
textStyle(BOLD);
text('MRT NETWORK MAP', W * 0.03, W * 0.02);
textStyle(NORMAL);
textSize(W * 0.016);
fill(0, 0, 0, 80);
text(
`${routes.length} Lines • ${routes.reduce(
(t, r) => t + r.stations.length,
0,
)} Stations`,
W * 0.03,
W * 0.05,
);
pop();
}
function keyPressed() {
if (key === 'c') saveCanvas(`mrt-${round(Date.now() / 100000)}`, 'jpeg');
if (key === 'r') generate();
}
Day14: 3-D Particles
ぶっちゃけこの 𝕏 の投稿のパクリです
code
let fallingParticles = [];
let roadParticlesTop = [];
let roadParticlesBottom = [];
let numFallingParticles = 300;
let numRoadParticles = 200;
let cylinderRadius;
let innerCircleRadius; // 中央の円の半径
function setup() {
createCanvas(windowWidth, windowHeight);
cylinderRadius = min(width, height) * 0.35;
innerCircleRadius = cylinderRadius * 0.5; // 中央島の半径
// 落下するパーティクルの初期化
for (let i = 0; i < numFallingParticles; i++) {
fallingParticles.push(createFallingParticle());
}
// 上の環状交差点のパーティクル
for (let i = 0; i < numRoadParticles; i++) {
roadParticlesTop.push(
createRoadParticle(height * 0.15, 1, i / numRoadParticles),
);
}
// 下の環状交差点のパーティクル
for (let i = 0; i < numRoadParticles; i++) {
roadParticlesBottom.push(
createRoadParticle(height * 0.85, 0.8, i / numRoadParticles),
);
}
}
function createFallingParticle() {
// 上の中央円からランダムに出発
const angle = random(TWO_PI);
const radius = random(innerCircleRadius * 0.8); // 中央円の範囲内
const topY = height * 0.15;
const scaleY = 0.5; // 上の円の遠近スケール
return {
x: width / 2 + cos(angle) * radius,
z: sin(angle) * radius,
y: topY + height * 0.85 * random() + sin(angle) * radius * scaleY, // 楕円の高さに合わせる
targetY: height * 0.85, // 下の円の位置
speed: random(1.5, 4),
baseSize: random(1.5, 3),
trail: [],
noiseOffsetX: random(1000),
noiseOffsetZ: random(1000),
flowSpeed: random(0.01, 0.03),
flowStrength: random(8, 20),
type: 'falling',
startAngle: angle, // 開始角度を記憶
startRadius: radius, // 開始半径を記憶
currentAngle: angle, // 現在の角度
currentRadius: radius, // 現在の半径
};
}
function createRoadParticle(centerY, depthPosition, angleOffset) {
const angle = angleOffset * TWO_PI;
const laneRadius = cylinderRadius * random(0.4, 0.95);
const scaleY = map(depthPosition, 0, 1, 0.3, 0.5);
return {
centerY: centerY,
angle: angle,
laneRadius: laneRadius,
scaleY: scaleY,
speed: random(0.005, 0.01),
baseSize: random(1, 2.5),
trail: [],
type: 'road',
direction: random() > 0.5 ? 1 : -1,
};
}
function resetFallingParticle(particle) {
const angle = random(TWO_PI);
const radius = random(innerCircleRadius * 0.8);
const topY = height * 0.15;
const scaleY = 0.5;
particle.currentAngle = angle;
particle.currentRadius = radius;
particle.y = topY + sin(angle) * radius * scaleY;
particle.speed = random(1.5, 4);
particle.baseSize = random(1.5, 3);
particle.trail = [];
particle.noiseOffsetX = random(1000);
particle.noiseOffsetZ = random(1000);
particle.flowSpeed = random(0.01, 0.03);
particle.flowStrength = random(8, 20);
particle.startAngle = angle;
particle.startRadius = radius;
}
function draw() {
// 半透明の背景で残像効果
fill(20, 20, 30, 30);
noStroke();
rect(0, 0, width, height);
// すべてのパーティクルを統合
let allParticles = [];
// 上の環状交差点のパーティクルを更新
for (let particle of roadParticlesTop) {
updateRoadParticle(particle);
allParticles.push(particle);
}
// 落下するパーティクルを更新
for (let particle of fallingParticles) {
updateFallingParticle(particle);
allParticles.push(particle);
}
// 下の環状交差点のパーティクルを更新
for (let particle of roadParticlesBottom) {
updateRoadParticle(particle);
allParticles.push(particle);
}
// パーティクルを描画
for (let particle of allParticles) {
if (particle.type === 'falling') {
drawFallingParticle(particle);
} else {
drawRoadParticle(particle);
}
}
// 中央の円を描画(視覚的な参考)
drawCentralCircles();
// UI表示
displayInfo();
}
function updateFallingParticle(particle) {
// 軌跡を保存
particle.trail.push({ x: particle.x, y: particle.y, z: particle.z });
if (particle.trail.length > 25) {
particle.trail.shift();
}
// 流線形の動き(角度方向に)
const noiseAngle = noise(
particle.noiseOffsetX + frameCount * particle.flowSpeed,
);
const noiseRadius = noise(
particle.noiseOffsetZ + frameCount * particle.flowSpeed,
);
particle.currentAngle += (noiseAngle - 0.5) * 0.02;
particle.currentRadius += (noiseRadius - 0.5) * particle.flowStrength * 0.02;
// 落下
particle.y += particle.speed;
// 進行度を計算
const progress = map(particle.y, height * 0.15, height * 0.85, 0, 1);
// 中央円の範囲内に緩やかに収束
const targetRadius = innerCircleRadius * 0.8;
if (particle.currentRadius > targetRadius && progress > 0.3) {
const pullStrength = map(progress, 0.3, 1, 0, 0.1);
particle.currentRadius = lerp(
particle.currentRadius,
targetRadius,
pullStrength,
);
}
// 円柱の円形を保ちながらXとZを更新
particle.x = width / 2 + cos(particle.currentAngle) * particle.currentRadius;
particle.z = sin(particle.currentAngle) * particle.currentRadius;
// 下の円に到達したらリセット
if (particle.y > height * 0.85 - particle.z) {
resetFallingParticle(particle);
}
}
function updateRoadParticle(particle) {
// 軌跡を保存
const x = width / 2 + cos(particle.angle) * particle.laneRadius;
const y =
particle.centerY +
sin(particle.angle) * particle.laneRadius * particle.scaleY;
particle.trail.push({ x: x, y: y });
if (particle.trail.length > 20) {
particle.trail.shift();
}
// 角度を更新(回転)
particle.angle += particle.speed * particle.direction;
}
function drawFallingParticle(particle) {
// 奥行きに応じたスケールと透明度
const depthScale = map(particle.z, -cylinderRadius, cylinderRadius, 0.3, 1.2);
const alpha = map(particle.z, -cylinderRadius, cylinderRadius, 100, 255);
// 2D描画位置を計算(Xはそのまま、Yに奥行き(Z)の影響を加える)
const drawX = particle.x;
const drawY = particle.y; // Zの影響は透明度とサイズのみに反映
// 軌跡を描画
if (particle.trail.length >= 2) {
noFill();
for (let i = 0; i < particle.trail.length - 1; i++) {
const t1 = particle.trail[i];
const t2 = particle.trail[i + 1];
// 各軌跡点の2D位置
const t1x = t1.x;
const t1y = t1.y;
const t2x = t2.x;
const t2y = t2.y;
const t1DepthScale = map(t1.z, -cylinderRadius, cylinderRadius, 0.3, 1.2);
const trailAlpha = map(i, 0, particle.trail.length, 0, alpha * 0.7);
const weight = map(i, 0, particle.trail.length, 0.3, 1) * t1DepthScale;
stroke(255, 255, 255, trailAlpha);
strokeWeight(weight);
line(t1x, t1y, t2x, t2y);
}
}
// パーティクル本体
const size = particle.baseSize * depthScale;
noStroke();
fill(255, 255, 255, alpha);
ellipse(drawX, drawY, size * 2, size * 2);
}
function drawRoadParticle(particle) {
const x = width / 2 + cos(particle.angle) * particle.laneRadius;
const y =
particle.centerY +
sin(particle.angle) * particle.laneRadius * particle.scaleY;
// 軌跡を描画
if (particle.trail.length >= 2) {
noFill();
for (let i = 0; i < particle.trail.length - 1; i++) {
const t1 = particle.trail[i];
const t2 = particle.trail[i + 1];
const trailAlpha = map(i, 0, particle.trail.length, 0, 180);
const weight = map(i, 0, particle.trail.length, 0.3, 0.8);
stroke(150, 200, 255, trailAlpha);
strokeWeight(weight);
line(t1.x, t1.y, t2.x, t2.y);
}
}
// パーティクル本体
noStroke();
ellipse(x, y, particle.baseSize * 2, particle.baseSize * 2);
}
function drawCentralCircles() {
// 上の中央円
push();
noFill();
stroke(80, 100, 120, 100);
strokeWeight(1.5);
const topScaleY = 0.5;
ellipse(
width / 2,
height * 0.15,
innerCircleRadius * 2,
innerCircleRadius * 2 * topScaleY,
);
pop();
// 下の中央円
push();
noFill();
stroke(80, 100, 120, 100);
strokeWeight(1.5);
const bottomScaleY = 0.4;
ellipse(
width / 2,
height * 0.85,
innerCircleRadius * 2,
innerCircleRadius * 2 * bottomScaleY,
);
pop();
}
function displayInfo() {
fill(255);
noStroke();
textAlign(LEFT, TOP);
textSize(14);
text(`Falling: ${numFallingParticles}`, 10, 10);
text(`Road: ${numRoadParticles * 2}`, 10, 30);
text('Keys:', 10, 50);
text('1/2: Falling particles ±50', 10, 70);
text('3/4: Road particles ±25', 10, 90);
text('5/6: Flow strength ±', 10, 110);
text('R: Reset', 10, 130);
text('S: Save screenshot', 10, 150);
}
function adjustFallingParticles() {
const diff = numFallingParticles - fallingParticles.length;
if (diff > 0) {
for (let i = 0; i < diff; i++) {
fallingParticles.push(createFallingParticle());
}
} else if (diff < 0) {
fallingParticles = fallingParticles.slice(0, numFallingParticles);
}
}
function adjustRoadParticles() {
roadParticlesTop = [];
roadParticlesBottom = [];
for (let i = 0; i < numRoadParticles; i++) {
roadParticlesTop.push(
createRoadParticle(height * 0.15, 1, i / numRoadParticles),
);
roadParticlesBottom.push(
createRoadParticle(height * 0.85, 0.8, i / numRoadParticles),
);
}
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
cylinderRadius = min(width, height) * 0.35;
innerCircleRadius = cylinderRadius * 0.5;
}
function keyPressed() {
if (key === '1') {
numFallingParticles = max(50, numFallingParticles - 50);
adjustFallingParticles();
}
if (key === '2') {
numFallingParticles = min(1000, numFallingParticles + 50);
adjustFallingParticles();
}
if (key === '3') {
numRoadParticles = max(50, numRoadParticles - 25);
adjustRoadParticles();
}
if (key === '4') {
numRoadParticles = min(400, numRoadParticles + 25);
adjustRoadParticles();
}
if (key === '5') {
fallingParticles.forEach(
(p) => (p.flowStrength = max(3, p.flowStrength - 3)),
);
}
if (key === '6') {
fallingParticles.forEach(
(p) => (p.flowStrength = min(50, p.flowStrength + 3)),
);
}
if (key === 'r' || key === 'R') {
particles = [];
for (let i = 0; i < numFallingParticles; i++) {
particles.push(createFallingParticle());
}
}
if (key === 's' || key === 'S') {
saveGif(`mySketch-${round(new Date().getTime() / 100000)}`, 5);
}
if (key === 'c' || key === 'C') {
saveCanvas(`mySketch-${round(new Date().getTime() / 100000)}`, 'jpeg');
}
}
Day15: Platonic solid(regular polyhedron)
3層の自己双対を点描で描いてみた

Day16: A rotating particle system
元々のプロンプトは以下.これをちょいちょい微調整していった感じ.
中心から放射状に広がる複数の「腕」を持ち、各腕に沿って粒子が流れる。腕は対称的に配置され、回転しながら呼吸するようにサイズが変化。
コードもに関してもこちらをご参照ください💁