three.js 航拍全景图(+陀螺仪)

three.js 航拍全景图(+陀螺仪)_第1张图片
three.js 航拍全景图(+陀螺仪)_第2张图片

three.js 航拍全景图(+陀螺仪)_第3张图片
在这里插入图片描述

右上角陀螺仪也可点击,需要https的环境,手动下载DeviceOrientationControls.js文件
后台包含打点功能

<template>
  <div id="quanjing" style="width: 100vw; height: 100vh; overflow: hidden">
    <span
      id="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"
    />
    <img
      v-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;
      "
    />
    <!-- 封面图切换结束 -->
    <img
      src="../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" />
        <div
          style="
            background-color: rgb(253, 143, 143);
            margin-left: 10px;
            padding: 0 5px;
          "
          @click="delPoint(index)"
        >
          删除
        </div>
        <div
          style="
            background-color: rgb(249, 253, 143);
            margin-left: 10px;
            padding: 0 5px;
          "
          @click="huoqu(index)"
        >
          获取
        </div>
        <div
          style="
            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->75
    initThree() {
      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>

你可能感兴趣的:(javascript,开发语言,ecmascript)