123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638 |
- import * as THREE from 'three'; //导入整个 three.js核心库
- import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; //导入控制器模块,轨道控制器
- import { CSS3DRenderer, CSS3DSprite } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
- import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
- import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
- // 用于模型边缘高亮
- 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 { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';
- import * as TWEEN from '@tweenjs/tween.js';
- import { getBottomMaterial } from './material.js';
- import GltfModelManager from '@/views/largeScreen/three/GltfModelManager';
- import ComponentHandle from '@/utils/ComponentHandle';
- import usageReal from '@/views/largeScreen/dialog/usage-real.vue';
- const dracoLoader = new DRACOLoader();
- // 设置draco路径
- dracoLoader.setDecoderPath('/draco/');
- dracoLoader.setDecoderConfig({ type: 'js' });
- // 定义一个 class类
- class renderModel {
- constructor(selector) {
- this.container = document.querySelector(selector);
- // 相机
- this.camera;
- // 场景
- this.scene;
- //渲染器
- this.renderer;
- // 控制器
- this.controls;
- // 3d文字渲染器
- this.css3DRenderer = null;
- // 3d文字控制器
- this.css3dControls = null;
- // 模型
- this.model;
- // 室内模型1
- this.model1;
- // 室内模型2
- this.model2;
- // 环境光
- this.ambientLight;
- //模型平面
- this.planeGeometry;
- this.outlineObjs = [];
- this.sharedComposer = null;
- this.sharedOutlinePass = null;
- this.sharedBloomPass = null;
- this.isComposerInitialized = false;
- this.raycaster = new THREE.Raycaster();
- this.mouse = new THREE.Vector2();
- this.autoRotate = true;
- this.labels = [];
- this.roomLabelGroup = null;
- }
- controlRotate(bool) {
- this.autoRotate = bool;
- if (!bool) {
- this.model.rotation.z = 0;
- }
- }
- // 初始化共享后处理系统
- initSharedPostProcessing() {
- if (this.isComposerInitialized) return;
- // 创建共享的EffectComposer
- this.sharedComposer = new EffectComposer(this.renderer);
- this.sharedComposer.renderTarget1.texture.outputColorSpace = THREE.sRGBEncoding;
- this.sharedComposer.renderTarget2.texture.outputColorSpace = THREE.sRGBEncoding;
- this.sharedComposer.renderTarget1.texture.encoding = THREE.sRGBEncoding;
- this.sharedComposer.renderTarget2.texture.encoding = THREE.sRGBEncoding;
- // 添加基础渲染通道
- const renderPass = new RenderPass(this.scene, this.camera);
- this.sharedComposer.addPass(renderPass);
- // 创建共享的OutlinePass
- this.sharedOutlinePass = new OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), this.scene, this.camera);
- // 设置轮廓效果参数
- this.sharedOutlinePass.edgeStrength = 5.0; // 边框的亮度
- this.sharedOutlinePass.edgeGlow = 0.3; // 光晕[0,1]
- this.sharedOutlinePass.edgeThickness = 1.0; // 边框宽度
- // this.sharedOutlinePass.pulsePeriod = 3; // 呼吸闪烁的速度
- // this.sharedOutlinePass.visibleEdgeColor.set(0x00ff00); // 呼吸显示的颜色
- // this.sharedOutlinePass.hiddenEdgeColor.set(0x000000); // 呼吸消失的颜色
- // this.sharedOutlinePass.clear = true;
- this.sharedComposer.addPass(this.sharedOutlinePass);
- // 创建共享的UnrealBloomPass
- this.sharedBloomPass = new UnrealBloomPass();
- this.sharedBloomPass.strength = 0.1;
- this.sharedBloomPass.radius = 0;
- this.sharedBloomPass.threshold = 1;
- this.sharedComposer.addPass(this.sharedBloomPass);
- this.isComposerInitialized = true;
- // 监听窗口大小变化,更新后处理分辨率
- window.addEventListener('resize', () => {
- if (this.sharedOutlinePass) {
- this.sharedOutlinePass.resolution.set(window.innerWidth, window.innerHeight);
- }
- });
- }
- // 优化后的模型高亮方法
- outlineObj(selectedObjects) {
- // 初始化共享后处理系统
- this.initSharedPostProcessing();
- // 更新选中对象
- if (this.sharedOutlinePass) {
- this.sharedOutlinePass.selectedObjects = selectedObjects;
- // 如果之前有高亮对象,先清除
- if (this.outlineObjs.length > 0) {
- this.outlineObjs = [];
- }
- // 添加当前共享的composer到数组
- this.outlineObjs.push(this.sharedComposer);
- }
- }
- // 清理高亮效果
- clearHighlight() {
- if (this.sharedOutlinePass) {
- this.sharedOutlinePass.selectedObjects = [];
- this.outlineObjs = [];
- }
- }
- // 初始化加载模型方法
- init(loaded) {
- //初始化场景
- this.initScene();
- //初始化相机
- this.initCamera();
- //初始化渲染器
- this.initRender();
- // 创建灯光
- this.createLight();
- //初始化控制器,控制摄像头,控制器一定要在渲染器后
- this.initControls();
- this.setFBXModel(loaded);
- this.setRoomModel();
- //监听场景大小改变,跳转渲染尺寸
- window.addEventListener('resize', this.onWindowResizes.bind(this));
- //场景渲染
- this.sceneAnimation();
- // 添加鼠标点击事件
- window.addEventListener('click', this.onMouseClick.bind(this));
- }
- initGltfModel() {
- //初始化场景
- this.initScene();
- //初始化相机
- this.initCamera();
- //初始化渲染器
- this.initRender();
- // 创建灯光
- this.createLight();
- //初始化控制器,控制摄像头,控制器一定要在渲染器后
- this.initControls();
- this.setGlbModel();
- //监听场景大小改变,跳转渲染尺寸
- window.addEventListener('resize', this.onWindowResizes.bind(this));
- //场景渲染
- this.sceneAnimation();
- // 添加鼠标点击事件
- window.addEventListener('click', this.onMouseClick.bind(this));
- }
- onMouseClick(event) {
- // 计算鼠标在标准化设备坐标中的位置 (-1 to +1)
- this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
- this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
- // 更新射线
- this.raycaster.setFromCamera(this.mouse, this.camera);
- // 计算射线与场景中物体的交点
- const intersects = this.raycaster.intersectObjects(this.scene.children, true);
- if (intersects.length > 0) {
- const point = intersects[0].point;
- const object = intersects[0].object;
- console.log(`Clicked on object: ${object.name} at (${point.x}, ${point.y}, ${point.z})`);
- // 示例:点击时高亮对象
- // this.outlineObj([object]);
- }
- }
- //创建场景
- initScene() {
- this.scene = new THREE.Scene();
- this.scene.background = new THREE.Color(0x000000);
- //底部平面
- const geometry = new THREE.PlaneGeometry(1, 1);
- // 创建材质,可以设置贴图
- const material = getBottomMaterial(
- '#ffffff', // 设置模型的颜色
- require('@/assets/images/models/bg.png'),
- 1,
- 5
- );
- // 创建平面网格
- const plane = new THREE.Mesh(geometry, material);
- plane.scale.set(1000, 1000, 1000);
- plane.position.y = 0;
- const plane2 = plane.clone();
- plane2.material = getBottomMaterial(
- '#000000', // 设置模型的颜色
- require('@/assets/images/models/bg.png'),
- 2,
- 5
- );
- this.scene.add(plane2);
- this.scene.add(plane);
- }
- // 创建相机
- initCamera() {
- const { clientHeight, clientWidth } = this.container;
- this.camera = new THREE.PerspectiveCamera(18, clientWidth / clientHeight, 0.1, 10000);
- }
- // 创建渲染器
- initRender() {
- this.renderer = new THREE.WebGLRenderer({
- logarithmicDepthBuffer: true,
- antialias: true, // true/false表示是否开启反锯齿
- alpha: true, // true/false 表示是否可以设置背景色透明
- // precision: 'mediump', // highp/mediump/lowp 表示着色精度选择
- // premultipliedAlpha: true // true/false 表示是否可以设置像素深度(用来度量图像的分辨率)
- // preserveDrawingBuffer: false, // true/false 表示是否保存绘图缓冲
- // physicallyCorrectLights: true, // true/false 表示是否开启物理光照
- });
- //设置屏幕像素比
- this.renderer.setPixelRatio(window.devicePixelRatio);
- //渲染的尺寸大小
- const { clientHeight, clientWidth } = this.container;
- this.renderer.setSize(clientWidth, clientHeight);
- this.renderer.toneMapping = Number(THREE.LinearToneMapping);
- this.renderer.toneMappingExposure = Math.pow(2, 0.0);
- this.container.appendChild(this.renderer.domElement);
- // 创建一个CSS3渲染器CSS3DRenderer
- this.css3DRenderer = new CSS3DRenderer();
- this.css3DRenderer.setSize(clientWidth, clientHeight);
- // HTML标签<div id="tag"></div>外面父元素叠加到canvas画布上且重合
- this.css3DRenderer.domElement.style.position = 'absolute';
- this.css3DRenderer.domElement.style.top = '0px';
- this.css3DRenderer.domElement.style.zIndex = '9999';
- //设置.pointerEvents=none,解决HTML元素标签对threejs canvas画布鼠标事件的遮挡
- this.css3DRenderer.domElement.style.pointerEvents = 'none';
- this.css3DRenderer.domElement.style.className = 'css3DRenderer';
- this.container.appendChild(this.css3DRenderer.domElement);
- }
- // 创建光源
- createLight() {
- const light1 = new THREE.AmbientLight('#FFFFFF', 1);
- this.scene.add(light1);
- const light2 = new THREE.DirectionalLight('#FFFFFF', 0.8 * Math.PI);
- light2.position.set(0.5, 0, 0.866); // ~60º
- this.scene.add(light2);
- }
- initControls() {
- this.controls = new OrbitControls(this.camera, this.renderer.domElement);
- this.controls.maxPolarAngle = Math.PI / 1.1;
- this.controls.minPolarAngle = Math.PI / 2;
- this.controls.minAzimuthAngle = 0;
- this.controls.maxAzimuthAngle = 0;
- this.controls.maxDistance = 2000;
- //标签控制器
- this.css3dControls = new OrbitControls(this.camera, this.css3DRenderer.domElement);
- this.css3dControls.enablePan = false;
- this.css3dControls.enableDamping = true;
- this.controls.mouseButtons = {
- LEFT: THREE.MOUSE.PAN,
- MIDDLE: THREE.MOUSE.DOLLY,
- RIGHT: THREE.MOUSE.ROTATE
- };
- }
- animate() {
- this.renderer.render(this.scene, this.camera);
- this.controls.update();
- this.css3DRenderer.render(this.scene, this.camera);
- TWEEN.update();
- if (this.model && this.autoRotate) {
- this.model.rotation.z += 0.001; // 每帧绕y轴旋转0.01弧度
- }
- }
- // 使用动画器不断更新场景
- sceneAnimation() {
- this.renderer.setAnimationLoop(this.animate.bind(this));
- }
- flyTo(target, scale = 1, duration = 1000) {
- const targetPosition = new THREE.Vector3().copy(target.position);
- const targetFocus = new THREE.Vector3().copy(target.targetContent);
- // 移动相机位置
- new TWEEN.Tween(this.camera.position)
- .to(targetPosition, target.time || 500)
- .easing(TWEEN.Easing.Quadratic.InOut)
- .onUpdate(() => {
- this.controls.update(); // 更新控制器
- })
- .start();
- // 移动控制器目标
- new TWEEN.Tween(this.controls.target)
- .to(targetFocus, target.time || 500)
- .easing(TWEEN.Easing.Quadratic.InOut)
- .onUpdate(() => {
- this.controls.update(); // 更新控制器
- })
- .start();
- new TWEEN.Tween(this.model.scale)
- .to(
- {
- x: scale,
- y: scale,
- z: scale
- },
- duration
- )
- .easing(TWEEN.Easing.Quadratic.InOut)
- .start();
- }
- tag3D(name) {
- // 创建div元素(作为标签)
- let div = document.createElement('div');
- div.innerHTML = name;
- div.classList.add('tag3d');
- //div元素包装为CSS3模型对象CSS3DObject
- let label = new CSS3DSprite(div);
- div.style.pointerEvents = 'none'; //避免HTML标签遮挡三维场景的鼠标事件
- //缩放CSS3DObject模型对象
- label.scale.set(0.15, 0.15, 0.15); //根据相机渲染范围控制HTML 3D标签尺寸
- label.rotateY(Math.PI / 2); //控制HTML标签CSS3对象姿态角度
- return label; //返回CSS3模型标签
- }
- createDialog(html) {
- //div元素包装为CSS3模型对象CSS3DSprite
- const element = document.createElement('div');
- element.className = 'customDialog';
- element.appendChild(html);
- const dialog = new CSS3DSprite(element);
- element.style.pointerEvents = 'none'; //避免HTML标签遮挡三维场景的鼠标事件
- // 设置HTML元素标签在three.js世界坐标中位置
- // label.position.set(x, y, z);
- //缩放CSS3DSprite模型对象
- dialog.scale.set(0.15, 0.15, 0.15); //根据相机渲染范围控制HTML 3D标签尺寸
- dialog.rotateY(Math.PI / 2); //控制HTML标签CSS3对象姿态角度
- return dialog; //返回CSS3模型标签
- }
- /**
- * 设置加载模型居中
- * {Object} object 模型对象
- */
- setModelPosition(object) {
- object.updateMatrixWorld();
- // 获得包围盒得min和max
- const box = new THREE.Box3().setFromObject(object);
- // 返回包围盒的中心点
- const center = box.getCenter(new THREE.Vector3());
- object.position.x += object.position.x - center.x;
- object.position.y += object.position.y - center.y;
- object.position.z = 0;
- }
- setGlbModel() {
- new GltfModelManager().loadModel('model-road-pv', '/models/model-road-pv.glb').then(object => {
- this.calcMeshCenter(object.scene);
- this.model = object.scene;
- const selectedObjects = [];
- object.scene.traverse(function (child) {
- if (child.isMesh) {
- child.material.emissive = child.material.color;
- child.material.emissiveMap = child.material.map;
- }
- // console.log(child.name)
- // if (["solar_cell097001", "solar_cell097002", "solar_cell097003", "solar_cell097004"].includes(child.name)) {
- // selectedObjects.push(child);
- // }
- });
- // 设置相机位置
- this.camera.position.set(-20, -652, 500);
- this.controls.target.set(-20, -20, 0);
- this.model.scale.set(2, 2, 2);
- // 设置相机坐标系
- this.camera.lookAt(0, 0, 0);
- // 将模型添加到场景中去
- this.scene.add(this.model);
- // 高亮收集的对象
- // if (selectedObjects.length > 0) {
- // this.outlineObj(selectedObjects);
- // }
- });
- }
- setFBXModel(loaded) {
- var fbxLoader = new GLTFLoader();
- fbxLoader.load('/models/main.glb', object => {
- this.calcMeshCenter(object.scene);
- this.model = object.scene;
- object.scene.traverse(function (child) {
- if (child.isMesh) {
- child.material.emissive = child.material.color;
- child.material.emissiveMap = child.material.map;
- }
- });
- // 设置相机位置
- this.camera.position.set(-17.701772776272723, -728.8405392761424, 374.20604159261217);
- this.controls.target.set(-17.701772776272723, -307.6331500346563, 148.44436360369687);
- this.model.scale.set(0.8, 0.8, 0.8);
- // 设置相机坐标系
- this.camera.lookAt(0, 0, 0);
- // 将模型添加到场景中去
- this.scene.add(this.model);
- loaded&&loaded()
- });
- }
- setModelDisplay(type, areaInfo) {
- if (type == 'model1' || type == 'model2') {
- this.camera.position.set(2.9287885309866817, -403.9781890137868, 376.33849737114133);
- this.controls.target.set(-22.9298060627659623, 15.311141772539067, 64.637911374941055);
- const targetFocus = new THREE.Vector3(-22.9298060627659623, 15.311141772539067, 24.637911374941055);
- new TWEEN.Tween(this.controls.target)
- .to(targetFocus, 500)
- .easing(TWEEN.Easing.Quadratic.InOut)
- .onUpdate(() => {
- this.controls.update(); // 更新控制器
- })
- .start();
- this.addRoomDialog(
- type,
- ComponentHandle.createComponent({
- component: usageReal,
- props: {
- areaCode: areaInfo.value
- }
- }),
- `${areaInfo.name}${type == 'model1' ? '负一楼水泵' : '室内主机'}`,
- type == 'model1'
- ? {
- x: -3,
- y: 8,
- z: 0
- }
- : {
- x: -3,
- y: 3,
- z: 0
- }
- );
- }
- if (type == 'model1') {
- new TWEEN.Tween(this.model1.scale)
- .to(
- {
- x: 10,
- y: 10,
- z: 10
- },
- 500
- )
- .easing(TWEEN.Easing.Quadratic.InOut)
- .start();
- }
- if (type == 'model2') {
- new TWEEN.Tween(this.model2.scale)
- .to(
- {
- x: 15,
- y: 15,
- z: 15
- },
- 500
- )
- .easing(TWEEN.Easing.Quadratic.InOut)
- .start();
- }
- }
- modelChange(type, areaInfo) {
- if (this.roomLabelGroup) {
- this.model1.remove(this.roomLabelGroup);
- this.model2.remove(this.roomLabelGroup);
- this.roomLabelGroup.clear();
- }
- this.setModelDisplay(type, areaInfo);
- if (type == 'model') {
- this.model1.visible = false;
- this.model2.visible = false;
- this.model.visible = true;
- this.labels.forEach(item => {
- item.visible = true;
- });
- } else if (type == 'model1') {
- this.model.visible = false;
- this.labels.forEach(item => {
- item.visible = false;
- });
- this.model2.visible = false;
- this.model1.visible = true;
- } else if (type == 'model2') {
- this.model.visible = false;
- this.labels.forEach(item => {
- item.visible = false;
- });
- this.model1.visible = false;
- this.model2.visible = true;
- }
- }
- setRoomModel() {
- const loader = new GLTFLoader();
- loader.setDRACOLoader(dracoLoader);
- loader.load('/models/model1.glb', object => {
- this.calcMeshCenter(object.scene);
- const perfectMaterial = new THREE.MeshStandardMaterial({
- color: 0xcccccc, //
- side: THREE.DoubleSide // 防止面片反向不可见
- });
- this.model1 = object.scene;
- this.model1.scale.set(10, 10, 10);
- this.model1.rotation.x = Math.PI / 2;
- this.model1.rotation.y = Math.PI / 6;
- // 将模型添加到场景中去
- this.model1.visible = false;
- this.scene.add(this.model1);
- });
- loader.load('/models/model2.glb', object => {
- this.calcMeshCenter(object.scene);
- object.scene.traverse(child => {
- if (child.isMesh) {
- if (child.name == '平面' || child.name == '平面001') {
- child.material = new THREE.MeshStandardMaterial({
- color: '#B0C4E9',
- side: THREE.DoubleSide
- });
- }
- child.material.needsUpdate = true;
- }
- });
- this.model2 = object.scene;
- this.model2.scale.set(15, 15, 15);
- this.model2.rotation.x = Math.PI / 2;
- this.model2.rotation.y = -Math.PI / 3;
- // 将模型添加到场景中去
- this.model2.visible = false;
- this.scene.add(this.model2);
- });
- }
- onWindowResizes() {
- if (!this.container) return false;
- const { clientHeight, clientWidth } = this.container;
- //调整屏幕大小
- this.camera.aspect = clientWidth / clientHeight; // 摄像机宽高比例
- this.camera.updateProjectionMatrix(); //相机更新矩阵,将3d内容投射到2d面上转换
- this.renderer.setSize(clientWidth, clientHeight);
- this.css3DRenderer.setSize(clientWidth, clientHeight);
- }
- calcMeshCenter(group) {
- /**
- * 包围盒全自动计算:模型整体居中
- */
- var box3 = new THREE.Box3();
- // 计算层级模型group的包围盒
- // 模型group是加载一个三维模型返回的对象,包含多个网格模型
- box3.expandByObject(group);
- // 计算一个层级模型对应包围盒的几何体中心在世界坐标中的位置
- var center = new THREE.Vector3();
- box3.getCenter(center);
- // 重新设置模型的位置,使之居中。
- group.position.x = 0;
- group.position.y = 0;
- group.position.z = 17;
- }
- addRoomDialog(modelType, html, labelName, position) {
- const { x, y, z } = position;
- if (this.roomLabelGroup && this[modelType]) {
- this[modelType].remove(this.roomLabelGroup);
- this.roomLabelGroup.clear();
- }
- const label3D = this.tag3D(labelName);
- const dialog3D = this.createDialog(html);
- if (modelType == 'model1') {
- label3D.scale.set(0.015, 0.015, 0.015);
- dialog3D.scale.set(0.015, 0.015, 0.15);
- label3D.position.set(x - 0.2, y - 1.8, z);
- } else {
- label3D.position.set(x - 0.2, y - 1.5, z);
- label3D.scale.set(0.011, 0.011, 0.011);
- dialog3D.scale.set(0.011, 0.011, 0.11);
- }
- // 3. 创建一个组来容纳它们
- const group = new THREE.Group();
- dialog3D.position.set(x, y, z);
- group.add(label3D);
- group.add(dialog3D);
- this[modelType].add(group);
- this.roomLabelGroup = group; // 保存引用
- }
- addDialog(html, labelName, position) {
- const { x, y, z } = position;
- const label3D = this.tag3D(labelName);
- const dialog3D = this.createDialog(html); //设置标签名称
- label3D.position.y += y;
- label3D.position.x += x;
- label3D.position.z += z;
- dialog3D.position.copy(label3D.position);
- dialog3D.position.z = z + 20;
- this.model && this.model.add(label3D);
- this.model && this.model.add(dialog3D);
- this.labels.push(label3D);
- this.labels.push(dialog3D);
- }
- }
- export default renderModel;
|