Three.jsで3Dドローン操縦疑似体験(Three.js[r145])
Webブラウザ上でドローンの操縦が疑似体験できる3Dサイトを作ってみました。
このサンプルではオービットコントロール(OrbitControls)を使っているのですが、カメラがドローンを追従するようにオービットコントロールのtargetプロパティを、ドローン本体のpositionプロパティに設定しています。
下部にあるボタン8個で操縦できます。
ドローン本体の後ろ側に「赤色」、前側に「青色」のマークがあります。
[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>
