トップへ(mam-mam.net/)

ブラウザで3Dドローン操縦を体験!【Three.jsで動かすリアルな飛行シミュレーション】

English

Three.jsで3Dドローン操縦疑似体験(Three.js[r145])

Webブラウザ上でドローンの操縦が疑似体験できる3Dサイトを作ってみました。
このサンプルではオービットコントロール(OrbitControls)を使っているのですが、カメラがドローンを追従するようにオービットコントロールのtargetプロパティを、ドローン本体のpositionプロパティに設定しています。
下部にあるボタン8個で操縦できます。

Up
TL TR
Dn
MF
ML MR
MB

ドローン本体の後ろ側に「赤色」、前側に「青色」のマークがあります。
[Up]:上昇、[Dn]:下降、[TL]:左旋回、[TR]:右旋回
[MF]:前進、[MB]:後退、[ML]:左平行移動、[MR]:右平行移動

3Dドローン操縦疑似体験のソースコード

<script>
  var controls;
  var scene,camera,renderer;
  
  var pos={"c":"", "x":0, "y":0, "z":0, "vx":0, "vy":0, "vz":0, "turn":0};
  var main = function () {
    //描画先canvasを取得して幅と高さ取得
    let can=document.getElementById('can');
    let w = 600;//幅
    let h = 600;//高さ

    //■シーン
    scene = new THREE.Scene();
    scene.background = new THREE.Color(0xccddff);

    //■カメラ
    //THREE.PerspectiveCamera( 画角(度), アスペクト比, カメラに写る最短距離, カメラに写る最長距離 );
    camera = new THREE.PerspectiveCamera(25, w/h, 1, 10000);
    camera.position.set(0, 100, 150);//カメラの位置を設定
    //camera.rotation.set(0,8,0);//カメラの角度を(0度,0度,0度)にする
    camera.lookAt(new THREE.Vector3(0, 0, 0));//カメラの向きを(0,0,0)座標にする
    scene.add(camera);//カメラをシーンに追加

    //■レンダラー
    renderer = new THREE.WebGLRenderer({canvas:can, antialias: true});//アンチエイリアス有効
    renderer.setSize(w, h);
    //シャドー(影)マップを有効にする
    renderer.shadowMap.enabled = true;
    //renderer.setClearColor(0x000000, 1);//背景色を設定

    //■空
    let sky=new THREE.Sky();
    sky.scale.setScalar(50000);
    sky.material.uniforms.turbidity.value=0.8;// 大気の透明度
    sky.material.uniforms.rayleigh.value=0.3;// 入射する光子の数
    sky.material.uniforms.mieCoefficient.value=0.005;// 太陽光の散乱度 三重係数
    sky.material.uniforms.mieDirectionalG.value=0.8; // 太陽光の散乱度 三重指向性G
    sky.material.uniforms.sunPosition.value.x= 10000;//太陽の位置
    sky.material.uniforms.sunPosition.value.y= 30000;//太陽の位置
    sky.material.uniforms.sunPosition.value.z=-40000;//太陽の位置
    scene.add(sky);

    //■光源------------------------------------------------
    //環境光(すべての物体にすべての方向から与える光)光の当たらない部分でも真っ黒にならない
    var AmbientLight=new THREE.AmbientLight(0xffffff,0.3);
    scene.add( AmbientLight );//環境光をシーンに追加

    //スポットライト(色,強さ,距離,角度(angle),半影[0-1],減衰[1,1])
    const spotLight=new THREE.SpotLight(0xffffff, 3, 4000,Math.PI/40, 0.8, 0.8);
    spotLight.position.set(0,3000,0);
    spotLight.target.position.set(0,0,0);
    spotLight.castShadow=true;//影を表示可能に
    spotLight.shadow.mapSize.width = 4096;  //影をきれいにする 4096
    spotLight.shadow.mapSize.height = 4096; //影をきれいにする 8192
    spotLight.shadow.radius=1;  //影をぼかす
    scene.add(spotLight);
    scene.add(spotLight.target);
    
    //点光源(かなり重たくなる)     色      強度 距離 減衰
    //light = new THREE.PointLight( 0xffffff, 0.1, 4, 2 );
    //scene.add( light );


    //■マテリアル
    //透明
    let mt1=new THREE.MeshLambertMaterial({
      color:0xffffff,side:THREE.FrontSide,transparent:true,opacity:0.3
    });
    //青
    let mt2=new THREE.MeshLambertMaterial({
      color:0x0022cc,side:THREE.FrontSide
    });
    //赤
    let mt3=new THREE.MeshBasicMaterial({
      color:0xff3333, 
      side:THREE.FrontSide
    });
    //うす緑
    let mt4=new THREE.MeshLambertMaterial({
      color:0x66ff66,side:THREE.FrontSide,
    });
    //青
    let mt5=new THREE.MeshBasicMaterial({
      color:0x6666ff, 
      side:THREE.FrontSide
    });

    //床
    let floorTexture=new THREE.TextureLoader().load('./three_r145/img/flag_g.png');
    floorTexture.wrapS=THREE.RepeatWrapping;
    floorTexture.wrapT=THREE.RepeatWrapping;
    //テクスチャの繰り返し回数を指定する
    floorTexture.repeat.set(8,8);
    let mt6=new THREE.MeshLambertMaterial({
      color:0xffffff,side:THREE.FrontSide,
      map:floorTexture,
    });
    //白色
    let mt7=new THREE.MeshLambertMaterial({
      color:0xffffff,side:THREE.FrontSide,transparent:false,opacity:1.0
    });
    //灰色
    let mt8=new THREE.MeshLambertMaterial({
      color:0xaaaaaa,side:THREE.FrontSide
    });
    //濃い灰色
    let mt9=new THREE.MeshLambertMaterial({
      color:0x666666,side:THREE.FrontSide
    });


    //直方体ジオメトリ
    let bg=new THREE.BoxGeometry(1,1,1);
    //球体ジオメトリ
    //let sg =new THREE.SphereGeometry(0.5,16,16,0,      Math.PI,  0,Math.PI);
    //球体ジオメトリ(半球)
    //let sg2=new THREE.SphereGeometry(0.5,16,16,Math.PI,Math.PI*2,0,Math.PI);
    //平面ジオメトリ
    let pg=new THREE.PlaneGeometry(1,1,1,1);
    //円柱ジオメトリ
    let cg=new THREE.CylinderGeometry(1,1,1,32,1,false,0,Math.PI*2);

    //床
    let m0=new THREE.Mesh(pg,mt6);
    m0.rotation.set(-Math.PI/2,0,0);
    m0.receiveShadow=true;
    m0.position.set(0,-1,0);
    m0.scale.set(1000,1000,1);
    scene.add(m0);

    m0=new THREE.Mesh(pg,mt4);
    m0.rotation.set(-Math.PI/2,0,0);
    //m0.castShadow=true;
    m0.receiveShadow=true;
    m0.scale.set(20,20,1);
    m0.position.set(0,0,0);
    scene.add(m0);


    //ドローンのグループ
    drone=new THREE.Group;
    drone.castShadow=true;
    drone.receiveShadow=true;

    //本体メッシュを円柱ジオメトリから作成
    md0=new THREE.Mesh(cg,mt8);
    md0.castShadow=true;
    md0.scale.set(5,2,5);
    md0.position.set(0,4,0);
    drone.add(md0);

    //直方体ジオメトリからルータを支える棒を作成
    md1=new THREE.Mesh(bg,mt8);
    md1.castShadow=true;
    md1.scale.set(20,1,1);
    md1.position.set(0, 4.5, 0);
    md1.rotation.set(0,Math.PI*0.25,0);
    drone.add(md1);
    //直方体ジオメトリからルータを支える棒を作成
    md2=new THREE.Mesh(bg,mt8);
    md2.castShadow=true;
    md2.scale.set(20,1,1);
    md2.position.set(0, 4.5, 0);
    md2.rotation.set(0,-Math.PI*0.25,0);
    drone.add(md2);

    //直方体ジオメトリから前を表す青い小さいメッシュを作成
    md3=new THREE.Mesh(bg,mt5);
    md3.position.set(0,4,-5);
    drone.add(md3);
    //直方体ジオメトリから後ろを表す赤い小さいメッシュを作成
    md4=new THREE.Mesh(bg,mt3);
    md4.position.set(0,4,5);
    drone.add(md4);

    //直方体ジオメトリから脚部を作成
    md5=new THREE.Mesh(bg,mt8);
    md5.castShadow=true;
    md5.receiveShadow=true;
    md5.scale.set(10,0.5,0.5);
    md5.position.set(-3,0.25,0);
    md5.rotation.set(0,Math.PI/2,0);
    drone.add(md5);
    //直方体ジオメトリから脚部を作成
    md6=new THREE.Mesh(bg,mt8);
    md6.castShadow=true;
    md6.receiveShadow=true;
    md6.scale.set(10,0.5,0.5);
    md6.position.set(3,0.25,0);
    md6.rotation.set(0,Math.PI/2,0);
    drone.add(md6);
    //直方体ジオメトリから脚部を作成
    md7=new THREE.Mesh(bg,mt8);
    md7.castShadow=true;
    md7.receiveShadow=true;
    md7.scale.set(3,0.5,0.5);
    md7.position.set(-3,1.5,0);
    md7.rotation.set(0,0,Math.PI/2);
    drone.add(md7);
    //直方体ジオメトリから脚部を作成
    md8=new THREE.Mesh(bg,mt8);
    md8.castShadow=true;
    md8.receiveShadow=true;
    md8.scale.set(3,0.5,0.5);
    md8.position.set(3,1.5,0);
    md8.rotation.set(0,0,Math.PI/2);
    drone.add(md8);


    //直方体ジオメトリから羽(ローター)を作成
    drone1=new THREE.Group;
    drone1.position.set(-7,5,-7);
    md11=new THREE.Mesh(bg,mt7);
    md11.castShadow=true;
    md11.receiveShadow=true;
    md11.scale.set(10,0.5,0.5);
    md11.rotation.set(0,Math.PI*0.5,0);
    drone1.add(md11);
    md12=new THREE.Mesh(bg,mt7);
    md12.castShadow=true;
    md12.receiveShadow=true;
    md12.scale.set(8,0.5,0.5);
    md12.rotation.set(0,Math.PI*0.0,0);
    drone1.add(md12);
    drone.add(drone1);
    //直方体ジオメトリから羽(ローター)を作成
    drone2=new THREE.Group;
    drone2.position.set(-7,5,7);
    md21=new THREE.Mesh(bg,mt9);
    md21.castShadow=true;
    md21.receiveShadow=true;
    md21.scale.set(10,0.5,0.5);
    md21.rotation.set(0,Math.PI*0.5,0);
    drone2.add(md21);
    md22=new THREE.Mesh(bg,mt9);
    md22.castShadow=true;
    md22.receiveShadow=true;
    md22.scale.set(8,0.5,0.5);
    md22.rotation.set(0,Math.PI*0.0,0);
    drone2.add(md22);
    drone.add(drone2);
    //直方体ジオメトリから羽(ローター)を作成
    drone3=new THREE.Group;
    drone3.position.set(7,5,7);
    md31=new THREE.Mesh(bg,mt9);
    md31.castShadow=true;
    md31.receiveShadow=true;
    md31.scale.set(10,0.5,0.5);
    md31.rotation.set(0,Math.PI*0.5,0);
    drone3.add(md31);
    md32=new THREE.Mesh(bg,mt9);
    md32.castShadow=true;
    md32.receiveShadow=true;
    md32.scale.set(8,0.5,0.5);
    md32.rotation.set(0,Math.PI*0.0,0);
    drone3.add(md32);
    drone.add(drone3);
    //直方体ジオメトリから羽(ローター)を作成
    drone4=new THREE.Group;
    drone4.position.set(7,5,-7);
    md41=new THREE.Mesh(bg,mt7);
    md41.castShadow=true;
    md41.receiveShadow=true;
    md41.scale.set(10,0.5,0.5);
    md41.rotation.set(0,Math.PI*0.5,0);
    drone4.add(md41);
    md42=new THREE.Mesh(bg,mt7);
    md42.castShadow=true;
    md42.receiveShadow=true;
    md42.scale.set(8,0.5,0.5);
    md42.rotation.set(0,Math.PI*0.0,0);
    drone4.add(md42);
    drone.add(drone4);

    scene.add(drone);


    //オービットコントロールの作成(マウスの左右ボタンドラッグ、ホイール等でカメラが動くようになる)
    controls = new THREE.OrbitControls(camera, renderer.domElement);
    //controls.target.set(0,7,0);
    //オービットコントロールのターゲットをドローンに設定する
    controls.target=drone.position;
    controls.update();
    controls.enablePan=false;
    controls.enableZoom=false;
    controls.enableRotate=false;
    setInterval(renderLoop,33);
  }

  function renderLoop () {
    controls.update();
    //羽(ローター)の回転
    drone1.rotation.y+=Math.PI/8;
    drone2.rotation.y+=Math.PI/8;
    drone3.rotation.y+=Math.PI/8;
    drone4.rotation.y+=Math.PI/8;
    
    let limit=1.0;
    
    pos.vx *= 0.95;
    pos.vy *= 0.95;
    pos.vz *= 0.95;
    if(pos.c=="up"){
      pos.vy+=0.1;
      if(pos.vy>limit){vy=limit;}
    }else if(pos.c=="dn"){
      pos.vy-=0.1;
      if(pos.vy<-limit){vy=-limit;}
    }else if(pos.c=="tl"){
      pos.turn += Math.PI/72;
    }else if(pos.c=="tr"){
      pos.turn -= Math.PI/72;
    }else if(pos.c=="mf"){
      pos.vz+=0.1;
      if(pos.vz>limit){pos.vz=limit;}
    }else if(pos.c=="mb"){
      pos.vz-=0.1;
      if(pos.vz<-limit){pos.vz=-limit;}
    }else if(pos.c=="ml"){
      pos.vx-=0.1;
      if(pos.vx<-limit){pos.vx=-limit;}
    }else if(pos.c=="mr"){
      pos.vx+=0.1;
      if(pos.vx>limit){pos.vx=limit;}
    }

    pos.y += pos.vy;
    if(pos.y<0){
      pos.y -=pos.vy;
      pos.vy=-pos.vy*0.5;
    }
    pos.x += Math.cos(-pos.turn-Math.PI/2)*pos.vz;
    pos.z += Math.sin(-pos.turn-Math.PI/2)*pos.vz;
    pos.x += Math.cos(-pos.turn)*pos.vx;
    pos.z += Math.sin(-pos.turn)*pos.vx;
    

    drone.position.set(pos.x,pos.y,pos.z);
    drone.rotation.set(-pos.vz*Math.PI/8, pos.turn, -pos.vx*Math.PI/8);
    renderer.render(scene, camera);

  }

  window.addEventListener('DOMContentLoaded', main, false);

  window.addEventListener('load',function(){
    //X:右 Y:上 Z:手前
    //pos={"x":0,"y":0,"z":0,"vx":0,"vy":0,"vz":0,"turn":0};
    document.getElementById("up").addEventListener("mousedown",up);
    document.getElementById("dn").addEventListener("mousedown",dn);
    document.getElementById("tl").addEventListener("mousedown",tl);
    document.getElementById("tr").addEventListener("mousedown",tr);
    document.getElementById("mf").addEventListener("mousedown",mf);
    document.getElementById("mb").addEventListener("mousedown",mb);
    document.getElementById("ml").addEventListener("mousedown",ml);
    document.getElementById("mr").addEventListener("mousedown",mr);

    document.getElementById("up").addEventListener("touchstart",up);
    document.getElementById("dn").addEventListener("touchstart",dn);
    document.getElementById("tl").addEventListener("touchstart",tl);
    document.getElementById("tr").addEventListener("touchstart",tr);
    document.getElementById("mf").addEventListener("touchstart",mf);
    document.getElementById("mb").addEventListener("touchstart",mb);
    document.getElementById("ml").addEventListener("touchstart",ml);
    document.getElementById("mr").addEventListener("touchstart",mr);


    window.addEventListener("mouseup",function(){pos.c="";});
    window.addEventListener("touchend",function(){pos.c="";});
  });

  function up(){
    pos.c="up";
  }
  function dn(){
    pos.c="dn";
  }
  function tl(){
    pos.c="tl";
  }
  function tr(){
    pos.c="tr";
  }
  function mf(){
    pos.c="mf";
  }
  function mb(){
    pos.c="mb";
  }
  function ml(){
    pos.c="ml";
  }
  function mr(){
    pos.c="mr";
  }

</script>

Javascriptで3Dコンテンツ一覧へ