? ? ? ?
一、實(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ì)圖如下
二、實(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è)路面
? ? ? ? ? ? ? ? ?
首先道路可以細(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)的邏輯
? ? ? ? ? ? ?
創(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)即可。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-805195.html


<!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>


/** * 辦公園區(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();


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;


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;


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;
?文章來(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)!