HOME > natural science Laboratory > コンピュータ・シミュレーション講座 > 仮想物理実験室

HTML5による物理シミュレーション環境の構築
点電荷による電気力線シミュレータ

文責:遠藤 理平 (2012年3月11日) カテゴリ:WebGL(39)仮想物理実験室(247)

HTML5の新要素 canvas 要素に WebGL(Three.js)+Javascript を利用して構築する物理シミュレータの第2弾として、「点電荷による電気力線の描画シミュレータ」を公開します。 4つの点電荷の各「電荷と座標」を任意に指定することができます。その際の電気力線を描画します。下記の図はシミュレータのデモ画面です。

以下の図はデモ画面です実行ページこちら


計算アルゴリズム

そもそも電気力線とは、電気力を視覚的に表現するために導入された仮想的な線のことです。 電気力線の一般的な特徴は次のとおりです。

(1)正の電荷から負の電荷に向けて電場の向きを沿う。
(2)途中で途切れたり交差したりせず、正の点電荷から無限遠へ、逆に負の電荷にむけて無限遠から電気力線が到達する。
(3)電場が強いほど、電気力線の密度が高い。

電気力線は電場の空間依存性から計算することができるわけですが、 電気力線を\mathbf{l}(s)、電場を\mathbf{E}(\mathbf{r})と表した場合、

(1)

の関係があります。sは1次元的な電気力線の位置を表す媒介変数となります。 式(1)からコンピュータで計算するためのアルゴリズムは、

(2)

となります。式(2)を図示したのが下図です。

式(1)→(2)の導出はオイラー法として知られる差分法となります。 空間上の任意の点\mathbf{l}(s)とその位置における電場が得られると、電気力線の次の位置を決めることができます。点電荷でつくられる電場は、各点電荷によるクーロンの法則の重ねあわせで厳密に計算することができます。

(3)

つまり、厳密に得られる電場の空間分布から式(2)を用いて、近似的に電気力線を計算することができるわけです。ただし、 本稿で作成した電気力線シミュレータは、厳密な意味で電気力線ではありません。 その理由は、

(1)電荷の大きさと、電気力線の本数を関連付けしていない
(2)正の点電荷からだけではなく、負の点電荷からも電気力線を延ばしている。
(3)電場が「0」になる所で交わることがある。

ためです。 上記(1)は単純に描画結果を単純化するためです。(2)は負の点電荷に無限遠から流入する電気力線を表現するためです。 もちろん負の点電荷の場合、式(2)の電場の符号は「-」になります。 (3)についてですが、点電荷が生み出す電場が打ち消しあい「0」となる点が存在します(例えば、正の点電荷が2つだけ並んでいる場合のちょうど中間地点など)。電気力線を描画する際の初期点によってはその電場「0」の地点に向かうものもあり、その点が電気力線の終点となります。その点に向かう線が複数存在した場合、終点となるその点で交わることになります。 つまり、電気力線の描画ルール「交わらない、途切れない」に違反することになります。 しかしながら、もともと電気力線は電場の流れを可視化するために導入された手法なので、 ルール違反があっても物理的には間違いはありません。


スクリーンショット


Javascript プログラムソース

  var PI = Math.PI;
  var e = Math.E;
  var c = 2.99792458E+8;
  var mu0 = 4.0*PI*1.0E-7;
  var epsilon0 = 1.0/(4.0*PI*c*c)*1.0E+7;
  var e0 = 1.60217733 * 1.0E-19;
  
  var Lx=100, Ly=100, Lz=0;
  var dz = 1.0E-6; //空間刻み幅
  
  //////////////////////////////////////
  //点電荷の設定
  /////////////////////////////////////
  var ElectricCharge = function(charge, x, y, z){
    this.charge = charge;
    this.x = x;
    this.y = y;
    this.z = z;
    this.flag = true;
  }
  var N_EC = 4; //点電荷の個数
  var ECs = new Array();
  ECs[0] = new ElectricCharge (1.0*e0, -10*dz, -10*dz, 0 );
  ECs[1] = new ElectricCharge (-1.0*e0,  10*dz, -10*dz, 0 );
  ECs[2] = new ElectricCharge (-1.0*e0, -10*dz,  10*dz, 0 );
  ECs[3] = new ElectricCharge (1.0*e0,  10*dz, 10*dz, 0 );  
  var N_EF   = 20000; //電気力線のステップ数
  var N_Skip = 100;   //データ出力間引き数
  var N_D    = 100;   //電気力線計算時の単位長さあたりの刻み幅  
  var N_EF_V = 6;     //緯度方向の分割数
  var N_EF_H = 18;   //経度方向の分割数
  var n_step = N_EF/N_D; //電気力線1本値のデータ数
  var n_line = N_EF_V*N_EF_H;//1電荷あたりの電気力線の本数
  /////////////////////////////////////////////////////////////////////////////
  //計算結果を記録用3次元配列を定義
  var Lines;
  function initLine(){
    Lines = new Array(N_EC); 
    for (var i = 0; i < N_EC; i++)
      Lines[i] = new Array(n_line);
    for (var i = 0; i < N_EC; i++)
      for (var j = 0; j < n_line; j++)
        Lines[i][j] = new Array();
  }
  var $id = function(id) { return document.getElementById(id); }
  function init(){
    initLine();
    for(var i=0; i<N_EC; i++){
      $id("e"+i+"c").value = ECs[i].charge/e0;
      $id("e"+i+"x").value = ECs[i].x/dz;
      $id("e"+i+"y").value = ECs[i].y/dz;
      $id("e"+i+"z").value = ECs[i].z/dz;    
    }
  }
  function restart(){
    initLine();
    for(var i=0; i<N_EC*n_line; i++){
      scene.remove( lines[i] );
    }
    for(var i=0; i<N_EC; i++){
      scene.remove( balls[i] );
      ECs[i] = { charge: parseFloat($id("e"+i+"c").value)*e0, 
          x:parseFloat($id("e"+i+"x").value)*dz, 
          y:parseFloat($id("e"+i+"y").value)*dz, 
          z:parseFloat($id("e"+i+"z").value)*dz,
          flag: $id("e"+i+"i").checked }
    }
    initObject();
  }
  function culStart(){  
    ////////////////////////////////////////////////////////////////////////////
    for(var q=0; q<N_EC; q++){//各点電荷
      if(!ECs[q].flag) continue;
      var ll=0;
      for(var n_theta=0; n_theta<N_EF_V; n_theta++){
        var theta = PI/N_EF_V * n_theta;
        var n_EF_H = parseInt(N_EF_H * Math.abs(Math.sin(theta)));//経度方向の分割数を緯度によって変える
        if(n_EF_H%2==1) n_EF_H++;  //奇数→偶数
        if(n_EF_H == 0) n_EF_H =1; //0だとまずいので
        for(var n_phi=0; n_phi<n_EF_H; n_phi++){
          ll++;      
          var phi = 2.0*PI/n_EF_H * n_phi ;
          var ELine_x, ELine_y, ELine_z;
          ELine_x = ECs[q].x + dz * Math.sin(theta) * Math.cos(phi);
          ELine_y = ECs[q].y + dz * Math.sin(theta) * Math.sin(phi);
          ELine_z = ECs[q].z + dz * Math.cos(theta);
          Lines[q][ll-1][0] = {x:ELine_x/dz, y:ELine_y/dz, z:ELine_z/dz};        
          OUT :
          for(var j=1; j<=N_EF; j++){
            if(j%N_Skip==0) Lines[q][ll-1][j/N_Skip] = {x:ELine_x/dz, y:ELine_y/dz, z:ELine_z/dz};    
            var Ex =0, Ey = 0, Ez = 0, E_abs;
            for(var i=0; i<N_EC; i++){
              if(!ECs[i].flag) continue;
              var R = Math.sqrt(Math.pow(ELine_x-ECs[i].x,2) + Math.pow(ELine_y-ECs[i].y,2) + Math.pow(ELine_z-ECs[i].z,2)); 
              Ex += ECs[i].charge/(4.0*PI*e0) * (ELine_x-ECs[i].x)/Math.pow(R,3);
              Ey += ECs[i].charge/(4.0*PI*e0) * (ELine_y-ECs[i].y)/Math.pow(R,3);
              Ez += ECs[i].charge/(4.0*PI*e0) * (ELine_z-ECs[i].z)/Math.pow(R,3);
            }
            E_abs = Math.sqrt(Math.pow(Ex,2)+Math.pow(Ey,2)+Math.pow(Ez,2));
            if(ECs[q].charge > 0){
              ELine_x += Ex/E_abs *dz/N_D;
              ELine_y += Ey/E_abs *dz/N_D;
              ELine_z += Ez/E_abs *dz/N_D;
            }else{
              ELine_x -= Ex/E_abs *dz/100 ;
              ELine_y -= Ey/E_abs *dz/100 ;
              ELine_z -= Ez/E_abs *dz/100 ;
            }
            if(E_abs*e0<1E-15) break;
            for(var i=0; i<N_EC; i++) 
              if(Math.sqrt(Math.pow(ELine_x-ECs[i].x,2)+Math.pow(ELine_y-ECs[i].y,2)+Math.pow(ELine_z-ECs[i].z,2)) < dz ) break OUT;//点電荷に到達したら終了
          }
        }
      }
    }
  }
  var width, height;
  var renderer;
  function initThree() {
    width = document.getElementById('canvas-frame').clientWidth;
    height = document.getElementById('canvas-frame').clientHeight;  
    try {renderer = new THREE.WebGLRenderer({antialias: true});} catch (e) {}  
    if(!renderer) document.getElementById("errer").innerHTML = '<p style="text-align:center;font-size:small; color:red">お使いの環境ではWebGLはご利用いただけません。<br />WebGLに対応していない方のためにGIFファイルを以下に用意しました。<br /><br /><img src="http://www.natural-science.or.jp/images/tutorial6.gif" alt="WEBGLデモ" /></p>';
    renderer.setSize(width, height);
    document.getElementById('canvas-frame').appendChild(renderer.domElement);
    renderer.setClearColorHex(0x000000, 1.0);
  }
  
  var camera;
  function initCamera() {  
    camera = new THREE.PerspectiveCamera( 45 , width / height , 1 , 10000 );
    camera.up.x = 0;
    camera.up.y = 0;
    camera.up.z = 1;

  }
  var scene;
  function initScene() {    
    scene = new THREE.Scene();
  }
  var light, light2;
  function initLight() {
    light = new THREE.DirectionalLight(0xFFFFFF, 1.0, 0);
    light.position.set( 100, 100, 100 );
    scene.add(light);

    light2 = new THREE.AmbientLight(0x777777);
    scene.add(light2);
  }

  var lines = new Array();
  var balls = new Array();
  function initObject(){   
    culStart();  
    var ll=0;
    for (var i = 0; i < N_EC; i++) {
      if(!ECs[i].flag) continue;
      for (var j = 0; j < n_line; j++) {
        var geometry = new THREE.Geometry();
        for (var k = 0; k < n_step; k++) {
          if(Lines[i][j][k] != undefined && Lines[i][j][k] != undefined && Lines[i][j][k] != undefined)
              geometry.vertices.push( new THREE.Vertex( new THREE.Vector3( Lines[i][j][k].x , Lines[i][j][k].y, Lines[i][j][k].z) ) );
          else break;
        }
        lines[ll] = new THREE.Line( geometry, new THREE.LineBasicMaterial( { color: "0xFFFFFF", opacity:0.4} ) );
        scene.add( lines[ll] );//シーンに追加
        ll++;
      }
      //点電荷の描画
      var chargeColor;
      if(ECs[i].charge < 0) chargeColor = "0x0000FF";
      else chargeColor = "0xFF0000";
      balls[i] = new THREE.Mesh(
        new THREE.SphereGeometry(ECs[i].charge/e0,20,20),
        new THREE.MeshLambertMaterial({color: chargeColor, ambient:chargeColor})
      );
      balls[i].position.set(ECs[i].x/dz, ECs[i].y/dz, ECs[i].z/dz);
      scene.add(balls[i]);
    }
  }

  function initEvent(){
    window.addEventListener("mousedown", onMousedown, false);
    window.addEventListener("mouseup", onMouseup, false);
    window.addEventListener("mousemove",  onMousemove, false);
    window.addEventListener("mousewheel", onMousewheel, false);
    window.addEventListener("dragover", onCancel, false);
    window.addEventListener("dragenter", onCancel, false);
    window.addEventListener("drop", onDropFile, false);
    $id("restartButton").addEventListener("click", restart, false);
    $id("outputButton").addEventListener("click", f_write, false);
  }

  function loop() {
    camera.position.set(cameraR*Math.sin(cameraTheta)*Math.cos(cameraPhi),
              cameraR*Math.sin(cameraTheta)*Math.sin(cameraPhi),
              cameraR*Math.cos(cameraTheta));
    renderer.clear();
    camera.lookAt( {x:0, y:0, z:0 } );    
    renderer.render(scene, camera);
    window.requestAnimationFrame(loop);
  }
  function threeStart() {
    initEvent();
    initThree();
    initCamera();
    initScene();    
    initLight();  
    initObject();
    loop();
  }

////マウスイベント////////////////////////////////////////////////////
  var down = false;
  var sy = 0, sz = 0;
  var cameraR = 100, cameraTheta =PI/4, cameraPhi =PI/2;
  function onMousedown (ev){  //マウスダウン時イベント
    if (ev.target == renderer.domElement) { 
      down = true;
      sy = ev.clientX; sz = ev.clientY;
    }
  };
  function onMouseup (){       //マウスアップ時イベント
    down = false; 
  };
  function onMousemove (ev) {  //マウスムーブ時イベント
    var speed = 0.01;
    if (down) {
      if (ev.target == renderer.domElement) { 
        var dy = -(ev.clientX - sy);
        var dz = -(ev.clientY - sz);
        cameraPhi += dy*speed;
        cameraTheta += dz*speed;
        sy -= dy;
        sz -= dz;
      }
    }
  }
  function onMousewheel(ev){//マウスホイール時イベント
    var speed = 0.1;    
    cameraR += ev.wheelDelta * speed ; 
  }
  var onDropFile = function(ev){         // ファイルドロップ時イベント
    ev.preventDefault();
    var file = ev.dataTransfer.files[0]; // File オブジェクトを取得
    readFile(file);                     // ファイル読み込み
  };
  var onCancel = function(ev){            // デフォル処理をキャンセル
    if(ev.preventDefault) { ev.preventDefault(); }
    return false;
  };
  function f_write(){
    var  str = "";

    for (var i = 0; i < N_EC; i++) {
      if(!ECs[i].flag) continue;
      for (var j = 0; j < n_line; j++) {
        var geometry = new THREE.Geometry();
        for (var k = 0; k < n_step; k++) {
          if(Lines[i][j][k] != undefined && Lines[i][j][k] != undefined && Lines[i][j][k] != undefined)
            str  +=  Lines[i][j][k].x  + " " + Lines[i][j][k].y  + " " + Lines[i][j][k].z  + "\n";
          else break;
        }
        str  += "\n";
      }
    }
    var blobBuilder;
    if ("MozBlobBuilder" in window) {
      blobBuilder = new MozBlobBuilder();
    } else if ("WebKitBlobBuilder" in window) {
      blobBuilder = new WebKitBlobBuilder();
    }
    blobBuilder.append(str);
    //var disp = document.getElementById("download");
    if (window.URL) {
      window.open(window.URL.createObjectURL(blobBuilder.getBlob()) , "New Window", "");
    } else if (window.webkitURL) {
      window.open(window.webkitURL.createObjectURL(blobBuilder.getBlob()), "New Window", "");          
    }
    //disp.innerHTML = 'ファイルがダウンロードされました (t='+ t +')';
  }  
///////////////////////////////////////////////////////////////////////
window.onload = function(){
  init();
  threeStart();
}

C言語プログラムソース

任意の点電荷分布に対する電気力線を計算するC言語のプログラムも合わせて公開しておきます。
ダウンロード(4kB)

参考ページ

HTML5による物理シミュレーション環境の構築 ~2重振子シミュレータ~
HTML5による物理シミュレーション環境の構築 ~WebGLライブラリThree.js 入門(1/3)~
HTML5による物理シミュレーション環境の構築 ~WebGLライブラリThree.js 入門(2/3)~
HTML5による物理シミュレーション環境の構築 ~WebGLライブラリThree.js 入門(3/3)~

ミスの指摘、質問やコメントなどがありましたら、お気軽にこちらまで、お願いいたします。



▲このページのトップNPO法人 natural science トップ

関連記事

WebGL

仮想物理実験室







▲このページのトップNPO法人 natural science トップ