Threejs路径规划案例V1

        最近在做一个路径规划的例子,大概场景是在一个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);
    },

 不过目前路径规划还有很多问题,后面会继续修改,但是已经可以实现直观的帮助我调整规划逻辑了

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

baker_zhuang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值