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.
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>
