パーティクルが色の類似性によってクラスタリングされ、似ている色のパーティクルが引き合う。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>カラー パーティクル クラスタリング</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
}
canvas {
display: block;
}
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
<script>
let particles = [];
const numParticles = 100; // パーティクルの数
function setup() {
createCanvas(windowWidth, windowHeight);
noStroke(); // パーティクルに輪郭をつけない
for (let i = 0; i < numParticles; i++) {
// ランダムな位置にパーティクルを生成
particles.push(new Particle(random(width), random(height)));
}
}
function draw() {
background(255); // 背景を白にリセット
for (let p of particles) {
p.move(); // パーティクルを移動させる
p.display(); // パーティクルを表示する
p.interact(particles); // 他のパーティクルとの相互作用
}
}
class Particle {
constructor(x, y) {
this.pos = createVector(x, y); // パーティクルの位置
this.vel = createVector(random(-1, 1), random(-1, 1)); // ランダムな速度
this.acc = createVector(0, 0); // 初期加速度は0
this.radius = random(10, 20); // ランダムなサイズ
this.color = [random(255), random(255), random(255)]; // ランダムなカラー
}
move() {
this.vel.add(this.acc); // 加速度を速度に加算
this.pos.add(this.vel); // 速度を位置に加算
this.acc.mult(0); // 加速度をリセット
this.vel.limit(2); // 最大速度を2に制限
// 画面の端に達したら反射
if (this.pos.x < 0 || this.pos.x > width) this.vel.x *= -1;
if (this.pos.y < 0 || this.pos.y > height) this.vel.y *= -1;
}
display() {
// パーティクルを描画 (カラーとサイズ)
fill(this.color[0], this.color[1], this.color[2]);
ellipse(this.pos.x, this.pos.y, this.radius * 2);
}
interact(particles) {
for (let other of particles) {
if (other !== this) {
// 他のパーティクルとの距離を計算
let distVect = p5.Vector.sub(other.pos, this.pos);
let distance = distVect.mag(); // 距離の大きさ
let minDist = this.radius + other.radius; // パーティクル同士の最小距離
// 距離が最小距離より小さい場合
if (distance < minDist) {
let force = distVect.copy().normalize(); // 力の方向ベクトルを正規化
let colorSimilarity = this.getColorSimilarity(other); // 色の類似度を計算
// 色が似ている場合、引力を強める
if (colorSimilarity > 0.75) {
force.mult(0.5); // 引力を強化
this.acc.add(force);
}
// 色があまり似ていない場合、反発力を弱める
else if (colorSimilarity < 0.5) {
force.mult(-0.02); // 反発力を弱める
this.acc.add(force);
}
}
}
}
}
// 2つのパーティクルのカラーの類似度を計算する関数
getColorSimilarity(other) {
let color1 = this.color;
let color2 = other.color;
// ベクトルのコサイン類似度を計算
let dotProduct = color1[0] * color2[0] + color1[1] * color2[1] + color1[2] * color2[2];
let mag1 = sqrt(color1[0] ** 2 + color1[1] ** 2 + color1[2] ** 2);
let mag2 = sqrt(color2[0] ** 2 + color2[1] ** 2 + color2[2] ** 2);
return dotProduct / (mag1 * mag2); // 類似度(1に近いほど類似)
}
}
// ウィンドウサイズが変更されたときにキャンバスをリサイズ
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}
</script>
</body>
</html>
説明:
全体のパーティクルシステム:
particles 配列でパーティクルのインスタンスを管理。
各パーティクルは move 関数で動かされ、interact 関数で他のパーティクルとの相互作用を計算。
パーティクルの動作:
パーティクルはランダムな初期位置と速度で生成され、画面内をランダムに移動。
画面の端に達すると、速度の符号を反転させて画面内に留まるようにしている(壁にぶつかると跳ね返る)。
パーティクル間の相互作用:
interact 関数では、色の類似度に基づいて引力や反発力を計算。
似た色のパーティクルは引き合い、色が異なる場合は反発する。
類似度の計算には「コサイン類似度」を使用し、ベクトルとして色の値を扱う。