AR室内导航-Three.js
Posted 我想当喝水人
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AR室内导航-Three.js相关的知识,希望对你有一定的参考价值。
AR室内导航-Three.js
概述
如有不明白的可以加QQ:2781128388
源码获取:https://mbd.pub/o/bread/Y5uck5lr
这一次的AR室内导航是使用蜂鸟云地图加上three.js做的,具备室内楼层切换,2D/3D模型切换,指北针控件,AR开启/关闭。模拟室内导航的功能,先来看看视频效果
AR室内导航
初始化室内地图
初始化蜂鸟云室内地图很简单,使用的也是蜂鸟云自带的地图数据
vue文件中调用mapCreate创建地图
this.$nextTick(() =>
this.mapCreate();
);
地图配置参数,需要自己去创建key值
options:
appName: '蜂鸟研发SDK_2_0',
key: '',
mapID: '1321274646113083394',
// 缩放级别
mapZoom: 20,
// 显示楼层
visibleLevels: [1, 2, 3, 4, 5],
// 默认显示几楼
level: 3
window.map = new fengmap.FMMap(this.options);
此时地图创建显示成功
创建楼层控件
地图创建完成后生成楼层控件,指北针,导航控件
//监听地图加载完成
map.on('loaded', () =>
//创建导航对象
this.creatNavigation();
//创建楼层控件
this.creatFloorControl();
//创建指北针控件
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
);
);
导航控件
// FMNaviAnalyser 是可分析最短路径、最快路径并返回分析结果的路径类。可独立于地图工作,支持Web Worker 和 Node
let analyser = new fengmap.FMNaviAnalyser(
this.options,
function()
// FMNavigation 是导航相关的功能类, 可用于模拟导航和真实导航使用
window.navi = new fengmap.FMNavigation(
map: map,
analyser: analyser,
locationMarkerUrl: './img/导航.png',
locationMarkerSize: 32
);
,
(error) =>
console.log(error);
);
此时就可以切换楼层显示和控制2D/3D转换
导航
一个输入开始地址和结束地点的UI,随便写写就ok
然后需在地图点击时输入起始点和终点,需要在地图上绑定点击事件
isNavBoxShow 为组件显示状态,startPointSelect 为起始点状态,endPointSelect 为结束点状态
// //路径规划
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 = '当前起点位置';
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 = '当前终点位置';
this.$store.commit('endPointSelectFalse');
);
此时我们点击地图模块就可以输入起始点和结束点了
点击确定后调用路径计算函数
window.routeOpiton 为起始点和结束点对象
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的背景中,这样就可以呈现了,然后怎么在场景中显示路径呢,也不难,蜂鸟云的api会返回一条最短路径的数组,通过这个最短路径的数据我们就可以计算,首先判断每一个点之间的距离是否大于1,如何计算两点之间的距离呢,通过两点的的平方开根就好了,计算出后大于1的就是存在有转角的,这时我们就要计算角度了,角度通过反正切来计算,这里需要注意的是轴的旋转方向,最后在监听陀螺仪来改变生成的点和线的角度就可以了,整体来说思路ok了接下来就是变成代码就行了,实现代码不难,主要是思路~
初始化Three
//初始参数
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参数
let renderParam =
antialias: true, // true/false表示是否开启反锯齿
// alpha: true, // true/false 表示是否可以设置背景色透明
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)
判断是否支持摄像头并返回视频流,这里有一个小细节,判断是否是手机还是PC,手机强制使用后置摄像头
let video = document.createElement('video');
// navigator.mediaDevices.getUserMedia 提示用户给予使用媒体输入的许可,媒体输入会产生一个MediaStream,里面包含了请求的媒体类型的轨道。
const stream = await navigator.mediaDevices.getUserMedia(
// 关闭音频
audio: false,
video:
// 在移动设备上面,表示优先使用前置摄像头
// 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) =>
// 在视频的元数据加载后执行 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;
这里我们就可以在看到视频了
接着我们创建一个起始点标记
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)
//添加坐标轴
let axes = new THREE.AxesHelper(500)
scene.add(axes)
绘制导航线
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
计算弧度代码
//计算偏转角度(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)
到这里就可以看到生成的线了
监听陀螺仪window.DeviceOrientationEvent
window.DeviceOrientationEvent说明
DeviceOrientationEvent.absolute 只读
用来说明设备是提供的旋转数据是否是绝对定位的布尔值。
DeviceOrientationEvent.alpha 只读
一个表示设备绕z轴旋转的角度(范围在0-360之间)的数字
DeviceOrientationEvent.beta 只读
一个表示设备绕x轴旋转(范围在-180到180之间)的数字,从前到后的方向为正方向。
DeviceOrientationEvent.gamma 只读
一个表示设备绕y轴旋转(范围在-90到90之间)的数字,从左向右为正方向。
throttle只是节流函数
if (window.DeviceOrientationEvent)
window.addEventListener('deviceorientation', throttle(setMeshCamera, 100), false)
else
console.log('你的浏览器不支持陀螺仪')
最后根据陀螺仪计算起始点和线的旋转角度就可以了
ARFoundation系列讲解 - 76 AR室内导航一
--------------------------------------(参考视频来源于网络,侵权必删)--------------------------------------
一、室内定位主流方案
目前室内定位主流的有Wifi、蓝牙、超宽带(UWB)、LED灯、地磁、视觉算法(稀疏点云)等定位技术,每一种技术各有优缺点,室内定位这一块还是处于百家争鸣阶段。
定位分类 | 精度 | 响应时间 | 穿透性 | 抗干扰 | 是否可用到手机 |
以上是关于AR室内导航-Three.js的主要内容,如果未能解决你的问题,请参考以下文章