import { TextSprite } from "./TextSprite.js"
import Vectors from "./Vectors.js"
/**
* 根据算法提供的全景图点数据,在3d中标记并围成一个矩形线框
*/
export default class PanoWireframe extends THREE.Group {
constructor(scene, sceneCode) {
super()
this.sceneCode = sceneCode
scene.add(this)
}
clearAll() {
// 清除已有线框
this.traverse(obj => {
if (obj.isMesh) {
obj.geometry.dispose()
obj.material.dispose()
}
})
this.clear()
}
/**
* 加载点位标记数据
* @param {*} panoId
*/
async load(panoId) {
let data = await axios.post(
"/service/scene/sceneMarkShape/getInfo",
{
num: this.sceneCode,
imagePath: panoId + ".jpg"
}
)
data = data.data
if (!data.data || !data.success) return
let { shapes, imageHeight, imageWidth } = data.data
let labels = ""
shapes.forEach(shape => {
// 填充色和线框色
// let { fill_color, line_color } = shape
let { fill_color, color } = shape
let line_color = [...color, 255]
if (!fill_color) fill_color = [255, 255, 255, 0]
if (!line_color) line_color = [255, 0, 0, 255]
this.showSignalFrom2d(shape.category, shape.bbox, imageWidth, imageHeight, {
fill: {
color: new THREE.Color().setRGB(fill_color[0] / 255, fill_color[1] / 255, fill_color[2] / 255),
opacity: fill_color[3] / 255,
},
line: {
color: new THREE.Color().setRGB(line_color[0] / 255, line_color[1] / 255, line_color[2] / 255),
opacity: line_color[3] / 255,
},
})
labels += "
" + shape.label + ''
})
return labels
}
/**
* 根据坐标标记全景图
*/
showSignalFrom2d(name, rect, w, h, options) {
// 目前rect给的是矩形对角的两个点坐标,将它扩展成四个顶点
let cornerArr = [
[rect[0], rect[1]],
[rect[2], rect[1]],
[rect[2], rect[3]],
[rect[0], rect[3]],
]
// 2d坐标转3d坐标
let transform2dTo3d = point => {
// 计算方向向量
let x = point[0],
y = point[1]
let yaw = (-x / w) * (Math.PI * 2)
let pitch = Math.PI / 2 - (y / h) * Math.PI
let dir = new THREE.Vector3()
dir.copy(Vectors.RIGHT).applyAxisAngle(Vectors.BACK, pitch).applyAxisAngle(Vectors.UP, yaw)
return dir
}
// // 计算矩形线框中点坐标(取x、y的平均值)
// let center = [cornerArr.reduce((a, b) => [a[0] + b[0], 0])[0] / cornerArr.length, cornerArr.reduce((a, b) => [0, a[1] + b[1]])[1] / cornerArr.length]
// let centerVec = transform2dTo3d(center) // 计算中点3d坐标
// 根据四个顶点,填充中间点
let pointArr = []
for (let i = 0; i < cornerArr.length; i++) {
let corner1 = cornerArr[i]
let corner2 = cornerArr[(i + 1) % cornerArr.length]
pointArr.push(corner1)
// 横向角度超过150度时,3d中边框的弧线已经不太明显,准确画出全景图线框
if ((rect[2] - rect[0]) / w < 5 / 12 && i % 2 == 0) continue
const vec = [corner2[0] - corner1[0], corner2[1] - corner1[1]]
let length = Math.sqrt(vec[0] * vec[0] + vec[1] * vec[1])
let num = length / 150
for (let j = 1; j <= num; j++) {
pointArr.push([corner1[0] + (vec[0] / num) * j, corner1[1] + (vec[1] / num) * j])
}
}
let points = []
pointArr.forEach(point => {
let dir = transform2dTo3d(point)
// points.push(dir.sub(centerVec)) // 计算其他点相对于中点的坐标,方便旋转平移等
points.push(dir)
})
// 线框
const lineGeometry = new THREE.BufferGeometry().setFromPoints(points)
const lineMaterial = new THREE.LineBasicMaterial({ color: options.line.color, opacity: options.line.opacity, transparent: true, depthTest: false })
const wireframe = new THREE.LineLoop(lineGeometry, lineMaterial)
// wireframe.position.copy(centerVec) // 将中点作为线框坐标
wireframe.renderOrder = 100
// 填充颜色
const fillGeometry = lineGeometry.clone().setIndex(new THREE.BufferAttribute(new Uint16Array([0, 1, 3, 2, 3, 1]), 1))
const fillMaterial = new THREE.MeshBasicMaterial({ color: options.fill.color, opacity: options.fill.opacity, transparent: true, side: THREE.DoubleSide, depthTest: false })
const plane = new THREE.Mesh(fillGeometry, fillMaterial)
plane.renderOrder = wireframe.renderOrder - 1
wireframe.add(plane)
// 名称
const textMesh = new TextSprite({
text: name,
backgroundColor: { r: options.line.color.r * 255, g: options.line.color.g * 255, b: options.line.color.b * 255, a: 0.4 },
textColor: { r: 255, g: 255, b: 255, a: 1 },
borderRadius: 15,
renderOrder: wireframe.renderOrder + 1
})
textMesh.position.copy(points[0]) // 线框左上角
textMesh.lookAt(0, 0, 0) // 看向相机
textMesh.scale.set(0.2, 0.2, 0.2)
let group = new THREE.Group()
group.add(wireframe)
group.add(textMesh)
this.add(group)
}
}