482 lines
14 KiB
Vue
482 lines
14 KiB
Vue
<template>
|
||
<div>
|
||
<canvas id="three"></canvas>
|
||
<div class="btnGroup">
|
||
<div class="button" @click="toHomeView">
|
||
主视角
|
||
</div>
|
||
<div class="button" @click="setLabel">
|
||
{{ isAddLabel ? '添加标签' : '移除标签' }}
|
||
</div>
|
||
<div class="button" @click="walk">
|
||
漫游
|
||
</div>
|
||
</div>
|
||
<div id="dom"></div>
|
||
</div>
|
||
</template>
|
||
<script setup>
|
||
import * as THREE from "three";
|
||
import { ref, onMounted } from "vue";
|
||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
||
// 引入轨道控制器:支持鼠标左中右键操作和键盘方向键操作
|
||
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
|
||
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader";
|
||
// 引入轨道控制器:支持鼠标左中右键操作和键盘方向键操作
|
||
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
|
||
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js";
|
||
import { OutlinePass } from "three/examples/jsm/postprocessing/OutlinePass.js";
|
||
import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass.js";
|
||
import { FXAAShader } from "three/examples/jsm/shaders/FXAAShader.js";
|
||
import {
|
||
CSS2DObject,
|
||
CSS2DRenderer,
|
||
} from "three/examples/jsm/renderers/CSS2DRenderer";
|
||
// import renderModel from "../renderModel";
|
||
import homeIcon from '@/assets/image/bg7.jpg'
|
||
import TWEEN from "@tweenjs/tween.js";
|
||
import Bus from '@/utils/bus.js'
|
||
import bimStore from '@/store/modules/bim';
|
||
|
||
let scene = ref(null);
|
||
let renderer = ref(null);
|
||
let camera = ref(null);
|
||
// 控制器
|
||
let orbit = ref(null);
|
||
let group = ref(null);
|
||
let mouse = new THREE.Vector2();
|
||
let raycaster = new THREE.Raycaster();
|
||
let labelRenderer = new CSS2DRenderer(); //新建CSS2DRenderer
|
||
let positionObj = ref(null);
|
||
// 选中的模型
|
||
let selectedObjects = ref([]);
|
||
|
||
const props = defineProps({
|
||
background: { // 背景颜色
|
||
default: '#fff',
|
||
type: String
|
||
},
|
||
sceneUrl: { // 模型路径
|
||
default: '/jz/glb/scene.gltf',
|
||
type: String
|
||
},
|
||
light: {
|
||
default: '0xffffff',
|
||
type: String
|
||
}
|
||
})
|
||
const data = reactive({
|
||
gltfObj: null,
|
||
btnLabelName: '添加标签',
|
||
isAddLabel: true
|
||
});
|
||
const { gltfObj, isAddLabel } = toRefs(data);
|
||
|
||
watch(() => props.sceneUrl, val => {
|
||
init();
|
||
loadSence();
|
||
});
|
||
let objArr = [];
|
||
let objM = [];
|
||
// 建筑树点击
|
||
Bus.on('clickBuild', (isParent) => {
|
||
// Todo
|
||
console.log('clickBuild', isParent);
|
||
if (!isParent) {// 点击子级
|
||
var clickName = bimStore().activateTree.clickName;
|
||
// cleanColor();
|
||
var Floor = gltfObj.value.scene.getObjectByName(clickName);
|
||
// 染色
|
||
// Floor.traverse(e => {
|
||
// e.material = new THREE.MeshLambertMaterial({
|
||
// color: 0x00ff00,
|
||
// });
|
||
// })
|
||
// 高亮轮廓
|
||
outlineObj([Floor]);
|
||
return;
|
||
}
|
||
// 父级
|
||
toHomeView();
|
||
})
|
||
// 设备树点击
|
||
Bus.on('clickDevice', (isParent) => {
|
||
if (!isParent) {// 点击子级
|
||
var clickName = bimStore().activateDevice.clickName;
|
||
var Floor = gltfObj.value.scene.getObjectByName(clickName);
|
||
console.log(1, Floor);
|
||
// 高亮轮廓
|
||
outlineObj([Floor])
|
||
return;
|
||
}
|
||
// 父级
|
||
toHomeView();
|
||
})
|
||
|
||
// 系统树点击
|
||
Bus.on('clickApplication', e => {
|
||
// Todo
|
||
console.log('clickApplication');
|
||
})
|
||
|
||
onMounted(() => {
|
||
init();
|
||
loadSence();
|
||
// 启动动画
|
||
renderScene();
|
||
document.addEventListener("click", onMouseDown);
|
||
document.addEventListener("mousemove", onMouseDown);
|
||
});
|
||
|
||
|
||
const setLabel = () => {
|
||
if(isAddLabel.value) {
|
||
addLabel();
|
||
} else {
|
||
removeLabel();
|
||
}
|
||
isAddLabel.value = !isAddLabel.value;
|
||
}
|
||
|
||
const removeLabel = () => {
|
||
document.body.removeChild(labelRenderer.domElement);
|
||
}
|
||
// 添加标签
|
||
|
||
const addLabel = () => {
|
||
let obj = gltfObj.value.scene.getObjectByName('set2');
|
||
let text = "设备二";
|
||
let pointLabel = createLableObj(text);
|
||
obj.add(pointLabel);
|
||
labelRenderer.setSize(window.innerWidth, window.innerHeight);
|
||
labelRenderer.domElement.style.position = "absolute";
|
||
labelRenderer.domElement.style.top = 0;
|
||
labelRenderer.domElement.style.pointerEvents = 'none';// 必须加上
|
||
document.body.appendChild(labelRenderer.domElement);
|
||
|
||
// 将呈现器的输出添加到HTML元素
|
||
document.getElementById("dom").appendChild(renderer.domElement);
|
||
};
|
||
|
||
const createLableObj = (text) => {
|
||
let laberDiv = document.createElement("div"); //创建div容器
|
||
laberDiv.className = "laber_name";
|
||
laberDiv.innerHTML = `<div class="arrow_outer"></div><span>设备名称:${text}</span><span>状态:启用</span><span>压力:50 Pa</span>`
|
||
let pointLabel = new CSS2DObject(laberDiv);
|
||
return pointLabel;
|
||
};
|
||
// 清除模型颜色
|
||
const cleanColor = () => {
|
||
|
||
// 恢复之前被选中模型的材质
|
||
if (objArr.length > 0 && objM.length > 0) {
|
||
objArr.forEach((ele, index) => {
|
||
console.log(objM[index]);
|
||
ele.material = objM[index]
|
||
})
|
||
objArr = [];
|
||
objM = [];
|
||
}
|
||
gltfObj.value.scene.traverse(e => {
|
||
objArr.push(e);
|
||
objM.push(e.material);
|
||
})
|
||
}
|
||
const init = () => {
|
||
// scene = new THREE.Scene("#00ffff");
|
||
// const texture = new THREE.TextureLoader().load(homeIcon);
|
||
// texture.mapping = THREE.EquirectangularReflectionMapping;
|
||
// scene.background = texture
|
||
// scene.environment = texture
|
||
|
||
scene = new THREE.Scene();
|
||
console.log(123, props.background);
|
||
scene.background = new THREE.Color(props.background);
|
||
const canvas = document.querySelector("#three");
|
||
var cubeLoader = new THREE.CubeTextureLoader();
|
||
// 创建一个渲染器并设置大小,WebGLRenderer将会使用电脑显卡来渲染场景
|
||
renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true, preserveDrawingBuffer: true });
|
||
//曝光
|
||
renderer.toneMappingExposure = 2;
|
||
renderer.shadowMap.enabled = true;
|
||
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
||
|
||
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.25, 2000)
|
||
// 将摄像机对准场景的中心
|
||
camera.position.set(-92.936, 180.990, -28.179);
|
||
camera.lookAt(scene.position);
|
||
// 创建控件对象
|
||
orbit = new OrbitControls(camera, renderer.domElement);
|
||
orbit.autoRotate = true;
|
||
setTimeout(function () {
|
||
orbit.autoRotate = false;
|
||
}, 7000);
|
||
// renderer.setClearColor(new THREE.Color("#0e0934"));
|
||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||
// 环境光 光源颜色从0x444444更改为0x888888,可以看到threejs场景中的网格模型表面变的更亮。
|
||
// intensity 强度属性0.5
|
||
var alight = new THREE.AmbientLight(props.light, 1);
|
||
alight.name = "aLight";
|
||
scene.add(alight);
|
||
};
|
||
|
||
const loadSence = () => {
|
||
const gltfLoader = new GLTFLoader();
|
||
gltfLoader.load(props.sceneUrl, (gltf) => {
|
||
// model.value = gltf.scene;
|
||
gltfObj.value = gltf;
|
||
var model = gltf.scene;
|
||
scene.add(model);
|
||
});
|
||
};
|
||
|
||
const renderScene = () => {
|
||
// orbit.update(); // 拖动
|
||
// 使用requestAnimationFrame函数进行渲染
|
||
requestAnimationFrame(renderScene);
|
||
renderer.render(scene, camera);
|
||
// 写在requestAnimationFrame之后,否则会报错
|
||
labelRenderer.render(scene, camera);
|
||
if (composer) {
|
||
composer.render()
|
||
}
|
||
};
|
||
// 相机移动动画
|
||
const initTween = (targetX, targetY, targetZ) => {
|
||
let initPosition = {
|
||
x: camera.position.x,
|
||
y: camera.position.y,
|
||
z: camera.position.z,
|
||
};
|
||
let tween = new TWEEN.Tween(initPosition)
|
||
.to({ x: targetX, y: targetY, z: targetZ }, 2000)
|
||
.easing(TWEEN.Easing.Sinusoidal.InOut);
|
||
let onUpdate = (pos) => {
|
||
let x = pos.x;
|
||
let y = pos.y;
|
||
let z = pos.z;
|
||
if (pos.z < 0) {
|
||
camera.position.set(x, y, z - 12);
|
||
} else {
|
||
camera.position.set(x, y, z + 12);
|
||
}
|
||
};
|
||
tween.onUpdate(onUpdate);
|
||
tween.start();
|
||
if (positionObj.z < 0) {
|
||
orbit.target.set(
|
||
positionObj.x,
|
||
positionObj.y - 0.4,
|
||
-12
|
||
);
|
||
} else {
|
||
orbit.target.set(
|
||
positionObj.x,
|
||
positionObj.y - 0.4,
|
||
12
|
||
);
|
||
}
|
||
}
|
||
|
||
// 储存被选中的模型和材质
|
||
let selectedObject = null;
|
||
let selectedMaterial = null;
|
||
// 能选中的组
|
||
const enableGroup = [
|
||
'set1',
|
||
'set2',
|
||
'set3',
|
||
'set4',
|
||
'yuanliao_room',
|
||
'tanghua_room',
|
||
'touliao_room',
|
||
]
|
||
// const onMouseDown = (event) => {
|
||
// // 计算鼠标点击位置的归一化设备坐标
|
||
// mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
|
||
// mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
||
|
||
// var raycaster = new THREE.Raycaster()
|
||
// cleanColor();
|
||
// // 更新射线的起点和方向
|
||
// raycaster.setFromCamera(mouse, camera);
|
||
// // 计算射线和场景中所有可点击物体的相交情况
|
||
// const intersects = raycaster.intersectObjects(scene.children, true);
|
||
// // 如果找到了模型,将其材质修改为绿色
|
||
// if (intersects.length > 0) {
|
||
// const clickedObject = intersects[0].object;
|
||
// // 储存被选中的模型和材质
|
||
// selectedObject = clickedObject;
|
||
// // // 修改材质为绿色
|
||
// selectedObject.material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
|
||
// nearCamera(intersects);
|
||
// }
|
||
|
||
// }
|
||
const onMouseDown = (event) => {
|
||
var raycaster = new THREE.Raycaster()
|
||
// 计算鼠标点击位置的归一化设备坐标
|
||
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
|
||
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
||
|
||
// 更新射线的起点和方向
|
||
raycaster.setFromCamera(mouse, camera);
|
||
// 计算射线和场景中所有可点击物体的相交情况
|
||
const intersects = raycaster.intersectObjects(scene.children, true);
|
||
// 如果找到了模型,将其材质修改为绿色
|
||
if (intersects.length > 0) {
|
||
const clickedObject = intersects[0].object;
|
||
// 储存被选中的模型和材质
|
||
selectedObject = clickedObject.parent;
|
||
if (enableGroup.indexOf(selectedObject.name) != -1) {
|
||
outlineObj([selectedObject])
|
||
// nearCamera(intersects);
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
let composer = null
|
||
let outlinePass = null
|
||
let renderPass = null
|
||
const outlineObj = (selectedObject_list) => {
|
||
// 创建一个EffectComposer(效果组合器)对象,然后在该对象上添加后期处理通道。
|
||
composer = new EffectComposer(renderer)
|
||
// 新建一个场景通道 为了覆盖到原理来的场景上
|
||
renderPass = new RenderPass(scene, camera)
|
||
composer.addPass(renderPass);
|
||
// 物体边缘发光通道
|
||
outlinePass = new OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), scene, camera, selectedObject_list)
|
||
outlinePass.selectedObjects = selectedObject_list
|
||
// 下面都注掉。不然太开了
|
||
outlinePass.edgeStrength = 10.0 // 边框的亮度
|
||
outlinePass.edgeThickness = 1.0 // 边框宽度
|
||
outlinePass.visibleEdgeColor.set(0x00ff00) // 边框颜色
|
||
composer.addPass(outlinePass)
|
||
// 自定义的着色器通道 作为参数
|
||
var effectFXAA = new ShaderPass(FXAAShader)
|
||
effectFXAA.uniforms.resolution.value.set(1 / window.innerWidth, 1 / window.innerHeight)
|
||
effectFXAA.renderToScreen = true
|
||
composer.addPass(effectFXAA)
|
||
}
|
||
|
||
const nearCamera = (intersects) => {
|
||
console.log(5555, intersects)
|
||
// 拉近场景
|
||
if (!intersects[0]) {
|
||
return;
|
||
} else {
|
||
if (intersects[0].object.name) {
|
||
selectedObjects = [];
|
||
selectedObjects.push(intersects[0].object);
|
||
positionObj = {
|
||
x: intersects[0].object.position.x,
|
||
y: intersects[0].object.position.y,
|
||
z: intersects[0].object.position.z,
|
||
};
|
||
|
||
initTween(
|
||
positionObj.x,
|
||
positionObj.y,
|
||
positionObj.z + 10
|
||
);
|
||
}
|
||
}
|
||
}
|
||
const toHomeView = () => {
|
||
camera.position.set(-92.936, 180.990, -28.179);
|
||
camera.lookAt(scene.position);
|
||
// cleanColor();
|
||
outlineObj([]);
|
||
}
|
||
// 返回主页
|
||
const toHomeView1 = () => {
|
||
let initPosition = {
|
||
x: camera.position.x,
|
||
y: camera.position.y,
|
||
z: camera.position.z,
|
||
};
|
||
let tween = new TWEEN.Tween(initPosition)
|
||
.to({ x: 0, y: -(5 * 24) / 12, z: (6 * 100) / 5 }, 2000)
|
||
.easing(TWEEN.Easing.Sinusoidal.InOut);
|
||
let onUpdate = (pos) => {
|
||
let x = pos.x;
|
||
let y = pos.y;
|
||
let z = pos.z;
|
||
camera.position.set(x, y, z);
|
||
};
|
||
tween.onUpdate(onUpdate);
|
||
tween.start();
|
||
orbit.target.set(0, 0, 0);
|
||
}
|
||
</script>
|
||
<style lang='scss'>
|
||
#three {
|
||
height: 100%;
|
||
width: 100%;
|
||
}
|
||
|
||
.btnGroup {
|
||
width: 180px;
|
||
position: absolute;
|
||
left: 0px;
|
||
bottom: 40px;
|
||
z-index: 999;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
cursor: pointer;
|
||
color: #ffff;
|
||
|
||
>div {
|
||
background: #477efa;
|
||
width: 120px;
|
||
height: 40px;
|
||
border-radius: 0 50px 50px 0;
|
||
line-height: 40px;
|
||
text-align: center;
|
||
margin-top: 10px;
|
||
}
|
||
}
|
||
|
||
.laber_name {
|
||
width: 120px;
|
||
height: 90px;
|
||
padding: 5px 10px;
|
||
white-space: nowrap;
|
||
background-color: #ffffffb3;
|
||
border-top-right-radius: 12px;
|
||
border-bottom-right-radius: 12px;
|
||
font-size: 12px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
&::before {
|
||
// content: "";
|
||
// display: inline-block;
|
||
// width: 0;
|
||
// height: 0;
|
||
// border: 12px solid transparent;
|
||
// border-right-color: #ffffffb3;
|
||
// position: absolute;
|
||
// left: -23px;
|
||
// top: 0;
|
||
}
|
||
|
||
.arrow_outer {
|
||
// position: absolute;
|
||
// left: -22px;
|
||
// top: 6px;
|
||
// margin: 0;
|
||
// width: 12px;
|
||
// height: 12px;
|
||
// background-color: #eb6852;
|
||
// border-radius: 100%;
|
||
// border: 1px solid #fff;
|
||
}
|
||
|
||
span {
|
||
line-height: 24px;
|
||
}
|
||
}
|
||
</style> |