背景: 在three中使用2:1的全景圖可以迅速成型VR項(xiàng)目上線, 但如果場景中需要標(biāo)注或者定位時(shí),由于沒有建模,很難迅速的找到三維坐標(biāo).尤其是當(dāng)需要定位的點(diǎn)很多時(shí),手工定位是一項(xiàng)十分痛苦的事情.
這個(gè)問題想了一周時(shí)間,極坐標(biāo)變換,高斯投影,geo定位等方法都試過,但是由于低維轉(zhuǎn)高維,缺少信息,均宣告失敗.周一例會(huì)的時(shí)候和三維圖形算法大神聊天,大神說可以嘗試著用數(shù)據(jù)擬合的方式來找找聯(lián)系,死馬當(dāng)活馬醫(yī),結(jié)果擬合分析后居然真找出了聯(lián)系,然后還意外發(fā)現(xiàn)了three是怎么拼接全景圖的(見附圖,坐標(biāo)點(diǎn)一致的地方three會(huì)進(jìn)行拼接,各象限的極限點(diǎn)已經(jīng)標(biāo)出,很容易寫出轉(zhuǎn)換函數(shù)),
今天花了一下午寫完轉(zhuǎn)換函數(shù),這樣就可以實(shí)現(xiàn)自動(dòng)標(biāo)記定位了,整體思路就是把全景圖按4:2:2的比例,切成8個(gè)象限,將uv與xy關(guān)聯(lián),再將z與uv的權(quán)重關(guān)系算出來,這樣就可以根據(jù)uv逆推出z,由于全景圖邊緣部分會(huì)存在畸變,所以要根據(jù)比例算出修正系數(shù),我司的系數(shù)大概是1.25,這個(gè)和相機(jī)有關(guān),完整代碼如下:
/**
* 八象限法---根據(jù)全景二維坐標(biāo)換算球面坐標(biāo)
* @param
* panoramaX: 二維全景x
* panoramaY: 二維全景y
* panoramaW: 全景圖寬度w
* panoramaH: 全景圖高度h
* R: 球體半徑
*/
export function coordinateTransformation(panoramaX, panoramaY, panoramaW = 7680, panoramaH = 3840, R = 7000) {
if (!panoramaX || !panoramaY) {
return false
}
// 默認(rèn)第一象限(0,0)=>(0,1,0)
let quadrantNum = 0
// 球坐標(biāo)法線
let normal = { x: 1, y: 1, z: 1 }
// 球坐標(biāo)
let sphereCoordinate = { x: 0, y: 0, z: 0 }
// 全景圖切分?jǐn)?shù),按4:2:2的比例,至少8個(gè)象限
const QUARTER_W_SEGMENT = 4
const HALF_H_SEGMENT = 2
// 象限單位
const QUARTER_W = Math.floor(panoramaW / QUARTER_W_SEGMENT)
const HALF_H = Math.floor(panoramaH / HALF_H_SEGMENT)
// 象限外偏移,用于確定坐標(biāo)象限
const QUADRANT_X = Math.floor(panoramaX / QUARTER_W)
const QUADRANT_Y = Math.floor(panoramaY / HALF_H)
// 象限內(nèi)偏移,用于換算球體坐標(biāo)
const OFFSET_X_2D = Math.floor(panoramaX % QUARTER_W)
const OFFSET_Y_2D = Math.floor(panoramaY % HALF_H)
// 在南半球
if (QUADRANT_Y) {
quadrantNum += QUARTER_W_SEGMENT
normal.y = -1
}
// 確定象限
if (QUADRANT_X) {
quadrantNum += QUADRANT_X
}
// 在左半球
if (quadrantNum % QUARTER_W_SEGMENT < 2) {
normal.x = -1
}
// 在前半球
if (quadrantNum % QUARTER_W_SEGMENT < 3 && quadrantNum % QUARTER_W_SEGMENT > 0) {
normal.z = -1
}
console.log('球體法線normal', normal);
// 對Z軸影響的權(quán)重
let POWER_Z = ""
// 南北半球坐標(biāo)轉(zhuǎn)換,通過normal.y消除Y軸偏移帶來的誤差
if (normal.y > 0) {
//北半球右旋
sphereCoordinate.y = (HALF_H - OFFSET_Y_2D) * 1.25 / HALF_H * normal.y
POWER_Z = OFFSET_Y_2D / OFFSET_X_2D < 1 ? "Y" : "X"
} else {
//南半球左旋
sphereCoordinate.y = (OFFSET_Y_2D / HALF_H) * 1.25 * normal.y
POWER_Z = OFFSET_Y_2D / OFFSET_X_2D > 1 ? "Y" : "X"
}
//沒有了Y軸,簡化為四象限
switch (quadrantNum % QUARTER_W_SEGMENT) {
case 0: {
sphereCoordinate.x = OFFSET_X_2D / QUARTER_W * normal.x
if (POWER_Z === "X") {
sphereCoordinate.z = (QUARTER_W - OFFSET_X_2D) / QUARTER_W * normal.z
} else if (POWER_Z === "Y") {
if (normal.y > 0) {
sphereCoordinate.z = OFFSET_Y_2D / HALF_H * normal.z
} else {
sphereCoordinate.z = (HALF_H - OFFSET_Y_2D) / HALF_H * normal.z
}
}
break
}
case 1: {
sphereCoordinate.x = (QUARTER_W - OFFSET_X_2D) / QUARTER_W * normal.x
if (POWER_Z === "X") {
sphereCoordinate.z = (OFFSET_X_2D) / QUARTER_W * normal.z
} else if (POWER_Z === "Y") {
if (normal.y > 0) {
sphereCoordinate.z = OFFSET_Y_2D / HALF_H * normal.z
} else {
sphereCoordinate.z = (HALF_H - OFFSET_Y_2D) / HALF_H * normal.z
}
}
break
}
case 2: {
sphereCoordinate.x = OFFSET_X_2D / QUARTER_W * normal.x
if (POWER_Z === "X") {
sphereCoordinate.z = (QUARTER_W - OFFSET_X_2D) / QUARTER_W * normal.z
} else if (POWER_Z === "Y") {
if (normal.y > 0) {
sphereCoordinate.z = OFFSET_Y_2D / HALF_H * normal.z
} else {
sphereCoordinate.z = (HALF_H - OFFSET_Y_2D) / HALF_H * normal.z
}
}
break
}
case 3: {
sphereCoordinate.x = (QUARTER_W - OFFSET_X_2D) / QUARTER_W * normal.x
if (POWER_Z === "X") {
sphereCoordinate.z = (OFFSET_X_2D) / QUARTER_W * normal.z
} else if (POWER_Z === "Y") {
if (normal.y > 0) {
sphereCoordinate.z = OFFSET_Y_2D / HALF_H * normal.z
} else {
sphereCoordinate.z = (HALF_H - OFFSET_Y_2D) / HALF_H * normal.z
}
}
break
}
}
sphereCoordinate = {
x: Math.ceil(sphereCoordinate.x * R),
y: Math.ceil(sphereCoordinate.y * R),
z: Math.ceil(sphereCoordinate.z * R)
}
console.log('球體坐標(biāo)sphereCoordinate', sphereCoordinate);
return sphereCoordinate
}