three.js+vue智慧社区web3d数字孪生三维地图

news/2025/1/10 22:57:42/文章来源:https://www.cnblogs.com/tiandi/p/18522337

案例效果截图如下:

具体案例场景和功能,详见b站视频:

https://www.bilibili.com/video/BV1Bb421E7WL/?vd_source=7d4ec9c9275b9c7d16afe9b4625f636c

 案例场景逻辑代码:

<template><div id="whole"><!-- threejs容器 --><div id="three" ref="container"></div><!-- 搜索框 --><div id="search" v-if="props.itemType === '房屋数据'"><a-input v-model:value="searchValue" placeholder="楼栋搜索" id="searchFrame" style="width: 100%; height: 4vh" @input="searchChange" /><div id="searchContent" v-show="searchData.length > 0"><div v-for="(val, index) in searchData" :key="index" id="searchItem" @click="viewAngleZoomIn(val)">{{ val }}</div></div></div><!-- 建筑标记元素 --><div id="buildMarker" ref="buildMarker" style="display: none"><div id="content">1幢</div></div><!-- 楼栋点击弹出框 --><div id="popup" ref="popup" style="display: none"><div id="head"><div id="title">{{ popupTitle }}</div><div id="close" @click="popupClose"></div></div><div id="content"><div class="common" @click="popupClick('1单元')">1单元</div><div class="common" @click="popupClick('2单元')">2单元</div><div class="common" @click="popupClick('3单元')">3单元</div><div class="common" @click="popupClick('4单元')">4单元</div></div></div></div><!-- 楼栋单元信息弹框  --><infoPopFrame:building="popupTitle":buildingUnit="buildingUnit":visible="infoPopFrameVisible":baseInfo="buildingBaseInfo":floorData="floorData"@closePopFrame="infoPopFrameVisible = false"></infoPopFrame>
</template>
<script lang="ts" setup>
import * as THREE from 'three';
import TWEEN from '@tweenjs/tween.js';
import { onMounted, ref, onUnmounted } from 'vue';
import { CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
import { initBaseConfig } from './components/threeBaseConfig';
import { loadingModel } from './components/modelImport.js';
import { disposeObject } from './components/disposeObject.js';
import infoPopFrame from './components/infoPopFrame.vue';// 父组件传值
const props = defineProps({// 项目类型:总览/房屋数据
  itemType: {type: String,},
});// threejs基础配置
let scene, camera, renderer, controls, css2DRenderer;
// threejs画布容器
const container = ref();
// 弹框标题
const popupTitle = ref('');
// 弹框元素
const popup = ref();
// 2D弹框
let cSS2DPopup;
// 建筑标记元素
const buildMarker = ref();
// 搜索框输入值
const searchValue = ref('');
// 小区建筑模型
let model = null;
// 搜索框检索到的数据
const searchData = ref([]);
// 建筑名称数据,用于匹配搜索框的值searchValue
const buildNameData = [];
// 建筑单元
const buildingUnit = ref('');
// 建筑单元基本信息
const buildingBaseInfo = ref([{ name: '产权人', value: '于晓敏' },{ name: '商铺', value: 2 },{ name: '自住房间', value: 3 },{ name: '租住房间', value: 12 },{ name: '常驻人口', value: 4 },{ name: '流动人口', value: 8 },
]);
// 建筑信息弹框显示
const infoPopFrameVisible = ref(false);
// 建筑楼层数据
const floorData = ref([{name: '一楼',houseNumArr: [{ num: '1-101', type: '商铺' },{ num: '1-102', type: '商铺' },{ num: '1-103', type: '商铺' },{ num: '1-104', type: '商铺' },],},{name: '二楼',houseNumArr: [{ num: '2-101', type: '租住' },{ num: '2-102', type: '租住' },{ num: '2-103', type: '租住' },{ num: '2-104', type: '租住' },],},{name: '三楼',houseNumArr: [{ num: '3-101', type: '租住' },{ num: '3-102', type: '租住' },{ num: '3-103', type: '租住' },{ num: '3-104', type: '租住' },],},{name: '四楼',houseNumArr: [{ num: '4-101', type: '自住' },{ num: '4-102', type: '自住' },{ num: '4-103', type: '自住' },{ num: '4-104', type: '自住' },],},{name: '五楼',houseNumArr: [{ num: '5-101', type: '自住' },{ num: '5-102', type: '自住' },{ num: '5-103', type: '自住' },{ num: '5-104', type: '自住' },],},
]);
// 组件卸载时清除场景scene中的所有内容,释放资源
onUnmounted(() => {disposeObject(scene);
});// 组件挂载完成,进行初始化
onMounted(async () => {// 初始化基础配置:场景、相机、渲染器等const baseConfig = initBaseConfig();scene = baseConfig.scene;camera = baseConfig.camera;renderer = baseConfig.renderer;controls = baseConfig.controls;css2DRenderer = baseConfig.css2DRenderer;// 渲染器dom挂在threejs容器中
  container.value.appendChild(renderer.domElement);container.value.appendChild(css2DRenderer.domElement);// 加载3D模型model = await loadingModel();scene.add(model);// 初始化css2D弹框
  initPopup();// 添加鼠标移动事件
  addMouseMoveEvent();// 添加鼠标点击事件
  addMouseClickEvent();// 添加建筑的标记
  addBuildMarker();if (props.itemType === '房屋数据') {// 获取建筑名称数据,用以搜索框检索model.getObjectByName('建筑').traverse((item) => {if (item.isMesh && item.name && item.name.includes('幢')) {if (buildNameData.includes(item.name)) return;buildNameData.push(item.name);}});}// 开始循环渲染
  render();
});// 循环渲染
const render = () => {requestAnimationFrame(render);TWEEN.update();controls.update();css2DRenderer.render(scene, camera);renderer.render(scene, camera);
};// 射线检测
const rayTest = (e) => {const px = e.offsetX;const py = e.offsetY;// 屏幕坐标转为标准设备坐标const x = (px / window.innerWidth) * 2 - 1;const y = -(py / (window.innerHeight - 36 - 56)) * 2 + 1;// 创建射线const raycaster = new THREE.Raycaster();// 设置射线参数raycaster.setFromCamera(new THREE.Vector2(x, y), camera);// 射线交叉计算拾取模型let intersects = raycaster.intersectObjects(model.getObjectByName('建筑').children);return intersects;
};// 鼠标移动事件,释放射线进行检测建筑模型,改变检测到的建筑模型的颜色
const moveEvent = (e) => {const intersects = rayTest(e);// 所有建筑模型发射光emissive重置黑色model.getObjectByName('建筑').traverse((item) => {if (item.isMesh) {item.material.emissive = new THREE.Color('#000');}});// 检测结果存在时if (intersects[0]) {// 改变鼠标样式为手指document.body.style.cursor = 'pointer';// 当前检测建筑模型const currentBuildModel = intersects[0].object;// 定义材质颜色currentBuildModel.material.emissive = new THREE.Color('#00BFFF');} else {// 恢复默认鼠标样式document.body.style.cursor = 'default';}
};// 鼠标点击事件,释放射线进行检测建筑模型
const clickEvent = (e) => {const intersects = rayTest(e);// 检测结果存在时if (intersects[0]) {// 过滤掉其他建筑if (intersects[0].object.name.includes('其他')) return;if (intersects[0].object.name.includes('配电')) {popupTitle.value = '配电';} else {popupTitle.value = intersects[0].object.name;}model.getObjectByName('建筑').traverse((item) => {if (item.isMesh) {item.material.color = item.color;}});intersects[0].object.material.color = new THREE.Color('#00C5CD');cSS2DPopup.visible = true;controls.update();cSS2DPopup.position.copy(controls.target);}
};// 初始css2D弹框,将弹框元素转换成threejs中的css2D对象
function initPopup() {popup.value.style.display = 'block';cSS2DPopup = new CSS2DObject(popup.value);cSS2DPopup.renderOrder = 99;cSS2DPopup.visible = false;cSS2DPopup.position.set(0, 0, 0);scene.add(cSS2DPopup);
}// 添加鼠标移动事件
function addMouseMoveEvent() {// 节流函数const throttleChange = throttle(moveEvent, 10);// 监听鼠标移动事件container.value.addEventListener('mousemove', (e) => {throttleChange(e);});
}// 节流函数,鼠标移动事件触发太过频繁需要节制触发次数
function throttle(func, limit) {let inThrottle;return function () {const args = arguments;const context = this;if (!inThrottle) {func.apply(context, args);inThrottle = true;setTimeout(() => (inThrottle = false), limit);}};
}// 添加鼠标点击事件
function addMouseClickEvent() {// 监听鼠标点击事件container.value.addEventListener('click', (e) => {clickEvent(e);});
}// 视角拉近
function viewAngleZoomIn(val) {cSS2DPopup.visible = false;// 当前目标建筑模型const target = model.getObjectByName(val);// 重置所有建筑模型颜色model.getObjectByName('建筑').traverse((item) => {if (item.isMesh) {item.material.color = item.color;}});// 设置建筑模型颜色target.material.color = new THREE.Color('#00C5CD');// 目标位置const targetPos = target.getWorldPosition(new THREE.Vector3());// 移动位置const movePos = targetPos.clone();movePos.y += 80;movePos.z += 55;// 开始位置const startPos = camera.position.clone();// 初始的控件目标const initialTarget = controls.target.clone();new TWEEN.Tween({ t: 0 }).to({ t: 1 }, 1500).easing(TWEEN.Easing.Sinusoidal.InOut).onUpdate(function (e) {const t = e.t;camera.position.lerpVectors(startPos, movePos, t);controls.target.lerpVectors(initialTarget, targetPos, t);camera.updateProjectionMatrix();controls.update();}).onComplete(function () {cSS2DPopup.visible = true;popupTitle.value = val;controls.update();cSS2DPopup.position.copy(controls.target);}).start();
}// 添加建筑标记
function addBuildMarker() {model.getObjectByName('建筑').traverse((item) => {if (item.name.includes('其他')) return;if (item.isMesh) {let closeDom;if (item.name.includes('配电')) {closeDom = buildMarker.value.cloneNode(true);closeDom.style.width = '2vw';closeDom.children[0].innerHTML = '配电';} else {closeDom = buildMarker.value.cloneNode(true);closeDom.style.width = `${item.name.length * 0.7}vw`;closeDom.children[0].innerHTML = item.name;}const cSS2DObject = new CSS2DObject(closeDom);const pos = item.getWorldPosition(new THREE.Vector3());cSS2DObject.position.copy(pos);cSS2DObject.position.y += 5;cSS2DObject.name = item.name + '标记';scene.add(cSS2DObject);}});
}// 搜索框内容变化事件,模糊匹配建筑名称数据
function searchChange(e) {if (!e) {searchData.value = [];return;}// 匹配结果const rel = buildNameData.filter((item) => item.includes(e));// 对匹配结果进行排序rel.sort((a, b) => {// 提取数值const getNumber = (str) => parseInt(str.match(/\d+/)[0]);// 检测名称中是否带有别墅const isVilla = (str) => str.includes('别墅');if (isVilla(a) && isVilla(b)) {return getNumber(a) - getNumber(b);} else if (isVilla(a)) {return 1;} else if (isVilla(b)) {return -1;} else {return getNumber(a) - getNumber(b);}});searchData.value = rel;
}// 弹框关闭事件
function popupClose() {cSS2DPopup.visible = false;model.getObjectByName('建筑').traverse((item) => {if (item.isMesh) {item.material.color = item.color;}});
}// 弹框点击事件
function popupClick(e) {buildingUnit.value = e;infoPopFrameVisible.value = true;
}
</script><style lang="less" scoped>
body {font-size: 0.7vw;
}
::v-deep .arco-card-body {padding: 0px !important;width: 100%;height: 100%;
}::v-deep .arco-input-wrapper {border-radius: 10px;background: #000;border: 1px solid #009acd;color: #fafafa;
}::v-deep .arco-input-wrapper .arco-input.arco-input-size-medium {font-size: 0.7vw !important;
}/* 当视口宽度小于 1400 像素时,设置最小字体大小 */
@media (max-width: 1400px) {#buildMarker {font-size: 12px !important;width: 55px !important;height: 20px !important;}
}
#whole {width: 100%;height: calc(100% - 36px - 56px);#three {width: 100%;height: 100%;}#search {z-index: 999;position: absolute;width: 22vw;right: 1%;top: 3%;#searchContent {border-radius: 4px;margin-top: 4px;width: 100%;max-height: 300px;border: 1px solid #0e2346;background: rgba(0, 0, 0, 0.7);#searchItem {text-indent: 1em;line-height: 2.5vh;font-size: 0.7vw;width: 100%;height: 2.5vh;color: #eee;border-bottom: 1px solid #12485a;}#searchItem:hover {cursor: pointer;background: #0e2346;}}#searchContent {overflow-y: auto;}#searchContent::-webkit-scrollbar {width: 4px;}#searchContent::-webkit-scrollbar-thumb {border-radius: 10px;background: rgba(30, 150, 200, 0.7);}#searchContent::-webkit-scrollbar-track {border-radius: 0;background: rgba(0, 0, 0, 0.1);}}#buildMarker {z-index: 997;position: absolute;top: 0;left: 0;font-size: 0.6vw;height: 2.5vh;display: flex;flex-direction: column;justify-content: center;align-items: center;#content {width: 100%;height: 100%;background: #0e2346;border: 1px solid #6298a9;display: flex;align-items: center;justify-content: center;color: #fafafa;#mapTag_value {color: #ffd700;}}}#popup {z-index: 999;position: absolute;top: 0;left: 0;width: 20vw;height: 15vh;background: rgba(15, 41, 77, 0.85);border-radius: 0.3vw;border: 1px solid rgba(10, 109, 155, 0.95);#head {width: 95%;margin-left: 2.5%;height: 30%;border-bottom: 1px solid #009acd;display: flex;align-items: center;justify-content: space-between;#title {font-size: 0.85vw;color: #bbffff;margin-left: 2.5%;}#close {pointer-events: all;width: 1vw;height: 1vw;background: url('../../assets/close.png') no-repeat;background-size: 100% 100%;}#close:hover {cursor: pointer;}}#content {width: 95%;margin-left: 2.5%;height: 70%;display: flex;justify-content: space-evenly;align-items: center;.common {font-size: 0.7vw;display: flex;justify-content: center;align-items: center;pointer-events: all;width: 18%;height: 3vh;border: 1px solid #1f81a1;color: #fafafa;}.common:hover {cursor: pointer;color: #bbffff;border: 1px solid #03c0ff;}}}
}
</style>

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/825841.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网易云课堂视频课件课程下载工具,如何在电脑端下载网易云课堂视频课程课件资料到本地?

一. 安装网易云课堂课程下载器 1.获取学无止下载器 https://www.xuewuzhi.cn/study163_downloader 2.下载安装后,然后点击桌面快捷方式运行即可。 注意:杀毒软件可能会阻止外部exe文件运行,并将其当做成病毒,直接添加信任即可,本软件绝对没有木马病毒。 二. 使用说明 1.学…

html结合js设计一个可拍照可录制可下载的摄像头页面

功能如下html文件用户设置样式,代码如下<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Came…

ENSP之AR路由器一直#####的极端情况判断

系统版本,CPU 如下: 故障原因:AR设备一直###### 分析判断:由于是新安装的系统,没有安全软件限制,防火墙也都放行了,其他一切正常。ENSP凡是要通过virtual box调用的设备都出现了###。判断有两种情况,系统版本或者CPU问题。 通过和客户沟通得知,他对CPU进行了超频。…

如何使用Ida Pro和Core Dump文件定位崩溃位置(Linux下无调试符号的进程专享)

我们在嵌入式Linux开发过程中经常会遇到一个问题,就是程序崩溃后不知道具体位置。因为我们发布到生产环境的一般是没有调试符号的(使用strip或编译时加-s参数,CMake生成的编译指令中的-O3也会造成调试符号丢失),毕竟嵌入式的存储都比较有限,肯定是需要剥离调试符号的。另…

统计学习方法笔记

统计学习方法 1.3 统计学习方法的三要素 1.3.1 模型 好,为什么要从1.3开始呢,因为看前面的课,我还没有用到这个软件。 方法=模型+策略+算法 模型有好多个,试试 策略:按照什么样的准则去选取模型 比如说看预测值和真实值有多大,或者损失函数最小等 算法 即怎样去实现去寻找…

zookeeper的安装与搭建

1、下载zookeeper,并上传到Linux并解压tar -xvf zookeeper-3.5.7.tar.gz -C ../2、修改文件名配置环境变量mv apache-zookeeper-3.5.7-bin zookeeper-3.5.7 vim /etc/profile source /etc/profile3、修改配置文件创建data目录mkdir data创建myid文件touch myid vim myidserver…

Windows Server2022服务器部署RuoYi若依前后端分离

部署准备 虚拟机Windows Server2022 若依前后端分离v3.8.8打包好 jdk1.8 redis5 mysql8.4 iis服务 路由插件 重写插件 1.安装jdk1.8 https://www.azul.com/downloads/#downloads-table-zulu 略 2.安装启动redis5 https://github.com/tporadowski/redis/releases 下载安装默认配…

20222407 2024-2025-1 《网络与系统攻防技术》实验四实验报告

(一)实践目标 恶意代码文件类型标识、脱壳与字符串提取 对提供的rada恶意代码样本,进行文件类型识别,脱壳与字符串提取,以获得rada恶意代码的编写作者,具体操作如下: o使用文件格式和类型识别工具,给出rada恶意代码样本的文件格式、运行平台和加壳工具; o使用超级巡…

状态压缩动态规划

\(3^n\)枚举子集 状压DP中相当重要的技巧(虽然后位有FWT,FMT替代,但不是都能代) for(int i = x; i; i = (i - 1) & x) { // i 就是 x 的子集 }题目 P6622 [省选联考 2020 A/B 卷] 信号传递 看数据范围,\(m \le 23\),且不同分数段增长很慢,表明会有\(O(2^m)\)的做法,…

专题

求区间第k小值 静态 分块 排序 划分树 动态 主席树 平衡树 子树求交 树上颜色问题 统计颜色数量 对于子树\(x\),子树内同种颜色的点只有深度最浅的对子树外有贡献 #3628. 「2021 集训队互测」树上的孤独 贡献上传:对于\(x\),设它同颜色祖先为\(p\),则\(x\)对路径\(p\thicks…

BEVDet4D: Exploit Temporal Cues in Multi-camera 3D Object Detection阅读小结

BEVDet4D提出:提出BEVDet4D范式,将BEVDet从仅空间的3D扩展到时空4D工作空间。BEVDet4D: Exploit Temporal Cues in Multi-camera 3D Object Detection BEVDet4D:在多相机三维目标检测中利用时间线索 摘要背景:单帧数据包含有限信息,限制了基于视觉的多相机3D目标检测性能。…