PanoWireframe.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import { TextSprite } from "./TextSprite.js"
  2. import Vectors from "./Vectors.js"
  3. /**
  4. * 根据算法提供的全景图点数据,在3d中标记并围成一个矩形线框
  5. */
  6. export default class PanoWireframe extends THREE.Group {
  7. constructor(scene, sceneCode) {
  8. super()
  9. this.sceneCode = sceneCode
  10. scene.add(this)
  11. }
  12. clearAll() {
  13. // 清除已有线框
  14. this.traverse(obj => {
  15. if (obj.isMesh) {
  16. obj.geometry.dispose()
  17. obj.material.dispose()
  18. }
  19. })
  20. this.clear()
  21. }
  22. /**
  23. * 加载点位标记数据
  24. * @param {*} panoId
  25. */
  26. async load(panoId) {
  27. let data = await axios.post(
  28. "/service/scene/sceneMarkShape/getInfo",
  29. {
  30. num: this.sceneCode,
  31. imagePath: panoId + ".jpg"
  32. }
  33. )
  34. data = data.data
  35. if (!data.data || !data.success) return
  36. let { shapes, imageHeight, imageWidth } = data.data
  37. let labels = ""
  38. shapes.forEach(shape => {
  39. // 填充色和线框色
  40. // let { fill_color, line_color } = shape
  41. let { fill_color, color } = shape
  42. let line_color = [...color, 255]
  43. if (!fill_color) fill_color = [255, 255, 255, 0]
  44. if (!line_color) line_color = [255, 0, 0, 255]
  45. this.showSignalFrom2d(shape.category, shape.bbox, imageWidth, imageHeight, {
  46. fill: {
  47. color: new THREE.Color().setRGB(fill_color[0] / 255, fill_color[1] / 255, fill_color[2] / 255),
  48. opacity: fill_color[3] / 255,
  49. },
  50. line: {
  51. color: new THREE.Color().setRGB(line_color[0] / 255, line_color[1] / 255, line_color[2] / 255),
  52. opacity: line_color[3] / 255,
  53. },
  54. })
  55. labels += "<li>" + shape.label + '</li>'
  56. })
  57. return labels
  58. }
  59. /**
  60. * 根据坐标标记全景图
  61. */
  62. showSignalFrom2d(name, rect, w, h, options) {
  63. // 目前rect给的是矩形对角的两个点坐标,将它扩展成四个顶点
  64. let cornerArr = [
  65. [rect[0], rect[1]],
  66. [rect[2], rect[1]],
  67. [rect[2], rect[3]],
  68. [rect[0], rect[3]],
  69. ]
  70. // 2d坐标转3d坐标
  71. let transform2dTo3d = point => {
  72. // 计算方向向量
  73. let x = point[0],
  74. y = point[1]
  75. let yaw = (-x / w) * (Math.PI * 2)
  76. let pitch = Math.PI / 2 - (y / h) * Math.PI
  77. let dir = new THREE.Vector3()
  78. dir.copy(Vectors.RIGHT).applyAxisAngle(Vectors.BACK, pitch).applyAxisAngle(Vectors.UP, yaw)
  79. return dir
  80. }
  81. // // 计算矩形线框中点坐标(取x、y的平均值)
  82. // 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]
  83. // let centerVec = transform2dTo3d(center) // 计算中点3d坐标
  84. // 根据四个顶点,填充中间点
  85. let pointArr = []
  86. for (let i = 0; i < cornerArr.length; i++) {
  87. let corner1 = cornerArr[i]
  88. let corner2 = cornerArr[(i + 1) % cornerArr.length]
  89. pointArr.push(corner1)
  90. // 横向角度超过150度时,3d中边框的弧线已经不太明显,准确画出全景图线框
  91. if ((rect[2] - rect[0]) / w < 5 / 12 && i % 2 == 0) continue
  92. const vec = [corner2[0] - corner1[0], corner2[1] - corner1[1]]
  93. let length = Math.sqrt(vec[0] * vec[0] + vec[1] * vec[1])
  94. let num = length / 150
  95. for (let j = 1; j <= num; j++) {
  96. pointArr.push([corner1[0] + (vec[0] / num) * j, corner1[1] + (vec[1] / num) * j])
  97. }
  98. }
  99. let points = []
  100. pointArr.forEach(point => {
  101. let dir = transform2dTo3d(point)
  102. // points.push(dir.sub(centerVec)) // 计算其他点相对于中点的坐标,方便旋转平移等
  103. points.push(dir)
  104. })
  105. // 线框
  106. const lineGeometry = new THREE.BufferGeometry().setFromPoints(points)
  107. const lineMaterial = new THREE.LineBasicMaterial({ color: options.line.color, opacity: options.line.opacity, transparent: true, depthTest: false })
  108. const wireframe = new THREE.LineLoop(lineGeometry, lineMaterial)
  109. // wireframe.position.copy(centerVec) // 将中点作为线框坐标
  110. wireframe.renderOrder = 100
  111. // 填充颜色
  112. const fillGeometry = lineGeometry.clone().setIndex(new THREE.BufferAttribute(new Uint16Array([0, 1, 3, 2, 3, 1]), 1))
  113. const fillMaterial = new THREE.MeshBasicMaterial({ color: options.fill.color, opacity: options.fill.opacity, transparent: true, side: THREE.DoubleSide, depthTest: false })
  114. const plane = new THREE.Mesh(fillGeometry, fillMaterial)
  115. plane.renderOrder = wireframe.renderOrder - 1
  116. wireframe.add(plane)
  117. // 名称
  118. const textMesh = new TextSprite({
  119. text: name,
  120. backgroundColor: { r: options.line.color.r * 255, g: options.line.color.g * 255, b: options.line.color.b * 255, a: 0.4 },
  121. textColor: { r: 255, g: 255, b: 255, a: 1 },
  122. borderRadius: 15,
  123. renderOrder: wireframe.renderOrder + 1
  124. })
  125. textMesh.position.copy(points[0]) // 线框左上角
  126. textMesh.lookAt(0, 0, 0) // 看向相机
  127. textMesh.scale.set(0.2, 0.2, 0.2)
  128. let group = new THREE.Group()
  129. group.add(wireframe)
  130. group.add(textMesh)
  131. this.add(group)
  132. }
  133. }