最近在做一个路径规划的例子,大概场景是在一个xy坐标系中,有几个障碍物,一个车要绕过这些障碍物到达目的地,本来用java来实现,但是java调试太不直观了,我就想起用threejs把场景简单搭建出来,规划好的路线也直接展示出来,就可以实时查看路径规划的怎么样了。
先来一张效果图:
首先是添加threejs场景,包括灯光,相机,渲染器,也包括地板,然后得到一个空的场景,为了方便观看,我们把相机调整到地图的正上方,且对准地图的中心点。
initScene(){
this.scene = new THREE.Scene();
const axesHelper = new THREE.AxesHelper( 100 );
axesHelper.position.set(0,0,10)
this.scene.add( axesHelper );
},
initCamera(){
this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000);
this.camera.position.set(200,150,400);
this.camera.lookAt(200,150,0);
// this.camera.up.set(0, 0, 1); // <=== spin // around Z-axis
},
initLight(){
//添加两个平行光
const directionalLight1 = new THREE.DirectionalLight(0xffffff, 1.5);
directionalLight1.position.set(300,300,600)
this.scene.add(directionalLight1);
const directionalLight2 = new THREE.DirectionalLight(0xffffff, 1.5);
directionalLight2.position.set(600,200,600)
this.scene.add(directionalLight2);
},
initFloor(){
let floorGeometry = new THREE.BoxGeometry( this.floor.floorWidth,this.floor.floorLength,this.floor.depth);
let floorMaterial = new THREE.MeshPhysicalMaterial({color:'#FFFFFF'});
let textureFloor = new THREE.TextureLoader().load('/static/images/floor.jpg', function (texture) {
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
})
floorMaterial.map = textureFloor
let floor = new THREE.Mesh( floorGeometry, floorMaterial );
floor.name = '地板';
floor.position.set(this.floor.floorWidth/2,this.floor.floorLength/2,0)
this.scene.add(floor)
},
initRenderer(){
this.renderer = new THREE.WebGLRenderer({ antialias: true });
this.container = document.getElementById("container")
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
this.renderer.setClearColor('#AAAAAA', 1.0);
this.container.appendChild(this.renderer.domElement);
},
initControl(){
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.controls.enableDamping = true;
this.controls.dampingFactor = 0.5;
// 视角最小距离
this.controls.minDistance = 100;
// 视角最远距离
this.controls.maxDistance = 1000;
// 最大角度
this.controls.target = new THREE.Vector3(200, 150, 0);
this.camera.position.set(200, 150, 400);
this.camera.lookAt(200, 150, 0);
},
initAnimate() {
requestAnimationFrame(this.initAnimate);
this.renderer.render(this.scene, this.camera);
},
为了方面后期调试,这里给页面添加上几个输入框,分别输入起始点的xy和目标点的xy,再加一个路径规划的按钮,输入起始点和目的地后点击路径规划,会擦除上一次的路径,再将本次的路径显示在地图上。这里可以引入element框架,简单且漂亮。
<div style="position:absolute;width:360px; right:30px;top:60px;">
<el-form :inline="true" class="demo-form-inline">
<el-form-item label="起始点X" label-width="100px">
<el-input style="width:80px;" type="number" v-model="beginPoint.x" placeholder="请输入Y"></el-input>
</el-form-item>
<el-form-item label="Y">
<el-input style="width:80px;" type="number" v-model="beginPoint.y" placeholder="请输入Y"></el-input>
</el-form-item>
<el-form-item label="目标点X" label-width="100px">
<el-input style="width:80px;" type="number" v-model="endPoint.x" placeholder="请输入Y"></el-input>
</el-form-item>
<el-form-item label="Y">
<el-input style="width:80px;" type="number" v-model="endPoint.y" placeholder="请输入Y"></el-input>
</el-form-item>
</el-form>
<el-button type="primary" @click="buildPath">绘制路径</el-button>
</div>
然后开始添加障碍物,这里先添加三个障碍物,随机放在地图上,障碍物用红色表示
initBox(){
const particlesGeometry1 = new THREE.BoxGeometry((this.box1.endX-this.box1.beginX), (this.box1.endY-this.box1.beginY), 10);
let material = new THREE.MeshPhongMaterial({
color: '#FF0000', // 设置颜色
shininess: 20 // 设置高光大小,范围为0到128,默认值为30
});
let box1 = new THREE.Mesh( particlesGeometry1, material, 0 );
box1.position.set((this.box1.endX+this.box1.beginX)/2,(this.box1.endY+this.box1.beginY)/2,5)
this.scene.add(box1);
const particlesGeometry2 = new THREE.BoxGeometry((this.box2.endX-this.box2.beginX), (this.box2.endY-this.box2.beginY),10);
let box2 = new THREE.Mesh( particlesGeometry2, material, 0 );
box2.position.set((this.box2.endX+this.box2.beginX)/2,(this.box2.endY+this.box2.beginY)/2,5)
this.scene.add(box2);
const particlesGeometry3 = new THREE.BoxGeometry((this.box3.endX-this.box3.beginX), (this.box3.endY-this.box3.beginY), 10);
let box3 = new THREE.Mesh( particlesGeometry3, material, 0 );
box3.position.set((this.box3.endX+this.box3.beginX)/2,(this.box3.endY+this.box3.beginY)/2,5)
this.scene.add(box3);
},
然后添加绘制线的方法,将路径上多个点用线连起来,我们再给每个点添加一个小圆球,我这里是要求线不能太长所以会有多个小圆球,但并非每个小圆球都是拐弯点,顺便再添加一个擦除的方法,在绘制的时候把线和点的name设置为相同的,后期擦除的时候就只需要比较同一个name了
drawPoint(point){
const particlesGeometry = new THREE.SphereGeometry(2, 8, 8);
let material = new THREE.MeshPhongMaterial({
color: '#00FF00', // 设置颜色
shininess: 20 // 设置高光大小,范围为0到128,默认值为30
});
let pointMesh = new THREE.Mesh( particlesGeometry, material, 0 );
pointMesh.position.set(point.x,point.y,point.z)
pointMesh.name ='once'
this.scene.add(pointMesh);
},
drawPath(pointList){
let drawPointList = [];
for (let i = 0; i < pointList.length; i++) {
let vector = new THREE.Vector3(pointList[i].x, pointList[i].y, pointList[i].z)
drawPointList.push(vector)
this.drawPoint(pointList[i])
}
if(pointList.length>1){
let curve = new THREE.CatmullRomCurve3(drawPointList);
curve.curveType = 'chordal'; // 曲线类型
curve.closed = false; // 曲线是否自动闭环
let ponits = curve.getPoints(100); // 分段值,数值越大,曲线越圆滑
let line = new THREE.Line(new THREE.BufferGeometry().setFromPoints(ponits), new THREE.LineBasicMaterial({ color: 0x000000 })); // 构建三维曲线
line.name ='once'
this.scene.add(line); // 加入场景
}
},
removeOnce(){
for (let i = 0; i < this.scene.children.length; i++) {
if(this.scene.children[i].name === 'once'){
this.scene.remove(this.scene.children[i]);
i--;
}
}
},
然后是添加路径规划,暂时是临时写的规划方法,逻辑是沿着x和y向目的地方向移动,当发现x或者y与障碍物重合时,就沿着另一个方向移动,直到没有障碍物遮挡。再继续沿着x和y向目的地移动,最终到达目标。
judgeObstacle(x,y){ //判断障碍物
let result = {
xy:"",
distance:0,
box:null
};
if(x>=this.box1.beginX && x<=this.box1.endX){
result.xy = "x";result.distance = Math.abs(this.box1.beginX-x);result.box = this.box1;
}else if (x>=this.box2.beginX && x<=this.box2.endX){
result.xy = "x";result.distance = Math.abs(this.box2.beginX-x);result.box = this.box2;
}else if (x>=this.box3.beginX && x<=this.box3.endX){
result.xy = "x";result.distance = Math.abs(this.box3.beginX-x);result.box = this.box2;
}
if(y>=this.box1.beginY && y<=this.box1.endY){
result.xy = "y";result.distance = Math.abs(this.box1.beginX-x);result.box = this.box1;
}else if(y>=this.box2.beginY && y<=this.box2.endY){
result.xy = "y";result.distance = Math.abs(this.box2.beginX-x);result.box = this.box2;
}else if(y>=this.box3.beginY && y<=this.box3.endY){
result.xy = "y";result.distance = Math.abs(this.box3.beginX-x);result.box = this.box3;
}
if(x>=this.box1.beginX && x<=this.box1.endX && y>=this.box1.beginY && y<=this.box1.endY){
result.xy = "xy";
}else if (x>=this.box2.beginX && x<=this.box2.endX && y>=this.box2.beginY && y<=this.box2.endY){
result.xy = "xy";
}else if (x>=this.box3.beginX && x<=this.box3.endX && y>=this.box3.beginY && y<=this.box3.endY){
result.xy = "xy";
}
return result;
},
buildPath(){
let judgeBegin = this.judgeObstacle(this.beginPoint.x,this.beginPoint.y)
let judgeEnd = this.judgeObstacle(this.endPoint.x,this.endPoint.y)
if("xy" === judgeBegin.xy || "xy" === judgeEnd.xy){
console.log("出发和结束点不能再障碍物里")
return "出发和结束点不能再障碍物里"
}
this.removeOnce();
let pointList = [];
let distanceX = this.endPoint.x - this.beginPoint.x;
let abxX = distanceX/Math.abs(distanceX)
let distanceY = this.endPoint.y - this.beginPoint.y;
let abxY = distanceY/Math.abs(distanceY)
let countX = Math.abs(distanceX) / 5;
let countY = Math.abs(distanceY) / 5;
let tempPoint = {
x:this.beginPoint.x,
y:this.beginPoint.y
}
for (let i = 0; i < countX; i++) {
tempPoint.x = parseInt(tempPoint.x) + 5 * abxX
if(countY>0){
countY --;
tempPoint.y = parseInt(tempPoint.y) + 5 * abxY
}
let judgeResult = this.judgeObstacle(tempPoint.x, tempPoint.y);
if("x" === judgeResult.xy){
if(i === countX){
tempPoint.x = parseInt(tempPoint.x) + 5 * abxX
}else{
if(countY>0){
countY ++;
tempPoint.y = tempPoint.y - 5 * abxY
}
}
console.log("在x里")
}
else if("y" === judgeResult.xy){
if(countY === 0){
tempPoint.y = parseInt(tempPoint.y) + 5 * abxX
}else{
i --;
tempPoint.x = tempPoint.x - 5 * abxX
}
console.log("在y里,")
}
console.log(tempPoint.x + " " + tempPoint.y)
let point = {x:tempPoint.x,y:tempPoint.y,z:2}
pointList.push(point)
}
let point = {x:this.endPoint.x,y:this.endPoint.y,z:2}
pointList.push(point)
this.drawPath(pointList);
},
不过目前路径规划还有很多问题,后面会继续修改,但是已经可以实现直观的帮助我调整规划逻辑了
标签:Threejs,案例,THREE,V1,let,new,box1,tempPoint,box3 From: https://blog.51cto.com/BakerZhuang/10894150