487 lines
14 KiB
Vue
487 lines
14 KiB
Vue
<template>
|
||
<div>
|
||
<div id="gltf"></div>
|
||
<div id="dom"></div>
|
||
<div class="btnGroup">
|
||
<div class="button" @click="toHomeView">
|
||
主视角
|
||
</div>
|
||
<div class="button" @click="setLabel">
|
||
{{ isAddLabel ? '添加标签' : '移除标签' }}
|
||
</div>
|
||
<div class="button" @click="setBottom()">
|
||
{{ isRemoveBottom ? '移除地板' : '恢复地板' }}
|
||
</div>
|
||
<div class="button" @click="roam()">
|
||
漫游
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<script setup>
|
||
import { onMounted, ref, watch } from 'vue';
|
||
import * as THREE from 'three';
|
||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
||
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
|
||
import { RoomEnvironment } from "three/examples/jsm/environments/RoomEnvironment.js";
|
||
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
|
||
import TWEEN from "@tweenjs/tween.js";
|
||
import {
|
||
CSS2DObject,
|
||
CSS2DRenderer,
|
||
} from "three/examples/jsm/renderers/CSS2DRenderer";
|
||
import Bus from '@/utils/bus.js'
|
||
import bimStore from '@/store/modules/bim';
|
||
import gsap from "gsap";
|
||
onMounted(() => {
|
||
init();
|
||
loadSence();
|
||
renderScene();
|
||
//点击模型
|
||
document.addEventListener('click', onMouseDown);
|
||
// 右击事件
|
||
window.addEventListener('contextmenu', function (e) {
|
||
if (e.button === 2) {
|
||
onMouseDownRight(e);
|
||
}
|
||
});
|
||
// document.addEventListener("mousemove", onMouseMove);
|
||
});
|
||
let scene, renderer, camera, controls, pointsArr = true;
|
||
let biggerSphereMesh = null;
|
||
let i = 1;
|
||
let mouse = new THREE.Vector2();
|
||
let labelRenderer = new CSS2DRenderer(); //新建CSS2DRenderer
|
||
const data = reactive({
|
||
isAddLabel: true,
|
||
isRemoveBottom: true
|
||
});
|
||
const { isAddLabel, isRemoveBottom } = toRefs(data);
|
||
// 建筑树点击
|
||
Bus.on('clickBuild', (isParent) => {
|
||
// Todo
|
||
console.log('clickBuild', isParent);
|
||
if (!isParent) {// 点击子级
|
||
var clickName = bimStore().activateTree.clickName;
|
||
var Floor = scene.getObjectByName(clickName);
|
||
|
||
//投放间坐标 x: 42.6454163 y: 5.36651754 z: -95.03277
|
||
//糖化间坐标 x: 27.127655 y: 8.55 z: -6.132766
|
||
//原料处理间坐标 x: 27.6954441 y: 10.7530384 z: -37.73485
|
||
|
||
// 拉近场景
|
||
// nearCamera(Floor);
|
||
|
||
boxLight(Floor);
|
||
return;
|
||
}
|
||
})
|
||
// 设备树点击
|
||
Bus.on('clickDevice', (isParent) => {
|
||
if (!isParent) {// 点击子级
|
||
var clickName = bimStore().activateDevice.clickName;
|
||
var Floor = scene.getObjectByName(clickName);
|
||
boxLight(Floor);
|
||
// 拉近距离
|
||
// nearCamera(Floor);
|
||
return;
|
||
}
|
||
})
|
||
|
||
// 系统树点击
|
||
Bus.on('clickApplication', e => {
|
||
// Todo
|
||
console.log('clickApplication');
|
||
})
|
||
|
||
watch(() => bimStore().activateMenu, value => {
|
||
// 更换场景颜色
|
||
scene.background = new THREE.Color(value.background);
|
||
toHomeView();
|
||
}, { deep: true });
|
||
|
||
const init = () => {
|
||
// 场景
|
||
scene = new THREE.Scene();
|
||
scene.background = new THREE.Color(bimStore().activateMenu.background);
|
||
// 相机
|
||
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000);
|
||
camera.position.set(-92.650, 67.456, 38.088);
|
||
//渲染器
|
||
renderer = new THREE.WebGLRenderer({ antialias: true });
|
||
renderer.setPixelRatio(window.devicePixelRatio);
|
||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||
renderer.outputEncoding = THREE.sRGBEncoding;
|
||
document.getElementById('gltf').appendChild(renderer.domElement);
|
||
|
||
controls = new OrbitControls(camera, renderer.domElement);
|
||
const pmremGenerator = new THREE.PMREMGenerator(renderer);
|
||
scene.environment = pmremGenerator.fromScene(new RoomEnvironment()).texture;
|
||
}
|
||
const loadSence = () => {
|
||
const gltfLoader = new GLTFLoader();
|
||
const dracoLoader = new DRACOLoader();
|
||
dracoLoader.setDecoderPath('/draco/gltf/')//设置解压库文件路径
|
||
gltfLoader.setDRACOLoader(dracoLoader)
|
||
gltfLoader.load('/jzc/scene.gltf', (gltf) => {
|
||
var model = gltf.scene;
|
||
roamdObjects = model.children[0].children;
|
||
scene.add(model);
|
||
});
|
||
};
|
||
const renderScene = () => {
|
||
requestAnimationFrame(renderScene);
|
||
TWEEN.update();
|
||
controls.update();
|
||
renderer.render(scene, camera);
|
||
labelRenderer.render(scene, camera);
|
||
};
|
||
|
||
const onWindowResize = () => {
|
||
camera.aspect = window.innerWidth / window.innerHeight;
|
||
camera.updateProjectionMatrix();
|
||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||
}
|
||
window.addEventListener("resize", onWindowResize, false);
|
||
|
||
// 能选中的组
|
||
const enableGroup = [
|
||
'set1',
|
||
'set2',
|
||
'set3',
|
||
'set4',
|
||
'set5',
|
||
'set6',
|
||
'set7',
|
||
'set8',
|
||
'yuanliao_room',
|
||
'tanghua_room',
|
||
'touliao_room',
|
||
]
|
||
const isSelent = (obj) => {
|
||
var o = obj
|
||
while (true) {
|
||
if (o.name != "jzgltf" && enableGroup.indexOf(o.name) != -1) {
|
||
return o;
|
||
} else {
|
||
if (o.parent.name == "jzgltf") {
|
||
return null;
|
||
} else {
|
||
o = o.parent;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
let selectedObjects = ref([]);
|
||
let selectBoxByClick, selectBoxByMouseon, pointLabel;
|
||
var marginLeft = 0;
|
||
var marginTop = 70;
|
||
// 鼠标移动效果
|
||
const onMouseMove = (event) => {
|
||
var raycaster = new THREE.Raycaster()
|
||
mouse.x = ((event.clientX - marginLeft) / window.innerWidth) * 2 - 1;
|
||
mouse.y = -((event.clientY - marginTop) / window.innerHeight) * 2 + 1;
|
||
raycaster.setFromCamera(mouse, camera);
|
||
const intersects = raycaster.intersectObjects(scene.children, true);
|
||
if (intersects.length > 0) {
|
||
const clickedObject = intersects[0].object;
|
||
var selectedObject = isSelent(clickedObject);
|
||
if (selectedObject) {
|
||
selectedObjects[0] = selectedObject;
|
||
if (selectBoxByMouseon) {
|
||
scene.remove(selectBoxByMouseon);
|
||
}
|
||
selectBoxByMouseon = new THREE.BoxHelper(selectedObjects[0], '#ffffff');
|
||
scene.add(selectBoxByMouseon);
|
||
}
|
||
}
|
||
}
|
||
const emit = defineEmits(['handleRightClick'])
|
||
// 鼠标右击事件
|
||
const onMouseDownRight = (event) => {
|
||
var raycaster = new THREE.Raycaster()
|
||
mouse.x = ((event.clientX - marginLeft) / window.innerWidth) * 2 - 1;
|
||
mouse.y = -((event.clientY - marginTop) / window.innerHeight) * 2 + 1;
|
||
raycaster.setFromCamera(mouse, camera);
|
||
const intersects = raycaster.intersectObjects(scene.children, true);
|
||
console.log(123, event);
|
||
|
||
if (intersects.length > 0) {
|
||
const clickedObject = intersects[0].object;
|
||
console.log('这是我选中的模型', clickedObject);
|
||
var selectedObject = isSelent(clickedObject);
|
||
emit('handleRightClick', selectedObject);
|
||
}
|
||
}
|
||
// 鼠标点击效果
|
||
const onMouseDown = (event) => {
|
||
var raycaster = new THREE.Raycaster()
|
||
mouse.x = ((event.clientX - marginLeft) / window.innerWidth) * 2 - 1;
|
||
mouse.y = -((event.clientY - marginTop) / window.innerHeight) * 2 + 1;
|
||
raycaster.setFromCamera(mouse, camera);
|
||
const intersects = raycaster.intersectObjects(scene.children, true);
|
||
console.log(123, event);
|
||
|
||
if (intersects.length > 0) {
|
||
const clickedObject = intersects[0].object;
|
||
console.log('这是我选中的模型', clickedObject);
|
||
var selectedObject = isSelent(clickedObject);
|
||
// 高亮所在区域
|
||
boxLight(selectedObject);
|
||
if(bimStore().activateIndex == '0') {
|
||
// 联动左侧菜单
|
||
Bus.emit('handleTreeClick', clickedObject);
|
||
return;
|
||
}
|
||
if(bimStore().activateIndex == '1') {
|
||
// 联动左侧菜单
|
||
Bus.emit('handleTreeClick1', clickedObject);
|
||
return;
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
const boxLight = (selectedObject) => {
|
||
if (selectedObject) {
|
||
selectedObjects[0] = selectedObject;
|
||
if (selectBoxByClick) {
|
||
scene.remove(selectBoxByClick);
|
||
}
|
||
selectBoxByClick = new THREE.BoxHelper(selectedObjects[0], '#00ffff');
|
||
scene.add(selectBoxByClick);
|
||
centerSelectedGroup(selectedObject);
|
||
}
|
||
}
|
||
// 居中显示选中
|
||
const centerSelectedGroup = (obj) => {
|
||
let tween = new TWEEN.Tween(controls.target).to(obj.position, 1000).easing(TWEEN.Easing.Quadratic.Out);
|
||
controls.enabled = false;
|
||
tween.onComplete(function () {
|
||
controls.enabled = true;
|
||
});
|
||
tween.start();
|
||
}
|
||
|
||
// 漫游
|
||
const roam = () => {
|
||
//漫游坐标
|
||
const path = new THREE.CatmullRomCurve3([
|
||
new THREE.Vector3(130.076658411178222, 58.489999368786826, -32.514521795452985), // 起始坐标
|
||
new THREE.Vector3(128.938067229663826, 55.0220540878815, 30.184599697589874) //中间节点
|
||
]);
|
||
|
||
|
||
d(path)
|
||
pointsArr = path.getSpacedPoints(50);
|
||
|
||
|
||
i = 0;
|
||
console.log(pointsArr[i]);
|
||
// 设置相机起点, 相机位置:曲线上当前点pointsArr[i]
|
||
camera.position.copy(pointsArr[i]);
|
||
// 相机观察目标:当前点的下一个点pointsArr[i + 1]
|
||
camera.lookAt(pointsArr[i + 1]);
|
||
// controls.target.copy(pointsArr[i+1])
|
||
|
||
setTimeout(() => {
|
||
renders();
|
||
}, 1000);
|
||
}
|
||
const d = (cameraCurve) => {
|
||
let sphereCurve = null;
|
||
//参考路径上取100个点,每个点上添加蓝色小球
|
||
//做一个小0.6倍的路径添加到场景,作相机运动路径参考
|
||
sphereCurve = cameraCurve.clone()
|
||
sphereCurve.points.forEach(point => {
|
||
point.x *= 0.6
|
||
point.y *= 0.2
|
||
point.z *= 0.6
|
||
return point
|
||
})
|
||
|
||
//参考路径上取100个点,每个点上添加蓝色小球
|
||
const pathPoints = sphereCurve.getPoints(100)
|
||
pathPoints.forEach(point => {
|
||
const sphere = new THREE.SphereGeometry(0.2)
|
||
const sphereMaterial = new THREE.MeshBasicMaterial({
|
||
color: 0x0000ff
|
||
})
|
||
const sphereMesh = new THREE.Mesh(sphere, sphereMaterial)
|
||
sphereMesh.position.set(point.x, point.y, point.z)
|
||
scene.add(sphereMesh)
|
||
})
|
||
|
||
//绘制一条红色的路径参考线
|
||
const geometry = new THREE.BufferGeometry().setFromPoints(pathPoints)
|
||
const material = new THREE.LineBasicMaterial({
|
||
color: 0xff0000
|
||
})
|
||
const curveObject = new THREE.Line(geometry, material)
|
||
scene.add(curveObject)
|
||
|
||
//做一个较大的绿色小球沿相机相反反向移动
|
||
const biggerSphere = new THREE.SphereGeometry(2)
|
||
const sphereMaterial1 = new THREE.MeshBasicMaterial({ color: 0x0ff00 })
|
||
biggerSphereMesh = new THREE.Mesh(biggerSphere, sphereMaterial1)
|
||
biggerSphereMesh.position.set(
|
||
pathPoints[0].x,
|
||
pathPoints[0].y,
|
||
pathPoints[0].z
|
||
)
|
||
scene.add(biggerSphereMesh)
|
||
|
||
|
||
}
|
||
let num = 1;
|
||
const renders = () => {
|
||
/* angle += 0.01;
|
||
// 相机y坐标不变,在XOZ平面上做圆周运动
|
||
camera.position.x = R * Math.sin(angle);
|
||
camera.lookAt(0, 0, 0); */
|
||
if (num < pointsArr.length - 1) {
|
||
// 相机位置设置在当前点位置
|
||
camera.position.copy(pointsArr[num]);
|
||
// camera.position.set(pointsArr[num]);
|
||
// 曲线上当前点pointsArr[num]和下一个点pointsArr[num+1]近似模拟当前点曲线切线
|
||
// 设置相机观察点为当前点的下一个点,相机视线和当前点曲线切线重合
|
||
camera.lookAt(pointsArr[num + 1]);
|
||
biggerSphereMesh.position.copy(pointsArr[num]
|
||
)
|
||
|
||
// controls.target.copy(pointsArr[num+1])
|
||
num += 1; //调节速度
|
||
requestAnimationFrame(renders);
|
||
controls.update();
|
||
} else {
|
||
num = 0
|
||
}
|
||
|
||
};
|
||
|
||
let roamdObjects = ref([]);
|
||
// 返回初始值
|
||
const toHomeView = () => {
|
||
if (selectBoxByClick) {
|
||
scene.remove(selectBoxByClick);
|
||
}
|
||
removeLabel();
|
||
controls.reset();
|
||
}
|
||
// 拉近距离
|
||
const nearCamera = (floor) => {
|
||
controls.reset();
|
||
|
||
gsap.to(camera.position, {
|
||
x: floor.position.x,
|
||
y: floor.position.y,
|
||
z: floor.position.z,
|
||
duration: 2,
|
||
ease: "power1.inOut",
|
||
onComplete: () => {
|
||
},
|
||
});
|
||
|
||
}
|
||
//标签
|
||
const setLabel = () => {
|
||
if (isAddLabel.value) {
|
||
addLabel();
|
||
} else {
|
||
removeLabel();
|
||
toHomeView();
|
||
}
|
||
isAddLabel.value = !isAddLabel.value;
|
||
}
|
||
// 添加标签
|
||
const addLabel = () => {
|
||
let obj = scene.getObjectByName('set2');
|
||
// console.log(123, obj);
|
||
centerSelectedGroup(obj);
|
||
let text = "设备二";
|
||
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 removeLabel = () => {
|
||
if(pointLabel) {
|
||
isAddLabel.value = true;
|
||
document.body.removeChild(labelRenderer.domElement);
|
||
}
|
||
|
||
}
|
||
|
||
// 设置地板
|
||
const setBottom = () => {
|
||
var target = scene.getObjectByName('floor2F');
|
||
console.log(target);
|
||
if (isRemoveBottom.value) {
|
||
target.visible = false;
|
||
} else {
|
||
target.visible = true;
|
||
}
|
||
isRemoveBottom.value = !isRemoveBottom.value;
|
||
}
|
||
|
||
</script>
|
||
<style lang='scss'>
|
||
#gltf {
|
||
height: 100%;
|
||
width: 100%;
|
||
}
|
||
|
||
.btnGroup {
|
||
width: 180px;
|
||
position: absolute;
|
||
left: 260px;
|
||
top: 10px;
|
||
z-index: 999;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
color: #ffff;
|
||
|
||
>div {
|
||
cursor: pointer;
|
||
// background: #0549a7;
|
||
border: 1px solid #3cbfdf;
|
||
width: 110px;
|
||
height: 40px;
|
||
border-radius: 5px;
|
||
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-radius: 12px;
|
||
font-size: 12px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
span {
|
||
line-height: 24px;
|
||
}
|
||
}
|
||
</style> |