|
@@ -1,165 +1,258 @@
|
|
|
-<!-- 测试demo -->
|
|
|
<template>
|
|
|
- <div class="demo7">
|
|
|
- <div class="map-max-big-box ">
|
|
|
- <div class="map-box">
|
|
|
- <canvasMap ref="canvasMap" :alarmPropsList="alarmPropsList"></canvasMap>
|
|
|
+ <div class="container">
|
|
|
+ <div ref="canvasContainer" class="canvas-container"></div>
|
|
|
+ <div class="buttons-container">
|
|
|
+ <div
|
|
|
+ v-for="house in houses"
|
|
|
+ :key="house.uuid"
|
|
|
+ class="house-button"
|
|
|
+ :style="{
|
|
|
+ left: `${house.screenPosition.x}px`,
|
|
|
+ top: `${house.screenPosition.y}px`,
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <button @click="changeMaterial(house)">更换材质</button>
|
|
|
+ <button @click="changeTexture(house)">更换贴图</button>
|
|
|
+ <button @click="restoreOriginalMaterial(house)">恢复材质</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
<script>
|
|
|
- import mqtt from 'mqtt'
|
|
|
- import {
|
|
|
- laboratoryBigViewSelectTriggerInfo,
|
|
|
- } from "@/api/index";
|
|
|
- //地图模型组件
|
|
|
- import canvasMap from "@/views/cengterMaxBox/canvasMap/index.vue";
|
|
|
+ import * as THREE from 'three';
|
|
|
+ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
|
|
+ import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
|
|
|
+
|
|
|
+ import TWEEN from 'tween.js';
|
|
|
+ import { CSS2DRenderer, CSS2DObject, } from "three/examples/jsm/renderers/CSS2DRenderer";
|
|
|
export default {
|
|
|
- name: 'demo7',
|
|
|
- components: {
|
|
|
- //地图模型组件
|
|
|
- canvasMap,
|
|
|
- },
|
|
|
- data () {
|
|
|
+ data() {
|
|
|
return {
|
|
|
- //预案MQTT
|
|
|
- planOpic:'lab/risk/plan/change',
|
|
|
- planClient:{},
|
|
|
- //预案数据
|
|
|
- alarmPropsList:[],
|
|
|
- alarmPropsData:{},
|
|
|
- pageType:1,
|
|
|
- audioType:false,
|
|
|
- alarmTitle:'',
|
|
|
- //南北校区相关
|
|
|
-
|
|
|
- }
|
|
|
- },
|
|
|
- created () {
|
|
|
+ houses: [], // 存储每个房子的信息
|
|
|
+ scene: null,
|
|
|
+ camera: null,
|
|
|
+ renderer: null,
|
|
|
+ controls: null,
|
|
|
+ materials: [], // 预加载的材质选项
|
|
|
+ textures: [], // 预加载的贴图选项
|
|
|
|
|
|
+ subModels: [],
|
|
|
+ labelRenderer:null,
|
|
|
+ initialCameraPosition: new THREE.Vector3(0.5, 1, 0.5),//旋转-距离-俯角
|
|
|
+ initialControlsTarget: new THREE.Vector3(0, 0, 0),
|
|
|
+ };
|
|
|
},
|
|
|
- mounted () {
|
|
|
- //判断是否已有预警MQTT链接
|
|
|
- if(!this.planClient.unsubscribe){
|
|
|
- this.offPlanMQTT('on');
|
|
|
- }
|
|
|
- this.laboratoryBigViewSelectTriggerInfo();
|
|
|
+ mounted() {
|
|
|
+ this.initThree();
|
|
|
+ this.loadModel();
|
|
|
+ this.loadAssets();
|
|
|
+ this.animate();
|
|
|
},
|
|
|
methods: {
|
|
|
- //预案-MQTT连接
|
|
|
- offPlanMQTT(type){
|
|
|
- let self = this;
|
|
|
- if(self.planClient.unsubscribe){
|
|
|
- self.planClient.unsubscribe(self.planOpic, error => {
|
|
|
- if (error) {
|
|
|
- // console.log('mqtt关闭连接错误:', error)
|
|
|
- }
|
|
|
- })
|
|
|
- self.planClient.end();
|
|
|
- this.$set(this,'planClient',{});
|
|
|
- }
|
|
|
- //判断传入参数如果存在 发起一次新的连接
|
|
|
- if(type){
|
|
|
- this.planMQTT();
|
|
|
- }
|
|
|
+ initThree() {
|
|
|
+ // 初始化场景
|
|
|
+ this.scene = new THREE.Scene();
|
|
|
+ this.scene.background = new THREE.Color(0xdddddd);
|
|
|
+
|
|
|
+ // // 初始化相机
|
|
|
+ // this.camera = new THREE.PerspectiveCamera(
|
|
|
+ // 75,
|
|
|
+ // window.innerWidth / window.innerHeight,
|
|
|
+ // 0.1,
|
|
|
+ // 1000
|
|
|
+ // );
|
|
|
+ // this.camera.position.set(0.5, 0.5, 0.5);
|
|
|
+
|
|
|
+ // 初始化渲染器
|
|
|
+ this.renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
|
+ this.renderer.setSize(window.innerWidth, window.innerHeight);
|
|
|
+ this.$refs.canvasContainer.appendChild(this.renderer.domElement);
|
|
|
+
|
|
|
+ // 初始化相机时使用存储的位置
|
|
|
+ this.camera = new THREE.PerspectiveCamera(
|
|
|
+ 75,
|
|
|
+ window.innerWidth / window.innerHeight,
|
|
|
+ 0.1,
|
|
|
+ 1000
|
|
|
+ );
|
|
|
+ this.camera.position.copy(this.initialCameraPosition);
|
|
|
+
|
|
|
+ // 初始化控制器时存储初始目标
|
|
|
+ this.controls = new OrbitControls(this.camera, this.renderer.domElement);
|
|
|
+ this.controls.target.copy(this.initialControlsTarget);
|
|
|
+ // ...其他初始化代码保持不变...
|
|
|
+
|
|
|
+
|
|
|
+ this.labelRenderer = new CSS2DRenderer();
|
|
|
+ this.labelRenderer.setSize(
|
|
|
+ this.$refs.canvasContainer.clientWidth,
|
|
|
+ this.$refs.canvasContainer.clientHeight
|
|
|
+ );
|
|
|
+ this.labelRenderer.domElement.style.position = 'absolute';
|
|
|
+ this.labelRenderer.domElement.style.top = '0';
|
|
|
+ this.labelRenderer.domElement.style.pointerEvents = 'none';
|
|
|
+ this.$refs.canvasContainer.appendChild(this.labelRenderer.domElement);
|
|
|
+
|
|
|
+ // 初始化控制器
|
|
|
+ this.controls = new OrbitControls(this.camera, this.renderer.domElement);
|
|
|
+ this.controls.enableDamping = true;
|
|
|
+ this.controls.dampingFactor = 0.05;
|
|
|
+
|
|
|
+ // 添加灯光
|
|
|
+ const ambientLight = new THREE.AmbientLight(0xffffff, 1);
|
|
|
+ this.scene.add(ambientLight);
|
|
|
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
|
|
|
+ directionalLight.position.set(0, 5, 5);
|
|
|
+ this.scene.add(directionalLight);
|
|
|
+
|
|
|
+ // 启动动画循环
|
|
|
+ this.animate();
|
|
|
},
|
|
|
- //预案-MQTT订阅
|
|
|
- planMQTT(){
|
|
|
- let self = this;
|
|
|
- this.planClient = mqtt.connect(localStorage.getItem('mqttUrl'), {
|
|
|
- username: localStorage.getItem('mqttUser'),
|
|
|
- password:localStorage.getItem('mqttPassword')
|
|
|
+
|
|
|
+ async loadModel() {
|
|
|
+ const loader = new GLTFLoader();
|
|
|
+ const gltf = await loader.loadAsync('/models/demo2.glb');
|
|
|
+
|
|
|
+ // 递归查找所有Group
|
|
|
+ const traverseGroups = (obj) => {
|
|
|
+ if (obj.isGroup) {
|
|
|
+ this.registerHouse(obj);
|
|
|
+ }
|
|
|
+ obj.children.forEach(child => traverseGroups(child));
|
|
|
+ };
|
|
|
+
|
|
|
+ traverseGroups(gltf.scene);
|
|
|
+ this.scene.add(gltf.scene);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 修改后的 registerHouse 方法
|
|
|
+ registerHouse(group) {
|
|
|
+ const bbox = new THREE.Box3().setFromObject(group);
|
|
|
+ const center = bbox.getCenter(new THREE.Vector3());
|
|
|
+
|
|
|
+ // 记录原始材质
|
|
|
+ const originalMaterials = [];
|
|
|
+ group.traverse(child => {
|
|
|
+ if (child.isMesh) {
|
|
|
+ originalMaterials.push({
|
|
|
+ mesh: child,
|
|
|
+ material: child.material.clone() // 重要:必须克隆原始材质
|
|
|
+ });
|
|
|
+ }
|
|
|
});
|
|
|
- this.planClient.on("connect", e =>{
|
|
|
- this.planClient.subscribe(self.planOpic, (err) => {
|
|
|
- if (!err) {
|
|
|
- // console.log("预案-订阅成功:" + self.planOpic);
|
|
|
- }else{
|
|
|
- // console.log("预案-连接错误:" + err);
|
|
|
- }
|
|
|
- });
|
|
|
+
|
|
|
+ this.houses.push({
|
|
|
+ uuid: group.uuid,
|
|
|
+ group: group,
|
|
|
+ screenPosition: new THREE.Vector2(),
|
|
|
+ worldPosition: center,
|
|
|
+ originalMaterials: originalMaterials // 新增原始材质存储
|
|
|
});
|
|
|
- this.planClient.on("message", (topic, message) => {
|
|
|
- if (message){
|
|
|
- //获取预案数据
|
|
|
- this.laboratoryBigViewSelectTriggerInfo();
|
|
|
+ },
|
|
|
+
|
|
|
+ loadAssets() {
|
|
|
+ // 预加载材质和贴图
|
|
|
+ const textureLoader = new THREE.TextureLoader();
|
|
|
+ this.textures = [
|
|
|
+ textureLoader.load('/png/alarm.png'),
|
|
|
+ textureLoader.load('/png/noAlarm.png'),
|
|
|
+ ];
|
|
|
+
|
|
|
+ this.materials = [
|
|
|
+ new THREE.MeshStandardMaterial({ color: 0xff0000 }),
|
|
|
+ new THREE.MeshStandardMaterial({ color: 0x00ff00 }),
|
|
|
+ ];
|
|
|
+ },
|
|
|
+
|
|
|
+ updateScreenPositions() {
|
|
|
+ const width = window.innerWidth;
|
|
|
+ const height = window.innerHeight;
|
|
|
+
|
|
|
+ this.houses.forEach(house => {
|
|
|
+ // 将三维坐标转换为屏幕坐标
|
|
|
+ const vector = house.worldPosition.clone().project(this.camera);
|
|
|
+ house.screenPosition.x = (vector.x * 0.5 + 0.5) * width;
|
|
|
+ house.screenPosition.y = (vector.y * -0.5 + 0.5) * height;
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 新增恢复方法
|
|
|
+ restoreOriginalMaterial(house) {
|
|
|
+ house.originalMaterials.forEach(entry => {
|
|
|
+ entry.mesh.material.dispose(); // 清理当前材质
|
|
|
+ entry.mesh.material = entry.material.clone();
|
|
|
+ entry.mesh.material.needsUpdate = true;
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 修改原有材质切换方法(添加清理)
|
|
|
+ changeMaterial(house) {
|
|
|
+ house.group.traverse(child => {
|
|
|
+ if (child.isMesh) {
|
|
|
+ child.material.dispose(); // 清理旧材质
|
|
|
+ child.material = this.materials[Math.floor(Math.random() * this.materials.length)];
|
|
|
}
|
|
|
});
|
|
|
},
|
|
|
- //查询当前正在发生的预案
|
|
|
- laboratoryBigViewSelectTriggerInfo(){
|
|
|
- let self = this;
|
|
|
- laboratoryBigViewSelectTriggerInfo().then(response => {
|
|
|
- if(response.data[0]){
|
|
|
- response.data.forEach((item)=>{
|
|
|
- item.triggerUploadData = JSON.parse(item.triggerUploadData);
|
|
|
- item.alarmText = item.riskPlanName.split('预案');
|
|
|
- item.alarmText = item.alarmText[0].split('预警');
|
|
|
- item.alarmText = item.alarmText[0]+'预警';
|
|
|
- })
|
|
|
- //报警提示title信息
|
|
|
- this.$set(this,'alarmTitle',response.data[response.data.length-1].alarmText);
|
|
|
- //报警窗口数据
|
|
|
- this.$set(this,'alarmPropsData',response.data[response.data.length-1]);
|
|
|
- //报警窗口开启
|
|
|
- this.$set(this,'pageType',2);
|
|
|
- //判断报警楼栋属于南北校区
|
|
|
- setTimeout(function(){
|
|
|
- self.$refs['canvasMap'].alarmTrigger(response.data,response.data[response.data.length-1]);
|
|
|
- },500);
|
|
|
- //报警声音
|
|
|
- if(this.audioType){
|
|
|
- /* 报警音频播放 */
|
|
|
- const audio = this.$refs.audioPlayer;
|
|
|
- // 尝试静音自动播放
|
|
|
- audio.play().catch(() => {
|
|
|
- // 如果失败,延迟 2 秒再次尝试(趁浏览器未完全冻结)
|
|
|
- setTimeout(() => audio.play(), 2000);
|
|
|
- });
|
|
|
- // 延迟 3 秒后尝试取消静音(需浏览器允许)
|
|
|
- setTimeout(() => {
|
|
|
- audio.muted = false;
|
|
|
- }, 3000);
|
|
|
- }
|
|
|
- }else{
|
|
|
- this.$set(this,'pageType',1);
|
|
|
- setTimeout(function(){
|
|
|
- self.$refs['canvasMap'].alarmOff();
|
|
|
- },500);
|
|
|
+
|
|
|
+ changeTexture(house) {
|
|
|
+ const texture = this.textures[Math.floor(Math.random() * this.textures.length)];
|
|
|
+ texture.needsUpdate = true;
|
|
|
+
|
|
|
+ house.group.traverse(child => {
|
|
|
+ if (child.isMesh) {
|
|
|
+ child.material.map = texture;
|
|
|
+ child.material.needsUpdate = true;
|
|
|
}
|
|
|
- })
|
|
|
+ });
|
|
|
+ },
|
|
|
+ onWindowResize() {
|
|
|
+ this.camera.aspect = window.innerWidth / window.innerHeight;
|
|
|
+ this.camera.updateProjectionMatrix();
|
|
|
+ this.renderer.setSize(window.innerWidth, window.innerHeight);
|
|
|
+ },
|
|
|
+
|
|
|
+ animate() {
|
|
|
+ requestAnimationFrame(this.animate);
|
|
|
+ this.controls.update();
|
|
|
+ this.updateScreenPositions();
|
|
|
+ this.renderer.render(this.scene, this.camera);
|
|
|
},
|
|
|
},
|
|
|
beforeDestroy() {
|
|
|
- let self = this;
|
|
|
- // MQTT预警
|
|
|
- self.offPlanMQTT();
|
|
|
- },
|
|
|
- }
|
|
|
+ window.removeEventListener('resize', this.onWindowResize);
|
|
|
+ // 清理Three.js资源
|
|
|
+ this.renderer.dispose();
|
|
|
+ }
|
|
|
+ };
|
|
|
</script>
|
|
|
-<style scoped lang="scss">
|
|
|
- .demo7{
|
|
|
- width: 11520px;
|
|
|
- height: 2160px;
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+ .container {
|
|
|
position: relative;
|
|
|
- overflow: hidden;
|
|
|
- .map-max-big-box{
|
|
|
- z-index: 10;
|
|
|
- position: absolute;
|
|
|
- width:5927px;
|
|
|
- height: 2160px;
|
|
|
- top:3px;
|
|
|
- /*left:2760px;*/
|
|
|
- left:0;
|
|
|
- overflow: hidden;
|
|
|
- .map-box{
|
|
|
- transform:scaleX(1.4);
|
|
|
- transform-origin: 50% 0;
|
|
|
- }
|
|
|
- }
|
|
|
- .showBox{
|
|
|
- z-index: 11;
|
|
|
- }
|
|
|
+ width: 100%;
|
|
|
+ height: 100vh;
|
|
|
+ }
|
|
|
+
|
|
|
+ .canvas-container {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .buttons-container {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ pointer-events: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .house-button {
|
|
|
+ position: absolute;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ pointer-events: auto;
|
|
|
+ }
|
|
|
+
|
|
|
+ .house-button button {
|
|
|
+ margin: 5px;
|
|
|
}
|
|
|
</style>
|