利用threejs實(shí)現(xiàn)一個(gè)簡(jiǎn)易的泊車功能
簡(jiǎn)易版小車
因?yàn)橹坝玫哪P捅容^大,加載很慢,這里就先自己簡(jiǎn)單實(shí)現(xiàn)一輛小車(后面統(tǒng)稱自車),如下圖:
將車輪、車體和邊框組合成一個(gè) Group
,便于后面做自車的一些操作,實(shí)現(xiàn)代碼如下:
// 自車車體 const geometry = new THREE.BoxGeometry(2, 0.6, 3); const material = new THREE.MeshBasicMaterial({ color: 0x00ffff, side: THREE.DoubleSide, }); const vehicle = new THREE.Mesh(geometry, material); vehicle.position.set(0, 1, 0); scene.add(vehicle); // 增加自車邊框 const box = geometry.clone(); const edges = new THREE.EdgesGeometry(box); const edgesMaterial = new THREE.LineBasicMaterial({ color: 0x333333, }); const line = new THREE.LineSegments(edges, edgesMaterial); line.position.x = 0; line.position.y = 1; line.position.z = 0; scene.add(line); // 組成一個(gè)Group const egoCar = new THREE.Group(); egoCar.name = "自車"; egoCar.add(vehicle, line); scene.add(egoCar); // 車輪 const axlewidth = 0.7; const radius = 0.4; const wheels: any[] = []; const wheelObjects: any[] = []; wheels.push({ position: [axlewidth, 0.4, -1], radius }); wheels.push({ position: [-axlewidth, 0.4, -1], radius, }); wheels.push({ position: [axlewidth, 0.4, 1], radius }); wheels.push({ position: [-axlewidth, 0.4, 1], radius }); wheels.forEach(function (wheel) { const geometry = new THREE.CylinderGeometry( wheel.radius, wheel.radius, 0.4, 32 ); const material = new THREE.MeshPhongMaterial({ color: 0xd0901d, emissive: 0xee0000, side: THREE.DoubleSide, flatShading: true, }); const cylinder = new THREE.Mesh(geometry, material); cylinder.geometry.rotateZ(Math.PI / 2); cylinder.position.set( wheel.position[0], wheel.position[1], wheel.position[2] ); egoCar.add(cylinder); // 后面修改車輪方向會(huì)用到 wheelObjects.push(cylinder); });
跟車相機(jī)
讓相機(jī)一直跟著自車,體驗(yàn)更好一點(diǎn)
// ... const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 800); // 設(shè)置攝像機(jī)位置,并將其朝向場(chǎng)景中心 camera.position.x = 0; // camera.position.y = 10; // camera.position.z = 20; // camera.lookAt(scene.position); camera.lookAt(egoCar.position); // ... function animate() { stats.begin(); controls.update(); // 相機(jī)跟隨自車 camera.position.y = egoCar.position.y + 15; camera.position.z = egoCar.position.z + 25; camera.lookAt(egoCar.position); renderer.render(scene, camera); stats.end(); requestAnimationFrame(animate); } // ...
自車行駛
實(shí)現(xiàn)自車前行后退和左右轉(zhuǎn)向
// ... // 記錄開始按下的時(shí)間 let startTime = 0; const activeKeys = new Set(); let t = 0; document.addEventListener("keydown", (e) => { activeKeys.add(e.key); if (startTime === 0) { startTime = Date.now(); } t = (Date.now() - startTime) / 1000; if (t > 10) { t = 10; } }); document.addEventListener("keyup", (e) => { activeKeys.delete(e.key); if (activeKeys.size === 0) { startTime = 0; } }); function animate() { stats.begin(); controls.update(); // 相機(jī)跟隨自車 camera.position.y = egoCar.position.y + 15; camera.position.z = egoCar.position.z + 25; camera.lookAt(egoCar.position); if (activeKeys.has("ArrowUp")) { // 估算對(duì)應(yīng)方向的移動(dòng)距離 egoCar.position.z -= t * 0.1 * Math.cos(egoCar.rotation.y); egoCar.position.x -= t * 0.1 * Math.sin(egoCar.rotation.y); } if (activeKeys.has("ArrowDown")) { egoCar.position.z += t * 0.1 * Math.cos(egoCar.rotation.y); egoCar.position.x += t * 0.1 * Math.sin(egoCar.rotation.y); } if (activeKeys.has("ArrowLeft")) { egoCar.rotation.y += 0.01; } if (activeKeys.has("ArrowRight")) { egoCar.rotation.y -= 0.01; } renderer.render(scene, camera); stats.end(); requestAnimationFrame(animate); } //...
車輪轉(zhuǎn)動(dòng)
遍歷車輪對(duì)象,動(dòng)態(tài)修改車輪的偏轉(zhuǎn)角 rotation
,以車頭方向?yàn)榛鶞?zhǔn)偏轉(zhuǎn)固定的角度
function animate() { // ... if (activeKeys.has("ArrowLeft")) { egoCar.rotation.y += 0.01; wheelObjects.forEach((wheel) => { wheel.rotation.y = egoCar.rotation.y + Math.PI / 4; }); } if (activeKeys.has("ArrowRight")) { egoCar.rotation.y -= 0.01; wheelObjects.forEach((wheel) => { wheel.rotation.y = egoCar.rotation.y - Math.PI / 4; }); } // ... }
行進(jìn)效果還是有點(diǎn)僵硬(能用就行),這里的問題是行進(jìn)方向應(yīng)該是按車頭方向,而不是固定按某個(gè)坐標(biāo)軸方向,不過這里也只是簡(jiǎn)單模擬這個(gè)行進(jìn)效果,后面再引入物理庫(kù) cannon.js
優(yōu)化下這塊控制邏輯
泊車功能
車位實(shí)現(xiàn)
做一個(gè)貼地面的矩形框來模擬車位,可以使用 THREE.PlaneGeometry
來創(chuàng)建平面幾何體
createParkingSpace() { const plane = new THREE.PlaneGeometry(8, 5); const material = new THREE.MeshPhongMaterial({ color: 0x666666, side: THREE.DoubleSide, }); const mesh = new THREE.Mesh(plane, material); mesh.rotation.x = -Math.PI / 2; mesh.position.set(10, 0.12, -20); this.scene?.add(mesh); // 增加自定義type,便于后面處理車位的選中邏輯 mesh.userData.type = "parkingSpace"; }
現(xiàn)在咱們把小車開過去停到那個(gè)位置
自動(dòng)泊車
需要實(shí)現(xiàn)點(diǎn)擊車位后高亮對(duì)應(yīng)的車位,之后小車自動(dòng)行駛到對(duì)應(yīng)的位置并停好。點(diǎn)擊原理是用射線的方式采集第一個(gè)碰到的車位物體,當(dāng)點(diǎn)擊鼠標(biāo)時(shí),會(huì)發(fā)生以下步驟:
- 基于屏幕上的點(diǎn)擊位置創(chuàng)建一個(gè)
THREE.Vector3
向量 - 使用
vector.unproject
方法將屏幕上點(diǎn)擊位置的坐標(biāo)轉(zhuǎn)換成 three.js 場(chǎng)景中的坐標(biāo) - 創(chuàng)建
THREE.Raycaster
可以從攝像機(jī)的位置向場(chǎng)景中鼠標(biāo)的點(diǎn)擊位置發(fā)出一條射線 raycaster.intersectObjects
返回包含了所有被射線穿過的對(duì)象信息的數(shù)組(從攝像機(jī)位置開始由短到長(zhǎng))
function handleParkSpaceClick(event: any) { let vector = new THREE.Vector3( (event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1, 0.5 ); vector = vector.unproject(camera); const raycaster = new THREE.Raycaster( camera.position, vector.sub(camera.position).normalize() ); const intersects = raycaster.intersectObjects(scene.children); for (let i = 0; i < intersects.length; i++) { const obj = intersects[i]; // @ts-ignore if (obj.object.userData.type === "parkingSpace") // @ts-ignore obj.object.material.color.set(0x00ff00); } } document.addEventListener("click", handleParkSpaceClick);
自動(dòng)泊車的實(shí)現(xiàn)邏輯也比較簡(jiǎn)單,這里簡(jiǎn)單記住了車位的位置信息,然后讓小車按一定的偏移駛?cè)耄鋵?shí)實(shí)際場(chǎng)景可能還要考慮躲避障礙物、加減速、偏轉(zhuǎn)角等,一般也不由前端操心這些。實(shí)現(xiàn)代碼參考 three-gta v0.1.1 -- 在線體驗(yàn)
到此這篇關(guān)于利用threejs實(shí)現(xiàn)一個(gè)簡(jiǎn)易的泊車功能的文章就介紹到這了,更多相關(guān)threejs泊車功能內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JS隨機(jī)生成不重復(fù)數(shù)據(jù)的實(shí)例方法
這篇文章介紹了JS隨機(jī)生成不重復(fù)數(shù)據(jù)的實(shí)例方法,有需要的朋友可以參考一下2013-07-07js組件SlotMachine實(shí)現(xiàn)圖片切換效果制作抽獎(jiǎng)系統(tǒng)
這篇文章主要介紹了js組件SlotMachine實(shí)現(xiàn)圖片切換效果制作抽獎(jiǎng)系統(tǒng)的相關(guān)資料,需要的朋友可以參考下2016-04-04javascript引用類型之時(shí)間Date和數(shù)組Array
引用類型的值(對(duì)象)其實(shí)就是引用類型的一個(gè)實(shí)例,接下來,通過本篇文章給大家介紹javascript引用類型之時(shí)間Date和數(shù)組Array,需要的朋友可以參考下2015-08-08微信小程序中如何計(jì)算距離某個(gè)節(jié)日還有多少天
這篇文章主要給大家介紹了關(guān)于微信小程序中如何計(jì)算距離某個(gè)節(jié)日還有多少天的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用微信小程序具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07不用jQuery實(shí)現(xiàn)的動(dòng)畫效果代碼
jQuery 框架用的人越來越多了, 無論是性能還是功能強(qiáng)大都不用多說.2010-11-11