Explorar o código

Revert: 模型处理

lianghanqiang %!s(int64=4) %!d(string=hai) anos
pai
achega
7978907ae7

+ 306 - 0
src/components/3DScene/3DScene.vue

@@ -0,0 +1,306 @@
+<template>
+  <div class="full" style="overflow: hidden">
+    <div class="full" id="3DScene"></div>
+  </div>
+</template>
+
+<script>
+import * as Three from 'three'
+import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js';
+import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
+import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader';
+import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader';
+import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader';
+import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
+import Box1 from "../3DMap/map-components/infobox/box1";
+import InfoLayer from "../3DMap/map-components/InfoLayer";
+import MapForScene from "./map";
+import TWEEN from '@tweenjs/tween.js'
+import Box2 from "../3DMap/map-components/infobox/box2";
+import {getBox1, getCameraBox} from "./infoBox";
+import { createLogger } from 'vuex';
+
+export default {
+  name: "Scene3D",
+  components: {Box2, MapForScene, InfoLayer, Box1},
+  provide(){
+    return {
+      scene: this
+    }
+  },
+  data(){
+    return{
+      raycaster: {},
+      mouse: {},
+      scene2D: {},
+      labelRenderer: {},
+      loadingSchedule: 0,
+      position: [0,0],
+      camera: null,
+      scene: null,
+      renderer: null,
+      mesh: null,
+      controls: null,
+      container: null,
+      tween: {},
+    }
+  },
+  props: {
+    cameraOption: {
+     type: "Object",
+      default: {
+        position: [1000,1000,1000],
+        near: 0.001,
+        far: 10000000,
+        lookAt: [0,0,0],
+      }
+    },
+    controlsOption: {
+      type: 'Object',
+      default: {
+        minDistance : 0,
+        maxDistance : 1000000,
+        autoRotate : false,
+        autoRotateSpeed : 1,
+        keyPanSpeed : 3,
+        target : new Three.Vector3(0,0,0),
+        enableDamping : true,
+      }
+    }
+  },
+  methods: {
+    //加载json文件
+    async addJson(url){
+      return new Promise((resolve, reject) => {
+        let loader = new Three.FileLoader();
+        loader.load(url,  (data) => {
+          resolve( JSON.parse(data));
+        });
+      } )
+    },
+    async addObj(mtlUrl,objUrl,handle,process){
+      return new Promise((resolve, reject) => {
+        let loader = new OBJLoader();
+        let mtlLoader = new MTLLoader();
+        mtlLoader.load(mtlUrl,  (material) => {
+          loader.setMaterials(material);
+          loader.load(objUrl, (loadedMesh) => {
+            handle(loadedMesh);
+          },(xhr)=>{
+            process(xhr);
+            if((Math.floor((xhr.loaded/xhr.total)*1000)*0.1+'').substring(0,4)=='100'){
+              resolve();
+            }
+          },(err)=>{
+            reject(err)});
+        });
+      })
+    },
+    addHDR(url){
+      const pmremGenerator = new Three.PMREMGenerator(this.renderer); // 使用hdr作为背景色
+      pmremGenerator.compileEquirectangularShader();
+      const scene = this.scene;
+      new RGBELoader()
+        .setDataType(Three.UnsignedByteType)
+        .load(url, function (texture) {
+          const envMap = pmremGenerator.fromEquirectangular(texture).texture;
+          // envMap.isPmremTexture = true;
+          pmremGenerator.dispose();
+
+          scene.environment = envMap; // 给场景添加环境光效果
+          scene.background = envMap; // 给场景添加背景图
+        });
+    },
+    //添加天空盒子===》urls 6张方位图图片路径  //六张图片分别是朝前的(posz)、朝后的(negz)、朝上的(posy)、朝下的(negy)、朝右的(posx)和朝左的(negx)。
+    addSkyBox(urls){
+      let cubeTextureLoader = new Three.CubeTextureLoader();
+      let cubeTexture = cubeTextureLoader.load( urls);
+      this.scene.background = cubeTexture;
+    },
+    //添加平行光
+    addDirectionalLight(position,color=0xffffff){
+      //添加光源
+      let dirLight = new Three.DirectionalLight(color, 1)
+      dirLight.position.set(position[0], position[1], position[2]);
+      dirLight.castShadow = true //可以产生阴影
+      dirLight.shadow.mapSize = new Three.Vector2(1024, 1024);
+      dirLight.onlyShadow = true;
+      dirLight.shadow.camera.near = 200;
+      dirLight.shadow.camera.far = 1000;
+
+      this.scene.add(dirLight)
+      this.scene.add(dirLight.target)
+    },
+
+    //三维坐标转换屏幕坐标
+    transPosition(position=[0,0,0]){
+      let world_vector = new Three.Vector3(position[0],position[1],position[2]);
+      let vector =world_vector.project(this.camera);
+      let halfWidth =  window.innerWidth / 2,
+        halfHeight =  window.innerHeight / 2;
+      return [
+        Math.round(vector.x * halfWidth + halfWidth),
+        Math.round(-vector.y * halfHeight + halfHeight)
+      ];
+    },
+    //添加gltf模型
+    addGltf(url,handleGLTF,handleProgress){
+      let loader = new GLTFLoader();
+      loader.castShadow =true;
+      loader.receiveShadow = true;
+      loader.load( url,  (gltf) => {
+          handleGLTF(gltf);
+        }, (xhr)=> {
+          handleProgress(xhr)
+        },
+        ( error ) => {
+          alert(error);
+        } );
+    },
+    //初始化场景
+    async init() {
+      
+      //挂载容器
+      let container = document.getElementById('3DScene');
+
+      this.raycaster = new Three.Raycaster();
+
+      //初始化相机
+      this.camera = new Three.PerspectiveCamera(50, container.clientWidth/container.clientHeight, this.cameraOption.near, this.cameraOption.far);
+      this.camera.position.z = this.cameraOption.position[0];
+      this.camera.position.x = this.cameraOption.position[1];
+      this.camera.position.y = this.cameraOption.position[2];
+      this.camera.lookAt(...this.cameraOption.lookAt);
+
+      //初始化场景
+      this.scene = new Three.Scene();
+
+
+      //渲染器
+      this.renderer = new Three.WebGLRenderer({antialias: true,alpha: true,logarithmicDepthBuffer: true});
+      this.renderer.setSize(container.clientWidth, container.clientHeight);
+      this.renderer.shadowMap.enabled = true;
+      this.renderer.shadowMap.type = Three.PCFSoftShadowMap;
+      container.appendChild(this.renderer.domElement);
+
+      this.labelRenderer = new CSS2DRenderer();
+      this.labelRenderer.setSize( container.clientWidth, container.clientHeight);
+      this.labelRenderer.domElement.style.position = 'absolute';
+      this.labelRenderer.domElement.style.top = '0px';
+      container.appendChild( this.labelRenderer.domElement );
+
+      //坐标轴
+      // let axes = new Three.AxisHelper(30000);
+      //  this.scene.add(axes);
+
+
+      //半球光照
+      this.scene.add(new Three.HemisphereLight(0xffffff,0xffffff,0.45));
+
+      //控制器
+        this.controls = new OrbitControls(this.camera, this.labelRenderer.domElement);
+      this.controls.minDistance = this.controlsOption.minDistance;
+      this.controls.maxDistance = this.controlsOption.maxDistance;
+      this.controls.autoRotate = this.controlsOption.autoRotate;
+      this.controls.autoRotateSpeed = this.controlsOption.autoRotateSpeed;
+      this.controls.keyPanSpeed = this.controlsOption.keyPanSpeed;
+      this.controls.target = this.controlsOption.target;
+      this.controls.enableDamping = this.controlsOption.enableDamping;
+      this.controls.rotateSpeed = 0.5;
+      this.controls.maxPolarAngle = 0.49 * Math.PI;
+      this.controls.enableDamping = this.controlsOption.enableDamping;
+      this.controls.zoomSpeed = 0.5;
+      this.controls.panSpeed = 0.5;
+      this.controls.enablePan = true;
+
+      window.addEventListener( 'mousemove', this.onMouseMove, false );
+      window.addEventListener( 'click', this.onMouseClick, false );
+      this.$emit('loadingFinish',{});
+    },
+    //每帧渲染函数
+    animate() {
+      requestAnimationFrame(this.animate);
+      this.controls.update();
+      this.renderer.render(this.scene, this.camera);
+      this.labelRenderer.render( this.scene, this.camera );
+      TWEEN.update();
+    },
+    //设置背景颜色
+    setBackground(color){
+      this.renderer.setClearColor(color);
+    },
+    //设置背景透明度
+    setAlpha(num){
+      this.renderer.setClearAlpha(num);
+    },
+    //窗口大小监听
+    resize(resizeHandle){
+     let width = window.innerWidth;
+     let height = window.innerHeight;
+      this.camera.aspect = width / height;
+      this.camera.updateProjectionMatrix();
+      this.renderer.setSize(width, height);
+      this.labelRenderer.setSize( width, height);
+    },
+    //定位相机视角
+
+    //鼠标移动事件
+    onMouseMove(event) {
+      const _this =this;
+      // _this.mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
+      // _this.mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
+      // _this.eventOffset.x = event.clientX;
+      // _this.eventOffset.y = event.clientY;
+      // _this.infoPosition.x = _this.eventOffset.x + 2 ;
+      // _this.infoPosition.y = _this.eventOffset.y + 2 ;
+    },
+    //鼠标单击事件
+    onMouseClick(){
+     // alert(1);
+    },
+    //漫游视角到指定位置
+    flyto(to,from=this.camera.position,time=1000){
+      let _this = this;
+      const {x,y,z} = to;
+      console.log(this.controls,123456);
+      this.tween = new TWEEN.Tween(from)
+      .to({x:x+50,y:y+50,z:z+50},time)
+      .easing(TWEEN.Easing.Quadratic.InOut);
+
+      this.tween.onUpdate(function(){
+        _this.controls.target.set(x,y,z);
+      })
+      this.tween.onComplete(()=>{
+        this.camera.lookAt(to)
+      })
+      this.tween.start();
+    }
+  },
+
+  async mounted() {
+    const _this = this;
+    await this.init();
+    this.animate();
+    window.addEventListener('resize', this.resize);
+  }
+}
+</script>
+
+<style scoped>
+  .full{
+    width: 100%;
+    height: 100%;
+    background: #dddddd;
+  }
+
+  .center{
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+  @keyframes fade-in {
+    from{
+      opacity: 0.3;
+    }
+  }
+</style>

+ 77 - 0
src/components/3DScene/infoBox.js

@@ -0,0 +1,77 @@
+ const getBox1 = (text,_this) => {
+  let container =  document.createElement("div");
+  container.style.width = '300px';
+  container.style.height = '120px';
+
+  let textDiv =  document.createElement("div");
+  textDiv.style.cssText= 'width: 300px;height: 38px;background: url(/data/popupLbl.png) no-repeat;position:absolute;bottom: 60px;left: 150px;';
+  container.appendChild(textDiv);
+
+  let dataDiv = document.createElement("div");
+  dataDiv.style.cssText = `position: absolute;bottom: 98px;left: 170px;background: rgba(0,0,0,0.5);width: 280px;height: 50px;color: white;font-style: 20px;font-weight: bold;
+        box-shadow: 2px 2px 2px black;letter-spacing: 2px;cursor: pointer;
+      `
+  dataDiv.className = "toTop center linkHover enterBuilding"
+  dataDiv.onclick = ()=>{
+    switch (text){
+      case '软件园主楼':
+        _this.$router.push({path: "/park/floorView"})
+        break;
+      default: ;
+    }
+  }
+  dataDiv.innerHTML = text;
+  container.appendChild(dataDiv);
+  return container;
+}
+
+
+ const getCameraBox = (item,_this) => {
+   let container =  document.createElement("div");
+   container.style.cssText = `width: 300px;height: 200px;`
+   let mainContainer = document.createElement('div');
+   mainContainer.className= "full";
+   mainContainer.style.cssText = 'position: absolute;left: 150px;bottom: 100px;'
+   container.appendChild(mainContainer);
+   let bottomLine = document.createElement('div');
+   bottomLine.style.cssText = 'position: absolute;height: 1px;width:20px;background: white;bottom:0'
+   let middleLine = document.createElement('div');
+   middleLine.style.cssText = 'position: absolute;height: 100px;width:1px;background: white;bottom:0;left: 20px'
+
+
+   let topLine  = document.createElement('div');
+   topLine.style.cssText = 'position: absolute;height: 1px;width:200px;background: white;bottom:100px;left: 20px;box-shadow: 2px 2px 2px white';
+
+   let topLine2  = document.createElement('div');
+   topLine2.style.cssText = 'position: absolute;height: 1px;width:180px;background: white;bottom:90px;left: 30px;box-shadow: 2px 2px 2px white';
+
+   let icon  = document.createElement('div');
+   icon.style.cssText = 'position: absolute;height: 50px;width:50px;bottom:102px;left: 20px;background: url(/img/camera.png) no-repeat center/70%';
+   icon.className = 'toRight'
+
+   let no  = document.createElement('div');
+   no.style.cssText = 'position: absolute;height: 50px;width:150px;bottom:102px;left: 70px;color: #dddddd;letter-spacing: 2px;background: linear-gradient(to top,rgba(0,0,0,0.7),transparent);';
+   no.className = 'center toRight linkHover'
+   no.innerHTML = item.number;
+
+
+   let videoContainer = document.createElement('video');
+   videoContainer.style.cssText = `width: 300px;height: 250px;position:absolute;bottom: 140px;display: none`;
+   videoContainer.setAttribute("src",item.src);
+   videoContainer.setAttribute("controls",'controls');
+   videoContainer.autoplay = true;
+   videoContainer.loop = true;
+   videoContainer.muted = true;
+   // videoContainer.className = 'toBottom'
+   videoContainer.onbeforeunload = () => {
+     videoContainer.style.display = 'none'
+   }
+   no.onclick = () => {
+      videoContainer.style.display = videoContainer.style.display=='none'? 'inline':'none' ;
+   }
+
+   mainContainer.append(bottomLine,middleLine,topLine,icon,topLine2,no,videoContainer);
+   return container;
+ }
+
+export { getBox1,getCameraBox}

+ 230 - 0
src/components/3DScene/loading/loading.vue

@@ -0,0 +1,230 @@
+<template>
+  <div v-if="show"  class=" loadingContainer full center">
+<!--    <div class="logo"></div>-->
+    <h1>{{text}}</h1>
+    <h5>加载中 <span v-if="typeof current != 'undefined' && typeof total != 'undefined'">{{current}}/{{total}}</span></h5>
+<!--    <div v-if="typeof schedule != 'undefined'" class="process-bar"></div>-->
+    <div  class="process-bar">
+      <div class="full bar" :style="{width: `${schedule}%`}"></div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "loading",
+  data(){
+    return {
+      show: false,
+    }
+  },
+  methods: {
+    showLoading(){
+      this.show = true;
+    },
+   async hide(){
+      return new Promise((resolve, reject) => {
+        setTimeout(()=>{
+          this.show = false;
+          resolve();
+        },1500)
+      })
+    },
+  },
+  props: {
+    text: {type: 'String'},
+    logo: {type: 'String'},
+    schedule: {type: 'String'},
+    total: {type: 'Number'},
+    current: {type: 'Number'},
+  }
+}
+</script>
+
+<style scoped>
+  .loadingContainer{
+    /*animation: fade-out 1s ease-out both;*/
+    user-select: none;
+    position: absolute;
+    flex-direction: column;
+    top: 0;
+    right: 0;
+    background: rgba(0,0,0,0.9);
+    z-index: 99999
+  }
+  h1 {
+    color: hsla(0,0%,100%,.8);
+    font: 900 800% Baskerville, 'Palatino Linotype', Palatino, serif;
+    animation: fade-in 2s linear  both;
+    text-shadow: 0 0 10px rgba(255, 255, 255, 0.6), 0 0 25px rgba(255, 255, 255, 0.45), 0 0 50px rgba(255, 255, 255, 0.25), 0 0 25px rgba(255, 255, 255, 0.1);
+  }
+
+  @keyframes text-flicker-in-glow {
+    0% {
+      opacity: 0;
+    }
+    10% {
+      opacity: 0;
+      text-shadow: none;
+    }
+    10.1% {
+      opacity: 1;
+      text-shadow: none;
+    }
+    10.2% {
+      opacity: 0;
+      text-shadow: none;
+    }
+    20% {
+      opacity: 0;
+      text-shadow: none;
+    }
+    20.1% {
+      opacity: 1;
+      text-shadow: 0 0 30px rgba(255, 255, 255, 0.25);
+    }
+    20.6% {
+      opacity: 0;
+      text-shadow: none;
+    }
+    30% {
+      opacity: 0;
+      text-shadow: none;
+    }
+    30.1% {
+      opacity: 1;
+      text-shadow: 0 0 30px rgba(255, 255, 255, 0.45), 0 0 60px rgba(255, 255, 255, 0.25);
+    }
+    30.5% {
+      opacity: 1;
+      text-shadow: 0 0 30px rgba(255, 255, 255, 0.45), 0 0 60px rgba(255, 255, 255, 0.25);
+    }
+    30.6% {
+      opacity: 0;
+      text-shadow: none;
+    }
+    45% {
+      opacity: 0;
+      text-shadow: none;
+    }
+    45.1% {
+      opacity: 1;
+      text-shadow: 0 0 30px rgba(255, 255, 255, 0.45), 0 0 60px rgba(255, 255, 255, 0.25);
+    }
+    50% {
+      opacity: 1;
+      text-shadow: 0 0 30px rgba(255, 255, 255, 0.45), 0 0 60px rgba(255, 255, 255, 0.25);
+    }
+    55% {
+      opacity: 1;
+      text-shadow: 0 0 30px rgba(255, 255, 255, 0.45), 0 0 60px rgba(255, 255, 255, 0.25);
+    }
+    55.1% {
+      opacity: 0;
+      text-shadow: none;
+    }
+    57% {
+      opacity: 0;
+      text-shadow: none;
+    }
+    57.1% {
+      opacity: 1;
+      text-shadow: 0 0 30px rgba(255, 255, 255, 0.55), 0 0 60px rgba(255, 255, 255, 0.35);
+    }
+    60% {
+      opacity: 1;
+      text-shadow: 0 0 30px rgba(255, 255, 255, 0.55), 0 0 60px rgba(255, 255, 255, 0.35);
+    }
+    60.1% {
+      opacity: 0;
+      text-shadow: none;
+    }
+    65% {
+      opacity: 0;
+      text-shadow: none;
+    }
+    65.1% {
+      opacity: 1;
+      text-shadow: 0 0 30px rgba(255, 255, 255, 0.55), 0 0 60px rgba(255, 255, 255, 0.35), 0 0 100px rgba(255, 255, 255, 0.1);
+    }
+    75% {
+      opacity: 1;
+      text-shadow: 0 0 30px rgba(255, 255, 255, 0.55), 0 0 60px rgba(255, 255, 255, 0.35), 0 0 100px rgba(255, 255, 255, 0.1);
+    }
+    75.1% {
+      opacity: 0;
+      text-shadow: none;
+    }
+    77% {
+      opacity: 0;
+      text-shadow: none;
+    }
+    77.1% {
+      opacity: 1;
+      text-shadow: 0 0 30px rgba(255, 255, 255, 0.55), 0 0 60px rgba(255, 255, 255, 0.4), 0 0 110px rgba(255, 255, 255, 0.2), 0 0 100px rgba(255, 255, 255, 0.1);
+    }
+    85% {
+      opacity: 1;
+      text-shadow: 0 0 30px rgba(255, 255, 255, 0.55), 0 0 60px rgba(255, 255, 255, 0.4), 0 0 110px rgba(255, 255, 255, 0.2), 0 0 100px rgba(255, 255, 255, 0.1);
+    }
+    85.1% {
+      opacity: 0;
+      text-shadow: none;
+    }
+    86% {
+      opacity: 0;
+      text-shadow: none;
+    }
+    86.1% {
+      opacity: 1;
+      text-shadow: 0 0 30px rgba(255, 255, 255, 0.6), 0 0 60px rgba(255, 255, 255, 0.45), 0 0 110px rgba(255, 255, 255, 0.25), 0 0 100px rgba(255, 255, 255, 0.1);
+    }
+    100% {
+      opacity: 1;
+      text-shadow: 0 0 30px rgba(255, 255, 255, 0.6), 0 0 60px rgba(255, 255, 255, 0.45), 0 0 110px rgba(255, 255, 255, 0.25), 0 0 100px rgba(255, 255, 255, 0.1);
+    }
+  }
+  .process-bar{
+    width: 700px;
+    height: 15px;
+    border-radius: 10px;
+
+    /*border: 1px #d2d2d2 solid;*/
+  }
+  h5{
+    font-size: 25px;
+    color: #dddddd;
+    font-family: Baskerville, 'Palatino Linotype', Palatino, serif;
+    text-shadow: 2px 2px 2px white;
+    font-style: italic;
+    letter-spacing: 5px;
+    font-weight: 100;
+  }
+  .bar{
+    border-radius: 10px;
+    height: 1px;
+    margin: 2px 0;
+    background: #2187e7;
+    box-shadow: 0px 0px 10px 2px rgb(0 198 255 / 40%);
+    transition: width 1s;
+    /*animation: process-bar 2s ease-in-out;*/
+  }
+  @keyframes process-bar {
+    0% { background-position: 0 0; }
+    100% { background-position: 20px 0; }
+  }
+  @keyframes fade-in {
+    0% {
+      opacity: 0;
+    }
+    100% {
+      opacity: 1;
+    }
+  }
+  .logo{
+    width: 800px;
+    height: 300px;
+    background: #00bdff;
+    background: url("/logo.png") no-repeat center/contain;
+  }
+</style>

BIN=BIN
src/components/3DScene/logo.png


+ 251 - 0
src/components/3DScene/map.vue

@@ -0,0 +1,251 @@
+<template>
+  <div class="slide-in-top"  v-if="infoShow" style="width: 300px;height: 250px;position: absolute;"  :style="{
+    left: infoPosition.x-150 + 'px',
+    top: infoPosition.y-265 + 'px',
+  }">
+    <div  style="height: 200px;width: 100%;">
+      <dv-border-box-7 class="full" :color="['rgba(0, 0, 0, 0.3)', '#0dcbec']" backgroundColor="rgba(0, 0, 0, 0.3)">
+        <div class="full text-blur-out">
+          <div style="width: 100%;height: 50px;color: #00ffe2;font-weight: bold;letter-spacing: 20px" class="center">{{infoProperties.name}}</div>
+          <div style="width: 100%;height: 200px">
+            <slot></slot>
+          </div>
+        </div>
+      </dv-border-box-7>
+    </div>
+    <div class="line"></div>
+    <div class="point"></div>
+  </div>
+</template>
+
+<script>
+import * as d3 from "d3";
+import * as Three from "three";
+
+export default {
+  name: "mapForScene",
+  inject: ['scene'],
+  data(){
+    return {
+      infoShow: false,
+      infoPosition: {x:0,y:0},
+      mapCenterPosition: [106.292249,37.206647],
+      scale: 8000,
+      infoProperties: {},
+      raycaster: null,
+      mouse: {},
+      map: {},
+      onClickList: [],
+      eventOffset: {x:0,y:0},
+      activeInstersect: [],
+      isInit: false,
+    }
+  },
+  methods: {
+    onLoad(){
+      this.raycaster = new Three.Raycaster();
+      this.mouse = new Three.Vector2();
+      this.eventOffset = {};
+      const _this = this;
+      window.addEventListener( 'mousemove', this.onMouseMove, false );
+      window.addEventListener( 'click', this.onMouseClick, false );
+      this.isInit = true;
+      this.addPoints();
+       // this.addPoints();
+    },
+    //三维坐标转换屏幕坐标
+    transPosition(position=[0,0,0]){
+      let world_vector = new Three.Vector3(position[0],position[1],position[2]);
+      let vector =world_vector.project(this.scene.camera);
+      let halfWidth =  window.innerWidth / 2,
+        halfHeight =  window.innerHeight / 2;
+      return [
+        Math.round(vector.x * halfWidth + halfWidth),
+        Math.round(-vector.y * halfHeight + halfHeight)
+      ];
+    },
+    //添加地图
+    async addMap(url){
+      const _this =this;
+      let json = await this.scene.addJson(url)
+      // 墨卡托投影转换
+      const projection = d3.geoMercator().center(this.mapCenterPosition).scale(this.scale).translate([0, 0]);
+      //地图对象模型(省份)
+      let map = new Three.Object3D();
+      this.map = map;
+      json['features'].forEach(city => {
+        let City = new Three.Object3D();
+        city['geometry']['coordinates'].forEach(polygons => {
+          polygons.forEach(polygon => {
+            const shape = new Three.Shape();
+            let linePoints = [];
+            polygon.forEach((position,index) => {
+              const [x,y] = projection(position)
+              if(index==0){
+                shape.moveTo(x,y);
+              }
+              shape.lineTo(x,y);   //添加区域定点
+              linePoints.push(x,y);  //添加区域边界顶点
+            })
+
+            const extrudeSettings = {
+              depth: 20,
+              bevelEnabled: false
+            };
+
+            //构建区域模型
+            const geometry = new Three.ExtrudeGeometry(shape, extrudeSettings)
+            const material = new Three.MeshBasicMaterial({ color: '#02A1E2', transparent: true, opacity: 0.6 })
+            const material1 = new Three.MeshBasicMaterial({ color: '#3480c4', transparent: true, opacity: 0.6 })
+            const mesh = new Three.Mesh(geometry, [material, material1])
+
+            //构建区域边界模型
+            const lineGeometry = new Three.BufferGeometry();
+            lineGeometry.attributes.position = new Three.BufferAttribute(new Float32Array(linePoints),2);
+            const lineMaterial = new Three.LineBasicMaterial({ color: '#02A1E2' });
+            const line = new Three.Line(lineGeometry, lineMaterial)
+
+            City.add(mesh);
+            City.add(line);
+          })
+        })
+        //添加区域属性(GeoJson文件所包含的属性)
+        City.properties = city.properties;
+        if (city.properties.contorid) {
+          const [x, y] = projection(city.properties.contorid);
+          City.properties._centroid = [x, y];
+        }
+        // _this.scene.box3.push({
+        //   position: city.properties.center,
+        //   data: city.properties.name,
+        //   areaCode: city.properties.adcode,
+        //   x:0,
+        //   y:0
+        // })
+        map.add(City)
+      })
+      _this.scene.scene.add(map)
+    },
+    //坐标渲染
+    renderPosition(){
+      const projection = d3.geoMercator().center(this.mapCenterPosition).scale(this.scale).translate([0, 0]);
+      this.scene.box3.forEach(item => {
+        const [x,y] =  projection(item.position);
+        const [x2,y2] = this.transPosition([x,y,0]);
+        item.x =x2;
+        item.y = y2;
+      })
+    },
+    //地图添加圆点标记
+    addPoints(positionList=[[106.292249,38.206647],[106.492249,37.206647],[107.292249,37.206647]]){
+      const projection = d3.geoMercator().center(this.mapCenterPosition).scale(this.scale).translate([0, 0]);
+      let points = [];
+      let normals = [];
+      positionList.forEach(item => {
+        const [x,y] =  projection(item);
+        points.push(x,y);
+        normals.push(0,0,1);
+      })
+       let geometry = new Three.BufferGeometry();
+       let textureMap = new Three.TextureLoader().load( "/data/point.png");
+       geometry.attributes.position = new Three.BufferAttribute(new Float32Array(points), 2);
+      geometry.attributes.normal = new Three.BufferAttribute(new Float32Array(normals), 3);
+       let material = new Three.PointsMaterial({
+        wireframe: true,
+        map: textureMap,
+       color: 0xff000,
+       size: 100.0,
+         transparent: true,
+       });
+     let mesh  = new Three.Points(geometry, material);
+     this.scene.scene.add(mesh);
+    },
+    //鼠标移动事件
+    onMouseMove(event) {
+      const _this =this;
+      _this.mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
+      _this.mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
+      _this.eventOffset.x = event.clientX;
+      _this.eventOffset.y = event.clientY;
+      _this.infoPosition.x = _this.eventOffset.x + 2 ;
+      _this.infoPosition.y = _this.eventOffset.y + 2 ;
+    },
+    //鼠标单击事件
+    onMouseClick(){
+      this.onClickList.forEach(item => {
+        item(this.infoProperties);
+      })
+    },
+    //判断鼠标指向(渲染鼠标指向实体)
+    animate() {
+      if(this.isInit){
+        this.raycaster.setFromCamera( this.mouse, this.scene.camera );
+        if(this.map) {
+          let intersects = this.raycaster.intersectObjects(this.map.children, true);
+          if (this.activeInstersect && this.activeInstersect.length > 0) {
+            this.activeInstersect.forEach(element => {
+              element.object.material[0].color.set('#02A1E2');
+              element.object.material[1].color.set('#02A1E2');
+            });
+          }
+          this.activeInstersect = [];
+          for (let i = 0; i < intersects.length; i++) {
+            if (intersects[i].object.material && intersects[i].object.material.length === 2) {
+              this.activeInstersect.push(intersects[i]);
+              intersects[i].object.material[0].color.set('#34ead5');
+              intersects[i].object.material[1].color.set('#34ead5');
+              intersects[i].object.geometry.parameters.options.depth = Math.random() * 10;
+              intersects[i].object.geometry.needsUpdate = true;
+              break; // 只取第一个
+            }
+          }
+          this.createCityInfo();
+        }
+      }
+    },
+    //添加鼠标点击事件
+    onClick(f){
+      this.onClickList.push(f);
+    },
+    //信息框配置
+    createCityInfo() { // 显示省份的信息
+      if (this.activeInstersect.length !== 0 && this.activeInstersect[0].object.parent.properties.name) {
+        this.infoProperties =  this.activeInstersect[0].object.parent.properties;
+        this.infoShow = true;
+      } else {
+        this.infoShow = false;
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+  .line{
+    width: 1px;
+    height: 48px;
+    margin: auto;
+    background: linear-gradient(to top,rgba(0,0,0,1),rgba(0,0,0,0.5));
+  }
+  .point{
+    width: 10px;
+    height: 10px;
+    margin: auto;
+    border-radius: 50%;background: radial-gradient(#00ffe2,#004480,#02004a)
+  }
+  .full{
+    width: 100%;
+    height: 100%;
+  }
+  .center{
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+  .slide-in-top{animation:slide-in-top .5s cubic-bezier(.25,.46,.45,.94) both}
+  @keyframes slide-in-top{0%{transform:translateY(-200px);opacity:0}100%{transform:translateY(0);opacity:1}}
+  @keyframes slide-in-elliptic-top-fwd{0%{transform:translateY(-600px) rotateX(-30deg) scale(0);transform-origin:50% 100%;opacity:0}100%{transform:translateY(0) rotateX(0) scale(1);transform-origin:50% 1400px;opacity:1}}
+
+  .text-blur-out{animation:text-blur-out 0.3s cubic-bezier(.55,.085,.68,.53) both}
+  @keyframes text-blur-out{0%{filter:blur(12px) opacity(0)}100%{filter:blur(.01)}}
+</style>

+ 89 - 0
src/components/3DScene/tools-bar/tools-bar.vue

@@ -0,0 +1,89 @@
+<template>
+  <div class="bars-container toLeft" :style="{    top: `calc(10% + ${offsetY}px)`,
+    right:` calc(24% + ${offsetX}px)`}">
+    <div @click="isOpen=!isOpen" class=" button-close center" draggable="false">
+      <div :key="item" v-for="item of 3" class="point" ></div>
+    </div>
+    <div class="contain-main" style="width: 100%;" :style="{height: isOpen? '460px':'0px'}">
+      <div :key="item" v-for="item of option" @click="item.event" class="click" style="width: 100%;height: 70px;" :style="{background: `url(${item.icon}) no-repeat center/50%`}"></div>
+    </div>
+    <div @click="isOpen=!isOpen" style="width: 100%;height: 20px;background: rgba(0,0,0,0.5)" class="center"> <div class="arrow full"
+      :style="{transform: `rotate(${isOpen? 0:180}deg)`}"
+    ></div> </div>
+  </div>
+</template>
+
+<script>
+import {fullscreenToggel} from "../../../util/util";
+
+export default {
+  name: "tools-bar",
+  inject: [ 'index'],
+  props: {
+    option: {
+      type: 'Array',
+      default: []
+    }
+  },
+  mounted() {
+
+  },
+  data(){
+    return{
+      isOpen: true,
+      offsetX: 0,
+      offsetY: 0,
+    }
+  },
+  methods: {
+
+    ondrop(e){
+      // this.offsetX = e.offsetX;
+      // this.offsetY = e.offsetY;
+      // console.log(this.offsetX,this.offsetY)
+    },
+  }
+}
+</script>
+
+<style scoped>
+  .bars-container{
+    user-select: none;
+    width: 70px;
+    height: auto;
+    position: absolute;
+    background: rgba(0,0,0,0.5);
+    box-shadow: 2px 2px 2px #131313;
+    z-index: 999;
+  }
+  .button-close{
+    cursor: pointer;
+    width: 100%;
+    height: 20px;
+    background: rgba(0,0,0,0.5);
+  }
+  .point{
+    width: 10px;
+    height: 10px;
+    margin-left: 5px;
+    background: white;
+    box-shadow: 2px 2px 2px #393939;
+    border-radius: 50%;
+  }
+  .arrow{
+    cursor: pointer;
+    background: url("/img/arrow-up.png") no-repeat center/contain;
+    /*transform: rotate(180deg);*/
+    transition: transform 1s;
+  }
+  .click{
+    cursor: pointer;
+  }
+  .click:hover{
+    transform: translate(1px,1px);
+  }
+  .contain-main{
+    transition: height 1s;
+    overflow: hidden;
+  }
+</style>

+ 219 - 0
src/components/3DScene/top-bar/top-bar.vue

@@ -0,0 +1,219 @@
+<template>
+  <div class="container toBottom center">
+    <!--  名称  -->
+    <div class="center" style="width: 18%;height: 100%">
+      <div class="name center" style="flex-direction: column">
+        {{title}}
+        <span style="font-size: 17px;letter-spacing: 2px;font-weight: normal;font-style: italic;color: #dddddd">Xinyang Rongji soft Garden</span>
+      </div>
+    </div>
+
+    <!--  时间日期  -->
+    <div style="width: 10%;height: 100%" class="center">
+      <div class="time-date">
+        <div class="full center" style="height: 30%;font-style: italic">{{dateTime.date}} {{weekStr[dateTime.week]}}</div>
+        <div class="full center" style="height: 70%;font-size: 35px">{{dateTime.time}}</div>
+      </div>
+    </div>
+
+    <div class="center" style="width:60%;height: 100%;justify-content: right">
+      <el-page-header v-if="showBack" @back="$router.go(-1)" content="楼栋详情">
+      </el-page-header>
+    </div>
+
+    <!-- 天气  -->
+    <div class="center" style="width:7%;height: 100%;">
+      <div class="weather">
+        <div class="full center" style="height: 75%;font-size: 35px;font-weight: 100">{{weather.dayWeather}}</div>
+        <div class="full center" style="height: 25%;font-style: italic;color: #dddddd">{{weather.nightTemp+'℃ ~' + weather.dayTemp+'℃'}}</div>
+      </div>
+    </div>
+
+    <!-- 开关   -->
+    <div @click="logout" style="width:5%;height: 100%" class="switch-bg">
+      <div class="switch full"></div>
+    </div>
+  </div>
+</template>
+
+<script>
+import {dateFormat} from "../../../util/date";
+import {fullscreenToggel} from "../../../util/util";
+import {resetRouter} from "../../../router/router";
+
+export default {
+  name: "top-bar",
+  async mounted() {
+    this.currentTime();
+    await this.getWeather();
+  },
+  props: {
+    title: {type: 'String'},
+    showBack: {type: 'Boolean',default: false}
+  },
+  data(){
+    return{
+      dateTime: {},
+      weekStr: ['星期天','星期一','星期二','星期三','星期四','星期五','星期六',],
+      temperature: '',
+      weather: '',
+      weatherClass: 'duoyun',
+    }
+  },
+  watch: {
+    weather(){
+      this.weatherClass = this.getWeatherClass(this.weather);
+    },
+  },
+  methods: {
+    back(){
+      this.$router.go(-1);
+    },
+    logout() {
+      this.$confirm('退出系统, 是否继续?', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+      }).then(() => {
+        this.$store.dispatch("LogOut").then(() => {
+          resetRouter();
+          this.$router.push({path: "/login"});
+        });
+      });
+    },
+    currentTime() {
+      setInterval(this.getTime, 500);
+    },
+    getTime() {
+      let date = new Date();
+      let str = dateFormat(date);
+      let week = date.getDay();
+      str = str.split(" ");
+      this.dateTime = {
+        week: week,
+        date: str[0],
+        time: str[1]
+      }
+    },
+    getWeatherClass(weather) {
+      let duoyun = ['少云', '晴间多云', '多云', '阴'];
+      let wu = ['雾', '浓雾', '强浓雾', '轻雾', '大雾', '特强浓雾'];
+      let mai = ['霾', '中度霾', '重度霾', '严重霾'];
+      let feng = ['有风', '平静', '微风', '和风', '清风', '强风/劲风', , '疾风', '大风', '烈风', '风暴', '狂爆风', '飓风', '热带风暴'];
+      let yu = ['阵雨', '雷阵雨', '雷阵雨并伴有冰雹', '小雨', '中雨', '大雨', '暴雨', '大暴雨', '特大暴雨', '强阵雨', '强雷阵雨', '极端降雨', '毛毛雨/细雨', '雨', '小雨-中雨', '中雨-大雨', '大雨-暴雨', '暴雨-大暴雨', '大暴雨-特大暴雨', '雨雪天气', '雨夹雪', '阵雨夹雪', '冻雨'];
+      let sun = ['晴'];
+      let xue = ['雪', '阵雪', '小雪', '中雪', '大雪', '暴雪', '小雪-中雪', '中雪-大雪', '大雪-暴雪'];
+      if (duoyun.indexOf(weather) >= 0) return 'duoyun';
+      if (wu.indexOf(weather) >= 0) return 'wu';
+      if (yu.indexOf(weather) >= 0) return 'yu';
+      if (sun.indexOf(weather) >= 0) return 'sun';
+      if (xue.indexOf(weather) >= 0) return 'xue';
+      if (mai.indexOf(weather) >= 0) return 'mai';
+      if (feng.indexOf(weather) >= 0) return 'feng';
+      return 'duoyun';
+    },
+    async getWeather() {
+      console.log(this.$map)
+      let weather = new this.$AMap.Weather();
+      //执行实时天气信息查询
+      weather.getForecast('信阳市', (err, data) => {
+        this.weather = data.forecasts[0];
+      });
+    },
+  }
+}
+</script>
+
+<style scoped>
+  .container{
+    width: 100%;
+    height: 7%;
+    position: absolute;
+    top: 0;
+    overflow: hidden;
+    background: rgba(0,0,0,0.7);
+  }
+  .name{
+    width: 100%;
+    height: 70%;
+    letter-spacing: 10px;
+    font-size: 30px;
+    font-weight: 900;
+    border-right: 1px solid;
+    border-right-color: rgba(255,255,255,0.8);
+    color: white;
+    user-select: none;
+    text-shadow: 0 0 18px rgba(0, 0, 0, 0.35);
+  }
+  .time-date{
+    width: 100%;
+    height: 70%;
+    color: white;
+    font-size: 20px;
+    user-select: none;
+    text-shadow: 0 0 18px rgba(0, 0, 0, 0.35);
+  }
+  .weather{
+    width: 100%;
+    height: 70%;
+    color: white;
+    font-size: 20px;
+    user-select: none;
+    text-shadow: 0 0 30px rgba(255, 255, 255, 0.6), 0 0 60px rgba(255, 255, 255, 0.45), 0 0 110px rgba(255, 255, 255, 0.25), 0 0 100px rgba(255, 255, 255, 0.1);
+  }
+  .switch{
+    background: url("/data/switch.png") no-repeat center/30%;
+  }
+  .switch-bg:active,.switch-bg:hover{
+    cursor: pointer;
+    background: rgba(0,0,0,0.5);
+    box-shadow: 0 0 20px 0px rgba(0, 0, 0, 0.35);
+    text-shadow: 0 0 30px rgba(255, 255, 255, 0.6), 0 0 60px rgba(255, 255, 255, 0.45), 0 0 110px rgba(255, 255, 255, 0.25), 0 0 100px rgba(255, 255, 255, 0.1);
+  }
+  .switch-bg:active{
+    transform: translate(-1px,1px);
+  }
+  @keyframes shake-horizontal {
+    0%,
+    100% {
+      -webkit-transform: translateX(0);
+      transform: translateX(0);
+    }
+    10%,
+    30%,
+    50%,
+    70% {
+      -webkit-transform: translateX(-10px);
+      transform: translateX(-10px);
+    }
+    20%,
+    40%,
+    60% {
+      -webkit-transform: translateX(10px);
+      transform: translateX(10px);
+    }
+    80% {
+      -webkit-transform: translateX(8px);
+      transform: translateX(8px);
+    }
+    90% {
+      -webkit-transform: translateX(-8px);
+      transform: translateX(-8px);
+    }
+  }
+ /deep/ .el-page-header__title {
+    color: white;
+    font-size: 24px;
+    font-weight: 500;
+  }
+ /deep/ .el-page-header__content {
+   font-size: 18px;
+   color: #acacac;
+ }
+  /deep/ .el-page-header__left .el-icon-back {
+    font-size: 18px;
+    color: white;
+    margin-right: 6px;
+    -ms-flex-item-align: center;
+    align-self: center;
+  }
+</style>