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

Experience 3D Drone Control in Your Browser! Realistic Flight Simulation Powered by Three.js

Japanese

3D Drone Flight Simulation with Three.js (Three.js r145)

I created a 3D demo that lets you experience simulated drone controls directly in your web browser.
In this sample, OrbitControls is used, and the camera’s target property is set to the drone’s position so that the camera smoothly follows the drone.
You can control the drone using the eight buttons at the bottom.

Up
TL TR
Dn
MF
ML MR
MB

The drone has a red marker on the rear side and a blue marker on the front side.
[Up]: Ascend, [Dn]: Descend, [TL]: Turn Left, [TR]: Turn Right
[MF]: Move Forward, [MB]: Move Backward, [ML]: Move Left, [MR]: Move Right

Source Code for the 3D Drone Flight Simulation

<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 () {
    // Get the target canvas and retrieve its width and height
    let can=document.getElementById('can');
    let w = 600; // width
    let h = 600; // height

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

    //■Camera
    //THREE.PerspectiveCamera( FOV(deg), aspect ratio, near, far );
    camera = new THREE.PerspectiveCamera(25, w/h, 1, 10000);
    camera.position.set(0, 100, 150);//set camera position
    //camera.rotation.set(0,8,0);//set camera rotation (0°,0°,0°)
    camera.lookAt(new THREE.Vector3(0, 0, 0));//camera looks at (0,0,0)
    scene.add(camera);//add camera to scene

    //■Renderer
    renderer = new THREE.WebGLRenderer({canvas:can, antialias: true});//enable antialiasing
    renderer.setSize(w, h);
    //Enable shadow map
    renderer.shadowMap.enabled = true;
    //renderer.setClearColor(0x000000, 1);//set background color

    //■ Sky
    let sky=new THREE.Sky();
    sky.scale.setScalar(50000);
    sky.material.uniforms.turbidity.value=0.8; // atmospheric clarity
    sky.material.uniforms.rayleigh.value=0.3; // number of incoming photons
    sky.material.uniforms.mieCoefficient.value=0.005; // scattering coefficient
    sky.material.uniforms.mieDirectionalG.value=0.8; // directional scattering
    sky.material.uniforms.sunPosition.value.x= 10000; // sun position
    sky.material.uniforms.sunPosition.value.y= 30000; // sun position
    sky.material.uniforms.sunPosition.value.z=-40000; // sun position
    scene.add(sky);

    // ■ Lights ------------------------------------------------
    // Ambient light (soft light from all directions; prevents completely dark areas)
    var AmbientLight=new THREE.AmbientLight(0xffffff,0.3);
    scene.add(AmbientLight); // add ambient light to scene

    // Spotlight (color, intensity, distance, angle, penumbra, decay)
    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; // enable shadows
    spotLight.shadow.mapSize.width = 4096;  // high-quality shadows
    spotLight.shadow.mapSize.height = 4096; // high-quality shadows
    spotLight.shadow.radius=1;  // soften shadow edges
    scene.add(spotLight);
    scene.add(spotLight.target);
    
    // Point light (very heavy)
    //light = new THREE.PointLight( 0xffffff, 0.1, 4, 2 );
    //scene.add( light );


    // ■ Materials
    // Transparent
    let mt1=new THREE.MeshLambertMaterial({
      color:0xffffff,side:THREE.FrontSide,transparent:true,opacity:0.3
    });
    // Blue
    let mt2=new THREE.MeshLambertMaterial({
      color:0x0022cc,side:THREE.FrontSide
    });
    // Red
    let mt3=new THREE.MeshBasicMaterial({
      color:0xff3333,
      side:THREE.FrontSide
    });
    // Light green
    let mt4=new THREE.MeshLambertMaterial({
      color:0x66ff66,side:THREE.FrontSide,
    });
    // Blue
    let mt5=new THREE.MeshBasicMaterial({
      color:0x6666ff,
      side:THREE.FrontSide
    });

    // Floor texture
    let floorTexture=new THREE.TextureLoader().load('./three_r145/img/flag_g.png');
    floorTexture.wrapS=THREE.RepeatWrapping;
    floorTexture.wrapT=THREE.RepeatWrapping;
    // Set texture repeat count
    floorTexture.repeat.set(8,8);
    let mt6=new THREE.MeshLambertMaterial({
      color:0xffffff,side:THREE.FrontSide,
      map:floorTexture,
    });
    // White
    let mt7=new THREE.MeshLambertMaterial({
      color:0xffffff,side:THREE.FrontSide,transparent:false,opacity:1.0
    });
    // Gray
    let mt8=new THREE.MeshLambertMaterial({
      color:0xaaaaaa,side:THREE.FrontSide
    });
    // Dark gray
    let mt9=new THREE.MeshLambertMaterial({
      color:0x666666,side:THREE.FrontSide
    });


    // Box geometry
    let bg=new THREE.BoxGeometry(1,1,1);
    // Sphere geometry
    //let sg =new THREE.SphereGeometry(0.5,16,16,0,      Math.PI,  0,Math.PI);
    // Sphere geometry(Half)
    //let sg2=new THREE.SphereGeometry(0.5,16,16,Math.PI,Math.PI*2,0,Math.PI);
    // Plane geometry
    let pg=new THREE.PlaneGeometry(1,1,1,1);
    // Cylinder geometry
    let cg=new THREE.CylinderGeometry(1,1,1,32,1,false,0,Math.PI*2);

    // Floor
    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 group
    drone=new THREE.Group;
    drone.castShadow=true;
    drone.receiveShadow=true;

    //Create drone body from cylinder geometry
    md0=new THREE.Mesh(cg,mt8);
    md0.castShadow=true;
    md0.scale.set(5,2,5);
    md0.position.set(0,4,0);
    drone.add(md0);

    //Create support bars from box geometry
    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);
    //Create support bars from box geometry
    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);

    //Blue marker (front)
    md3=new THREE.Mesh(bg,mt5);
    md3.position.set(0,4,-5);
    drone.add(md3);
    //Red marker (rear)
    md4=new THREE.Mesh(bg,mt3);
    md4.position.set(0,4,5);
    drone.add(md4);

    //Legs
    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);
    //Legs
    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);
    //Legs
    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);
    //Legs
    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);


    //Rotors (propellers)
    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);
    //Rotors (propellers)
    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);
    //Rotors (propellers)
    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);
    //Rotors (propellers)
    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);


    //Create OrbitControls (enables camera movement via mouse drag and wheel)
    controls = new THREE.OrbitControls(camera, renderer.domElement);
    //controls.target.set(0,7,0);
    //Set OrbitControls target to the drone
    controls.target=drone.position;
    controls.update();
    controls.enablePan=false;
    controls.enableZoom=false;
    controls.enableRotate=false;
    setInterval(renderLoop,33);
  }

  function renderLoop () {
    controls.update();
    //Rotate rotors
    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: right, Y: up, Z: forward
    //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>

Back to the List of 3D JavaScript Content