

// ============================
// Configuration
// ============================
const CANVAS_SIZE = 800;
const TILE_SIZE = 50;
const GRID_NOISE_SCALE = 0.01;
const TIME_NOISE_SCALE = 0.01;
const TIME_STEP = 0.2;
// ============================
// State
// ============================
let tiles = [];
let time = 0;
// ============================
// p5 Lifecycle
// ============================
function setup() {
createCanvas(CANVAS_SIZE, CANVAS_SIZE);
colorMode(HSB, 360, 100, 100, 100);
angleMode(DEGREES);
tiles = createTiles();
}
function draw() {
background(5, 5, 5, 100);
time += TIME_STEP;
for (const tile of tiles) {
updateTile(tile);
renderTile(tile);
}
}
// ============================
// Tile Creation
// ============================
function createTiles() {
const result = [];
for (let x = 0; x < width; x += TILE_SIZE) {
for (let y = 0; y < height; y += TILE_SIZE) {
result.push({
position: createVector(x, y),
rotation: random(270),
rotationSpeed: random(-4, 4),
hueBase: random(360),
shapeType: floor(random(4)),
noiseOffset: random(1000),
});
}
}
return result;
}
// ============================
// Tile Update
// ============================
function updateTile(tile) {
const { x, y } = tile.position;
tile.noiseValue = noise(
x * GRID_NOISE_SCALE,
y * GRID_NOISE_SCALE,
time * TIME_NOISE_SCALE + tile.noiseOffset
);
tile.rotation += tile.rotationSpeed * tile.noiseValue;
}
// ============================
// Tile Rendering
// ============================
function renderTile(tile) {
const { x, y } = tile.position;
const centerX = x + TILE_SIZE * 0.5;
const centerY = y + TILE_SIZE * 0.5;
const n = tile.noiseValue;
const hue = (tile.hueBase + time * 0.5 + n * 100) % 360;
const saturation = 40 + sin(time * 6 + x * 0.01) * 20;
const brightness = 50 + cos(time * 3 + y * 0.1) * 50;
const size = TILE_SIZE * 0.5 * (0.8 + n * 0.4);
push();
translate(centerX, centerY);
rotate(tile.rotation + n * 20);
noStroke();
fill(hue, saturation, brightness, 100);
SHAPE_RENDERERS[tile.shapeType](size);
drawInnerDot(size);
pop();
}
// ============================
// Shape Renderers
// ============================
const SHAPE_RENDERERS = [
drawSquare,
drawCircle,
drawTriangle,
drawCross,
];
function drawSquare(size) {
rect(-size / 2, -size / 2, size, size);
}
function drawCircle(size) {
ellipse(0, 0, size, size);
}
function drawTriangle(size) {
beginShape();
for (let i = 0; i < 3; i++) {
const angle = 120 * i - 90;
vertex(cos(angle) * size * 0.5, sin(angle) * size * 0.5);
}
endShape(CLOSE);
}
function drawCross(size) {
rect(-size / 2, -size / 6, size, size / 3);
rect(-size / 6, -size / 2, size / 3, size);
}
function drawInnerDot(size) {
fill(0, 0, 100, 100);
ellipse(10, 0, size * 0.2);
}
// ============================
// Interaction
// ============================
function keyPressed() {
if (key === " ") {
for (const tile of tiles) {
tile.rotationSpeed = random(-3, 3);
tile.hueBase = random(360);
tile.shapeType = floor(random(4));
}
}
}
// ======================================================
// 1. ์ ์ญ ์ค์ ๊ฐ (Configuration Constants)
// ======================================================
const CANVAS_SIZE = 800; // ์บ๋ฒ์ค์ ๊ฐ๋กยท์ธ๋ก ํฌ๊ธฐ
const TILE_SIZE = 50; // ํ๋์ ํ์ผ์ด ์ฐจ์งํ๋ ์ ์ฌ๊ฐํ ํฌ๊ธฐ
const GRID_NOISE_SCALE = 0.01; // ๊ณต๊ฐ ๋
ธ์ด์ฆ์ ์ธ๋ฐํจ
const TIME_NOISE_SCALE = 0.01; // ์๊ฐ์ ๋ฐ๋ฅธ ๋
ธ์ด์ฆ ๋ณํ ์๋
const TIME_STEP = 0.2; // ๋งค ํ๋ ์๋ง๋ค time ๋ณ์์ ๋ํด์ง๋ ๊ฐ (์๋ ์กฐ์ )
// ======================================================
// 2. ์ ์ญ ์ํ ๋ณ์ (Global State)
// ======================================================
// p5.js์ draw ๋ฃจํ ์ ์ฒด์์ ๊ณต์ ๋๋ ์ํ๊ฐ
let tiles = []; // ๋ชจ๋ ํ์ผ ๊ฐ์ฒด(Tile ์ธ์คํด์ค)๋ฅผ ๋ด๋ ๋ฐฐ์ด
let time = 0; // ์ ๋๋ฉ์ด์
์ ์๊ฐ ์ถ ์ญํ ์ ํ๋ ๋ณ์. ์ ์ ์ฆ๊ฐํ๋ฉฐ ๋ณํ์ ๊ธฐ์ค์ด ๋จ
// ======================================================
// 3. p5.js ๋ผ์ดํ์ฌ์ดํด ํจ์
// ======================================================
// p5.js ์ด๊ธฐ ์ค์ ํจ์ (ํ๋ก๊ทธ๋จ ์์ ์ ํ ๋ฒ๋ง ์คํ)
function setup() {
// ์บ๋ฒ์ค ์์ฑ
createCanvas(CANVAS_SIZE, CANVAS_SIZE);
// ์์ ๋ชจ๋๋ฅผ HSB๋ก ์ค์
// hue: 0~360, saturation/brightness/alpha: 0~100
colorMode(HSB, 360, 100, 100, 100);
// ๊ฐ๋ ๋จ์๋ฅผ ๋ผ๋์์ด ์๋ ๋(degree)๋ก ์ค์
angleMode(DEGREES);
// ํ๋ฉด ์ ์ฒด๋ฅผ TILE_SIZE ๊ฐ๊ฒฉ์ผ๋ก ๋๋์ด ํ์ผ ๊ฐ์ฒด๋ค์ ์์ฑํ๊ณ ๋ฐฐ์ด์ ์ ์ฅ
tiles = createTileGrid();
}
// p5.js ๋ฌดํ ๋ฐ๋ณต ํจ์ (๋งค ํ๋ ์๋ง๋ค ์คํ๋จ)
function draw() {
// ์ค์ ํ ๋ฐฐ๊ฒฝ ์์ ์ํ๊ฐ์ ๋งค ํ๋ ์๋ง๋ค ๋ฎ์ด์์
background(5, 5, 5, 100);
// ์๊ฐ ๊ฐ ์ฆ๊ฐ. ์ด๊ฐ์ ๋
ธ์ด์ฆ, ์์ ๋ณํ, ํ์ ์ ์ฌ์ฉ๋จ.
time += TIME_STEP;
// ๋ชจ๋ ํ์ผ์ ๋ํด
// 1) ํ์ฌ ์๊ฐ ๊ฐ์ ์ ๋ฌํ๋ฉฐ ํ์ผ์ ๋
ธ์ด์ฆ์ ํ์ ์ํ ๊ฐฑ์
// 2) ๊ฐฑ์ ๋ ์ํ๋ฅผ ๋ฐํ์ผ๋ก ํ์ผ์ ํ๋ฉด์ ๊ทธ๋ฆผ
for (const tile of tiles) {
tile.update(time);
tile.render();
}
}
// ======================================================
// 4. ํ์ผ ๊ทธ๋ฆฌ๋ ์์ฑ ํจ์
// ======================================================
// ํ๋ฉด์ TILE_SIZE ๋จ์์ ๊ฒฉ์๋ก ๋๋์ด ๊ฐ ์์น์ Tile ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ํจ์
function createTileGrid() {
// ์์ฑ๋ ํ์ผ๋ค์ ์ ์ฅํ ๋ฐฐ์ด
const result = [];
// ๊ฐ๋ก ์ธ๋ก ๋ฐฉํฅ์ผ๋ก ๊ฐ๊ฐ TILE_SIZE ๊ฐ๊ฒฉ๋งํผ ์ด๋ํ๋ฉฐ ๋ฐ๋ณต
for (let x = 0; x < width; x += TILE_SIZE) {
for (let y = 0; y < height; y += TILE_SIZE) {
// ํ์ฌ ๊ฒฉ์ ์์น(x, y)์ ์๋ก์ด Tile ๊ฐ์ฒด๋ฅผ ์์ฑํ์ฌ ๋ฐฐ์ด์ ์ถ๊ฐ
result.push(new Tile(x, y, TILE_SIZE));
}
}
// ์์ฑ๋ ํ์ผ ๋ฐฐ์ด ๋ฐํ
return result;
}
// ======================================================
// 5. Tile ํด๋์ค ์ ์
// ======================================================
// Tile ํด๋์ค๋ "ํ์ผ ํ๋"๋ฅผ ํํํ๋ ๋จ์ ๊ฐ์ฒด
// ์์น, ์, ํ์ , ํํ, ์ ๋๋ฉ์ด์
๋ก์ง์ ์ ์ํจ
class Tile {
// --------------------------------------------------
// 5-1. ์์ฑ์ (constructor)
// --------------------------------------------------
// new Tile(x, y, size) ํํ๋ก ํธ์ถ๋จ
// ํ์ผ์ด ์ฒ์ ๋ง๋ค์ด์ง ๋ ํ ๋ฒ๋ง ์คํ๋จ
constructor(x, y, size) {
// ํ์ผ์ ์ข์๋จ ์์น
// p5.Vector๋ฅผ ์ฌ์ฉํ๋ฉด ์ดํ ๋ฒกํฐ ์ฐ์ฐ ํ์ฅ์ด ์ฉ์ดํจ
this.position = createVector(x, y);
// ํ์ผ์ ๊ธฐ๋ณธ ํฌ๊ธฐ ์ ์ฅ
this.size = size;
// ์ด๊ธฐ ํ์ ๊ฐ๋๋ฅผ ๋๋คํ๊ฒ ์ค์ (0~270๋ ์ฌ์ด)
this.rotation = random(270);
// ํ์ ์๋๋ฅผ ๋๋คํ๊ฒ ์ค์ (-4 ~ +4๋/ํ๋ ์)
this.rotationSpeed = random(-4, 4);
// ํ์ผ์ ๊ธฐ๋ณธ ์์กฐ(hue)๋ฅผ ๋๋คํ๊ฒ ์ค์ (0~30์ผ๋ก ์ ์ฌ์ ๋ถํฌ)
this.hueBase = random(30);
// ์ด๋ค ํํ๋ฅผ ๊ทธ๋ฆด์ง ๊ฒฐ์ ํ๋ ์ธ๋ฑ์ค
// 0: ์ฌ๊ฐํ, 1: ์, 2: ์ผ๊ฐํ, 3: ์ญ์
this.shapeType = floor(random(4));
// ๊ฐ ํ์ผ๋ง๋ค ๋ค๋ฅธ ๋
ธ์ด์ฆ ํจํด์ ๋ง๋ค๊ธฐ ์ํ ๋๋ค ์คํ์
this.noiseOffset = random(1000);
// ํ์ฌ ํ๋ ์์์ ๊ณ์ฐ๋ ๋
ธ์ด์ฆ ๊ฐ ์ ์ฅ์ฉ ๋ณ์ (์ด๊ธฐ๊ฐ 0)
this.noiseValue = 0;
}
// --------------------------------------------------
// 5-2. update ๋ฉ์๋
// --------------------------------------------------
// ์๊ฐ์ ๋ฐ๋ฅธ ํ์ผ์ "์ํ ๋ณํ"๋ฅผ ๋ด๋น
// ์์น๋ ๊ณ ์ ๋์ด ์๊ณ , ํ์ ๊ณผ ๋
ธ์ด์ฆ ๊ฐ๋ง ๋ณํจ
update(t) {
// ํ์ผ ์์น์ x, y ์ขํ๋ฅผ ๊ตฌ์กฐ ๋ถํด ํ ๋น์ผ๋ก ์ถ์ถ
const { x, y } = this.position;
// 3์ฐจ์ Perlin ๋
ธ์ด์ฆ ๊ฐ์ ๊ณ์ฐ (๊ณต๊ฐ x, ๊ณต๊ฐ y, ์๊ฐ t + ์คํ์
)
this.noiseValue = noise(
x * GRID_NOISE_SCALE,
y * GRID_NOISE_SCALE,
t * TIME_NOISE_SCALE + this.noiseOffset
);
// ๋
ธ์ด์ฆ ๊ฐ์ ๋น๋กํ์ฌ ํ์ ์๋ ์ ์ฉ (๋
ธ์ด์ฆ๊ฐ ํด์๋ก ๋ ๋น ๋ฅด๊ฒ ํ์ )
this.rotation += this.rotationSpeed * this.noiseValue;
}
// --------------------------------------------------
// 5-3. render ๋ฉ์๋
// --------------------------------------------------
// ํ์ผ์ ์ค์ ๋ก ํ๋ฉด์ ๊ทธ๋ฆฌ๋ ์ญํ ์ ํ๋ค.
// ๊ณ์ฐ๋ ์ํ๊ฐ์ ์๊ฐ์ ์์๋ก ๋ณํํ๋ค.
render() {
// ํ์ผ ์์น์ x, y ์ขํ ์ถ์ถ
const { x, y } = this.position;
// ํ์ผ์ ์ค์ฌ ์ขํ ๊ณ์ฐ
const centerX = x + this.size * 0.5;
const centerY = y + this.size * 0.5;
// ํ์ฌ ๊ณ์ฐ๋ ๋
ธ์ด์ฆ ๊ฐ์ n์ผ๋ก ์ฌ์ฉ
const n = this.noiseValue;
// ์์กฐ(hue): ๊ธฐ๋ณธ hue + ์๊ฐ์ ๋ฐ๋ฅธ ๋ณํ + ๋
ธ์ด์ฆ ์ํฅ
const hue = (this.hueBase + time * 0.5 + n * 100) % 360;
// ์ฑ๋(saturation): ์๊ฐ๊ณผ x ์์น์ ๋ฐ๋ฅธ ์ฌ์ธํ๋ก ๋ถ๋๋ฝ๊ฒ ์ง๋
const saturation = 40 + sin(time * 6 + x * 0.01) * 20;
// ๋ช
๋(brightness): ์๊ฐ๊ณผ y ์์น์ ๋ฐ๋ฅธ ์ฝ์ฌ์ธํ๋ก ๋ถ๋๋ฝ๊ฒ ์ง๋
const brightness = 50 + cos(time * 3 + y * 0.1) * 50;
// ๋ํ์ ์ค์ ๊ทธ๋ ค์ง ํฌ๊ธฐ: ๋
ธ์ด์ฆ์ ๋ฐ๋ผ 0.8~1.2๋ฐฐ ์ฌ์ด์์ ๋ณ๋
const drawSize = this.size * 0.5 * (0.8 + n * 0.4);
push(); // ํ์ฌ ์ขํ๊ณ์ ์คํ์ผ ์ ์ฅ
// ํ์ผ ์ค์ฌ์ผ๋ก ์ขํ๊ณ๋ฅผ ์ด๋
translate(centerX, centerY);
// ๊ณ์ฐ๋ ํ์ ๊ฐ๋ ์ ์ฉ (๊ธฐ๋ณธ ํ์ + ๋
ธ์ด์ฆ์ ๋ฐ๋ฅธ ์ถ๊ฐ ํ์ )
rotate(this.rotation + n * 20);
// ํ
๋๋ฆฌ ์์
noStroke();
// ๊ณ์ฐ๋ HSB ์์๊ณผ ๋ถํฌ๋ช
๋ 100์ผ๋ก ์ฑ์ฐ๊ธฐ
fill(hue, saturation, brightness, 100);
// shapeType์ ๋ฐ๋ผ ๋ํ ๊ทธ๋ฆฌ๊ธฐ
this.drawShape(drawSize);
// ๋ํ ์ค์์ ์์ ํฐ ์ ์ถ๊ฐ
this.drawInnerDot(drawSize);
pop(); // ์ขํ๊ณ์ ์คํ์ผ ๋ณต์
}
// --------------------------------------------------
// 5-4. ๋ํ ์ ํ ๋ฉ์๋
// --------------------------------------------------
// shapeType ๊ฐ์ ๊ธฐ๋ฐ์ผ๋ก
// ๋ฏธ๋ฆฌ ์ ์๋ ํจ์ ๋ฐฐ์ด์์ ๋ํ์ ํธ์ถ
drawShape(size) {
SHAPE_RENDERERS[this.shapeType](size);
}
// --------------------------------------------------
// 5-5. ๋ด๋ถ ์ ์ฅ์
// --------------------------------------------------
// ํ์ผ ์ค์์ ์์ ํฐ์ ์ ๊ทธ๋ฆผ
drawInnerDot(size) {
fill(0, 0, 100, 100); // HSB Mode๋ก ์ฑ์ฐ๊ธฐ
ellipse(10, 0, size * 0.2); // ์ค์ฌ์์ ์ฝ๊ฐ ์ค๋ฅธ์ชฝ(10,0)
}
// --------------------------------------------------
// 5-6. ์ธํ ๋๋คํ ๋ฉ์๋
// --------------------------------------------------
// ์ธ๋ถ์์ ํธ์ถ ์ ํ์ผ์ ํ์ ์๋, ์์, ๋ํ์ ๋ค์ ๋๋คํ
randomizeAppearance() {
this.rotationSpeed = random(-4, 4);
this.hueBase = random(30);
this.shapeType = floor(random(4));
}
}
// ======================================================
// 6. ๋ํ ๋ ๋๋ง ํจ์๋ค
// ======================================================
// ๊ฐ ํจ์๋ "ํ์ฌ ์ขํ๊ณ ๊ธฐ์ค"์ผ๋ก ๋ํ์ ๊ทธ๋ฆผ
// Tile ํด๋์ค์์๋ ์ขํ ๋ณํ๋ง ๋ด๋นํ๊ณ
// ์ค์ ๋ํ ์ ์๋ ์ด ์์ญ์ ์์ํจ
// ๊ฐ ๋ํ์ ๊ทธ๋ฆฌ๋ ํจ์๋ค์ ๋ฐฐ์ด๋ก ๋ฌถ์ (0,1,2,3 ์ธ๋ฑ์ค๋ก ์ ํ)
const SHAPE_RENDERERS = [drawSquare, drawCircle, drawTriangle, drawCross];
// ์ฌ๊ฐํ ๊ทธ๋ฆฌ๊ธฐ (์ค์ฌ ๊ธฐ์ค)
function drawSquare(size) {
rect(-size / 2, -size / 2, size, size);
}
// ์ ๊ทธ๋ฆฌ๊ธฐ (์ค์ฌ ๊ธฐ์ค)
function drawCircle(size) {
ellipse(0, 0, size, size);
}
// ์ ์ผ๊ฐํ ๊ทธ๋ฆฌ๊ธฐ (์ค์ฌ ๊ธฐ์ค, ์์ชฝ์ด ๋พฐ์กฑ)
function drawTriangle(size) {
beginShape();
for (let i = 0; i < 3; i++) {
const angle = 120 * i - 90; // 120๋ ๊ฐ๊ฒฉ, -90๋๋ก ํ์ ํ์ฌ ์์ชฝ ์ ์
vertex(
cos(angle) * size * 0.5,
sin(angle) * size * 0.5
);
}
endShape(CLOSE);
}
// ์ญ์ ๋ชจ์ ๊ทธ๋ฆฌ๊ธฐ (๊ฐ๋ก ๋ง๋ + ์ธ๋ก ๋ง๋)
function drawCross(size) {
rect(-size / 2, -size / 6, size, size / 3); // ๊ฐ๋ก ๋ง๋
rect(-size / 6, -size / 2, size / 3, size); // ์ธ๋ก ๋ง๋
}
// ======================================================
// 7. ์
๋ ฅ ์ฒ๋ฆฌ
// ======================================================
// ์คํ์ด์ค๋ฐ๋ฅผ ๋๋ฅด๋ฉด ๋ชจ๋ ํ์ผ์ ์ธํ์ด ์๋ก ๋๋คํ
function keyPressed() {
if (key === " ") {
for (const tile of tiles) {
tile.randomizeAppearance();
}
}
}