webglVectorRenderer.ts 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. import { type MapScene } from './mapRenderer'
  2. import { TrackLayer } from '../layer/trackLayer'
  3. import { GpsLayer } from '../layer/gpsLayer'
  4. function createShader(gl: any, type: number, source: string): any {
  5. const shader = gl.createShader(type)
  6. if (!shader) {
  7. throw new Error('WebGL shader 创建失败')
  8. }
  9. gl.shaderSource(shader, source)
  10. gl.compileShader(shader)
  11. if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
  12. const message = gl.getShaderInfoLog(shader) || 'unknown shader error'
  13. gl.deleteShader(shader)
  14. throw new Error(message)
  15. }
  16. return shader
  17. }
  18. function createProgram(gl: any, vertexSource: string, fragmentSource: string): any {
  19. const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource)
  20. const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource)
  21. const program = gl.createProgram()
  22. if (!program) {
  23. throw new Error('WebGL program 创建失败')
  24. }
  25. gl.attachShader(program, vertexShader)
  26. gl.attachShader(program, fragmentShader)
  27. gl.linkProgram(program)
  28. gl.deleteShader(vertexShader)
  29. gl.deleteShader(fragmentShader)
  30. if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
  31. const message = gl.getProgramInfoLog(program) || 'unknown program error'
  32. gl.deleteProgram(program)
  33. throw new Error(message)
  34. }
  35. return program
  36. }
  37. export class WebGLVectorRenderer {
  38. canvas: any
  39. gl: any
  40. dpr: number
  41. trackLayer: TrackLayer
  42. gpsLayer: GpsLayer
  43. program: any
  44. positionBuffer: any
  45. colorBuffer: any
  46. positionLocation: number
  47. colorLocation: number
  48. constructor(trackLayer: TrackLayer, gpsLayer: GpsLayer) {
  49. this.canvas = null
  50. this.gl = null
  51. this.dpr = 1
  52. this.trackLayer = trackLayer
  53. this.gpsLayer = gpsLayer
  54. this.program = null
  55. this.positionBuffer = null
  56. this.colorBuffer = null
  57. this.positionLocation = -1
  58. this.colorLocation = -1
  59. }
  60. attachCanvas(canvasNode: any, width: number, height: number, dpr: number): void {
  61. this.canvas = canvasNode
  62. this.dpr = dpr || 1
  63. canvasNode.width = Math.max(1, Math.floor(width * this.dpr))
  64. canvasNode.height = Math.max(1, Math.floor(height * this.dpr))
  65. this.attachContext(canvasNode.getContext('webgl') || canvasNode.getContext('experimental-webgl'), canvasNode)
  66. }
  67. attachContext(gl: any, canvasNode: any): void {
  68. if (!gl) {
  69. throw new Error('当前环境不支持 WebGL Vector Layer')
  70. }
  71. this.canvas = canvasNode
  72. this.gl = gl
  73. this.program = createProgram(
  74. gl,
  75. 'attribute vec2 a_position; attribute vec4 a_color; varying vec4 v_color; void main() { gl_Position = vec4(a_position, 0.0, 1.0); v_color = a_color; }',
  76. 'precision mediump float; varying vec4 v_color; void main() { gl_FragColor = v_color; }',
  77. )
  78. this.positionBuffer = gl.createBuffer()
  79. this.colorBuffer = gl.createBuffer()
  80. this.positionLocation = gl.getAttribLocation(this.program, 'a_position')
  81. this.colorLocation = gl.getAttribLocation(this.program, 'a_color')
  82. gl.viewport(0, 0, canvasNode.width, canvasNode.height)
  83. gl.enable(gl.BLEND)
  84. gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
  85. }
  86. destroy(): void {
  87. if (this.gl) {
  88. if (this.program) {
  89. this.gl.deleteProgram(this.program)
  90. }
  91. if (this.positionBuffer) {
  92. this.gl.deleteBuffer(this.positionBuffer)
  93. }
  94. if (this.colorBuffer) {
  95. this.gl.deleteBuffer(this.colorBuffer)
  96. }
  97. }
  98. this.program = null
  99. this.positionBuffer = null
  100. this.colorBuffer = null
  101. this.gl = null
  102. this.canvas = null
  103. }
  104. render(scene: MapScene, pulseFrame: number): void {
  105. if (!this.gl || !this.program || !this.positionBuffer || !this.colorBuffer || !this.canvas) {
  106. return
  107. }
  108. const gl = this.gl
  109. const trackPoints = this.trackLayer.projectPoints(scene)
  110. const gpsPoint = this.gpsLayer.projectPoint(scene)
  111. const positions: number[] = []
  112. const colors: number[] = []
  113. for (let index = 1; index < trackPoints.length; index += 1) {
  114. this.pushSegment(positions, colors, trackPoints[index - 1], trackPoints[index], 6, [0.09, 0.43, 0.36, 0.96], scene)
  115. }
  116. for (const point of trackPoints) {
  117. this.pushCircle(positions, colors, point.x, point.y, 10, [0.09, 0.43, 0.36, 1], scene)
  118. this.pushCircle(positions, colors, point.x, point.y, 6.5, [0.97, 0.98, 0.95, 1], scene)
  119. }
  120. this.pushCircle(positions, colors, gpsPoint.x, gpsPoint.y, this.gpsLayer.getPulseRadius(pulseFrame), [0.13, 0.62, 0.74, 0.22], scene)
  121. this.pushCircle(positions, colors, gpsPoint.x, gpsPoint.y, 13, [1, 1, 1, 0.95], scene)
  122. this.pushCircle(positions, colors, gpsPoint.x, gpsPoint.y, 9, [0.13, 0.63, 0.74, 1], scene)
  123. gl.viewport(0, 0, this.canvas.width, this.canvas.height)
  124. gl.useProgram(this.program)
  125. gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer)
  126. gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STREAM_DRAW)
  127. gl.enableVertexAttribArray(this.positionLocation)
  128. gl.vertexAttribPointer(this.positionLocation, 2, gl.FLOAT, false, 0, 0)
  129. gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer)
  130. gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STREAM_DRAW)
  131. gl.enableVertexAttribArray(this.colorLocation)
  132. gl.vertexAttribPointer(this.colorLocation, 4, gl.FLOAT, false, 0, 0)
  133. gl.drawArrays(gl.TRIANGLES, 0, positions.length / 2)
  134. }
  135. pushSegment(
  136. positions: number[],
  137. colors: number[],
  138. start: { x: number; y: number },
  139. end: { x: number; y: number },
  140. width: number,
  141. color: [number, number, number, number],
  142. scene: MapScene,
  143. ): void {
  144. const deltaX = end.x - start.x
  145. const deltaY = end.y - start.y
  146. const length = Math.sqrt(deltaX * deltaX + deltaY * deltaY)
  147. if (!length) {
  148. return
  149. }
  150. const normalX = -deltaY / length * (width / 2)
  151. const normalY = deltaX / length * (width / 2)
  152. const topLeft = this.toClip(start.x + normalX, start.y + normalY, scene)
  153. const topRight = this.toClip(end.x + normalX, end.y + normalY, scene)
  154. const bottomLeft = this.toClip(start.x - normalX, start.y - normalY, scene)
  155. const bottomRight = this.toClip(end.x - normalX, end.y - normalY, scene)
  156. this.pushTriangle(positions, colors, topLeft, topRight, bottomLeft, color)
  157. this.pushTriangle(positions, colors, bottomLeft, topRight, bottomRight, color)
  158. }
  159. pushCircle(
  160. positions: number[],
  161. colors: number[],
  162. centerX: number,
  163. centerY: number,
  164. radius: number,
  165. color: [number, number, number, number],
  166. scene: MapScene,
  167. ): void {
  168. const segments = 20
  169. const center = this.toClip(centerX, centerY, scene)
  170. for (let index = 0; index < segments; index += 1) {
  171. const startAngle = index / segments * Math.PI * 2
  172. const endAngle = (index + 1) / segments * Math.PI * 2
  173. const start = this.toClip(centerX + Math.cos(startAngle) * radius, centerY + Math.sin(startAngle) * radius, scene)
  174. const end = this.toClip(centerX + Math.cos(endAngle) * radius, centerY + Math.sin(endAngle) * radius, scene)
  175. this.pushTriangle(positions, colors, center, start, end, color)
  176. }
  177. }
  178. pushTriangle(
  179. positions: number[],
  180. colors: number[],
  181. first: { x: number; y: number },
  182. second: { x: number; y: number },
  183. third: { x: number; y: number },
  184. color: [number, number, number, number],
  185. ): void {
  186. positions.push(first.x, first.y, second.x, second.y, third.x, third.y)
  187. for (let index = 0; index < 3; index += 1) {
  188. colors.push(color[0], color[1], color[2], color[3])
  189. }
  190. }
  191. toClip(x: number, y: number, scene: MapScene): { x: number; y: number } {
  192. const previewScale = scene.previewScale || 1
  193. const originX = scene.previewOriginX || scene.viewportWidth / 2
  194. const originY = scene.previewOriginY || scene.viewportHeight / 2
  195. const scaledX = originX + (x - originX) * previewScale
  196. const scaledY = originY + (y - originY) * previewScale
  197. return {
  198. x: scaledX / scene.viewportWidth * 2 - 1,
  199. y: 1 - scaledY / scene.viewportHeight * 2,
  200. }
  201. }
  202. }