Processing|噂が広がる概念図

Processing

噂が広がる概念図を作ってみました。

球状に。もう少し直したいところもありますが、途中段階の記録として。

Visual

時間とともにノードが増えていく。

Code

// SNS / Rumor Spread Model
// 3D Spherical, Black & White
// Multiple sources over time
// Processing 4.x

int NODE_NUM = 180;

ArrayList<Node> nodes = new ArrayList<Node>();
ArrayList<Edge> edges = new ArrayList<Edge>();

// 拡散パラメータ
float spreadDelay = 2.2;     // 拡散までの待ち時間(秒)
float spreadProb  = 0.55;    // 拡散確率

// 新しい発信源
float newSourceInterval = 12.0;  // 秒
float lastSourceTime = 0;

// 球体・回転
float sphereRadius = 300;
float rotation = 0;

void setup() {
  size(900, 900, P3D);
  smooth(8);
  
  // ノード生成(球の内部)
  for (int i = 0; i < NODE_NUM; i++) {
    PVector p = randomPointInSphere(sphereRadius);
    nodes.add(new Node(p));
  }
  
  // ネットワーク生成
  for (int i = 0; i < NODE_NUM; i++) {
    int connections = int(random(3, 7));
    for (int j = 0; j < connections; j++) {
      int t = int(random(NODE_NUM));
      if (t != i) {
        edges.add(new Edge(nodes.get(i), nodes.get(t)));
      }
    }
  }
  
  // 最初の発信源
  nodes.get(int(random(NODE_NUM))).activate();
  lastSourceTime = millis() / 1000.0;
}

void draw() {
  background(0);
  lights();
  
  translate(width/2, height/2);
  rotateY(rotation);
  rotateX(rotation * 0.6);
  rotation += 0.0015;
  
  // 一定時間ごとに新しい発信源
  float now = millis() / 1000.0;
  if (now - lastSourceTime > newSourceInterval) {
    spawnNewSource();
    lastSourceTime = now;
  }
  
  // 描画
  for (Edge e : edges) e.display();
  for (Node n : nodes) {
    n.update();
    n.display();
  }
}

// ------------------------
// 球内部ランダム配置

PVector randomPointInSphere(float r) {
  PVector p;
  do {
    p = new PVector(
      random(-r, r),
      random(-r, r),
      random(-r, r)
    );
  } while (p.mag() > r);
  return p;
}

// ------------------------
// 新しい発信源を生成

void spawnNewSource() {
  ArrayList<Node> candidates = new ArrayList<Node>();
  
  for (Node n : nodes) {
    if (!n.active) candidates.add(n);
  }
  
  if (candidates.size() > 0) {
    Node n = candidates.get(int(random(candidates.size())));
    n.activate();
  }
}

// ------------------------
// Node

class Node {
  PVector pos;
  boolean active = false;
  float activatedTime = -1;
  
  Node(PVector p) {
    pos = p;
  }
  
  void activate() {
    active = true;
    activatedTime = millis() / 1000.0;
  }
  
  void update() {
    if (!active) return;
    
    float now = millis() / 1000.0;
    
    if (now - activatedTime > spreadDelay) {
      for (Edge e : edges) {
        Node other = null;
        if (e.a == this) other = e.b;
        if (e.b == this) other = e.a;
        
        if (other != null && !other.active) {
          if (random(1) < spreadProb) {
            other.activate();
          }
        }
      }
      
      // 二重拡散防止
      activatedTime = now + 999;
    }
  }
  
  void display() {
    if (!active) return;
    
    float now = millis() / 1000.0;
    float age = now - activatedTime;
    
    // 時間とともに明るくなる
    float brightness = constrain(map(age, 0, 8, 120, 240), 120, 240);
    float depth = map(pos.z, -sphereRadius, sphereRadius, 0.5, 1.2);
    
    pushMatrix();
    translate(pos.x, pos.y, pos.z);
    noStroke();
    fill(brightness * depth);
    sphereDetail(6);
    sphere(4 * depth);
    popMatrix();
  }
}

// ------------------------
// Edge

class Edge {
  Node a, b;
  
  Edge(Node a, Node b) {
    this.a = a;
    this.b = b;
  }
  
  void display() {
    if (!(a.active || b.active)) return;
    
    float zAvg = (a.pos.z + b.pos.z) * 0.5;
    float col = map(zAvg, -sphereRadius, sphereRadius, 80, 200);
    
    stroke(col);
    strokeWeight(1.2);
    line(
      a.pos.x, a.pos.y, a.pos.z,
      b.pos.x, b.pos.y, b.pos.z
    );
  }
}

オススメのProcessing参考書

Processing クリエイティブ・コーディング入門
―コードが生み出す創造表現

コメント

タイトルとURLをコピーしました