Three.js頂點(diǎn)射線碰撞檢測(cè)具體實(shí)現(xiàn)步驟詳解
一、基本思路
核心算法流程:
第1步:遍歷幾何體所有頂點(diǎn),分別創(chuàng)建與幾何體中心坐標(biāo)構(gòu)成的射線
對(duì)于 每個(gè)幾何體A 的 每個(gè)頂點(diǎn)V:
頂點(diǎn)位置 = V 的世界坐標(biāo)位置
中心位置 = 幾何體A 的世界坐標(biāo)中心點(diǎn)
方向向量 = 中心位置 - 頂點(diǎn)位置
創(chuàng)建射線Raycaster:
起點(diǎn) = 頂點(diǎn)位置
方向 = 方向向量(歸一化)
實(shí)現(xiàn)細(xì)節(jié):
- 獲取幾何體的頂點(diǎn)坐標(biāo)數(shù)組(Float32Array,每3個(gè)值表示一個(gè)頂點(diǎn)的x,y,z坐標(biāo))
- 將頂點(diǎn)從局部坐標(biāo)系轉(zhuǎn)換到世界坐標(biāo)系
- 計(jì)算幾何體的世界中心點(diǎn)(通常是物體位置)
- 對(duì)每個(gè)頂點(diǎn)創(chuàng)建從頂點(diǎn)指向中心的射線
第2步:射線交叉計(jì)算
對(duì)于 幾何體A 的 每條射線R:
使用Raycaster的intersectObject()方法:
檢測(cè)射線R 是否與 幾何體B 相交
如果相交:
獲取交點(diǎn)信息(交點(diǎn)坐標(biāo)、距離、面信息等)
關(guān)鍵點(diǎn):
- Raycaster.intersectObject(幾何體B) 返回交點(diǎn)數(shù)組
- 交點(diǎn)數(shù)組按距離從近到遠(yuǎn)排序
- 如果射線與幾何體B有交點(diǎn),說(shuō)明這條射線可能穿過(guò)了幾何體B
第3步:通過(guò)距離判斷兩個(gè)網(wǎng)格模型是否碰撞
對(duì)于 幾何體A 的 每條射線R 的 每個(gè)交點(diǎn)I:
計(jì)算:起點(diǎn)到交點(diǎn)的距離 = 距離1
計(jì)算:起點(diǎn)到幾何體A中心的距離 = 距離2
如果 距離1 < 距離2:
說(shuō)明射線在到達(dá)自己中心之前就擊中了對(duì)方
判斷為"這條射線穿過(guò)了對(duì)方幾何體"
如果 至少有一條射線穿過(guò)對(duì)方幾何體:
判斷為"兩個(gè)幾何體發(fā)生碰撞"
邏輯解釋:
- 射線是從自己頂點(diǎn)指向自己中心的
- 如果射線在到達(dá)自己中心之前就擊中了對(duì)方,說(shuō)明對(duì)方在自己"內(nèi)部"或"前方"
- 如果射線先到達(dá)自己中心,說(shuō)明對(duì)方在自己"后方"或"外部"
二、具體實(shí)現(xiàn)步驟詳解
Step 1: 獲取幾何體所有頂點(diǎn)(世界坐標(biāo))
// 1.1 獲取幾何體的頂點(diǎn)位置數(shù)據(jù)
const geometry = mesh.geometry
const positionAttribute = geometry.attributes.position
const positions = positionAttribute.array // [x1, y1, z1, x2, y2, z2, ...]
// 1.2 獲取世界變換矩陣
const matrixWorld = mesh.matrixWorld
// 1.3 遍歷所有頂點(diǎn),轉(zhuǎn)換為世界坐標(biāo)
const worldVertices = []
for (let i = 0; i < positions.length; i += 3) {
// 創(chuàng)建局部坐標(biāo)頂點(diǎn)
const localVertex = new THREE.Vector3(
positions[i], // x
positions[i + 1], // y
positions[i + 2] // z
)
// 轉(zhuǎn)換為世界坐標(biāo)
const worldVertex = localVertex.clone()
worldVertex.applyMatrix4(matrixWorld)
worldVertices.push(worldVertex)
}
Step 2: 計(jì)算幾何體中心點(diǎn)(世界坐標(biāo))
// 方法1:直接使用mesh的世界位置(對(duì)于對(duì)稱幾何體)
const center = new THREE.Vector3()
mesh.getWorldPosition(center)
// 方法2:計(jì)算所有頂點(diǎn)的平均值(更精確)
let sum = new THREE.Vector3(0, 0, 0)
for (const vertex of worldVertices) {
sum.add(vertex)
}
const center = sum.divideScalar(worldVertices.length)
Step 3: 創(chuàng)建頂點(diǎn)到中心的射線
// 3.1 創(chuàng)建Raycaster實(shí)例
const raycaster = new THREE.Raycaster()
// 3.2 對(duì)于每個(gè)頂點(diǎn),創(chuàng)建從頂點(diǎn)指向中心的射線
for (const vertex of worldVertices) {
// 計(jì)算方向向量(從頂點(diǎn)指向中心)
const direction = center.clone().sub(vertex).normalize()
// 設(shè)置射線
raycaster.set(vertex, direction)
// 現(xiàn)在raycaster就代表了一條從頂點(diǎn)指向中心的射線
// 我們可以用它來(lái)檢測(cè)與其他幾何體的交點(diǎn)
}
Step 4: 射線交叉計(jì)算
// 4.1 檢測(cè)射線是否與另一個(gè)幾何體相交
const otherMesh = ... // 另一個(gè)幾何體
const intersects = raycaster.intersectObject(otherMesh)
// 4.2 分析交點(diǎn)信息
if (intersects.length > 0) {
const firstIntersect = intersects[0] // 最近的交點(diǎn)
// 交點(diǎn)信息包含:
// - point: 交點(diǎn)坐標(biāo)(Vector3)
// - distance: 從射線起點(diǎn)到交點(diǎn)的距離
// - object: 被擊中的物體
// - face: 被擊中的面
// - faceIndex: 面的索引
}
Step 5: 通過(guò)距離判斷是否碰撞
// 5.1 計(jì)算關(guān)鍵距離
const distanceToIntersect = vertex.distanceTo(firstIntersect.point) // 頂點(diǎn)到交點(diǎn)的距離
const distanceToCenter = vertex.distanceTo(center) // 頂點(diǎn)到中心的距離
// 5.2 判斷邏輯
if (distanceToIntersect < distanceToCenter) {
// 情況:射線在到達(dá)自己中心之前就擊中了對(duì)方
// 說(shuō)明對(duì)方幾何體在自己"前方"或"內(nèi)部"
// 這很可能表示兩個(gè)幾何體相交或碰撞
console.log('這條射線穿過(guò)了對(duì)方幾何體!')
// 記錄碰撞信息
collisions.push({
vertex: vertex,
intersectPoint: firstIntersect.point,
rayDirection: direction
})
} else {
// 情況:射線先到達(dá)自己中心,然后才可能擊中對(duì)方
// 說(shuō)明對(duì)方幾何體在自己"后方"或"外部"
// 這很可能表示兩個(gè)幾何體沒(méi)有碰撞
}
Step 6: 綜合判斷碰撞
// 6.1 統(tǒng)計(jì)所有穿過(guò)的射線
let collisionCount = 0
for (所有頂點(diǎn)射線) {
if (射線穿過(guò)了對(duì)方幾何體) {
collisionCount++
}
}
// 6.2 判斷是否發(fā)生碰撞
if (collisionCount > 0) {
console.log(`發(fā)生碰撞!共有 ${collisionCount} 條射線穿過(guò)對(duì)方幾何體`)
return true // 發(fā)生碰撞
} else {
console.log('沒(méi)有碰撞')
return false // 沒(méi)有碰撞
}
三、數(shù)學(xué)原理圖解
情況1:發(fā)生碰撞(射線穿過(guò)對(duì)方)
頂點(diǎn)
│
│ 距離1(到交點(diǎn))
│
▼ 交點(diǎn)(在對(duì)方幾何體上)
│
│ 距離2(從交點(diǎn)到中心)
│
▼ 中心
距離1 < 距離1+距離2(即距離1 < 頂點(diǎn)到中心的距離)
∴ 射線在到達(dá)中心之前擊中了對(duì)方 → 碰撞!情況2:沒(méi)有碰撞(射線先到中心)
頂點(diǎn)
│
│ 距離1(到中心)
│
▼ 中心
│
│ 距離2(從中心到交點(diǎn))
│
▼ 交點(diǎn)(在對(duì)方幾何體上)
距離1 < 距離1+距離2(但射線要先經(jīng)過(guò)中心)
∴ 射線先到達(dá)中心,然后才可能擊中對(duì)方 → 沒(méi)有碰撞!
四、性能優(yōu)化考慮
1. 頂點(diǎn)采樣優(yōu)化
// 不檢測(cè)所有頂點(diǎn),只采樣一部分
const sampleRate = 0.3 // 采樣率30%
for (let i = 0; i < worldVertices.length; i += Math.floor(1/sampleRate)) {
// 只檢測(cè)部分頂點(diǎn)
}
2. 分層次檢測(cè)
// 第一步:快速檢測(cè)(包圍盒)
const box1 = new THREE.Box3().setFromObject(mesh1)
const box2 = new THREE.Box3().setFromObject(mesh2)
if (!box1.intersectsBox(box2)) {
return false // 包圍盒不相交,肯定不碰撞,快速返回
}
// 第二步:精確檢測(cè)(頂點(diǎn)射線)
// 只有包圍盒相交時(shí),才進(jìn)行更耗時(shí)的頂點(diǎn)檢測(cè)
3. 空間分割優(yōu)化
// 使用八叉樹(shù)或BVH(包圍盒層次結(jié)構(gòu))加速 // 只檢測(cè)可能相交的幾何體
4. 距離閾值優(yōu)化
// 添加容差閾值
const threshold = 0.01 // 1厘米容差
if (distanceToIntersect < distanceToCenter + threshold) {
// 考慮為碰撞(避免浮點(diǎn)數(shù)精度問(wèn)題)
}
五、適用場(chǎng)景與限制
適用場(chǎng)景:
- 需要精確碰撞檢測(cè)(如物理模擬、游戲碰撞)
- 不規(guī)則幾何體碰撞(非AABB/球體等簡(jiǎn)單形狀)
- 需要知道碰撞位置(不僅僅是是否碰撞)
限制:
- 計(jì)算量大:頂點(diǎn)越多越慢
- 凹形幾何體問(wèn)題:射線可能從凹處"穿過(guò)"而不碰撞
- 薄物體問(wèn)題:對(duì)于非常薄的幾何體可能漏檢
六、偽代碼總結(jié)
function 頂點(diǎn)射線碰撞檢測(cè)(幾何體A, 幾何體B):
// Step 1: 獲取頂點(diǎn)
頂點(diǎn)數(shù)組A = 獲取世界坐標(biāo)頂點(diǎn)(幾何體A)
頂點(diǎn)數(shù)組B = 獲取世界坐標(biāo)頂點(diǎn)(幾何體B)
// Step 2: 計(jì)算中心
中心A = 計(jì)算世界中心(幾何體A)
中心B = 計(jì)算世界中心(幾何體B)
// Step 3-4: 檢測(cè)A的頂點(diǎn)射線
for 每個(gè)頂點(diǎn)V in 頂點(diǎn)數(shù)組A:
方向 = 歸一化(中心A - 頂點(diǎn)V)
射線 = 創(chuàng)建射線(頂點(diǎn)V, 方向)
交點(diǎn) = 射線.檢測(cè)相交(幾何體B)
if 交點(diǎn)存在:
if 距離(頂點(diǎn)V, 交點(diǎn)) < 距離(頂點(diǎn)V, 中心A):
碰撞計(jì)數(shù)器++
// Step 3-4: 檢測(cè)B的頂點(diǎn)射線
for 每個(gè)頂點(diǎn)V in 頂點(diǎn)數(shù)組B:
方向 = 歸一化(中心B - 頂點(diǎn)V)
射線 = 創(chuàng)建射線(頂點(diǎn)V, 方向)
交點(diǎn) = 射線.檢測(cè)相交(幾何體A)
if 交點(diǎn)存在:
if 距離(頂點(diǎn)V, 交點(diǎn)) < 距離(頂點(diǎn)V, 中心B):
碰撞計(jì)數(shù)器++
// Step 5: 判斷結(jié)果
if 碰撞計(jì)數(shù)器 > 0:
return true // 發(fā)生碰撞
else:
return false // 沒(méi)有碰撞
—# Three.js 頂點(diǎn)射線碰撞檢測(cè)實(shí)現(xiàn)步驟詳解
一、基本思路
核心算法流程:
第1步:遍歷幾何體所有頂點(diǎn),分別創(chuàng)建與幾何體中心坐標(biāo)構(gòu)成的射線
對(duì)于 每個(gè)幾何體A 的 每個(gè)頂點(diǎn)V:
頂點(diǎn)位置 = V 的世界坐標(biāo)位置
中心位置 = 幾何體A 的世界坐標(biāo)中心點(diǎn)
方向向量 = 中心位置 - 頂點(diǎn)位置
創(chuàng)建射線Raycaster:
起點(diǎn) = 頂點(diǎn)位置
方向 = 方向向量(歸一化)
實(shí)現(xiàn)細(xì)節(jié):
- 獲取幾何體的頂點(diǎn)坐標(biāo)數(shù)組(Float32Array,每3個(gè)值表示一個(gè)頂點(diǎn)的x,y,z坐標(biāo))
- 將頂點(diǎn)從局部坐標(biāo)系轉(zhuǎn)換到世界坐標(biāo)系
- 計(jì)算幾何體的世界中心點(diǎn)(通常是物體位置)
- 對(duì)每個(gè)頂點(diǎn)創(chuàng)建從頂點(diǎn)指向中心的射線
第2步:射線交叉計(jì)算
對(duì)于 幾何體A 的 每條射線R:
使用Raycaster的intersectObject()方法:
檢測(cè)射線R 是否與 幾何體B 相交
如果相交:
獲取交點(diǎn)信息(交點(diǎn)坐標(biāo)、距離、面信息等)
關(guān)鍵點(diǎn):
- Raycaster.intersectObject(幾何體B) 返回交點(diǎn)數(shù)組
- 交點(diǎn)數(shù)組按距離從近到遠(yuǎn)排序
- 如果射線與幾何體B有交點(diǎn),說(shuō)明這條射線可能穿過(guò)了幾何體B
第3步:通過(guò)距離判斷兩個(gè)網(wǎng)格模型是否碰撞
對(duì)于 幾何體A 的 每條射線R 的 每個(gè)交點(diǎn)I:
計(jì)算:起點(diǎn)到交點(diǎn)的距離 = 距離1
計(jì)算:起點(diǎn)到幾何體A中心的距離 = 距離2
如果 距離1 < 距離2:
說(shuō)明射線在到達(dá)自己中心之前就擊中了對(duì)方
判斷為"這條射線穿過(guò)了對(duì)方幾何體"
如果 至少有一條射線穿過(guò)對(duì)方幾何體:
判斷為"兩個(gè)幾何體發(fā)生碰撞"
邏輯解釋:
- 射線是從自己頂點(diǎn)指向自己中心的
- 如果射線在到達(dá)自己中心之前就擊中了對(duì)方,說(shuō)明對(duì)方在自己"內(nèi)部"或"前方"
- 如果射線先到達(dá)自己中心,說(shuō)明對(duì)方在自己"后方"或"外部"
二、具體實(shí)現(xiàn)步驟詳解
Step 1: 獲取幾何體所有頂點(diǎn)(世界坐標(biāo))
// 1.1 獲取幾何體的頂點(diǎn)位置數(shù)據(jù)
const geometry = mesh.geometry
const positionAttribute = geometry.attributes.position
const positions = positionAttribute.array // [x1, y1, z1, x2, y2, z2, ...]
// 1.2 獲取世界變換矩陣
const matrixWorld = mesh.matrixWorld
// 1.3 遍歷所有頂點(diǎn),轉(zhuǎn)換為世界坐標(biāo)
const worldVertices = []
for (let i = 0; i < positions.length; i += 3) {
// 創(chuàng)建局部坐標(biāo)頂點(diǎn)
const localVertex = new THREE.Vector3(
positions[i], // x
positions[i + 1], // y
positions[i + 2] // z
)
// 轉(zhuǎn)換為世界坐標(biāo)
const worldVertex = localVertex.clone()
worldVertex.applyMatrix4(matrixWorld)
worldVertices.push(worldVertex)
}
Step 2: 計(jì)算幾何體中心點(diǎn)(世界坐標(biāo))
// 方法1:直接使用mesh的世界位置(對(duì)于對(duì)稱幾何體)
const center = new THREE.Vector3()
mesh.getWorldPosition(center)
// 方法2:計(jì)算所有頂點(diǎn)的平均值(更精確)
let sum = new THREE.Vector3(0, 0, 0)
for (const vertex of worldVertices) {
sum.add(vertex)
}
const center = sum.divideScalar(worldVertices.length)
Step 3: 創(chuàng)建頂點(diǎn)到中心的射線
// 3.1 創(chuàng)建Raycaster實(shí)例
const raycaster = new THREE.Raycaster()
// 3.2 對(duì)于每個(gè)頂點(diǎn),創(chuàng)建從頂點(diǎn)指向中心的射線
for (const vertex of worldVertices) {
// 計(jì)算方向向量(從頂點(diǎn)指向中心)
const direction = center.clone().sub(vertex).normalize()
// 設(shè)置射線
raycaster.set(vertex, direction)
// 現(xiàn)在raycaster就代表了一條從頂點(diǎn)指向中心的射線
// 我們可以用它來(lái)檢測(cè)與其他幾何體的交點(diǎn)
}
Step 4: 射線交叉計(jì)算
// 4.1 檢測(cè)射線是否與另一個(gè)幾何體相交
const otherMesh = ... // 另一個(gè)幾何體
const intersects = raycaster.intersectObject(otherMesh)
// 4.2 分析交點(diǎn)信息
if (intersects.length > 0) {
const firstIntersect = intersects[0] // 最近的交點(diǎn)
// 交點(diǎn)信息包含:
// - point: 交點(diǎn)坐標(biāo)(Vector3)
// - distance: 從射線起點(diǎn)到交點(diǎn)的距離
// - object: 被擊中的物體
// - face: 被擊中的面
// - faceIndex: 面的索引
}
Step 5: 通過(guò)距離判斷是否碰撞
// 5.1 計(jì)算關(guān)鍵距離
const distanceToIntersect = vertex.distanceTo(firstIntersect.point) // 頂點(diǎn)到交點(diǎn)的距離
const distanceToCenter = vertex.distanceTo(center) // 頂點(diǎn)到中心的距離
// 5.2 判斷邏輯
if (distanceToIntersect < distanceToCenter) {
// 情況:射線在到達(dá)自己中心之前就擊中了對(duì)方
// 說(shuō)明對(duì)方幾何體在自己"前方"或"內(nèi)部"
// 這很可能表示兩個(gè)幾何體相交或碰撞
console.log('這條射線穿過(guò)了對(duì)方幾何體!')
// 記錄碰撞信息
collisions.push({
vertex: vertex,
intersectPoint: firstIntersect.point,
rayDirection: direction
})
} else {
// 情況:射線先到達(dá)自己中心,然后才可能擊中對(duì)方
// 說(shuō)明對(duì)方幾何體在自己"后方"或"外部"
// 這很可能表示兩個(gè)幾何體沒(méi)有碰撞
}
Step 6: 綜合判斷碰撞
// 6.1 統(tǒng)計(jì)所有穿過(guò)的射線
let collisionCount = 0
for (所有頂點(diǎn)射線) {
if (射線穿過(guò)了對(duì)方幾何體) {
collisionCount++
}
}
// 6.2 判斷是否發(fā)生碰撞
if (collisionCount > 0) {
console.log(`發(fā)生碰撞!共有 ${collisionCount} 條射線穿過(guò)對(duì)方幾何體`)
return true // 發(fā)生碰撞
} else {
console.log('沒(méi)有碰撞')
return false // 沒(méi)有碰撞
}
三、數(shù)學(xué)原理圖解
情況1:發(fā)生碰撞(射線穿過(guò)對(duì)方)
頂點(diǎn)
│
│ 距離1(到交點(diǎn))
│
▼ 交點(diǎn)(在對(duì)方幾何體上)
│
│ 距離2(從交點(diǎn)到中心)
│
▼ 中心
距離1 < 距離1+距離2(即距離1 < 頂點(diǎn)到中心的距離)
∴ 射線在到達(dá)中心之前擊中了對(duì)方 → 碰撞!情況2:沒(méi)有碰撞(射線先到中心)
頂點(diǎn)
│
│ 距離1(到中心)
│
▼ 中心
│
│ 距離2(從中心到交點(diǎn))
│
▼ 交點(diǎn)(在對(duì)方幾何體上)
距離1 < 距離1+距離2(但射線要先經(jīng)過(guò)中心)
∴ 射線先到達(dá)中心,然后才可能擊中對(duì)方 → 沒(méi)有碰撞!
四、性能優(yōu)化考慮
1. 頂點(diǎn)采樣優(yōu)化
// 不檢測(cè)所有頂點(diǎn),只采樣一部分
const sampleRate = 0.3 // 采樣率30%
for (let i = 0; i < worldVertices.length; i += Math.floor(1/sampleRate)) {
// 只檢測(cè)部分頂點(diǎn)
}
2. 分層次檢測(cè)
// 第一步:快速檢測(cè)(包圍盒)
const box1 = new THREE.Box3().setFromObject(mesh1)
const box2 = new THREE.Box3().setFromObject(mesh2)
if (!box1.intersectsBox(box2)) {
return false // 包圍盒不相交,肯定不碰撞,快速返回
}
// 第二步:精確檢測(cè)(頂點(diǎn)射線)
// 只有包圍盒相交時(shí),才進(jìn)行更耗時(shí)的頂點(diǎn)檢測(cè)
3. 空間分割優(yōu)化
// 使用八叉樹(shù)或BVH(包圍盒層次結(jié)構(gòu))加速 // 只檢測(cè)可能相交的幾何體
4. 距離閾值優(yōu)化
// 添加容差閾值
const threshold = 0.01 // 1厘米容差
if (distanceToIntersect < distanceToCenter + threshold) {
// 考慮為碰撞(避免浮點(diǎn)數(shù)精度問(wèn)題)
}
五、適用場(chǎng)景與限制
適用場(chǎng)景:
- 需要精確碰撞檢測(cè)(如物理模擬、游戲碰撞)
- 不規(guī)則幾何體碰撞(非AABB/球體等簡(jiǎn)單形狀)
- 需要知道碰撞位置(不僅僅是是否碰撞)
限制:
- 計(jì)算量大:頂點(diǎn)越多越慢
- 凹形幾何體問(wèn)題:射線可能從凹處"穿過(guò)"而不碰撞
- 薄物體問(wèn)題:對(duì)于非常薄的幾何體可能漏檢
六、偽代碼總結(jié)
function 頂點(diǎn)射線碰撞檢測(cè)(幾何體A, 幾何體B):
// Step 1: 獲取頂點(diǎn)
頂點(diǎn)數(shù)組A = 獲取世界坐標(biāo)頂點(diǎn)(幾何體A)
頂點(diǎn)數(shù)組B = 獲取世界坐標(biāo)頂點(diǎn)(幾何體B)
// Step 2: 計(jì)算中心
中心A = 計(jì)算世界中心(幾何體A)
中心B = 計(jì)算世界中心(幾何體B)
// Step 3-4: 檢測(cè)A的頂點(diǎn)射線
for 每個(gè)頂點(diǎn)V in 頂點(diǎn)數(shù)組A:
方向 = 歸一化(中心A - 頂點(diǎn)V)
射線 = 創(chuàng)建射線(頂點(diǎn)V, 方向)
交點(diǎn) = 射線.檢測(cè)相交(幾何體B)
if 交點(diǎn)存在:
if 距離(頂點(diǎn)V, 交點(diǎn)) < 距離(頂點(diǎn)V, 中心A):
碰撞計(jì)數(shù)器++
// Step 3-4: 檢測(cè)B的頂點(diǎn)射線
for 每個(gè)頂點(diǎn)V in 頂點(diǎn)數(shù)組B:
方向 = 歸一化(中心B - 頂點(diǎn)V)
射線 = 創(chuàng)建射線(頂點(diǎn)V, 方向)
交點(diǎn) = 射線.檢測(cè)相交(幾何體A)
if 交點(diǎn)存在:
if 距離(頂點(diǎn)V, 交點(diǎn)) < 距離(頂點(diǎn)V, 中心B):
碰撞計(jì)數(shù)器++
// Step 5: 判斷結(jié)果
if 碰撞計(jì)數(shù)器 > 0:
return true // 發(fā)生碰撞
else:
return false // 沒(méi)有碰撞
這個(gè)算法的主要思想是:如果一個(gè)幾何體的頂點(diǎn)發(fā)出的、指向自己中心的射線,在到達(dá)中心之前就擊中了另一個(gè)幾何體,那么這兩個(gè)幾何體很可能發(fā)生了碰撞或相交。
這個(gè)算法的主要思想是:如果一個(gè)幾何體的頂點(diǎn)發(fā)出的、指向自己中心的射線,在到達(dá)中心之前就擊中了另一個(gè)幾何體,那么這兩個(gè)幾何體很可能發(fā)生了碰撞或相交。
<template>
<div ref="containerRef"></div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import * as THREE from 'three'
const containerRef = ref(null)
let cube1, cube2
onMounted(() => {
const scene = new THREE.Scene()
scene.background = new THREE.Color(0x111111)
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.set(0, 5, 10)
const renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
containerRef.value.appendChild(renderer.domElement)
// 創(chuàng)建立方體1(可移動(dòng))
cube1 = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true })
)
cube1.position.set(-3, 0, 0)
scene.add(cube1)
// 創(chuàng)建立方體2(靜止)
cube2 = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshBasicMaterial({ color: 0x0000ff, wireframe: true })
)
cube2.position.set(0, 0, 0)
scene.add(cube2)
// 輔助線
scene.add(new THREE.AxesHelper(5))
// 核心算法:頂點(diǎn)射線碰撞檢測(cè)
const checkVertexRayCollision = () => {
console.log('=== 頂點(diǎn)射線碰撞檢測(cè) ===')
// 1. 獲取頂點(diǎn)(世界坐標(biāo))
const getVertices = (mesh) => {
const geometry = mesh.geometry
const positions = geometry.attributes.position.array
const vertices = []
const matrixWorld = mesh.matrixWorld
for (let i = 0; i < positions.length; i += 3) {
const vertex = new THREE.Vector3(
positions[i],
positions[i + 1],
positions[i + 2]
)
vertex.applyMatrix4(matrixWorld)
vertices.push(vertex)
}
return vertices
}
const vertices1 = getVertices(cube1)
const vertices2 = getVertices(cube2)
// 2. 獲取中心點(diǎn)(世界坐標(biāo))
const center1 = new THREE.Vector3()
cube1.getWorldPosition(center1)
const center2 = new THREE.Vector3()
cube2.getWorldPosition(center2)
console.log('立方體1頂點(diǎn)數(shù):', vertices1.length)
console.log('立方體2頂點(diǎn)數(shù):', vertices2.length)
console.log('立方體1中心:', center1)
console.log('立方體2中心:', center2)
// 3. 檢測(cè)立方體1的頂點(diǎn)射線
let collisionCount = 0
for (let i = 0; i < vertices1.length; i++) {
const vertex = vertices1[i]
const direction = new THREE.Vector3().subVectors(center1, vertex).normalize()
const raycaster = new THREE.Raycaster()
raycaster.set(vertex, direction)
const intersects = raycaster.intersectObject(cube2)
if (intersects.length > 0) {
const intersect = intersects[0]
const distanceToHit = vertex.distanceTo(intersect.point)
const distanceToCenter = vertex.distanceTo(center1)
if (distanceToHit < distanceToCenter) {
collisionCount++
console.log(`立方體1頂點(diǎn)${i}: 射線穿過(guò)了立方體2`)
}
}
}
// 4. 檢測(cè)立方體2的頂點(diǎn)射線
for (let i = 0; i < vertices2.length; i++) {
const vertex = vertices2[i]
const direction = new THREE.Vector3().subVectors(center2, vertex).normalize()
const raycaster = new THREE.Raycaster()
raycaster.set(vertex, direction)
const intersects = raycaster.intersectObject(cube1)
if (intersects.length > 0) {
const intersect = intersects[0]
const distanceToHit = vertex.distanceTo(intersect.point)
const distanceToCenter = vertex.distanceTo(center2)
if (distanceToHit < distanceToCenter) {
collisionCount++
console.log(`立方體2頂點(diǎn)${i}: 射線穿過(guò)了立方體1`)
}
}
}
// 5. 判斷結(jié)果
if (collisionCount > 0) {
console.log(`?? 發(fā)生碰撞!共有 ${collisionCount} 條射線相交`)
cube1.material.color.set(0xff00ff)
cube2.material.color.set(0xff00ff)
return true
} else {
console.log('? 沒(méi)有碰撞')
cube1.material.color.set(0xff0000)
cube2.material.color.set(0x0000ff)
return false
}
}
// 自動(dòng)移動(dòng)并檢測(cè)
let direction = 1
const animate = () => {
requestAnimationFrame(animate)
// 移動(dòng)立方體1
cube1.position.x += 0.01 * direction
// 邊界檢測(cè)
if (cube1.position.x > 2) direction = -1
if (cube1.position.x < -4) direction = 1
// 每10幀檢測(cè)一次
if (Math.floor(cube1.position.x * 10) % 10 === 0) {
checkVertexRayCollision()
}
renderer.render(scene, camera)
}
animate()
})
</script>
<style scoped>
div {
width: 100vw;
height: 100vh;
}
</style>
總結(jié)
到此這篇關(guān)于Three.js頂點(diǎn)射線碰撞檢測(cè)具體實(shí)現(xiàn)步驟的文章就介紹到這了,更多相關(guān)Three.js頂點(diǎn)射線碰撞檢測(cè)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript?Object.defineProperty與proxy代理模式的使用詳細(xì)分析
這篇文章主要介紹了JavaScript?Object.defineProperty與proxy代理模式的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2022-10-10
JS+DIV+CSS排版布局實(shí)現(xiàn)美觀的選項(xiàng)卡效果
這篇文章主要介紹了JS+DIV+CSS排版布局實(shí)現(xiàn)美觀的選項(xiàng)卡效果,通過(guò)簡(jiǎn)單的div+css布局結(jié)合JavaScript切換頁(yè)面樣式實(shí)現(xiàn)美觀的選項(xiàng)卡效果,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10
談?wù)凧avaScript數(shù)組常用方法總結(jié)
本篇文章主要介紹了談?wù)凧avaScript數(shù)組常用方法總結(jié),在JavaScript中,我們需要時(shí)常對(duì)數(shù)組進(jìn)行操作。一起跟隨小編過(guò)來(lái)看看吧2017-01-01
JavaScript實(shí)現(xiàn)英語(yǔ)單詞題庫(kù)
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)英語(yǔ)單詞題庫(kù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12
JavaScript中URL.createObjectURL和Blob實(shí)現(xiàn)保存文件
本文介紹了利用JavaScript中的Blob對(duì)象和URL.createObjectURL方法在瀏覽器端創(chuàng)建文件并實(shí)現(xiàn)下載功能,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-07-07
Layui 設(shè)置select下拉框自動(dòng)選中某項(xiàng)的方法
今天小編就為大家分享一篇Layui 設(shè)置select下拉框自動(dòng)選中某項(xiàng)的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-08-08
typescript中type和interface的區(qū)別有哪些
大家使用typescript總會(huì)使用到interface和type,所以下面這篇文章主要給大家介紹了關(guān)于typescript中type和interface區(qū)別的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-02-02

