deep-engine-demo/packages/demo/src/panels/GoldMineScene/DevelopmentMiningPanel.vue
2026-04-19 18:46:28 +08:00

519 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<n-collapse-item name="developmentMining" title="开拓采准回采">
<n-space style="width: 100%" vertical>
<n-button style="width: 100%" type="primary" @click="handleDevelopment">
钻孔
</n-button>
<n-button style="width: 100%" type="primary" @click="handlePreparation">
开拓/采准
</n-button>
<n-button style="width: 100%" type="primary" @click="handleStoping">
回采
</n-button>
</n-space>
</n-collapse-item>
</template>
<script lang="ts" setup>
import {onMounted, ref} from 'vue';
import {NButton, NCollapseItem, NSpace} from 'naive-ui';
import {useBus} from '@/hooks';
import {BusEvents} from '@/hooks/Bus.ts';
import {CSGOperationType, MiningRobot, ParametricPipe, Viewer} from '@deep/engine';
import * as THREE from 'three/webgpu';
const bus = useBus();
let viewer: Viewer | null = null;
let verticalPipes: ParametricPipe[] = []; // 开拓管道
let horizontalPipe: ParametricPipe | null = null; // 采准管道
let connectionPipes: ParametricPipe[] = []; // 回采连接管道
let miningRobot: MiningRobot | null = null; // 回采机器人
let currentRowIndex = ref(0); // 当前挖掘行索引
let currentLayerIndex = ref(0); // 当前挖掘层索引
// 配置3个管道的起始点位沿Z轴分布
const pipeConfigs = [
{
startPoint: new THREE.Vector3(-0.11, 2.5, -1.5),
depth: 2.3,
endPoint: null,
direction: 'down',
type: 'intake',
label: '进风井'
},
{
startPoint: new THREE.Vector3(-0.11, 2.5, 0),
depth: 2,
endPoint: null,
direction: 'down',
type: 'filling',
label: '充填井'
},
{
startPoint: new THREE.Vector3(0.2233053054589904, 2.5, 1.5),
depth: 2.8,
endPoint: null,
direction: 'down',
type: 'exhaust',
label: '回风井'
},
{startPoint: new THREE.Vector3(-0.11, 0, -2.505), depth: 1.1, endPoint: null, direction: 'z-negative'},
];
onMounted(() => {
bus.once(BusEvents.VIEWER_INITIALIZED).then(() => {
viewer = bus.getViewer();
});
});
const handleDevelopment = () => {
console.log('开拓');
clearVerticalPipes();
createVerticalPipes();
};
const handlePreparation = () => {
console.log('采准');
// 创建水平管道
createHorizontalPipe();
};
const handleStoping = () => {
console.log('回采');
// 在场景中查找名为"矿体2"的对象
let oreMesh = viewer?.scene.getObjectByName("矿体2") as THREE.Mesh;
oreMesh.material.side = THREE.DoubleSide
// 计算矿体的包围盒
const boundingBox = new THREE.Box3().setFromObject(oreMesh);
const size = new THREE.Vector3();
boundingBox.getSize(size);
const center = new THREE.Vector3();
boundingBox.getCenter(center);
console.log('矿体包围盒:', {
min: boundingBox.min,
max: boundingBox.max,
size,
center
});
// 创建机器人
if (miningRobot) {
miningRobot.dispose();
}
miningRobot = new MiningRobot({
size: 0.075,
color: 0xff6600,
widthRatio: 1.5,
heightRatio: 1.2,
depthRatio: 2
});
miningRobot.group.position.set(0, 0, -0.55);
// 添加机器人到场景
viewer.scene.add(miningRobot.group);
miningRobot.group.visible = true;
bus.triggerSceneTreeUpdate()
// 添加矿体作为 CSG 目标对象(要被切割头挖掘的对象)
miningRobot.addCSGTarget(oreMesh, CSGOperationType.SUBTRACTION);
// 获取切割头的尺寸和偏移(用于计算挖掘路径)
const cuttingHeadSize = miningRobot.options.size * 1.2;
const rowSpacing = cuttingHeadSize; // Z 方向间距,严格等于切割头尺寸
const layerHeight = cuttingHeadSize; // Y 方向间距,严格等于切割头尺寸
// 计算切割头相对于机器人组的偏移
const baseSize = miningRobot.options.size;
const depth = baseSize * miningRobot.options.depthRatio;
const connectorLength = depth * 0.4;
const cuttingHeadOffset = depth / 2 + connectorLength + cuttingHeadSize / 2; // 切割头在 Z 轴正方向的偏移
// 计算挖掘路径(这些是切割头的目标位置)
const miningPaths: Array<{ start: THREE.Vector3, end: THREE.Vector3 }> = [];
// 从 Z 负方向开始,每个 Z 层从下往上挖
const startY = boundingBox.min.y + cuttingHeadSize / 2;
const endY = boundingBox.max.y - cuttingHeadSize / 2;
const startZ = boundingBox.min.z + cuttingHeadSize / 2;
const endZ = boundingBox.max.z - cuttingHeadSize / 2;
const startX = boundingBox.min.x + cuttingHeadSize / 2;
const endX = boundingBox.max.x - cuttingHeadSize / 2;
let currentZ = startZ;
let zLayerIndex = 0;
// 遍历每个 Z 层
while (currentZ <= endZ) {
let currentY = startY;
let rowIndex = 0;
// 在当前 Z 层,从下往上挖
while (currentY <= endY) {
// 交替方向挖掘(之字形):从 X 负方向挖到正方向,再从正方向挖回负方向
const isEvenRow = rowIndex % 2 === 0;
const pathStartX = isEvenRow ? startX : endX;
const pathEndX = isEvenRow ? endX : startX;
// 机器人组的位置 = 切割头目标位置 - 切割头偏移
miningPaths.push({
start: new THREE.Vector3(pathStartX, currentY, currentZ - cuttingHeadOffset),
end: new THREE.Vector3(pathEndX, currentY, currentZ - cuttingHeadOffset)
});
currentY += layerHeight;
rowIndex++;
}
currentZ += rowSpacing;
zLayerIndex++;
}
console.log(`生成了 ${miningPaths.length} 条挖掘路径`);
// 开始挖掘第一条路径
currentRowIndex.value = 0;
currentLayerIndex.value = 0;
const startNextPath = () => {
if (currentRowIndex.value >= miningPaths.length) {
console.log('挖掘完成!');
return;
}
const path = miningPaths[currentRowIndex.value];
if (!path) {
console.warn('路径不存在');
return;
}
console.log(`开始挖掘路径 ${currentRowIndex.value + 1}/${miningPaths.length}`);
miningRobot!.startMining(
viewer!,
path.start,
path.end,
{
duration: 1,
csgFrequency: 0.5,
onProgress: (_progress) => {
// 可以在这里更新进度UI
},
onComplete: () => {
console.log(`路径 ${currentRowIndex.value + 1} 完成`);
currentRowIndex.value++;
// 延迟一点再开始下一条路径
setTimeout(() => {
startNextPath();
}, 100);
}
}
);
};
// 开始第一条路径
startNextPath();
bus.triggerSceneTreeUpdate();
};
/**
* 创建3个垂直向下的管道
*/
const createVerticalPipes = () => {
if (!viewer) return;
const currentViewer = viewer;
// 前3个垂直管道配置
const verticalConfigs = pipeConfigs.slice(0, 3);
verticalConfigs.forEach((config, index) => {
const {startPoint, depth, type, label} = config;
const points: THREE.Vector3[] = [];
// if (index === 1) {
const topPoint = new THREE.Vector3(startPoint.x, startPoint.y, startPoint.z);
const bottomPoint = new THREE.Vector3(
startPoint.x,
startPoint.y - depth,
startPoint.z
);
// 生成向下的管道点
const segments = 10;
for (let i = 0; i <= segments; i++) {
const t = i / segments;
const y = topPoint.y + (bottomPoint.y - topPoint.y) * t;
points.push(new THREE.Vector3(startPoint.x, y, startPoint.z));
}
const pipeMaterial = new THREE.MeshBasicNodeMaterial({
color: 0x808080,
transparent: true,
opacity: 0.6,
side: THREE.DoubleSide,
});
const pipe = new ParametricPipe(currentViewer, {
points: points,
radius: 0.1,
radialSegments: 16,
cornerRadius: 0.1,
cornerSplit: 5,
progress: 0,
material: pipeMaterial,
capStart: false,
capEnd: false,
enableDrillingRobot: true,
robotColor: 0xff0000,
// debugCollisionProxy: true,
metadata: {
drillingType: type,
},
});
if (index === 1) {
pipe.setCollisionProxyType(false);
pipe.addCollisionTarget(bus.getSection(), CSGOperationType.HOLLOW_SUBTRACTION, -1);
} else if (index === 2) {
pipe.setCollisionProxyType(false);
const mesh = viewer?.scene.getObjectByName("地质层") as THREE.Mesh;
pipe.addCollisionTarget(mesh, CSGOperationType.HOLLOW_SUBTRACTION, -1);
}else {
pipe.setCollisionProxyType(true);
}
currentViewer.scene.add(pipe);
pipe.name = label
// 启用CSG碰撞
pipe.addCollisionTarget(bus.getRockSample());
pipe.setDuration(3);
pipe.startAnimation();
verticalPipes.push(pipe);
// }
});
bus.triggerSceneTreeUpdate();
};
/**
* 创建水平方向的管道沿Z轴
*/
const createHorizontalPipe = () => {
if (!viewer) return;
const currentViewer = viewer;
// 第4个水平管道配置
const config = pipeConfigs[3];
if (!config) return;
const {startPoint, depth} = config;
const points: THREE.Vector3[] = [];
// 沿Z轴正方向的管道
const segments = 10;
for (let i = 0; i <= segments; i++) {
const t = i / segments;
const z = startPoint.z + depth * t;
points.push(new THREE.Vector3(startPoint.x, startPoint.y, z));
}
const pipeMaterial = new THREE.MeshBasicNodeMaterial({
color: 0x808080,
transparent: true,
opacity: 0.6,
side: THREE.DoubleSide,
});
const pipe = new ParametricPipe(currentViewer, {
points: points,
radius: 0.25,
radialSegments: 16,
cornerRadius: 0.1,
cornerSplit: 5,
progress: 0,
material: pipeMaterial,
capStart: false,
capEnd: false,
enableDrillingRobot: true,
robotColor: 0xff0000,
});
currentViewer.scene.add(pipe);
pipe.name = `回采管道_4`;
// 启用CSG碰撞
pipe.setCollisionProxyType(true); // true = 静态
pipe.addCollisionTarget(bus.getRockSample());
// 监听动画完成 - 使用定时器检查进度
const checkAnimationComplete = setInterval(() => {
if (pipe.options.progress >= 1) {
clearInterval(checkAnimationComplete);
console.log('水平管道动画完成,创建连接管道');
createConnectionPipes();
}
}, 100);
// 点击后立即开始动画
pipe.setDuration(3);
pipe.startAnimation();
horizontalPipe = pipe;
bus.triggerSceneTreeUpdate();
};
/**
* 创建3个连接管道
*/
const createConnectionPipes = () => {
if (!viewer) return;
const meshTarget = viewer.scene.getObjectByName("矿体1") as THREE.Mesh
const meshTarget2 = viewer.scene.getObjectByName("地质层") as THREE.Mesh
const currentViewer = viewer;
const config = pipeConfigs[3];
if (!config) return;
// 获取水平管道的终点
const endPoint = new THREE.Vector3(
config.startPoint.x,
config.startPoint.y,
config.startPoint.z + config.depth
);
const Points = [
new THREE.Vector3(0.2233053054589904, -0.37226677965409577, -0.4178163281883125 - 0.4),
new THREE.Vector3(0.2233053054589904 - 0.3, -0.37226677965409577, -0.4178163281883125 - 0.4),
new THREE.Vector3(0.2233053054589904 - 0.6, -0.37226677965409577, -0.4178163281883125 - 0.4),
];
// 3个目标点x坐标依次递减0.3
const targetPoints = [
new THREE.Vector3(0.2233053054589904, -0.37226677965409577, -0.4178163281883125),
new THREE.Vector3(0.2233053054589904 - 0.3, -0.37226677965409577, -0.4178163281883125),
new THREE.Vector3(0.2233053054589904 - 0.6, -0.37226677965409577, -0.4178163281883125),
];
targetPoints.forEach((targetPoint, index) => {
// 计算向外的偏移方向根据index决定左右中间为0
const lateralOffset = index === 0 ? 0.3 : index === 1 ? 0 : -0.3;
// 起点:从水平管道侧面衍生,向外偏移
const startPoint = new THREE.Vector3(
endPoint.x + lateralOffset * 0.4,
endPoint.y,
endPoint.z - 0.1
);
// 平行段终点
const parallelDistance = 0.2;
const parallelEndPoint = new THREE.Vector3(
startPoint.x,
startPoint.y - 0.05,
startPoint.z + parallelDistance
);
// 控制点:用于创建曲线
const controlPoint = new THREE.Vector3(
parallelEndPoint.x + lateralOffset * 0.5,
parallelEndPoint.y - 0.5,
parallelEndPoint.z
);
// 获取对应的中间点
const middlePoint = Points[index];
if (!middlePoint) return;
let points: THREE.Vector3[];
// 第一个管道index === 0一直延伸到 z = 2.4
if (index === 0) {
// 最后平行于 Z 轴延伸到 z = 2.4
const finalPoint = new THREE.Vector3(
middlePoint.x,
middlePoint.y,
2.4
);
const baseStart = new THREE.Vector3(
startPoint.x,
startPoint.y - 0.05, -2.5
);
points = [ startPoint, parallelEndPoint, controlPoint, middlePoint, finalPoint];
} else {
// 其他管道:从中间点延伸到目标点
points = [startPoint, parallelEndPoint, controlPoint, middlePoint, targetPoint];
}
const pipeMaterial = new THREE.MeshBasicNodeMaterial({
color: 0x808080,
transparent: true,
opacity: 0.6,
side: THREE.DoubleSide,
});
const pipe = new ParametricPipe(currentViewer, {
points: points,
radius: 0.1,
radialSegments: 20,
cornerRadius: 1,
cornerSplit: 30,
progress: 0,
material: pipeMaterial,
capStart: false,
capEnd: false,
enableDrillingRobot: true,
robotColor: 0xff0000,
robotCylinderLengthRatio: 1.5,
robotConeLengthRatio: 1,
collisionCheckProgressStep: 0.06,
collisionProxyHeight: 0.4,
// debugCollisionProxy: true
});
pipe.renderOrder = 1
if (index === 0) {
pipe.setCollisionProxyType(false);
pipe.addCollisionTarget(bus.getSection(), CSGOperationType.HOLLOW_SUBTRACTION, -1);
pipe.addCollisionTarget(meshTarget, CSGOperationType.HOLLOW_SUBTRACTION, -1);
pipe.addCollisionTarget(meshTarget2, CSGOperationType.SUBTRACTION, -1);
bus.setTemperatureLine(pipe)
}
currentViewer.scene.add(pipe);
pipe.name = `连接管道_${index + 1}`;
// 立即开始动画
pipe.setDuration(3);
pipe.startAnimation();
connectionPipes.push(pipe);
});
bus.triggerSceneTreeUpdate();
};
/**
* 清除垂直管道
*/
const clearVerticalPipes = () => {
verticalPipes.forEach(pipe => {
pipe.dispose();
});
verticalPipes = [];
bus.triggerSceneTreeUpdate();
};
</script>
<style scoped>
</style>