|
|
|
@ -34,274 +34,430 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits<Emits>();
|
|
|
|
|
|
|
|
|
|
// 定义全局变量
|
|
|
|
|
let scene;
|
|
|
|
|
let renderer;
|
|
|
|
|
// 状态管理
|
|
|
|
|
const pcdContainer = ref(null);
|
|
|
|
|
const show = computed({
|
|
|
|
|
get() { return props.value; },
|
|
|
|
|
set(val) { emit("update:value", val); }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 新增加载状态和进度
|
|
|
|
|
// Three.js核心对象
|
|
|
|
|
const scene = ref(null);
|
|
|
|
|
const camera = ref(null);
|
|
|
|
|
const renderer = ref(null);
|
|
|
|
|
const controls = ref(null);
|
|
|
|
|
const pointCloud = ref(null);
|
|
|
|
|
|
|
|
|
|
// 状态变量
|
|
|
|
|
const pcdLoaded = ref(false);
|
|
|
|
|
const loading = ref(false);
|
|
|
|
|
const progress = ref(0);
|
|
|
|
|
// 处理对话框关闭事件
|
|
|
|
|
const handleClose = () => {
|
|
|
|
|
if (pcdContainer.value) {
|
|
|
|
|
// 移除渲染器的 DOM 元素
|
|
|
|
|
while (pcdContainer.value.firstChild) {
|
|
|
|
|
pcdContainer.value.removeChild(pcdContainer.value.firstChild);
|
|
|
|
|
}
|
|
|
|
|
// 释放场景资源
|
|
|
|
|
if (scene) {
|
|
|
|
|
scene.traverse((object) => {
|
|
|
|
|
if (object instanceof THREE.Mesh) {
|
|
|
|
|
object.geometry.dispose();
|
|
|
|
|
if (object.material instanceof THREE.Material) {
|
|
|
|
|
object.material.dispose();
|
|
|
|
|
} else {
|
|
|
|
|
object.material.forEach((material) => material.dispose());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
// 销毁控制器
|
|
|
|
|
if (controls) {
|
|
|
|
|
controls.dispose();
|
|
|
|
|
}
|
|
|
|
|
// 移除窗口大小变化监听器
|
|
|
|
|
// window.removeEventListener('resize', onWindowResize);
|
|
|
|
|
// 重置变量
|
|
|
|
|
scene = null;
|
|
|
|
|
camera = null;
|
|
|
|
|
renderer = null;
|
|
|
|
|
controls = null;
|
|
|
|
|
pcdLoaded.value = false;
|
|
|
|
|
}
|
|
|
|
|
emit("update:value", false);
|
|
|
|
|
const errorMessage = ref("");
|
|
|
|
|
const topView = ref(true); // 默认使用俯视图
|
|
|
|
|
|
|
|
|
|
// 渲染循环ID
|
|
|
|
|
let animationId = null;
|
|
|
|
|
|
|
|
|
|
// 视图状态保存
|
|
|
|
|
let initialCameraPosition = null;
|
|
|
|
|
let initialCameraRotation = null;
|
|
|
|
|
let initialControlsTarget = null;
|
|
|
|
|
let initialCameraZoom = 1;
|
|
|
|
|
let pointCloudCenter = new THREE.Vector3(); // 点云中心位置
|
|
|
|
|
let pointCloudSize = new THREE.Vector3(); // 点云尺寸
|
|
|
|
|
let normalViewPosition = null; // 普通视图相机位置
|
|
|
|
|
let normalViewRotation = null; // 普通视图相机旋转
|
|
|
|
|
|
|
|
|
|
// 窗口大小调整处理
|
|
|
|
|
const handleResize = () => {
|
|
|
|
|
if (camera.value && renderer.value && pcdContainer.value) {
|
|
|
|
|
const container = pcdContainer.value;
|
|
|
|
|
camera.value.aspect = container.clientWidth / container.clientHeight;
|
|
|
|
|
camera.value.updateProjectionMatrix();
|
|
|
|
|
renderer.value.setSize(container.clientWidth, container.clientHeight);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const show = computed({
|
|
|
|
|
get() {
|
|
|
|
|
return props.value;
|
|
|
|
|
// 处理点云加载和渲染
|
|
|
|
|
const loadPointCloud = (url) => {
|
|
|
|
|
// 重置状态
|
|
|
|
|
errorMessage.value = "";
|
|
|
|
|
loading.value = true;
|
|
|
|
|
|
|
|
|
|
// 清除现有点云
|
|
|
|
|
if (pointCloud.value && scene.value) {
|
|
|
|
|
scene.value.remove(pointCloud.value);
|
|
|
|
|
pointCloud.value.geometry.dispose();
|
|
|
|
|
pointCloud.value.material.dispose();
|
|
|
|
|
pointCloud.value = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const loader = new PCDLoader();
|
|
|
|
|
loader.load(
|
|
|
|
|
url,
|
|
|
|
|
(parsedPointCloud) => {
|
|
|
|
|
console.log("PCD解析成功,点数:", parsedPointCloud.geometry.attributes.position.count);
|
|
|
|
|
processPointCloud(parsedPointCloud);
|
|
|
|
|
},
|
|
|
|
|
set(val: boolean) {
|
|
|
|
|
emit("update:value", val);
|
|
|
|
|
(xhr) => {
|
|
|
|
|
const percent = Math.round((xhr.loaded / xhr.total) * 100);
|
|
|
|
|
console.log(`加载进度: ${percent}%`);
|
|
|
|
|
},
|
|
|
|
|
(error) => {
|
|
|
|
|
console.error("点云加载失败:", error);
|
|
|
|
|
errorMessage.value = "点云文件加载失败";
|
|
|
|
|
loading.value = false;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
const pcdContainer = ref(null);
|
|
|
|
|
let initialCameraPosition;
|
|
|
|
|
let initialCameraRotation;
|
|
|
|
|
let initialControlsTarget;
|
|
|
|
|
let initialCameraZoom;
|
|
|
|
|
let controls;
|
|
|
|
|
let camera;
|
|
|
|
|
const pcdLoaded = ref(false);
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const onDialogOpened = () => {
|
|
|
|
|
// 检查点云数据URL是否存在
|
|
|
|
|
if (!props.info.point_cloud_url) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (pcdContainer.value) {
|
|
|
|
|
scene = new THREE.Scene();
|
|
|
|
|
camera = new THREE.PerspectiveCamera(
|
|
|
|
|
75,
|
|
|
|
|
pcdContainer.value.clientWidth / pcdContainer.value.clientHeight,
|
|
|
|
|
0.1,
|
|
|
|
|
1000
|
|
|
|
|
);
|
|
|
|
|
renderer = new THREE.WebGLRenderer();
|
|
|
|
|
renderer.setSize(pcdContainer.value.clientWidth, pcdContainer.value.clientHeight);
|
|
|
|
|
pcdContainer.value.appendChild(renderer.domElement);
|
|
|
|
|
|
|
|
|
|
// 创建 OrbitControls 实例
|
|
|
|
|
controls = new OrbitControls(camera, renderer.domElement);
|
|
|
|
|
|
|
|
|
|
const loader = new PCDLoader();
|
|
|
|
|
// 开始加载,设置加载状态为 true
|
|
|
|
|
loading.value = true;
|
|
|
|
|
loader.load(
|
|
|
|
|
props.info.point_cloud_url,
|
|
|
|
|
function (pointCloud) {
|
|
|
|
|
// 加载完成,设置加载状态为 false
|
|
|
|
|
loading.value = false;
|
|
|
|
|
// 调整点云的位置到原点
|
|
|
|
|
pointCloud.position.set(0, 100, 0);
|
|
|
|
|
|
|
|
|
|
// 获取点云的几何体
|
|
|
|
|
const geometry = pointCloud.geometry;
|
|
|
|
|
|
|
|
|
|
// 创建颜色数组
|
|
|
|
|
const colors = [];
|
|
|
|
|
const positions = geometry.attributes.position.array;
|
|
|
|
|
const numPoints = positions.length / 3;
|
|
|
|
|
|
|
|
|
|
// 定义起始颜色和结束颜色
|
|
|
|
|
const startColor = new THREE.Color(0x0000ff); // 蓝色
|
|
|
|
|
const midColor1 = new THREE.Color(0x00ff00); // 绿色
|
|
|
|
|
const midColor2 = new THREE.Color(0xffff00); // 黄色
|
|
|
|
|
const endColor = new THREE.Color(0xff0000); // 红色
|
|
|
|
|
|
|
|
|
|
// 计算点云在 z 轴上的最小值和最大值
|
|
|
|
|
let minZ = Infinity;
|
|
|
|
|
let maxZ = -Infinity;
|
|
|
|
|
for (let i = 0; i < numPoints; i++) {
|
|
|
|
|
const z = positions[i * 3 + 2];
|
|
|
|
|
if (z < minZ) minZ = z;
|
|
|
|
|
if (z > maxZ) maxZ = z;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < numPoints; i++) {
|
|
|
|
|
const z = positions[i * 3 + 2];
|
|
|
|
|
// 计算颜色渐变因子
|
|
|
|
|
const factor = (z - minZ) / (maxZ - minZ);
|
|
|
|
|
|
|
|
|
|
let color;
|
|
|
|
|
if (factor < 0.33) {
|
|
|
|
|
// 从蓝到绿的渐变
|
|
|
|
|
const subFactor = factor / 0.33;
|
|
|
|
|
color = startColor.clone().lerp(midColor1, subFactor);
|
|
|
|
|
} else if (factor < 0.66) {
|
|
|
|
|
// 从绿到黄的渐变
|
|
|
|
|
const subFactor = (factor - 0.33) / 0.33;
|
|
|
|
|
color = midColor1.clone().lerp(midColor2, subFactor);
|
|
|
|
|
} else {
|
|
|
|
|
// 从黄到红的渐变
|
|
|
|
|
const subFactor = (factor - 0.66) / 0.34;
|
|
|
|
|
color = midColor2.clone().lerp(endColor, subFactor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 将颜色添加到颜色数组中
|
|
|
|
|
colors.push(color.r, color.g, color.b);
|
|
|
|
|
}
|
|
|
|
|
// 创建颜色属性
|
|
|
|
|
const colorAttribute = new THREE.Float32BufferAttribute(colors, 3);
|
|
|
|
|
geometry.setAttribute('color', colorAttribute);
|
|
|
|
|
|
|
|
|
|
// 创建一个新的材质并启用顶点颜色
|
|
|
|
|
const material = new THREE.PointsMaterial({ vertexColors: true });
|
|
|
|
|
pointCloud.material = material;
|
|
|
|
|
|
|
|
|
|
// 调整点云的缩放比例
|
|
|
|
|
pointCloud.scale.set(1, 1, 1);
|
|
|
|
|
// 计算点云的边界框
|
|
|
|
|
const box = new THREE.Box3().setFromObject(pointCloud);
|
|
|
|
|
const size = box.getSize(new THREE.Vector3());
|
|
|
|
|
const maxSize = Math.max(size.x, size.y, size.z);
|
|
|
|
|
const fov = camera.fov * (Math.PI / -180);
|
|
|
|
|
const distance = maxSize / 2 / Math.tan(fov / 3);
|
|
|
|
|
|
|
|
|
|
// 调整相机位置为正视图
|
|
|
|
|
camera.position.set(0, distance, 0);
|
|
|
|
|
camera.rotation.set(0, 0, 0);
|
|
|
|
|
camera.lookAt(0, 0, 0);
|
|
|
|
|
|
|
|
|
|
// 调整点云的旋转角度,绕 y 轴旋转 45 度(Math.PI / 4 弧度)
|
|
|
|
|
pointCloud.rotation.set(0, 0, 0);
|
|
|
|
|
|
|
|
|
|
// 保存初始相机位置、旋转、缩放和控制器目标位置
|
|
|
|
|
initialCameraPosition = camera.position.clone();
|
|
|
|
|
initialCameraRotation = camera.rotation.clone();
|
|
|
|
|
initialCameraZoom = camera.zoom;
|
|
|
|
|
initialControlsTarget = controls.target.clone();
|
|
|
|
|
|
|
|
|
|
scene.add(pointCloud);
|
|
|
|
|
pcdLoaded.value = true;
|
|
|
|
|
|
|
|
|
|
// // 监听窗口大小变化
|
|
|
|
|
// const onWindowResize = () => {
|
|
|
|
|
// camera.aspect = pcdContainer.value.clientWidth / pcdContainer.value.clientHeight;
|
|
|
|
|
// camera.updateProjectionMatrix();
|
|
|
|
|
// renderer.setSize(pcdContainer.value.clientWidth, pcdContainer.value.clientHeight);
|
|
|
|
|
// };
|
|
|
|
|
// window.addEventListener('resize', onWindowResize);
|
|
|
|
|
},
|
|
|
|
|
function (xhr) {
|
|
|
|
|
// 更新加载进度
|
|
|
|
|
progress.value = (xhr.loaded / xhr.total) * 100;
|
|
|
|
|
},
|
|
|
|
|
function (error) {
|
|
|
|
|
// 加载失败,设置加载状态为 false
|
|
|
|
|
loading.value = false;
|
|
|
|
|
console.error('点云文件加载失败:', error);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
function animate() {
|
|
|
|
|
requestAnimationFrame(animate);
|
|
|
|
|
// 更新控制器
|
|
|
|
|
if (controls) {
|
|
|
|
|
controls.update();
|
|
|
|
|
}
|
|
|
|
|
if (renderer && scene && camera) {
|
|
|
|
|
renderer.render(scene, camera);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 处理解析后的点云数据
|
|
|
|
|
const processPointCloud = (parsedPointCloud) => {
|
|
|
|
|
// 保存点云对象
|
|
|
|
|
pointCloud.value = markRaw(parsedPointCloud);
|
|
|
|
|
|
|
|
|
|
// 计算点云边界和尺寸
|
|
|
|
|
const box = new THREE.Box3().setFromObject(pointCloud.value);
|
|
|
|
|
box.getCenter(pointCloudCenter);
|
|
|
|
|
box.getSize(pointCloudSize);
|
|
|
|
|
|
|
|
|
|
const maxDim = Math.max(pointCloudSize.x, pointCloudSize.y, pointCloudSize.z);
|
|
|
|
|
|
|
|
|
|
// 调整点云位置使其居中
|
|
|
|
|
pointCloud.value.position.sub(pointCloudCenter);
|
|
|
|
|
|
|
|
|
|
animate();
|
|
|
|
|
// 应用颜色渐变
|
|
|
|
|
applyColorGradient(pointCloud.value, maxDim);
|
|
|
|
|
|
|
|
|
|
// 添加到场景
|
|
|
|
|
scene.value.add(pointCloud.value);
|
|
|
|
|
|
|
|
|
|
// 计算相机距离和位置
|
|
|
|
|
const fov = camera.value.fov * (Math.PI / 180);
|
|
|
|
|
const distance = maxDim / 2 / Math.tan(fov / 2);
|
|
|
|
|
|
|
|
|
|
// 保存普通视角的相机位置和旋转
|
|
|
|
|
normalViewPosition = new THREE.Vector3(0, distance, 0);
|
|
|
|
|
normalViewRotation = new THREE.Euler(0, 0, 0);
|
|
|
|
|
|
|
|
|
|
// 设置相机位置为俯视图
|
|
|
|
|
setTopView(distance);
|
|
|
|
|
|
|
|
|
|
// 保存初始状态
|
|
|
|
|
initialCameraPosition = camera.value.position.clone();
|
|
|
|
|
initialCameraRotation = camera.value.rotation.clone();
|
|
|
|
|
initialCameraZoom = camera.value.zoom;
|
|
|
|
|
initialControlsTarget = controls.value.target.clone();
|
|
|
|
|
|
|
|
|
|
// 更新状态
|
|
|
|
|
pcdLoaded.value = true;
|
|
|
|
|
loading.value = false;
|
|
|
|
|
|
|
|
|
|
// 触发重绘
|
|
|
|
|
controls.value.update();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 设置俯视图
|
|
|
|
|
const setTopView = (distance) => {
|
|
|
|
|
if (!camera.value || !controls.value) return;
|
|
|
|
|
|
|
|
|
|
// 设置相机位置为正上方
|
|
|
|
|
camera.value.position.set(0, 0, distance);
|
|
|
|
|
// 设置相机朝向为向下 (沿Z轴负方向)
|
|
|
|
|
camera.value.lookAt(0, 0, 0);
|
|
|
|
|
|
|
|
|
|
// 锁定控制器只能绕Z轴旋转 (水平旋转)
|
|
|
|
|
controls.value.minPolarAngle = Math.PI / 2;
|
|
|
|
|
controls.value.maxPolarAngle = Math.PI / 2;
|
|
|
|
|
|
|
|
|
|
topView.value = true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 切换俯视图和普通视图
|
|
|
|
|
const toggleTopView = () => {
|
|
|
|
|
if (!pcdLoaded.value) return;
|
|
|
|
|
|
|
|
|
|
if (topView.value) {
|
|
|
|
|
// 切换到普通视图
|
|
|
|
|
camera.value.position.copy(normalViewPosition);
|
|
|
|
|
camera.value.rotation.copy(normalViewRotation);
|
|
|
|
|
|
|
|
|
|
// 解锁控制器
|
|
|
|
|
controls.value.minPolarAngle = 0;
|
|
|
|
|
controls.value.maxPolarAngle = Math.PI;
|
|
|
|
|
|
|
|
|
|
topView.value = false;
|
|
|
|
|
} else {
|
|
|
|
|
// 切换到俯视图
|
|
|
|
|
const fov = camera.value.fov * (Math.PI / 180);
|
|
|
|
|
const distance = Math.max(pointCloudSize.x, pointCloudSize.y, pointCloudSize.z) / 2 / Math.tan(fov / 2);
|
|
|
|
|
setTopView(distance);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
controls.value.update();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 应用颜色渐变
|
|
|
|
|
const applyColorGradient = (pointCloud, maxDim) => {
|
|
|
|
|
const geometry = pointCloud.geometry;
|
|
|
|
|
|
|
|
|
|
// 检查点云是否已经有颜色
|
|
|
|
|
if (geometry.attributes.color) {
|
|
|
|
|
console.log("点云已有颜色信息,使用顶点颜色");
|
|
|
|
|
pointCloud.material = new THREE.PointsMaterial({
|
|
|
|
|
vertexColors: true,
|
|
|
|
|
size: maxDim * 0.0001, // 使用与参考代码相同的点大小计算方式
|
|
|
|
|
sizeAttenuation: true
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log("点云没有颜色信息,应用颜色渐变");
|
|
|
|
|
|
|
|
|
|
// 创建颜色数组
|
|
|
|
|
const colors = [];
|
|
|
|
|
const positions = geometry.attributes.position.array;
|
|
|
|
|
const numPoints = positions.length / 3;
|
|
|
|
|
|
|
|
|
|
// 定义起始颜色和结束颜色(与参考代码一致)
|
|
|
|
|
const startColor = new THREE.Color(0x0000ff); // 蓝色
|
|
|
|
|
const midColor1 = new THREE.Color(0x00ff00); // 绿色
|
|
|
|
|
const midColor2 = new THREE.Color(0xffff00); // 黄色
|
|
|
|
|
const endColor = new THREE.Color(0xff0000); // 红色
|
|
|
|
|
|
|
|
|
|
// 计算点云在 z 轴上的最小值和最大值
|
|
|
|
|
let minZ = Infinity;
|
|
|
|
|
let maxZ = -Infinity;
|
|
|
|
|
for (let i = 0; i < numPoints; i++) {
|
|
|
|
|
const z = positions[i * 3 + 2];
|
|
|
|
|
if (z < minZ) minZ = z;
|
|
|
|
|
if (z > maxZ) maxZ = z;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 应用四色渐变(与参考代码一致)
|
|
|
|
|
for (let i = 0; i < numPoints; i++) {
|
|
|
|
|
const z = positions[i * 3 + 2];
|
|
|
|
|
// 计算颜色渐变因子
|
|
|
|
|
const factor = (z - minZ) / (maxZ - minZ);
|
|
|
|
|
|
|
|
|
|
let color;
|
|
|
|
|
if (factor < 0.33) {
|
|
|
|
|
// 从蓝到绿的渐变
|
|
|
|
|
const subFactor = factor / 0.33;
|
|
|
|
|
color = startColor.clone().lerp(midColor1, subFactor);
|
|
|
|
|
} else if (factor < 0.66) {
|
|
|
|
|
// 从绿到黄的渐变
|
|
|
|
|
const subFactor = (factor - 0.33) / 0.33;
|
|
|
|
|
color = midColor1.clone().lerp(midColor2, subFactor);
|
|
|
|
|
} else {
|
|
|
|
|
console.error('pcdContainer is null');
|
|
|
|
|
// 从黄到红的渐变
|
|
|
|
|
const subFactor = (factor - 0.66) / 0.34;
|
|
|
|
|
color = midColor2.clone().lerp(endColor, subFactor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 将颜色添加到颜色数组中
|
|
|
|
|
colors.push(color.r, color.g, color.b);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 创建颜色属性
|
|
|
|
|
const colorAttribute = new THREE.Float32BufferAttribute(colors, 3);
|
|
|
|
|
geometry.setAttribute("color", colorAttribute);
|
|
|
|
|
|
|
|
|
|
// 创建材质(与参考代码一致)
|
|
|
|
|
pointCloud.material = new THREE.PointsMaterial({
|
|
|
|
|
vertexColors: true,
|
|
|
|
|
size: maxDim * 0.005,
|
|
|
|
|
sizeAttenuation: true
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const zoomIn = () => {
|
|
|
|
|
if (camera) {
|
|
|
|
|
camera.position.multiplyScalar(0.9);
|
|
|
|
|
camera.updateProjectionMatrix();
|
|
|
|
|
if (controls) {
|
|
|
|
|
controls.update();
|
|
|
|
|
}
|
|
|
|
|
// 初始化场景
|
|
|
|
|
const initScene = () => {
|
|
|
|
|
// 重置保存的状态
|
|
|
|
|
initialCameraPosition = null;
|
|
|
|
|
initialCameraRotation = null;
|
|
|
|
|
initialControlsTarget = null;
|
|
|
|
|
initialCameraZoom = 1;
|
|
|
|
|
pointCloudCenter = new THREE.Vector3();
|
|
|
|
|
pointCloudSize = new THREE.Vector3();
|
|
|
|
|
|
|
|
|
|
// 创建场景
|
|
|
|
|
scene.value = markRaw(new THREE.Scene());
|
|
|
|
|
scene.value.background = new THREE.Color(0x000000);
|
|
|
|
|
|
|
|
|
|
// 创建相机(与参考代码一致的参数)
|
|
|
|
|
const container = pcdContainer.value;
|
|
|
|
|
camera.value = markRaw(
|
|
|
|
|
new THREE.PerspectiveCamera(
|
|
|
|
|
75,
|
|
|
|
|
container.clientWidth / container.clientHeight,
|
|
|
|
|
0.1,
|
|
|
|
|
1000
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 创建渲染器
|
|
|
|
|
renderer.value = markRaw(new THREE.WebGLRenderer({ antialias: true }));
|
|
|
|
|
renderer.value.setSize(container.clientWidth, container.clientHeight);
|
|
|
|
|
container.appendChild(renderer.value.domElement);
|
|
|
|
|
|
|
|
|
|
// 添加轨道控制器
|
|
|
|
|
controls.value = markRaw(
|
|
|
|
|
new OrbitControls(camera.value, renderer.value.domElement)
|
|
|
|
|
);
|
|
|
|
|
controls.value.enableDamping = true;
|
|
|
|
|
controls.value.dampingFactor = 0.05;
|
|
|
|
|
|
|
|
|
|
// 添加环境光
|
|
|
|
|
const ambientLight = markRaw(new THREE.AmbientLight(0xffffff, 0.5));
|
|
|
|
|
scene.value.add(ambientLight);
|
|
|
|
|
|
|
|
|
|
// 添加方向光
|
|
|
|
|
const directionalLight = markRaw(new THREE.DirectionalLight(0xffffff, 0.8));
|
|
|
|
|
directionalLight.position.set(0, 0, 1); // 从上方照射
|
|
|
|
|
scene.value.add(directionalLight);
|
|
|
|
|
|
|
|
|
|
// 启动动画循环
|
|
|
|
|
const animate = () => {
|
|
|
|
|
if (scene.value && camera.value && renderer.value && controls.value) {
|
|
|
|
|
controls.value.update();
|
|
|
|
|
renderer.value.render(scene.value, camera.value);
|
|
|
|
|
}
|
|
|
|
|
animationId = requestAnimationFrame(animate);
|
|
|
|
|
};
|
|
|
|
|
animationId = requestAnimationFrame(animate);
|
|
|
|
|
|
|
|
|
|
// 监听窗口大小变化
|
|
|
|
|
window.addEventListener("resize", handleResize);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const zoomOut = () => {
|
|
|
|
|
if (camera) {
|
|
|
|
|
camera.position.multiplyScalar(1.1);
|
|
|
|
|
camera.updateProjectionMatrix();
|
|
|
|
|
if (controls) {
|
|
|
|
|
controls.update();
|
|
|
|
|
}
|
|
|
|
|
// 关闭对话框前重置视图
|
|
|
|
|
const resetViewBeforeClose = () => {
|
|
|
|
|
if (controls.value && camera.value && initialCameraPosition && pcdLoaded.value) {
|
|
|
|
|
// 重置相机到初始状态
|
|
|
|
|
camera.value.position.copy(initialCameraPosition);
|
|
|
|
|
camera.value.rotation.copy(initialCameraRotation);
|
|
|
|
|
camera.value.zoom = initialCameraZoom;
|
|
|
|
|
camera.value.updateProjectionMatrix();
|
|
|
|
|
|
|
|
|
|
controls.value.target.copy(initialControlsTarget);
|
|
|
|
|
controls.value.update();
|
|
|
|
|
|
|
|
|
|
// 强制渲染一帧
|
|
|
|
|
if (scene.value && renderer.value) {
|
|
|
|
|
renderer.value.render(scene.value, camera.value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const returnToCenter = () => {
|
|
|
|
|
if (initialCameraPosition && initialCameraRotation && initialControlsTarget && camera && controls) {
|
|
|
|
|
// 重置控制器的临时状态
|
|
|
|
|
controls.reset();
|
|
|
|
|
|
|
|
|
|
// 恢复相机位置
|
|
|
|
|
camera.position.copy(initialCameraPosition);
|
|
|
|
|
// 恢复相机旋转角度
|
|
|
|
|
camera.rotation.copy(initialCameraRotation);
|
|
|
|
|
// 恢复相机缩放
|
|
|
|
|
camera.zoom = initialCameraZoom;
|
|
|
|
|
|
|
|
|
|
// 多次更新投影矩阵和控制器
|
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
|
|
|
camera.updateProjectionMatrix();
|
|
|
|
|
if (controls) {
|
|
|
|
|
controls.update();
|
|
|
|
|
}
|
|
|
|
|
// 关闭对话框清理资源
|
|
|
|
|
const handleClose = () => {
|
|
|
|
|
// 关闭前重置视图
|
|
|
|
|
resetViewBeforeClose();
|
|
|
|
|
|
|
|
|
|
// 取消动画循环
|
|
|
|
|
if (animationId) {
|
|
|
|
|
cancelAnimationFrame(animationId);
|
|
|
|
|
animationId = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 移除渲染器
|
|
|
|
|
if (pcdContainer.value && renderer.value) {
|
|
|
|
|
pcdContainer.value.removeChild(renderer.value.domElement);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 释放资源
|
|
|
|
|
if (scene.value) {
|
|
|
|
|
scene.value.traverse((obj) => {
|
|
|
|
|
if (obj instanceof THREE.Mesh || obj instanceof THREE.Points) {
|
|
|
|
|
obj.geometry.dispose();
|
|
|
|
|
if (obj.material instanceof THREE.Material) {
|
|
|
|
|
obj.material.dispose();
|
|
|
|
|
} else {
|
|
|
|
|
obj.material.forEach(m => m.dispose());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 销毁控制器
|
|
|
|
|
if (controls.value) {
|
|
|
|
|
controls.value.dispose();
|
|
|
|
|
controls.value = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 重置状态
|
|
|
|
|
scene.value = null;
|
|
|
|
|
camera.value = null;
|
|
|
|
|
renderer.value = null;
|
|
|
|
|
pointCloud.value = null;
|
|
|
|
|
pcdLoaded.value = false;
|
|
|
|
|
loading.value = false;
|
|
|
|
|
|
|
|
|
|
// 移除事件监听
|
|
|
|
|
window.removeEventListener("resize", handleResize);
|
|
|
|
|
|
|
|
|
|
// 通知父组件关闭
|
|
|
|
|
emit("update:value", false);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 对话框打开时初始化
|
|
|
|
|
const onDialogOpened = () => {
|
|
|
|
|
if (!props.info.point_cloud_url) {
|
|
|
|
|
errorMessage.value = "没有点云数据URL";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 恢复控制器目标位置
|
|
|
|
|
controls.target.copy(initialControlsTarget);
|
|
|
|
|
// 初始化场景
|
|
|
|
|
initScene();
|
|
|
|
|
|
|
|
|
|
// 重置控制器的所有状态
|
|
|
|
|
controls.target0.copy(initialControlsTarget);
|
|
|
|
|
controls.position0.copy(initialCameraPosition);
|
|
|
|
|
controls.zoom0 = initialCameraZoom;
|
|
|
|
|
// 加载点云
|
|
|
|
|
loadPointCloud(props.info.point_cloud_url);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 延迟更新控制器
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
if (controls) {
|
|
|
|
|
controls.update();
|
|
|
|
|
}
|
|
|
|
|
}, 100);
|
|
|
|
|
} else {
|
|
|
|
|
console.error("Initial state is not defined");
|
|
|
|
|
}
|
|
|
|
|
// 相机控制函数
|
|
|
|
|
const zoomIn = () => {
|
|
|
|
|
if (camera.value) {
|
|
|
|
|
camera.value.position.multiplyScalar(0.9); // 与参考代码相同的缩放因子
|
|
|
|
|
camera.value.updateProjectionMatrix();
|
|
|
|
|
controls.value.update();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const zoomOut = () => {
|
|
|
|
|
if (camera.value) {
|
|
|
|
|
camera.value.position.multiplyScalar(1.1); // 与参考代码相同的缩放因子
|
|
|
|
|
camera.value.updateProjectionMatrix();
|
|
|
|
|
controls.value.update();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 回到中心功能(简化版本,直接设置而不是动画)
|
|
|
|
|
const returnToCenter = () => {
|
|
|
|
|
if (initialCameraPosition && initialCameraRotation && initialControlsTarget) {
|
|
|
|
|
camera.value.position.copy(initialCameraPosition);
|
|
|
|
|
camera.value.rotation.copy(initialCameraRotation);
|
|
|
|
|
camera.value.zoom = initialCameraZoom;
|
|
|
|
|
camera.value.updateProjectionMatrix();
|
|
|
|
|
|
|
|
|
|
controls.value.target.copy(initialControlsTarget);
|
|
|
|
|
controls.value.update();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 组件卸载时清理
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
// 确保动画循环已取消
|
|
|
|
|
if (animationId) {
|
|
|
|
|
cancelAnimationFrame(animationId);
|
|
|
|
|
animationId = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
window.removeEventListener("resize", handleResize);
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss">
|
|
|
|
|
/* 样式保持不变 */
|
|
|
|
|
.pointModal-wrap.el-dialog {
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
width: 1600px;
|
|
|
|
@ -311,10 +467,12 @@ const returnToCenter = () => {
|
|
|
|
|
background-position: center;
|
|
|
|
|
background-repeat: no-repeat;
|
|
|
|
|
background-color: #000;
|
|
|
|
|
|
|
|
|
|
.pcd-container {
|
|
|
|
|
position: relative;
|
|
|
|
|
width: 1560px;
|
|
|
|
|
height: 760px;
|
|
|
|
|
|
|
|
|
|
.button-first,
|
|
|
|
|
.button-second,
|
|
|
|
|
.button-thrid {
|
|
|
|
@ -322,35 +480,30 @@ const returnToCenter = () => {
|
|
|
|
|
width: 24px;
|
|
|
|
|
height: 24px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
background-image: url("@/assets/common/amplify_btn.png");
|
|
|
|
|
background-size: 100% 100%;
|
|
|
|
|
background-position: center;
|
|
|
|
|
background-repeat: no-repeat;
|
|
|
|
|
bottom: 112px;
|
|
|
|
|
right: 32px;
|
|
|
|
|
/* 添加过渡效果 */
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.button-first {
|
|
|
|
|
background-image: url("@/assets/common/amplify_btn.png");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.button-second {
|
|
|
|
|
bottom: 72px;
|
|
|
|
|
right: 32px;
|
|
|
|
|
background-image: url("@/assets/common/reduce_btn.png");
|
|
|
|
|
background-size: 100% 100%;
|
|
|
|
|
background-position: center;
|
|
|
|
|
background-repeat: no-repeat;
|
|
|
|
|
/* 添加过渡效果 */
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.button-thrid {
|
|
|
|
|
bottom: 33px;
|
|
|
|
|
right: 32px;
|
|
|
|
|
background-image: url("@/assets/common/reset_btn.png");
|
|
|
|
|
background-size: 100% 100%;
|
|
|
|
|
background-position: center;
|
|
|
|
|
background-repeat: no-repeat;
|
|
|
|
|
/* 添加过渡效果 */
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-bar {
|
|
|
|
|
position: absolute;
|
|
|
|
|
bottom: 20px;
|
|
|
|
@ -362,6 +515,7 @@ const returnToCenter = () => {
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress {
|
|
|
|
|
height: 100%;
|
|
|
|
|
background-color: #007bff;
|
|
|
|
|