はじめに
先回「3体間の衝突回避と発振」を書いて思ったことがありました。この3体間は3つの物体相互に衝突しないように動くタイプです。そして衝突問題を扱ったのは、2025.08.27のblog「Boids(ボイド)群コントロール」です。これは複数のオブジェクトが相互に衝突を回避する場合でより複雑なタイプでした。こうした衝突の例の前に、衝突すると相手側が移動するが、複数ある相手側同士の衝突はOKとするタイプを述べるべきであったと思いました。そこで今回このタイプの衝突を扱います。最初に示す衝突は、3つのオブジェクトに別のオブジェクトが衝突して動かすタイプです。これは基本的な衝突問題です。そして次に、砂のように非常に多くのオブジェクトに別のオブジェクトが衝突し、砂を動かすタイプです。色を使ったベクトル計算を扱います。これはYouTubeの
「https://www.youtube.com/watch?v=w47xTWMNTFA」を利用しました。このYouTubeは是非見てください。Touchdesignerを使う方が対象ですが、多くの内容を含んでいます。今回のblogではこれと同じことを行います。解説自体も行いたいところですが、それはこのYouTubeを作製された方のを見ていただいて、Youtubeが解説していなかった注意点を述べます。noise TOPの設定やTOPを使った衝突判定部分は、私が長く理解できなかった内容です。その後に、衝突して移動したままではなく、徐々に回復する方法を説明します。この部分がYouTubeの例に対して私が加えたところです。プログラムは全てTouchdesignerによるものです。
衝突問題の基本
まず映像を見ていただきましょう。
映像が左右あるのは、3次元の衝突ですので、1つの画面では奥行に対して衝突しているのか・衝突していないのか、わからないので2つの画面を並べて表示しました。同じモノを180度違った2つのカメラで見ています。
白いボールが動いており、赤、青、黄色のボールは固定位置にあります。これに白いボールが衝突すると、移動するというタイプです。赤、青、黄色のボールがそれぞれ衝突していてもそれはOKとしています。白いボールが他のボールを押していることが分かると思います。赤、青、黄色のボールは周期的にその初期位置を変えています。プログラムの主要部分を次に示します。

白いボールを動かすためにnoise CHOPで(x,y,z)の座標を作っています。衝突対象を赤いボールで説明します。赤いボールの位置もnoise CHOPで作っています。hold CHOPを使って固定しています。周期的なパルスによって、位置が周期的に変わります。holdの次のaddはmath CHOPです。これは衝突して求める移動量を元の座標に加える部分です。移動量を求めてaddするというのが、基本的な構造です。addした座標が赤いボールの座標になります。次に移動量をどうして求めるかを述べます。object CHOPという便利なオペレータがあります。これは2つの入力座標をA, BとするとA-Bベクトルと2つの座標の距離を求めます。勿論math CHOPで計算することもできます。object CHOPで求めた値を、ベクトルと距離に分けて、ベクトルを距離で割って単位ベクトルを求めます。この方向に赤いボールを動かします。動かし方には幾つか考えることができます。ここではspeed CHOPを使う方法を述べます。speedチョップは積算して値を求めるのに便利なCHOPです。衝突に対して少しずつ移動させるために、0.1を掛けています。フィードバックを続けることで0.1ステップで赤いボールを動かします。衝突しているのかどうかを判定する必要があります。距離からexpress CHOPで判定しています。これは2つのボールの距離がそれぞれの半径を足した値以下になれば衝突したとして1を出し、衝突しない場合は0を出力するようにします。この出力でスイッチを切り替えています。衝突していないときはconstant CHOPからの0の値が加わります。衝突している間は単位ベクトルの0.1倍が加わります。この値はspeedによって積算されて行きます。衝突していないときは0をが積算されるので移動量はかわりません。衝突している間は単位ベクトルの0.1倍が積算されていきます。これが移動量ですので、feedback CHOPでフィードバックできるようにして、これをaddしています。speedはholdにパルスが加わるタイミングでリセットして、赤、青、黄色の初期値が変わるタイミングでゼロにします。赤のボールについて説明しましたが、同様なことを青、黄色についても行っています。移動量を求めてそれを衝突している期間フィードバックして加えるという方法は衝突問題の基本です。
複数のオブジェクトへの衝突
砂の移動
次に示す映像はYouTubeが説明しているものです。砂のような細かいオブジェクトにマウスで動かすオブジェクトが衝突すると、砂を移動させます。映像を見てください。
基本的な考えは4つのボールの例と基本同じです。しかし多くの数の衝突を扱うためには、「衝突問題の基本」の場合のように一つ一つ計算していくわけにはいきません。このためTOPを使ったベクトル計算を使います。
まずは全体構成を見ていただきましょう。

上の部分が、衝突に対する移動量を求め、砂の位置を計算する部分です。中間にある、復元のためのブロックは今は使いません。下のブロックは絵を描くためのブロックになります。砂の絵の描き方は、砂の位置を決め(右側のposition 砂の位置)、砂のサイズをnoise TOPでランダムにインスタンシング、また色もnoise TOPでランダムにインスタンシングすることで作製します。
noise TOPの注意点
noise TOPの設定について説明致します。砂の数ですがこれをnoiseのresolutionで表します。今回は32768(2^15)個ですが、これを設定するのが、resolution (32768, 1)です。32768行1列のベクトルです。それぞれにnoiseの値が入ります。それがPixel Formatできまります。このに入れるのは、大きくはfixedとfloatがあります。fixedは整数、floatは浮動小数点です。負の値を扱う場合はfloatを使います。floatでも16bit, 32bitがありますが、およそ-1~1の範囲でどれだけ細かくするのかで16 bitか32bitを決めます。今回のように大きな数を扱う場合は32bitを使います。16bitにするか32bitにするかは迷います。全体を作製してから変えてみて決めるのがいいと思います。今回は32bitを使っていますが、これは、後で述べる復元をともなう場合に、16bitでは復元した際に僅かにずれがあったためです。32-bitでも、32-bit float(RG), 32-bit float(RGBA)があります。RGはRとGに値が入る2次元ベクトルですし、RGBAは4次元ベクトルになります。この他にもグレースケースを扱う32-bit float(Mono)があります。これは1次元のスカラーになります。resolution (32768, 1)で32-bit float(RGBA)を選択すると、32768個の1つ一つが4つのデータを持つベクトルになります。このように選択の仕方でデータ量には膨大な違いがでます。
上の画像の「position1初期の砂の位置」では、32-bit float(RG)を選択しました。これは砂の位置を表しており、平面ですから、x, yの2次元ベクトルだからです。x, yがR,Gに対応します。「noise 砂のサイズ」では、32-bit float(Mono)を選んでいます。これはサイズはスカラーでいいからです。「noise砂の色」では32-bit float(RGBA)を選択しました。これは色はRGBの3次元が必要だからです。このように何のデータなのかでPixel Formatを決めることになります。それからもう一つnoise TOPの注意は、amplitudeとoffsetです。今回のマウスの位置はx, yともに-0.5~0.5で映像画面の全体にしています。これにともなって、「position1初期の砂の位置」も-0.5~0.5にする必要があります。この設定はamplitude 0.5 offset 0 となります。これで0を中心に-0.5~0.5になります。「noise砂のサイズ」ですが、通常どうり0~1にしています。この設定はamplitude 0.5 offset 0.5になります。amplitudeは振幅でoffsetは基準をどこに持っていくかです。offsetで基準を0.5にもっていき、振幅が0.5ですので、0~1の範囲となります。「noise砂の色」はRGBのそれぞれの値は0~1なので、これに合わせます、つまりamplitude 0.5, offset 0.5です。私はnoise TOPの後にlimit TOPを入れています。これは必ず入れる必要はありません。noise TOPでは、-0.5~0.5, -1~1,0~1に設定することが多いですが、HarmonicsやHarmonic Gain, exponentによって、僅かに範囲を超えた値を出すことが生じます。これを気にするなら、limit TOPを入れてClampでMinimum Value, Maximum Valueに値を入れて制限します。今回の場合はlimitを入れても入れなくても違いはありませんでした。このようにnoise TOPの設定はどんなデータなのかを考慮して決める必要があり、注意が必要です。
砂の位置計算の構成
図と説明が離れ過ぎてしまうので、先の図と同じですが再度示します。

衝突によって砂の移動量を求め、それをフィードバックして移動した跡の砂の位置を求める部分について説明します。移動量そのものの計算は次の節でのべます。図の上側に当たります。「position1初期の砂の位置」があり、「砂の移動量」を求め、これを「初期の砂の位置」に足したものが、新しい砂の位置(「position砂の位置」)になります。考え方は先に上げた「衝突問題の基本」と同じです。しかし、フィードバックの扱いがCHOPとTOPでは少し違います。またspeed CHOPに対応する積算機能がTOPにはありません。この部分の扱いに注意が必要です。feedback CHOPの説明を参考にするために、4つのボールを扱った図を見てください。speed CHOPで積算した値をfeedback CHOPを介してadd(math CHOP)に加えています。このfeedback CHOPの機能は一つ前の状態を保持してループさせるオペレータです。一つ前の値がaddに入ります。これが無いと無限ループになってしまいます。一方feedback TOPはインレットに接続する入力の他にTarget TOPを指定します。初期値がインレットでfeedbackではTarget TOPに指定したTOPに入れ替わります。この入れ替わるというのがCHOPと違っています。ですから、図の「feedback2砂の移動量のフィードバック」ではfeedback TOPの入力はconstant TOPであり、これはresorutionが(32768, 1)でcolorを(0,0,0)にして全て0が入っています。32-bit float(RG)を指定していますので、RとG即ちx,yにゼロが入っています。これがTarget TOPで指定した「砂の移動量」に置き換わります。移動量が常に置き換わっています。もう一つfeedabackがあります。「feedback1砂の位置のフィードバック」です。「初期の砂の位置」がインレットに入っています。そしてTarget TOPは「position 砂の位置」です。これによって新しいpsoitionがセットされます。これに、「砂の移動量のフィードバック」をadd TOPで足しています。これが新しいpositionで、これがまたセットされ次の移動量が加算されます。
移動量の計算
次に移動量の計算と書いた内部のプログラムを見てみましょう。

「マウスの位置」と書いた部分はconstant TOPによって作製しています。砂の位置との間でベクトル計算をするので、これに合わせます。ですのでconstant TOPのResolutionは(32768, 1)にしています。Pixcel Formatも32-bit float(RG)に合わせます。panel CHOPから得るマウスの位置は0~1の値になるので、-0.5~0.5になるように、x,yの値を変換します(-0.5引けばよい)、それをconstant TOPのcolorr, colorgに使います。全てのベクトルにマウスの座標を入れるということです。これと砂の位置を引き算します。これがマウスの位置と全ての砂の位置との差を求めたことになります。math TOPを使って距離を求めます。引き算したベクトルを距離で割って、単位ベクトルを求めます。ここで注意するのはマウス位置から砂の位置を引いて作製したベクトルですので、これはマウス位置の方向へ動くベクトルになっています。動かすのは砂側ですので、後でマイナスを掛けて方向を反転する必要があります。一方で砂の半径とマウスの半径を足し算しています。これは砂とマウスが接した時の距離です。そして距離同士を引き算します。衝突した場合はマウスの位置と砂の位置の差はマウスの半径と砂の半径を足したものより小さくなるので、この引き算は衝突していると負になります。この引き算にどれぐらいの距離を一回の計算で動かすかをかけで移動量を求めます。例えば0.1を掛けるとかです。そしてこれを単位ベクトルと掛け算します。これにより移動量がベクトルになります。この時衝突していたら負になっていることを思い出していただくと、移動量は単位ベクトルと逆の方向になり、これで砂が移動する方向になっています。ここで重要なのは、掛け算して得た移動量をベクトル化した値は、衝突していない位置のベクトル計算も入っているということです。砂を移動させたいのは、マウスと衝突した砂だけですので、衝突した砂は移動量を出力し、衝突していない砂は移動量をゼロにする必要があります。これが「衝突判定」部分です。この求め方はかなりクレバーです。まず引き算した(衝突していると負になる)出力をmath TOPでIntegerをFloorにします。Floorというのは、実数に対して自身以下の最大の整数を出力することです。例えば0.3は0になります。また-0.3は-1になります。ここで、引き算(衝突していると負になる)のところを見てください。マウスの位置と砂の位置は-0.5~0.5なので、最大の離れた場合はマウス(-0.5, 0.5)で砂が(0.5,-0.5)の位置のように対角に来る場合です。この時長さはルート2の1.414になります。これからマウスのと砂の半径を足した値を引くので、1.幾らというのが最大です。たいていは1より小さくなります。また衝突した場合は負になります、これは勿論-1より大きい値です。ですので、これのFloorをとると、衝突した値は-0.幾らですので、-1になります。そして衝突していない場合の大半は1より小さいですので、0になります。そして僅かながら対角にくるような関係の場合は1を超えるので、1になります。この後-1を掛けます。すると、衝突した場合は-1でしたので1になります。大多数の衝突していない場合は0のままです。そして少ないながら対角の関係となった場合は-1になります。その後limitでclampの最小を0最大を1に設定すると、衝突した場合は1となり、衝突しなかった大多数は0のまま、そして対角にあった関係は-1でしたので0にClampされます。この結果衝突した場合は1、衝突していない場合は0となります。これを移動量をベクトル化したものと掛け算すると、衝突したものだけがその値を残し、衝突していない場合はゼロになります。これがフィードバックされる量になります。TOPはCHOPと違ってexpression CHOPやobject CHOP, speed CHOPに対応するオペレータがありません。そこで工夫が必要となります。今回の衝突反転の部分はその典型だと思います。なかなか思いつきません。
複数のオブジェクトへの衝突 復元がある場合
次に全体構成の図の中で、「復元のためのブロック、復元しない場合は使わない」と書いた部分を使う場合を紹介します。この部分を加えたことが私の工夫です。その場合の映像を見ていただきましょう。
マウスを動かすと衝突した部分がよけていきますが、次第に元に戻っていくことがわかります。こうすることで無限にマウスを動かしつずけても、いつもよける砂がある状態が作れます。この機能を入れた場合を説明致します。

方針は「初期の砂の位置」から現在の「砂の位置」を引くことで、マウスによって移動した部分の位置だけが検出できます。これを徐々に描いでいくと徐々に復元されていきます。「position1初期の砂の位置」から「position砂の位置」を引き算します。feedback3は先に説明したと同様に、全てゼロの初期値を入れておき、引き算した値をTarget Topに設定して代入します。次の掛け算は、どれだけのスピードで回復させるかを決めます。少しずつ復元させるために0.01を掛けています。feedaback1とfeedaback2の経路とを加えたaddは、マウスによって砂をよけた位置です。これと復元する位置を足します。これが実際の「position砂の位置」になります。
元の砂から復元がある砂を引いた場合
最後に元の砂から復元が在る場合の砂を引いた場合を示しておきます。復元のある場合の反転した映像が得られます。
終わりに
今回はnoise TOPの使い方feedback TOPの使い方、TOPを使った衝突判定の方法など、設定に細かな注意が必要でした。次第に物理演算が簡単に使えるようになる方向ではありますが、今回のように自分でプログラムしていかないといけない案件は非常に多いと思います。blog「Boids(ボイド)群コントロール」の前にこのblogを書いておけばよかったのではないかと思っております。Boidsの場合は、オブジェクト同士の衝突(今回でいうなら砂同士の衝突)も入るのでマトリクスの計算になったのです。衝突して移動させる原理は全て同じですが、YouTubeで紹介してくださっている方々がいなと、一人ではとてもプログラムできるとは思えません。感謝です。

コメント