2026年4月2日木曜日

🎡ボロノイ図とドロネー三角形分割 distance関数(= length(p2-p1)) 

 簡単な例として、ある街にある商店群を考えてみよう。ここで、ある店の顧客数を推定したいとする。他のすべての条件(価格,商品,サービスの質など)が同じであれば,顧客は単に距離の考慮によって好きな店を選ぶと仮定するのが妥当である:彼らは自分に最も近い店に行くだろう.

https://en.wikipedia.org/wiki/Voronoi_diagram
画像

ボロノイ図の双対グラフ(点群からなるユークリッド空間の場合)は、同じ点群に対するドロネーの三角形分割に相当する。

一般に離散点集合Pのドロネー三角形分割は、Pのボロノイ図の双対グラフに相当する。ドロネー三角形の外接はボロノイ図の頂点となる。2次元の場合、ボロノイ図の頂点は、ドロネーの三角形の隣接関係から得られる辺で結ばれています。ドロネーの三角形分割で辺を共有する2つの三角形は、その外周がボロノイ分割の辺で接続されます。

https://en.wikipedia.org/wiki/Delaunay_triangulation

しくみはいまいちわからないが、たぶんボロのってるコードなのかなと思う。distのところとか

画像
https://glslsandbox.com/e#92546.1

GLSLのコード、距離とって最小とって濃淡付けてというのは同じみたいだが、コードを読んでみても三角形がどうこうというのと直感的につながらない。さて

セルラーノイズではディスタンスフィールドに基づいて、複数の点の中から最も近い点への距離を計算します。4つの点に対してディスタンスフィールドを作りたいとしましょう。どうすれば良いでしょうか。それぞれのピクセルについて、最も近い点への距離を計算したいですね。現在のピクセルからすべての点への距離を繰り返し計算し、最も近い点への距離を記録するようにします。

黙って受け入れよう

distance は2点 p0 と p1 の間の距離を返す。つまり、length(p0 - p1) である。

https://registry.khronos.org/OpenGL-Refpages/gl4/html/distance.xhtml

ドロネー三角形分割(Delaunay triangulation)とボロノイ図(Voronoi diagram)は、点の集合を基にした幾何学的な構造であり、密接に関連していますが、それぞれ異なる概念を持っています。

  1. ドロネー三角形分割 (Delaunay triangulation):

    • これは、2次元の点の集合を基にして、その点たちを三角形で繋ぐ方法です。

    • 三角形分割の中で、任意の三角形の外接円の内部に他の点が存在しないようにします。

    • この性質により、分割された三角形の角度がなるべく大きくなり、狭い角度を持つ三角形が生成されにくいです。

  2. ボロノイ図 (Voronoi diagram):

    • 2次元の点の集合を基にして、それぞれの点に最も近い領域を生成する方法です。

    • 具体的には、ボロノイ図におけるある領域内の任意の点は、その領域に属する生成点に他の生成点よりも近いです。

    • それぞれの領域は多角形として描かれ、隣接する領域の境界は、2つの生成点の中点を結んだ直線になります。


画像
https://alexbeutel.com/webgl/voronoi.html


画像
https://d3js.org/d3-delaunay/voronoi


画像
https://github.com/Dozed12/p5.voronoi

ボロノイ図を手動で作成する場合、以下の基本的なステップを参考にすることができます:

  1. 点の配置:

    • まず、平面上に点(生成点と呼ばれる)をいくつか配置します。これらの点が、ボロノイ図の各セルの「中心」となります。

  2. 中点の特定:

    • 任意の2つの生成点を選び、それらの中点を求めます。

  3. 垂直二等分線の引き方:

    • 2つの生成点を結ぶ線分の垂直二等分線を求めます。この線は、2つの生成点に最も近い領域を分割する境界となります。

  4. 境界の確定:

    • その他の生成点に対しても、2つの点の組み合わせごとに垂直二等分線を引き、境界を確定します。

  5. セルの確定:

    • ある生成点に最も近い領域をその点のボロノイセルとします。上記の手順で、すべての生成点に対してセルが定義され、ボロノイ図が完成します。

  6. 無限の境界:

    • ボロノイ図には、無限に広がるセルが存在することがあります。これは、その生成点が他の生成点から十分離れている場合に起こります。手動での描画では、このようなセルの境界は適切に切り取られるか、無限に伸びるとみなされます。

平面上に与えられた有限個の点 P={p1​,p2​,...,pn​} に対して、任意の点 pi​ のボロノイ領域 V(pi​) は以下のように定義されます。


画像


画像
let points = [];
let n = 10; // 生成する点の数

function setup() {
  createCanvas(400, 400);
  colorMode(HSB); // 色をHSBモードで指定
  // ランダムな位置に点をn個生成
  for (let i = 0; i < n; i++) {
    points.push(createVector(random(width), random(height)));
  }
}

function draw() {
  background(255);
  
  // 各ピクセルについて、最も近い点を見つけて色を決定
  loadPixels();
  for (let x = 0; x < width; x++) {
    for (let y = 0; y < height; y++) {
      let nearestPointIndex = 0;
      let minDist = dist(x, y, points[0].x, points[0].y);
      for (let i = 1; i < points.length; i++) {
        let d = dist(x, y, points[i].x, points[i].y);
        if (d < minDist) {
          minDist = d;
          nearestPointIndex = i;
        }
      }
      // 色を点のインデックスに基づいて決定
      let col = color((nearestPointIndex * 360 / n) % 360, 80, 80);
      set(x, y, col);
    }
  }
  updatePixels();
  
  // 点を黒色で描画
  fill(0);
  noStroke();
  for (let point of points) {
    ellipse(point.x, point.y, 5, 5);
  }
}


画像
https://editor.p5js.org/setapolo/sketches/wWDLXgcBT

🎡コースティクスと光の包絡線

 「コースティクス」と「カスプ」は、光や曲線に関連する用語であり、それぞれ異なる文脈で使われることが多いですが、以下にその特徴と違いを説明します。

コースティクス (Caustics)
コンピュータグラフィックスや光学の文脈で使われることが多い。
コースティクスは、光が透明な物体や水面などを通過する際に生じる、集中した明るいパターンや模様のことを指します。例えば、水中のプールの底に見られる太陽光の模様や、拡大鏡を使って光を焦点に集めたときの明るい部分などがこれに該当します。
これらの効果は、レンダリングでリアルなイメージを生成するためにしばしばシミュレートされます。
カスプ (Cusp)
数学や曲線の文脈で使われることが多い。
カスプは、曲線や曲面上の点で、その点だけが急激に方向を変える場所を指します。カスプの位置では、曲線の接線が存在しない、または接線が突然変わる点として表現されます。
例としては、心形の曲線(ハートの形)の下部の先端などがカスプとして考えられます。

光学学では、コースティクスまたはコースティクスネットワーク[1]とは、曲面または物体によって反射または屈折された光線の包絡線、またはその包絡線を別の面に投影したものである[2]。 カウスティックは、光線のそれぞれが接線する曲線または面であり、光線の包絡線の境界を集光の曲線として定義している[2]。 これらの形状はしばしばカスプ特異点を持つ。

数学では、カスプ(古いテキストではスピノーデと呼ばれることもあります)とは、曲線上で移動する点が方向を逆にしなければならない点のことです。

// Found this on GLSL sandbox. I really liked it, changed a few things and made it tileable.
// :)
// by David Hoskins.
// Original water turbulence effect by joltz0r


// Redefine below to see the tiling...
//#define SHOW_TILING

#define TAU 6.28318530718
#define MAX_ITER 5

void mainImage( out vec4 fragColor, in vec2 fragCoord ) 
{
	float time = iTime * .5+23.0;
    // uv should be the 0-1 uv of texture...
	vec2 uv = fragCoord.xy / iResolution.xy;
    
#ifdef SHOW_TILING
	vec2 p = mod(uv*TAU*2.0, TAU)-250.0;
#else
    vec2 p = mod(uv*TAU, TAU)-250.0;
#endif
	vec2 i = vec2(p);
	float c = 1.0;
	float inten = .005;

	for (int n = 0; n < MAX_ITER; n++) 
	{
		float t = time * (1.0 - (3.5 / float(n+1)));
		i = p + vec2(cos(t - i.x) + sin(t + i.y), sin(t - i.y) + cos(t + i.x));
		c += 1.0/length(vec2(p.x / (sin(i.x+t)/inten),p.y / (cos(i.y+t)/inten)));
	}
	c /= float(MAX_ITER);
	c = 1.17-pow(c, 1.4);
	vec3 colour = vec3(pow(abs(c), 8.0));
    colour = clamp(colour + vec3(0.0, 0.35, 0.5), 0.0, 1.0);

	#ifdef SHOW_TILING
	// Flash tile borders...
	vec2 pixel = 2.0 / iResolution.xy;
	uv *= 2.0;
	float f = floor(mod(iTime*.5, 2.0)); 	// Flash value.
	vec2 first = step(pixel, uv) * f;		   	// Rule out first screen pixels and flash.
	uv  = step(fract(uv), pixel);				// Add one line of pixels per tile.
	colour = mix(colour, vec3(1.0, 1.0, 0.0), (uv.x + uv.y) * first.x * first.y); // Yellow line
	#endif
    
	fragColor = vec4(colour, 1.0);
}

このシェーダーコードは、GLSL(OpenGL Shading Language)を使用して、動的な水の乱流エフェクトを作成しています。以下にコードの各部分の詳細な説明を行います。

定数とマクロの定義

#define TAU 6.28318530718
#define MAX_ITER 5
  • `TAU` は (2\pi) の値で、円周率の倍数です。

  • `MAX_ITER` はループの最大反復回数を定義しています。

メイン関数

void mainImage( out vec4 fragColor, in vec2 fragCoord ) 
{
    float time = iTime * .5+23.0;
    vec2 uv = fragCoord.xy / iResolution.xy;
  • `mainImage` は、各ピクセルの色を計算するメイン関数です。

  • `fragCoord` は現在のフラグメント(ピクセル)の座標です。

  • `time` はアニメーションのための時間変数で、`iTime` はシステムから提供される経過時間です。

  • `uv` はテクスチャ座標(0から1の範囲)を計算しています。

タイリングのための条件分岐

#ifdef SHOW_TILING
    vec2 p = mod(uv*TAU*2.0, TAU)-250.0;
#else
    vec2 p = mod(uv*TAU, TAU)-250.0;
#endif
  • `SHOW_TILING` が定義されている場合、タイリングを表示するための条件分岐です。

  • `p` はモジュロ演算を使用してテクスチャ座標を TAU の範囲内に収め、視覚的なタイル効果を作成します。

乱流の計算

vec2 i = vec2(p);
float c = 1.0;
float inten = .005;

for (int n = 0; n < MAX_ITER; n++) 
{
    float t = time * (1.0 - (3.5 / float(n+1)));
    i = p + vec2(cos(t - i.x) + sin(t + i.y), sin(t - i.y) + cos(t + i.x));
    c += 1.0/length(vec2(p.x / (sin(i.x+t)/inten),p.y / (cos(i.y+t)/inten)));
}
  • `i` は `p` のコピーで、乱流の計算に使用されます。

  • `c` は乱流の強度を示す変数です。

  • `inten` は乱流の強度の調整に使用される定数です。

  • ループの中で、現在の時間と座標に基づいて `i` を更新し、乱流のパターンを生成します。

色の計算

c /= float(MAX_ITER);
c = 1.17-pow(c, 1.4);
vec3 colour = vec3(pow(abs(c), 8.0));
colour = clamp(colour + vec3(0.0, 0.35, 0.5), 0.0, 1.0);
  • `c` を反復回数で割り、正規化します。

  • `c` にべき乗を適用して強度を調整し、カラー値を計算します。

  • 最終的なカラー値に追加の補正を加え、範囲を 0.0 から 1.0 にクランプします。

タイルの境界線の表示

#ifdef SHOW_TILING
vec2 pixel = 2.0 / iResolution.xy;
uv *= 2.0;
float f = floor(mod(iTime*.5, 2.0)); 
vec2 first = step(pixel, uv) * f;
uv = step(fract(uv), pixel);
colour = mix(colour, vec3(1.0, 1.0, 0.0), (uv.x + uv.y) * first.x * first.y);
#endif
  • `SHOW_TILING` が定義されている場合、タイルの境界線を表示します。

  • `pixel` はピクセルサイズを計算します。

  • `uv` を調整してタイルの境界線を強調します。

  • `colour` を混ぜて境界線を黄色にします。

フラグメントの色の設定

fragColor = vec4(colour, 1.0);
  • 最終的に計算されたカラー値を `fragColor` に設定し、フラグメントの色として出力します。

このシェーダーは、GLSL の基本的な機能を使用して、動的で視覚的に魅力的な水の乱流エフェクトを生成する方法を示しています。タイリングオプションを使うことで、エフェクトを無限に繰り返すことも可能です。