Threejs实现立体3D园区解决方案及代码

一、实现方案

单独贴代码可能容易混乱,所以这里只讲实现思路,代码放在最后汇总了下。

想要实现一个简单的工业园区、主要包含的内容是一个大楼、左右两片停车位、四条道路以及多个可在道路上随机移动的车辆、遇到停车位时随机选择是否要停车,简单设计图如下

二、实现步奏

2.1 引入环境,天空和地面

 引入天空有三种方式:

  1) 第一种通过添加天空盒导入六个不同角度的天空图片可以形成,简单方便,缺点是在两个面之间会有视觉差

  2) 第二种是设置scene的背景和环境是一张天空图片来实现的,缺点图片单一,而且在天、地斜街处很生硬

  3) 不需要导入外部图片,通过在一个球体上添加渐变色实现,缺点球体只有一部分是天空颜色,内部为白色,需要设定旋转范围

  4) 使用Three.js中的example中的Sky.js实现,效果比较完美

 引入地面:给一个大平面添加一张草地纹理即可。

2.2 创建一块地基

  创建一个固定长度的平面,然后绕X轴旋转即可

2.3 布置围墙

  导入一个围墙模型作为一个围墙的基本单位A,算出围墙所占的长和宽,为了完整性,可以将园区的长和宽设定为A的整数倍。

2.4 办公楼、停车场、充电桩加载

  1)导入一个办公大楼模型

  2)创建一个停车场类Parking.js,主要用来创建单个停车位,其中需要计算出停车位的进入点,方便以后车辆进入。

  3)导入一个充电桩,每两个停车位使用一个充电桩

2.5 添加办公楼前景观、树、公交站点

  1)在指定位置导入景观模型和公交站点模型

  2)导入树模型,在园区前侧围墙均匀分布

2.6 铺设路面

       

           

  首先道路可以细化为上下行多个车道,而车辆则是行驶在各车道的中心线位置处,所以为了方便后续车辆的控制,需要先将道路拆分,然后获取各个道路中心线和车道中心线信息

  1)创建一个道路类Road.js,道路点信息传入的是图中红色点信息(图中菱形点),需要标记出从哪个点开始道路非直线,

    比如点信息格式为:[{ coord: [10, 0], type: 1}, { coord: [10, 10], type: 0}, { coord: [0, ], type: 1}] ;0代表曲线点,1代表直线点

  2)由于使用传入的原始道路点无法绘制出平滑的曲线而且在细化道路点的时候直线点数据细化不明显,所以需要先按照一定的间隔插入部分点信息(图中绿色五角星点)

  3)根据细化后的道路点按照道路宽度向两边开始扩展点信息,扩展方式通过获取当前点A和前一个点B组成的直线,取AB上垂线且距AB直线距离均为路宽的点即可,最终得到道路左侧点A和右侧点B

  4)通过ThreeJS中创建一条平滑曲线获取曲线上的多个点即可得到三条平滑的曲线A、B、C。

  5)经过第四步虽然可以得到道路数据,但是无法区分上下行,仍然不满足使用,通过图二上下行车辆最后生成组合成的一条闭合轨迹应该是逆时针的,

     所以需要将最后生成的A、B线顶点反转拼接成一个完整的多边形,如果是逆时针则可以得到正确的上下行路线。

  6)根据道路顶点即可画出道路面以及道路边界和中心线。

2.7 添加车辆以及车辆在道路随机移动的逻辑

  

             

  创建一个移动类,可以承接车辆或者行人,当前以车辆为主,主要包含移动轨迹、当前移动所在道路和车道、车位停车、驶离车位等内容。

  1)创建一个Move.js类,创建对象时传入停车场对象信息、道路对象信息,方便后续移动时可以计算出轨迹信息

  2)根据提供的初始位置计算出最近的道路和车道信息,与当前位置拼接在一起即可生成行动轨迹。

  3)当车辆移动到道路尽头时可以获取到本道路的另外一条车道即可实现掉头

  4)路口的判断:图三中,车辆由M车道途径N车道时,由M车道左侧当前位置和上一个位置组成的线段与N车道右侧车道起始或者终止点组成的线段有交集时则代表有路口,同样方法可以得到右侧道路的路口信息

  5)路口处拐入其他车道的轨迹生成:根据4)可以找到转向N的车道信息,但是无法保证平稳转向,所以可以通过找到M和N的车道中心线所在直线获取到交点C,然后由A、C、B生成一条贝塞尔曲线即可平滑转弯

2.8 添加停车逻辑以及车辆驶离逻辑

  1)寻找停车场:如图四,车辆在向前移动时,移动到的每个点都和所有停车场的入口B的位置判断距离,如果小于一个固定值的则代表临近车位,可以停车。

  2)停车方式:根据6)获取到的停车位,同时在当前路径上继续向前取15个点的位置C、B、A组成的曲线则是倒车入口的路径线。

三、遗留问题、待优化点

1. 拐弯添加的点不多,所以在拐弯处速度较快

  ---  可以通过在拐弯处组成的多个点通过生成的线获取多个点来解决这个问题

2. 需要添加一个路口来管理各条之间的关系

  --- 优点:(1). 有了路口后,可以解决车辆在路口移动时实时计算和其他路口的位置关系,可能会导致路口转弯混乱,通过在路口中心点生成一个外接圆,如果进入路口,则锁死移动方向,如果移出路口则解除锁定

    (2). 解决在路口处,各道路绘制的边线有重叠问题,使各个道路之间能看着更平滑

    缺点:最好不需要导入路口,而是由各个道路之间的相交关系计算得出,计算逻辑较为复杂。

3. 最好能添加一个停车场方便管理车位以及车辆驶入、驶离停车位

  --- 添加停车场,车辆只需要和停车场的位置计算即可,不需要和每个停车位计算位置,减少冗余计算,而且车辆如果和单个停车位计算位置,可能存在从停车位A使出,途径相邻的停车位B,又会进入。

    添加停车场通过给停车场添加标识即可解决这个问题

4. 车位和车道的边缘线无法加宽

  --- Three.js目前的缺陷,尝试几种办法暂时没有解决

5. 没有添加车辆防碰撞功能

四、完整的代码

  为了简单点,没有用Node安装依赖包,下述JS中引入的其他文件均在threeJS安装包中可以找到,拷贝过来即可。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>园区案例</title>
</head>

<body style="margin: 0;">
    <div id="webgl" style="border: 1px solid;"></div>
<script type="importmap">
    {
        "imports": {
            "three": "./three.module.js"
        }
    }
</script>
      <script type="module" src="./Objects/Main.js"></script>
    </script>
</body>

</html>

主页面index.html
/**
 * 办公园区
 */
import * as THREE from 'three';
import { OrbitControls } from '../OrbitControls.js';
import { GLTFLoader } from '../GLTFLoader.js';
import { addEnviorment, segmentsIntr } from '../Objects/Common.js';
import Move from './Move.js';
import Road from './Road.js';
import Parking from './Parking.js';

/**
 * 1. 先引入环境 天空和地面
 * 2. 创建一块全区的地皮
 * 3. 布置围墙
 * 4. 办公楼、停车场、充电桩的位置
 * 5. 添加办公楼前装饰物、树、公交站点
 * 6. 铺设路面
 * 7. 写动态逻辑,设置页面动态化
 */

const wWidth = window.innerWidth; // 屏幕宽度
const wHeight = window.innerHeight; // 屏幕高度

const scene = new THREE.Scene();
let renderer = null;
let camera = null;
let controls = null;
const roadObj = []; // 存储道路数据
const moveObj = []; // 存储车辆数据

// 园区宽度本身
const long = 600; // 园区的长
const width = 300; // 园区的宽
// 停车场的长和宽
const [parkingW, parkingH] = [20, 30];
const parks = []; // 存储停车场数据

let everyL = 0; // 单个围墙的长度
let everyW = 0; // 单个围墙的厚度
let buildH = 0; // 办公楼的厚度
let wallNumL = 0; // 展示园区占多少个墙的长度,当前设置为最大的整数-1
let wallNumW = 0;

/**
 * 初始化
 */
function init() {
  addEnvir(true, false);
  createBase();
  loadWall();
  setTimeout(() => {
    loadBuildings();
    setTimeout(() => {
      loadOrnament();
    }, 200)
    loadRoad();
    loadBusAndPeople();
    addClick();
  }, 500)
}

/**
 * 添加相机等基础功能
 */
function addEnvir(lightFlag = true, axFlag = true, gridFlag = false) {
  // 初始化相机
  camera = new THREE.PerspectiveCamera(100, wWidth / wHeight, 1, 3000);
  camera.position.set(300, 100, 300);
  camera.lookAt(0, 0, 0);

  // 创建灯光
  // 创建环境光
  const ambientLight = new THREE.AmbientLight(0xf0f0f0, 1.0);
  ambientLight.position.set(0,0,0);
  scene.add(ambientLight);
  if (lightFlag) {
    // 创建点光源
    const pointLight = new THREE.PointLight(0xffffff, 1);
    pointLight.decay = 0.0;
    pointLight.position.set(200, 200, 50);
    scene.add(pointLight);
  }

  // 添加辅助坐标系
  if (axFlag) {
    const axesHelper = new THREE.AxesHelper(150);
    scene.add(axesHelper);
  }

  // 添加网格坐标
  if (gridFlag) {
    const gridHelper = new THREE.GridHelper(300, 25, 0x004444, 0x004444);
    scene.add(gridHelper);
  }

  // 创建渲染器
  renderer = new THREE.WebGLRenderer({ antialias:true, logarithmicDepthBuffer: true });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setClearColor(0xf0f0f0, 0.8);
  renderer.setSize(wWidth, wHeight); //设置three.js渲染区域的尺寸(像素px)
  renderer.render(scene, camera); //执行渲染操作

  controls = new OrbitControls(camera, renderer.domElement);
  // 设置拖动范围
  controls.minPolarAngle = - Math.PI / 2;
  controls.maxPolarAngle = Math.PI / 2 - Math.PI / 360;
  
  controls.addEventListener('change', () => {
    renderer.render(scene, camera);
  })

  // 添加天空和草地
  scene.add(...addEnviorment());

  function render() {
    // 随机选择一个移动物体作为第一视角
    // const cur = moveObj[3];
    // if (cur) {
    //   const relativeCameraOffset = new THREE.Vector3(0, 20, -15);  
    
    //   const cameraOffset = relativeCameraOffset.applyMatrix4( cur.target.matrixWorld );  
        
    //   camera.position.x = cameraOffset.x;
    //   camera.position.y = cameraOffset.y;
    //   camera.position.z = cameraOffset.z;
    //   // 始终让相机看向物体  
    //   controls.target = cur.target.position;  

    //   camera.lookAt(...cur.target.position.toArray());
    // }
    
    renderer.render(scene, camera);
    requestAnimationFrame(render);
  }
  render();

  document.getElementById('webgl').appendChild(renderer.domElement);
}

/**
 * 创建园区的地基
 */
function createBase() {
  const baseGeo = new THREE.PlaneGeometry(long, width);
  baseGeo.rotateX(-Math.PI / 2);
  const baseMesh = new THREE.Mesh(
    baseGeo,
    new THREE.MeshBasicMaterial({
      color: '#808080',
      side: THREE.FrontSide
    })
  );
  baseMesh.name = 'BASE';
  scene.add(baseMesh);
}

/**
 * 加载围墙
 */
function loadWall() {
  const loader = new GLTFLoader();
  loader.load('./Objects/model/wall.gltf', (gltf) => {
    gltf.scene.scale.set(3, 3, 3);
    const source = gltf.scene.clone();

    // 获取单个围墙的大小
    const box3 = new THREE.Box3().setFromObject(gltf.scene);
    everyL = box3.max.x - box3.min.x;
    everyW = box3.max.z - box3.min.z;
    wallNumL = Math.floor(long / everyL) - 1;
    wallNumW = Math.floor(width / everyL) - 1;
    
    // 加载后墙
    // 墙的起点和终点
    const backS = [-long / 2, 0, -width / 2];
    for (let i = 0; i < wallNumL; i++) {
      const cloneWall = source.clone();
      cloneWall.position.x = backS[0] + everyL * i + everyL / 2;
      cloneWall.position.z = backS[2];
      scene.add(cloneWall);
    }

    // 加载左侧墙
    const leftS = [-long / 2, 0, -width / 2];
    for (let i = 0; i < wallNumW; i++) {
      const cloneWall = source.clone();
      cloneWall.rotateY(Math.PI / 2);
      cloneWall.position.x = leftS[0];
      cloneWall.position.z = leftS[2] + everyL * i + everyL / 2;
      scene.add(cloneWall);
    }

    // 加载右侧墙
    const rightS = [-long / 2 + wallNumL * everyL, 0, -width / 2];
    for (let i = 0; i < wallNumW; i++) {
      const cloneWall = source.clone();
      cloneWall.rotateY(Math.PI / 2);
      cloneWall.position.x = rightS[0];
      cloneWall.position.z = rightS[2] + everyL * i + everyL / 2;
      scene.add(cloneWall);
    }

    // 加载前侧墙
    const frontS = [-long / 2, 0, -width / 2 + wallNumW * everyL];
    for (let i = 0; i < wallNumL; i++) {
      if (i !== Math.floor(wallNumL / 2)) {
        const cloneWall = source.clone();
        cloneWall.position.x = frontS[0] + everyL * i + everyL / 2;
        cloneWall.position.z = frontS[2];
        scene.add(cloneWall);
      }
    }
  })
}

/**
 * 加载办公大楼以及停车场和充电桩
 */
function loadBuildings() {
  const loader = new GLTFLoader();
  loader.load('./Objects/model/buildings.gltf', (gltf) => {
    gltf.scene.scale.set(4, 4, 4);

    // 获取大楼的大小
    const box3 = new THREE.Box3().setFromObject(gltf.scene);
    buildH = box3.max.z - box3.min.z;
    gltf.scene.position.z = -width / 2 + buildH / 2;
    scene.add(gltf.scene);
  })

  // 添加左侧停车场
  // 左侧停车场起始点坐标
  const leftSPos = [-long / 2 + everyW + parkingH / 2, 0, -width / 2 + everyW + parkingW / 2 + 3];
  for (let i = 0; i < 4; i++) {
    const z =  leftSPos[2] + i * parkingW;
    const parking = new Parking({
      name: `A00${i + 1}`,
      width: parkingW,
      height: parkingH,
      position: [leftSPos[0], leftSPos[1] + 1, z]
    })
    scene.add(parking.group);
    parks.push(parking);
  }

  // 右侧充电桩起始点坐标 并预留位置给充电枪
  const rightSPos = [-long / 2 + wallNumL * everyL - everyW - parkingH / 2 - 10, 0, -width / 2 + everyW + parkingW / 2 + 3];
  for (let i = 0; i < 4; i++) {
    const parking = new Parking({
      name: `B00${i + 1}`,
      width: parkingW,
      height: parkingH,
      position: [rightSPos[0], rightSPos[1] + 1, rightSPos[2] + i * parkingW],
      rotate: Math.PI
    })
    scene.add(parking.group);
    parks.push(parking);
  }
  
  // 添加充电桩
  const chargePos = [-long / 2 + wallNumL * everyL - everyW - 4, 0, -width / 2 + everyW + 3 + parkingW];
  loader.load('./Objects/model/charging.gltf', (gltf) => {
    for (let i = 0; i < 2; i++) {
      const source = gltf.scene.clone();
      source.scale.set(6, 6, 6);
      source.rotateY(Math.PI / 2);
      source.position.x = chargePos[0];
      source.position.y = chargePos[1];
      source.position.z = chargePos[2] + i * 2 * parkingW;
      scene.add(source);
    }
  })
}


/**
 * 添加办公楼前装饰物、树、公交站点
 */
function loadOrnament() {
  // 加载办公室前方雕塑
  const loader = new GLTFLoader();
  loader.load('./Objects/model/bed.gltf', (bedGltf) => {
    bedGltf.scene.scale.set(2, 2, 2);
    bedGltf.scene.rotateY(-Math.PI * 7 / 12);
    loader.load('./Objects/model/sculpture.gltf', (sculGltf) => {
      sculGltf.scene.scale.set(20, 20, 20);
      sculGltf.scene.y = sculGltf.scene.y + 4;
      const group = new THREE.Group();
      group.add(bedGltf.scene);
      group.add(sculGltf.scene);
      group.position.set(0, 0, -width / 2 + everyW + buildH + 10);
      scene.add(group);
    });
  });
  
  // 加载树木,沿街用的是柏树
  loader.load('./Objects/model/songshu.gltf', (gltf) => {
    const source = gltf.scene;
    source.scale.set(8, 8, 8);
    // 前面墙的树木, 单个墙的中间区域放置一棵树
    const frontS = [-long / 2 + everyL / 2, 0, -width / 2 + wallNumW * everyL - 5];
    for (let i = 0; i < wallNumL; i++) {
      // 同样门口不放置树
      if (i !== Math.floor(wallNumL / 2)) {
        const temp = source.clone();
        temp.position.set(frontS[0] + i * everyL, frontS[1], frontS[2]);
        scene.add(temp);
      }
    }
  });

  // 加载公交站点,位置在距离大门右侧第二单面墙处
  loader.load('./Objects/model/busStops.gltf', (gltf) => {
    const source = gltf.scene;
    source.scale.set(4, 4, 4);
    gltf.scene.position.set(-long / 2 + (Math.floor(wallNumL / 2) + 3) * everyL, 0, -width / 2 + wallNumW * everyL + everyW + 3);
    scene.add(gltf.scene);
  });
}

/**
 * 铺设园区和园区外面的公路
 * 包含公路以及部分人行道路
 */
function loadRoad() {
  const space = 40;
  const outWidth = 40;
  // 加载园区外面的公路
  const outerP1 = [
    { coord: [-long / 2, 0, -width / 2 + wallNumW * everyL + space], type: 1 },
    { coord: [long / 2, 0, -width / 2 + wallNumW * everyL + space], type: 1 },
  ];
  const road1 = new Road({
    name: 'road_1',
    sourceCoord: outerP1,
    width: outWidth,
    showCenterLine: true
  });
  scene.add(road1.group);

  const outerP2 = [
    { coord: [-long / 2 + wallNumL * everyL + outWidth / 2  + 10, 0, -width / 2 + wallNumW * everyL + space - outWidth / 2 + 0.5], type: 1 },
    { coord: [-long / 2 + wallNumL * everyL + outWidth / 2  + 10, 0, -width / 2], type: 1 },
  ];
  const road2 = new Road({
    name: 'road_2',
    sourceCoord: outerP2,
    width: outWidth,
    showCenterLine: true,
    zIndex: 0.8
  });
  scene.add(road2.group);

  // 加载园区内的道路
  const innerWidth = 25;
  const color = 0x787878;
  const lineColor = 0xc2c2c2;
  // 加载到停车场的道路
  const innerP1 = [
    { coord: [-long / 2 + Math.floor(wallNumL / 2) * everyL + everyL / 2, 0, -width / 2 + wallNumW * everyL + space - outWidth / 2 + 0.5], type: 1 },
    { coord: [-long / 2 + Math.floor(wallNumL / 2) * everyL + everyL / 2, 0, -width / 2 + wallNumW * everyL + space - 60], type: 0 },
    { coord: [-long / 2 + Math.floor(wallNumL / 2) * everyL + everyL / 2 - innerWidth / 2, 0, -width / 2 + wallNumW * everyL + space - 60 - innerWidth / 2], type: 1 },
    { coord: [-long / 2 + parkingH + 20 + innerWidth / 2, 0, -width / 2 + wallNumW * everyL + space - 60 - innerWidth / 2], type: 0 },
    { coord: [-long / 2 + parkingH + 20, 0, -width / 2 + wallNumW * everyL + space - 60 - innerWidth], type: 1 },
    { coord: [-long / 2 + parkingH + 20, 0, -width / 2 + everyW + 10], type: 1 },
  ];
  const street1 = new Road({
    name: 'street_1',
    sourceCoord: innerP1,
    width: innerWidth,
    showCenterLine: true,
    zIndex: 0.8,
    planeColor: color,
    sideColor: lineColor
  });
  scene.add(street1.group);

  // 加载到充电桩的道路
  const innerP2 = [
    { coord: [-long / 2 + Math.floor(wallNumL / 2) * everyL + everyL / 2, 0, -width / 2 + wallNumW * everyL + space - outWidth / 2 + 0.5], type: 1 },
    { coord: [-long / 2 + Math.floor(wallNumL / 2) * everyL + everyL / 2, 0, -width / 2 + wallNumW * everyL + space - 60], type: 0 },
    { coord: [-long / 2 + Math.floor(wallNumL / 2) * everyL + everyL / 2 + innerWidth / 2, 0, -width / 2 + wallNumW * everyL + space - 60 - innerWidth / 2], type: 1 },
    { coord: [-long / 2 + wallNumL * everyL - parkingH - everyW - 39, 0, -width / 2 + wallNumW * everyL + space - 60 - innerWidth / 2], type: 0 },
    { coord: [-long / 2 + wallNumL * everyL - parkingH - everyW - 39 + innerWidth / 2, 0, -width / 2 + wallNumW * everyL + space - 60 - innerWidth], type: 1 },
    { coord: [-long / 2 + wallNumL * everyL - parkingH - everyW - 39 + innerWidth / 2, 0, -width / 2 + everyW + 10], type: 1 },
  ];
  const street2 = new Road({
    name: 'street_2',
    sourceCoord: innerP2,
    width: innerWidth,
    showCenterLine: true,
    zIndex: 0.8,
    planeColor: color,
    sideColor: lineColor
  });
  scene.add(street2.group);

  roadObj.push(
    road1,
    road2,
    street1,
    street2
  );

  calFork();
}

/**
 * 计算pointA和pointB 组成的直线与点集points是否有相交
 * @param {*} points 
 * @param {*} pontA 
 * @param {*} pointB 
 */
function judgeIntersect(points, pointA, pointB) {
  let res = { flag: false, interP: [] };
  for (let i = 0; i < points.length - 1; i++) {
    const cur = points[i];
    const nextP = points[i + 1];
    const interP = segmentsIntr(cur, nextP, pointA, pointB, true)
    if ( interP !== false) {
      res.flag = true;
      res.interP = interP;
      res.index = i;
      break;
    }
  }
  return res;
}

/**
 * 计算各条道路的岔口信息并统计到道路对象中
 */
function calFork() {
  function setInter(cur, next, interP, corner, width) {
    const circle = new THREE.ArcCurve(corner[0], corner[2], width * 2).getPoints(20);
    const cirPoints = circle.map(e => new THREE.Vector3(e.x, 0, e.y));

    cur.intersect.push({ name: next.name,
      interPoint: interP,
      corner: cirPoints,
      cornerCenter: corner
    });

    next.intersect.push({
      name: cur.name,
      interPoint: interP,
      corner: cirPoints,
      cornerCenter: corner
    });
  }

  roadObj.forEach((e, i) => {
    if (i < roadObj.length - 1) {
      for (let j = i + 1; j < roadObj.length; j++) {
        if (e.intersect.map(e => e.name).indexOf(roadObj[j].name) < 0) {
          const middle = roadObj[j].middle;
          // 计算路牙和其他道路是否有相交
          // 左边路牙和下一条路的起始位置做对比
          let inter = judgeIntersect(e.left, middle[0], middle[1]);
          if (inter.flag) {
            const cornerCenter = segmentsIntr(e.middle[inter.index], e.middle[inter.index + 1], middle[0], middle[1]);
            setInter(e, roadObj[j], inter.interP, cornerCenter, roadObj[j].width);
            continue;
          }

          // 左边路牙和下一条路的终止位置做对比
          inter = judgeIntersect(e.left, middle[middle.length - 1], middle[middle.length - 2])
          if (inter.flag) {
            const cornerCenter = segmentsIntr(e.middle[inter.index], e.middle[inter.index + 1], middle[middle.length - 1], middle[middle.length - 2]);
            setInter(e, roadObj[j], inter.interP, cornerCenter, roadObj[j].width);
            continue;
          } 
          
          // 右边路牙和下一条路的起始位置做对比
          inter = judgeIntersect(e.right, middle[0], middle[1]);
          if (inter.flag) {
            const cornerCenter = segmentsIntr(e.middle[inter.index], e.middle[inter.index + 1], middle[0], middle[1]);
            setInter(e, roadObj[j], inter.interP, cornerCenter, roadObj[j].width);
            continue;
          }
          
          // 右边路牙和下一条路的终止位置做对比
          inter = judgeIntersect(e.right, middle[middle.length - 1], middle[middle.length - 2]);
          if (inter.flag) {
            const cornerCenter = segmentsIntr(e.middle[inter.index], e.middle[inter.index + 1], middle[middle.length - 1], middle[middle.length - 2]);
            setInter(e, roadObj[j], inter.interP, cornerCenter, roadObj[j].width);
            continue;
          }
        }
      }
    }
  })
}


function actionTemp(target, name, flag, moveName) {
  const filter = roadObj.filter(e => e.name === name)[0];

  const carObject = new Move({
    name: moveName,
    target: target,
    roads: roadObj,
    startPos: flag ? filter.left[0] : filter.right[0],
    parks: parks
  });
  moveObj.push(carObject);
}

/**
 * 加载行人和汽车
 */
function loadBusAndPeople() {
  // 加载汽车和公交车
  const loader = new GLTFLoader();

  const carId = [
    'car0',
    'car2',
    'car4',
    'car5',
    'bus',

    'car3',
  ];
  const roadIds = [
    'road_1',
    'road_2',
    'street_1',
    'street_2',
    'street_2',

    'road_2',
  ];

  carId.forEach((e, i) => {
    loader.load(`./Objects/model/${e}.gltf`, (gltf) => {
      gltf.scene.scale.set(4, 4, 4);
      scene.add(gltf.scene);
      gltf.scene.name = e;
      actionTemp(gltf.scene, roadIds[i], false, e);
    });
  })
}

/**
 * 点击汽车驶离停车位
 */
function addClick() {
  renderer.domElement.addEventListener('click', (event) => {
    const px = event.offsetX;
    const py = event.offsetY;
    const x = (px / wWidth) * 2 - 1;
    const y = -(py / wHeight) * 2 + 1;
    //创建一个射线发射器
    const raycaster = new THREE.Raycaster();
    // .setFormCamera()计算射线投射器的射线属性ray
    // 即在点击位置创造一条射线,被射线穿过的模型代表选中
    raycaster.setFromCamera(new THREE.Vector2(x, y), camera);

    const intersects = raycaster.intersectObjects(moveObj.map(e => e.target));
    if (intersects.length > 0) {
      const move = moveObj.filter(e => e.name === intersects[0].object.parent.name || e.name === intersects[0].object.parent.parent.name)[0];
      if (move && move.pause) {
        move.unParkCar();
      }
    }
  })
}

init();

控制器Main.js
import * as THREE from 'three';
import { getCurvePoint, getSidePoints, segmentsIntr, clone, isClockWise } from './Common.js';

/**
 * 移动类,实现物体如何按照路径运动以及在岔路口如何选择等功能
 * 后期可以增加碰撞检测避让等功能
 */
class Road {
  constructor(props) {

    // 道路的原始点信息,通过这些点信息扩展道路
    this.sourceCoord = props.sourceCoord;

    // 道路名称
    this.name = props.name;

    // 道路宽度
    this.width = props.width;

    // 是否显示道路中心线
    this.showCenterLine = props.showCenterLine === false ? false : true;

    // 左侧路牙点集合
    this.left = [];

    // 道路中心线点集合
    this.middle = [];

    // 右侧路牙点集合
    this.right = [];

    // 道路面的颜色
    this.planeColor = props.planeColor || 0x606060;

    // 道路边线的颜色
    this.sideColor = props.sideColor || 0xffffff;

    // 道路中心线的颜色
    this.middleColor = props.middleColor || 0xe0e0e0;
    
    // 道路的层级
    this.zIndex = props.zIndex || 0.5;

    // 车道信息
    this.lanes = [];

    // 道路组合对象
    this.group = null;

    // 相交的道路名称 数据格式{name: ***, interPoint: [xx,xx,xx]}
    this.intersect = [];

    this.lineInsert();
    this.create();
  }

  /**
   * 由于直线获取贝塞尔点的时候插入的点较少导致物体运动较快,所以在
   * 平行与X、Y轴的线插入部分点,保证物体运动平滑,插入点时保证X或者Z轴间距为路宽的一半
   */
  lineInsert() {
    const temp = [];
    const half = this.width / 2;
    this.sourceCoord.forEach((cur, i) => {
      temp.push(cur);
      if (i < this.sourceCoord.length - 1) {
        const e = cur.coord;
        const nextP = this.sourceCoord[i + 1].coord;
        // 处理直线
        if (cur.type === 1) {
          if (e[0] - nextP[0] === 0) {
            // 平行Z轴
            if (e[2] < nextP[2]) {
              for (let i = e[2] + half; i < nextP[2]; i += half) {
                temp.push({
                  coord: [e[0], e[1], i],
                  type: 1
                });
              }
            } else {
              for (let i = e[2] - half; i > nextP[2]; i -= half) {
                temp.push({
                  coord: [e[0], e[1], i],
                  type: 1
                });
              }
            }
          } else if (e[2] - nextP[2] === 0) {
            // 平行X轴
            if (e[0] < nextP[0]) {
              for (let i = e[0] + half; i < nextP[0]; i += half) {
                temp.push({
                  coord: [i, e[1], e[2]],
                  type: 1
                });
              }
            } else {
              for (let i = e[0] - half; i > nextP[0]; i -= half) {
                temp.push({
                  coord: [i, e[1], e[2]],
                  type: 1
                });
              }
            }
          }
        }
      }
    })
    this.sourceCoord = temp;
  }

  /**
   * 创建道路
   */
  create() {
    const group = new THREE.Group();
    const roadPoints = this.getPoints(this.sourceCoord, this.width);
    
    this.left = roadPoints[0];
    this.middle = roadPoints[1];
    this.right = roadPoints[2];

    const isWise = isClockWise(this.left.concat(clone(this.right).reverse()));

    // 添加左车道
    this.lanes.push(new Lane({
      name: `${this.name}_lane_0`,
      type: 'left',
      isReverse: isWise,
      side: this.left,
      middle: this.middle
    }));

    // 添加右车道
    this.lanes.push(new Lane({
      name: `${this.name}_lane_1`,
      type: 'right',
      isReverse: !isWise,
      side: this.right,
      middle: this.middle
    }));
    
    const outlinePoint = roadPoints[0].concat(clone(roadPoints[2]).reverse());
    outlinePoint.push(roadPoints[0][0]);
    const shape = new THREE.Shape();
    outlinePoint.forEach((e, i) => {
      if (i === 0) {
        shape.moveTo(e[0], e[2], e[1]);
      } else {
        shape.lineTo(e[0], e[2], e[1]);
      }
    })

    // 创建道路面
    const plane = new THREE.Mesh(
      new THREE.ShapeGeometry(shape),
      new THREE.MeshBasicMaterial({
        color: this.planeColor,
        side: THREE.DoubleSide
      })
    )
    plane.rotateX(Math.PI / 2);
    group.add(plane);
    
    // 创建道路边沿线
    const sideL = new THREE.Line(
      new THREE.BufferGeometry().setFromPoints(roadPoints[0].map(e => new THREE.Vector3(...e))),
      new THREE.LineBasicMaterial({ color: this.sideColor, linewidth: 10 })
    );

    const sideR = new THREE.Line(
      new THREE.BufferGeometry().setFromPoints(roadPoints[2].map(e => new THREE.Vector3(...e))),
      new THREE.LineBasicMaterial({ color: this.sideColor, linewidth: 10 })
    );
    group.add(sideL, sideR);
    // 创建道路中心虚线
    if (this.showCenterLine) {
      const sideM = new THREE.Line(
        new THREE.BufferGeometry().setFromPoints(roadPoints[1].map(e => new THREE.Vector3(...e))),
        new THREE.LineDashedMaterial({ color: this.middleColor, linewidth: 10, gapSize: 5, dashSize: 5 })
      );
      sideM.computeLineDistances();
      group.add(sideM);
    }

    group.position.y = group.position.y + this.zIndex;
    group.name = this.name;
    this.group = group;
  }

  /**
   * 获取道路的顶点信息
   */
  getPoints(points) {
    const half = this.width / 2;

    // 存储左中右三条线路的顶点信息
    const [left, middle, right] = [[], [], []];
    for (let i = 0; i < points.length;) {
      const e = points[i].coord;
      if (points[i].type === 1) {
        // 直线处理方式
        if (i === 0) {
          const nextP = points[i + 1].coord;
          const side = getSidePoints(e, nextP, e, half);
          left.push(side[0]);
          middle.push(e);
          right.push(side[1]);
        } else {
          const preP = points[i - 1].coord;
          const side = getSidePoints(preP, e, e, half);
          left.push(side[0]);
          middle.push(e);
          right.push(side[1]);
        }
        i++;
      } else {
        // 曲线处理方式
        const preMidP = points[i - 1].coord;
        const nextMidP1 = points[i + 1].coord;
        const nextMidP2 = points[i + 2].coord;

        // 获取两侧点信息
        const sideP1 = getSidePoints(preMidP, e, e, half);
        const sideP2 = getSidePoints(nextMidP1, nextMidP2, nextMidP1, half);
        const sideP3 = getSidePoints(nextMidP1, nextMidP2, nextMidP2, half);
        
        // 左侧
        const interLeft = segmentsIntr(left[left.length - 1], sideP1[0], sideP2[0], sideP3[0]);
        const curveLeft = getCurvePoint(sideP1[0], interLeft, sideP2[0]);
        left.push(...curveLeft);

        // 中间
        const interMid = segmentsIntr(middle[middle.length - 1], e, nextMidP1, nextMidP2);
        const curveMid = getCurvePoint(e, interMid, nextMidP1);
        middle.push(...curveMid);

        // 右侧
        const interRight = segmentsIntr(right[right.length - 1], sideP1[1], sideP2[1], sideP3[1]);
        const curveRight = getCurvePoint(sideP1[1], interRight, sideP2[1]);
        right.push(...curveRight);
        i += 2;
      }
    }

    return [left, middle, right];
  }
}

/**
 * 车道对象
 */
class Lane {
  constructor(options) {
    //车道名称
    this.name = options.name;

    // 标识左车道还是右车道
    this.type = options.type;

    // 行驶方向和点的方向是否一致
    this.direction = options.direction;

    // 车道中心线
    this.coord = this.getCenter(options.middle, options.side, options.isReverse);
  }
  getCenter(middle, side, reverseFlag) {
    const center = middle.map((e, i) => {
      return [(e[0] + side[i][0]) / 2, e[1], (e[2] + side[i][2]) / 2];
    });
    return reverseFlag ? center.reverse() : center;
  }
}

export default Road;

道路类Road.js
import * as THREE from 'three';
import { calDistance, drawLine, clone, pointInPolygon, segmentsIntr } from './Common.js';

/**
 * 移动类,实现物体如何按照路径运动以及在岔路口如何选择等功能
 * 后期可以增加碰撞检测避让等功能
 */
class Move {
  constructor(props) {
    // 所有道路信息
    this.roads = {};
    props.roads.forEach(e => this.roads[e.name] = e);

    this.name = props.name;

    // 物体对象
    this.target = props.target;

    // 前进还是倒车 1:前进;-1:倒车
    this.direction = 1;

    // 物体初始位置
    this.startPos = props.startPos;

    // 移动轨迹
    this.trace = [];

    // 当前形式的道路
    this.curRoad = null;

    // 锁定下一步行动趋势,主要是为了解决在路口物体获取到下一步行动后再次获取行动导致功能乱掉
    this.trendLock = false;

    this.preRoadName = null;

    // 当前移动所在的车道
    this.curLane = null;

    // 是否暂停移动
    this.pause = false;

    // 园区停车场信息
    this.parks = props.parks;

    this.parkObj = null;

    this.init();
  }

  /**
   * 获取当前实体所在道路以及移动方向和移动轨迹
   */
  init() {
    let minDis = {
      roadName: '',  // 起始位置所在道路的道路名
      distance: 100000, // 起始物体与车道左右边线的最小距离
      curLane: null, // 移动的车道
    };
    for (let o in this.roads) {
      const road = this.roads[o];
      const startLeftDis = calDistance(road.lanes[0].coord[0], this.startPos);
      const startRightDis = calDistance(road.lanes[1].coord[0], this.startPos);
      const endLeftDis = calDistance(road.lanes[0].coord[road.lanes[0].coord.length - 1], this.startPos);
      const endRightDis = calDistance(road.lanes[1].coord[road.lanes[1].coord.length - 1], this.startPos);
      const min = Math.min(startLeftDis, startRightDis, endLeftDis, endRightDis);

      if (minDis.distance > min) {
        minDis = {
          roadName: o,
          distance: min,
          curLane: (min === startRightDis || min === endRightDis) ? road.lanes[0] : road.lanes[1],
          index: (startLeftDis === min || startRightDis === min) ? 0 : road.left.length - 1
        };
      }
    }

    this.curLane = minDis.curLane;
    
    this.curRoad = this.roads[minDis.roadName];
    this.trace = this.getSpreadPoint(this.curLane.coord);
    this.moveIndex = minDis.index;
    this.move();
  }

  /**
   * 获取车道中心线
   * 将车道的左右线取平均值即可
   * @param {*} params 
   */
  getLaneCenter(points1, points2) {
    return points1.map((e, i) => {
      return [(e[0] + points2[i][0]) / 2, e[1], (e[2] + points2[i][2]) / 2];
    })
  }

  move() {
    if (!this.pause) {
      this.checkMoveOutCorner();
      this.parkCar();
      // 如果移动到当前轨迹的最后一个点时,需要进行掉头重新寻找轨迹
      const forks = this.checkFork();
      if (forks.length !== 0) {
        this.getRandomTrend(forks);
      } else if (this.moveIndex === this.trace.length - 1) {
        this.getTurnPoint();
      }
      const curPosition = this.trace[this.moveIndex].toArray();
      this.target.position.set(...curPosition);
      if (this.direction === 1) {
        // 前进
        if (this.moveIndex !== this.trace.length - 1) {
          this.target.lookAt(...this.trace[this.moveIndex + 1].toArray());
        }
      } else {
        // 倒车
        if (this.moveIndex !== 0) {
          this.target.lookAt(...this.trace[this.moveIndex - 1].toArray());
        }
      }
      
      this.moveIndex ++;
    }
    requestAnimationFrame(this.move.bind(this));
  }

  /**
   * 在锁定后检查物体是否已经移出路口,如果移出则解除锁定
   */
  checkMoveOutCorner() {
    if (!this.trendLock) {
      return false;
    }
    const preObj = this.curRoad.intersect.filter(e => e.name === this.preRoadName)[0];
    if (preObj && !pointInPolygon(this.trace[this.moveIndex], preObj.corner)) {
      this.trendLock = false;
      this.preRoadName = null;
    }
    return this.trendLock;
  }

  /**
   * 根据提供的点信息寻找最近的点值
   */
  findNearIndex(points, pointA) {
    let min = 100000;
    let index = -1;
    for (let i = 0; i < points.length; i++) {
      const dis = calDistance(points[i], pointA);
      if (dis < min) {
        min = dis;
      } else {
        index = i - 1;
        break;
      }
    }
    return index;
  }

  /**
   * 在岔路口时随机获取下一步行动方向
   */
  getRandomTrend(forks) {
    const isEnd = calDistance(this.trace[this.moveIndex].toArray(), this.trace[this.trace.length - 1].toArray()) < this.curRoad.width / 2;
    // 从多条路中随机选择一条,当前园区路况简单 路口数据目前只有一条
    const randomRoad = forks[Math.floor(Math.random() * forks.length)];
    // 分别代表掉头、转弯、直行四种情况
    let types = [0, 1, 2];
    if (isEnd) {
      // 如果是道路的尽头 可以选择掉头或者转弯
      types = [0, 1, 2];
    } else {
      // 如果不是道路的尽头,可以选择转弯或者直行
      types = [1, 2];
    }

    const random = types[Math.floor(Math.random() * types.length)];
    if (random === 0) {
      // 掉头
      this.trendLock = true;
      this.getTurnPoint();
    } else if (random === 1) {
      // 转弯
      this.trendLock = true;
      this.getForkPoint(randomRoad, isEnd);
    } else if (random === 2) {
      this.preRoadName = randomRoad;
      // 直线
      this.trendLock = true;
    }
  }

  /**
   * 在岔路口时根据获取道路轨迹信息
   */
  getForkPoint(name, isEnd) {
    this.preRoadName = this.curRoad.name;
    const roadObj = this.roads[name];
    let splitPoint = [];
    if (isEnd) {
      // 如果在道路尽头转弯,随机产生是左侧还是右侧道路
      const leftOrRight = Math.floor(Math.random() * roadObj.lanes.length);
      const coord = roadObj.lanes[leftOrRight].coord;
      this.curLane = roadObj.lanes;
      const index = this.findNearIndex(coord, this.trace[this.moveIndex].toArray());
      splitPoint = coord.slice(index + 1);
      // 为了平滑过渡获取道路末端和当前行驶位置的交点
      const corInter = segmentsIntr(splitPoint[0], splitPoint[1], this.trace[this.trace.length - 2].toArray(), this.trace[this.trace.length - 3].toArray());
      if (corInter) {
        splitPoint.unshift(corInter);
      }
      splitPoint.unshift(this.trace[this.moveIndex]);
    } else {
      // 转弯前需要判断当前路可以向那个方向转弯,比如临近路口只能转向右车道,非临近路口需要转到对象车道
      // 可以根据当前点和车道点的距离来判断
      const lane1Dis = calDistance(roadObj.lanes[0].coord[0], this.trace[this.moveIndex].toArray());
      const lane2Dis = calDistance(roadObj.lanes[1].coord[0], this.trace[this.moveIndex].toArray());
      let temp = null;
      if (lane1Dis < lane2Dis) {
        temp = clone(roadObj.lanes[0].coord);
        this.curLane = roadObj.lanes[0];
      } else {
        temp = clone(roadObj.lanes[1].coord);
        this.curLane = roadObj.lanes[1];
      }
      this.curRoad = roadObj;
      const index = this.findNearIndex(temp, this.trace[this.moveIndex].toArray());
      splitPoint = temp.slice(index + 1);
      // 为了平滑过渡获取道路末端和当前行驶位置的交点
      const corInter = segmentsIntr(splitPoint[0], splitPoint[1], this.trace[this.moveIndex].toArray(), this.trace[this.moveIndex + 1].toArray());
      if (corInter) {
        splitPoint.unshift(corInter);  
      }
      splitPoint.unshift(this.trace[this.moveIndex]);
    }
    
    this.trace = this.getSpreadPoint(splitPoint).map(e => new THREE.Vector3(...e));
    // drawLine(this.target.parent, this.trace);

    this.moveIndex = 0;
  }

  /**
   * 掉头后获取道路轨迹信息
   */
  getTurnPoint() {
    const roadObj = this.curRoad;
    const nextLane = roadObj.lanes.filter(e => e.name !== this.curLane.name)[0]
    const clonePoint = clone(nextLane.coord);
    clonePoint.unshift(this.trace[this.moveIndex].toArray());
    this.trace = this.getSpreadPoint(clonePoint);
    this.curLane = nextLane;
    // drawLine(this.target.parent, this.trace);
    this.moveIndex = 0;
  }

  /**
   * 获取离散点即将点与点之间更加细化,使物体运行起来更加平滑
   * @param {*} points 
   * @returns 
   */
  getSpreadPoint(points, beta = 1) {
    const trail = new THREE.CatmullRomCurve3([...points.map(e => new THREE.Vector3(...e))]);
    return trail.getPoints(trail.getLength() * beta);
  }

  /**
   * 将车辆停入停车场内
   */
  parkCar() {
    // 是否允许停车
    if (this.parkEnable) {
      return;
    }
    if (this.parkObj) {
      if (this.direction === -1 && this.moveIndex === this.trace.length - 2) {
        this.pause = true;
        this.parkObj.object.placed = true;
      }
      if (this.moveIndex === this.trace.length - 1) {
        this.direction = -1;
        this.trace = this.parkObj.backTrace;
        // drawLine(this.target.parent, this.trace);
        this.moveIndex = 0;
      } else {
        return;
      }
    } else {
      let flag = false;
      let parkObj = null;
      const frontIndex = 13;
      for (let i = 0; i < this.parks.length; i++) {
        if (calDistance(this.parks[i].entryPoint, this.trace[this.moveIndex].toArray()) < 13 && !this.parks[i].placed) {
          flag = true;
          parkObj = this.parks[i];
          break;
        }
      }
      // return flag;
      if (flag) {
        const random = Math.floor(Math.random() * 2);
        // 1代表停车
        if (random === 1) {
          const front = this.trace.slice(this.moveIndex, this.moveIndex + frontIndex).map(e => new THREE.Vector3(...e));
          const back = this.getSpreadPoint([front[front.length - 1], parkObj.entryPoint, parkObj.position]);
          this.moveIndex = 0;
          this.trace = front;
          this.parkObj = { object: parkObj, backTrace: back };
        }
      }
    }
  }

  unParkCar() {
    this.direction = 1;
    const parkObj = this.parkObj.object;
    parkObj.placed = false;
    const index = this.findNearIndex(this.curLane.coord, parkObj.entryPoint);
    const temp = this.curLane.coord.slice(index);
    temp.unshift(parkObj.entryPoint);
    temp.unshift(parkObj.position);
    this.trace = this.getSpreadPoint(temp);
    this.moveIndex = 0;
    this.pause = false;
    this.parkObj = null;
    this.parkEnable = true;
    // 不想再扩展功能了 就在这临时用标识符处理了下出库时不断循环查询车位的情况
    setTimeout(() => {
      this.parkEnable = false;
    }, 3000)
  }

  /**
   * 检查岔路口
   */
  checkFork() {
    if (this.trendLock) {
      return [];
    }
    const forks = [];
    for (let i = 0; i < this.curRoad.intersect.length; i++) {
      if (this.moveIndex < this.trace.length - 1) {
        const dis1 = calDistance(this.trace[this.moveIndex].toArray(), this.curRoad.intersect[i].interPoint);
        const dis2 = calDistance(this.trace[this.moveIndex + 1].toArray(), this.curRoad.intersect[i].interPoint);
        if (dis1 < this.curRoad.width && dis1 > dis2) {
          forks.push(this.curRoad.intersect[i].name);
        }
      }
    }
    return forks;
  }
}

export default Move;

移动类Move.js

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/339437.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【MATLAB源码-第121期】基于matlab的斑马优化算法(ZOA)机器人栅格路径规划,输出做短路径图和适应度曲线。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 斑马优化算法&#xff08;Zebra Optimization Algorithm&#xff0c;简称ZOA&#xff09;是一种模仿斑马群体行为的优化算法。在自然界中&#xff0c;斑马是一种社会性很强的动物&#xff0c;它们具有独特的群体行为模式&…

精品基于Uniapp+springboot菜谱美食饮食健康管理App

《[含文档PPT源码等]精品基于Uniappspringboot饮食健康管理App》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功&#xff01; 软件开发环境及开发工具&#xff1a; 开发语言&#xff1a;Java 后台框架&#xff1a;springboot、ssm 安卓…

Leveraging Unlabeled Data for Crowd Counting by Learning to Rank

无标签人群技术&#xff0c;作者引入了一种排名。 利用的是一个图的人群数量一定小于等于包含这个图的图 生成排名数据集 作者提出了一种自监督任务&#xff0c;利用的是一个图的人群数量一定小于等于包含这个图的图 流程&#xff1a; 1.以图像中心为中心&#xff0c;划分一…

All the stories begin at installation

Before installation, there are some key points about Conan: “Conan is a dependency and package manager for C and C languages.”“With full binary management, Conan can create and reuse any number of different binaries (for different configurations like a…

MATLAB Fundamentals>>>Smoothing Data with Moving Average

MATLAB Fundamentals>Common Data Analysis Techniques>Smoothing Data> (2/5) Smoothing Data with Moving Average 例1&#xff1a; Smoothing method:Moving mean Moving window:Centered 2 代码2&#xff1a; % Smooth input data ySm smoothdata(y,"mov…

系统引导器GRUB

全称为GNU GRUB&#xff0c;来自GNU计划的多操作系统引导器。 作用&#xff1a; 查看引导分区内容 ls /boot ls /boot/grub2/ 查看GRUB2的配置文件 cat /boot/grub2/grub.cfg 可以看到/boot中保存的文件主要是linux内核、内存映像文件等。 注意到是子用户&#xff0c;但在…

Elasticsearch+Kibana 学习记录

文章目录 安装Elasticsearch 安装Kibana 安装 Rest风格API操作索引基本概念示例创建索引查看索引删除索引映射配置&#xff08;不配置好像也行、智能判断&#xff09;新增数据随机生成ID自定义ID 修改数据删除数据 查询基本查询查询所有&#xff08;match_all&#xff09;匹配查…

axios的基本使用

在项目根目录下创建js目录&#xff0c;目录中添加axios.min.js文件 准备页面&#xff1a;testAxios.html: 实现一个简单的GET请求 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title>…

Java数据结构之排序(头歌平台,详细注释)

目录 第1关&#xff1a;选择排序 任务描述 相关知识 代码&#xff1a; 第2关&#xff1a;插入排序 任务描述 相关知识 插入排序 代码&#xff1a; 第3关&#xff1a;归并排序 任务描述 相关知识 归并排序 原理 代码&#xff1a; 第4关&#xff1a;快速排序 任务描述 相关…

Prometheus配置Grafana监控大屏(Docker)

拉取镜像 docker pull grafana/grafana挂载目录 mkdir /data/prometheus/grafana -p chmod 777 /data/prometheus/grafana临时启动 docker run -d -p 3000:3000 --name grafana grafana/grafana从容器拷贝配置文件至对应目录 docker exec -it grafana cat /etc/grafana/gra…

[C++]:12:模拟实现list

[C]:12:模拟实现list 一.看一看SGI的stl_list的源码&#xff1a;1.基础结构构造函数1.节点结构&#xff1a;2.节点构造函数&#xff1a;3.链表结构&#xff1a;4.链表的构造函数&#xff1a; 2.析构1.节点析构&#xff1a;2.链表的析构&#xff1a; 3.迭代器 二.模拟实现list1.…

PyTorch深度学习实战(31)——生成对抗网络(Generative Adversarial Network, GAN)

PyTorch深度学习实战&#xff08;31&#xff09;——生成对抗网络 0. 前言1. GAN2. GAN 模型分析3. 利用 GAN 模型生成手写数字小结系列链接 0. 前言 生成对抗网络 (Generative Adversarial Networks, GAN) 是一种由两个相互竞争的神经网络组成的深度学习模型&#xff0c;它由…

EOCR电机保护器带煤电厂的具体应用

EOCR系列电动机智能保护器在煤电厂中有广泛的应用。这些保护器具有齐全的保护功能、直观的测量参数、快速的反应灵敏度、可靠的行动以及与上位机通讯构成远程监控的能力&#xff0c;使其成为理想的低压电动机保护及远程监控产品。 在煤电厂中&#xff0c;电动机保护器需要具备过…

SpringCloud Aliba-Sentinel【上篇】-从入门到学废【4】

&#x1f3b5;诗词分享&#x1f3b5; 大江东去&#xff0c;浪淘尽&#xff0c;千古风流人物。 ——苏轼《念奴娇赤壁怀古》 目录 &#x1f37f;1.Sentinel是什么 &#x1f9c2;2.特点 &#x1f9c8;3.下载 &#x1f32d;4.sentinel启动 &#x1f953;5.实例演示 1.Senti…

IOT pwn

已经过了填坑的黄金时期 环境搭建 交叉编译工具链 很多开源项目需要交叉编译到特定架构上&#xff0c;因此需要安装对应的交叉编译工具链。 sudo apt install gcc-arm-linux-gnueabi g-arm-linux-gnueabi -y sudo apt install gcc-aarch64-linux-gnu g-aarch64-linux-gnu -…

RK3568笔记十:Zlmediakit交叉编译

若该文为原创文章&#xff0c;转载请注明原文出处。 编译Zlmediakit的主要目的是想实现在RK3568拉取多路RTPS流&#xff0c;并通过MPP硬解码&#xff0c;DRM显示出来。为了实现拉取多路流选择了Zlmediakit,使用FFMEPG也可以&#xff0c;在RV1126上已经验证了可行性。 一、环境…

对MODNet 主干网络 MobileNetV2的剪枝探索

目录 1 引言 1.1 MODNet 原理 1.2 MODNet 模型分析 2 MobileNetV2 剪枝 2.1 剪枝过程 2.2 剪枝结果 2.2.1 网络结构 2.2.2 推理时延 2.3 实验结论 3 模型嵌入 3.1 模型保存与加载 法一&#xff1a;保存整个模型 法二&#xff1a;仅保存模型的参数 小试牛刀 小结…

服务端实现微信小游戏登录

1 微信小程序用户登录及其流程 小程序可以通过微信官方提供的登录能力,便能方便的获取微信提供的用户身份标识,达到建立用户体系的作用。 官方文档提供了登录流程时序图,如下: 从上述的登录流程时序图中我们发现,这里总共涉及到三个概念。 第一个是小程序,小程序即我们…

C#,入门教程(30)——扎好程序的笼子,错误处理 try catch

上一篇&#xff1a; C#&#xff0c;入门教程(29)——修饰词静态&#xff08;static&#xff09;的用法详解https://blog.csdn.net/beijinghorn/article/details/124683349 程序员语录&#xff1a;凡程序必有错&#xff0c;凡有错未必改&#xff01; 程序出错的原因千千万&…

Rockchip linux USB 驱动开发

Linux USB 驱动架构 Linux USB 协议栈是一个分层的架构&#xff0c;如下图 5-1 所示&#xff0c;左边是 USB Device 驱动&#xff0c;右边是 USB Host 驱动&#xff0c;最底层是 Rockchip 系列芯片不同 USB 控制器和 PHY 的驱动。 Linux USB 驱动架构 USB PHY 驱动开发 USB 2…