概述
如有不明白的可以加QQ:2354528292;wx: aichitudousien
更多教學(xué)視頻請訪問:https://space.bilibili.com/236087412
源碼獲?。篽ttps://item.taobao.com/item.htm?spm=a21dvs.23580594.0.0.3c3a645ebB8H6o&ft=t&id=714574529746
這一次的AR室內(nèi)導(dǎo)航是使用蜂鳥云地圖加上three.js做的,具備室內(nèi)樓層切換,2D/3D模型切換,指北針控件,AR開啟/關(guān)閉。模擬室內(nèi)導(dǎo)航的功能,先來看看視頻效果
AR室內(nèi)導(dǎo)航
初始化室內(nèi)地圖
初始化蜂鳥云室內(nèi)地圖很簡單,使用的也是蜂鳥云自帶的地圖數(shù)據(jù)
vue文件中調(diào)用mapCreate創(chuàng)建地圖
this.$nextTick(() => {
this.mapCreate();
});
地圖配置參數(shù),需要自己去創(chuàng)建key值
options: {
appName: '蜂鳥研發(fā)SDK_2_0',
key: '',
mapID: '1321274646113083394',
// 縮放級別
mapZoom: 20,
// 顯示樓層
visibleLevels: [1, 2, 3, 4, 5],
// 默認顯示幾樓
level: 3
}
window.map = new fengmap.FMMap(this.options);
此時地圖創(chuàng)建顯示成功
創(chuàng)建樓層控件
地圖創(chuàng)建完成后生成樓層控件,指北針,導(dǎo)航控件
//監(jiān)聽地圖加載完成
map.on('loaded', () => {
//創(chuàng)建導(dǎo)航對象
this.creatNavigation();
//創(chuàng)建樓層控件
this.creatFloorControl();
//創(chuàng)建指北針控件
this.creatCompassControl();
});
樓層控件
creatFloorControl() {
let toolbar = new fengmap.FMToolbar({
//默認在右上角
position: fengmap.FMControlPosition.RIGHT_TOP,
//初始是否是多層顯示,默認單層顯示
allLayer: false,
//是否顯示多層/單層切換按鈕
needAllLayerBtn: true,
//控件位置x,y的偏移量
offset: {
x: -10,
y: 320
}
});
toolbar.addTo(map);
},
指北針
let compass = new fengmap.FMCompass({
position: fengmap.FMControlPosition.LEFT_TOP,
width: 40,
height: 40,
offset: {
x: 12,
y: 460
}
});
compass.addTo(map);
compass.on('click', function() {
map.setRotation({
rotation: 0,
animate: true,
duration: 0.3
});
});
導(dǎo)航控件
// FMNaviAnalyser 是可分析最短路徑、最快路徑并返回分析結(jié)果的路徑類。可獨立于地圖工作,支持Web Worker 和 Node
let analyser = new fengmap.FMNaviAnalyser(
this.options,
function() {
// FMNavigation 是導(dǎo)航相關(guān)的功能類, 可用于模擬導(dǎo)航和真實導(dǎo)航使用
window.navi = new fengmap.FMNavigation({
map: map,
analyser: analyser,
locationMarkerUrl: './img/導(dǎo)航.png',
locationMarkerSize: 32
});
},
(error) => {
console.log(error);
}
);
此時就可以切換樓層顯示和控制2D/3D轉(zhuǎn)換
導(dǎo)航
一個輸入開始地址和結(jié)束地點的UI,隨便寫寫就ok
然后需在地圖點擊時輸入起始點和終點,需要在地圖上綁定點擊事件
isNavBoxShow 為組件顯示狀態(tài),startPointSelect 為起始點狀態(tài),endPointSelect 為結(jié)束點狀態(tài)
// //路徑規(guī)劃
map.on('click', (event) => {
if (this.$store.state.isNavBoxShow === true) {
if (this.$store.state.startPointSelect === true) {
window.routeOpiton.start = {
x: event.coords.x,
y: event.coords.y,
level: event.targets[0].level,
url: './img/start.png',
height: 3
};
navi.setStartPoint(window.routeOpiton.start);
if (event.targets[0].name) {
document.getElementById('startInput').value = event.targets[0].name;
} else {
document.getElementById('startInput').value = '當(dāng)前起點位置';
}
this.$store.commit('startPointSelectFalse');
} else if (this.$store.state.endPointSelect === true) {
window.routeOpiton.end = {
x: event.coords.x,
y: event.coords.y,
level: event.targets[0].level,
url: './img/end.png',
height: 3
}
navi.setDestPoint(window.routeOpiton.end);
if (event.targets[0].name) {
document.getElementById('endInput').value = event.targets[0].name;
} else {
document.getElementById('endInput').value = '當(dāng)前終點位置';
}
this.$store.commit('endPointSelectFalse');
}
}
});
此時我們點擊地圖模塊就可以輸入起始點和結(jié)束點了
點擊確定后調(diào)用路徑計算函數(shù)
window.routeOpiton 為起始點和結(jié)束點對象
navi.route(window.routeOpiton, function(result) {
let line = navi.drawNaviLine();
let coordinates = [];
result.subs.forEach(item => {
item.waypoint.points.forEach(point => {
coordinates.push(point)
})
});
})
使用Three.js 生成AR模塊原理
說明一下生成步驟,第一步同樣是先驗證是否能打開攝像頭,然后初始化Three.js,然后將攝像頭的視頻流使用video貼圖map到three.js的背景中,這樣就可以呈現(xiàn)了,然后怎么在場景中顯示路徑呢,也不難,蜂鳥云的api會返回一條最短路徑的數(shù)組,通過這個最短路徑的數(shù)據(jù)我們就可以計算,首先判斷每一個點之間的距離是否大于1,如何計算兩點之間的距離呢,通過兩點的的平方開根就好了,計算出后大于1的就是存在有轉(zhuǎn)角的,這時我們就要計算角度了,角度通過反正切來計算,這里需要注意的是軸的旋轉(zhuǎn)方向,最后在監(jiān)聽陀螺儀來改變生成的點和線的角度就可以了,整體來說思路ok了接下來就是變成代碼就行了,實現(xiàn)代碼不難,主要是思路~
初始化Three
//初始參數(shù)
canvas = document.getElementById('webGL3d')
arWidth = canvas.offsetWidth
arHeight = canvas.offsetHeight
scene = new THREE.Scene()
camera = new THREE.PerspectiveCamera(60, arWidth / arHeight, 0.0001, 7000)
camera.position.set(0, -7, 5)
// //renderer參數(shù)
let renderParam = {
antialias: true, // true/false表示是否開啟反鋸齒
// alpha: true, // true/false 表示是否可以設(shè)置背景色透明
precision: 'highp', // highp/mediump/lowp 表示著色精度選擇
premultipliedAlpha: false,
maxLights: 3,
canvas: canvas
}
renderer = new THREE.WebGLRenderer(renderParam)
renderer.setSize(arWidth, arHeight)
orbitControls = new OrbitControls(camera, renderer.domElement)
判斷是否支持攝像頭并返回視頻流,這里有一個小細節(jié),判斷是否是手機還是PC,手機強制使用后置攝像頭
let video = document.createElement('video');
// navigator.mediaDevices.getUserMedia 提示用戶給予使用媒體輸入的許可,媒體輸入會產(chǎn)生一個MediaStream,里面包含了請求的媒體類型的軌道。
const stream = await navigator.mediaDevices.getUserMedia({
// 關(guān)閉音頻
audio: false,
video: {
// 在移動設(shè)備上面,表示優(yōu)先使用前置攝像頭
// facingMode: 'user',
facingMode: isMobile() ? { exact: "environment" } : 'user',
width: width,
height: height
}
});
video.srcObject = stream;
video.play();
video.width = width;
video.height = height;
return new Promise((resolve) => {
// 在視頻的元數(shù)據(jù)加載后執(zhí)行 JavaScript
video.onloadedmetadata = () => {
resolve(video);
};
});
function isMobile() {
const isAndroid = /Android/i.test(navigator.userAgent);
const isiOS = /iPhone|iPad|iPod/i.test(navigator.userAgent);
return isAndroid || isiOS;
}
獲取到視頻流后將視頻貼到three.js的背景中
let video = await openCamera(arWidth, arHeight);
console.log(video);
videoTexture = new THREE.Texture(video);
videoTexture.minFilter = THREE.LinearFilter;
scene.background = videoTexture;
這里我們就可以在看到視頻了
接著我們創(chuàng)建一個起始點標(biāo)記
let plane = new THREE.PlaneGeometry(1, 1)
let map = new THREE.TextureLoader().load(require('@/assets/img/WechatIMG1129.png'))
let material = new THREE.MeshBasicMaterial({
map: map,
alphaTest: 0.1,
color: 0xffffff,
side: THREE.DoubleSide,
})
nowPosPic = new THREE.Mesh(plane, material)
nowPosPic.position.set(0, offsetY, 0)
scene.add(nowPosPic)
//添加坐標(biāo)軸
let axes = new THREE.AxesHelper(500)
scene.add(axes)
繪制導(dǎo)航線
if (coordinates.length !== 0) {
group = new THREE.Group()
let starPoint = {
x: 0,
y: 0
}
for (let i = 1; i < coordinates.length; i++) {
let x = coordinates[i].x - coordinates[0].x
let y = coordinates[i].y - coordinates[0].y
// 計算兩點的距離
let distance = Math.sqrt(Math.pow(x - starPoint.x, 2) + Math.pow(y - starPoint.y, 2))
if (distance >= 1) {
// 計算弧度
let angle = calAngleX(x - starPoint.x, y - starPoint.y)
// 生成線
createLine(starPoint, distance, angle)
starPoint.x = x
starPoint.y = y
}
}
scene.add(group)
group.position.y = offsetY
group.rotation.z = -alpha * Math.PI / 180
}
計算弧度代碼
//計算偏轉(zhuǎn)角度(X逆時針)
function calAngleX(x, y) {
let angle = Math.atan(Math.abs(y) / Math.abs(x))
if (x >= 0 && y >= 0) {
} else if (x <= 0 && y >= 0) {
angle = Math.PI - angle
} else if (x <= 0 && y <= 0) {
angle = Math.PI + angle
} else {
angle = Math.PI * 2 - angle
}
return angle
}
生成線
let plane = new THREE.PlaneGeometry(1, 1)
let map = new THREE.TextureLoader().load(require('@/assets/img/WechatIMG1123.png'))
let material = new THREE.MeshBasicMaterial({
map: map,
alphaTest: 0.1,
color: 0xffffff,
side: THREE.DoubleSide,
})
for (let i = 0.6; i <= length; i++) {
let mesh = new THREE.Mesh(plane, material)
let x = starPoint.x + i * Math.cos(angle)
let y = starPoint.y + i * Math.sin(angle)
mesh.position.set(x, y, 0)
let obj = {
x: x + coordinates[0].x,
y: y + coordinates[0].y
}
lingMeshArray.push(obj)
mesh.rotation.z = angle - Math.PI / 2
group.add(mesh)
}
到這里就可以看到生成的線了
監(jiān)聽陀螺儀window.DeviceOrientationEvent
window.DeviceOrientationEvent說明
DeviceOrientationEvent.absolute 只讀
用來說明設(shè)備是提供的旋轉(zhuǎn)數(shù)據(jù)是否是絕對定位的布爾值。
DeviceOrientationEvent.alpha 只讀
一個表示設(shè)備繞z軸旋轉(zhuǎn)的角度(范圍在0-360之間)的數(shù)字
DeviceOrientationEvent.beta 只讀
一個表示設(shè)備繞x軸旋轉(zhuǎn)(范圍在-180到180之間)的數(shù)字,從前到后的方向為正方向。
DeviceOrientationEvent.gamma 只讀
一個表示設(shè)備繞y軸旋轉(zhuǎn)(范圍在-90到90之間)的數(shù)字,從左向右為正方向。
throttle只是節(jié)流函數(shù)文章來源:http://www.zghlxwxcb.cn/news/detail-400003.html
if (window.DeviceOrientationEvent) {
window.addEventListener('deviceorientation', throttle(setMeshCamera, 100), false)
} else {
console.log('你的瀏覽器不支持陀螺儀')
}
最后根據(jù)陀螺儀計算起始點和線的旋轉(zhuǎn)角度就可以了文章來源地址http://www.zghlxwxcb.cn/news/detail-400003.html
到了這里,關(guān)于AR室內(nèi)導(dǎo)航-Three.js的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!