

const noiseScale = 0.0005;
const numCurves = 1000;
let curves = [];
function setup() {
createCanvas(1000, 1000);
background(0);
noiseSeed(1000);
for (let i = 0; i < numCurves; i++) {
curves.push(new FlowCurve());
}
}
function draw() {
background(0, 20);
for (let curve of curves) {
curve.update();
curve.display();
}
}
class FlowCurve {
constructor() {
this.pos = createVector(random(width), random(height));
this.history = [];
this.maxLength = floor(random(30, 80));
this.strokeW = random(0.1, 0.3);
this.speed = random(1, 5);
this.age = 0;
this.lifespan = this.maxLength + random(300, 500);
this.growthPhase = this.maxLength;
this.dying = false;
}
update() {
this.age++;
// ์ฑ์ฅ ๋จ๊ณ: ์ ์ง์ ์ผ๋ก ๊ธธ์ด์ง
if (this.age < this.growthPhase) {
this.updatePosition();
}
// ์ฑ์ ๋จ๊ณ: ์ต๋ ๊ธธ์ด ์ ์ง
else if (this.age < this.lifespan - this.maxLength) {
this.updatePosition();
}
// ์๋ฉธ ๋จ๊ณ: ๊ผฌ๋ฆฌ๋ถํฐ ์ฌ๋ผ์ง
else {
this.dying = true;
if (this.history.length > 0) {
this.history.shift();
}
}
// ์์ ํ ์๋ฉธํ๋ฉด ์ฌ์์ฑ
if (this.dying && this.history.length === 0) {
this.pos.set(random(width), random(height));
this.history = [];
this.age = 0;
this.lifespan = this.maxLength + random(300, 500);
this.dying = false;
}
// ํ๋ฉด ๋ฐ์ผ๋ก ๋๊ฐ๋ฉด ์ฌ์์ฑ
if (
this.pos.x < -10 ||
this.pos.x > width + 10 ||
this.pos.y < -10 ||
this.pos.y > height + 10
) {
this.pos.set(random(width), random(height));
this.history = [];
this.age = 0;
this.lifespan = this.maxLength + random(300, 500);
this.dying = false;
}
}
updatePosition() {
let angle =
noise(this.pos.x * noiseScale, this.pos.y * noiseScale) * TWO_PI * 2;
this.pos.x += cos(angle) * this.speed;
this.pos.y += sin(angle) * this.speed;
this.history.push(this.pos.copy());
if (this.history.length > this.maxLength) {
this.history.shift();
}
}
display() {
const len = this.history.length;
if (len < 2) return;
noFill();
for (let i = 0; i < len - 1; i++) {
let t = i / (len - 1);
let alpha = (1 - t) * 255;
stroke(255, alpha);
strokeWeight(this.strokeW * (1 - t * 0.5));
let p1 = this.history[i];
let p2 = this.history[i + 1];
line(p1.x, p1.y, p2.x, p2.y);
}
}
}
// ๋
ธ์ด์ฆ ์ค์ผ์ผ: Perlin ๋
ธ์ด์ฆ์ ์ํ๋ง ๊ฐ๊ฒฉ์ ๊ฒฐ์ ํฉ๋๋ค.
// ๊ฐ์ด ์์์๋ก ๋ ๋๊ณ ๋ถ๋๋ฌ์ด ํ๋ฆ์ด ์์ฑ๋๊ณ , ํด์๋ก ๊ธ๊ฒฉํ ๋ณํ๊ฐ ์๊น๋๋ค.
const noiseScale = 0.0005;
// ์์ฑํ ๊ณก์ (ํํฐํด)์ ์ด ๊ฐ์์
๋๋ค.
// ๋ง์์๋ก ํ๋ฉด์ด ๋ ๋ฐ์ง๋๊ณ ๋ณต์กํ ํจํด์ ๋ง๋ญ๋๋ค.
const numCurves = 1000;
// ๋ชจ๋ ๊ณก์ ๊ฐ์ฒด๋ฅผ ์ ์ฅํ๋ ๋ฐฐ์ด์
๋๋ค.
let curves = [];
/**
* setup ํจ์: ํ๋ก๊ทธ๋จ ์์ ์ ํ ๋ฒ๋ง ์คํ๋ฉ๋๋ค.
* ์บ๋ฒ์ค๋ฅผ ์์ฑํ๊ณ , ๋
ธ์ด์ฆ ์๋๋ฅผ ๊ณ ์ ํ๋ฉฐ, ๊ณก์ ๊ฐ์ฒด๋ค์ ์ด๊ธฐํํฉ๋๋ค.
*/
function setup() {
// 1000x1000 ํฝ์
์ ์บ๋ฒ์ค๋ฅผ ์์ฑํฉ๋๋ค.
createCanvas(1000, 1000);
// ๋ฐฐ๊ฒฝ์ ๊ฒ์์(0)์ผ๋ก ์ค์ ํฉ๋๋ค.
background(0);
// Perlin ๋
ธ์ด์ฆ์ ์๋๋ฅผ 1000์ผ๋ก ๊ณ ์ ํฉ๋๋ค.
// ์๋๋ฅผ ๊ณ ์ ํ๋ฉด ์คํํ ๋๋ง๋ค ๋์ผํ ๋
ธ์ด์ฆ ํจํด์ด ์์ฑ๋์ด ์ฌํ ๊ฐ๋ฅํ ๊ฒฐ๊ณผ๋ฅผ ์ป์ต๋๋ค.
noiseSeed(1000);
// numCurves ๊ฐ์๋งํผ ๊ณก์ ๊ฐ์ฒด๋ฅผ ์์ฑํ์ฌ ๋ฐฐ์ด์ ์ถ๊ฐํฉ๋๋ค.
for (let i = 0; i < numCurves; i++) {
curves.push(new FlowCurve());
}
}
/**
* draw ํจ์: ๋งค ํ๋ ์(๋ณดํต ์ด๋น 60ํ)๋ง๋ค ๋ฐ๋ณต ์คํ๋ฉ๋๋ค.
* ๋ฐฐ๊ฒฝ์ ์ง์ฐ๊ณ (์์ ํจ๊ณผ๋ฅผ ์ํด ํฌ๋ช
ํ๊ฒ), ๋ชจ๋ ๊ณก์ ์ ์
๋ฐ์ดํธํ๊ณ ๊ทธ๋ฆฝ๋๋ค.
*/
function draw() {
// ๋ฐฐ๊ฒฝ์ ๊ฒ์์์ผ๋ก ๋ฎ๋, ์ํ๊ฐ 20์ ์ฌ์ฉํ์ฌ ์์(trail) ํจ๊ณผ๋ฅผ ๋ง๋ญ๋๋ค.
// ์ํ๊ฐ์ด ๋ฎ์์๋ก ์ด์ ํ๋ ์์ด ์ฒ์ฒํ ์ฌ๋ผ์ ธ ๋ถ๋๋ฌ์ด ์์์ด ๋จ์ต๋๋ค.
background(0, 20);
// curves ๋ฐฐ์ด์ ๋ชจ๋ ๊ณก์ ๊ฐ์ฒด๋ฅผ ์ํํ๋ฉฐ ์
๋ฐ์ดํธ์ ํ์๋ฅผ ์ํํฉ๋๋ค.
for (let curve of curves) {
curve.update(); // ์์น์ ์ํ๋ฅผ ์
๋ฐ์ดํธํฉ๋๋ค.
curve.display(); // ํ๋ฉด์ ๊ทธ๋ฆฝ๋๋ค.
}
}
/**
* FlowCurve ํด๋์ค: ๊ฐ๊ฐ์ ํ๋ฆ ๊ณก์ ์ ๋ํ๋ด๋ ๊ฐ์ฒด์
๋๋ค.
* ์์น ์ด๋ ฅ(history)์ ์ ์ฅํ์ฌ ๊ผฌ๋ฆฌ์ฒ๋ผ ๋ณด์ด๊ฒ ํ๊ณ ,
* ์ฑ์ฅโ์ฑ์โ์๋ฉธ์ ์๋ช
์ฃผ๊ธฐ๋ฅผ ๊ฐ์ง๋๋ค.
*/
class FlowCurve {
/**
* ์์ฑ์: ๊ฐ์ฒด๊ฐ ์์ฑ๋ ๋ ์ด๊ธฐ ์์ฑ์ ์ค์ ํฉ๋๋ค.
*/
constructor() {
// ํ์ฌ ์์น๋ฅผ ํ๋ฉด ๋ด ๋ฌด์์ ์ขํ๋ก ์ค์ ํฉ๋๋ค.
// createVector๋ x, y ์ขํ๋ฅผ ๊ฐ์ง ๋ฒกํฐ ๊ฐ์ฒด๋ฅผ ๋ง๋ญ๋๋ค.
this.pos = createVector(random(width), random(height));
// ๊ณผ๊ฑฐ ์์น๋ค์ ์ ์ฅํ๋ ๋ฐฐ์ด์
๋๋ค.
// ์ ์ ๊ทธ๋ฆด ๋ ์ด์ ์์น๋ค์ ์ฐ๊ฒฐํ์ฌ ๊ผฌ๋ฆฌ ๋ชจ์์ ๋ง๋ญ๋๋ค.
this.history = [];
// ๊ผฌ๋ฆฌ์ ์ต๋ ๊ธธ์ด(ํ์คํ ๋ฆฌ ๋ฐฐ์ด์ ์ต๋ ํฌ๊ธฐ)์
๋๋ค.
// 30์์ 80 ์ฌ์ด์ ๋ฌด์์ ์ ์๋ก ์ค์ ๋ฉ๋๋ค.
this.maxLength = floor(random(30, 80));
// ์ ์ ๊ตต๊ธฐ์
๋๋ค. 0.1์์ 0.3 ์ฌ์ด์ ๋งค์ฐ ๊ฐ๋ ์ ์ ๋ง๋ญ๋๋ค.
this.strokeW = random(0.1, 0.3);
// ์ด๋ ์๋์
๋๋ค. 1์์ 5 ์ฌ์ด์ ๋ฌด์์ ๊ฐ์ผ๋ก,
// ๋
ธ์ด์ฆ ํ๋๋ฅผ ๋ฐ๋ผ ์ด๋ํ๋ ์๋๋ฅผ ๊ฒฐ์ ํฉ๋๋ค.
this.speed = random(1, 5);
// ๊ฐ์ฒด์ ํ์ฌ ๋์ด(ํ๋ ์ ์)์
๋๋ค.
// ์๋ช
์ฃผ๊ธฐ ๊ด๋ฆฌ์ ์ฌ์ฉ๋ฉ๋๋ค.
this.age = 0;
// ์ ์ฒด ์๋ช
(ํ๋ ์ ์)์
๋๋ค.
// maxLength(์ฑ์ฅ/์๋ฉธ ๋จ๊ณ) + 300~500(์ฑ์ ๋จ๊ณ)์ผ๋ก ๊ณ์ฐ๋ฉ๋๋ค.
this.lifespan = this.maxLength + random(300, 500);
// ์ฑ์ฅ ๋จ๊ณ์ ๊ธธ์ด์
๋๋ค.
// maxLength๋งํผ ์ฑ์ฅํ์ฌ ์ต๋ ๊ธธ์ด์ ๋๋ฌํฉ๋๋ค.
this.growthPhase = this.maxLength;
// ์๋ฉธ ์ค์ธ์ง ์ฌ๋ถ๋ฅผ ๋ํ๋ด๋ ํ๋๊ทธ์
๋๋ค.
this.dying = false;
}
/**
* update ๋ฉ์๋: ๋งค ํ๋ ์๋ง๋ค ๊ฐ์ฒด์ ์ํ๋ฅผ ์
๋ฐ์ดํธํฉ๋๋ค.
* ์ฑ์ฅ, ์ฑ์, ์๋ฉธ ๋จ๊ณ๋ฅผ ๊ด๋ฆฌํ๊ณ ํ๋ฉด ๋ฐ์ผ๋ก ๋๊ฐ๋ฉด ์ฌ์์ฑํฉ๋๋ค.
*/
update() {
// ๋์ด๋ฅผ 1 ์ฆ๊ฐ์ํต๋๋ค.
this.age++;
// 1๋จ๊ณ: ์ฑ์ฅ ๋จ๊ณ
// ๋์ด๊ฐ growthPhase๋ณด๋ค ์์ ๋๋ ๊ผฌ๋ฆฌ๊ฐ ์ ์ ๊ธธ์ด์ง๋๋ค.
if (this.age < this.growthPhase) {
this.updatePosition();
}
// 2๋จ๊ณ: ์ฑ์ ๋จ๊ณ
// ์ต๋ ๊ธธ์ด๋ฅผ ์ ์งํ๋ฉฐ ์ด๋ํฉ๋๋ค.
// ์๋ช
์ด lifespan - maxLength์ ๋๋ฌํ ๋๊น์ง ์ง์๋ฉ๋๋ค.
else if (this.age < this.lifespan - this.maxLength) {
this.updatePosition();
}
// 3๋จ๊ณ: ์๋ฉธ ๋จ๊ณ
// ๊ผฌ๋ฆฌ๊ฐ ๋จธ๋ฆฌ๋ถํฐ ์ ์ ์งง์์ง๋๋ค(shift๋ก ์๋ถ๋ถ ์ ๊ฑฐ).
else {
this.dying = true;
if (this.history.length > 0) {
// ๋ฐฐ์ด์ ์ฒซ ๋ฒ์งธ ์์(๊ฐ์ฅ ์ค๋๋ ์์น)๋ฅผ ์ ๊ฑฐํฉ๋๋ค.
this.history.shift();
}
}
// ์์ ํ ์๋ฉธํ๋ฉด(ํ์คํ ๋ฆฌ๊ฐ ๋น๋ฉด) ์๋ก์ด ์์น์์ ์ฌ์์ฑํฉ๋๋ค.
if (this.dying && this.history.length === 0) {
this.reset();
}
// ํ๋ฉด ๋ฐ์ผ๋ก ๋๊ฐ๋ฉด(์ฌ๋ฐฑ 10ํฝ์
) ์ฆ์ ์ฌ์์ฑํฉ๋๋ค.
if (
this.pos.x < -10 ||
this.pos.x > width + 10 ||
this.pos.y < -10 ||
this.pos.y > height + 10
) {
this.reset();
}
}
/**
* reset ๋ฉ์๋: ๊ฐ์ฒด๋ฅผ ์ด๊ธฐ ์ํ๋ก ๋ฆฌ์
ํ์ฌ ์๋ก์ด ์๋ช
์ฃผ๊ธฐ๋ฅผ ์์ํฉ๋๋ค.
*/
reset() {
// ์์น๋ฅผ ํ๋ฉด ๋ด ๋ฌด์์๋ก ์ฌ์ค์ ํฉ๋๋ค.
this.pos.set(random(width), random(height));
// ํ์คํ ๋ฆฌ๋ฅผ ๋น์๋๋ค.
this.history = [];
// ๋์ด๋ฅผ 0์ผ๋ก ์ด๊ธฐํํฉ๋๋ค.
this.age = 0;
// ์๋ก์ด ์๋ช
์ ์ค์ ํฉ๋๋ค.
this.lifespan = this.maxLength + random(300, 500);
// ์๋ฉธ ์ํ๋ฅผ ํด์ ํฉ๋๋ค.
this.dying = false;
}
/**
* updatePosition ๋ฉ์๋: Perlin ๋
ธ์ด์ฆ๋ฅผ ์ฌ์ฉํ์ฌ ๋ค์ ์์น๋ฅผ ๊ณ์ฐํ๊ณ ์ด๋ํฉ๋๋ค.
*/
updatePosition() {
// ํ์ฌ ์์น์ Perlin ๋
ธ์ด์ฆ๋ฅผ ์ ์ฉํ์ฌ ๊ฐ๋๋ฅผ ๊ณ์ฐํฉ๋๋ค.
// noiseScale์ ๊ณฑํ์ฌ ์ํ๋ง ์์น๋ฅผ ์กฐ์ ํ๊ณ , TWO_PI * 2(720๋) ๋ฒ์๋ก ๋งคํํฉ๋๋ค.
let angle =
noise(this.pos.x * noiseScale, this.pos.y * noiseScale) * TWO_PI * 2;
// ์ผ๊ฐํจ์๋ฅผ ์ฌ์ฉํ์ฌ x, y ๋ฐฉํฅ์ผ๋ก ์ด๋๋์ ๊ณ์ฐํฉ๋๋ค.
this.pos.x += cos(angle) * this.speed;
this.pos.y += sin(angle) * this.speed;
// ํ์ฌ ์์น๋ฅผ ํ์คํ ๋ฆฌ ๋ฐฐ์ด์ ์ถ๊ฐํฉ๋๋ค(๋ณต์ฌ๋ณธ์ ์ ์ฅ).
this.history.push(this.pos.copy());
// ํ์คํ ๋ฆฌ๊ฐ maxLength๋ฅผ ์ด๊ณผํ๋ฉด ๊ฐ์ฅ ์ค๋๋ ์์น๋ฅผ ์ ๊ฑฐํฉ๋๋ค.
if (this.history.length > this.maxLength) {
this.history.shift();
}
}
/**
* display ๋ฉ์๋: ํ์คํ ๋ฆฌ ๋ฐฐ์ด์ ์ ๋ค์ ์ ์ผ๋ก ์ฐ๊ฒฐํ์ฌ ํ๋ฉด์ ๊ทธ๋ฆฝ๋๋ค.
* ๊ทธ๋ผ๋ฐ์ด์
ํจ๊ณผ๋ฅผ ์ํด ์ ๋ถ๋ง๋ค ํฌ๋ช
๋๋ฅผ ๋ค๋ฅด๊ฒ ์ ์ฉํฉ๋๋ค.
*/
display() {
// ํ์คํ ๋ฆฌ ๊ธธ์ด๋ฅผ ์ ์ฅํฉ๋๋ค.
const len = this.history.length;
// ์ ์ด 2๊ฐ ๋ฏธ๋ง์ด๋ฉด ์ ์ ๊ทธ๋ฆด ์ ์์ผ๋ฏ๋ก ์ข
๋ฃํฉ๋๋ค.
if (len < 2) return;
// ์ฑ์ฐ๊ธฐ ์์ด ์ ๋ง ๊ทธ๋ฆฝ๋๋ค.
noFill();
// ํ์คํ ๋ฆฌ์ ๊ฐ ์ ์ ์ํํ๋ฉฐ ์ ๋ถ์ ๊ทธ๋ฆฝ๋๋ค.
for (let i = 0; i < len - 1; i++) {
// ํ์ฌ ์ธ๋ฑ์ค๋ฅผ 0~1 ๋ฒ์๋ก ์ ๊ทํํฉ๋๋ค.
// t=0์ ๊ผฌ๋ฆฌ(์ค๋๋ ๋ถ๋ถ), t=1์ ๋จธ๋ฆฌ(์๋ก์ด ๋ถ๋ถ)์
๋๋ค.
let t = i / (len - 1);
// ํฌ๋ช
๋๋ฅผ ๊ณ์ฐํฉ๋๋ค.
// t๊ฐ 0(๊ผฌ๋ฆฌ)์ผ ๋ alpha=255(๋ถํฌ๋ช
),
// t๊ฐ 1(๋จธ๋ฆฌ)์ ๊ฐ๊น์ธ์๋ก alpha๊ฐ 0์ ๊ฐ๊น์์ง๋๋ค.
// ๊ทธ๋ฌ๋ ์ฌ๊ธฐ์๋ (1-t)๋ฅผ ์ฌ์ฉํ์ฌ ๊ผฌ๋ฆฌ๊ฐ ํ๋ฆฟํ๊ฒ, ๋จธ๋ฆฌ๊ฐ ์ ๋ช
ํ๊ฒ ๋ง๋ญ๋๋ค.
let alpha = (1 - t) * 255;
// ํฐ์(255)์ ๊ณ์ฐ๋ ํฌ๋ช
๋๋ฅผ ์ ์ฉํฉ๋๋ค.
stroke(255, alpha);
// ์ ๊ตต๊ธฐ๋ t์ ๋ฐ๋ผ ๋ณํ์ํต๋๋ค.
// ๋จธ๋ฆฌ(t=1)์ชฝ์ผ๋ก ๊ฐ์๋ก ๊ฐ๋์ด์ง๋๋ค(0.5๋ฐฐ).
strokeWeight(this.strokeW * (1 - t * 0.5));
// ํ์ฌ ์ ๊ณผ ๋ค์ ์ ์ ์ฐ๊ฒฐํ๋ ์ ์ ๊ทธ๋ฆฝ๋๋ค.
let p1 = this.history[i];
let p2 = this.history[i + 1];
line(p1.x, p1.y, p2.x, p2.y);
}
}
}