雪の結晶シミュレーション(2次元DLA)
【アルゴリズム】水分子(水色)の運動をランダムウォークとみなしランダムな位置からスタートさせます。 中心部分にある核(赤)に接触すると、水分子は吸着して固まります。 固まった後は、核と同様に水分子を吸着していく。 その結果、数珠繋ぎに成長していくことになります。
本シミュレーションは拡散律則凝集と呼ばれ、結晶成長の単純なモデルとして考えられています。
参考:DLAによる樹木状クラスタのシミュレーション、DLAによる樹木状クラスタのフラクタル次元
プログラムソース(HTML5, JavaScript)
////////////////////////////////////////////// // 雪の結晶シミュレーション ////////////////////////////////////////////// //DLAクラス var DLA2D = function () { //粒子数 this.N = 20000; //初期位置の最小値 this.L0 = 100; //初期位置の最大値 this.Lmax = 500; //内部プロパティ this.perticles = []; //粒子情報が格納される配列 this.fixPerticleIndex = []; //吸着された粒子インデックスが格納される配列 this.maxLengthSq = 1; //原点からの最大距離の2乗 //形状オブジェクトの宣言と生成 this.geometry = new THREE.PlaneGeometry(1, 1); //材質オブジェクトの宣言と生成 this.material = new THREE.MeshBasicMaterial( {color:0x0000FF} ); //材質オブジェクトの宣言と生成 this.fixedMaterial = new THREE.MeshBasicMaterial( {color:0xFF0000} ); //初期化 this.init = function() { //分布密度関数の係数 var A = 2 / ( this.L0*this.L0 + this.Lmax*this.Lmax ); for( var i = 0; i <= 100; i++ ) this.fixPerticleIndex[ i ] = [ ]; for( var n = 0; n < this.N; n++ ){ //積分分布密度 var F = Math.random(); //積分分布密度から得られる距離 var L = Math.sqrt( this.L0*this.L0 + 2 * F / A ); //偏角 var theta = 2 * Math.PI * Math.random(); var x = L * Math.cos( theta ) ; var y = L * Math.sin( theta ) ; x = Math.round(x); y = Math.round(y); //平面オブジェクトの生成 this.perticles[n] = new THREE.Mesh( this.geometry, this.material ); //初期値を指定 this.perticles[n].position.set( x, y, 0 ); //平面オブジェクトのシーンへの追加 scene.add( this.perticles[n] ); //固定フラグ this.perticles[n].fixed = false; } //核を設定 this.perticles[0].position.set( 0, 0, 0 ); this.perticles[0].fixed = true; this.fixPerticleIndex[ 0 ].push( 0 ); //描画色を変更 this.perticles[0].material = this.fixedMaterial;; this.perticles[0].needsUpdate = true; } //時間発展 this.timeEvolution = function() { var x, y, r, lengthSq; return function() { for( var i = 0; i < this.N; i++ ){ if( this.perticles[i].fixed ) continue; x = this.perticles[i].position.x; y = this.perticles[i].position.y; r = Math.random(); if( r < 0.25 ) x += 1.0; else if( r < 0.50 ) x -= 1.0; else if( r< 0.75 ) y += 1.0; else y -= 1.0; this.perticles[i].position.x = x; this.perticles[i].position.y = y; lengthSq = x*x + y*y; //吸着テスト if( lengthSq <= this.maxLengthSq ) this.checkCollision( i , lengthSq); } } }(); //吸着テスト this.checkCollision = function ( n, lengthSq ){ var v = new THREE.Vector3(); var lengthIndex, M, index; var i, j; return function( n, lengthSq ){ //距離配列要素番号 lengthIndex = Math.floor( Math.sqrt( lengthSq ) ); for( j = lengthIndex - 1; j <= lengthIndex + 1; j++ ){ if( j < 0 ) continue; M = this.fixPerticleIndex[ j ].length; for( i = 0; i < M ; i++ ){ index = this.fixPerticleIndex[ j ][ i ]; lengthSq = v.copy( this.perticles[ n ].position ).sub( this.perticles[ index ].position ).lengthSq(); if( lengthSq <= 1.0 ) { this.perticles[n].fixed = true; this.fixPerticleIndex[ lengthIndex ].push( n ); //描画色を変更 this.perticles[n].material = this.fixedMaterial; this.perticles[n].needsUpdate = true; //最大半径の更新 if( this.perticles[ n ].position.lengthSq() >= this.maxLengthSq ) { this.maxLengthSq = Math.pow( this.perticles[ n ].position.length() + 1, 2); //console.log( this.maxLengthSq ); } return; } } } } }(); }