๐ŸŽ‡ Pointing Triangle

BamgasiJMยท2025๋…„ 12์›” 22์ผ

p5.js Art

๋ชฉ๋ก ๋ณด๊ธฐ
39/65
post-thumbnail

๐Ÿ“ p5.js

const TRI_SIZE = 30; 
const SPACING = 30; 
const BRIGHT_RANGE = 600;
const ROTATION_EASING = 0.1;

const HUE_MIN = 180; 
const HUE_MAX = 240; 
const BRIGHTNESS_NEAR = 90; 
const BRIGHTNESS_FAR = 10; 

let grid;

function setup() {
  createCanvas(windowWidth, windowHeight);
  colorMode(HSB, 360, 100, 100);
  grid = new TriangleGrid();
}

function draw() {
  background(0);
  grid.update();
  grid.display();
}

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  grid = new TriangleGrid();
}

class TriangleGrid {
  constructor() {
    this.cols = ceil(width / SPACING);
    this.rows = ceil(height / SPACING);
    this.triangles = [];
    this.initializeTriangles();
  }

  initializeTriangles() {
    for (let i = 0; i < this.cols; i++) {
      for (let j = 0; j < this.rows; j++) {
        let x = SPACING / 2 + i * SPACING;
        let y = SPACING / 2 + j * SPACING;
        this.triangles.push(new Triangle(x, y));
      }
    }
  }

  update() {
    for (let tri of this.triangles) {
      tri.update();
    }
  }

  display() {
    for (let tri of this.triangles) {
      tri.display();
    }
  }
}

class Triangle {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.currentAngle = 0;
  }

  update() {
    let targetAngle = atan2(mouseY - this.y, mouseX - this.x);

    let diff = targetAngle - this.currentAngle;
    if (diff > PI) diff -= TWO_PI;
    if (diff < -PI) diff += TWO_PI;

    this.currentAngle += diff * ROTATION_EASING;
  }

  display() {
    let distance = dist(mouseX, mouseY, this.x, this.y);

    let hue = map(distance, 0, BRIGHT_RANGE, HUE_MIN, HUE_MAX, true);

    let brightness;
    if (distance < BRIGHT_RANGE) {
      let t = distance / BRIGHT_RANGE;
      let easedT = 1 - pow(1 - t, 3);
      brightness = lerp(BRIGHTNESS_NEAR, BRIGHTNESS_FAR, easedT);
    } else {
      brightness = BRIGHTNESS_FAR;
    }

    noStroke();
    fill(hue, 100, brightness);

    push();

    translate(this.x, this.y);
    rotate(this.currentAngle);
    beginShape();
    vertex(TRI_SIZE / 2, 0);
    vertex(-TRI_SIZE / 2, -TRI_SIZE / 6);
    vertex(-TRI_SIZE / 2, TRI_SIZE / 6);
    endShape(CLOSE);

    pop();
  }
}

๐Ÿ“ p5.js + comment

// ======================================================
// 1. ์ „์—ญ ์„ค์ •๊ฐ’ (Configuration Constants)
// ======================================================

// ์‚ผ๊ฐํ˜• ๊ทธ๋ฆฌ๋“œ ๊ธฐ๋ณธ ์„ค์ •
const TRI_SIZE = 20;              // ์‚ผ๊ฐํ˜• ํฌ๊ธฐ
const SPACING = 30;               // ์‚ผ๊ฐํ˜• ๊ฐ„๊ฒฉ
const BRIGHT_RANGE = 500;         // ๋ฐ์•„์ง€๋Š” ํšจ๊ณผ ๋ฒ”์œ„ (ํ”ฝ์…€)
const ROTATION_EASING = 0.1;      // ํšŒ์ „ easing ๊ฐ’ (0~1, ์ž‘์„์ˆ˜๋ก ๋ถ€๋“œ๋Ÿฌ์›€)

// HSB ์ƒ‰์ƒ ์„ค์ •
const HUE_MIN = 180;              // Hue ์ตœ์†Œ๊ฐ’ (0~360, ์ฒญ๋ก์ƒ‰)
const HUE_MAX = 240;              // Hue ์ตœ๋Œ€๊ฐ’ (0~360, ํŒŒ๋ž€์ƒ‰)
const BRIGHTNESS_NEAR = 80;       // ๊ฐ€๊นŒ์šด ๊ณณ์˜ ๋ฐ๊ธฐ (0~100)
const BRIGHTNESS_FAR = 20;        // ๋จผ ๊ณณ์˜ ๋ฐ๊ธฐ (0~100)

// ======================================================
// 2. ์ „์—ญ ์ƒํƒœ ๋ณ€์ˆ˜ (Global State)
// ======================================================

let grid; // ์‚ผ๊ฐํ˜• ๊ทธ๋ฆฌ๋“œ ๊ฐ์ฒด (TriangleGrid ์ธ์Šคํ„ด์Šค)

// ======================================================
// 3. p5.js ๋ผ์ดํ”„์‚ฌ์ดํด ํ•จ์ˆ˜
// ======================================================

// p5.js ์ดˆ๊ธฐ ์„ค์ • ํ•จ์ˆ˜ (ํ”„๋กœ๊ทธ๋žจ ์‹œ์ž‘ ์‹œ ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰)
function setup() {
  // ์บ”๋ฒ„์Šค ์ƒ์„ฑ (์ „์ฒด ์œˆ๋„์šฐ ํฌ๊ธฐ)
  createCanvas(windowWidth, windowHeight);
  
  // ์ƒ‰์ƒ ๋ชจ๋“œ๋ฅผ HSB๋กœ ์„ค์ •
  // hue: 0~360, saturation/brightness: 0~100
  colorMode(HSB, 360, 100, 100);
  
  // ์‚ผ๊ฐํ˜• ๊ทธ๋ฆฌ๋“œ ์ดˆ๊ธฐํ™”
  grid = new TriangleGrid();
}

// p5.js ๋ฉ”์ธ ๋ฃจํ”„ ํ•จ์ˆ˜ (๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ์‹คํ–‰)
function draw() {
  // ๋ฐฐ๊ฒฝ์„ ๊ฒ€์€์ƒ‰์œผ๋กœ ์ดˆ๊ธฐํ™”
  background(0);
  
  // ๋ชจ๋“  ์‚ผ๊ฐํ˜•์˜ ์ƒํƒœ ์—…๋ฐ์ดํŠธ (๊ฐ๋„ ๊ณ„์‚ฐ)
  grid.update();
  
  // ๋ชจ๋“  ์‚ผ๊ฐํ˜• ํ™”๋ฉด์— ๊ทธ๋ฆฌ๊ธฐ
  grid.display();
}

// ์œˆ๋„์šฐ ํฌ๊ธฐ ๋ณ€๊ฒฝ ์‹œ ํ˜ธ์ถœ๋˜๋Š” ํ•จ์ˆ˜
function windowResized() {
  // ์บ”๋ฒ„์Šค ํฌ๊ธฐ ์žฌ์กฐ์ •
  resizeCanvas(windowWidth, windowHeight);
  
  // ๊ทธ๋ฆฌ๋“œ ์žฌ์ƒ์„ฑ
  grid = new TriangleGrid();
}

// ======================================================
// 4. ํด๋ž˜์Šค ์ •์˜ (Class Definitions)
// ======================================================

// ์‚ผ๊ฐํ˜• ๊ทธ๋ฆฌ๋“œ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ํด๋ž˜์Šค
class TriangleGrid {
  constructor() {
    // ํ™”๋ฉด์— ํ•„์š”ํ•œ ์—ด(column)๊ณผ ํ–‰(row) ๊ฐœ์ˆ˜ ๊ณ„์‚ฐ
    this.cols = ceil(width / SPACING);
    this.rows = ceil(height / SPACING);
    
    // ๋ชจ๋“  ์‚ผ๊ฐํ˜• ๊ฐ์ฒด๋ฅผ ๋‹ด์„ ๋ฐฐ์—ด
    this.triangles = [];
    
    // ์‚ผ๊ฐํ˜• ๊ฐ์ฒด๋“ค ์ƒ์„ฑ ๋ฐ ์ดˆ๊ธฐํ™”
    this.initializeTriangles();
  }
  
  // ๊ทธ๋ฆฌ๋“œ ์ƒ์˜ ๋ชจ๋“  ์œ„์น˜์— ์‚ผ๊ฐํ˜• ๊ฐ์ฒด ์ƒ์„ฑ
  initializeTriangles() {
    for (let i = 0; i < this.cols; i++) {
      for (let j = 0; j < this.rows; j++) {
        // ๊ฐ ์‚ผ๊ฐํ˜•์˜ ํ™”๋ฉด ์ƒ ์ขŒํ‘œ ๊ณ„์‚ฐ
        let x = SPACING / 2 + i * SPACING;
        let y = SPACING / 2 + j * SPACING;
        
        // Triangle ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ ํ›„ ๋ฐฐ์—ด์— ์ถ”๊ฐ€
        this.triangles.push(new Triangle(x, y));
      }
    }
  }
  
  // ๋ชจ๋“  ์‚ผ๊ฐํ˜•์˜ ์ƒํƒœ ์—…๋ฐ์ดํŠธ (๊ฐ๋„ ๊ณ„์‚ฐ)
  update() {
    for (let tri of this.triangles) {
      tri.update();
    }
  }
  
  // ๋ชจ๋“  ์‚ผ๊ฐํ˜•์„ ํ™”๋ฉด์— ๊ทธ๋ฆฌ๊ธฐ
  display() {
    for (let tri of this.triangles) {
      tri.display();
    }
  }
}

// ๊ฐœ๋ณ„ ์‚ผ๊ฐํ˜•์„ ๋‚˜ํƒ€๋‚ด๋Š” ํด๋ž˜์Šค
class Triangle {
  constructor(x, y) {
    this.x = x;              // ์‚ผ๊ฐํ˜•์˜ X ์ขŒํ‘œ
    this.y = y;              // ์‚ผ๊ฐํ˜•์˜ Y ์ขŒํ‘œ
    this.currentAngle = 0;   // ํ˜„์žฌ ํšŒ์ „ ๊ฐ๋„ (๋ผ๋””์•ˆ)
  }
  
  // ์‚ผ๊ฐํ˜•์˜ ํšŒ์ „ ๊ฐ๋„๋ฅผ ๋งˆ์šฐ์Šค ๋ฐฉํ–ฅ์œผ๋กœ ์—…๋ฐ์ดํŠธ
  update() {
    // ๋งˆ์šฐ์Šค๋ฅผ ํ–ฅํ•˜๋Š” ๋ชฉํ‘œ ๊ฐ๋„ ๊ณ„์‚ฐ
    let targetAngle = atan2(mouseY - this.y, mouseX - this.x);
    
    // ํ˜„์žฌ ๊ฐ๋„์™€ ๋ชฉํ‘œ ๊ฐ๋„์˜ ์ฐจ์ด ๊ณ„์‚ฐ
    let diff = targetAngle - this.currentAngle;
    
    // ๊ฐ๋„ ์ฐจ์ด๋ฅผ -PI ~ PI ๋ฒ”์œ„๋กœ ์ •๊ทœํ™”
    // (์ตœ๋‹จ ๊ฒฝ๋กœ๋กœ ํšŒ์ „ํ•˜๋„๋ก ๋ณด์ •)
    if (diff > PI) diff -= TWO_PI;
    if (diff < -PI) diff += TWO_PI;
    
    // easing์„ ์ ์šฉํ•˜์—ฌ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ํšŒ์ „
    this.currentAngle += diff * ROTATION_EASING;
  }
  
  // ์‚ผ๊ฐํ˜•์„ ํ™”๋ฉด์— ๊ทธ๋ฆฌ๊ธฐ
  display() {
    // ๋งˆ์šฐ์Šค์™€ ์‚ผ๊ฐํ˜• ์‚ฌ์ด์˜ ๊ฑฐ๋ฆฌ ๊ณ„์‚ฐ
    let distance = dist(mouseX, mouseY, this.x, this.y);
    
    // ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ฅธ Hue ๊ฐ’ ๊ณ„์‚ฐ
    // ๊ฐ€๊นŒ์šฐ๋ฉด HUE_MIN, ๋ฉ€๋ฉด HUE_MAX
    let hue = map(distance, 0, BRIGHT_RANGE, HUE_MIN, HUE_MAX, true);
    
    // ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ฅธ ๋ฐ๊ธฐ ๊ณ„์‚ฐ (ease-out cubic ์ ์šฉ)
    let brightness;
    if (distance < BRIGHT_RANGE) {
      // 0~1 ์‚ฌ์ด๋กœ ์ •๊ทœํ™”
      let t = distance / BRIGHT_RANGE;
      
      // ease-out cubic ํ•จ์ˆ˜: 1 - (1-t)ยณ
      // ๊ฐ€๊นŒ์šธ ๋•Œ๋Š” ์ฒœ์ฒœํžˆ ๋ณ€ํ™”, ๋ฉ€ ๋•Œ๋Š” ๊ธ‰๊ฒฉํžˆ ๋ณ€ํ™”
      let easedT = 1 - pow(1 - t, 3);
      
      // ์ •๊ทœํ™”๋œ ๊ฐ’์„ ์‹ค์ œ ๋ฐ๊ธฐ ๊ฐ’์œผ๋กœ ๋ณ€ํ™˜
      brightness = lerp(BRIGHTNESS_NEAR, BRIGHTNESS_FAR, easedT);
    } else {
      // ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚œ ๊ฒฝ์šฐ ๋จผ ๊ณณ์˜ ๋ฐ๊ธฐ ์ ์šฉ
      brightness = BRIGHTNESS_FAR;
    }
    
    // ์™ธ๊ณฝ์„  ์—†์ด ์ƒ‰์ƒ๋งŒ ์ฑ„์šฐ๊ธฐ
    noStroke();
    fill(hue, 100, brightness);
    
    // ์‚ผ๊ฐํ˜• ๊ทธ๋ฆฌ๊ธฐ
    push();

    translate(this.x, this.y);             // ์‚ผ๊ฐํ˜• ์œ„์น˜๋กœ ์ด๋™
    rotate(this.currentAngle);             // ๊ณ„์‚ฐ๋œ ๊ฐ๋„๋กœ ํšŒ์ „
    beginShape();
    vertex(TRI_SIZE / 2, 0);               // ์˜ค๋ฅธ์ชฝ ๋ (์•ž์ชฝ)
    vertex(-TRI_SIZE / 2, -TRI_SIZE / 4);  // ์™ผ์ชฝ ์•„๋ž˜
    vertex(-TRI_SIZE / 2, TRI_SIZE / 4);   // ์™ผ์ชฝ ์œ„
    endShape(CLOSE);

    pop();
  }
}

profile
Coding Art with Blender / oF / Processing / p5.js / nannou

0๊ฐœ์˜ ๋Œ“๊ธ€