519 lines
14 KiB
Vue
519 lines
14 KiB
Vue
<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>
|