|
|
@@ -0,0 +1,651 @@
|
|
|
+import * as THREE from 'three';
|
|
|
+import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
|
|
|
+import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
|
|
|
+import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
|
|
|
+import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';
|
|
|
+import { OutputPass } from 'three/examples/jsm/postprocessing/OutputPass.js';
|
|
|
+import { PathPointList, PathGeometry } from 'three.path';
|
|
|
+
|
|
|
+export function modalAnimate(modal) {
|
|
|
+ modal.renderer.toneMappingExposure = 1.2;
|
|
|
+
|
|
|
+ console.log('初始化模型', modal);
|
|
|
+ modal.isRender = true;
|
|
|
+
|
|
|
+ // 【关键步骤】设置旋转速度
|
|
|
+ // 默认是 1.0,设置为 0.5 会让移动幅度减半(更慢/更精细)
|
|
|
+ modal.orbitControls.rotateSpeed = 0.5;
|
|
|
+
|
|
|
+ // 可选:如果你也想减慢缩放和平移的速度
|
|
|
+ modal.orbitControls.zoomSpeed = 0.5; // 缩放速度
|
|
|
+ modal.orbitControls.panSpeed = 0.5; // 平移速度
|
|
|
+
|
|
|
+ // 启用阻尼效果(可选,让运动更有惯性,感觉更平滑)
|
|
|
+ modal.orbitControls.enableDamping = true;
|
|
|
+ modal.orbitControls.dampingFactor = 0.9;
|
|
|
+
|
|
|
+ const bounds = {
|
|
|
+ min: new THREE.Vector3(-25, -2, -35),
|
|
|
+ max: new THREE.Vector3(10, 10, 30),
|
|
|
+ };
|
|
|
+
|
|
|
+ // 初始化多种颜色气泡
|
|
|
+ const allBubbles = initGasAnimate(modal);
|
|
|
+
|
|
|
+ const zhuqiModal = modal.scene.getObjectByName('zhuqi').getObjectByName('ZhuQiChangJing_1');
|
|
|
+ const touming3 = zhuqiModal.getObjectByName('touming3');
|
|
|
+ touming3.parent.remove(touming3);
|
|
|
+
|
|
|
+ const time = Date.now() * 0.001;
|
|
|
+ const clock = new THREE.Clock();
|
|
|
+
|
|
|
+ const bloomObj = addZhuqiAnimate(zhuqiModal, modal);
|
|
|
+ const arrowObj = addArrow(zhuqiModal);
|
|
|
+ const unitObj = addUnit(zhuqiModal);
|
|
|
+
|
|
|
+ // 帧刷新
|
|
|
+ modal.startAnimation = () => {
|
|
|
+ /**气泡运动 - 遍历所有颜色组*/
|
|
|
+ allBubbles.forEach((bubbleGroup) => {
|
|
|
+ const { bubblesData, instancedMesh, bubbleCount, dummy } = bubbleGroup;
|
|
|
+
|
|
|
+ for (let i = 0; i < bubbleCount; i++) {
|
|
|
+ const data = bubblesData[i];
|
|
|
+
|
|
|
+ // 1. 更新位置
|
|
|
+ data.position.add(data.velocity);
|
|
|
+
|
|
|
+ // 2. 添加正弦波动
|
|
|
+ data.position.y += Math.sin(time + i) * 0.02;
|
|
|
+
|
|
|
+ // 3. 边界检测与反弹
|
|
|
+ if (data.position.x < bounds.min.x || data.position.x > bounds.max.x) {
|
|
|
+ data.velocity.x *= -1;
|
|
|
+ data.position.x = THREE.MathUtils.clamp(data.position.x, bounds.min.x, bounds.max.x);
|
|
|
+ }
|
|
|
+ if (data.position.y < bounds.min.y || data.position.y > bounds.max.y) {
|
|
|
+ data.velocity.y *= -1;
|
|
|
+ data.position.y = THREE.MathUtils.clamp(data.position.y, bounds.min.y, bounds.max.y);
|
|
|
+ }
|
|
|
+ if (data.position.z < bounds.min.z || data.position.z > bounds.max.z) {
|
|
|
+ data.velocity.z *= -1;
|
|
|
+ data.position.z = THREE.MathUtils.clamp(data.position.z, bounds.min.z, bounds.max.z);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 更新实例矩阵
|
|
|
+ dummy.position.copy(data.position);
|
|
|
+ dummy.scale.setScalar(data.scale);
|
|
|
+ dummy.rotation.y = time * 0.5;
|
|
|
+ dummy.rotation.z = time * 0.2;
|
|
|
+ dummy.updateMatrix();
|
|
|
+ instancedMesh.setMatrixAt(i, dummy.matrix);
|
|
|
+ }
|
|
|
+ instancedMesh.instanceMatrix.needsUpdate = true;
|
|
|
+ });
|
|
|
+
|
|
|
+ /**自发光*/
|
|
|
+ if (bloomObj) {
|
|
|
+ // const { zhuqiPips, bloomComposer, finalComposer, materials, darkMaterial, shader, bloomLayer } = bloomObj;
|
|
|
+ // modal.scene.traverse((item) => {
|
|
|
+ // darkenNonBloomed(item, bloomLayer, materials, darkMaterial);
|
|
|
+ // });
|
|
|
+ // bloomComposer.render();
|
|
|
+ // modal.scene.traverse((item) => {
|
|
|
+ // restoreMaterial(item, materials);
|
|
|
+ // });
|
|
|
+ // finalComposer.render();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**箭头流动效果*/
|
|
|
+ if (arrowObj) {
|
|
|
+ const { texture } = arrowObj;
|
|
|
+ if (texture) {
|
|
|
+ texture.offset.x -= 0.1776;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (unitObj) {
|
|
|
+ const { arrowTexture } = unitObj;
|
|
|
+ arrowTexture.offset.x -= 0.0576;
|
|
|
+ }
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+function initGasAnimate(modal, colorConfigs: any[] = []) {
|
|
|
+ // 默认配置:如果没有传入颜色配置,使用默认的红蓝两种
|
|
|
+ if (colorConfigs.length === 0) {
|
|
|
+ colorConfigs = [
|
|
|
+ {
|
|
|
+ name: 'gas',
|
|
|
+ color: 0xffa2a244,
|
|
|
+ emissive: 0xff4400,
|
|
|
+ bubbleCount: 200,
|
|
|
+ hslHue: 0.4, // 红色
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: 'n2',
|
|
|
+ color: 0x4fedf788,
|
|
|
+ emissive: 0x0044aa,
|
|
|
+ bubbleCount: 500,
|
|
|
+ hslHue: 0.5, // 蓝色
|
|
|
+ },
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 1. 定义容器边界 (矩形)
|
|
|
+ const bounds = {
|
|
|
+ min: new THREE.Vector3(-25, -2, -35),
|
|
|
+ max: new THREE.Vector3(10, 10, 30),
|
|
|
+ };
|
|
|
+
|
|
|
+ const allBubbles = [] as {
|
|
|
+ bubblesData: { position: THREE.Vector3; velocity: THREE.Vector3; scale: number }[];
|
|
|
+ instancedMesh: THREE.InstancedMesh;
|
|
|
+ bubbleCount: number;
|
|
|
+ dummy: THREE.Object3D;
|
|
|
+ }[];
|
|
|
+
|
|
|
+ // 2. 为每种颜色配置创建气泡
|
|
|
+ colorConfigs.forEach((config) => {
|
|
|
+ const { bubbleCount, color, emissive, hslHue } = config;
|
|
|
+ const geometry = new THREE.SphereGeometry(1, 16, 16);
|
|
|
+
|
|
|
+ // 创建材质
|
|
|
+ const material = new THREE.MeshPhysicalMaterial({
|
|
|
+ color: color,
|
|
|
+ emissive: emissive,
|
|
|
+ emissiveIntensity: 0.4,
|
|
|
+ transparent: true,
|
|
|
+ opacity: 0.1,
|
|
|
+ depthWrite: false,
|
|
|
+ roughness: 0.0,
|
|
|
+ metalness: 0.1,
|
|
|
+ clearcoat: 1.0,
|
|
|
+ clearcoatRoughness: 0.1,
|
|
|
+ transmission: 0.7,
|
|
|
+ thickness: 0.5,
|
|
|
+ ior: 1.0004,
|
|
|
+ // blending: THREE.AdditiveBlending,
|
|
|
+ side: THREE.DoubleSide,
|
|
|
+ });
|
|
|
+
|
|
|
+ // 创建实例化网格
|
|
|
+ const instancedMesh = new THREE.InstancedMesh(geometry, material, bubbleCount);
|
|
|
+ modal.scene.add(instancedMesh);
|
|
|
+
|
|
|
+ // 存储气泡运动数据
|
|
|
+ const dummy = new THREE.Object3D();
|
|
|
+ const bubblesData = [] as { position: THREE.Vector3; velocity: THREE.Vector3; scale: number }[];
|
|
|
+
|
|
|
+ for (let i = 0; i < bubbleCount; i++) {
|
|
|
+ const x = THREE.MathUtils.randFloat(bounds.min.x, bounds.max.x);
|
|
|
+ const y = THREE.MathUtils.randFloat(bounds.min.y, bounds.max.y);
|
|
|
+ const z = THREE.MathUtils.randFloat(bounds.min.z, bounds.max.z);
|
|
|
+ const scale = THREE.MathUtils.randFloat(0.2, 0.8);
|
|
|
+ const velocity = new THREE.Vector3(
|
|
|
+ THREE.MathUtils.randFloat(-0.05, 0.05),
|
|
|
+ THREE.MathUtils.randFloat(-0.05, 0.05),
|
|
|
+ THREE.MathUtils.randFloat(-0.05, 0.05)
|
|
|
+ );
|
|
|
+
|
|
|
+ bubblesData.push({ position: new THREE.Vector3(x, y, z), velocity, scale });
|
|
|
+
|
|
|
+ dummy.position.copy(bubblesData[i].position);
|
|
|
+ dummy.scale.setScalar(bubblesData[i].scale);
|
|
|
+ dummy.updateMatrix();
|
|
|
+ instancedMesh.setMatrixAt(i, dummy.matrix);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置实例颜色
|
|
|
+ const colorObj = new THREE.Color();
|
|
|
+ for (let i = 0; i < bubbleCount; i++) {
|
|
|
+ colorObj.setHSL(hslHue, 0.5, 0.8 + Math.random() * 0.2);
|
|
|
+ instancedMesh.setColorAt(i, colorObj);
|
|
|
+ }
|
|
|
+ instancedMesh.instanceColor.needsUpdate = true;
|
|
|
+
|
|
|
+ allBubbles.push({ bubblesData, instancedMesh, bubbleCount, dummy });
|
|
|
+ });
|
|
|
+
|
|
|
+ return allBubbles;
|
|
|
+}
|
|
|
+
|
|
|
+function addZhuqiAnimate(zhuqiModal, modal) {
|
|
|
+ return null;
|
|
|
+ const renderer = modal.renderer;
|
|
|
+
|
|
|
+ const renderScene = new RenderPass(modal.scene, modal.camera);
|
|
|
+ const BLOOM_SCENE = 1;
|
|
|
+
|
|
|
+ const bloomLayer = new THREE.Layers();
|
|
|
+ bloomLayer.set(BLOOM_SCENE);
|
|
|
+
|
|
|
+ const params = {
|
|
|
+ threshold: 0,
|
|
|
+ strength: 0,
|
|
|
+ radius: 0.001,
|
|
|
+ exposure: 0,
|
|
|
+ };
|
|
|
+
|
|
|
+ const materials = {};
|
|
|
+
|
|
|
+ const darkMaterial = new THREE.MeshBasicMaterial({ color: 'black' });
|
|
|
+
|
|
|
+ const bloomPass = new UnrealBloomPass(new THREE.Vector2(renderer.domElement.innerWidth, renderer.domElement.innerHeight), 1.5, 0.4, 0.85);
|
|
|
+ bloomPass.threshold = params.threshold;
|
|
|
+ bloomPass.strength = params.strength;
|
|
|
+ bloomPass.radius = params.radius;
|
|
|
+
|
|
|
+ const bloomRenderTarget = new THREE.WebGLRenderTarget(renderer.domElement.innerWidth, renderer.domElement.innerHeight, {
|
|
|
+ type: THREE.HalfFloatType,
|
|
|
+ });
|
|
|
+ const bloomComposer = new EffectComposer(renderer, bloomRenderTarget);
|
|
|
+ bloomComposer.renderToScreen = false;
|
|
|
+ bloomComposer.addPass(renderScene);
|
|
|
+ bloomComposer.addPass(bloomPass);
|
|
|
+ const shader = new THREE.ShaderMaterial({
|
|
|
+ uniforms: {
|
|
|
+ baseTexture: { value: null },
|
|
|
+ bloomTexture: { value: bloomComposer.renderTarget2.texture },
|
|
|
+ bloomStrength: { value: params.strength },
|
|
|
+ },
|
|
|
+ vertexShader: `varying vec2 vUv;
|
|
|
+
|
|
|
+ void main() {
|
|
|
+
|
|
|
+ vUv = uv;
|
|
|
+
|
|
|
+ gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
|
|
+
|
|
|
+ }`,
|
|
|
+ fragmentShader: `uniform sampler2D baseTexture;
|
|
|
+ uniform sampler2D bloomTexture;
|
|
|
+ uniform float bloomStrength;
|
|
|
+
|
|
|
+ varying vec2 vUv;
|
|
|
+
|
|
|
+ void main() {
|
|
|
+
|
|
|
+ gl_FragColor = ( texture2D( baseTexture, vUv ) + texture2D( bloomTexture, vUv ) * bloomStrength );
|
|
|
+
|
|
|
+ }`,
|
|
|
+ defines: {},
|
|
|
+ });
|
|
|
+
|
|
|
+ const mixPass = new ShaderPass(shader, 'baseTexture');
|
|
|
+ mixPass.needsSwap = true;
|
|
|
+
|
|
|
+ const outputPass = new OutputPass();
|
|
|
+
|
|
|
+ const finalRenderTarget = new THREE.WebGLRenderTarget(modal.canvasContainer.clientWidth, modal.canvasContainer.clientHeight, {
|
|
|
+ type: THREE.HalfFloatType,
|
|
|
+ samples: 4,
|
|
|
+ });
|
|
|
+ const finalComposer = new EffectComposer(renderer, finalRenderTarget);
|
|
|
+ finalComposer.addPass(renderScene);
|
|
|
+ finalComposer.addPass(mixPass);
|
|
|
+ finalComposer.addPass(outputPass);
|
|
|
+
|
|
|
+ // 注气动画
|
|
|
+ const changjingzhuqiguan = zhuqiModal.getObjectByName('changjingzhuqiguan');
|
|
|
+ if (changjingzhuqiguan) {
|
|
|
+ const zhuqiPips = changjingzhuqiguan.children.filter((item) => {
|
|
|
+ return item.name.includes('changjingwire');
|
|
|
+ });
|
|
|
+ zhuqiPips.forEach((item) => {
|
|
|
+ // modal.scene.add(item);
|
|
|
+ const material = new THREE.MeshStandardMaterial({ color: '#f00', roughness: 1, metalness: 1 });
|
|
|
+ item.material = material;
|
|
|
+ item.layers.enable(BLOOM_SCENE);
|
|
|
+ });
|
|
|
+
|
|
|
+ return { zhuqiPips, bloomComposer, finalComposer, materials, darkMaterial, shader, bloomLayer };
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+}
|
|
|
+
|
|
|
+function addArrow1(zhuqiModal) {
|
|
|
+ const changjingzhuqiguan = zhuqiModal.getObjectByName('changjing5');
|
|
|
+ if (changjingzhuqiguan) {
|
|
|
+ const zhuqiPips = changjingzhuqiguan.children.filter((item) => {
|
|
|
+ return item.name.includes('changjingcurve10');
|
|
|
+ });
|
|
|
+
|
|
|
+ const flowUniforms = {
|
|
|
+ uTime: { value: 0 },
|
|
|
+ uColor: { value: new THREE.Color(0x00aaff) }, // 流体颜色
|
|
|
+ uSpeed: { value: 0.5 }, // 流动速度
|
|
|
+ uBaseColor: { value: new THREE.Color(0x222222) }, // 管道底色
|
|
|
+ };
|
|
|
+
|
|
|
+ const flowMaterial = new THREE.ShaderMaterial({
|
|
|
+ uniforms: flowUniforms,
|
|
|
+ vertexShader: `
|
|
|
+ varying vec2 vUv;
|
|
|
+ void main() {
|
|
|
+ vUv = uv;
|
|
|
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
|
+ }
|
|
|
+ `,
|
|
|
+ fragmentShader: `
|
|
|
+ uniform float uTime;
|
|
|
+ uniform vec3 uColor;
|
|
|
+ uniform float uSpeed;
|
|
|
+ uniform vec3 uBaseColor;
|
|
|
+ varying vec2 vUv;
|
|
|
+
|
|
|
+ void main() {
|
|
|
+ // 核心:沿 V 轴 (长度方向) 移动纹理
|
|
|
+ // fract 实现无限循环
|
|
|
+ float flow = fract(vUv.y + uTime * uSpeed);
|
|
|
+
|
|
|
+ // 简单的条纹效果
|
|
|
+ float stripe = step(0.8, fract(flow * 5.0));
|
|
|
+
|
|
|
+ // 混合颜色:底色 + 流动的高亮色
|
|
|
+ vec3 finalColor = mix(uBaseColor, uColor, stripe * 0.8);
|
|
|
+
|
|
|
+ // 增加一点菲涅尔效果让它看起来更立体 (可选)
|
|
|
+ gl_FragColor = vec4(finalColor, 1.0);
|
|
|
+ }
|
|
|
+ `,
|
|
|
+ side: THREE.DoubleSide,
|
|
|
+ });
|
|
|
+
|
|
|
+ const material1 = new THREE.MeshStandardMaterial({
|
|
|
+ color: '#f00',
|
|
|
+ roughness: 1,
|
|
|
+ metalness: 1,
|
|
|
+ });
|
|
|
+ debugger;
|
|
|
+ zhuqiPips.forEach((item) => {
|
|
|
+ // const oldGeometry = item.geometry.clone();
|
|
|
+ // // const geometry = new THREE.TubeGeometry(oldGeometry.parameters.path, oldGeometry.parameters.tubularSegments, 0.05, 8, false);
|
|
|
+ // const newMesh = new THREE.Mesh(oldGeometry, flowMaterial.clone());
|
|
|
+ // item.parent.add(newMesh);
|
|
|
+ // // item.parent.remove(item);
|
|
|
+ // item.visible = false;
|
|
|
+ // // material.needsUpdate = true;
|
|
|
+ // // item.material = material;
|
|
|
+ });
|
|
|
+ return { zhuqiPips, flowMaterial };
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+}
|
|
|
+
|
|
|
+function addArrow(zhuqiModal, curveNum = 8) {
|
|
|
+ const changjing5 = zhuqiModal.getObjectByName('changjing5');
|
|
|
+ if (changjing5) {
|
|
|
+ const pCube53 = changjing5.getObjectByName('pCube53');
|
|
|
+ pCube53.material.color.set('#888');
|
|
|
+ // 盒子
|
|
|
+ const box = new THREE.Box3();
|
|
|
+ box.setFromObject(pCube53);
|
|
|
+ const min = box.min;
|
|
|
+ const max = box.max;
|
|
|
+ const height = -300;
|
|
|
+ // 获取包围盒的四个顶点
|
|
|
+ const vertices = [
|
|
|
+ new THREE.Vector3(min.x + 200, min.y + 500, min.z), // 点 4
|
|
|
+ new THREE.Vector3(max.x, min.y + 500, min.z), //点3
|
|
|
+ new THREE.Vector3(min.x + 200, max.y, min.z), // 起点1
|
|
|
+ new THREE.Vector3(max.x, max.y, min.z), // 点2
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 绘制钻孔曲线
|
|
|
+ // 根据份数计算均线位置, max.x - min.x 为总长, 端点坐标
|
|
|
+ // 纵长
|
|
|
+ const num1 = 9; // 钻孔个数+1
|
|
|
+ const gap = (max.x - (min.x + 200)) / num1;
|
|
|
+ const curvePoints: THREE.Vector3[] = [];
|
|
|
+ for (let i = 0; i < num1; i++) {
|
|
|
+ if (i !== 0) {
|
|
|
+ const x = min.x + 200 + gap * i;
|
|
|
+ const y = min.y + 500;
|
|
|
+ const z = min.z + height;
|
|
|
+ const position = new THREE.Vector3(x, y, z);
|
|
|
+ curvePoints.push(position);
|
|
|
+
|
|
|
+ // const ball = new THREE.Mesh(new THREE.SphereGeometry(50, 32, 32), new THREE.MeshStandardMaterial({ color: '#f00' }));
|
|
|
+ // ball.position.set(position.x, position.y, position.z);
|
|
|
+ // pCube53.add(ball);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 绘制终点集合
|
|
|
+ // (max.y - min.y + 300)
|
|
|
+ const startP = min.y + 300 + (max.y - (min.y + 300)) / 2; // 起点
|
|
|
+ const len = max.y - startP;
|
|
|
+ const curvePoints1: THREE.Vector3[] = [];
|
|
|
+ for (let i = 0; i < curveNum; i++) {
|
|
|
+ const x = curvePoints[curvePoints.length - i - 1].x;
|
|
|
+ const y = startP + (len / curveNum) * i;
|
|
|
+ const z = min.z + height;
|
|
|
+ const position = new THREE.Vector3(x, y, z);
|
|
|
+ curvePoints1.push(position);
|
|
|
+
|
|
|
+ // const ball = new THREE.Mesh(new THREE.SphereGeometry(50, 32, 32), new THREE.MeshStandardMaterial({ color: '#ff0' }));
|
|
|
+ // ball.position.set(position.x, position.y, position.z);
|
|
|
+ // pCube53.add(ball);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 绘制起点集合
|
|
|
+ const curvePoints0: THREE.Vector3[] = [];
|
|
|
+ for (let i = 0; i < curvePoints.length; i++) {
|
|
|
+ const position = new THREE.Vector3(vertices[2].x + 50 * i, vertices[2].y - 50, min.z);
|
|
|
+ curvePoints0.unshift(position);
|
|
|
+ // const ball = new THREE.Mesh(new THREE.SphereGeometry(50, 32, 32), new THREE.MeshStandardMaterial({ color: '#0ff' }));
|
|
|
+ // ball.position.copy(position);
|
|
|
+ // pCube53.add(ball);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 绘制控制点
|
|
|
+ const curvePoints2: THREE.Vector3[] = [];
|
|
|
+ for (let i = 0; i < curvePoints.length; i++) {
|
|
|
+ const y = curvePoints1[i].y + (vertices[2].y - curvePoints1[i].y) / 2;
|
|
|
+ const x = curvePoints1[i].x - gap / 2;
|
|
|
+ const position = new THREE.Vector3(x, y, min.z + height / 2);
|
|
|
+ curvePoints2.push(position);
|
|
|
+ // const ball = new THREE.Mesh(new THREE.SphereGeometry(50, 32, 32), new THREE.MeshStandardMaterial({ color: '#00f' }));
|
|
|
+ // ball.position.copy(position);
|
|
|
+ // pCube53.add(ball);
|
|
|
+ }
|
|
|
+
|
|
|
+ const textureLoader = new THREE.TextureLoader();
|
|
|
+ const texture = textureLoader.load(`/public/texture/2-1.png`);
|
|
|
+
|
|
|
+ // 设置阵列模式 RepeatWrapping
|
|
|
+ texture.wrapS = THREE.RepeatWrapping;
|
|
|
+ texture.wrapT = THREE.RepeatWrapping;
|
|
|
+ // 设置x方向的重复数(沿着管道路径方向)
|
|
|
+ // 设置y方向的重复数(环绕管道方向)
|
|
|
+ texture.repeat.x = 50;
|
|
|
+ texture.repeat.y = 6;
|
|
|
+ // 设置管道纹理偏移数,便于对中
|
|
|
+ texture.offset.y = 100;
|
|
|
+ const tubeMaterial = new THREE.MeshPhongMaterial({
|
|
|
+ map: texture,
|
|
|
+ color: '#fff',
|
|
|
+ transparent: true,
|
|
|
+ opacity: 1.0,
|
|
|
+ });
|
|
|
+
|
|
|
+ // 根据起点curvePoints0、控制点curvePoints2、终点curvePoints1集合 绘制多条管道
|
|
|
+ for (let i = 0; i < curvePoints0.length; i++) {
|
|
|
+ const contrlP2 = new THREE.Vector3(curvePoints1[i].x, curvePoints1[i].y - 500 * (i + 1), curvePoints1[i].z);
|
|
|
+ const points = [curvePoints0[i], curvePoints2[i], curvePoints1[i], contrlP2];
|
|
|
+ const vec3List = new THREE.CubicBezierCurve3(...points).getPoints();
|
|
|
+ vec3List.push(curvePoints[curvePoints0.length - i - 1]);
|
|
|
+ const geometry = new THREE.TubeGeometry(new THREE.CatmullRomCurve3(vec3List), 100, 10, 8, false);
|
|
|
+ const material = new THREE.MeshStandardMaterial({
|
|
|
+ color: '#4F4F4F',
|
|
|
+ roughness: 0.31,
|
|
|
+ metalness: 0.74,
|
|
|
+ });
|
|
|
+ const mesh = new THREE.Mesh(geometry, material);
|
|
|
+ pCube53.add(mesh);
|
|
|
+
|
|
|
+ const geometry1 = new THREE.TubeGeometry(new THREE.CatmullRomCurve3(vec3List), 100, 40, 8, false);
|
|
|
+ const mesh1 = new THREE.Mesh(geometry1, tubeMaterial);
|
|
|
+ pCube53.add(mesh1);
|
|
|
+ mesh1.renderOrder = 1;
|
|
|
+ }
|
|
|
+ return { texture };
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+}
|
|
|
+
|
|
|
+function addUnit(zhuqiModal, unitNum = 8) {
|
|
|
+ const changjingzhuqiguan = zhuqiModal.getObjectByName('changjingzhuqiguan');
|
|
|
+ if (changjingzhuqiguan) {
|
|
|
+ changjingzhuqiguan.parent.remove(changjingzhuqiguan);
|
|
|
+ }
|
|
|
+ const choucaiguandao = zhuqiModal.getObjectByName('choucaiguandao');
|
|
|
+ if (choucaiguandao) {
|
|
|
+ choucaiguandao.parent.remove(choucaiguandao);
|
|
|
+ }
|
|
|
+
|
|
|
+ const changjing5 = zhuqiModal.getObjectByName('changjing5');
|
|
|
+ if (changjing5) {
|
|
|
+ const pCube53 = changjing5.getObjectByName('pCube53');
|
|
|
+ // 盒子
|
|
|
+ const box = new THREE.Box3();
|
|
|
+ box.setFromObject(pCube53);
|
|
|
+ const min = box.min;
|
|
|
+ const max = box.max;
|
|
|
+ const height = 300;
|
|
|
+ // 获取包围盒的四个顶点
|
|
|
+ const vertices = [
|
|
|
+ new THREE.Vector3(min.x, min.y + 400, min.z + height), // 点 4
|
|
|
+ new THREE.Vector3(max.x, min.y + 400, min.z + height), //点3
|
|
|
+ new THREE.Vector3(min.x, max.y - 700, min.z + height), // 起点1
|
|
|
+ new THREE.Vector3(max.x, max.y - 700, min.z + height), // 点2
|
|
|
+ ];
|
|
|
+
|
|
|
+ // const num = 4;
|
|
|
+ // // 绘制 mun 个小球
|
|
|
+ // for (let i = 0; i < num; i++) {
|
|
|
+ // const ball = new THREE.Mesh(new THREE.SphereGeometry(100, 32, 32), new THREE.MeshStandardMaterial({ color: '#f00' }));
|
|
|
+ // ball.position.set(vertices[i].x, vertices[i].y, vertices[i].z);
|
|
|
+ // pCube53.add(ball);
|
|
|
+ // }
|
|
|
+
|
|
|
+ const textureLoader = new THREE.TextureLoader();
|
|
|
+ const texture = textureLoader.load(`/public/texture/3-2.png`);
|
|
|
+
|
|
|
+ // 设置阵列模式 RepeatWrapping
|
|
|
+ texture.wrapS = THREE.RepeatWrapping;
|
|
|
+ texture.wrapT = THREE.RepeatWrapping;
|
|
|
+ // 设置x方向的重复数(沿着管道路径方向)
|
|
|
+ // 设置y方向的重复数(环绕管道方向)
|
|
|
+ texture.repeat.x = 0.4;
|
|
|
+ texture.repeat.y = 1;
|
|
|
+ // // 设置管道纹理偏移数,便于对中
|
|
|
+ texture.offset.y = 1;
|
|
|
+ const tubeMaterial = new THREE.MeshPhongMaterial({
|
|
|
+ map: texture,
|
|
|
+ // color: '#FF3B40',
|
|
|
+ color: '#FFC4C4',
|
|
|
+ transparent: true,
|
|
|
+ opacity: 0.5,
|
|
|
+ side: THREE.DoubleSide,
|
|
|
+ });
|
|
|
+
|
|
|
+ // 横长
|
|
|
+ const len = max.y - 700 - (min.y + 400);
|
|
|
+ const gap = len / unitNum;
|
|
|
+ const curvePoints: THREE.Vector3[] = [];
|
|
|
+ // 绘制区域面
|
|
|
+ let lastP = new THREE.Vector3(vertices[0].x, vertices[0].y, vertices[0].z);
|
|
|
+ for (let i = 0; i < unitNum; i++) {
|
|
|
+ let p1, p2, p3, p4;
|
|
|
+ if (i == 0) {
|
|
|
+ const x = vertices[0].x;
|
|
|
+ const y = vertices[0].y + gap;
|
|
|
+ const z = vertices[0].z;
|
|
|
+ p1 = lastP.clone();
|
|
|
+ p2 = new THREE.Vector3(x, y, z);
|
|
|
+ p3 = new THREE.Vector3(max.x, y, z);
|
|
|
+ p4 = new THREE.Vector3(max.x, vertices[0].y, vertices[0].z);
|
|
|
+ lastP = p2.clone();
|
|
|
+ } else {
|
|
|
+ p1 = lastP.clone();
|
|
|
+ p2 = new THREE.Vector3(p1.x, p1.y + gap, p1.z);
|
|
|
+ p3 = new THREE.Vector3(max.x, p2.y, p2.z);
|
|
|
+ p4 = new THREE.Vector3(max.x, p1.y, p1.z);
|
|
|
+ lastP = p2.clone();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 绘制面
|
|
|
+ if (p1 && p2 && p3 && p4) {
|
|
|
+ const geometry = new THREE.BufferGeometry();
|
|
|
+ geometry.setFromPoints([p1, p2, p3, p3, p4, p1]);
|
|
|
+ const material = new THREE.MeshPhongMaterial({
|
|
|
+ color: '#f00',
|
|
|
+ });
|
|
|
+ const mesh = new THREE.Mesh(geometry, tubeMaterial);
|
|
|
+ pCube53.add(mesh);
|
|
|
+
|
|
|
+ // p1 p2 p3 p4
|
|
|
+ // 在该区域内绘制管道, p2->p3走向的管道
|
|
|
+ const num = 5; // 管道数量-1
|
|
|
+ let startGap = getMinGapThreshold(gap, num - 2);
|
|
|
+ startGap = startGap + startGap / 3;
|
|
|
+ const unitGap = (gap - startGap * 2) / (num - 2);
|
|
|
+ const gapStartP = new THREE.Vector3(p1.x, p1.y + startGap, p1.z);
|
|
|
+ for (let i = 0; i < num - 1; i++) {
|
|
|
+ const startP = new THREE.Vector3(gapStartP.x, gapStartP.y + unitGap * i, gapStartP.z);
|
|
|
+ const endP = new THREE.Vector3(max.x, startP.y, startP.z);
|
|
|
+ const geometry = new THREE.TubeGeometry(new THREE.CatmullRomCurve3([startP, endP]), 100, 6, 8, false);
|
|
|
+ const material = new THREE.MeshStandardMaterial({
|
|
|
+ color: '#6F6F6F',
|
|
|
+ roughness: 0.31,
|
|
|
+ metalness: 0.74,
|
|
|
+ });
|
|
|
+ const mesh = new THREE.Mesh(geometry, material);
|
|
|
+ pCube53.add(mesh);
|
|
|
+ }
|
|
|
+
|
|
|
+ const y = p1.y + (p2.y - p1.y) / 2;
|
|
|
+ const startP = new THREE.Vector3(p1.x, y, p1.z - 20);
|
|
|
+ const endP = new THREE.Vector3(max.x, y, p1.z - 20);
|
|
|
+ const cuver = new THREE.CatmullRomCurve3([endP, startP]);
|
|
|
+ const points = cuver.getPoints(64);
|
|
|
+ // const geometry1 = new THREE.TubeGeometry(new THREE.CatmullRomCurve3([endP, startP]), 64, 50, 4, false);
|
|
|
+ // const mesh1 = new THREE.Mesh(geometry1, tubeMaterial);
|
|
|
+ // pCube53.add(mesh1);
|
|
|
+
|
|
|
+ const pathPointList = new PathPointList();
|
|
|
+ const up = new THREE.Vector3(0, 0, -1);
|
|
|
+ pathPointList.set(points, 0, 0, up, false);
|
|
|
+ const geometry1 = new PathGeometry(points.length, false);
|
|
|
+ geometry1.update(pathPointList, {
|
|
|
+ width: 200,
|
|
|
+ arrow: false,
|
|
|
+ });
|
|
|
+ const mesh1 = new THREE.Mesh(geometry1, tubeMaterial);
|
|
|
+ mesh1.renderOrder = 99;
|
|
|
+ pCube53.add(mesh1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return { arrowTexture: texture };
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 计算满足不等式 gap*2 > (len - gap*2)/num 的最小临界值
|
|
|
+ * @param {number} len - 总长度
|
|
|
+ * @param {number} num - 数量/分段数
|
|
|
+ * @returns {number} 返回 gap 必须大于的这个值
|
|
|
+ */
|
|
|
+function getMinGapThreshold(len, num) {
|
|
|
+ if (num <= 0) {
|
|
|
+ throw new Error('num 必须大于 0');
|
|
|
+ }
|
|
|
+ if (len <= 0) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 公式推导结果: gap > len / (2 * (num + 1))
|
|
|
+ return len / (2 * (num + 1));
|
|
|
+}
|