1.依赖引入
npm install three
2.创建组件
组件代码
<template>
<div
v-loading="loading"
@dblclick="onClick"
element-loading-background="rgba(0, 0, 0, 0.8)"
id="pcdcontainer"
>
</div>
</template>
<script>
import * as THREE from "three";
import {Scene, PerspectiveCamera, WebGLRenderer, DirectionalLight} from 'three';
import { PCDLoader } from "three/examples/jsm/loaders/PCDLoader.js";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { FormItem } from "element-ui";
export default {
props: {
//上传pcd文件路径
pcdUrl: {
type: String,
// required: true
},
//颜色控制
color: {
type: Number,
// required: true
},
//已标记点集合
markPoints:{
type:Array
},
identifying:{
type:Boolean
},
deleteFlag:{
type:Boolean
},
//判断是否为新增双击
isAdd:{
type:Boolean
}
},
created () {
this.localMarkPoints = this.markPoints.slice();
this.init()
this.mpoint()
this.delete()
console.log( this.localMarkPoints)
// this.localMarkPoints = this.markPoints.slice();
},
data () {
return {
localMarkPoints:this.markPoints,
clickedPointIndex :-1,
elem: null,
scene: null,
camera: null,
renderer: null,
loader: null,
controls: null,
mesh: null,
animationId: null,
pointcloud:{},
pointsMaterial:new THREE.PointsMaterial({ color: 0xff0000, size: 0.05 }),
selectedPointMaterial: new THREE.PointsMaterial({ color: 0x00ff00, size: 0.1 }),
selectedPoint:null,
clock: new THREE.Clock(),
mouse: new THREE.Vector2(1, 1),
client: { clientX: 0, clientY: 0 },
loading: true,
line:new THREE.LineBasicMaterial({ color: 0x0000ff }),
}
},
beforeMount () {
},
async mounted () {
// this.localMarkPoints = this.markPoints.slice();
await this.init()
//标记点加载渲染
await this.mpoint()
await this.cancel()
await this.delete()
this.localMarkPoints = this.markPoints.slice();
console.log( this.localMarkPoints)
},
methods: {
async init () {
let elem = document.getElementById('pcdcontainer');//获取要渲染的Dom
// 相机
this.camera = new THREE.PerspectiveCamera(
30, // 视野
elem.clientWidth / elem.clientHeight, // 纵横比
0.1, // 近平面
1000 // 远平面
);
this.renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
this.renderer.setClearColor(new THREE.Color(0x303030)); // 背景色
this.renderer.setSize(elem.clientWidth, elem.clientHeight);
elem.appendChild(this.renderer.domElement);
this.scene = new THREE.Scene(); // 场景
this.loader = new PCDLoader(); //PCD加载器
const THIS = this
//加载PCD文件
if(this.pcdUrl){
THIS.loader.load(
// this.pcdUrl,
this.pcdUrl,
function (points) {
points.geometry.rotateX(0.5 * Math.PI);//旋转模型,可调
points.material.color = new THREE.Color(THIS.color); // 模型颜色
THIS.pointcloud=points
THIS.scene.add(points);
var middle = new THREE.Vector3();
// points.material = THIS.selectedPointMaterial;
points.geometry.computeBoundingBox();
points.geometry.boundingBox.getCenter(middle);
// 构造盒子
points.applyMatrix4(
new THREE.Matrix4().makeTranslation(
-middle.x,
-middle.y,
-middle.z
)
);
// 比例
var largestDimension = Math.max(
points.geometry.boundingBox.max.x,
points.geometry.boundingBox.max.y,
points.geometry.boundingBox.max.z
);
THIS.camera.position.y = largestDimension * 3;//相机位置,可调
THIS.animate();
//轨道控制器 旋转、平移、缩放
THIS.controls = new OrbitControls(
THIS.camera,
THIS.renderer.domElement
);
THIS.controls.enableDamping = true;//旋转、平移开启阻尼
THIS.controls.addEventListener("change", THIS.render); // 监听鼠标、键盘事件
//放大缩小等
},
function (xhr) {
let load = xhr.loaded / xhr.total
if (load == 1) {
THIS.loading = false
}
},
function (error) {
console.log(error);
}
);
}
},
render () {
this.renderer.render(this.scene, this.camera);
},
delete(){
this.localMarkPoints=this.markPoints
},
cancel(){
if(this.identifying){
this.localMarkPoints=this.localMarkPoints.slice(0,-1);
}
console.log(this.identifying)
console.log(this.markPoints)
console.log(this.localMarkPoints)
console.log(this.scene)
},
animate () {
let delta = this.clock.getDelta();
if (this.controls) {
this.controls.update(delta);
}
this.animationId =requestAnimationFrame(this.animate)
this.render();
},
async mpoint(){
console.log(this.localMarkPoints)
if(this.localMarkPoints&&this.localMarkPoints.length!=0){
for(var i=0;i<this.localMarkPoints.length;i++){
var sphereGeometry = new THREE.TetrahedronGeometry(0.02, 8, 8);
var sphereMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
var selectedSphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
const point = new THREE.Vector3( this.localMarkPoints[i].xPoints,this.localMarkPoints[i].yPoints,this.localMarkPoints[i].zPoints );
selectedSphere.position.copy(point);
this.scene.add(selectedSphere)
var randomColor;
if(this.localMarkPoints[i].isColor){
randomColor = new THREE.Color(0xFF0000);
}else{
randomColor = new THREE.Color(Math.random(), Math.random(), Math.random());
}
selectedSphere.material.color = randomColor;
}
this.animate();
}
},
onClick(event) {
console.log(this.scene)
//添加点云的双击
if(this.isAdd){
event.preventDefault();
//判断当前点是否在图标内
const container = document.getElementById('pcdcontainer');
let getBoundingClientRect = container.getBoundingClientRect()
this.mouse.x = (event.offsetX / container.clientWidth) * 2 - 1;
this.mouse.y = -(event.offsetY / container.clientHeight) * 2 + 1;
const raycaster = new THREE.Raycaster();
raycaster.near = 0; // 射线的起始距离
raycaster.far = 100; // 射线的最大距离
raycaster.params.Points.threshold = 0.1; // 设置点云的阈值
raycaster.ray.direction.set(this.mouse.x, this.mouse.y, 0.5).unproject(this.camera).sub(this.camera.position).normalize();
raycaster.setFromCamera(this.mouse, this.camera);
const intersects = raycaster.intersectObject(this.scene );
if (intersects.length <= 0) return void 0;
let point = intersects[0].point;
intersects[ 0 ].object.material.color.set( 0xff0000 );
this.$emit('position',point)
var sphereGeometry = new THREE.TetrahedronGeometry(0.02, 8, 8);
var sphereMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
var selectedSphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
selectedSphere.position.copy(point);
// for (const markedPoint of this.scene.children){
// if(markedPoint instanceof THREE.Points){
// markedPoint
// }
// }
this.scene.add(selectedSphere)
const clickedPoint = {
xPoints: point.clone().x,
yPoints: point.clone().y,
zPoints: point.clone().z,
};
this.localMarkPoints.push(clickedPoint);
var randomColor = new THREE.Color(Math.random(), Math.random(), Math.random());
selectedSphere.material.color = randomColor;
}
else{
event.preventDefault();
const container = document.getElementById('pcdcontainer');
this.mouse.x = (event.offsetX / container.clientWidth) * 2 - 1;
this.mouse.y = -(event.offsetY / container.clientHeight) * 2 + 1;
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(this.mouse, this.camera);
const intersects = raycaster.intersectObject(this.scene);
if (intersects.length <= 0) return void 0;
let point = intersects[0].point;
let clickedPoint = intersects[0].point;
console.log(point)
console.log(this.markPoints)
//取已标记点最接近的点
let closestPoint = null;
let closestDistance = Infinity;
for (const markedPoint of this.scene.children) {
const markedVector = new THREE.Vector3(markedPoint.position
.x, markedPoint.position
.y,markedPoint.position
.z);
const distance = clickedPoint.distanceTo(markedVector);
if (distance < closestDistance) {
closestDistance = distance;
closestPoint = markedPoint;
}
}
if (closestPoint) {
//把标记点变红
var sphereGeometry = new THREE.TetrahedronGeometry(0.02, 8, 8);
var sphereMaterial = new THREE.MeshBasicMaterial({ color: 0xFF0000 });
var selectedSphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
selectedSphere=closestPoint
var color = new THREE.Color(0xFF0000);
selectedSphere.material.color = color;
this.scene.add(selectedSphere)
this.$emit('position',closestPoint)
}
}
},
},
beforeDestroy() {
clearTimeout(); // 这一行需要提供具体的定时器ID或函数,以清除定时器。例如:clearTimeout(this.timerId);
try {
// 清除场景中的子对象
this.scene.children.forEach(child => {
this.scene.remove(child);
});
// 释放渲染器的资源
this.renderer.dispose();
this.renderer.forceContextLoss(); // 不需要强制上下文丢失
this.renderer.domElement = null;
// 取消动画帧
cancelAnimationFrame(this.animationId);
// 关闭 WebGL 上下文
const gl = this.renderer.context;
if (gl) {
const loseContextExtension = gl.getExtension("WEBGL_lose_context");
if (loseContextExtension) {
loseContextExtension.loseContext();
}
}
} catch (e) {
console.error("An error occurred during cleanup:", e);
}
// 清除 Three.js 缓存
THREE.Cache.clear();
},
computed: {},
watch: {
markPoints (newValue) {
console.log(newValue)
// 当 markPoints prop 发生变化时,更新 localMarkPoints
this.localMarkPoints = newValue.slice();
},
},
filters: {},
components: {}
}
</script>
<style scoped lang='scss'>
#pcdcontainer {
width: 960px;
height:686px;
}
</style>
3.引用组件页面及效果展示
import StationPC3D from '@/components/StationPointCloud3D'
<StationPC3D :pcdUrl="pcdUrl"
@position="position" :key="key" :color="color" :markPoints="markPoints" :identifying="identifying" :deleteFlag="deleteFlag" :isAdd="isAdd"></StationPC3D>
//拿到点击坐标,给相关参数赋值
position(mouse){
this.workInfoModel = {
xPoints: mouse.x + "",
yPoints: mouse.y + "",
zPoints: mouse.z + "",
angle: 0,
deviceId: undefined,
direction: undefined,
addrId: this.currentPoint.id,
addrName: this.currentPoint.label,
remark: undefined,
stationCode: undefined,
stationId: "-1",
typeId: undefined,
stationName: undefined,
};
this.$nextTick(() => {
this.$refs["workInfoModelForm"].clearValidate();
});
this.ableOpen=true
this.selectItem = null;
this.selectMove = false;
this.clickRight = false;
},
4.流程分析
通过上传pcd文件,加载pcd文件,用户双击可视化图像上的点,然后对xyz坐标进行保存及渲染.每次加载pcd文件,可以把保存好的点,进行上传渲染
5.效果
最后,欢迎在评论区交流学习以及提问