import * as THREE from 'three'; // import { setModalCenter } from '/@/utils/threejs/util'; import Smoke from '../../comment/threejs/Smoke'; import { CSS3DObject, CSS3DSprite } from 'three/examples/jsm/renderers/CSS3DRenderer'; import * as dat from 'dat.gui'; // const gui = new dat.GUI(); // gui.domElement.style = 'position:absolute;top:100px;left:10px;z-index:99999999999999'; class ModelContext { model; // modelName = 'jbfj-hd'; modelName = 'jbfj-dual'; /** 本模型的根3D对象 */ group?: THREE.Object3D; /** 本模型所包含的所有元素合集 */ private elements: unknown[] = []; /** 本模型支持的 Object3DGroup 模块 */ private modules: { /** 模块名称 */ name: string; /** 控制该模块所用的上下文 */ context: THREE.Object3D; /** 控制时行为声明 */ behavior: (context: THREE.Object3D) => void; }[] = []; constructor(model) { this.model = model; } addLight() { // optional implementation } /** 设置模型类型并切换,不同的类型通常对应不同的具体模型,在模型总控制器下的具体模型会根据传入的参数彼此交互、切换 */ setModelType(modelType: string, data: any) { this.modules.forEach(({ name, context, behavior }) => { if (name === modelType) { behavior(context); } }); } mountedThree() { return new Promise((resolve) => { this.model.setGLTFModel([this.modelName]).then(async (gltf) => { this.group = gltf[0]; if (this.group) { // this.group.scale.set(2, 2, 2); // setModalCenter(this.group); this.addLight(); this.setModelPosition(); this.initModules().then(resolve); } }); }); } destroy() { if (!this.model) return; this.elements.forEach((element) => { this.model.clearGroup(element); }); } // 设置模型位置 setModelPosition() { if (!this.group) return; this.group.scale.set(0.6, 0.6, 0.6); // const ff = gui.addFolder(`位置调整`); // ff.add(this.group.position, 'x', -100, 100); // ff.add(this.group.position, 'y', -100, 100); // ff.add(this.group.position, 'z', -100, 100); this.group.position.set(0, 0, -60); this.group.rotation.y = Math.PI / 2; } // hideElements(eles: THREE.Object3D[]) { // eles.forEach((g) => { // g.visible = false; // }); // } // showElements(eles: THREE.Object3D[]) { // eles.forEach((g) => { // g.visible = true; // }); // } weakElements(eles: unknown[]) { eles.forEach((g) => { if (g instanceof Smoke) { g.oldOpacityFactor = 0.4; } if (g instanceof CSS3DObject) { g.element.style.setProperty('opacity', '0.5'); } }); } strongElements(eles: unknown[]) { eles.forEach((g) => { if (g instanceof Smoke) { g.oldOpacityFactor = 0.75; } if (g instanceof CSS3DObject) { g.element.style.setProperty('opacity', '1'); } }); } startAnimation(eles: unknown[]) { eles.forEach((g) => { if (g instanceof Smoke) { g.startSmoke(); } }); } stopAnimation(eles: unknown[]) { const smokes = eles.filter((g) => { return g instanceof Smoke; }); return Promise.all(smokes.map((e) => e.stopSmoke())); } /** 核心方法,初始化本模型的各个模块,这些模块可以实现特定场景的展示、控制等功能 */ async initModules() { if (this.elements.length > 0) return; // 右侧风机-主风机进风 const curveFan1Right = this.generateSmokePath(['dian', 'dian1', 'dian2', 'dian3', 'dian4', 'dian5', 'dian6', 'dian7', 'dian8'], true); // 右侧风机-备风机进风 const curveFan2Right = this.generateSmokePath(['dian9', 'dian10', 'dian2', 'dian3', 'dian4', 'dian5', 'dian6', 'dian7', 'dian8'], true); // 左侧风机-主风机进风 const curveFan1Left = this.generateSmokePath(['dian11', 'dian12', 'dian15', 'dian16', 'dian17'], true); // 左侧风机-备风机进风 const curveFan2Left = this.generateSmokePath(['dian14', 'dian13', 'dian15', 'dian16', 'dian17'], true); // 右侧巷道-回风前段 const curveTunnelRight = this.generateSmokePath(['dian18', 'dian19', 'dian20']); // 左侧巷道-回风前段 const curveTunnelLeft = this.generateSmokePath(['dian20', 'dian22']); // 左侧巷道-回风全长 const curveTunnelMajor = this.generateSmokePath(['dian21', 'dian22']); const group1 = new THREE.Group(); const smokeFan1Right = new Smoke('/model/img/texture-smoke.png', '#ffffff', 10, 0.75, 0.5, 400); smokeFan1Right.setPath(curveFan1Right); this.elements.push(smokeFan1Right); const smokeFan2Right = new Smoke('/model/img/texture-smoke.png', '#ffffff', 10, 0.75, 0.5, 400); smokeFan2Right.setPath(curveFan2Right); this.elements.push(smokeFan2Right); const smokeFan1Left = new Smoke('/model/img/texture-smoke.png', '#ffffff', 10, 0.75, 0.5, 400); smokeFan1Left.setPath(curveFan1Left); this.elements.push(smokeFan1Left); const smokeFan2Left = new Smoke('/model/img/texture-smoke.png', '#ffffff', 10, 0.75, 0.5, 400); smokeFan2Left.setPath(curveFan2Left); this.elements.push(smokeFan2Left); const smokeTunnelRight = new Smoke('/model/img/texture-smoke.png', '#ffffff', 0, 0.35, 1.5, 200); smokeTunnelRight.setPath(curveTunnelRight); this.elements.push(smokeTunnelRight); const smokeTunnelLeft = new Smoke('/model/img/texture-smoke.png', '#ffffff', 0, 0.35, 1.5, 200); smokeTunnelLeft.setPath(curveTunnelLeft); this.elements.push(smokeTunnelLeft); const smokeTunnelMajor = new Smoke('/model/img/texture-smoke.png', '#ffffff', 0, 0.35, 1.5, 200); smokeTunnelMajor.setPath(curveTunnelMajor); this.elements.push(smokeTunnelMajor); await smokeFan1Right.setPoints(); this.group?.add(smokeFan1Right.points); await smokeFan2Right.setPoints(); this.group?.add(smokeFan2Right.points); await smokeFan1Left.setPoints(); this.group?.add(smokeFan1Left.points); await smokeFan2Left.setPoints(); this.group?.add(smokeFan2Left.points); await smokeTunnelRight.setPoints(); this.group?.add(smokeTunnelRight.points); await smokeTunnelLeft.setPoints(); this.group?.add(smokeTunnelLeft.points); await smokeTunnelMajor.setPoints(); this.group?.add(smokeTunnelMajor.points); const fanRightSelectors = [ { query: '#inputBox2', position: [-70, 0, -15], scale: 0.1, }, { query: '#T1_1', position: [100, 15, -42], scale: 0.2, }, { query: '#T1_2', position: [30, 15, -37], scale: 0.175, }, ]; const fanLeftSelectors = [ { query: '#inputBox3', position: [-70, 0, 36], scale: 0.1, }, { query: '#T2_1', position: [110, 15, -80], scale: 0.2, }, { query: '#T2_2', position: [47, 15, -74], scale: 0.175, }, ]; const commonSelectors = [ { query: '#T3', position: [-40, 15, -65], scale: 0.15, }, ]; const fanLeftSprites = this.initCssElement(fanLeftSelectors); const fanRightSprites = this.initCssElement(fanRightSelectors); const commonSprites = this.initCssElement(commonSelectors); // 双巷道的通风机都开启有4种情况 this.modules.push({ name: 'fan1RightOpen&fan1LeftOpen', context: group1, behavior: () => { // this.weakElements(this.elements); this.stopAnimation(this.elements).then(() => { // this.strongElements(); this.startAnimation([smokeFan1Right, smokeFan1Left, smokeTunnelRight, smokeTunnelMajor]); }); }, }); this.modules.push({ name: 'fan2RightOpen&fan1LeftOpen', context: group1, behavior: () => { this.stopAnimation(this.elements).then(() => { this.startAnimation([smokeFan2Right, smokeFan1Left, smokeTunnelRight, smokeTunnelMajor]); }); }, }); this.modules.push({ name: 'fan1RightOpen&fan2LeftOpen', context: group1, behavior: () => { this.stopAnimation(this.elements).then(() => { this.startAnimation([smokeFan1Right, smokeFan2Left, smokeTunnelRight, smokeTunnelMajor]); }); }, }); this.modules.push({ name: 'fan2RightOpen&fan2LeftOpen', context: group1, behavior: () => { this.stopAnimation(this.elements).then(() => { this.startAnimation([smokeFan2Right, smokeFan2Left, smokeTunnelRight, smokeTunnelMajor]); }); }, }); // 只有一个风机启动有4种情况 this.modules.push({ name: 'fan1RightOpen', context: group1, behavior: () => { this.stopAnimation(this.elements).then(() => { this.startAnimation([smokeFan1Right, smokeTunnelMajor]); }); }, }); this.modules.push({ name: 'fan2RightOpen', context: group1, behavior: () => { this.stopAnimation(this.elements).then(() => { this.startAnimation([smokeFan2Right, smokeTunnelMajor]); }); }, }); this.modules.push({ name: 'fan1LeftOpen', context: group1, behavior: () => { this.stopAnimation(this.elements).then(() => { this.startAnimation([smokeFan1Left, smokeTunnelRight, smokeTunnelLeft]); }); }, }); this.modules.push({ name: 'fan2LeftOpen', context: group1, behavior: () => { this.stopAnimation(this.elements).then(() => { this.startAnimation([smokeFan2Left, smokeTunnelRight, smokeTunnelLeft]); }); }, }); // 只有一个风机启动有2种告示牌情况 this.modules.push({ name: 'fanLeftStrong', context: group1, behavior: () => { this.weakElements(this.elements); this.strongElements([...fanLeftSprites, ...commonSprites]); }, }); this.modules.push({ name: 'fanRightStrong', context: group1, behavior: () => { this.weakElements(this.elements); this.strongElements([...fanRightSprites, ...commonSprites]); }, }); } /** 初始化css元素,将css元素选择器传入,该方法会将这些元素按顺序放入传入的锚点中 */ initCssElement(selectors: { query: string; position: number[]; scale: number }[]) { const arr: CSS3DSprite[] = []; selectors.forEach(({ query, position, scale }) => { const element = document.querySelector(query) as HTMLElement; if (element) { const css3D = new CSS3DSprite(element); this.elements.push(css3D); arr.push(css3D); css3D.name = query; css3D.scale.set(scale, scale, scale); // const ff = gui.addFolder(`css元素${query}`); // ff.add(css3D.position, 'x', -100, 100); // ff.add(css3D.position, 'y', -100, 100); // ff.add(css3D.position, 'z', -100, 100); css3D.position.set(position[0], position[1], position[2]); this.group?.add(css3D); } }); return arr; } /** 生成适用于 Smoke 的曲线数据,输入途径点,输出路径,如果是进风类型,首个线段将有扩散效果,出风则是末尾线段有扩散效果 */ generateSmokePath(namelist: string[], airIn?: boolean, airOut?: boolean) { if (!this.group) return; const result: any[] = []; const points: THREE.Vector3[] = namelist.map((name) => { const obj = this.group?.getObjectByName(name); return obj?.position as THREE.Vector3; }); for (let index = 1; index < points.length; index++) { const path0 = points[index - 1]; const path1 = points[index]; const path = { path0, path1, isSpread: false, spreadDirection: 0, }; if (airIn) { // 首个线段需要扩散,由大变小 path.isSpread = index === 1; path.spreadDirection = -1; } if (airOut) { // 首个线段需要扩散,由小变大 path.isSpread = index === points.length - 1; path.spreadDirection = 1; } if (!airIn && !airOut) { path.isSpread = false; path.spreadDirection = 1; } result.push(path); } return result; } } export default ModelContext;