右上角陀螺仪也可点击,需要https的环境,手动下载DeviceOrientationControls.js文件
后台包含打点功能
<template><div id="quanjing" style="width: 100vw; height: 100vh; overflow: hidden"><spanid="tip"style="position: absolute; color: red; top: 0; left: 0"></span><!-- 封面图切换结束 --><img:src="imageUrl + aerialData.asteroidImg"style="position: absolute;top: 50%;height: 100vh;left: 50%;transform: translate(-50%, -50%);"v-if="isShowFM"/><imgv-if="isShowHand"src="../assets/images/quanjingHand.png"style="z-index: 2;position: absolute;top: 50%;left: 50%;margin-left: -80px;margin-top: -90px;border-radius: 10px;width: 160px;height: 180px;"/><!-- 封面图切换结束 --><imgsrc="../assets/images/quanjinglogo.png"style="position: absolute; top: 10px; left: 10px; width: 100px"/><!-- 右上角全景图标切换开始 --><div class="rig-list" id="hangpaiIcon" v-if="!isPc"><img v-if="isFull" src="../assets/images/hangpaiIcon.png" /><img v-else src="../assets/images/hangpaiIcon2.png" /></div><!-- 右上角全景图标切换开始结束 --><!-- 标注增删改查开始 --><div class="addPoint" v-if="isShowPoint"><div class="row" v-for="(item, index) in biaojiList" :key="index"><div>地名:</div><input placeholder="" class="inp" v-model="item.title" /><div>x:</div><input placeholder="" class="inp" v-model="item.x" /><div>y:</div><input placeholder="" class="inp" v-model="item.y" /><div>z:</div><input placeholder="" class="inp" v-model="item.z" /><divstyle="background-color: rgb(253, 143, 143);margin-left: 10px;padding: 0 5px;"@click="delPoint(index)">删除</div><divstyle="background-color: rgb(249, 253, 143);margin-left: 10px;padding: 0 5px;"@click="huoqu(index)">获取</div><divstyle="background-color: rgb(104, 255, 111);margin-left: 10px;padding: 0 5px;"@click="baocun(index)">保存</div></div><div class="row"><div@click="addPoint"style="background-color: rgb(143, 200, 253);margin-left: 10px;padding: 0 5px;">新增</div><div@click="submitPoint"style="background-color: rgb(143, 253, 191);margin-left: 10px;padding: 0 5px;">上传</div><span style="font-size: 12px">(*1.点击获取按钮鼠标右键点击在航拍图上。2.确认坐标后点击同行保存按钮。3.标注完成后点击上传按钮)</span></div></div><!-- 标注增删改查结束 --><div id="biaozhudian"></div></div>
</template>
<!-- [{"title":"东直门","x":"445.42720890862677","y":"-218.0846523283593","z":"-57.86235074865762"}] -->
<script>
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { DeviceOrientationControls } from "three/examples/jsm/controls/DeviceOrientationControls.js";
import TWEEN from "@tweenjs/tween.js";
import {CSS2DRenderer,CSS2DObject,
} from "three/examples/jsm/renderers/CSS2DRenderer.js";
import { aerialData, editAerial, updateMark } from "@/api/house/house.js";
export default {name: "quanjing",data() {return {isShowFM: false,isShowHand: false,imageUrl: "https://obj.qiniu.fangdadi.com/",title: "房大地-全景",scene: null,camera: null,renderer: null,css2Renderer: null,loader: null,texture: null,sphereGeometry: null,mesh: null,axesHelper: null,tween: null,controls: null,controls2: null,//标点vector: null,screenVector: null,doc: null,activePoint: null,div: null,raycaster: null,mouse: null,tagObject: null,//陀螺仪clock: null,isFull: null,dcontrols: null,aerialData: {},biaojiList: [],pointList: [{ title: "", x: "", y: "", z: "" }],pointIndex: null,isShowPoint: false,tagArray: [],isPc: false,};},watch: {},mounted() {//判断是什么环境,手机还是电脑if (document.documentElement.clientWidth < 720) {this.isPc = false;} else {this.isPc = true;}this.getAerialData();// 点击右上角陀螺仪document.getElementById("hangpaiIcon").addEventListener("click", this.fullOrExit, false);},methods: {//获取数据getAerialData() {let idNum = null;console.log(this.$route.params.id.split("+"));if (this.$route.params.id.split("+").length == 2) {console.log(1111);this.isShowPoint = true;idNum = this.$route.params.id.split("+")[0];} else {console.log(22222222);this.isShowPoint = false;idNum = this.$route.params.id;}aerialData(idNum).then((res) => {if (res.data) {this.aerialData = res.data;this.isShowFM = true;if (res.data.markData) {console.log(JSON.parse(res.data.markData));this.biaojiList = JSON.parse(res.data.markData);console.log(" this.biaojiList ", this.biaojiList.length);}}}).then(() => {this.clock = new THREE.Clock();this.container = document.body;this.isFull = false;//判断是什么环境,手机还是电脑// if (document.documentElement.clientWidth < 720) {// }this.initThree();this.clickBiaoji();this.objTween();window.addEventListener("pointerdown", this.onMouseDown, false);var _this = this;// 更改渲染器画布大小window.onresize = function () {// 重置渲染器输出画布canvas尺寸_this.renderer.setSize(window.innerWidth, window.innerHeight);// 全屏情况下:设置观察范围长宽比aspect为窗口宽高比_this.camera.aspect = window.innerWidth / window.innerHeight;// 渲染器执行render方法的时候会读取相机对象的投影矩阵属性projectionMatrix// 但是不会每渲染一帧,就通过相机的属性计算投影矩阵(节约计算资源)// 如果相机的一些属性发生了变化,需要执行updateProjectionMatrix ()方法更新相机的投影矩阵_this.camera.updateProjectionMatrix();};});},// /**// 准备全景图像:// 首先,您需要获得或创建全景图像。全景图像是一种呈现完整360度视野的特殊图像。您可以使用专业相机拍摄全景照片,或者从互联网上获取全景图像。确保全景图像采用通常的全景图像格式,如equirectangular格式。// 设置Three.js场景:// 创建一个HTML页面,引入Three.js库。您可以从Three.js官方网站下载或使用CDN来加载库。// 创建Three.js场景:// 在您的HTML页面中,创建一个Three.js场景、相机和渲染器。// */// // 相机第一个参数fov 130->75initThree() {this.scene = new THREE.Scene();this.camera = new THREE.PerspectiveCamera(130,window.innerWidth / window.innerHeight,1,1000);this.renderer = new THREE.WebGLRenderer();this.renderer.setSize(window.innerWidth, window.innerHeight);this.renderer.domElement.style.zIndex = -1;this.css2Renderer = new CSS2DRenderer();this.css2Renderer.setSize(window.innerWidth, window.innerHeight);this.LoadingImg();},LoadingImg() {var _this = this;/*加载全景图像:使用Three.js的TextureLoader加载全景图像。*/this.loader = new THREE.TextureLoader();this.texture = this.loader.load(this.imageUrl + this.aerialData.panoramaImg,function (obj) {console.log("加载完了");_this.isShowFM = false;}); // If texture is used for color information, set colorspace.this.texture.encoding = THREE.sRGBEncoding;this.sphereGeometry = new THREE.SphereGeometry(500, 60, 40);this.sphereGeometry.scale(-1, 1, 1); //创建的球形几何体执行这个方法, 镜像就正回来了this.sphereMaterial = new THREE.MeshBasicMaterial({map: this.texture,side: THREE.DoubleSide,});this.mesh = new THREE.Mesh(this.sphereGeometry, this.sphereMaterial);this.scene.add(this.mesh);// // AxesHelper:辅助观察的坐标系// this.axesHelper = new THREE.AxesHelper(150);// this.scene.add(this.axesHelper);/*设置相机视角:将相机朝向全景图像的中心,以确保全景图像填充整个视野。*/this.camera.position.set(0, 500, 0.1);this.camera.lookAt(0, 0, 0);/*** 模型旋转*/this.mesh.rotateY(-Math.PI / 2); //绕x轴旋转π/2},objTween() {var _this = this;// // 旋转过渡效果this.tween = new TWEEN.Tween({x: 0,y: 500,z: 10,ry: -Math.PI / 2,fov: 130,}) // 开始位置(2D).to({x: 0,y: 0,z: 0.1,ry: 0,fov: 75,},3000) // 结束位置(3D).easing(TWEEN.Easing.Quadratic.InOut) // 缓动函数.onUpdate(function (obj) {_this.camera.position.set(obj.x * 1, obj.y * 1, obj.z * 1);_this.camera.lookAt(_this.scene.position);_this.mesh.rotation.y = obj.ry;_this.camera.rotation.x = obj.ry;_this.camera.fov = obj.fov;_this.camera.updateProjectionMatrix();}).delay(3000).start().onComplete(function () {//运动结束后地图打点_this.biaoji();_this.isShowHand = true;setTimeout(() => {_this.isShowHand = false;}, 2000);});this.OrbitControlsFun();},// /*// 渲染场景:// 使用requestAnimationFrame函数循环渲染Three.js场景。// */animate() {requestAnimationFrame(this.animate);TWEEN.update(); // 更新Tween.js动画this.renderer.render(this.scene, this.camera);this.css2Renderer.render(this.scene, this.camera);if (this.isFull) {this.dcontrols.update(this.clock.getDelta());}},/*添加交互性(可选):您还可以添加鼠标或触摸交互,以允许用户在全景图像中浏览。Three.js提供了相关的控制器,如OrbitControls。*/OrbitControlsFun() {this.controls = new OrbitControls(this.camera, this.renderer.domElement);this.controls.enableZoom = false;this.controls.enablePan = false;this.controls2 = new OrbitControls(this.camera,this.css2Renderer.domElement);this.controls2.enableZoom = false;this.controls2.enablePan = false;// 如果OrbitControls改变了相机参数,重新调用渲染器渲染三维场景document.getElementById("quanjing").appendChild(this.renderer.domElement);this.animate();},//设置标记点开始biaoji() {if (this.tagArray.length != 0) {this.tagArray.forEach((i) => {this.scene.remove(i);});}this.biaojiList.forEach((item) => {let info1 = document.createElement("div");info1.setAttribute("class", "info1");info1.style.width = "40px";info1.style.height = "40px";info1.style.position = "absolute";info1.style.pointerEvents = "auto";var img = document.createElement("img");img.src ="https://wimg.588ku.com/gif620/20/06/28/a47c5cb0333780628b29c0e94f3b9423.gif";img.style.width = "40px";img.style.height = "40px";info1.appendChild(img);var p = document.createElement("p");p.innerText = item.title;p.style.position = "absolute";p.style.bottom = "100%";p.style.left = "-70%";p.style.minWidth = "100px";p.style.textAlign = "center";p.style.minHeight = "20px";p.style.background = "#31313194";p.style.padding = "5px";p.style.borderRadius = "10px";p.style.color = "white";info1.appendChild(p);let tag = new CSS2DObject(info1);tag.name = "proLabel";tag.position.set(item.x, item.y, item.z);this.scene.add(tag);this.tagArray.push(tag);this.css2Renderer.domElement.style.position = "absolute";this.css2Renderer.domElement.style.top = "0";this.css2Renderer.domElement.style.pointerEvents = "none";document.getElementById("biaozhudian").appendChild(this.css2Renderer.domElement);});},clickBiaoji() {this.vector = new THREE.Vector3();this.screenVector = new THREE.Vector3();this.doc = document;this.activePoint = null;this.div = this.doc.getElementById("tip");this.raycaster = new THREE.Raycaster();this.mouse = new THREE.Vector2();this.tagObject = new THREE.Object3D();},/*** 鼠标点击触发**/onMouseDown(event) {if (event.buttons === 2 && this.pointIndex != null) {// 屏幕坐标转标准设备坐标this.vector.set((event.clientX / window.innerWidth) * 2 - 1,-(event.clientY / window.innerHeight) * 2 + 1,0);// 将标准设备坐标转为世界坐标this.vector.unproject(this.camera);this.raycaster = new THREE.Raycaster(this.camera.position,this.vector.sub(this.camera.position).normalize());let intersects = this.raycaster.intersectObjects([this.mesh]);if (intersects.length > 0) {this.activePoint = intersects[0].point;let point = this.toScreenPosition({point: this.activePoint,});this.div.style.left = point.x + "px";this.div.style.top = point.y + 20 + "px";}let xyz = this.activePoint;this.biaojiList[this.pointIndex].x = xyz.x;this.biaojiList[this.pointIndex].y = xyz.y;this.biaojiList[this.pointIndex].z = xyz.z;console.log("点击坐标-----", JSON.stringify(this.activePoint)); // 传给后台的参数}},toScreenPosition({ obj = null, point = null }) {point ? this.screenVector.set(...point) : this.screenVector.set();// 屏幕坐标系中心let widthHalf = this.renderer.getContext().canvas.width / 2;let heightHalf = this.renderer.getContext().canvas.height / 2;if (obj) {// 更新物体及其后代的全局变换obj.updateMatrixWorld();// 提取位置相关的分量this.screenVector.setFromMatrixPosition(obj.matrixWorld);}// 世界坐标转标准设备坐标。范围[-1,1]this.screenVector.project(this.camera);//标准设备坐标转屏幕坐标(2D)this.screenVector.x = this.screenVector.x * widthHalf + widthHalf;this.screenVector.y = -this.screenVector.y * heightHalf + heightHalf;return {x: this.screenVector.x,y: this.screenVector.y,};},// 陀螺仪setOrientationControls(e) {// 判断手机电脑端if (!e.alpha) {return;}this.isFull = true;this.dcontrols = new DeviceOrientationControls(this.camera, true);this.dcontrols.connect();this.dcontrols.update();window.removeEventListener("deviceorientation",this.setOrientationControls,true);},// 陀螺仪权限判断fullOrExit() {let _this = this;if (!_this.isFull) {try {console.log("浏览器UA---->", navigator.userAgent);if (navigator.userAgent.includes("iPhone") ||navigator.userAgent.includes("iPad") ||navigator.userAgent.includes("iPod") ||navigator.userAgent.includes("Macintosh")) {// 这是苹果设备上的浏览器console.log("这是苹果设备上的浏览器");if (window.DeviceOrientationEvent !== undefined &&typeof window.DeviceOrientationEvent.requestPermission ==="function") {window.DeviceOrientationEvent.requestPermission().then(function (response) {if (response == "granted") {window.addEventListener("deviceorientation",_this.setOrientationControls,true);}}).catch(function (error) {console.error("THREE.DeviceOrientationControls: Unable to use DeviceOrientation API:",error);});} else {window.addEventListener("deviceorientation",_this.setOrientationControls,true);}} else {// 这不是苹果设备上的浏览器console.log("这不是苹果设备上的浏览器");window.addEventListener("deviceorientation",_this.setOrientationControls,true);}} catch (error) {console.error("监听事件处理程序出错:", error);}} else {if (_this.dcontrols) {_this.dcontrols.disconnect();_this.dcontrols.update();window.removeEventListener("deviceorientation",_this.setOrientationControls,true);} else {console.log("dcontrols 未创建");}_this.isFull = false;}},// 增删改查数据开始//删除标点delPoint(index) {this.pointIndex = null;this.biaojiList.splice(index, 1);this.biaoji();},//获取标点huoqu(index) {this.pointIndex = index;},//保存标点baocun(index) {this.pointIndex = null;this.biaoji();},//上传标点信息async submitPoint() {let FormData = new window.FormData();FormData.append("aerialId", this.aerialData.aerialId);FormData.append("markData", JSON.stringify(this.biaojiList));let res = await updateMark(FormData);if (res.data == 1) {this.$message.success("上传成功");console.log(res);}},//新增标点数据addPoint() {this.biaojiList.push({ title: "", x: "", y: "", z: "" });},// 增删改查数据结束},
};
</script><style rel="stylesheet/scss" lang="scss" scoped>
// ::v-deep#quanjing {overflow: hidden;width: 100vw;height: 100vh;touch-action: none;position: absolute;top: 0px;
}
.info1 {position: absolute;width: 40px;height: 40px;
}.info1 img {width: 40px;height: 40px;
}.info1 p {position: absolute;bottom: 100%;left: -70%;min-width: 100px;text-align: center;min-height: 20px;color: white;
}
.rig-list {position: absolute;right: 10px;top: 40px;img {width: 40px;height: 40px;margin-bottom: 10px;}#hangpaiIcon {z-index: 2;}
}
.addPoint {width: 750px;background: rgba(255, 255, 255, 0.453);min-height: 50px;position: absolute;top: 0;left: 50%;margin-left: -375px;
}
.row {width: 100%;overflow: hidden;margin-bottom: 5px;div {float: left;margin-left: 10px;}.inp {float: left;border: none;outline: none;width: 100px;margin-left: 10px;}
}
</style>