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

329 lines
8.5 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="drilling" title="钻进工程活动">
<n-form label-placement="left" label-width="140" size="small">
<n-form-item label="钻进深度">
<n-input-number
v-model:value="drillingParams.depth"
:max="5"
:min="0.1"
:step="0.1"
style="width: 100%"
/>
</n-form-item>
<n-form-item label="动画时长(s)">
<n-input-number
v-model:value="drillingParams.drillingDuration"
:max="30"
:min="0.5"
:step="0.5"
style="width: 100%"
/>
</n-form-item>
<n-form-item label="管道半径(m)">
<n-input-number
v-model:value="drillingParams.pipeRadius"
:max="0.5"
:min="0.05"
:step="0.01"
style="width: 100%"
/>
</n-form-item>
</n-form>
<n-space style="width: 100%" vertical>
<n-button
:disabled="isSelectingDrillingPoint"
block
type="primary"
@click="startSelectingDrillingPoint">
{{ isSelectingDrillingPoint ? '请点击场景选择钻进点...' : '选择钻进点' }}
</n-button>
<n-button
:disabled="pipes.length === 0"
block
type="error"
@click="clearAllPipes">
清除所有管道 ({{ pipes.length }})
</n-button>
</n-space>
</n-collapse-item>
</template>
<script lang="ts" setup>
import {onMounted, ref} from 'vue';
import {NButton, NCollapseItem, NForm, NFormItem, NInputNumber, NSpace} from 'naive-ui';
import {useBus} from '@/hooks';
import {BusEvents} from '@/hooks/Bus.ts';
import {CSGOperationType, CssType, EventManagerEvents, ParametricPipe, Viewer} from '@deep/engine';
import {DrillingHtmlPanel} from '@/htmlPanel';
import * as THREE from 'three/webgpu';
const bus = useBus();
let viewer: Viewer | null = null;
let pipes: ParametricPipe[] = [];
let pipeCounter = 0;
const isSelectingDrillingPoint = ref(false);
const pipeModals = new Map<ParametricPipe, DrillingHtmlPanel>();
let selectedPipe: ParametricPipe | null = null;
// 钻进参数
const drillingParams = ref({
depth: 2.5,
drillingDuration: 0.1,
pipeRadius: 0.1,
});
onMounted(() => {
bus.once(BusEvents.VIEWER_INITIALIZED).then(() => {
viewer = bus.getViewer();
setupClickListener();
});
});
/**
* 设置点击事件监听
*/
const setupClickListener = () => {
if (!viewer) return;
viewer.events.on(EventManagerEvents.RAYCAST_PICK, ({data}) => {
if (isSelectingDrillingPoint.value || !viewer) return;
const {intersects} = data;
if (!intersects || intersects.length === 0) return;
for (const intersection of intersects) {
const clickedObject = intersection.object;
const pipe = pipes.find(p => p === clickedObject);
if (pipe) {
// 如果钻进已完成,不显示弹窗
if (pipe.options.progress >= 1) {
break;
}
selectedPipe = pipe;
let modal = pipeModals.get(pipe);
if (!modal) {
modal = createDrillingModalForPipe(pipe);
}
updateDrillingModalInfo(pipe, modal);
if (modal && intersection.point) {
const modalObject = modal.getCssObject();
const clickPoint = intersection.point;
modalObject.position.copy(clickPoint);
modalObject.position.y += 0.5;
}
break;
}
}
});
};
/**
* 为管道创建钻进信息弹窗
*/
const createDrillingModalForPipe = (pipe: ParametricPipe): DrillingHtmlPanel => {
if (!viewer) throw new Error('Viewer not initialized');
const modal = new DrillingHtmlPanel(CssType.CSS2D);
modal.onDelete(() => {
modal.hide();
const modalObject = modal.getCssObject();
if (modalObject.parent) {
modalObject.parent.remove(modalObject);
}
pipeModals.delete(pipe);
if (selectedPipe === pipe) {
selectedPipe = null;
}
});
const pipePosition = pipe.position;
const modalObject = modal.getCssObject();
modalObject.position.set(pipePosition.x, pipePosition.y + 1, pipePosition.z);
viewer.scene.add(modalObject);
modal.show();
pipeModals.set(pipe, modal);
return modal;
};
/**
* 更新钻进信息弹窗内容
*/
const updateDrillingModalInfo = (pipe: ParametricPipe, modal: DrillingHtmlPanel) => {
const progress = pipe.options.progress * 100;
const currentDepth = pipe.options.progress * drillingParams.value.depth;
const status = progress >= 100 ? 'completed' : progress > 0 ? 'drilling' : 'stopped';
modal.updateData({
pipeName: pipe.name,
drillingSpeed: drillingParams.value.drillingDuration,
currentDepth: currentDepth,
targetDepth: drillingParams.value.depth,
progress: progress,
pipeRadius: pipe.options.radius,
status: status
});
};
/**
* 启动钻进信息实时更新
*/
const startDrillingInfoUpdate = (pipe: ParametricPipe) => {
const updateInterval = setInterval(() => {
if (!pipes.includes(pipe)) {
clearInterval(updateInterval);
return;
}
const modal = pipeModals.get(pipe);
if (modal) {
updateDrillingModalInfo(pipe, modal);
}
if (pipe.options.progress >= 1) {
clearInterval(updateInterval);
if (modal) {
updateDrillingModalInfo(pipe, modal);
}
}
}, 100);
};
/**
* 开始选择钻进点
*/
const startSelectingDrillingPoint = () => {
if (!viewer) return;
isSelectingDrillingPoint.value = true;
const canvas = viewer.renderer!.domElement;
canvas.addEventListener('click', onCanvasClick, {once: true});
canvas.style.cursor = 'crosshair';
};
/**
* 处理画布点击事件
*/
const onCanvasClick = (event: MouseEvent) => {
if (!viewer || !isSelectingDrillingPoint.value) return;
const canvas = viewer.renderer!.domElement;
const rect = canvas.getBoundingClientRect();
const mouse = new THREE.Vector2();
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, viewer.camera);
const intersects = raycaster.intersectObjects(viewer.scene.children, true);
if (intersects.length > 0) {
const intersection = intersects[0];
if (intersection) {
createDrillingPipe(intersection.point);
}
}
canvas.style.cursor = 'default';
isSelectingDrillingPoint.value = false;
};
/**
* 创建钻进管道
*/
const createDrillingPipe = (startPoint: THREE.Vector3) => {
if (!viewer) return;
const points: THREE.Vector3[] = [];
const topPoint = new THREE.Vector3(startPoint.x, startPoint.y, startPoint.z);
const bottomPoint = new THREE.Vector3(
startPoint.x,
startPoint.y - drillingParams.value.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: 0x1b1b1b,
side: THREE.DoubleSide,
});
const newPipe = new ParametricPipe(viewer, {
points: points,
radius: drillingParams.value.pipeRadius,
radialSegments: 16,
cornerRadius: 0.1,
cornerSplit: 5,
progress: 0,
material: pipeMaterial,
capStart: false,
capEnd: false,
enableDrillingRobot: true,
robotColor: 0xff0000,
});
viewer.scene.add(newPipe);
pipeCounter++;
newPipe.name = `钻进管道_${pipeCounter}`;
newPipe.setCollisionProxyType(true); // true = 静态
newPipe.addCollisionTarget(bus.getRockSample(), CSGOperationType.HOLLOW_SUBTRACTION);
// 存储 ParametricPipe 实例到 mesh.userData方便其他组件访问
newPipe.userData.pipeInstance = newPipe;
pipes.push(newPipe);
const duration = drillingParams.value.drillingDuration;
newPipe.setDuration(duration);
newPipe.startAnimation();
startDrillingInfoUpdate(newPipe);
bus.triggerSceneTreeUpdate()
console.log(newPipe)
};
/**
* 清除所有管道
*/
const clearAllPipes = () => {
if (pipes.length > 0) {
pipes.forEach(pipe => {
const modal = pipeModals.get(pipe);
if (modal) {
modal.hide();
const modalObject = modal.getCssObject();
if (modalObject.parent) {
modalObject.parent.remove(modalObject);
}
}
pipe.dispose();
});
pipes = [];
pipeModals.clear();
pipeCounter = 0;
selectedPipe = null;
bus.triggerSceneTreeUpdate()
}
};
</script>
<style scoped>
</style>