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) } }