国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Threejs實(shí)現(xiàn)一個(gè)園區(qū)

這篇具有很好參考價(jià)值的文章主要介紹了Threejs實(shí)現(xiàn)一個(gè)園區(qū)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

Threejs實(shí)現(xiàn)一個(gè)園區(qū)? ? ? ?

一、實(shí)現(xiàn)方案

單獨(dú)貼代碼可能容易混亂,所以這里只講實(shí)現(xiàn)思路,代碼放在最后匯總了下。

想要實(shí)現(xiàn)一個(gè)簡(jiǎn)單的工業(yè)園區(qū)、主要包含的內(nèi)容是一個(gè)大樓、左右兩片停車(chē)位、四條道路以及多個(gè)可在道路上隨機(jī)移動(dòng)的車(chē)輛、遇到停車(chē)位時(shí)隨機(jī)選擇是否要停車(chē),簡(jiǎn)單設(shè)計(jì)圖如下

Threejs實(shí)現(xiàn)一個(gè)園區(qū)

二、實(shí)現(xiàn)步奏

2.1 引入環(huán)境,天空和地面

 引入天空有三種方式:

  1) 第一種通過(guò)添加天空盒導(dǎo)入六個(gè)不同角度的天空?qǐng)D片可以形成,簡(jiǎn)單方便,缺點(diǎn)是在兩個(gè)面之間會(huì)有視覺(jué)差

  2)?第二種是設(shè)置scene的背景和環(huán)境是一張?zhí)炜請(qǐng)D片來(lái)實(shí)現(xiàn)的,缺點(diǎn)圖片單一,而且在天、地斜街處很生硬

  3)?不需要導(dǎo)入外部圖片,通過(guò)在一個(gè)球體上添加漸變色實(shí)現(xiàn),缺點(diǎn)球體只有一部分是天空顏色,內(nèi)部為白色,需要設(shè)定旋轉(zhuǎn)范圍

  4)?使用Three.js中的example中的Sky.js實(shí)現(xiàn),效果比較完美

 引入地面:給一個(gè)大平面添加一張草地紋理即可。

2.2 創(chuàng)建一塊地基

  創(chuàng)建一個(gè)固定長(zhǎng)度的平面,然后繞X軸旋轉(zhuǎn)即可

2.3 布置圍墻

  導(dǎo)入一個(gè)圍墻模型作為一個(gè)圍墻的基本單位A,算出圍墻所占的長(zhǎng)和寬,為了完整性,可以將園區(qū)的長(zhǎng)和寬設(shè)定為A的整數(shù)倍。

2.4?辦公樓、停車(chē)場(chǎng)、充電樁加載

  1)導(dǎo)入一個(gè)辦公大樓模型

  2)創(chuàng)建一個(gè)停車(chē)場(chǎng)類(lèi)Parking.js,主要用來(lái)創(chuàng)建單個(gè)停車(chē)位,其中需要計(jì)算出停車(chē)位的進(jìn)入點(diǎn),方便以后車(chē)輛進(jìn)入。

  3)導(dǎo)入一個(gè)充電樁,每?jī)蓚€(gè)停車(chē)位使用一個(gè)充電樁

2.5?添加辦公樓前景觀、樹(shù)、公交站點(diǎn)

  1)在指定位置導(dǎo)入景觀模型和公交站點(diǎn)模型

  2)導(dǎo)入樹(shù)模型,在園區(qū)前側(cè)圍墻均勻分布

2.6?鋪設(shè)路面

 ? ? ? Threejs實(shí)現(xiàn)一個(gè)園區(qū)? ? ? ? ? ?Threejs實(shí)現(xiàn)一個(gè)園區(qū)

  首先道路可以細(xì)化為上下行多個(gè)車(chē)道,而車(chē)輛則是行駛在各車(chē)道的中心線位置處,所以為了方便后續(xù)車(chē)輛的控制,需要先將道路拆分,然后獲取各個(gè)道路中心線和車(chē)道中心線信息

  1)創(chuàng)建一個(gè)道路類(lèi)Road.js,道路點(diǎn)信息傳入的是圖中紅色點(diǎn)信息(圖中菱形點(diǎn)),需要標(biāo)記出從哪個(gè)點(diǎn)開(kāi)始道路非直線,

    比如點(diǎn)信息格式為:[{ coord: [10, 0], type: 1}, { coord: [10, 10], type: 0},?{ coord: [0, ], type: 1}] ;0代表曲線點(diǎn),1代表直線點(diǎn)

  2)由于使用傳入的原始道路點(diǎn)無(wú)法繪制出平滑的曲線而且在細(xì)化道路點(diǎn)的時(shí)候直線點(diǎn)數(shù)據(jù)細(xì)化不明顯,所以需要先按照一定的間隔插入部分點(diǎn)信息(圖中綠色五角星點(diǎn))

  3)根據(jù)細(xì)化后的道路點(diǎn)按照道路寬度向兩邊開(kāi)始擴(kuò)展點(diǎn)信息,擴(kuò)展方式通過(guò)獲取當(dāng)前點(diǎn)A和前一個(gè)點(diǎn)B組成的直線,取AB上垂線且距AB直線距離均為路寬的點(diǎn)即可,最終得到道路左側(cè)點(diǎn)A和右側(cè)點(diǎn)B

  4)通過(guò)ThreeJS中創(chuàng)建一條平滑曲線獲取曲線上的多個(gè)點(diǎn)即可得到三條平滑的曲線A、B、C。

  5)經(jīng)過(guò)第四步雖然可以得到道路數(shù)據(jù),但是無(wú)法區(qū)分上下行,仍然不滿足使用,通過(guò)圖二上下行車(chē)輛最后生成組合成的一條閉合軌跡應(yīng)該是逆時(shí)針的,

   ? 所以需要將最后生成的A、B線頂點(diǎn)反轉(zhuǎn)拼接成一個(gè)完整的多邊形,如果是逆時(shí)針則可以得到正確的上下行路線。

  6)根據(jù)道路頂點(diǎn)即可畫(huà)出道路面以及道路邊界和中心線。

2.7?添加車(chē)輛以及車(chē)輛在道路隨機(jī)移動(dòng)的邏輯

  Threejs實(shí)現(xiàn)一個(gè)園區(qū)? ? ? ? ? ? ?Threejs實(shí)現(xiàn)一個(gè)園區(qū)

  創(chuàng)建一個(gè)移動(dòng)類(lèi),可以承接車(chē)輛或者行人,當(dāng)前以車(chē)輛為主,主要包含移動(dòng)軌跡、當(dāng)前移動(dòng)所在道路和車(chē)道、車(chē)位停車(chē)、駛離車(chē)位等內(nèi)容。

  1)創(chuàng)建一個(gè)Move.js類(lèi),創(chuàng)建對(duì)象時(shí)傳入停車(chē)場(chǎng)對(duì)象信息、道路對(duì)象信息,方便后續(xù)移動(dòng)時(shí)可以計(jì)算出軌跡信息

  2)根據(jù)提供的初始位置計(jì)算出最近的道路和車(chē)道信息,與當(dāng)前位置拼接在一起即可生成行動(dòng)軌跡。

  3)當(dāng)車(chē)輛移動(dòng)到道路盡頭時(shí)可以獲取到本道路的另外一條車(chē)道即可實(shí)現(xiàn)掉頭

  4)路口的判斷:圖三中,車(chē)輛由M車(chē)道途徑N車(chē)道時(shí),由M車(chē)道左側(cè)當(dāng)前位置和上一個(gè)位置組成的線段與N車(chē)道右側(cè)車(chē)道起始或者終止點(diǎn)組成的線段有交集時(shí)則代表有路口,同樣方法可以得到右側(cè)道路的路口信息

  5)路口處拐入其他車(chē)道的軌跡生成:根據(jù)4)可以找到轉(zhuǎn)向N的車(chē)道信息,但是無(wú)法保證平穩(wěn)轉(zhuǎn)向,所以可以通過(guò)找到M和N的車(chē)道中心線所在直線獲取到交點(diǎn)C,然后由A、C、B生成一條貝塞爾曲線即可平滑轉(zhuǎn)彎

2.8?添加停車(chē)邏輯以及車(chē)輛駛離邏輯

  1)尋找停車(chē)場(chǎng):如圖四,車(chē)輛在向前移動(dòng)時(shí),移動(dòng)到的每個(gè)點(diǎn)都和所有停車(chē)場(chǎng)的入口B的位置判斷距離,如果小于一個(gè)固定值的則代表臨近車(chē)位,可以停車(chē)。

  2)停車(chē)方式:根據(jù)6)獲取到的停車(chē)位,同時(shí)在當(dāng)前路徑上繼續(xù)向前取15個(gè)點(diǎn)的位置C、B、A組成的曲線則是倒車(chē)入口的路徑線。

三、遺留問(wèn)題、待優(yōu)化點(diǎn)

1.?拐彎添加的點(diǎn)不多,所以在拐彎處速度較快

  ---? 可以通過(guò)在拐彎處組成的多個(gè)點(diǎn)通過(guò)生成的線獲取多個(gè)點(diǎn)來(lái)解決這個(gè)問(wèn)題

2.?需要添加一個(gè)路口來(lái)管理各條之間的關(guān)系

  ---?優(yōu)點(diǎn):(1).?有了路口后,可以解決車(chē)輛在路口移動(dòng)時(shí)實(shí)時(shí)計(jì)算和其他路口的位置關(guān)系,可能會(huì)導(dǎo)致路口轉(zhuǎn)彎混亂,通過(guò)在路口中心點(diǎn)生成一個(gè)外接圓,如果進(jìn)入路口,則鎖死移動(dòng)方向,如果移出路口則解除鎖定

    (2).?解決在路口處,各道路繪制的邊線有重疊問(wèn)題,使各個(gè)道路之間能看著更平滑

    缺點(diǎn):最好不需要導(dǎo)入路口,而是由各個(gè)道路之間的相交關(guān)系計(jì)算得出,計(jì)算邏輯較為復(fù)雜。

3.?最好能添加一個(gè)停車(chē)場(chǎng)方便管理車(chē)位以及車(chē)輛駛?cè)搿Ⅰ傠x停車(chē)位

  ---?添加停車(chē)場(chǎng),車(chē)輛只需要和停車(chē)場(chǎng)的位置計(jì)算即可,不需要和每個(gè)停車(chē)位計(jì)算位置,減少冗余計(jì)算,而且車(chē)輛如果和單個(gè)停車(chē)位計(jì)算位置,可能存在從停車(chē)位A使出,途徑相鄰的停車(chē)位B,又會(huì)進(jìn)入。

    添加停車(chē)場(chǎng)通過(guò)給停車(chē)場(chǎng)添加標(biāo)識(shí)即可解決這個(gè)問(wèn)題

4.?車(chē)位和車(chē)道的邊緣線無(wú)法加寬

  ---?Three.js目前的缺陷,嘗試幾種辦法暫時(shí)沒(méi)有解決

5.?沒(méi)有添加車(chē)輛防碰撞功能

四、完整的代碼

  為了簡(jiǎn)單點(diǎn),沒(méi)有用Node安裝依賴包,下述JS中引入的其他文件均在threeJS安裝包中可以找到,拷貝過(guò)來(lái)即可。

Threejs實(shí)現(xiàn)一個(gè)園區(qū)Threejs實(shí)現(xiàn)一個(gè)園區(qū)
<!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>園區(qū)案例</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>
主頁(yè)面index.html
Threejs實(shí)現(xiàn)一個(gè)園區(qū)Threejs實(shí)現(xiàn)一個(gè)園區(qū)
/**
 * 辦公園區(qū)
 */
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. 先引入環(huán)境 天空和地面
 * 2. 創(chuàng)建一塊全區(qū)的地皮
 * 3. 布置圍墻
 * 4. 辦公樓、停車(chē)場(chǎng)、充電樁的位置
 * 5. 添加辦公樓前裝飾物、樹(shù)、公交站點(diǎn)
 * 6. 鋪設(shè)路面
 * 7. 寫(xiě)動(dòng)態(tài)邏輯,設(shè)置頁(yè)面動(dòng)態(tài)化
 */

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

const scene = new THREE.Scene();
let renderer = null;
let camera = null;
let controls = null;
const roadObj = []; // 存儲(chǔ)道路數(shù)據(jù)
const moveObj = []; // 存儲(chǔ)車(chē)輛數(shù)據(jù)

// 園區(qū)寬度本身
const long = 600; // 園區(qū)的長(zhǎng)
const width = 300; // 園區(qū)的寬
// 停車(chē)場(chǎng)的長(zhǎng)和寬
const [parkingW, parkingH] = [20, 30];
const parks = []; // 存儲(chǔ)停車(chē)場(chǎng)數(shù)據(jù)

let everyL = 0; // 單個(gè)圍墻的長(zhǎng)度
let everyW = 0; // 單個(gè)圍墻的厚度
let buildH = 0; // 辦公樓的厚度
let wallNumL = 0; // 展示園區(qū)占多少個(gè)墻的長(zhǎng)度,當(dāng)前設(shè)置為最大的整數(shù)-1
let wallNumW = 0;

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

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

  // 創(chuàng)建燈光
  // 創(chuàng)建環(huán)境光
  const ambientLight = new THREE.AmbientLight(0xf0f0f0, 1.0);
  ambientLight.position.set(0,0,0);
  scene.add(ambientLight);
  if (lightFlag) {
    // 創(chuàng)建點(diǎn)光源
    const pointLight = new THREE.PointLight(0xffffff, 1);
    pointLight.decay = 0.0;
    pointLight.position.set(200, 200, 50);
    scene.add(pointLight);
  }

  // 添加輔助坐標(biāo)系
  if (axFlag) {
    const axesHelper = new THREE.AxesHelper(150);
    scene.add(axesHelper);
  }

  // 添加網(wǎng)格坐標(biāo)
  if (gridFlag) {
    const gridHelper = new THREE.GridHelper(300, 25, 0x004444, 0x004444);
    scene.add(gridHelper);
  }

  // 創(chuàng)建渲染器
  renderer = new THREE.WebGLRenderer({ antialias:true, logarithmicDepthBuffer: true });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setClearColor(0xf0f0f0, 0.8);
  renderer.setSize(wWidth, wHeight); //設(shè)置three.js渲染區(qū)域的尺寸(像素px)
  renderer.render(scene, camera); //執(zhí)行渲染操作

  controls = new OrbitControls(camera, renderer.domElement);
  // 設(shè)置拖動(dòng)范圍
  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() {
    // 隨機(jī)選擇一個(gè)移動(dòng)物體作為第一視角
    // 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;
    //   // 始終讓相機(jī)看向物體  
    //   controls.target = cur.target.position;  

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

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

/**
 * 創(chuàng)建園區(qū)的地基
 */
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();

    // 獲取單個(gè)圍墻的大小
    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;
    
    // 加載后墻
    // 墻的起點(diǎn)和終點(diǎn)
    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);
    }

    // 加載左側(cè)墻
    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);
    }

    // 加載右側(cè)墻
    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);
    }

    // 加載前側(cè)墻
    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);
      }
    }
  })
}

/**
 * 加載辦公大樓以及停車(chē)場(chǎng)和充電樁
 */
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);
  })

  // 添加左側(cè)停車(chē)場(chǎng)
  // 左側(cè)停車(chē)場(chǎng)起始點(diǎn)坐標(biāo)
  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);
  }

  // 右側(cè)充電樁起始點(diǎn)坐標(biāo) 并預(yù)留位置給充電槍
  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);
    }
  })
}


/**
 * 添加辦公樓前裝飾物、樹(shù)、公交站點(diǎn)
 */
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);
    });
  });
  
  // 加載樹(shù)木,沿街用的是柏樹(shù)
  loader.load('./Objects/model/songshu.gltf', (gltf) => {
    const source = gltf.scene;
    source.scale.set(8, 8, 8);
    // 前面墻的樹(shù)木, 單個(gè)墻的中間區(qū)域放置一棵樹(shù)
    const frontS = [-long / 2 + everyL / 2, 0, -width / 2 + wallNumW * everyL - 5];
    for (let i = 0; i < wallNumL; i++) {
      // 同樣門(mén)口不放置樹(shù)
      if (i !== Math.floor(wallNumL / 2)) {
        const temp = source.clone();
        temp.position.set(frontS[0] + i * everyL, frontS[1], frontS[2]);
        scene.add(temp);
      }
    }
  });

  // 加載公交站點(diǎn),位置在距離大門(mén)右側(cè)第二單面墻處
  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);
  });
}

/**
 * 鋪設(shè)園區(qū)和園區(qū)外面的公路
 * 包含公路以及部分人行道路
 */
function loadRoad() {
  const space = 40;
  const outWidth = 40;
  // 加載園區(qū)外面的公路
  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);

  // 加載園區(qū)內(nèi)的道路
  const innerWidth = 25;
  const color = 0x787878;
  const lineColor = 0xc2c2c2;
  // 加載到停車(chē)場(chǎng)的道路
  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();
}

/**
 * 計(jì)算pointA和pointB 組成的直線與點(diǎn)集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;
}

/**
 * 計(jì)算各條道路的岔口信息并統(tǒng)計(jì)到道路對(duì)象中
 */
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;
          // 計(jì)算路牙和其他道路是否有相交
          // 左邊路牙和下一條路的起始位置做對(duì)比
          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;
          }

          // 左邊路牙和下一條路的終止位置做對(duì)比
          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;
          } 
          
          // 右邊路牙和下一條路的起始位置做對(duì)比
          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;
          }
          
          // 右邊路牙和下一條路的終止位置做對(duì)比
          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);
}

/**
 * 加載行人和汽車(chē)
 */
function loadBusAndPeople() {
  // 加載汽車(chē)和公交車(chē)
  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);
    });
  })
}

/**
 * 點(diǎn)擊汽車(chē)駛離停車(chē)位
 */
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;
    //創(chuàng)建一個(gè)射線發(fā)射器
    const raycaster = new THREE.Raycaster();
    // .setFormCamera()計(jì)算射線投射器的射線屬性ray
    // 即在點(diǎn)擊位置創(chuàng)造一條射線,被射線穿過(guò)的模型代表選中
    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
Threejs實(shí)現(xiàn)一個(gè)園區(qū)Threejs實(shí)現(xiàn)一個(gè)園區(qū)
import * as THREE from 'three';
import { getCurvePoint, getSidePoints, segmentsIntr, clone, isClockWise } from './Common.js';

/**
 * 移動(dòng)類(lèi),實(shí)現(xiàn)物體如何按照路徑運(yùn)動(dòng)以及在岔路口如何選擇等功能
 * 后期可以增加碰撞檢測(cè)避讓等功能
 */
class Road {
  constructor(props) {

    // 道路的原始點(diǎn)信息,通過(guò)這些點(diǎn)信息擴(kuò)展道路
    this.sourceCoord = props.sourceCoord;

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

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

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

    // 左側(cè)路牙點(diǎn)集合
    this.left = [];

    // 道路中心線點(diǎn)集合
    this.middle = [];

    // 右側(cè)路牙點(diǎn)集合
    this.right = [];

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

    // 道路邊線的顏色
    this.sideColor = props.sideColor || 0xffffff;

    // 道路中心線的顏色
    this.middleColor = props.middleColor || 0xe0e0e0;
    
    // 道路的層級(jí)
    this.zIndex = props.zIndex || 0.5;

    // 車(chē)道信息
    this.lanes = [];

    // 道路組合對(duì)象
    this.group = null;

    // 相交的道路名稱 數(shù)據(jù)格式{name: ***, interPoint: [xx,xx,xx]}
    this.intersect = [];

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

  /**
   * 由于直線獲取貝塞爾點(diǎn)的時(shí)候插入的點(diǎn)較少導(dǎo)致物體運(yùn)動(dòng)較快,所以在
   * 平行與X、Y軸的線插入部分點(diǎn),保證物體運(yùn)動(dòng)平滑,插入點(diǎn)時(shí)保證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;
  }

  /**
   * 創(chuàng)建道路
   */
  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()));

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

    // 添加右車(chē)道
    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]);
      }
    })

    // 創(chuàng)建道路面
    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);
    
    // 創(chuàng)建道路邊沿線
    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);
    // 創(chuàng)建道路中心虛線
    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;
  }

  /**
   * 獲取道路的頂點(diǎn)信息
   */
  getPoints(points) {
    const half = this.width / 2;

    // 存儲(chǔ)左中右三條線路的頂點(diǎn)信息
    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;

        // 獲取兩側(cè)點(diǎn)信息
        const sideP1 = getSidePoints(preMidP, e, e, half);
        const sideP2 = getSidePoints(nextMidP1, nextMidP2, nextMidP1, half);
        const sideP3 = getSidePoints(nextMidP1, nextMidP2, nextMidP2, half);
        
        // 左側(cè)
        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);

        // 右側(cè)
        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];
  }
}

/**
 * 車(chē)道對(duì)象
 */
class Lane {
  constructor(options) {
    //車(chē)道名稱
    this.name = options.name;

    // 標(biāo)識(shí)左車(chē)道還是右車(chē)道
    this.type = options.type;

    // 行駛方向和點(diǎn)的方向是否一致
    this.direction = options.direction;

    // 車(chē)道中心線
    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;
道路類(lèi)Road.js
Threejs實(shí)現(xiàn)一個(gè)園區(qū)Threejs實(shí)現(xiàn)一個(gè)園區(qū)
import * as THREE from 'three';
import { TextGeometry } from '../TextGeometry.js';
import { FontLoader } from '../FontLoader.js';

/**
 * 停車(chē)場(chǎng)類(lèi)
 * 
 */
class Parking {
  constructor(props) {
    // 停車(chē)場(chǎng)的名稱
    this.name = props.name;

    // 停車(chē)場(chǎng)的寬
    this.width = props.width;

    // 停車(chē)場(chǎng)的長(zhǎng)
    this.height = props.height;

    // 停車(chē)場(chǎng)的中心位置
    this.position = props.position;

    // 停車(chē)場(chǎng)的頂點(diǎn)信息
    this.coord = [];

    // 停車(chē)場(chǎng)有時(shí)候需要根據(jù)不同的顯示位置進(jìn)行Z軸旋轉(zhuǎn)
    this.rotate = props.rotate;

    // 停車(chē)位的入口點(diǎn),主要確保車(chē)輛進(jìn)出的方向
    this.entryPoint = [];

    // 車(chē)位已經(jīng)停車(chē)標(biāo)識(shí)
    this.placed = false;

    this.create();
  }

  /**
   * 創(chuàng)建道路
   */
  create() {
    const points = [
      new THREE.Vector3(-this.height / 2, this.width / 2, 0),
      new THREE.Vector3(-this.height / 2, -this.width / 2, 0),
      new THREE.Vector3(this.height / 2, -this.width / 2, 0),
      new THREE.Vector3(this.height / 2, this.width / 2, 0)
    ];

    // 停車(chē)場(chǎng)四周白色邊線
    const line = new THREE.LineLoop(
      new THREE.BufferGeometry().setFromPoints(points),
      new THREE.LineBasicMaterial({ color: 0xe0e0e0, linewidth: 1 })
    );
      
    // 停車(chē)場(chǎng)面
    const plane = new THREE.Mesh(
      new THREE.PlaneGeometry(-this.height, this.width),
      new THREE.MeshLambertMaterial({ color: 0xc0e9bf, side: THREE.DoubleSide })
    );
  
    // 添加車(chē)位編號(hào)
    const loader = new FontLoader();
    loader.load('./Objects/helvetiker_regular.typeface.json', (font) => {
      const textGeo = new TextGeometry(this.name, {
        font: font,
        size: 2,
        height: 0
      });
      textGeo.rotateZ(-Math.PI / 2);
      const text = new THREE.Mesh(
        textGeo,
        new THREE.MeshBasicMaterial({ color: 0xffffff })
      );
      text.position.x = this.height * 3 / 8;
      text.position.y = this.name.length / 2;
      text.position.z = text.position.z + 1;
      group.add(text);
    });
    const group = new THREE.Group();
    group.add(line);
    group.add(plane);
    group.rotateX(-Math.PI / 2);
    if (this.rotate) {
      group.rotateZ(this.rotate);
    }
    this.group = group;
    group.position.set(...this.position);
    let beta = 1;
    if (this.rotate === Math.PI) {
      beta = -1;
    }
    this.entryPoint = [this.height * beta / 2 + this.position[0], this.position[1], this.position[2]];
  }
}

export default Parking;
停車(chē)位類(lèi)Parking.js
Threejs實(shí)現(xiàn)一個(gè)園區(qū)Threejs實(shí)現(xiàn)一個(gè)園區(qū)
import * as THREE from 'three';
import { calDistance, drawLine, clone, pointInPolygon, segmentsIntr } from './Common.js';

/**
 * 移動(dòng)類(lèi),實(shí)現(xiàn)物體如何按照路徑運(yùn)動(dòng)以及在岔路口如何選擇等功能
 * 后期可以增加碰撞檢測(cè)避讓等功能
 */
class Move {
  constructor(props) {
    // 所有道路信息
    this.roads = {};
    props.roads.forEach(e => this.roads[e.name] = e);

    this.name = props.name;

    // 物體對(duì)象
    this.target = props.target;

    // 前進(jìn)還是倒車(chē) 1:前進(jìn);-1:倒車(chē)
    this.direction = 1;

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

    // 移動(dòng)軌跡
    this.trace = [];

    // 當(dāng)前形式的道路
    this.curRoad = null;

    // 鎖定下一步行動(dòng)趨勢(shì),主要是為了解決在路口物體獲取到下一步行動(dòng)后再次獲取行動(dòng)導(dǎo)致功能亂掉
    this.trendLock = false;

    this.preRoadName = null;

    // 當(dāng)前移動(dòng)所在的車(chē)道
    this.curLane = null;

    // 是否暫停移動(dòng)
    this.pause = false;

    // 園區(qū)停車(chē)場(chǎng)信息
    this.parks = props.parks;

    this.parkObj = null;

    this.init();
  }

  /**
   * 獲取當(dāng)前實(shí)體所在道路以及移動(dòng)方向和移動(dòng)軌跡
   */
  init() {
    let minDis = {
      roadName: '',  // 起始位置所在道路的道路名
      distance: 100000, // 起始物體與車(chē)道左右邊線的最小距離
      curLane: null, // 移動(dòng)的車(chē)道
    };
    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();
  }

  /**
   * 獲取車(chē)道中心線
   * 將車(chē)道的左右線取平均值即可
   * @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();
      // 如果移動(dòng)到當(dāng)前軌跡的最后一個(gè)點(diǎn)時(shí),需要進(jìn)行掉頭重新尋找軌跡
      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) {
        // 前進(jìn)
        if (this.moveIndex !== this.trace.length - 1) {
          this.target.lookAt(...this.trace[this.moveIndex + 1].toArray());
        }
      } else {
        // 倒車(chē)
        if (this.moveIndex !== 0) {
          this.target.lookAt(...this.trace[this.moveIndex - 1].toArray());
        }
      }
      
      this.moveIndex ++;
    }
    requestAnimationFrame(this.move.bind(this));
  }

  /**
   * 在鎖定后檢查物體是否已經(jīng)移出路口,如果移出則解除鎖定
   */
  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;
  }

  /**
   * 根據(jù)提供的點(diǎn)信息尋找最近的點(diǎn)值
   */
  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;
  }

  /**
   * 在岔路口時(shí)隨機(jī)獲取下一步行動(dòng)方向
   */
  getRandomTrend(forks) {
    const isEnd = calDistance(this.trace[this.moveIndex].toArray(), this.trace[this.trace.length - 1].toArray()) < this.curRoad.width / 2;
    // 從多條路中隨機(jī)選擇一條,當(dāng)前園區(qū)路況簡(jiǎn)單 路口數(shù)據(jù)目前只有一條
    const randomRoad = forks[Math.floor(Math.random() * forks.length)];
    // 分別代表掉頭、轉(zhuǎn)彎、直行四種情況
    let types = [0, 1, 2];
    if (isEnd) {
      // 如果是道路的盡頭 可以選擇掉頭或者轉(zhuǎn)彎
      types = [0, 1, 2];
    } else {
      // 如果不是道路的盡頭,可以選擇轉(zhuǎn)彎或者直行
      types = [1, 2];
    }

    const random = types[Math.floor(Math.random() * types.length)];
    if (random === 0) {
      // 掉頭
      this.trendLock = true;
      this.getTurnPoint();
    } else if (random === 1) {
      // 轉(zhuǎn)彎
      this.trendLock = true;
      this.getForkPoint(randomRoad, isEnd);
    } else if (random === 2) {
      this.preRoadName = randomRoad;
      // 直線
      this.trendLock = true;
    }
  }

  /**
   * 在岔路口時(shí)根據(jù)獲取道路軌跡信息
   */
  getForkPoint(name, isEnd) {
    this.preRoadName = this.curRoad.name;
    const roadObj = this.roads[name];
    let splitPoint = [];
    if (isEnd) {
      // 如果在道路盡頭轉(zhuǎn)彎,隨機(jī)產(chǎn)生是左側(cè)還是右側(cè)道路
      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);
      // 為了平滑過(guò)渡獲取道路末端和當(dāng)前行駛位置的交點(diǎn)
      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 {
      // 轉(zhuǎn)彎前需要判斷當(dāng)前路可以向那個(gè)方向轉(zhuǎn)彎,比如臨近路口只能轉(zhuǎn)向右車(chē)道,非臨近路口需要轉(zhuǎn)到對(duì)象車(chē)道
      // 可以根據(jù)當(dāng)前點(diǎn)和車(chē)道點(diǎn)的距離來(lái)判斷
      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);
      // 為了平滑過(guò)渡獲取道路末端和當(dāng)前行駛位置的交點(diǎn)
      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;
  }

  /**
   * 獲取離散點(diǎn)即將點(diǎn)與點(diǎn)之間更加細(xì)化,使物體運(yùn)行起來(lái)更加平滑
   * @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);
  }

  /**
   * 將車(chē)輛停入停車(chē)場(chǎng)內(nèi)
   */
  parkCar() {
    // 是否允許停車(chē)
    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代表停車(chē)
        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;
    // 不想再擴(kuò)展功能了 就在這臨時(shí)用標(biāo)識(shí)符處理了下出庫(kù)時(shí)不斷循環(huán)查詢車(chē)位的情況
    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;
移動(dòng)類(lèi)Move.js

?文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-805195.html

到了這里,關(guān)于Threejs實(shí)現(xiàn)一個(gè)園區(qū)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 智慧園區(qū)SaaS管理系統(tǒng)解決方案:賦能園區(qū)實(shí)現(xiàn)信息化、數(shù)字化管理

    智慧園區(qū)SaaS管理系統(tǒng)解決方案:賦能園區(qū)實(shí)現(xiàn)信息化、數(shù)字化管理

    智慧園區(qū)的概念來(lái)源于智慧城市,是指應(yīng)用云計(jì)算、互聯(lián)網(wǎng)等新一代信息通信技術(shù),改變園區(qū)中各機(jī)構(gòu)的交互方式,提高交互效率的新型科技園區(qū)。近年來(lái),我國(guó)智慧園區(qū)發(fā)展迅速,隨著大數(shù)據(jù)、人工智能等技術(shù)的發(fā)展不斷取得新突破,以科技賦能產(chǎn)業(yè)園區(qū)數(shù)字化升級(jí)是必然

    2024年02月02日
    瀏覽(29)
  • 記錄--Threejs-著色器實(shí)現(xiàn)一個(gè)水波紋

    記錄--Threejs-著色器實(shí)現(xiàn)一個(gè)水波紋

    hree.js 是一個(gè)基于 WebGL 的 JavaScript 3D 庫(kù),用于創(chuàng)建和渲染 3D 圖形場(chǎng)景。 1、webGL webGL: WebGL 是一種基于 JavaScript API 的圖形庫(kù),它允許在瀏覽器中進(jìn)行高性能的 3D 圖形渲染。webGL的渲染依賴于底層 GPU 的渲染能力。 通過(guò)獲取 canvas 元素獲取WebGL的上下文,從而獲得WebGL API和GPU。

    2024年02月11日
    瀏覽(23)
  • 使用THREEJS實(shí)現(xiàn)一個(gè)可調(diào)節(jié)檔位、可搖頭的電風(fēng)扇

    夏天到了,用 Three.js 實(shí)現(xiàn)一個(gè)可以搖頭和調(diào)節(jié)檔位的電風(fēng)扇。主要使用到 Blender 處理3D模型,用 Vite + Typescript 搭建項(xiàng)目框架。效果演示: 1、從愛(ài)(bai)給(gei)網(wǎng)下載一個(gè)風(fēng)扇的3D模型,在Blender中打開(kāi),給模型貼上圖。 2、拆解模型。將風(fēng)扇模型拆解成按鈕、底座、扇葉、頭部四

    2024年02月08日
    瀏覽(17)
  • 英碼深元“三位一體”AI場(chǎng)景化解決方案,助力多地化工園區(qū)快速實(shí)現(xiàn)智慧化轉(zhuǎn)型!

    英碼深元“三位一體”AI場(chǎng)景化解決方案,助力多地化工園區(qū)快速實(shí)現(xiàn)智慧化轉(zhuǎn)型!

    我國(guó)是世界公認(rèn)的化工大國(guó),同時(shí)也是崛起中的化工強(qiáng)國(guó)。近年來(lái)多起重大爆炸事故暴露出我國(guó)化工園區(qū)安全問(wèn)題突出,特別是在安全風(fēng)險(xiǎn)管控?cái)?shù)字化轉(zhuǎn)型、智能化升級(jí)方面存在明顯短板和不足,尤其突出的痛點(diǎn):化工園區(qū)的日常管理方式較為粗糙,各園區(qū)、廠區(qū)的門(mén)禁、視

    2024年02月10日
    瀏覽(17)
  • 為不同的調(diào)制方案設(shè)計(jì)一個(gè)單載波系統(tǒng)(映射器-信道-去映射器)(Matlab代碼實(shí)現(xiàn))

    為不同的調(diào)制方案設(shè)計(jì)一個(gè)單載波系統(tǒng)(映射器-信道-去映射器)(Matlab代碼實(shí)現(xiàn))

    ???????? 歡迎來(lái)到本博客 ???????? ??博主優(yōu)勢(shì): ?????? 博客內(nèi)容盡量做到思維縝密,邏輯清晰,為了方便讀者。 ?? 座右銘: 行百里者,半于九十。 ?????? 本文目錄如下: ?????? 目錄 ??1 概述 ??2 運(yùn)行結(jié)果 ??3 參考文獻(xiàn) ??4 Matlab代碼實(shí)現(xiàn) 本代碼為

    2024年02月10日
    瀏覽(17)
  • 基于粒子群優(yōu)化算法的面向綜合能源園區(qū)的三方市場(chǎng)主體非合作交易方法(Matlab代碼實(shí)現(xiàn))

    基于粒子群優(yōu)化算法的面向綜合能源園區(qū)的三方市場(chǎng)主體非合作交易方法(Matlab代碼實(shí)現(xiàn))

    ???????? 歡迎來(lái)到本博客 ???????? ??博主優(yōu)勢(shì): ?????? 博客內(nèi)容盡量做到思維縝密,邏輯清晰,為了方便讀者。 ?? 座右銘: 行百里者,半于九十。 ?????? 本文目錄如下: ?????? 目錄 ??1 概述 ??2 運(yùn)行結(jié)果 ??3?參考文獻(xiàn) ??4 Matlab代碼實(shí)現(xiàn) ? 隨著

    2023年04月09日
    瀏覽(20)
  • 智慧園區(qū)解決方案

    智慧園區(qū)解決方案

    智慧園區(qū)解決方案 智慧園區(qū)是以互聯(lián)網(wǎng)為載體,“互聯(lián)網(wǎng)+產(chǎn)業(yè)”融合產(chǎn)業(yè)模式為手段,面向園區(qū)提供全產(chǎn)業(yè)鏈支撐服務(wù)的解決方案。能夠幫助園區(qū)在信息化方面建立統(tǒng)一的組織管理協(xié)調(diào)架構(gòu),業(yè)務(wù)管理平臺(tái)和對(duì)內(nèi)對(duì)外服務(wù)運(yùn)營(yíng)平臺(tái)。將相關(guān)資源形成緊密聯(lián)系的整體,獲得高

    2023年04月09日
    瀏覽(22)
  • Qt程序打包成一個(gè)單獨(dú)exe的方法
Qt程序打包成一個(gè)單獨(dú)exe的方法

    Qt程序打包成一個(gè)單獨(dú)exe的方法 Qt程序打包成一個(gè)單獨(dú)exe的方法

    目錄 Qt程序打包成一個(gè)單獨(dú)exe的方法 程序發(fā)布 程序打包 問(wèn)題 Qt程序發(fā)布及打包,同時(shí)修改可執(zhí)行文件的圖標(biāo)。本教程使用Qt自帶的? windeployqt ?工具外加 Enigma Virtual Box 打包工具。首先需要知道的是,Qt程序發(fā)布需要的程序是用? Release ?方式編譯的。下面看一下具體的操作。

    2024年02月11日
    瀏覽(23)
  • 智慧工業(yè)園區(qū)建設(shè)方案-智慧化工園區(qū)物聯(lián)網(wǎng)管理系統(tǒng)平臺(tái)---豌豆云

    將化工園區(qū)海量信息互聯(lián)、互通、互融,結(jié)合化工園區(qū)建設(shè)管理經(jīng)驗(yàn),通過(guò)“動(dòng)態(tài)感知、主動(dòng)監(jiān)測(cè)、政企聯(lián)動(dòng)、綜合管理”。 將事件管理從事后處置變?yōu)槭虑胺揽?,保障生產(chǎn)安全,提升環(huán)保水平,結(jié)合可視化大屏,幫助管理者實(shí)時(shí)了解園區(qū)整體狀態(tài),為決策提供依據(jù)。助力化

    2024年01月23日
    瀏覽(27)
  • 園區(qū)預(yù)付費(fèi)遠(yuǎn)程抄表方案

    園區(qū)預(yù)付費(fèi)遠(yuǎn)程抄表方案

    園區(qū)預(yù)付費(fèi)遠(yuǎn)程抄表方案是一種能夠?qū)崿F(xiàn)園區(qū)內(nèi)電表數(shù)據(jù)遠(yuǎn)程采集、實(shí)時(shí)監(jiān)控和預(yù)付費(fèi)管理的方案。該方案解決了傳統(tǒng)手動(dòng)抄表方式的不便和不準(zhǔn)確問(wèn)題,同時(shí)避免了上門(mén)收費(fèi)的安全隱患和成本問(wèn)題。下面將從方案組成、工作原理、優(yōu)點(diǎn)等方面進(jìn)行詳細(xì)介紹。 ? 一、方案組成

    2024年02月15日
    瀏覽(15)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包